Comparar commits
326 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 392ffce8c7 | |||
| d7a238b5ec | |||
| a29015551c | |||
| 83e6a4a57e | |||
| 001183245e | |||
| 76e7161608 | |||
| 848ce7936f | |||
| d76d0a030f | |||
| e2af4c964d | |||
| 4527d8bd51 | |||
| a17a282b03 | |||
| 2c050522cd | |||
| c98e7cab4b | |||
| 4b49562949 | |||
| efb388ded4 | |||
| 08e9f19644 | |||
| 3b35a4b63b | |||
| b3b7f1ea97 | |||
| 075018d03d | |||
| 3b0748fe44 | |||
| 2b49a04227 | |||
| 097ee9f2a9 | |||
| e2821cbea8 | |||
| e9f9c6e777 | |||
| 95d4391680 | |||
| 04d4085759 | |||
| a75896c85b | |||
| 61675c2e77 | |||
| 718175eb34 | |||
| aefb84cdeb | |||
| 5aac826ec1 | |||
| aa86362a0a | |||
| 6104927cb6 | |||
| 74c889062b | |||
| 7b2539c4f4 | |||
| 851febd495 | |||
| a12abd7377 | |||
| 2f54cb4c22 | |||
| 81f7354fb5 | |||
| f8c20e1659 | |||
| 56394f6d8d | |||
| e0e6d96c84 | |||
| f41b558fe5 | |||
| 5507febe09 | |||
| b8ee3187ec | |||
| f74842cd4c | |||
| 2e61201f86 | |||
| 4108d572b8 | |||
| 423b133e75 | |||
| eb25d2e6a8 | |||
| eae06b62bc | |||
| 49aeef99b6 | |||
| 8e37d2ada6 | |||
| 7513fe9c69 | |||
| 34d3091fc9 | |||
| 992f520698 | |||
| b23e1d3d52 | |||
| dd53539799 | |||
| 93b1ce53c0 | |||
| e70d6d1f8f | |||
| c34db290e4 | |||
| 9d2b7875b9 | |||
| 1f025817f1 | |||
| 38d4e3a097 | |||
| c03d44da00 | |||
| 537c507efe | |||
| 0fbd08f1ab | |||
| 0f469a3269 | |||
| 9713bc8c02 | |||
| 7f0b2c54f3 | |||
| e9fd61a1ea | |||
| 33c95d1a58 | |||
| c5aa670569 | |||
| 533b11f991 | |||
| ebb68c3386 | |||
| c876876d5e | |||
| 7dde2fec55 | |||
| 49cf92c2a2 | |||
| 60319c30ac | |||
| 191e8ab240 | |||
| b4dec8ccbb | |||
| 7d28edc116 | |||
| 4d314f99ac | |||
| 1d146640e5 | |||
| b4c95d4fc9 | |||
| d9c258f27e | |||
| 4a56cc3693 | |||
| 8b149ff827 | |||
| f3145e671a | |||
| d3cb001d65 | |||
| 16da9b0506 | |||
| d7914d2c54 | |||
| 9849c62d80 | |||
| 3c630fb7f4 | |||
| 55f0b6a1f8 | |||
| 1b21fdda3b | |||
| c17d6ba487 | |||
| c6ff7e8934 | |||
| 1895e8143d | |||
| 5c74a02688 | |||
| 2f900e11ed | |||
| 1123ca4bc1 | |||
| 4973c2afc6 | |||
| 502e3ceb3b | |||
| 32b40fe1ad | |||
| 9a12b170f6 | |||
| 9f4b594bd0 | |||
| ea7a8c9670 | |||
| 113dac6e2b | |||
| 642427d015 | |||
| 26dad65c8d | |||
| 827bab90bf | |||
| 1a90f1c017 | |||
| 8172618bec | |||
| e12d779f00 | |||
| 1300955c13 | |||
| 7714c35829 | |||
| 3de070708f | |||
| 7aba7b0854 | |||
| f16c7247a9 | |||
| ef2ece3600 | |||
| 962b559638 | |||
| 9ab4707d43 | |||
| c3759feaa0 | |||
| 96418f3640 | |||
| a96c354f4f | |||
| 81462d9050 | |||
| 919bdca8f4 | |||
| 130e5cd3ec | |||
| 42301210c4 | |||
| 43a3c93d9b | |||
| cc98b88e28 | |||
| 212a192abf | |||
| ebc75b1ba8 | |||
| 7be6421989 | |||
| b4206d3202 | |||
| e8e0d5dd02 | |||
| be229d374e | |||
| 14b28a8a89 | |||
| c71bccd72c | |||
| cee9605fc5 | |||
| 7b5b942c47 | |||
| d5e0af2646 | |||
| a12e1d8b0e | |||
| d9d9b6800b | |||
| 8d7fd982e1 | |||
| 3e937e9811 | |||
| 4b993b99cd | |||
| 839f0b642c | |||
| f4d7586034 | |||
| ab23f624af | |||
| 86484e7426 | |||
| 734b6778b1 | |||
| 252682dd99 | |||
| 95e14486af | |||
| cf239ccac0 | |||
| dd041945d2 | |||
| 1cc4c52c7a | |||
| a2c894bcc9 | |||
| ebfd8ca4c3 | |||
| da938d8212 | |||
| 855f955dec | |||
| 1b8a8cf939 | |||
| 9147e86d96 | |||
| 6c748c2f87 | |||
| 4b6e0916c9 | |||
| fa5861a626 | |||
| f556890873 | |||
| 55f185f980 | |||
| d4474e1d4d | |||
| b64f6c5c27 | |||
| 616bb487f4 | |||
| 9f4fb29b58 | |||
| 1262d3fce9 | |||
| f4f74e1171 | |||
| f0c8c044c9 | |||
| c13b291c64 | |||
| 70191ea368 | |||
| d294bf4d05 | |||
| 10ca03f238 | |||
| 67f96e0fdd | |||
| 879ce3b30d | |||
| 3de96f1e7a | |||
| ee621bcace | |||
| c2118a8cb9 | |||
| 21f1579f73 | |||
| 4dce9d659f | |||
| ca4cfe358f | |||
| 7c8c4bd233 | |||
| 35cf96e15f | |||
| 7a9d408425 | |||
| 69300e0766 | |||
| 559f76e887 | |||
| 6eb88278ae | |||
| 662ddf9fd6 | |||
| 698f670350 | |||
| aebaf688a9 | |||
| 7e15a343f3 | |||
| f47eea1e6c | |||
| 5a944514f3 | |||
| fb955667f5 | |||
| 31aaa23b20 | |||
| 4d239b46e2 | |||
| 856b07197a | |||
| d07751605a | |||
| ddb09e98e8 | |||
| 75b40f603a | |||
| 5c21d71461 | |||
| 6c5f310cba | |||
| 909a951ff0 | |||
| 51a07bdb31 | |||
| 3dcdf0863e | |||
| 46b5731ac4 | |||
| 9be438d2f7 | |||
| bb8f4fb092 | |||
| 7b65a68450 | |||
| 7e6eecc6cf | |||
| 9d709ea152 | |||
| eadb3cf98c | |||
| 09e9c2f224 | |||
| dbae05605b | |||
| 3c5b721177 | |||
| b0e4ce7bb8 | |||
| 2bf7adef5c | |||
| 34fb03fbd3 | |||
| a8a28fea18 | |||
| 16cef29925 | |||
| 26564ee020 | |||
| 66a80ade30 | |||
| 685ec5d8d7 | |||
| 52139dc24e | |||
| 2357f46761 | |||
| 64fea21a38 | |||
| 71e73750e2 | |||
| 37b298fcba | |||
| 01ad97e7b0 | |||
| 988f4da6e1 | |||
| f48ee846e9 | |||
| a9c599abeb | |||
| c717d23901 | |||
| 6153375c76 | |||
| 850ecb23df | |||
| 8f03ff2952 | |||
| 0be166bb59 | |||
| cb2d24baca | |||
| 7609b08e86 | |||
| aeffa0e150 | |||
| 932d552ae7 | |||
| 78d8485243 | |||
| c3232a463c | |||
| 536beb0d40 | |||
| 1afe4f0cd9 | |||
| 334c4095bb | |||
| 986e5f9c7a | |||
| 6077cf2389 | |||
| f417e898f6 | |||
| 047d9525e7 | |||
| ca7da8a0da | |||
| 49b17023a5 | |||
| 693c4f8270 | |||
| df08c14aef | |||
| af903f6690 | |||
| cef25898bb | |||
| 0dea901368 | |||
| 9086171f45 | |||
| 9aec992693 | |||
| a788006d7c | |||
| 40512a4823 | |||
| 2e77f0ed7d | |||
| af5392b8ac | |||
| 471ac4976f | |||
| 16c892caa6 | |||
| 35b0e06260 | |||
| bbb7dfdd07 | |||
| 56a9316a28 | |||
| 7d676f51bd | |||
| 7ca8e31087 | |||
| 029d8b58db | |||
| 160a336b48 | |||
| 1ecd195e6e | |||
| 9f8a07fc27 | |||
| 1f3e3c698f | |||
| 1f9e33d995 | |||
| 32178541fe | |||
| 51e915c423 | |||
| 4ca00f7347 | |||
| b2d34d93ab | |||
| 78bb074c0d | |||
| 3a39c92ae4 | |||
| 883127f0d6 | |||
| 3b52cd018b | |||
| 6f34d0b346 | |||
| b35bd36ec7 | |||
| 8c037bf425 | |||
| ae5e07ddd6 | |||
| b16529bf9f | |||
| db0676cc51 | |||
| 2d1cb8b519 | |||
| 3a11c56438 | |||
| caf34d6a3a | |||
| a028dff6e9 | |||
| 0563ee4a97 | |||
| d466abdc64 | |||
| f4be899ae9 | |||
| 5403bc647a | |||
| 482eb6c0de | |||
| e02e4cd975 | |||
| ac90b13032 | |||
| 64aaf670ed | |||
| 4a764c2d9d | |||
| 0f0480b79f | |||
| df6feab346 | |||
| aef34c11b7 | |||
| 0292ba959f | |||
| 58275a2f0a | |||
| f0d3a06655 | |||
| 0ce67b307c | |||
| 598cdea22a | |||
| 32d23d6303 | |||
| 3279a575f6 | |||
| 16ffbd1920 | |||
| 9180060920 | |||
| fdae5fd89c | |||
| 8e218de3e7 | |||
| a6c89b75fc | |||
| 15cf475d04 |
@@ -88,3 +88,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/atom/apm.git
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ pairs:
|
||||
jc: Jerry Cheung; jerry
|
||||
bl: Brian Lopez; brian
|
||||
jp: Justin Palmer; justin
|
||||
gt: Garen Torikian; garen
|
||||
email:
|
||||
domain: github.com
|
||||
#global: true
|
||||
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
* 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
|
||||
* Fixed: End of line invisibles rendering incorrectly with the indent guide
|
||||
* Fixed: Updates not installing automatically on restart
|
||||
* Fixed: Wrap guide not displaying
|
||||
* Fixed: Error when saving with the markdown preview focused
|
||||
|
||||
* Fixed: Atom always running in dev mode
|
||||
* Fixed: Crash when running in dev mode without a path to the Atom source
|
||||
|
||||
* Fixed: Freeze when editing a RoR class
|
||||
* Added: meta-N to open a new untitled editor in the current window
|
||||
|
||||
|
||||
@@ -21,3 +21,8 @@
|
||||
* Add 3rd-party packages by submoduling in `vendor/packages/`
|
||||
* Commit messages are in the present tense
|
||||
* Files end with a newline
|
||||
* Class variables and methods should be in the following order:
|
||||
* Class variables (variables starting with a `@`)
|
||||
* Class methods (methods starting with a `@`)
|
||||
* Instance variables
|
||||
* Instance methods
|
||||
|
||||
+6
-2
@@ -63,8 +63,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()
|
||||
|
||||
@@ -91,6 +90,11 @@ task :clean do
|
||||
`rm -rf cef`
|
||||
end
|
||||
|
||||
desc "Delete cached cefodes"
|
||||
task "clean-cefode-cache" do
|
||||
`rm -rf /tmp/atom-cached-cefodes`
|
||||
end
|
||||
|
||||
desc "Run the specs"
|
||||
task :test => ["update-cef", "clone-default-bundles", "build"] do
|
||||
`pkill Atom`
|
||||
|
||||
Arquivo normal → Arquivo executável
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
+40
-35
@@ -1,36 +1,37 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
- `meta-shift-\` : open tree view with the current file selected
|
||||
- `meta-|` : open tree view with the current file selected
|
||||
- `ctrl-w v`, `ctrl-|` : split screen vertically
|
||||
- `ctrl-w s`, `ctrl--` : split screen horizontally
|
||||
- `meta-l` : go to line
|
||||
@@ -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,31 +111,31 @@ 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
|
||||
|
||||
You can split any editor pane horizontally or vertically by using `ctrl-shift-|` or
|
||||
You can split any editor pane horizontally or vertically by using `ctrl-\` or
|
||||
`ctrl-w v`. Once you have a split pane, you can move focus between them with
|
||||
`ctrl-tab` or `ctrl-w w`. To close a pane, close all tabs inside it.
|
||||
|
||||
### Folding
|
||||
|
||||
You can fold everything with `ctrl-shift-[` and unfold everything with
|
||||
`ctrl-shift-]`. Or, you can fold / unfold by a single level with `ctrl-[` and
|
||||
You can fold everything with `ctrl-{` and unfold everything with
|
||||
`ctrl-}`. Or, you can fold / unfold by a single level with `ctrl-[` and
|
||||
`ctrl-]`. The user interaction around folds is still a bit rough, but we're
|
||||
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
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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/
|
||||
@@ -278,13 +278,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:
|
||||
(NSApplication *)sender {
|
||||
for (NSWindow *window in [self windows]) {
|
||||
[window performClose:self];
|
||||
// The first call to terminate is canceled so that every window can be closed.
|
||||
// On AtomCefClient the OnBeforeClose method is called when a browser is
|
||||
// finished closing. Once all browsers have finished closing, AtomCefClient
|
||||
// calls terminate again, which will complete since no windows will be open.
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
bool atomWindowsAreOpen = NO;
|
||||
for (NSWindow *window in self.windows) {
|
||||
if ([window.windowController isKindOfClass:[AtomWindowController class]]) {
|
||||
atomWindowsAreOpen = YES;
|
||||
[window performClose:self];
|
||||
}
|
||||
}
|
||||
|
||||
if (atomWindowsAreOpen) {
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
else {
|
||||
CefQuitMessageLoop();
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
# pragma mark CefAppProtocol
|
||||
|
||||
@@ -171,11 +171,10 @@ bool AtomCefClient::OnKeyEvent(CefRefPtr<CefBrowser> browser,
|
||||
}
|
||||
|
||||
void AtomCefClient::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
|
||||
// REQUIRE_UI_THREAD(); // When uncommented this fails when app is terminated
|
||||
m_Browser = NULL;
|
||||
numberOfOpenBrowsers--;
|
||||
if (numberOfOpenBrowsers == 0) {
|
||||
CefQuitMessageLoop();
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ class AtomCefClient : public CefClient,
|
||||
void ShowSaveDialog(int replyId, CefRefPtr<CefBrowser> browser);
|
||||
CefRefPtr<CefListValue> CreateReplyDescriptor(int replyId, int callbackIndex);
|
||||
void Exit(int status);
|
||||
void Terminate();
|
||||
void Log(const char *message);
|
||||
void Show(CefRefPtr<CefBrowser> browser);
|
||||
void ToggleFullScreen(CefRefPtr<CefBrowser> browser);
|
||||
|
||||
@@ -159,6 +159,10 @@ void AtomCefClient::Exit(int status) {
|
||||
exit(status);
|
||||
}
|
||||
|
||||
void AtomCefClient::Terminate() {
|
||||
[NSApp terminate:NSApp];
|
||||
}
|
||||
|
||||
void AtomCefClient::Log(const char *message) {
|
||||
NSLog(@"%s", message);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class AtomCefClient;
|
||||
- (id)initBenchmarksThenExit:(BOOL)exitWhenDone;
|
||||
- (void)setPidToKillOnClose:(NSNumber *)pid;
|
||||
- (BOOL)isDevMode;
|
||||
- (BOOL)isDevFlagSpecified;
|
||||
|
||||
- (void)toggleDevTools;
|
||||
- (void)showDevTools;
|
||||
|
||||
@@ -34,10 +34,7 @@
|
||||
|
||||
AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication];
|
||||
|
||||
if (useBundleResourcePath) {
|
||||
_resourcePath = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
}
|
||||
else {
|
||||
if (!useBundleResourcePath) {
|
||||
_resourcePath = [[atomApplication.arguments objectForKey:@"resource-path"] stringByStandardizingPath];
|
||||
if (!_resourcePath) {
|
||||
NSString *defaultRepositoryPath = [@"~/github/atom" stringByStandardizingPath];
|
||||
@@ -53,6 +50,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_resourcePath) {
|
||||
_resourcePath = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
}
|
||||
|
||||
if ([self isDevMode]) {
|
||||
[self displayDevIcon];
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
- (id)initWithPath:(NSString *)path {
|
||||
_pathToOpen = [path retain];
|
||||
return [self initWithBootstrapScript:@"window-bootstrap" background:NO useBundleResourcePath:![self isDevMode]];
|
||||
return [self initWithBootstrapScript:@"window-bootstrap" background:NO useBundleResourcePath:![self isDevFlagSpecified]];
|
||||
}
|
||||
|
||||
- (id)initDevWithPath:(NSString *)path {
|
||||
@@ -119,7 +119,7 @@
|
||||
}
|
||||
|
||||
- (id)initInBackground {
|
||||
[self initWithBootstrapScript:@"window-bootstrap" background:YES useBundleResourcePath:![self isDevMode]];
|
||||
[self initWithBootstrapScript:@"window-bootstrap" background:YES useBundleResourcePath:![self isDevFlagSpecified]];
|
||||
[self.window setFrame:NSMakeRect(0, 0, 0, 0) display:NO];
|
||||
[self.window setExcludedFromWindowsMenu:YES];
|
||||
[self.window setCollectionBehavior:NSWindowCollectionBehaviorStationary];
|
||||
@@ -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 {
|
||||
@@ -283,6 +283,11 @@
|
||||
return ![_resourcePath isEqualToString:bundleResourcePath];
|
||||
}
|
||||
|
||||
- (BOOL)isDevFlagSpecified {
|
||||
AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication];
|
||||
return [atomApplication.arguments objectForKey:@"dev"] != nil;
|
||||
}
|
||||
|
||||
- (void)displayDevIcon {
|
||||
NSView *themeFrame = [self.window.contentView superview];
|
||||
NSButton *fullScreenButton = nil;
|
||||
|
||||
Arquivo binário não exibido.
+13
-10
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name" : "atom",
|
||||
"version" : "0.0.0",
|
||||
|
||||
"name": "atom",
|
||||
"version": "0.0.0",
|
||||
"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",
|
||||
@@ -16,18 +19,18 @@
|
||||
"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",
|
||||
"less": "git://github.com/nathansobo/less.js.git",
|
||||
"jqueryui-browser": "1.10.2-1"
|
||||
"roaster": "0.0.3",
|
||||
"jqueryui-browser": "1.10.2-1",
|
||||
"season": "0.7.0"
|
||||
},
|
||||
|
||||
"devDependencies" : {
|
||||
"biscotto" : "0.0.9"
|
||||
"devDependencies": {
|
||||
"biscotto": "0.0.11"
|
||||
},
|
||||
|
||||
"private": true,
|
||||
|
||||
"scripts": {
|
||||
"preinstall": "true"
|
||||
}
|
||||
|
||||
+5
-10
@@ -21,15 +21,10 @@ exit_unless_npm_exists() {
|
||||
exit_unless_xcode_exists
|
||||
exit_unless_npm_exists
|
||||
|
||||
npm install npm --silent
|
||||
|
||||
NODE_DIR="$HOME/.cefode-gyp"
|
||||
NODE_VERSION="0.10.3"
|
||||
NODE_URL="https://gh-contractor-zcbenz.s3.amazonaws.com/cefode2/dist"
|
||||
if [ ! -d "node_modules/node-gyp" ]; then
|
||||
./node_modules/.bin/npm install node-gyp --silent
|
||||
HOME="$NODE_DIR" ./node_modules/.bin/node-gyp install --target="$NODE_VERSION" --dist-url="$NODE_URL" --arch=ia32
|
||||
fi
|
||||
HOME="$NODE_DIR" ./node_modules/.bin/npm install --target="$NODE_VERSION" --arch=ia32 --silent
|
||||
git submodule --quiet sync
|
||||
git submodule --quiet update --recursive --init
|
||||
|
||||
(cd vendor/apm && npm install .)
|
||||
|
||||
npm install vendor/apm
|
||||
./node_modules/.bin/apm install
|
||||
|
||||
@@ -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))
|
||||
@@ -4,5 +4,5 @@ set -ex
|
||||
cd "$(dirname "$0")/../.."
|
||||
export PATH="/usr/local/Cellar/node/0.8.21/bin:/usr/local/bin:${PATH}"
|
||||
export VERSION=$VERSION
|
||||
rake clean
|
||||
rake clean clean-cefode-cache
|
||||
rake setup-codesigning create-xcode-project
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# From root of libgit2 repo:
|
||||
# mkdir build
|
||||
# cd build
|
||||
# cmake .. -DCMAKE_INSTALL_PREFIX=~/github/atom/git2 -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" -DCMAKE_BUILD_TYPE=Release -DTHREADSAFE=1 -DBUILD_CLAR=OFF
|
||||
|
||||
# cmake --build . --target install
|
||||
#
|
||||
# From root of atom repo:
|
||||
# mv git2/lib/libgit2.0.17.0.dylib git2/frameworks/libgit2.0.17.0.dylib
|
||||
# rm -fr git2/lib
|
||||
# script/update-libgit2
|
||||
|
||||
# update the id of the dylib
|
||||
install_name_tool -id @executable_path/libgit2.0.17.0.dylib git2/frameworks/libgit2.0.17.0.dylib
|
||||
|
||||
# Verify @executable_path is in the output of:
|
||||
otool -L git2/frameworks/libgit2.0.17.0.dylib
|
||||
@@ -350,10 +350,12 @@ describe "the `atom` global", ->
|
||||
results.pop()
|
||||
|
||||
errors = parseInt results.pop().match(/\d+/)
|
||||
expect(errors).toBe 0
|
||||
if errors > 0
|
||||
console.error results.join('\n')
|
||||
throw new Error("There were errors compiling documentation. See console for details.")
|
||||
|
||||
coverage = parseFloat results.pop().match(/.+?%/)
|
||||
expect(coverage).toBeGreaterThan 80
|
||||
expect(coverage).toBeGreaterThan 78
|
||||
|
||||
# stderr
|
||||
expect(docRunner.argsForCall[0][2]).toBe ''
|
||||
|
||||
@@ -39,9 +39,28 @@ describe "ConfigPanel", ->
|
||||
panel.floatInput.val('90.2').change()
|
||||
expect(config.get('foo.float')).toBe 90.2
|
||||
|
||||
panel.intInput.val('0').change()
|
||||
expect(config.get('foo.int')).toBe 0
|
||||
|
||||
panel.floatInput.val('0').change()
|
||||
expect(config.get('foo.float')).toBe 0
|
||||
|
||||
panel.stringInput.val('moo').change()
|
||||
expect(config.get('foo.string')).toBe 'moo'
|
||||
|
||||
panel.intInput.val('abcd').change()
|
||||
expect(config.get('foo.int')).toBe 'abcd'
|
||||
|
||||
panel.floatInput.val('defg').change()
|
||||
expect(config.get('foo.float')).toBe 'defg'
|
||||
|
||||
panel.intInput.val('').change()
|
||||
expect(config.get('foo.int')).toBe undefined
|
||||
panel.floatInput.val('').change()
|
||||
expect(config.get('foo.float')).toBe undefined
|
||||
panel.stringInput.val('').change()
|
||||
expect(config.get('foo.string')).toBe undefined
|
||||
|
||||
it "automatically binds named editors to their corresponding config keys", ->
|
||||
class TestPanel extends ConfigPanel
|
||||
@content: ->
|
||||
@@ -54,6 +73,7 @@ describe "ConfigPanel", ->
|
||||
config.set('foo.float', 1.1)
|
||||
config.set('foo.string', 'I think therefore I am.')
|
||||
panel = new TestPanel
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(panel.intEditor.getText()).toBe '1'
|
||||
expect(panel.floatEditor.getText()).toBe '1.1'
|
||||
expect(panel.stringEditor.getText()).toBe 'I think therefore I am.'
|
||||
@@ -73,10 +93,46 @@ describe "ConfigPanel", ->
|
||||
expect(config.get('foo.float')).toBe 3.3
|
||||
expect(config.get('foo.string')).toBe 'All limitations are self imposed.'
|
||||
|
||||
|
||||
panel.intEditor.setText('not an int')
|
||||
panel.floatEditor.setText('not a float')
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(config.get('foo.int')).toBe 'not an int'
|
||||
expect(config.get('foo.float')).toBe 'not a float'
|
||||
|
||||
panel.intEditor.setText('')
|
||||
panel.floatEditor.setText('')
|
||||
panel.stringEditor.setText('')
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(config.get('foo.int')).toBe undefined
|
||||
expect(config.get('foo.float')).toBe undefined
|
||||
expect(config.get('foo.string')).toBe undefined
|
||||
|
||||
panel.intEditor.setText('0')
|
||||
panel.floatEditor.setText('0')
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(config.get('foo.int')).toBe 0
|
||||
expect(config.get('foo.float')).toBe 0
|
||||
expect(config.get('foo.string')).toBe undefined
|
||||
|
||||
it "does not save the config value until it has been changed to a new value", ->
|
||||
class TestPanel extends ConfigPanel
|
||||
@content: ->
|
||||
@div =>
|
||||
@subview "fooInt", new Editor(mini: true, attributes: {id: 'foo.int', type: 'int'})
|
||||
|
||||
config.set('foo.int', 1)
|
||||
observeHandler = jasmine.createSpy("observeHandler")
|
||||
config.observe "foo.int", observeHandler
|
||||
observeHandler.reset()
|
||||
|
||||
testPanel = new TestPanel
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
|
||||
testPanel.fooInt.setText("1")
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
|
||||
testPanel.fooInt.setText("2")
|
||||
window.advanceClock(10000) # wait for contents-modified to be triggered
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
fs = require 'fs'
|
||||
fsUtils = require 'fs-utils'
|
||||
|
||||
describe "Config", ->
|
||||
describe ".get(keyPath) and .set(keyPath, value)", ->
|
||||
it "allows a key path's value to be read and written", ->
|
||||
describe ".get(keyPath)", ->
|
||||
it "allows a key path's value to be read", ->
|
||||
expect(config.set("foo.bar.baz", 42)).toBe 42
|
||||
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
|
||||
expect(config.get("foo.bar.baz")).toBe 42
|
||||
|
||||
it "updates observers and saves when a key path is set", ->
|
||||
observeHandler = jasmine.createSpy "observeHandler"
|
||||
config.observe "foo.bar.baz", observeHandler
|
||||
@@ -17,9 +30,53 @@ describe "Config", ->
|
||||
expect(config.save).toHaveBeenCalled()
|
||||
expect(observeHandler).toHaveBeenCalledWith 42
|
||||
|
||||
describe "when the value equals the default value", ->
|
||||
it "does not store the value", ->
|
||||
config.setDefaults("foo", same: 1, changes: 1)
|
||||
expect(config.settings.foo).toBeUndefined()
|
||||
config.set('foo.same', 1)
|
||||
config.set('foo.changes', 2)
|
||||
expect(config.settings.foo).toEqual {changes: 2}
|
||||
|
||||
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)
|
||||
expect(config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
|
||||
config.set('editor.preferredLineLength', -1234)
|
||||
expect(config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
|
||||
config.set('editor.preferredLineLength', 'abcd')
|
||||
expect(config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
|
||||
config.set('editor.preferredLineLength', null)
|
||||
expect(config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
|
||||
|
||||
describe ".save()", ->
|
||||
beforeEach ->
|
||||
spyOn(fsUtils, 'write')
|
||||
spyOn(fs, 'writeFileSync')
|
||||
jasmine.unspy config, 'save'
|
||||
|
||||
describe "when ~/.atom/config.json exists", ->
|
||||
@@ -30,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", ->
|
||||
@@ -45,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)", ->
|
||||
@@ -65,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
|
||||
|
||||
|
||||
@@ -138,7 +138,6 @@ describe "DisplayBuffer", ->
|
||||
expect(tokensText displayBuffer.lineForRow(4).tokens).toBe 'left = [], right = [];'
|
||||
expect(tokensText displayBuffer.lineForRow(5).tokens).toBe ' while(items.length > 0) {'
|
||||
expect(tokensText displayBuffer.lineForRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 15, screenDelta: 3, bufferDelta: 0)
|
||||
|
||||
describe "primitive folding", ->
|
||||
@@ -157,7 +156,6 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe fold
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferRows).toBe 4
|
||||
expect(line5.text).toBe '8'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 7, screenDelta: -3, bufferDelta: 0)
|
||||
@@ -167,7 +165,6 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBeUndefined()
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 4, screenDelta: 3, bufferDelta: 0)
|
||||
@@ -179,7 +176,6 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe fold
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 4, screenDelta: 0, bufferDelta: 0)
|
||||
@@ -193,7 +189,6 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBeUndefined()
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 4, screenDelta: 0, bufferDelta: 0)
|
||||
@@ -206,19 +201,15 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe outerFold
|
||||
expect(line4.text).toMatch /4-+/
|
||||
expect(line4.bufferRows).toEqual 5
|
||||
expect(line5.text).toMatch /9-+/
|
||||
|
||||
outerFold.destroy()
|
||||
|
||||
[line4, line5, line6, line7] = displayBuffer.linesForRows(4, 7)
|
||||
expect(line4.fold).toBeUndefined()
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
expect(line6.fold).toBe innerFold
|
||||
expect(line6.text).toBe '6'
|
||||
expect(line6.bufferRows).toEqual 2
|
||||
expect(line7.text).toBe '8'
|
||||
|
||||
it "allows the outer fold to start at the same location as the inner fold", ->
|
||||
@@ -228,17 +219,16 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe outerFold
|
||||
expect(line4.text).toMatch /4-+/
|
||||
expect(line4.bufferRows).toEqual 5
|
||||
expect(line5.text).toMatch /9-+/
|
||||
|
||||
describe "when creating a fold where one already exists", ->
|
||||
it "returns existing fold and does't create new fold", ->
|
||||
fold = displayBuffer.createFold(0,10)
|
||||
expect(displayBuffer.activeFolds[0].length).toBe 1
|
||||
expect(displayBuffer.findMarkers(class: 'fold').length).toBe 1
|
||||
|
||||
newFold = displayBuffer.createFold(0,10)
|
||||
expect(newFold).toBe fold
|
||||
expect(displayBuffer.activeFolds[0].length).toBe 1
|
||||
expect(displayBuffer.findMarkers(class: 'fold').length).toBe 1
|
||||
|
||||
describe "when a fold is created inside an existing folded region", ->
|
||||
it "creates/destroys the fold, but does not trigger change event", ->
|
||||
@@ -258,6 +248,12 @@ describe "DisplayBuffer", ->
|
||||
expect(line0.fold).toBe outerFold
|
||||
expect(line1.fold).toBeUndefined()
|
||||
|
||||
describe "when there is another display buffer pointing to the same buffer", ->
|
||||
it "does not create folds in the other display buffer", ->
|
||||
otherDisplayBuffer = new DisplayBuffer(buffer, { tabLength })
|
||||
displayBuffer.createFold(2, 4)
|
||||
expect(otherDisplayBuffer.foldsStartingAtBufferRow(2).length).toBe 0
|
||||
|
||||
describe "when the buffer changes", ->
|
||||
[fold1, fold2] = []
|
||||
beforeEach ->
|
||||
@@ -266,9 +262,10 @@ describe "DisplayBuffer", ->
|
||||
changeHandler.reset()
|
||||
|
||||
describe "when the old range surrounds a fold", ->
|
||||
it "removes the fold and replaces the selection with the new text", ->
|
||||
beforeEach ->
|
||||
buffer.change([[1, 0], [5, 1]], 'party!')
|
||||
|
||||
it "removes the fold and replaces the selection with the new text", ->
|
||||
expect(displayBuffer.lineForRow(0).text).toBe "0"
|
||||
expect(displayBuffer.lineForRow(1).text).toBe "party!"
|
||||
expect(displayBuffer.lineForRow(2).fold).toBe fold2
|
||||
@@ -276,6 +273,13 @@ describe "DisplayBuffer", ->
|
||||
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 3, screenDelta: -2, bufferDelta: -4)
|
||||
|
||||
describe "when the changes is subsequently undone", ->
|
||||
it "restores destroyed folds", ->
|
||||
buffer.undo()
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '2'
|
||||
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
||||
expect(displayBuffer.lineForRow(3).text).toBe '5'
|
||||
|
||||
describe "when the old range surrounds two nested folds", ->
|
||||
it "removes both folds and replaces the selection with the new text", ->
|
||||
displayBuffer.createFold(2, 9)
|
||||
@@ -294,8 +298,8 @@ describe "DisplayBuffer", ->
|
||||
buffer.delete([[1, 1], [2, 0]])
|
||||
buffer.insert([0, 1], "\nnew")
|
||||
|
||||
expect(fold1.startRow).toBe 2
|
||||
expect(fold1.endRow).toBe 4
|
||||
expect(fold1.getStartRow()).toBe 2
|
||||
expect(fold1.getEndRow()).toBe 4
|
||||
|
||||
describe "when the old range precedes lines with a fold", ->
|
||||
describe "when the new range precedes lines with a fold", ->
|
||||
@@ -322,15 +326,12 @@ describe "DisplayBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 1, screenDelta: 2, bufferDelta: 0)
|
||||
|
||||
describe "when the old range straddles the beginning of a fold", ->
|
||||
it "replaces lines in the portion of the range that precedes the fold and adjusts the end of the fold to encompass additional lines", ->
|
||||
it "destroys the fold", ->
|
||||
buffer.change([[1, 1], [3, 0]], "a\nb\nc\nd\n")
|
||||
|
||||
expect(fold1.startRow).toBe 2
|
||||
expect(fold1.endRow).toBe 6
|
||||
|
||||
expect(displayBuffer.lineForRow(1).text).toBe '1a'
|
||||
expect(displayBuffer.lineForRow(2).text).toBe 'b'
|
||||
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
||||
expect(displayBuffer.lineForRow(2).fold).toBeUndefined()
|
||||
expect(displayBuffer.lineForRow(3).text).toBe 'c'
|
||||
|
||||
describe "when the old range follows a fold", ->
|
||||
it "re-positions the screen ranges for the change event based on the preceding fold", ->
|
||||
@@ -348,13 +349,12 @@ describe "DisplayBuffer", ->
|
||||
describe "when the end of the new range precedes the end of the fold", ->
|
||||
it "updates the fold and ensures the change is present when the fold is destroyed", ->
|
||||
buffer.insert([3, 0], '\n')
|
||||
expect(fold1.startRow).toBe 2
|
||||
expect(fold1.endRow).toBe 5
|
||||
expect(fold1.getStartRow()).toBe 2
|
||||
expect(fold1.getEndRow()).toBe 5
|
||||
|
||||
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
||||
expect(displayBuffer.lineForRow(2).text).toBe "2"
|
||||
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
||||
expect(displayBuffer.lineForRow(2).bufferRows).toEqual 4
|
||||
expect(displayBuffer.lineForRow(3).text).toMatch "5"
|
||||
expect(displayBuffer.lineForRow(4).fold).toBe fold2
|
||||
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
|
||||
@@ -364,13 +364,12 @@ describe "DisplayBuffer", ->
|
||||
describe "when the end of the new range exceeds the end of the fold", ->
|
||||
it "expands the fold to contain all the inserted lines", ->
|
||||
buffer.change([[3, 0], [4, 0]], 'a\nb\nc\nd\n')
|
||||
expect(fold1.startRow).toBe 2
|
||||
expect(fold1.endRow).toBe 7
|
||||
expect(fold1.getStartRow()).toBe 2
|
||||
expect(fold1.getEndRow()).toBe 7
|
||||
|
||||
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
||||
expect(displayBuffer.lineForRow(2).text).toBe "2"
|
||||
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
||||
expect(displayBuffer.lineForRow(2).bufferRows).toEqual 6
|
||||
expect(displayBuffer.lineForRow(3).text).toMatch "5"
|
||||
expect(displayBuffer.lineForRow(4).fold).toBe fold2
|
||||
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
|
||||
@@ -379,12 +378,13 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when the old range straddles the end of the fold", ->
|
||||
describe "when the end of the new range precedes the end of the fold", ->
|
||||
it "shortens the fold so its end matches the end of the new range", ->
|
||||
it "destroys the fold", ->
|
||||
fold2.destroy()
|
||||
buffer.change([[3, 0], [6, 0]], 'a\n')
|
||||
|
||||
expect(fold1.startRow).toBe 2
|
||||
expect(fold1.endRow).toBe 4
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '2'
|
||||
expect(displayBuffer.lineForRow(2).fold).toBeUndefined()
|
||||
expect(displayBuffer.lineForRow(3).text).toBe 'a'
|
||||
expect(displayBuffer.lineForRow(4).text).toBe '6'
|
||||
|
||||
describe "when the old range is contained to a single line in-between two folds", ->
|
||||
it "re-renders the line with the placeholder and re-positions the second fold", ->
|
||||
@@ -401,7 +401,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "position translation", ->
|
||||
it "translates positions to account for folded lines and characters and the placeholder", ->
|
||||
displayBuffer.createFold(4, 7)
|
||||
fold = displayBuffer.createFold(4, 7)
|
||||
|
||||
# preceding fold: identity
|
||||
expect(displayBuffer.screenPositionForBufferPosition([3, 0])).toEqual [3, 0]
|
||||
@@ -425,23 +425,32 @@ describe "DisplayBuffer", ->
|
||||
expect(displayBuffer.bufferPositionForScreenPosition([-5, -5])).toEqual([0, 0])
|
||||
expect(displayBuffer.bufferPositionForScreenPosition([Infinity, Infinity])).toEqual([200, 0])
|
||||
|
||||
# after fold is destroyed
|
||||
fold.destroy()
|
||||
|
||||
expect(displayBuffer.screenPositionForBufferPosition([8, 0])).toEqual [8, 0]
|
||||
expect(displayBuffer.screenPositionForBufferPosition([11, 2])).toEqual [11, 2]
|
||||
|
||||
expect(displayBuffer.bufferPositionForScreenPosition([5, 0])).toEqual [5, 0]
|
||||
expect(displayBuffer.bufferPositionForScreenPosition([9, 2])).toEqual [9, 2]
|
||||
|
||||
describe ".destroyFoldsContainingBufferRow(row)", ->
|
||||
it "destroys all folds containing the given row", ->
|
||||
displayBuffer.createFold(2, 4)
|
||||
displayBuffer.createFold(2, 6)
|
||||
displayBuffer.createFold(7, 8)
|
||||
displayBuffer.createFold(1, 9)
|
||||
displayBuffer.createFold(11, 12)
|
||||
displayBuffer.createFold(2, 4)
|
||||
displayBuffer.createFold(2, 6)
|
||||
displayBuffer.createFold(7, 8)
|
||||
displayBuffer.createFold(1, 9)
|
||||
displayBuffer.createFold(11, 12)
|
||||
|
||||
expect(displayBuffer.lineForRow(1).text).toBe '1'
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '10'
|
||||
expect(displayBuffer.lineForRow(1).text).toBe '1'
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '10'
|
||||
|
||||
displayBuffer.destroyFoldsContainingBufferRow(2)
|
||||
expect(displayBuffer.lineForRow(1).text).toBe '1'
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '2'
|
||||
expect(displayBuffer.lineForRow(7).fold).toBeDefined()
|
||||
expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/
|
||||
expect(displayBuffer.lineForRow(10).fold).toBeDefined()
|
||||
displayBuffer.destroyFoldsContainingBufferRow(2)
|
||||
expect(displayBuffer.lineForRow(1).text).toBe '1'
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '2'
|
||||
expect(displayBuffer.lineForRow(7).fold).toBeDefined()
|
||||
expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/
|
||||
expect(displayBuffer.lineForRow(10).fold).toBeDefined()
|
||||
|
||||
describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
|
||||
beforeEach ->
|
||||
@@ -510,9 +519,11 @@ describe "DisplayBuffer", ->
|
||||
expect(displayBuffer.screenPositionForBufferPosition([0, 1])).toEqual [0, 2]
|
||||
expect(displayBuffer.bufferPositionForScreenPosition([0, 2])).toEqual [0, 1]
|
||||
|
||||
describe ".maxLineLength()", ->
|
||||
describe ".getMaxLineLength()", ->
|
||||
it "returns the length of the longest screen line", ->
|
||||
expect(displayBuffer.maxLineLength()).toBe 65
|
||||
expect(displayBuffer.getMaxLineLength()).toBe 65
|
||||
buffer.delete([[6, 0], [6, 65]])
|
||||
expect(displayBuffer.getMaxLineLength()).toBe 62
|
||||
|
||||
describe "markers", ->
|
||||
beforeEach ->
|
||||
@@ -522,44 +533,52 @@ describe "DisplayBuffer", ->
|
||||
it "allows markers to be created in terms of both screen and buffer coordinates", ->
|
||||
marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
marker2 = displayBuffer.markBufferRange([[8, 4], [8, 10]])
|
||||
expect(displayBuffer.getMarkerBufferRange(marker1)).toEqual [[8, 4], [8, 10]]
|
||||
expect(displayBuffer.getMarkerScreenRange(marker2)).toEqual [[5, 4], [5, 10]]
|
||||
expect(marker1.getBufferRange()).toEqual [[8, 4], [8, 10]]
|
||||
expect(marker2.getScreenRange()).toEqual [[5, 4], [5, 10]]
|
||||
|
||||
it "emits a 'marker-created' event on the DisplayBuffer whenever a marker is created", ->
|
||||
displayBuffer.on 'marker-created', markerCreatedHandler = jasmine.createSpy("markerCreatedHandler")
|
||||
|
||||
marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
expect(markerCreatedHandler).toHaveBeenCalledWith(marker1)
|
||||
markerCreatedHandler.reset()
|
||||
|
||||
marker2 = buffer.markRange([[5, 4], [5, 10]])
|
||||
expect(markerCreatedHandler).toHaveBeenCalledWith(displayBuffer.getMarker(marker2.id))
|
||||
|
||||
it "allows marker head and tail positions to be manipulated in both screen and buffer coordinates", ->
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])
|
||||
displayBuffer.setMarkerTailBufferPosition(marker, [5, 4])
|
||||
expect(displayBuffer.isMarkerReversed(marker)).toBeFalsy()
|
||||
expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]]
|
||||
|
||||
displayBuffer.setMarkerHeadBufferPosition(marker, [5, 4])
|
||||
displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])
|
||||
expect(displayBuffer.isMarkerReversed(marker)).toBeTruthy()
|
||||
expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]]
|
||||
marker.setHeadScreenPosition([5, 4])
|
||||
marker.setTailBufferPosition([5, 4])
|
||||
expect(marker.isReversed()).toBeFalsy()
|
||||
expect(marker.getBufferRange()).toEqual [[5, 4], [8, 4]]
|
||||
marker.setHeadBufferPosition([5, 4])
|
||||
marker.setTailScreenPosition([5, 4])
|
||||
expect(marker.isReversed()).toBeTruthy()
|
||||
expect(marker.getBufferRange()).toEqual [[5, 4], [8, 4]]
|
||||
|
||||
it "returns whether a position changed when it is assigned", ->
|
||||
marker = displayBuffer.markScreenRange([[0, 0], [0, 0]])
|
||||
expect(displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])).toBeTruthy()
|
||||
expect(displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])).toBeFalsy()
|
||||
expect(displayBuffer.setMarkerHeadBufferPosition(marker, [1, 0])).toBeTruthy()
|
||||
expect(displayBuffer.setMarkerHeadBufferPosition(marker, [1, 0])).toBeFalsy()
|
||||
expect(displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])).toBeTruthy()
|
||||
expect(displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])).toBeFalsy()
|
||||
expect(displayBuffer.setMarkerTailBufferPosition(marker, [1, 0])).toBeTruthy()
|
||||
expect(displayBuffer.setMarkerTailBufferPosition(marker, [1, 0])).toBeFalsy()
|
||||
expect(marker.setHeadScreenPosition([5, 4])).toBeTruthy()
|
||||
expect(marker.setHeadScreenPosition([5, 4])).toBeFalsy()
|
||||
expect(marker.setHeadBufferPosition([1, 0])).toBeTruthy()
|
||||
expect(marker.setHeadBufferPosition([1, 0])).toBeFalsy()
|
||||
expect(marker.setTailScreenPosition([5, 4])).toBeTruthy()
|
||||
expect(marker.setTailScreenPosition([5, 4])).toBeFalsy()
|
||||
expect(marker.setTailBufferPosition([1, 0])).toBeTruthy()
|
||||
expect(marker.setTailBufferPosition([1, 0])).toBeFalsy()
|
||||
|
||||
describe ".observeMarker(marker, callback)", ->
|
||||
[observeHandler, marker, subscription] = []
|
||||
describe "marker change events", ->
|
||||
[markerChangedHandler, marker] = []
|
||||
|
||||
beforeEach ->
|
||||
observeHandler = jasmine.createSpy("observeHandler")
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
subscription = displayBuffer.observeMarker(marker, observeHandler)
|
||||
marker.on 'changed', markerChangedHandler = jasmine.createSpy("markerChangedHandler")
|
||||
|
||||
it "calls the callback whenever the markers head's screen position changes in the buffer or on screen", ->
|
||||
displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20])
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
it "triggers the 'changed' event whenever the markers head's screen position changes in the buffer or on screen", ->
|
||||
marker.setHeadScreenPosition([8, 20])
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [5, 10]
|
||||
oldHeadBufferPosition: [8, 10]
|
||||
newHeadScreenPosition: [8, 20]
|
||||
@@ -571,11 +590,11 @@ describe "DisplayBuffer", ->
|
||||
bufferChanged: false
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
|
||||
buffer.insert([11, 0], '...')
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [8, 20]
|
||||
oldHeadBufferPosition: [11, 20]
|
||||
newHeadScreenPosition: [8, 23]
|
||||
@@ -587,11 +606,11 @@ describe "DisplayBuffer", ->
|
||||
bufferChanged: true
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
|
||||
displayBuffer.destroyFoldsContainingBufferRow(4)
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [8, 23]
|
||||
oldHeadBufferPosition: [11, 23]
|
||||
newHeadScreenPosition: [11, 23]
|
||||
@@ -603,11 +622,11 @@ describe "DisplayBuffer", ->
|
||||
bufferChanged: false
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [11, 23]
|
||||
oldHeadBufferPosition: [11, 23]
|
||||
newHeadScreenPosition: [8, 23]
|
||||
@@ -620,10 +639,10 @@ describe "DisplayBuffer", ->
|
||||
valid: true
|
||||
}
|
||||
|
||||
it "calls the callback whenever the marker tail's position changes in the buffer or on screen", ->
|
||||
displayBuffer.setMarkerTailScreenPosition(marker, [8, 20])
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
it "triggers the 'changed' event whenever the marker tail's position changes in the buffer or on screen", ->
|
||||
marker.setTailScreenPosition([8, 20])
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [5, 10]
|
||||
oldHeadBufferPosition: [8, 10]
|
||||
newHeadScreenPosition: [5, 10]
|
||||
@@ -635,11 +654,11 @@ describe "DisplayBuffer", ->
|
||||
bufferChanged: false
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
|
||||
buffer.insert([11, 0], '...')
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [5, 10]
|
||||
oldHeadBufferPosition: [8, 10]
|
||||
newHeadScreenPosition: [5, 10]
|
||||
@@ -652,10 +671,10 @@ describe "DisplayBuffer", ->
|
||||
valid: true
|
||||
}
|
||||
|
||||
it "calls the callback whenever the marker is invalidated or revalidated", ->
|
||||
it "triggers the 'changed' event whenever the marker is invalidated or revalidated", ->
|
||||
buffer.deleteRow(8)
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [5, 10]
|
||||
oldHeadBufferPosition: [8, 10]
|
||||
newHeadScreenPosition: [5, 10]
|
||||
@@ -668,11 +687,11 @@ describe "DisplayBuffer", ->
|
||||
valid: false
|
||||
}
|
||||
|
||||
observeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
buffer.undo()
|
||||
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadScreenPosition: [5, 10]
|
||||
oldHeadBufferPosition: [8, 10]
|
||||
newHeadScreenPosition: [5, 10]
|
||||
@@ -687,47 +706,122 @@ describe "DisplayBuffer", ->
|
||||
|
||||
it "does not call the callback for screen changes that don't change the position of the marker", ->
|
||||
displayBuffer.createFold(10, 11)
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "allows observation subscriptions to be cancelled", ->
|
||||
subscription.cancel()
|
||||
displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20])
|
||||
displayBuffer.destroyFoldsContainingBufferRow(4)
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
it "updates markers before emitting buffer change events, but does not notify their observers until the change event", ->
|
||||
marker2 = displayBuffer.markBufferRange([[8, 1], [8, 1]])
|
||||
marker2.on 'changed', marker2ChangedHandler = jasmine.createSpy("marker2ChangedHandler")
|
||||
displayBuffer.on 'changed', changeHandler = jasmine.createSpy("changeHandler").andCallFake -> onDisplayBufferChange()
|
||||
|
||||
it "updates the position of markers before emitting buffer change events, but does not notify their observers until the change event", ->
|
||||
changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
|
||||
# New change ----
|
||||
|
||||
onDisplayBufferChange = ->
|
||||
# calls change handler first
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).not.toHaveBeenCalled()
|
||||
# but still updates the markers
|
||||
expect(displayBuffer.getMarkerScreenRange(marker)).toEqual [[5, 7], [5, 13]]
|
||||
expect(displayBuffer.getMarkerHeadScreenPosition(marker)).toEqual [5, 13]
|
||||
expect(displayBuffer.getMarkerTailScreenPosition(marker)).toEqual [5, 7]
|
||||
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
|
||||
buffer.insert([8, 1], "...")
|
||||
expect(marker.getScreenRange()).toEqual [[5, 7], [5, 13]]
|
||||
expect(marker.getHeadScreenPosition()).toEqual [5, 13]
|
||||
expect(marker.getTailScreenPosition()).toEqual [5, 7]
|
||||
expect(marker2.isValid()).toBeFalsy()
|
||||
|
||||
buffer.change([[8, 0], [8, 2]], ".....")
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).toHaveBeenCalled()
|
||||
|
||||
# Undo change ----
|
||||
|
||||
changeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
marker2ChangedHandler.reset()
|
||||
|
||||
marker3 = displayBuffer.markBufferRange([[8, 1], [8, 2]])
|
||||
marker3.on 'changed', marker3ChangedHandler = jasmine.createSpy("marker3ChangedHandler")
|
||||
|
||||
onDisplayBufferChange = ->
|
||||
# calls change handler first
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).not.toHaveBeenCalled()
|
||||
expect(marker3ChangedHandler).not.toHaveBeenCalled()
|
||||
# but still updates the markers
|
||||
expect(marker.getScreenRange()).toEqual [[5, 4], [5, 10]]
|
||||
expect(marker.getHeadScreenPosition()).toEqual [5, 10]
|
||||
expect(marker.getTailScreenPosition()).toEqual [5, 4]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
|
||||
buffer.undo()
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).toHaveBeenCalled()
|
||||
expect(marker3ChangedHandler).toHaveBeenCalled()
|
||||
|
||||
# Redo change ----
|
||||
|
||||
changeHandler.reset()
|
||||
markerChangedHandler.reset()
|
||||
marker2ChangedHandler.reset()
|
||||
marker3ChangedHandler.reset()
|
||||
|
||||
onDisplayBufferChange = ->
|
||||
# calls change handler first
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).not.toHaveBeenCalled()
|
||||
expect(marker3ChangedHandler).not.toHaveBeenCalled()
|
||||
# but still updates the markers
|
||||
expect(marker.getScreenRange()).toEqual [[5, 7], [5, 13]]
|
||||
expect(marker.getHeadScreenPosition()).toEqual [5, 13]
|
||||
expect(marker.getTailScreenPosition()).toEqual [5, 7]
|
||||
expect(marker2.isValid()).toBeFalsy()
|
||||
expect(marker3.isValid()).toBeTruthy()
|
||||
|
||||
buffer.redo()
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).toHaveBeenCalled()
|
||||
expect(marker3ChangedHandler).toHaveBeenCalled()
|
||||
|
||||
it "updates the position of markers before emitting change events that aren't caused by a buffer change", ->
|
||||
changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
|
||||
displayBuffer.on 'changed', changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
|
||||
# calls change handler first
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
# but still updates the markers
|
||||
expect(displayBuffer.getMarkerScreenRange(marker)).toEqual [[8, 4], [8, 10]]
|
||||
expect(displayBuffer.getMarkerHeadScreenPosition(marker)).toEqual [8, 10]
|
||||
expect(displayBuffer.getMarkerTailScreenPosition(marker)).toEqual [8, 4]
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
expect(marker.getScreenRange()).toEqual [[8, 4], [8, 10]]
|
||||
expect(marker.getHeadScreenPosition()).toEqual [8, 10]
|
||||
expect(marker.getTailScreenPosition()).toEqual [8, 4]
|
||||
|
||||
displayBuffer.destroyFoldsContainingBufferRow(4)
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
|
||||
describe ".findMarkers(attributes)", ->
|
||||
it "allows the startBufferRow and endBufferRow to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[0, 0], [3, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[0, 0], [5, 0]], class: 'a')
|
||||
marker3 = displayBuffer.markBufferRange([[9, 0], [10, 0]], class: 'b')
|
||||
|
||||
expect(displayBuffer.findMarkers(class: 'a', startBufferRow: 0)).toEqual [marker2, marker1]
|
||||
expect(displayBuffer.findMarkers(class: 'a', startBufferRow: 0, endBufferRow: 3)).toEqual [marker1]
|
||||
expect(displayBuffer.findMarkers(endBufferRow: 10)).toEqual [marker3]
|
||||
|
||||
describe "marker destruction", ->
|
||||
it "allows markers to be destroyed", ->
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
displayBuffer.destroyMarker(marker)
|
||||
expect(displayBuffer.getMarkerBufferRange(marker)).toBeUndefined()
|
||||
marker.destroy()
|
||||
expect(marker.isValid()).toBeFalsy()
|
||||
expect(displayBuffer.getMarker(marker.id)).toBeUndefined()
|
||||
|
||||
it "emits 'destroyed' events when markers are destroyed", ->
|
||||
destroyedHandler = jasmine.createSpy("destroyedHandler")
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
marker.on 'destroyed', destroyedHandler
|
||||
marker.destroy()
|
||||
expect(destroyedHandler).toHaveBeenCalled()
|
||||
destroyedHandler.reset()
|
||||
|
||||
marker2 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
marker2.on 'destroyed', destroyedHandler
|
||||
buffer.getMarker(marker2.id).destroy()
|
||||
expect(destroyedHandler).toHaveBeenCalled()
|
||||
|
||||
+251
-299
@@ -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
|
||||
|
||||
@@ -744,17 +744,18 @@ describe "EditSession", ->
|
||||
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
|
||||
|
||||
describe ".selectMarker(marker)", ->
|
||||
describe "when the marker exists", ->
|
||||
it "selects the marker's range and returns true", ->
|
||||
describe "if the marker is valid", ->
|
||||
it "selects the marker's range and returns the selected range", ->
|
||||
marker = editSession.markBufferRange([[0, 1], [3, 3]])
|
||||
expect(editSession.selectMarker(marker)).toBeTruthy()
|
||||
expect(editSession.selectMarker(marker)).toEqual [[0, 1], [3, 3]]
|
||||
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 3]]
|
||||
|
||||
describe "when the marker does not exist", ->
|
||||
it "does not select the marker's range and returns false", ->
|
||||
rangeBefore = editSession.getSelectedBufferRange()
|
||||
expect(editSession.selectMarker('bogus')).toBeFalsy()
|
||||
expect(editSession.getSelectedBufferRange()).toEqual rangeBefore
|
||||
describe "if the marker is invalid", ->
|
||||
it "does not change the selection and returns a falsy value", ->
|
||||
marker = editSession.markBufferRange([[0, 1], [3, 3]])
|
||||
marker.destroy()
|
||||
expect(editSession.selectMarker(marker)).toBeFalsy()
|
||||
expect(editSession.getSelectedBufferRange()).toEqual [[0, 0], [0, 0]]
|
||||
|
||||
describe ".addSelectionBelow()", ->
|
||||
describe "when the selection is non-empty", ->
|
||||
@@ -1010,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", ->
|
||||
@@ -1346,14 +1159,14 @@ describe "EditSession", ->
|
||||
editSession.backspace()
|
||||
|
||||
describe "when the cursor is on the first column of a line below a fold", ->
|
||||
it "absorbs the current line into the fold", ->
|
||||
it "deletes the folded lines", ->
|
||||
editSession.setCursorScreenPosition([4,0])
|
||||
editSession.foldCurrentRow()
|
||||
editSession.setCursorScreenPosition([5,0])
|
||||
editSession.backspace()
|
||||
|
||||
expect(buffer.lineForRow(7)).toBe " } return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(buffer.lineForRow(8)).toBe " };"
|
||||
expect(buffer.lineForRow(4)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(buffer.lineForRow(4).fold).toBeUndefined()
|
||||
|
||||
describe "when the cursor is in the middle of a line below a fold", ->
|
||||
it "backspaces as normal", ->
|
||||
@@ -1370,6 +1183,7 @@ describe "EditSession", ->
|
||||
editSession.setCursorBufferPosition([3, 0])
|
||||
editSession.foldCurrentRow()
|
||||
editSession.backspace()
|
||||
|
||||
expect(buffer.lineForRow(1)).toBe ""
|
||||
expect(buffer.lineForRow(2)).toBe " return sort(Array.apply(this, arguments));"
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [1, 0]
|
||||
@@ -1433,14 +1247,13 @@ describe "EditSession", ->
|
||||
expect(editSession.buffer.lineForRow(0)).toBe 'var qsort = function () {'
|
||||
|
||||
describe "when the selection ends on a folded line", ->
|
||||
it "destroys the fold", ->
|
||||
it "preserves the fold", ->
|
||||
editSession.setSelectedBufferRange([[3,0], [4,0]])
|
||||
editSession.foldBufferRow(4)
|
||||
editSession.backspace()
|
||||
|
||||
expect(buffer.lineForRow(3)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(buffer.lineForRow(4)).toBe " };"
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [3, 0]
|
||||
expect(buffer.lineForRow(3)).toBe " while(items.length > 0) {"
|
||||
expect(editSession.lineForScreenRow(3).fold).toBeDefined()
|
||||
|
||||
describe "when there are multiple selections", ->
|
||||
it "removes all selected text", ->
|
||||
@@ -1778,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", ->
|
||||
@@ -1960,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) {"
|
||||
@@ -2037,15 +1839,28 @@ describe "EditSession", ->
|
||||
editSession.redo()
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
|
||||
|
||||
it "restores selected ranges even when the change occurred in another edit session", ->
|
||||
otherEditSession = project.buildEditSession(editSession.getPath())
|
||||
otherEditSession.setSelectedBufferRange([[2, 2], [3, 3]])
|
||||
otherEditSession.delete()
|
||||
it "restores folds after undo and redo", ->
|
||||
editSession.foldBufferRow(1)
|
||||
editSession.setSelectedBufferRange([[1, 0], [10, Infinity]], preserveFolds: true)
|
||||
expect(editSession.isFoldedAtBufferRow(1)).toBeTruthy()
|
||||
|
||||
editSession.insertText """
|
||||
\ // testing
|
||||
function foo() {
|
||||
return 1 + 2;
|
||||
}
|
||||
"""
|
||||
expect(editSession.isFoldedAtBufferRow(1)).toBeFalsy()
|
||||
editSession.foldBufferRow(2)
|
||||
|
||||
editSession.undo()
|
||||
expect(editSession.isFoldedAtBufferRow(1)).toBeTruthy()
|
||||
expect(editSession.isFoldedAtBufferRow(9)).toBeTruthy()
|
||||
expect(editSession.isFoldedAtBufferRow(10)).toBeFalsy()
|
||||
|
||||
expect(editSession.getSelectedBufferRange()).toEqual [[2, 2], [3, 3]]
|
||||
expect(otherEditSession.getSelectedBufferRange()).toEqual [[3, 3], [3, 3]]
|
||||
editSession.redo()
|
||||
expect(editSession.isFoldedAtBufferRow(1)).toBeFalsy()
|
||||
expect(editSession.isFoldedAtBufferRow(2)).toBeTruthy()
|
||||
|
||||
describe ".transact([fn])", ->
|
||||
describe "when called without a function", ->
|
||||
@@ -2125,30 +1940,30 @@ describe "EditSession", ->
|
||||
editSession.foldAll()
|
||||
|
||||
fold1 = editSession.lineForScreenRow(0).fold
|
||||
expect([fold1.startRow, fold1.endRow]).toEqual [0, 12]
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editSession.lineForScreenRow(1).fold
|
||||
expect([fold2.startRow, fold2.endRow]).toEqual [1, 9]
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
|
||||
fold2.destroy()
|
||||
|
||||
fold3 = editSession.lineForScreenRow(4).fold
|
||||
expect([fold3.startRow, fold3.endRow]).toEqual [4, 7]
|
||||
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]
|
||||
|
||||
describe ".foldBufferRow(bufferRow)", ->
|
||||
describe "when bufferRow can be folded", ->
|
||||
it "creates a fold based on the syntactic region starting at the given row", ->
|
||||
editSession.foldBufferRow(1)
|
||||
fold = editSession.lineForScreenRow(1).fold
|
||||
expect(fold.startRow).toBe 1
|
||||
expect(fold.endRow).toBe 9
|
||||
expect(fold.getStartRow()).toBe 1
|
||||
expect(fold.getEndRow()).toBe 9
|
||||
|
||||
describe "when bufferRow can't be folded", ->
|
||||
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
|
||||
editSession.foldBufferRow(8)
|
||||
fold = editSession.lineForScreenRow(1).fold
|
||||
expect(fold.startRow).toBe 1
|
||||
expect(fold.endRow).toBe 9
|
||||
expect(fold.getStartRow()).toBe 1
|
||||
expect(fold.getEndRow()).toBe 9
|
||||
|
||||
describe "when the bufferRow is already folded", ->
|
||||
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
|
||||
@@ -2164,16 +1979,16 @@ describe "EditSession", ->
|
||||
buffer.insert([1,0], " //this is a comment\n // and\n //more docs\n\n//second comment")
|
||||
editSession.foldBufferRow(1)
|
||||
fold = editSession.lineForScreenRow(1).fold
|
||||
expect(fold.startRow).toBe 1
|
||||
expect(fold.endRow).toBe 3
|
||||
expect(fold.getStartRow()).toBe 1
|
||||
expect(fold.getEndRow()).toBe 3
|
||||
|
||||
describe "when the bufferRow is a single-line comment", ->
|
||||
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
|
||||
buffer.insert([1,0], " //this is a single line comment\n")
|
||||
editSession.foldBufferRow(1)
|
||||
fold = editSession.lineForScreenRow(0).fold
|
||||
expect(fold.startRow).toBe 0
|
||||
expect(fold.endRow).toBe 13
|
||||
expect(fold.getStartRow()).toBe 0
|
||||
expect(fold.getEndRow()).toBe 13
|
||||
|
||||
describe ".unfoldBufferRow(bufferRow)", ->
|
||||
describe "when bufferRow can be unfolded", ->
|
||||
@@ -2334,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)", ->
|
||||
@@ -2369,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
|
||||
|
||||
@@ -2378,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", ->
|
||||
@@ -2503,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()
|
||||
@@ -320,8 +320,9 @@ describe "Editor", ->
|
||||
expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight
|
||||
|
||||
newEditor = new Editor(editor.activeEditSession.copy())
|
||||
editor.remove()
|
||||
newEditor.attachToDom()
|
||||
expect(editor.css('font-size')).toBe '30px'
|
||||
expect(newEditor.css('font-size')).toBe '30px'
|
||||
|
||||
it "updates the position and size of selection regions", ->
|
||||
config.set("editor.fontSize", 10)
|
||||
@@ -371,7 +372,6 @@ describe "Editor", ->
|
||||
describe "single-click", ->
|
||||
it "re-positions the cursor to the clicked row / column", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
||||
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 10])
|
||||
expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 10)
|
||||
|
||||
@@ -542,20 +542,23 @@ describe "Editor", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
||||
|
||||
it "selects and scrolls if the mouse is dragged outside of the editor itself", ->
|
||||
intervalFns = []
|
||||
editor.vScrollMargin = 0
|
||||
editor.attachToDom(heightInLines: 5)
|
||||
editor.scrollToBottom()
|
||||
|
||||
spyOn(window, 'setInterval').andCallFake (fn) -> intervalFns.push(fn)
|
||||
spyOn(window, 'setInterval').andCallFake ->
|
||||
|
||||
# start
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [12, 0])
|
||||
originalScrollTop = editor.scrollTop()
|
||||
|
||||
# moving changes selection
|
||||
$(document).trigger mousemoveEvent(editor: editor, pageX: 0, pageY: -15)
|
||||
expect(editor.scrollTop()).toBe 4 * editor.lineHeight
|
||||
$(document).trigger mousemoveEvent(editor: editor, pageX: 0, pageY: -1)
|
||||
expect(editor.scrollTop()).toBe originalScrollTop - editor.lineHeight
|
||||
|
||||
# if cursor stays off screen, we keep moving / scrolling up
|
||||
fn() for fn in intervalFns
|
||||
# every mouse move selects more text
|
||||
for x in [0..10]
|
||||
$(document).trigger mousemoveEvent(editor: editor, pageX: 0, pageY: -1)
|
||||
|
||||
expect(editor.scrollTop()).toBe 0
|
||||
|
||||
@@ -1211,7 +1214,7 @@ describe "Editor", ->
|
||||
|
||||
describe "when scrolling more than the editors height", ->
|
||||
it "removes lines that are offscreen and not in range of the overdraw and builds lines that become visible", ->
|
||||
editor.scrollTop(editor.scrollView.prop('scrollHeight') - editor.scrollView.height())
|
||||
editor.scrollTop(editor.layerHeight - editor.scrollView.height())
|
||||
expect(editor.renderedLines.find('.line').length).toBe 8
|
||||
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(5)
|
||||
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(12)
|
||||
@@ -1285,7 +1288,7 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
||||
|
||||
it "increases the width of the rendered lines element to be either the width of the longest line or the width of the scrollView (whichever is longer)", ->
|
||||
maxLineLength = editor.maxScreenLineLength()
|
||||
maxLineLength = editor.getMaxScreenLineLength()
|
||||
setEditorWidthInChars(editor, maxLineLength)
|
||||
widthBefore = editor.renderedLines.width()
|
||||
expect(widthBefore).toBe editor.scrollView.width() + 20
|
||||
@@ -1297,7 +1300,7 @@ describe "Editor", ->
|
||||
editor.attachToDom(heightInLines: 5)
|
||||
|
||||
it "sets the rendered screen line's width to either the max line length or the scollView's width (whichever is greater)", ->
|
||||
maxLineLength = editor.maxScreenLineLength()
|
||||
maxLineLength = editor.getMaxScreenLineLength()
|
||||
setEditorWidthInChars(editor, maxLineLength)
|
||||
buffer.change([[12,0], [12,0]], [1..maxLineLength*2].join(''))
|
||||
expect(editor.renderedLines.width()).toBeGreaterThan editor.scrollView.width()
|
||||
@@ -1385,9 +1388,18 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.find('.line').length).toBe 1
|
||||
expect(editor.renderedLines.find('.line').text()).toBe buffer.lineForRow(0)
|
||||
|
||||
describe "when folding leaves fewer screen lines than the first rendered screen line (regression)", ->
|
||||
it "clears all screen lines and does not throw any exceptions", ->
|
||||
editor.lineOverdraw = 1
|
||||
editor.attachToDom(heightInLines: 5)
|
||||
editor.scrollToBottom()
|
||||
editor.activeEditSession.foldBufferRow(0)
|
||||
expect(editor.renderedLines.find('.line').length).toBe 1
|
||||
expect(editor.renderedLines.find('.line').text()).toBe buffer.lineForRow(0)
|
||||
|
||||
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
|
||||
@@ -1548,6 +1560,31 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.find('.line:eq(10) .indent-guide').length).toBe 2
|
||||
expect(editor.renderedLines.find('.line:eq(10) .indent-guide').text()).toBe ' '
|
||||
|
||||
describe "when the line has leading and trailing whitespace", ->
|
||||
it "does not display the indent guide in the trailing whitespace", ->
|
||||
editor.attachToDom()
|
||||
config.set("editor.showIndentGuide", true)
|
||||
|
||||
editor.insertText("/*\n * \n*/")
|
||||
expect(editor.renderedLines.find('.line:eq(1) .indent-guide').length).toBe 1
|
||||
expect(editor.renderedLines.find('.line:eq(1) .indent-guide')).toHaveClass('leading-whitespace')
|
||||
|
||||
describe "when the line is empty and end of show invisibles are enabled", ->
|
||||
it "renders the indent guides interleaved the end of line invisibles", ->
|
||||
editor.attachToDom()
|
||||
config.set("editor.showIndentGuide", true)
|
||||
config.set("editor.showInvisibles", true)
|
||||
eol = editor.invisibles?.eol
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(10) .indent-guide').length).toBe 1
|
||||
expect(editor.renderedLines.find('.line:eq(10) .indent-guide').text()).toBe "#{eol} "
|
||||
|
||||
editor.setCursorBufferPosition([9])
|
||||
editor.indent()
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(10) .indent-guide').length).toBe 2
|
||||
expect(editor.renderedLines.find('.line:eq(10) .indent-guide').text()).toBe "#{eol} "
|
||||
|
||||
describe "when soft-wrap is enabled", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
@@ -1587,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)
|
||||
@@ -1623,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)
|
||||
@@ -1727,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
|
||||
|
||||
@@ -1889,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()
|
||||
@@ -1915,9 +1952,11 @@ describe "Editor", ->
|
||||
describe "when a fold placeholder line is clicked", ->
|
||||
it "removes the associated fold and places the cursor at its beginning", ->
|
||||
editor.setCursorBufferPosition([3,0])
|
||||
editor.trigger 'editor:fold-current-row'
|
||||
editSession.createFold(3, 5)
|
||||
|
||||
editor.find('.fold.line').mousedown()
|
||||
foldLine = editor.find('.line.fold')
|
||||
expect(foldLine).toExist()
|
||||
foldLine.mousedown()
|
||||
|
||||
expect(editor.find('.fold')).not.toExist()
|
||||
expect(editor.find('.fold-marker')).not.toExist()
|
||||
@@ -2025,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)
|
||||
@@ -2045,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", ->
|
||||
@@ -2126,16 +2164,13 @@ describe "Editor", ->
|
||||
|
||||
it "move the cursor to the end of the file", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual [0,0]
|
||||
event = $.Event("click")
|
||||
event.offsetY = Infinity
|
||||
event = mousedownEvent(editor: editor, point: [Infinity, 10])
|
||||
editor.underlayer.trigger event
|
||||
expect(editor.getCursorScreenPosition()).toEqual [12,2]
|
||||
|
||||
it "selects to the end of the files when shift is pressed", ->
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [[0,0], [0,0]]
|
||||
event = $.Event("click")
|
||||
event.offsetY = Infinity
|
||||
event.shiftKey = true
|
||||
event = mousedownEvent(editor: editor, point: [Infinity, 10], shiftKey: true)
|
||||
editor.underlayer.trigger event
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [[0,0], [12,2]]
|
||||
|
||||
@@ -2150,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()
|
||||
@@ -2170,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)
|
||||
@@ -2584,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)
|
||||
|
||||
@@ -303,6 +303,7 @@ describe "Pane", ->
|
||||
|
||||
describe "when the current item has no save method", ->
|
||||
it "does nothing", ->
|
||||
pane.activeItem.getUri = -> 'you are eye'
|
||||
expect(pane.activeItem.save).toBeUndefined()
|
||||
pane.trigger 'core:save'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -33,6 +33,14 @@ describe "Range", ->
|
||||
expect(new Range([2, 1], [3, 10]).union(new Range([2, 5], [3, 1]))).toEqual [[2, 1], [3, 10]]
|
||||
expect(new Range([2, 5], [3, 1]).union(new Range([2, 1], [3, 10]))).toEqual [[2, 1], [3, 10]]
|
||||
|
||||
describe ".compare(otherRange)", ->
|
||||
it "sorts earlier ranges first, and larger ranges first if both ranges start at the same place", ->
|
||||
expect(new Range([1, 1], [2, 10]).compare(new Range([2, 1], [3, 10]))).toBe -1
|
||||
expect(new Range([2, 1], [3, 10]).compare(new Range([1, 1], [2, 10]))).toBe 1
|
||||
expect(new Range([1, 1], [3, 10]).compare(new Range([1, 1], [2, 10]))).toBe -1
|
||||
expect(new Range([1, 1], [2, 10]).compare(new Range([1, 1], [3, 10]))).toBe 1
|
||||
expect(new Range([1, 1], [3, 10]).compare(new Range([1, 1], [3, 10]))).toBe 0
|
||||
|
||||
describe ".translate(startPoint, endPoint)", ->
|
||||
it "returns a range translates by the specified start and end points", ->
|
||||
expect(new Range([1, 1], [2, 10]).translate([1])).toEqual [[2, 1], [3, 10]]
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
RowMap = require 'row-map'
|
||||
|
||||
describe "RowMap", ->
|
||||
map = null
|
||||
|
||||
beforeEach ->
|
||||
map = new RowMap
|
||||
|
||||
describe "when no mappings have been recorded", ->
|
||||
it "maps buffer rows to screen rows 1:1", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
expect(map.screenRowRangeForBufferRow(100)).toEqual [100, 101]
|
||||
|
||||
describe ".mapBufferRowRange(startBufferRow, endBufferRow, screenRows)", ->
|
||||
describe "when mapping to a single screen row (like a visible fold)", ->
|
||||
beforeEach ->
|
||||
map.mapBufferRowRange(5, 10, 1)
|
||||
map.mapBufferRowRange(15, 20, 1)
|
||||
map.mapBufferRowRange(25, 30, 1)
|
||||
|
||||
it "accounts for the mapping when translating buffer rows to screen row ranges", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [6, 7]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(14)).toEqual [10, 11]
|
||||
expect(map.screenRowRangeForBufferRow(15)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [12, 13]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(24)).toEqual [16, 17]
|
||||
expect(map.screenRowRangeForBufferRow(25)).toEqual [17, 18]
|
||||
expect(map.screenRowRangeForBufferRow(29)).toEqual [17, 18]
|
||||
expect(map.screenRowRangeForBufferRow(30)).toEqual [18, 19]
|
||||
|
||||
it "accounts for the mapping when translating screen rows to buffer row ranges", ->
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(4)).toEqual [4, 5]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(6)).toEqual [10, 11]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(10)).toEqual [14, 15]
|
||||
expect(map.bufferRowRangeForScreenRow(11)).toEqual [15, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(12)).toEqual [20, 21]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(16)).toEqual [24, 25]
|
||||
expect(map.bufferRowRangeForScreenRow(17)).toEqual [25, 30]
|
||||
expect(map.bufferRowRangeForScreenRow(18)).toEqual [30, 31]
|
||||
|
||||
describe "when mapping to zero screen rows (like an invisible fold)", ->
|
||||
beforeEach ->
|
||||
map.mapBufferRowRange(5, 10, 0)
|
||||
map.mapBufferRowRange(15, 20, 0)
|
||||
map.mapBufferRowRange(25, 30, 0)
|
||||
|
||||
it "accounts for the mapping when translating buffer rows to screen row ranges", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 5]
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [5, 5]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [5, 6]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(14)).toEqual [9, 10]
|
||||
expect(map.screenRowRangeForBufferRow(15)).toEqual [10, 10]
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [10, 10]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [10, 11]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(24)).toEqual [14, 15]
|
||||
expect(map.screenRowRangeForBufferRow(25)).toEqual [15, 15]
|
||||
expect(map.screenRowRangeForBufferRow(29)).toEqual [15, 15]
|
||||
expect(map.screenRowRangeForBufferRow(30)).toEqual [15, 16]
|
||||
|
||||
it "accounts for the mapping when translating screen rows to buffer row ranges", ->
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(4)).toEqual [4, 5]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [10, 11]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(9)).toEqual [14, 15]
|
||||
expect(map.bufferRowRangeForScreenRow(10)).toEqual [20, 21]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(14)).toEqual [24, 25]
|
||||
expect(map.bufferRowRangeForScreenRow(15)).toEqual [30, 31]
|
||||
|
||||
describe "when mapping a single buffer row to multiple screen rows (like a wrapped line)", ->
|
||||
beforeEach ->
|
||||
map.mapBufferRowRange(5, 6, 3)
|
||||
map.mapBufferRowRange(10, 11, 2)
|
||||
map.mapBufferRowRange(20, 21, 5)
|
||||
|
||||
it "accounts for the mapping when translating buffer rows to screen row ranges", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 8]
|
||||
expect(map.screenRowRangeForBufferRow(6)).toEqual [8, 9]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [12, 14]
|
||||
expect(map.screenRowRangeForBufferRow(11)).toEqual [14, 15]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [22, 23]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [23, 28]
|
||||
expect(map.screenRowRangeForBufferRow(21)).toEqual [28, 29]
|
||||
|
||||
it "accounts for the mapping when translating screen rows to buffer row ranges", ->
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(4)).toEqual [4, 5]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 6]
|
||||
expect(map.bufferRowRangeForScreenRow(7)).toEqual [5, 6]
|
||||
expect(map.bufferRowRangeForScreenRow(8)).toEqual [6, 7]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(11)).toEqual [9, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(12)).toEqual [10, 11]
|
||||
expect(map.bufferRowRangeForScreenRow(13)).toEqual [10, 11]
|
||||
expect(map.bufferRowRangeForScreenRow(14)).toEqual [11, 12]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(22)).toEqual [19, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(23)).toEqual [20, 21]
|
||||
expect(map.bufferRowRangeForScreenRow(27)).toEqual [20, 21]
|
||||
expect(map.bufferRowRangeForScreenRow(28)).toEqual [21, 22]
|
||||
|
||||
describe "after re-mapping a row range to a new number of screen rows", ->
|
||||
beforeEach ->
|
||||
map.applyScreenDelta(12, 2) # simulate adding 2 more soft wraps
|
||||
map.mapBufferRowRange(10, 11, 4)
|
||||
|
||||
it "updates translation accordingly", ->
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 8]
|
||||
expect(map.screenRowRangeForBufferRow(6)).toEqual [8, 9]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [12, 16]
|
||||
expect(map.screenRowRangeForBufferRow(11)).toEqual [16, 17]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [24, 25]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [25, 30]
|
||||
expect(map.screenRowRangeForBufferRow(21)).toEqual [30, 31]
|
||||
|
||||
describe "when the row range is inside an existing 1:1 region", ->
|
||||
it "preserves the starting screen row of subsequent 1:N regions", ->
|
||||
map.mapBufferRowRange(5, 10, 1)
|
||||
map.mapBufferRowRange(25, 30, 1)
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(21)).toEqual [25, 30]
|
||||
|
||||
map.mapBufferRowRange(15, 20, 1)
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(11)).toEqual [15, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(21)).toEqual [25, 30]
|
||||
|
||||
describe "when the row range surrounds existing regions", ->
|
||||
it "replaces the regions inside the given buffer row range with a single region", ->
|
||||
map.mapBufferRowRange(5, 10, 1) # inner fold 1
|
||||
map.mapBufferRowRange(11, 13, 1) # inner fold 2
|
||||
map.mapBufferRowRange(15, 20, 1) # inner fold 3
|
||||
map.mapBufferRowRange(22, 27, 1) # following fold
|
||||
|
||||
map.mapBufferRowRange(5, 20, 1)
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(6)).toEqual [20, 21]
|
||||
expect(map.bufferRowRangeForScreenRow(7)).toEqual [21, 22]
|
||||
expect(map.bufferRowRangeForScreenRow(8)).toEqual [22, 27]
|
||||
|
||||
describe "when the row range straddles existing regions", ->
|
||||
it "splits the straddled regions and places the new region between them", ->
|
||||
# filler region 0
|
||||
map.mapBufferRowRange(4, 7, 1) # region 1
|
||||
# filler region 2
|
||||
map.mapBufferRowRange(13, 15, 1) # region 3
|
||||
|
||||
# create region straddling region 0 and region 2
|
||||
map.mapBufferRowRange(2, 10, 1)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 2, screenRows: 2)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 8, screenRows: 1)
|
||||
expect(map.regions[2]).toEqual(bufferRows: 3, screenRows: 8)
|
||||
expect(map.regions[3]).toEqual(bufferRows: 2, screenRows: 1)
|
||||
|
||||
it "merges adjacent isomorphic mappings", ->
|
||||
map.mapBufferRowRange(2, 4, 1)
|
||||
map.mapBufferRowRange(4, 5, 2)
|
||||
|
||||
map.mapBufferRowRange(1, 4, 3)
|
||||
expect(map.regions).toEqual [{bufferRows: 5, screenRows: 5}]
|
||||
|
||||
describe ".applyBufferDelta(startBufferRow, delta)", ->
|
||||
describe "when applying a positive delta", ->
|
||||
it "expands the region containing the given start row by the given delta", ->
|
||||
map.mapBufferRowRange(4, 8, 1)
|
||||
|
||||
map.applyBufferDelta(5, 4)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 4, screenRows: 4)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 8, screenRows: 1)
|
||||
|
||||
describe "when applying a negative delta", ->
|
||||
it "shrinks regions starting at the start row until the entire delta is consumed", ->
|
||||
map.mapBufferRowRange(4, 8, 1)
|
||||
map.mapBufferRowRange(10, 14, 1)
|
||||
|
||||
map.applyBufferDelta(3, -6)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 3, screenRows: 4)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 0, screenRows: 1)
|
||||
expect(map.regions[2]).toEqual(bufferRows: 1, screenRows: 2)
|
||||
expect(map.regions[3]).toEqual(bufferRows: 4, screenRows: 1)
|
||||
|
||||
describe ".applyScreenDelta(startScreenRow, delta)", ->
|
||||
describe "when applying a positive delta", ->
|
||||
it "can enlarge the screen side of existing regions", ->
|
||||
map.mapBufferRowRange(5, 6, 3) # wrapped line
|
||||
map.applyScreenDelta(5, 2) # wrap it twice more
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 10]
|
||||
|
||||
describe "when applying a negative delta", ->
|
||||
it "can collapse the screen side of multiple regions to 0 until the entire delta has been applied", ->
|
||||
map.mapBufferRowRange(5, 10, 1) # inner fold 1
|
||||
map.mapBufferRowRange(11, 13, 1) # inner fold 2
|
||||
map.mapBufferRowRange(15, 20, 1) # inner fold 3
|
||||
map.mapBufferRowRange(22, 27, 1) # following fold
|
||||
|
||||
map.applyScreenDelta(6, -5)
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [6, 6]
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [6, 6]
|
||||
expect(map.screenRowRangeForBufferRow(22)).toEqual [8, 9]
|
||||
expect(map.screenRowRangeForBufferRow(26)).toEqual [8, 9]
|
||||
|
||||
it "starts collapsing the first region at the start row, not before", ->
|
||||
map.mapBufferRowRange(5, 6, 4)
|
||||
map.mapBufferRowRange(11, 13, 1)
|
||||
|
||||
map.applyScreenDelta(7, -5)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 5, screenRows: 5)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 1, screenRows: 2)
|
||||
expect(map.regions[2]).toEqual(bufferRows: 5, screenRows: 2)
|
||||
|
||||
it "does not throw an exception when applying a delta beyond the last region", ->
|
||||
map.mapBufferRowRange(5, 10, 1) # inner fold 1
|
||||
map.applyScreenDelta(15, 10)
|
||||
@@ -71,4 +71,4 @@ describe "Selection", ->
|
||||
it "destroys its marker", ->
|
||||
selection.setBufferRange([[2, 0], [2, 10]])
|
||||
selection.destroy()
|
||||
expect(editSession.getMarkerBufferRange(selection.marker)).toBeUndefined()
|
||||
expect(selection.marker.isDestroyed()).toBeTruthy()
|
||||
|
||||
+167
-115
@@ -3,7 +3,7 @@ Buffer = require 'text-buffer'
|
||||
fsUtils = require 'fs-utils'
|
||||
_ = require 'underscore'
|
||||
|
||||
describe 'Buffer', ->
|
||||
describe 'TextBuffer', ->
|
||||
[filePath, fileContents, buffer] = []
|
||||
|
||||
beforeEach ->
|
||||
@@ -781,24 +781,33 @@ describe 'Buffer', ->
|
||||
expect(buffer.positionForCharacterIndex(20)).toEqual [3, 0]
|
||||
|
||||
describe "markers", ->
|
||||
markerCreatedHandler = null
|
||||
|
||||
beforeEach ->
|
||||
buffer.on('marker-created', markerCreatedHandler = jasmine.createSpy("markerCreatedHandler"))
|
||||
|
||||
describe "marker creation", ->
|
||||
it "allows markers to be created with ranges and positions", ->
|
||||
marker1 = buffer.markRange([[4, 20], [4, 23]])
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(buffer.getMarkerPosition(marker1)).toEqual [4, 23]
|
||||
expect(buffer.getMarkerTailPosition(marker1)).toEqual [4, 20]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.getHeadPosition()).toEqual [4, 23]
|
||||
expect(marker1.getTailPosition()).toEqual [4, 20]
|
||||
|
||||
marker2 = buffer.markPosition([4, 20])
|
||||
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 20]]
|
||||
expect(buffer.getMarkerPosition(marker2)).toEqual [4, 20]
|
||||
expect(buffer.getMarkerTailPosition(marker2)).toEqual [4, 20]
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 20]]
|
||||
expect(marker2.getHeadPosition()).toEqual [4, 20]
|
||||
expect(marker2.getTailPosition()).toEqual [4, 20]
|
||||
|
||||
it "allows markers to be created in a reversed orientation", ->
|
||||
marker = buffer.markRange([[4, 20], [4, 23]], reverse: true)
|
||||
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
|
||||
expect(buffer.getMarkerRange(marker)).toEqual [[4, 20], [4, 23]]
|
||||
expect(buffer.getMarkerHeadPosition(marker)).toEqual [4, 20]
|
||||
expect(buffer.getMarkerTailPosition(marker)).toEqual [4, 23]
|
||||
expect(marker.isReversed()).toBeTruthy()
|
||||
expect(marker.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker.getHeadPosition()).toEqual [4, 20]
|
||||
expect(marker.getTailPosition()).toEqual [4, 23]
|
||||
|
||||
it "emits the 'marker-created' event when markers are created", ->
|
||||
marker = buffer.markRange([[4, 20], [4, 23]])
|
||||
expect(markerCreatedHandler).toHaveBeenCalledWith(marker)
|
||||
|
||||
describe "marker manipulation", ->
|
||||
marker = null
|
||||
@@ -806,46 +815,45 @@ describe 'Buffer', ->
|
||||
marker = buffer.markRange([[4, 20], [4, 23]])
|
||||
|
||||
it "allows a marker's head and tail positions to be changed", ->
|
||||
buffer.setMarkerHeadPosition(marker, [5, 3])
|
||||
expect(buffer.getMarkerRange(marker)).toEqual [[4, 20], [5, 3]]
|
||||
marker.setHeadPosition([5, 3])
|
||||
expect(marker.getRange()).toEqual [[4, 20], [5, 3]]
|
||||
|
||||
buffer.setMarkerTailPosition(marker, [6, 3])
|
||||
expect(buffer.getMarkerRange(marker)).toEqual [[5, 3], [6, 3]]
|
||||
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
|
||||
marker.setTailPosition([6, 3])
|
||||
expect(marker.getRange()).toEqual [[5, 3], [6, 3]]
|
||||
expect(marker.isReversed()).toBeTruthy()
|
||||
|
||||
it "clips head and tail positions to ensure they are in bounds", ->
|
||||
buffer.setMarkerHeadPosition(marker, [-100, -5])
|
||||
expect(buffer.getMarkerRange(marker)).toEqual([[0, 0], [4, 20]])
|
||||
buffer.setMarkerTailPosition(marker, [Infinity, Infinity])
|
||||
expect(buffer.getMarkerRange(marker)).toEqual([[0, 0], [12, 2]])
|
||||
marker.setHeadPosition([-100, -5])
|
||||
expect(marker.getRange()).toEqual([[0, 0], [4, 20]])
|
||||
marker.setTailPosition([Infinity, Infinity])
|
||||
expect(marker.getRange()).toEqual([[0, 0], [12, 2]])
|
||||
|
||||
it "allows a marker's tail to be placed and cleared", ->
|
||||
buffer.clearMarkerTail(marker)
|
||||
expect(buffer.getMarkerRange(marker)).toEqual [[4, 23], [4, 23]]
|
||||
buffer.placeMarkerTail(marker)
|
||||
buffer.setMarkerHeadPosition(marker, [2, 0])
|
||||
expect(buffer.getMarkerRange(marker)).toEqual [[2, 0], [4, 23]]
|
||||
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
|
||||
marker.clearTail()
|
||||
expect(marker.getRange()).toEqual [[4, 23], [4, 23]]
|
||||
marker.placeTail()
|
||||
marker.setHeadPosition([2, 0])
|
||||
expect(marker.getRange()).toEqual [[2, 0], [4, 23]]
|
||||
expect(marker.isReversed()).toBeTruthy()
|
||||
|
||||
it "returns whether the position changed", ->
|
||||
expect(buffer.setMarkerHeadPosition(marker, [5, 3])).toBeTruthy()
|
||||
expect(buffer.setMarkerHeadPosition(marker, [5, 3])).toBeFalsy()
|
||||
expect(marker.setHeadPosition([5, 3])).toBeTruthy()
|
||||
expect(marker.setHeadPosition([5, 3])).toBeFalsy()
|
||||
|
||||
expect(buffer.setMarkerTailPosition(marker, [6, 3])).toBeTruthy()
|
||||
expect(buffer.setMarkerTailPosition(marker, [6, 3])).toBeFalsy()
|
||||
expect(marker.setTailPosition([6, 3])).toBeTruthy()
|
||||
expect(marker.setTailPosition([6, 3])).toBeFalsy()
|
||||
|
||||
describe ".observeMarker(marker, callback)", ->
|
||||
[observeHandler, marker, subscription] = []
|
||||
describe "change events", ->
|
||||
[changedHandler, marker] = []
|
||||
|
||||
beforeEach ->
|
||||
observeHandler = jasmine.createSpy("observeHandler")
|
||||
marker = buffer.markRange([[4, 20], [4, 23]])
|
||||
subscription = buffer.observeMarker(marker, observeHandler)
|
||||
marker.on 'changed', changedHandler = jasmine.createSpy("changedHandler")
|
||||
|
||||
it "calls the callback when the marker's head position changes", ->
|
||||
buffer.setMarkerHeadPosition(marker, [6, 2])
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
it "triggers 'changed' events when the marker's head position changes", ->
|
||||
marker.setHeadPosition([6, 2])
|
||||
expect(changedHandler).toHaveBeenCalled()
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadPosition: [4, 23]
|
||||
newHeadPosition: [6, 2]
|
||||
oldTailPosition: [4, 20]
|
||||
@@ -853,10 +861,10 @@ describe 'Buffer', ->
|
||||
bufferChanged: false
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
changedHandler.reset()
|
||||
|
||||
buffer.insert([6, 0], '...')
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldTailPosition: [4, 20]
|
||||
newTailPosition: [4, 20]
|
||||
oldHeadPosition: [6, 2]
|
||||
@@ -866,9 +874,9 @@ describe 'Buffer', ->
|
||||
}
|
||||
|
||||
it "calls the given callback when the marker's tail position changes", ->
|
||||
buffer.setMarkerTailPosition(marker, [6, 2])
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
marker.setTailPosition([6, 2])
|
||||
expect(changedHandler).toHaveBeenCalled()
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadPosition: [4, 23]
|
||||
newHeadPosition: [4, 23]
|
||||
oldTailPosition: [4, 20]
|
||||
@@ -876,11 +884,11 @@ describe 'Buffer', ->
|
||||
bufferChanged: false
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
changedHandler.reset()
|
||||
|
||||
buffer.insert([6, 0], '...')
|
||||
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadPosition: [4, 23]
|
||||
newHeadPosition: [4, 23]
|
||||
oldTailPosition: [6, 2]
|
||||
@@ -889,10 +897,10 @@ describe 'Buffer', ->
|
||||
valid: true
|
||||
}
|
||||
|
||||
it "calls the callback when the selection's tail is cleared", ->
|
||||
buffer.clearMarkerTail(marker)
|
||||
expect(observeHandler).toHaveBeenCalled()
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
it "triggers 'changed' events when the selection's tail is cleared", ->
|
||||
marker.clearTail()
|
||||
expect(changedHandler).toHaveBeenCalled()
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldHeadPosition: [4, 23]
|
||||
newHeadPosition: [4, 23]
|
||||
oldTailPosition: [4, 20]
|
||||
@@ -901,10 +909,10 @@ describe 'Buffer', ->
|
||||
valid: true
|
||||
}
|
||||
|
||||
it "only calls the callback once when both the marker's head and tail positions change due to the same operation", ->
|
||||
it "only triggers 'changed' events once when both the marker's head and tail positions change due to the same operation", ->
|
||||
buffer.insert([4, 0], '...')
|
||||
expect(observeHandler.callCount).toBe 1
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(changedHandler.callCount).toBe 1
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldTailPosition: [4, 20]
|
||||
newTailPosition: [4, 23]
|
||||
oldHeadPosition: [4, 23]
|
||||
@@ -912,11 +920,11 @@ describe 'Buffer', ->
|
||||
bufferChanged: true
|
||||
valid: true
|
||||
}
|
||||
observeHandler.reset()
|
||||
changedHandler.reset()
|
||||
|
||||
buffer.setMarkerRange(marker, [[0, 0], [1, 1]])
|
||||
expect(observeHandler.callCount).toBe 1
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
marker.setRange([[0, 0], [1, 1]])
|
||||
expect(changedHandler.callCount).toBe 1
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldTailPosition: [4, 23]
|
||||
newTailPosition: [0, 0]
|
||||
oldHeadPosition: [4, 26]
|
||||
@@ -925,10 +933,10 @@ describe 'Buffer', ->
|
||||
valid: true
|
||||
}
|
||||
|
||||
it "calls the callback with the valid flag set to false when the marker is invalidated", ->
|
||||
it "triggers 'changed' events with the valid flag set to false when the marker is invalidated", ->
|
||||
buffer.deleteRow(4)
|
||||
expect(observeHandler.callCount).toBe 1
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(changedHandler.callCount).toBe 1
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldTailPosition: [4, 20]
|
||||
newTailPosition: [4, 20]
|
||||
oldHeadPosition: [4, 23]
|
||||
@@ -937,10 +945,10 @@ describe 'Buffer', ->
|
||||
valid: false
|
||||
}
|
||||
|
||||
observeHandler.reset()
|
||||
changedHandler.reset()
|
||||
buffer.undo()
|
||||
expect(observeHandler.callCount).toBe 1
|
||||
expect(observeHandler.argsForCall[0][0]).toEqual {
|
||||
expect(changedHandler.callCount).toBe 1
|
||||
expect(changedHandler.argsForCall[0][0]).toEqual {
|
||||
oldTailPosition: [4, 20]
|
||||
newTailPosition: [4, 20]
|
||||
oldHeadPosition: [4, 23]
|
||||
@@ -949,10 +957,22 @@ describe 'Buffer', ->
|
||||
valid: true
|
||||
}
|
||||
|
||||
it "allows the observation subscription to be cancelled", ->
|
||||
subscription.cancel()
|
||||
buffer.setMarkerHeadPosition(marker, [6, 2])
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
describe ".findMarkers(attributes)", ->
|
||||
[marker1, marker2, marker3, marker4] = []
|
||||
|
||||
beforeEach ->
|
||||
marker1 = buffer.markRange([[0, 0], [3, 0]], class: 'a')
|
||||
marker2 = buffer.markRange([[0, 0], [5, 0]], class: 'a')
|
||||
marker3 = buffer.markRange([[6, 0], [7, 0]], class: 'a')
|
||||
marker4 = buffer.markRange([[9, 0], [10, 0]], class: 'b')
|
||||
|
||||
it "returns the markers matching the given attributes, sorted by the buffer location and size of their ranges", ->
|
||||
expect(buffer.findMarkers(class: 'a')).toEqual [marker2, marker1, marker3]
|
||||
|
||||
it "allows the startRow and endRow to be specified", ->
|
||||
expect(buffer.findMarkers(class: 'a', startRow: 0)).toEqual [marker2, marker1]
|
||||
expect(buffer.findMarkers(class: 'a', startRow: 0, endRow: 3)).toEqual [marker1]
|
||||
expect(buffer.findMarkers(endRow: 10)).toEqual [marker4]
|
||||
|
||||
describe "marker destruction", ->
|
||||
marker = null
|
||||
@@ -961,22 +981,27 @@ describe 'Buffer', ->
|
||||
marker = buffer.markRange([[4, 20], [4, 23]])
|
||||
|
||||
it "allows a marker to be destroyed", ->
|
||||
buffer.destroyMarker(marker)
|
||||
expect(buffer.getMarkerRange(marker)).toBeUndefined()
|
||||
marker.destroy()
|
||||
expect(buffer.getMarker(marker.id)).toBeUndefined()
|
||||
|
||||
it "does not restore invalidated markers that have been destroyed", ->
|
||||
buffer.delete([[4, 15], [4, 25]])
|
||||
expect(buffer.getMarkerRange(marker)).toBeUndefined()
|
||||
buffer.destroyMarker(marker)
|
||||
expect(buffer.getMarker(marker.id)).toBeUndefined()
|
||||
marker.destroy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker)).toBeUndefined()
|
||||
expect(buffer.getMarker(marker.id)).toBeUndefined()
|
||||
|
||||
# even "invalidationStrategy: never" markers get destroyed properly
|
||||
marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never')
|
||||
buffer.delete([[4, 15], [4, 25]])
|
||||
buffer.destroyMarker(marker2)
|
||||
marker2.destroy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker2)).toBeUndefined()
|
||||
expect(buffer.getMarker(marker2.id)).toBeUndefined()
|
||||
|
||||
it "emits 'destroyed' on the marker when it is destroyed", ->
|
||||
marker.on 'destroyed', destroyedHandler = jasmine.createSpy("destroyedHandler")
|
||||
marker.destroy()
|
||||
expect(destroyedHandler).toHaveBeenCalled()
|
||||
|
||||
describe "marker updates due to buffer changes", ->
|
||||
[marker1, marker2, marker3] = []
|
||||
@@ -990,149 +1015,176 @@ describe 'Buffer', ->
|
||||
describe "when the change precedes the marker range", ->
|
||||
it "moves the marker", ->
|
||||
buffer.insert([4, 5], '...')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 23], [4, 26]]
|
||||
expect(marker1.getRange()).toEqual [[4, 23], [4, 26]]
|
||||
buffer.delete([[4, 5], [4, 8]])
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
buffer.insert([0, 0], '\nhi\n')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[6, 20], [6, 23]]
|
||||
expect(marker1.getRange()).toEqual [[6, 20], [6, 23]]
|
||||
|
||||
# undo works
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 23], [4, 26]]
|
||||
expect(marker1.getRange()).toEqual [[4, 23], [4, 26]]
|
||||
|
||||
it "restores the marker range exactly on undo", ->
|
||||
marker = buffer.markRange([[3, 0], [3, 62]])
|
||||
buffer.delete([[2, 0], [3, 0]])
|
||||
expect(marker.getRange()).toEqual [[2, 0], [2, 62]]
|
||||
buffer.undo()
|
||||
expect(marker.getRange()).toEqual [[3, 0], [3, 62]]
|
||||
|
||||
describe "when the change follows the marker range", ->
|
||||
it "does not move the marker", ->
|
||||
buffer.insert([6, 5], '...')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
buffer.delete([[6, 5], [6, 8]])
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
buffer.insert([10, 0], '\nhi\n')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the change is an insertion at the start of the marker range", ->
|
||||
it "does not move the start point, but does move the end point", ->
|
||||
buffer.insert([4, 20], '...')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 26]]
|
||||
|
||||
describe "when the invalidation strategy is 'between'", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.insert([4, 20], '...')
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
|
||||
describe "when the change is an insertion at the end of the marker range", ->
|
||||
it "moves the end point", ->
|
||||
buffer.insert([4, 23], '...')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]]
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 26]]
|
||||
|
||||
describe "when the invalidation strategy is 'between'", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.insert([4, 23], '...')
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
|
||||
describe "when the change surrounds the marker range", ->
|
||||
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.delete([[4, 15], [4, 25]])
|
||||
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
|
||||
expect(marker1.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.isValid()).toBeTruthy()
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'between'", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.delete([[4, 15], [4, 25]])
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker3.isValid()).toBeTruthy()
|
||||
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'never'", ->
|
||||
it "does not invalidate the marker, but sets it to an empty range at the end of the change", ->
|
||||
buffer.change([[4, 15], [4, 25]], "...")
|
||||
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 18], [4, 18]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 18], [4, 18]]
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the change straddles the start of the marker range", ->
|
||||
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.delete([[4, 15], [4, 22]])
|
||||
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
|
||||
expect(marker1.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.isValid()).toBeTruthy()
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'between'", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.delete([[4, 15], [4, 22]])
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker3.isValid()).toBeTruthy()
|
||||
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'never'", ->
|
||||
it "moves the start of the marker range to the end of the change", ->
|
||||
buffer.delete([[4, 15], [4, 22]])
|
||||
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 15], [4, 16]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 15], [4, 16]]
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the change straddles the end of the marker range", ->
|
||||
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.delete([[4, 22], [4, 25]])
|
||||
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
|
||||
expect(marker1.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.isValid()).toBeTruthy()
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'between'", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.delete([[4, 22], [4, 25]])
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker3.isValid()).toBeTruthy()
|
||||
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'never'", ->
|
||||
it "moves the end of the marker range to the start of the change", ->
|
||||
buffer.delete([[4, 22], [4, 25]])
|
||||
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 22]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 22]]
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the change is between the start and the end of the marker range", ->
|
||||
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
|
||||
it "does not invalidate the marker", ->
|
||||
buffer.insert([4, 21], 'x')
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 24]]
|
||||
expect(marker1.isValid()).toBeTruthy()
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 24]]
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker1.isValid()).toBeTruthy()
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'between'", ->
|
||||
it "invalidates the marker", ->
|
||||
buffer.insert([4, 21], 'x')
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker3.isValid()).toBeTruthy()
|
||||
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the marker's invalidation strategy is 'never'", ->
|
||||
it "moves the end of the marker range to the start of the change", ->
|
||||
buffer.insert([4, 21], 'x')
|
||||
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 24]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 24]]
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker2.isValid()).toBeTruthy()
|
||||
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe "when the buffer changes due to the undo or redo of a previous operation", ->
|
||||
it "restores invalidated markers when undoing/redoing in the other direction", ->
|
||||
buffer.change([[4, 21], [4, 24]], "foo")
|
||||
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
|
||||
expect(marker1.isValid()).toBeFalsy()
|
||||
marker3 = buffer.markRange([[4, 20], [4, 23]])
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
|
||||
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
|
||||
expect(marker1.isValid()).toBeTruthy()
|
||||
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker3.isValid()).toBeFalsy()
|
||||
marker4 = buffer.markRange([[4, 20], [4, 23]])
|
||||
buffer.redo()
|
||||
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
|
||||
expect(buffer.getMarkerRange(marker4)).toBeUndefined()
|
||||
expect(marker3.isValid()).toBeTruthy()
|
||||
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker4.isValid()).toBeFalsy()
|
||||
buffer.undo()
|
||||
expect(buffer.getMarkerRange(marker4)).toEqual [[4, 20], [4, 23]]
|
||||
expect(marker4.isValid()).toBeTruthy()
|
||||
expect(marker4.getRange()).toEqual [[4, 20], [4, 23]]
|
||||
|
||||
describe ".markersForPosition(position)", ->
|
||||
it "returns all markers that intersect the given position", ->
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -64,103 +64,59 @@ describe "UndoManager", ->
|
||||
expect(buffer.getText()).toContain 'qsport'
|
||||
|
||||
describe "transaction methods", ->
|
||||
describe "transact([fn])", ->
|
||||
describe "when called with a function", ->
|
||||
it "causes changes performed within the function's dynamic extent to be undone simultaneously", ->
|
||||
buffer.insert([0, 0], "foo")
|
||||
describe "transact()", ->
|
||||
beforeEach ->
|
||||
buffer.setText('')
|
||||
|
||||
undoManager.transact ->
|
||||
undoManager.transact ->
|
||||
buffer.insert([1, 2], "111")
|
||||
buffer.insert([1, 9], "222")
|
||||
it "starts a transaction that can be committed later", ->
|
||||
buffer.append('1')
|
||||
undoManager.transact()
|
||||
buffer.append('2')
|
||||
buffer.append('3')
|
||||
undoManager.commit()
|
||||
buffer.append('4')
|
||||
|
||||
expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {'
|
||||
expect(buffer.getText()).toBe '1234'
|
||||
undoManager.undo()
|
||||
expect(buffer.getText()).toBe '123'
|
||||
undoManager.undo()
|
||||
expect(buffer.getText()).toBe '1'
|
||||
undoManager.redo()
|
||||
expect(buffer.getText()).toBe '123'
|
||||
|
||||
undoManager.undo()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {'
|
||||
expect(buffer.lineForRow(0)).toContain 'foo'
|
||||
it "starts a transaction that can be aborted later", ->
|
||||
buffer.append('1')
|
||||
buffer.append('2')
|
||||
|
||||
undoManager.undo()
|
||||
undoManager.transact()
|
||||
|
||||
expect(buffer.lineForRow(0)).not.toContain 'foo'
|
||||
buffer.append('3')
|
||||
buffer.append('4')
|
||||
expect(buffer.getText()).toBe '1234'
|
||||
|
||||
undoManager.redo()
|
||||
expect(buffer.lineForRow(0)).toContain 'foo'
|
||||
undoManager.abort()
|
||||
expect(buffer.getText()).toBe '12'
|
||||
|
||||
undoManager.redo()
|
||||
expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {'
|
||||
undoManager.undo()
|
||||
expect(buffer.getText()).toBe '1'
|
||||
|
||||
undoManager.undo()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {'
|
||||
undoManager.redo()
|
||||
expect(buffer.getText()).toBe '12'
|
||||
|
||||
it "does not record empty transactions", ->
|
||||
buffer.insert([0,0], "foo")
|
||||
undoManager.transact ->
|
||||
|
||||
undoManager.undo()
|
||||
expect(buffer.lineForRow(0)).not.toContain("foo")
|
||||
|
||||
it "undoes operations that occured prior to an exception when the transaction is undone", ->
|
||||
buffer.setText("jumpstreet")
|
||||
|
||||
expect(->
|
||||
undoManager.transact ->
|
||||
buffer.insert([0,0], "3")
|
||||
buffer.insert([0,0], "2")
|
||||
throw new Error("problem")
|
||||
buffer.insert([0,0], "2")
|
||||
).toThrow('problem')
|
||||
|
||||
expect(buffer.lineForRow(0)).toBe "23jumpstreet"
|
||||
undoManager.undo()
|
||||
expect(buffer.lineForRow(0)).toBe "jumpstreet"
|
||||
|
||||
describe "when called without a function", ->
|
||||
beforeEach ->
|
||||
buffer.setText('')
|
||||
|
||||
it "returns a transaction object that can be committed later", ->
|
||||
buffer.append('1')
|
||||
undoManager.transact()
|
||||
buffer.append('2')
|
||||
buffer.append('3')
|
||||
undoManager.commit()
|
||||
buffer.append('4')
|
||||
|
||||
expect(buffer.getText()).toBe '1234'
|
||||
undoManager.undo()
|
||||
expect(buffer.getText()).toBe '123'
|
||||
undoManager.undo()
|
||||
expect(buffer.getText()).toBe '1'
|
||||
undoManager.redo()
|
||||
expect(buffer.getText()).toBe '123'
|
||||
|
||||
it "returns a transaction object that can be aborted later", ->
|
||||
buffer.append('1')
|
||||
buffer.append('2')
|
||||
|
||||
undoManager.transact()
|
||||
|
||||
buffer.append('3')
|
||||
buffer.append('4')
|
||||
expect(buffer.getText()).toBe '1234'
|
||||
|
||||
undoManager.abort()
|
||||
expect(buffer.getText()).toBe '12'
|
||||
|
||||
undoManager.undo()
|
||||
expect(buffer.getText()).toBe '1'
|
||||
|
||||
undoManager.redo()
|
||||
expect(buffer.getText()).toBe '12'
|
||||
|
||||
undoManager.redo()
|
||||
expect(buffer.getText()).toBe '12'
|
||||
undoManager.redo()
|
||||
expect(buffer.getText()).toBe '12'
|
||||
|
||||
describe "commit", ->
|
||||
it "throws an exception if there is no current transaction", ->
|
||||
expect(-> buffer.commit()).toThrow()
|
||||
|
||||
it "does not record empty transactions", ->
|
||||
buffer.insert([0,0], "foo")
|
||||
undoManager.transact()
|
||||
undoManager.commit()
|
||||
undoManager.undo()
|
||||
expect(buffer.lineForRow(0)).not.toContain("foo")
|
||||
|
||||
describe "abort", ->
|
||||
it "does not affect the undo stack when the current transaction is empty", ->
|
||||
buffer.setText('')
|
||||
|
||||
+105
-21
@@ -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.handleWindowEvents()
|
||||
windowEventHandler = new WindowEventHandler()
|
||||
window.deserializeEditorWindow()
|
||||
projectPath = project.getPath()
|
||||
|
||||
afterEach ->
|
||||
window.unloadEditorWindow()
|
||||
windowEventHandler.unsubscribe()
|
||||
$(window).off 'beforeunload'
|
||||
|
||||
describe "when the window is loaded", ->
|
||||
@@ -150,28 +152,10 @@ describe "Window", ->
|
||||
expect(buffer.subscriptionCount()).toBe 0
|
||||
|
||||
it "only serializes window state the first time it is called", ->
|
||||
|
||||
window.unloadEditorWindow()
|
||||
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)
|
||||
@@ -230,3 +214,103 @@ describe "Window", ->
|
||||
event = buildDragEvent("drop", [])
|
||||
window.onDrop(event)
|
||||
expect(atom.open).not.toHaveBeenCalled()
|
||||
|
||||
describe "when a link is clicked", ->
|
||||
it "opens the http/https links in an external application", ->
|
||||
ChildProcess = require 'child_process'
|
||||
spyOn(ChildProcess, 'spawn')
|
||||
|
||||
$("<a href='http://github.com'>the website</a>").appendTo(document.body).click().remove()
|
||||
expect(ChildProcess.spawn).toHaveBeenCalled()
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe "http://github.com"
|
||||
|
||||
ChildProcess.spawn.reset()
|
||||
$("<a href='https://github.com'>the website</a>").appendTo(document.body).click().remove()
|
||||
expect(ChildProcess.spawn).toHaveBeenCalled()
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe "https://github.com"
|
||||
|
||||
ChildProcess.spawn.reset()
|
||||
$("<a href=''>the website</a>").appendTo(document.body).click().remove()
|
||||
expect(ChildProcess.spawn).not.toHaveBeenCalled()
|
||||
|
||||
ChildProcess.spawn.reset()
|
||||
$("<a href='#scroll-me'>link</a>").appendTo(document.body).click().remove()
|
||||
expect(ChildProcess.spawn).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()
|
||||
|
||||
+18
-1
@@ -1,3 +1,20 @@
|
||||
## File.markdown
|
||||
|
||||
:cool:
|
||||
:cool:
|
||||
|
||||
```ruby
|
||||
def func
|
||||
x = 1
|
||||
end
|
||||
```
|
||||
|
||||
```
|
||||
function f(x) {
|
||||
return x++;
|
||||
}
|
||||
```
|
||||
|
||||
```kombucha
|
||||
drink-that-stuff:
|
||||
tastes-weird~
|
||||
```
|
||||
|
||||
@@ -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,90 +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'"
|
||||
|
||||
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,12 +3,9 @@ Package = require 'package'
|
||||
fsUtils = require 'fs-utils'
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
CSON = require 'cson'
|
||||
CSON = require 'season'
|
||||
|
||||
|
||||
###
|
||||
# Internal: Loads and resolves packages. #
|
||||
###
|
||||
### Internal: Loads and resolves packages. ###
|
||||
|
||||
module.exports =
|
||||
class AtomPackage extends Package
|
||||
@@ -65,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')
|
||||
|
||||
@@ -5,13 +5,13 @@ Theme = require 'theme'
|
||||
module.exports =
|
||||
class AtomTheme extends Theme
|
||||
|
||||
# Internal: Given a path, this loads it as a stylesheet.
|
||||
# Given a path, this loads it as a stylesheet.
|
||||
#
|
||||
# stylesheetPath - A {String} to a stylesheet
|
||||
loadStylesheet: (stylesheetPath)->
|
||||
@stylesheets[stylesheetPath] = window.loadStylesheet(stylesheetPath)
|
||||
|
||||
# Internal: Loads the stylesheets found in a `package.cson` file.
|
||||
# Loads the stylesheets found in a `package.cson` file.
|
||||
load: ->
|
||||
if fsUtils.extension(@path) in ['.css', '.less']
|
||||
@loadStylesheet(@path)
|
||||
|
||||
@@ -71,7 +71,7 @@ _.extend 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)
|
||||
|
||||
@@ -5,9 +5,7 @@ fsUtils = require 'fs-utils'
|
||||
Specificity = require 'specificity'
|
||||
PEG = require 'pegjs'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class BindingSet
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Range = require 'range'
|
||||
_ = require 'underscore'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class BufferChangeOperation
|
||||
@@ -19,26 +17,35 @@ class BufferChangeOperation
|
||||
@options ?= {}
|
||||
|
||||
do: ->
|
||||
@oldText = @buffer.getTextInRange(@oldRange)
|
||||
@newRange = @calculateNewRange(@oldRange, @newText)
|
||||
@buffer.pauseEvents()
|
||||
@pauseMarkerObservation()
|
||||
@markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
|
||||
@changeBuffer
|
||||
oldRange: @oldRange
|
||||
newRange: @newRange
|
||||
oldText: @oldText
|
||||
newText: @newText
|
||||
|
||||
redo: ->
|
||||
@restoreMarkers(@markersToRestoreOnRedo)
|
||||
if @oldRange?
|
||||
@oldText = @buffer.getTextInRange(@oldRange)
|
||||
@newRange = @calculateNewRange(@oldRange, @newText)
|
||||
newRange = @changeBuffer
|
||||
oldRange: @oldRange
|
||||
newRange: @newRange
|
||||
oldText: @oldText
|
||||
newText: @newText
|
||||
@restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
|
||||
@buffer.resumeEvents()
|
||||
@resumeMarkerObservation()
|
||||
newRange
|
||||
|
||||
undo: ->
|
||||
@buffer.pauseEvents()
|
||||
@pauseMarkerObservation()
|
||||
@markersToRestoreOnRedo = @invalidateMarkers(@newRange)
|
||||
@changeBuffer
|
||||
oldRange: @newRange
|
||||
newRange: @oldRange
|
||||
oldText: @newText
|
||||
newText: @oldText
|
||||
if @oldRange?
|
||||
@changeBuffer
|
||||
oldRange: @newRange
|
||||
newRange: @oldRange
|
||||
oldText: @newText
|
||||
newText: @oldText
|
||||
@restoreMarkers(@markersToRestoreOnUndo)
|
||||
@buffer.resumeEvents()
|
||||
@resumeMarkerObservation()
|
||||
|
||||
splitLines: (text) ->
|
||||
lines = text.split('\n')
|
||||
@@ -74,13 +81,10 @@ class BufferChangeOperation
|
||||
@buffer.cachedMemoryContents = null
|
||||
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
|
||||
|
||||
@pauseMarkerObservation()
|
||||
event = { oldRange, newRange, oldText, newText }
|
||||
@updateMarkers(event)
|
||||
@buffer.trigger 'changed', event
|
||||
@buffer.scheduleModifiedEvents()
|
||||
@resumeMarkerObservation()
|
||||
@buffer.trigger 'markers-updated'
|
||||
|
||||
newRange
|
||||
|
||||
@@ -96,13 +100,14 @@ class BufferChangeOperation
|
||||
newRange
|
||||
|
||||
invalidateMarkers: (oldRange) ->
|
||||
_.compact(@buffer.getMarkers().map (marker) -> marker.tryToInvalidate(oldRange))
|
||||
@buffer.getMarkers().map (marker) -> marker.tryToInvalidate(oldRange)
|
||||
|
||||
pauseMarkerObservation: ->
|
||||
marker.pauseEvents() for marker in @buffer.getMarkers()
|
||||
marker.pauseEvents() for marker in @buffer.getMarkers(includeInvalid: true)
|
||||
|
||||
resumeMarkerObservation: ->
|
||||
marker.resumeEvents() for marker in @buffer.getMarkers()
|
||||
marker.resumeEvents() for marker in @buffer.getMarkers(includeInvalid: true)
|
||||
@buffer.trigger 'markers-updated' if @oldRange?
|
||||
|
||||
updateMarkers: (bufferChange) ->
|
||||
marker.handleBufferChange(bufferChange) for marker in @buffer.getMarkers()
|
||||
|
||||
@@ -10,23 +10,20 @@ class BufferMarker
|
||||
suppressObserverNotification: false
|
||||
invalidationStrategy: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
constructor: ({@id, @buffer, range, @invalidationStrategy, noTail, reverse}) ->
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@id, @buffer, range, @invalidationStrategy, @attributes, noTail, reverse}) ->
|
||||
@invalidationStrategy ?= 'contains'
|
||||
@setRange(range, {noTail, reverse})
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Sets the marker's range, potentialy modifying both its head and tail.
|
||||
# Sets the marker's range, potentialy modifying both its head and tail.
|
||||
#
|
||||
# range - The new {Range} the marker should cover
|
||||
# options - A hash of options with the following keys:
|
||||
# :reverse - if `true`, the marker is reversed; that is, its tail is "above" the head
|
||||
# :noTail - if `true`, the marker doesn't have a tail
|
||||
# reverse: if `true`, the marker is reversed; that is, its tail is "above" the head
|
||||
# noTail: if `true`, the marker doesn't have a tail
|
||||
setRange: (range, options={}) ->
|
||||
@consolidateObserverNotifications false, =>
|
||||
range = Range.fromObject(range)
|
||||
@@ -37,7 +34,7 @@ class BufferMarker
|
||||
@setTailPosition(range.start) unless options.noTail
|
||||
@setHeadPosition(range.end)
|
||||
|
||||
# Public: Identifies if the ending position of a marker is greater than the starting position.
|
||||
# Identifies if the ending position of a marker is greater than the starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {Buffer}.
|
||||
#
|
||||
@@ -45,37 +42,55 @@ class BufferMarker
|
||||
isReversed: ->
|
||||
@tailPosition? and @headPosition.isLessThan(@tailPosition)
|
||||
|
||||
# Public: Identifies if the marker's head position is equal to its tail.
|
||||
# Checks that the marker's attributes match the given attributes
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
matchesAttributes: (queryAttributes) ->
|
||||
for key, value of queryAttributes
|
||||
switch key
|
||||
when 'startRow'
|
||||
return false unless @getRange().start.row == value
|
||||
when 'endRow'
|
||||
return false unless @getRange().end.row == value
|
||||
when 'containsRange'
|
||||
return false unless @getRange().containsRange(value, exclusive: true)
|
||||
when 'containsRow'
|
||||
return false unless @getRange().containsRow(value)
|
||||
else
|
||||
return false unless _.isEqual(@attributes[key], value)
|
||||
true
|
||||
|
||||
# Identifies if the marker's head position is equal to its tail.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isRangeEmpty: ->
|
||||
@getHeadPosition().isEqual(@getTailPosition())
|
||||
|
||||
# Public: Retrieves the {Range} between a marker's head and its tail.
|
||||
#
|
||||
# Retrieves the {Range} between a marker's head and its tail.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getRange: ->
|
||||
if @tailPosition
|
||||
new Range(@tailPosition, @headPosition)
|
||||
new Range(@getTailPosition(), @getHeadPosition())
|
||||
else
|
||||
new Range(@headPosition, @headPosition)
|
||||
new Range(@getHeadPosition(), @getHeadPosition())
|
||||
|
||||
# Public: Retrieves the position of the marker's head.
|
||||
# Retrieves the position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadPosition: -> @headPosition
|
||||
getHeadPosition: -> @headPosition?.copy()
|
||||
|
||||
# Public: Retrieves the position of the marker's tail.
|
||||
# Retrieves the position of the marker's tail.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailPosition: -> @tailPosition ? @getHeadPosition()
|
||||
getTailPosition: -> @tailPosition?.copy() ? @getHeadPosition()
|
||||
|
||||
# Public: Sets the position of the marker's head.
|
||||
# Sets the position of the marker's head.
|
||||
#
|
||||
# newHeadPosition - The new {Point} to place the head
|
||||
# options - A hash with the following keys:
|
||||
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
#
|
||||
# Returns a {Point} representing the new head position.
|
||||
setHeadPosition: (newHeadPosition, options={}) ->
|
||||
@@ -88,12 +103,12 @@ class BufferMarker
|
||||
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
|
||||
@headPosition
|
||||
|
||||
# Public: Sets the position of the marker's tail.
|
||||
# Sets the position of the marker's tail.
|
||||
#
|
||||
# newHeadPosition - The new {Point} to place the tail
|
||||
# options - A hash with the following keys:
|
||||
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
setTailPosition: (newTailPosition, options={}) ->
|
||||
@@ -106,19 +121,19 @@ class BufferMarker
|
||||
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
|
||||
@tailPosition
|
||||
|
||||
# Public: Retrieves the starting position of the marker.
|
||||
# Retrieves the starting position of the marker.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartPosition: ->
|
||||
@getRange().start
|
||||
|
||||
# Public: Retrieves the ending position of the marker.
|
||||
# Retrieves the ending position of the marker.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndPosition: ->
|
||||
@getRange().end
|
||||
|
||||
# Public: Sets the marker's tail to the same position as the marker's head.
|
||||
# Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
@@ -126,56 +141,58 @@ class BufferMarker
|
||||
placeTail: ->
|
||||
@setTailPosition(@getHeadPosition()) unless @tailPosition
|
||||
|
||||
# Public: Removes the tail from the marker.
|
||||
# Removes the tail from the marker.
|
||||
clearTail: ->
|
||||
oldTailPosition = @getTailPosition()
|
||||
@tailPosition = null
|
||||
newTailPosition = @getTailPosition()
|
||||
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
|
||||
|
||||
# Public: Identifies if a {Point} is within the marker.
|
||||
# Identifies if a {Point} is within the marker.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsPoint: (point) ->
|
||||
@getRange().containsPoint(point)
|
||||
|
||||
# Public: Sets a callback to be fired whenever a marker is changed.
|
||||
observe: (callback) ->
|
||||
@on 'changed', callback
|
||||
cancel: => @unobserve(callback)
|
||||
# Destroys the marker
|
||||
destroy: ->
|
||||
@buffer.destroyMarker(@id)
|
||||
@trigger 'destroyed'
|
||||
|
||||
# Public: Removes the fired callback whenever a marker changes.
|
||||
unobserve: (callback) ->
|
||||
@off 'changed', callback
|
||||
# Returns a {Boolean} indicating whether the marker is valid. Markers can be
|
||||
# invalidated when a region surrounding them in the buffer is changed.
|
||||
isValid: ->
|
||||
@buffer.getMarker(@id)?
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
|
||||
# can be invalid without being destroyed, in which case undoing the invalidating
|
||||
# operation would restore the marker. Once a marker is destroyed by calling
|
||||
# {BufferMarker.destroy}, no undo/redo operation can ever bring it back.
|
||||
isDestroyed: ->
|
||||
not (@buffer.validMarkers[@id]? or @buffer.invalidMarkers[@id]?)
|
||||
|
||||
### Internal ###
|
||||
|
||||
tryToInvalidate: (changedRange) ->
|
||||
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
|
||||
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
|
||||
containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true)
|
||||
|
||||
switch @invalidationStrategy
|
||||
when 'between'
|
||||
if betweenStartAndEnd or containsStart or containsEnd
|
||||
@invalidate()
|
||||
[@id]
|
||||
when 'contains'
|
||||
if containsStart or containsEnd
|
||||
@invalidate()
|
||||
[@id]
|
||||
when 'never'
|
||||
if containsStart or containsEnd
|
||||
previousRange = @getRange()
|
||||
if containsStart and containsEnd
|
||||
@setRange([changedRange.end, changedRange.end])
|
||||
else if containsStart
|
||||
@setRange([changedRange.end, @getEndPosition()])
|
||||
else
|
||||
@setRange([@getStartPosition(), changedRange.start])
|
||||
[@id, previousRange]
|
||||
previousRange = @getRange()
|
||||
if changedRange
|
||||
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
|
||||
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
|
||||
containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true)
|
||||
switch @invalidationStrategy
|
||||
when 'between'
|
||||
@invalidate() if betweenStartAndEnd or containsStart or containsEnd
|
||||
when 'contains'
|
||||
@invalidate() if containsStart or containsEnd
|
||||
when 'never'
|
||||
if containsStart or containsEnd
|
||||
if containsStart and containsEnd
|
||||
@setRange([changedRange.end, changedRange.end])
|
||||
else if containsStart
|
||||
@setRange([changedRange.end, @getEndPosition()])
|
||||
else
|
||||
@setRange([@getStartPosition(), changedRange.start])
|
||||
[@id, previousRange]
|
||||
|
||||
handleBufferChange: (bufferChange) ->
|
||||
@consolidateObserverNotifications true, =>
|
||||
@@ -213,7 +230,7 @@ class BufferMarker
|
||||
newHeadPosition ?= @getHeadPosition()
|
||||
oldTailPosition ?= @getTailPosition()
|
||||
newTailPosition ?= @getTailPosition()
|
||||
valid = @buffer.validMarkers[@id]?
|
||||
valid = @isValid()
|
||||
@trigger 'changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}
|
||||
|
||||
consolidateObserverNotifications: (bufferChanged, fn) ->
|
||||
|
||||
@@ -17,22 +17,20 @@ class ConfigPanel extends View
|
||||
input = $(input)
|
||||
name = input.attr('id')
|
||||
type = input.attr('type')
|
||||
|
||||
@observeConfig name, (value) ->
|
||||
if type is 'checkbox'
|
||||
input.attr('checked', value)
|
||||
else
|
||||
input.val(value) if value
|
||||
input.on 'change', ->
|
||||
|
||||
input.on 'change', =>
|
||||
value = input.val()
|
||||
config.set name, switch type
|
||||
when 'int'
|
||||
parseInt(value)
|
||||
when 'float'
|
||||
parseFloat(value)
|
||||
when 'checkbox'
|
||||
!!input.attr('checked')
|
||||
else
|
||||
value
|
||||
if type == 'checkbox'
|
||||
value = !!input.attr('checked')
|
||||
else
|
||||
value = @parseValue(type, value)
|
||||
config.set(name, value)
|
||||
|
||||
bindEditors: ->
|
||||
for editor in @find('.editor[id]').views()
|
||||
@@ -45,9 +43,16 @@ class ConfigPanel extends View
|
||||
value ?= ""
|
||||
editor.setText(value.toString())
|
||||
|
||||
editor.getBuffer().on 'contents-modified', ->
|
||||
value = editor.getText()
|
||||
if type == 'int' then value = parseInt(value) or 0
|
||||
if type == 'float' then value = parseFloat(value) or 0
|
||||
if value == "" then value = undefined
|
||||
config.set name, value
|
||||
editor.getBuffer().on 'contents-modified', =>
|
||||
config.set(name, @parseValue(type, editor.getText()))
|
||||
|
||||
parseValue: (type, value) ->
|
||||
switch type
|
||||
when 'int'
|
||||
intValue = parseInt(value)
|
||||
value = intValue unless isNaN(intValue)
|
||||
when 'float'
|
||||
floatValue = parseFloat(value)
|
||||
value = floatValue unless isNaN(floatValue)
|
||||
value = undefined if value == ''
|
||||
value
|
||||
|
||||
+79
-32
@@ -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'
|
||||
@@ -29,9 +29,7 @@ class Config
|
||||
settings: null
|
||||
configFileHasErrors: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
constructor: ->
|
||||
@defaultSettings =
|
||||
@@ -46,7 +44,6 @@ class Config
|
||||
|
||||
fsUtils.makeDirectory(@configDirPath)
|
||||
|
||||
|
||||
queue = async.queue ({sourcePath, destinationPath}, callback) =>
|
||||
fsUtils.copy(sourcePath, destinationPath, callback)
|
||||
queue.drain = done
|
||||
@@ -73,7 +70,7 @@ class Config
|
||||
loadUserConfig: ->
|
||||
if fsUtils.exists(@configFilePath)
|
||||
try
|
||||
userConfig = CSON.readObject(@configFilePath)
|
||||
userConfig = CSON.readFileSync(@configFilePath)
|
||||
_.extend(@settings, userConfig)
|
||||
@configFileHasErrors = false
|
||||
@trigger 'updated'
|
||||
@@ -90,29 +87,6 @@ class Config
|
||||
@watchSubscription?.close()
|
||||
@watchSubscription = null
|
||||
|
||||
# Public: Retrieves the setting for the given key.
|
||||
#
|
||||
# keyPath - The {String} name of the key to retrieve
|
||||
#
|
||||
# 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)
|
||||
|
||||
# Public: Sets the value for a configuration setting.
|
||||
#
|
||||
# This value is stored in Atom's internal configuration file.
|
||||
#
|
||||
# keyPath - The {String} name of the key
|
||||
# value - The value of the setting
|
||||
#
|
||||
# Returns the `value`.
|
||||
set: (keyPath, value) ->
|
||||
_.setValueForKeyPath(@settings, keyPath, value)
|
||||
@update()
|
||||
value
|
||||
|
||||
setDefaults: (keyPath, defaults) ->
|
||||
keys = keyPath.split('.')
|
||||
hash = @defaultSettings
|
||||
@@ -123,9 +97,80 @@ class Config
|
||||
_.extend hash, defaults
|
||||
@update()
|
||||
|
||||
# Public: Establishes an event listener for a given key.
|
||||
### Public ###
|
||||
|
||||
# Retrieves the setting for the given key.
|
||||
#
|
||||
# Whenever the value of the key is changed, a callback is fired.
|
||||
# keyPath - The {String} name of the key to retrieve
|
||||
#
|
||||
# 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) ->
|
||||
value = _.valueForKeyPath(@settings, keyPath) ? _.valueForKeyPath(@defaultSettings, keyPath)
|
||||
_.deepClone(value)
|
||||
|
||||
# Retrieves the setting for the given key as an integer.
|
||||
#
|
||||
# keyPath - The {String} name of the key to retrieve
|
||||
#
|
||||
# Returns the value from Atom's default settings, the user's configuration file,
|
||||
# or `NaN` if the key doesn't exist in either.
|
||||
getInt: (keyPath, defaultValueWhenFalsy) ->
|
||||
parseInt(@get(keyPath))
|
||||
|
||||
# Retrieves the setting for the given key as a positive integer.
|
||||
#
|
||||
# keyPath - The {String} name of the key to retrieve
|
||||
# defaultValue - The integer {Number} to fall back to if the value isn't
|
||||
# positive
|
||||
#
|
||||
# Returns the value from Atom's default settings, the user's configuration file,
|
||||
# or `defaultValue` if the key value isn't greater than zero.
|
||||
getPositiveInt: (keyPath, defaultValue) ->
|
||||
Math.max(@getInt(keyPath), 0) or defaultValue
|
||||
|
||||
# Sets the value for a configuration setting.
|
||||
#
|
||||
# This value is stored in Atom's internal configuration file.
|
||||
#
|
||||
# keyPath - The {String} name of the key
|
||||
# value - The value of the setting
|
||||
#
|
||||
# Returns the `value`.
|
||||
set: (keyPath, value) ->
|
||||
if @get(keyPath) != value
|
||||
value = undefined if _.valueForKeyPath(@defaultSettings, keyPath) == value
|
||||
_.setValueForKeyPath(@settings, keyPath, value)
|
||||
@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
|
||||
#
|
||||
# keyPath - The {String} name of the key to watch
|
||||
# callback - The {Function} that fires when the. It is given a single argument, `value`,
|
||||
@@ -144,12 +189,14 @@ class Config
|
||||
callback(value)
|
||||
subscription
|
||||
|
||||
### Internal ###
|
||||
|
||||
update: ->
|
||||
return if @configFileHasErrors
|
||||
@save()
|
||||
@trigger 'updated'
|
||||
|
||||
save: ->
|
||||
CSON.writeObject(@configFilePath, @settings)
|
||||
CSON.writeFileSync(@configFilePath, @settings)
|
||||
|
||||
_.extend Config.prototype, EventEmitter
|
||||
|
||||
@@ -3,7 +3,7 @@ Point = require 'point'
|
||||
Range = require 'range'
|
||||
_ = require 'underscore'
|
||||
|
||||
# Internal:
|
||||
### Internal ###
|
||||
module.exports =
|
||||
class CursorView extends View
|
||||
@content: ->
|
||||
|
||||
+57
-59
@@ -14,13 +14,11 @@ class Cursor
|
||||
visible: true
|
||||
needsAutoscroll: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@editSession, @marker}) ->
|
||||
@updateVisibility()
|
||||
@editSession.observeMarker @marker, (e) =>
|
||||
@marker.on 'changed', (e) =>
|
||||
@updateVisibility()
|
||||
{oldHeadScreenPosition, newHeadScreenPosition} = e
|
||||
{oldHeadBufferPosition, newHeadBufferPosition} = e
|
||||
@@ -42,7 +40,7 @@ class Cursor
|
||||
|
||||
destroy: ->
|
||||
@destroyed = true
|
||||
@editSession.destroyMarker(@marker)
|
||||
@marker.destroy()
|
||||
@editSession.removeCursor(this)
|
||||
@trigger 'destroyed'
|
||||
|
||||
@@ -53,47 +51,45 @@ class Cursor
|
||||
unless fn()
|
||||
@trigger 'autoscrolled' if @needsAutoscroll
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Moves a cursor to a given screen position.
|
||||
# Moves a cursor to a given screen position.
|
||||
#
|
||||
# screenPosition - An {Array} of two numbers: the screen row, and the screen column.
|
||||
# options - An object with the following keys:
|
||||
# :autoscroll - A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to
|
||||
# autoscroll: A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to
|
||||
#
|
||||
setScreenPosition: (screenPosition, options={}) ->
|
||||
@changePosition options, =>
|
||||
@editSession.setMarkerHeadScreenPosition(@marker, screenPosition, options)
|
||||
@marker.setHeadScreenPosition(screenPosition, options)
|
||||
|
||||
# Public: Gets the screen position of the cursor.
|
||||
# Gets the screen position of the cursor.
|
||||
#
|
||||
# Returns an {Array} of two numbers: the screen row, and the screen column.
|
||||
getScreenPosition: ->
|
||||
@editSession.getMarkerHeadScreenPosition(@marker)
|
||||
@marker.getHeadScreenPosition()
|
||||
|
||||
# Public: Moves a cursor to a given buffer position.
|
||||
# Moves a cursor to a given buffer position.
|
||||
#
|
||||
# bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column.
|
||||
# options - An object with the following keys:
|
||||
# :autoscroll - A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to
|
||||
# autoscroll: A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to
|
||||
#
|
||||
setBufferPosition: (bufferPosition, options={}) ->
|
||||
@changePosition options, =>
|
||||
@editSession.setMarkerHeadBufferPosition(@marker, bufferPosition, options)
|
||||
@marker.setHeadBufferPosition(bufferPosition, options)
|
||||
|
||||
# Public: Gets the current buffer position.
|
||||
# Gets the current buffer position.
|
||||
#
|
||||
# Returns an {Array} of two numbers: the buffer row, and the buffer column.
|
||||
getBufferPosition: ->
|
||||
@editSession.getMarkerHeadBufferPosition(@marker)
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
# If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
@setVisible(@editSession.isMarkerRangeEmpty(@marker))
|
||||
@setVisible(@marker.getBufferRange().isEmpty())
|
||||
|
||||
# Public: Sets the visibility of the cursor.
|
||||
# Sets the visibility of the cursor.
|
||||
#
|
||||
# visible - A {Boolean} indicating whether the cursor should be visible
|
||||
setVisible: (visible) ->
|
||||
@@ -102,25 +98,27 @@ class Cursor
|
||||
@needsAutoscroll ?= true if @visible and @isLastCursor()
|
||||
@trigger 'visibility-changed', @visible
|
||||
|
||||
# Public: Retrieves the visibility of the cursor.
|
||||
# Retrieves the visibility of the cursor.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isVisible: -> @visible
|
||||
|
||||
# Public: Identifies what the cursor considers a "word" RegExp.
|
||||
# Identifies what the cursor considers a "word" RegExp.
|
||||
#
|
||||
# Returns a {RegExp}.
|
||||
wordRegExp: ->
|
||||
nonWordCharacters = config.get("editor.nonWordCharacters")
|
||||
new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g")
|
||||
|
||||
# Public: Identifies if this cursor is the last in the {EditSession}.
|
||||
# Identifies if this cursor is the last in the {EditSession}.
|
||||
#
|
||||
# "Last" is defined as the most recently added cursor.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isLastCursor: ->
|
||||
this == @editSession.getCursor()
|
||||
|
||||
# Public: Identifies if the cursor is surrounded by whitespace.
|
||||
# Identifies if the cursor is surrounded by whitespace.
|
||||
#
|
||||
# "Surrounded" here means that all characters before and after the cursor is whitespace.
|
||||
#
|
||||
@@ -130,84 +128,84 @@ class Cursor
|
||||
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
|
||||
/^\s+$/.test @editSession.getTextInBufferRange(range)
|
||||
|
||||
# Public: Removes the setting for auto-scroll.
|
||||
# Removes the setting for auto-scroll.
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
# Public: Deselects whatever the cursor is selecting.
|
||||
# Deselects whatever the cursor is selecting.
|
||||
clearSelection: ->
|
||||
if @selection
|
||||
@selection.goalBufferRange = null
|
||||
@selection.clear() unless @selection.retainSelection
|
||||
|
||||
# Public: Retrieves the cursor's screen row.
|
||||
# Retrieves the cursor's screen row.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getScreenRow: ->
|
||||
@getScreenPosition().row
|
||||
|
||||
# Public: Retrieves the cursor's screen column.
|
||||
# Retrieves the cursor's screen column.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getScreenColumn: ->
|
||||
@getScreenPosition().column
|
||||
|
||||
# Public: Retrieves the cursor's buffer row.
|
||||
# Retrieves the cursor's buffer row.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getBufferRow: ->
|
||||
@getBufferPosition().row
|
||||
|
||||
# Public: Retrieves the cursor's buffer column.
|
||||
# Retrieves the cursor's buffer column.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getBufferColumn: ->
|
||||
@getBufferPosition().column
|
||||
|
||||
# Public: Retrieves the cursor's buffer row text.
|
||||
# Retrieves the cursor's buffer row text.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getCurrentBufferLine: ->
|
||||
@editSession.lineForBufferRow(@getBufferRow())
|
||||
|
||||
# Public: Moves the cursor up one screen row.
|
||||
# Moves the cursor up one screen row.
|
||||
moveUp: (rowCount = 1) ->
|
||||
{ row, column } = @getScreenPosition()
|
||||
column = @goalColumn if @goalColumn?
|
||||
@setScreenPosition({row: row - rowCount, column: column})
|
||||
@goalColumn = column
|
||||
|
||||
# Public: Moves the cursor down one screen row.
|
||||
# Moves the cursor down one screen row.
|
||||
moveDown: (rowCount = 1) ->
|
||||
{ row, column } = @getScreenPosition()
|
||||
column = @goalColumn if @goalColumn?
|
||||
@setScreenPosition({row: row + rowCount, column: column})
|
||||
@goalColumn = column
|
||||
|
||||
# Public: Moves the cursor left one screen column.
|
||||
# Moves the cursor left one screen column.
|
||||
moveLeft: ->
|
||||
{ row, column } = @getScreenPosition()
|
||||
[row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity]
|
||||
@setScreenPosition({row, column})
|
||||
|
||||
# Public: Moves the cursor right one screen column.
|
||||
# Moves the cursor right one screen column.
|
||||
moveRight: ->
|
||||
{ row, column } = @getScreenPosition()
|
||||
@setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
|
||||
|
||||
# Public: Moves the cursor to the top of the buffer.
|
||||
# Moves the cursor to the top of the buffer.
|
||||
moveToTop: ->
|
||||
@setBufferPosition([0,0])
|
||||
|
||||
# Public: Moves the cursor to the bottom of the buffer.
|
||||
# Moves the cursor to the bottom of the buffer.
|
||||
moveToBottom: ->
|
||||
@setBufferPosition(@editSession.getEofBufferPosition())
|
||||
|
||||
# Public: Moves the cursor to the beginning of the buffer line.
|
||||
# Moves the cursor to the beginning of the buffer line.
|
||||
moveToBeginningOfLine: ->
|
||||
@setBufferPosition([@getBufferRow(), 0])
|
||||
|
||||
# Public: Moves the cursor to the beginning of the first character in the line.
|
||||
# Moves the cursor to the beginning of the first character in the line.
|
||||
moveToFirstCharacterOfLine: ->
|
||||
position = @getBufferPosition()
|
||||
scanRange = @getCurrentLineBufferRange()
|
||||
@@ -218,7 +216,7 @@ class Cursor
|
||||
newPosition = [position.row, 0] if newPosition.isEqual(position)
|
||||
@setBufferPosition(newPosition)
|
||||
|
||||
# Public: Moves the cursor to the beginning of the buffer line, skipping all whitespace.
|
||||
# Moves the cursor to the beginning of the buffer line, skipping all whitespace.
|
||||
skipLeadingWhitespace: ->
|
||||
position = @getBufferPosition()
|
||||
scanRange = @getCurrentLineBufferRange()
|
||||
@@ -228,28 +226,28 @@ class Cursor
|
||||
|
||||
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
|
||||
|
||||
# Public: Moves the cursor to the end of the buffer line.
|
||||
# Moves the cursor to the end of the buffer line.
|
||||
moveToEndOfLine: ->
|
||||
@setBufferPosition([@getBufferRow(), Infinity])
|
||||
|
||||
# Public: Moves the cursor to the beginning of the word.
|
||||
# Moves the cursor to the beginning of the word.
|
||||
moveToBeginningOfWord: ->
|
||||
@setBufferPosition(@getBeginningOfCurrentWordBufferPosition())
|
||||
|
||||
# Public: Moves the cursor to the end of the word.
|
||||
# Moves the cursor to the end of the word.
|
||||
moveToEndOfWord: ->
|
||||
if position = @getEndOfCurrentWordBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
|
||||
# Public: Moves the cursor to the beginning of the next word.
|
||||
# Moves the cursor to the beginning of the next word.
|
||||
moveToBeginningOfNextWord: ->
|
||||
if position = @getBeginningOfNextWordBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
|
||||
# Public: Retrieves the buffer position of where the current word starts.
|
||||
# Retrieves the buffer position of where the current word starts.
|
||||
#
|
||||
# options - A hash with one option:
|
||||
# :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
|
||||
# wordRegex: A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBeginningOfCurrentWordBufferPosition: (options = {}) ->
|
||||
@@ -267,10 +265,10 @@ class Cursor
|
||||
|
||||
beginningOfWordPosition or currentBufferPosition
|
||||
|
||||
# Public: Retrieves the buffer position of where the current word ends.
|
||||
# Retrieves the buffer position of where the current word ends.
|
||||
#
|
||||
# options - A hash with one option:
|
||||
# :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
|
||||
# wordRegex: A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getEndOfCurrentWordBufferPosition: (options = {}) ->
|
||||
@@ -287,10 +285,10 @@ class Cursor
|
||||
|
||||
endOfWordPosition ? currentBufferPosition
|
||||
|
||||
# Public: Retrieves the buffer position of where the next word starts.
|
||||
# Retrieves the buffer position of where the next word starts.
|
||||
#
|
||||
# options - A hash with one option:
|
||||
# :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
|
||||
# wordRegex: A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBeginningOfNextWordBufferPosition: (options = {}) ->
|
||||
@@ -305,7 +303,7 @@ class Cursor
|
||||
|
||||
beginningOfNextWordPosition or currentBufferPosition
|
||||
|
||||
# Public: Gets the word located under the cursor.
|
||||
# Gets the word located under the cursor.
|
||||
#
|
||||
# options - An object with properties based on {.getBeginningOfCurrentWordBufferPosition}.
|
||||
#
|
||||
@@ -315,7 +313,7 @@ class Cursor
|
||||
endOptions = _.extend(_.clone(options), allowNext: false)
|
||||
new Range(@getBeginningOfCurrentWordBufferPosition(startOptions), @getEndOfCurrentWordBufferPosition(endOptions))
|
||||
|
||||
# Public: Retrieves the range for the current line.
|
||||
# Retrieves the range for the current line.
|
||||
#
|
||||
# options - A hash with the same keys as {EditSession.bufferRangeForBufferRow}
|
||||
#
|
||||
@@ -323,7 +321,7 @@ class Cursor
|
||||
getCurrentLineBufferRange: (options) ->
|
||||
@editSession.bufferRangeForBufferRow(@getBufferRow(), options)
|
||||
|
||||
# Public: Retrieves the range for the current paragraph.
|
||||
# Retrieves the range for the current paragraph.
|
||||
#
|
||||
# A paragraph is defined as a block of text surrounded by empty lines.
|
||||
#
|
||||
@@ -345,19 +343,19 @@ class Cursor
|
||||
|
||||
new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)])
|
||||
|
||||
# Public: Retrieves the characters that constitute a word preceeding the current cursor position.
|
||||
# Retrieves the characters that constitute a word preceeding the current cursor position.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getCurrentWordPrefix: ->
|
||||
@editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
|
||||
|
||||
# Public: Identifies if the cursor is at the start of a line.
|
||||
# Identifies if the cursor is at the start of a line.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isAtBeginningOfLine: ->
|
||||
@getBufferPosition().column == 0
|
||||
|
||||
# Public: Retrieves the indentation level of the current line.
|
||||
# Retrieves the indentation level of the current line.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getIndentLevel: ->
|
||||
@@ -366,13 +364,13 @@ class Cursor
|
||||
else
|
||||
@getBufferColumn()
|
||||
|
||||
# Public: Identifies if the cursor is at the end of a line.
|
||||
# Identifies if the cursor is at the end of a line.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isAtEndOfLine: ->
|
||||
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
|
||||
|
||||
# Public: Retrieves the grammar's token scopes for the line.
|
||||
# Retrieves the grammar's token scopes for the line.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getScopes: ->
|
||||
|
||||
@@ -12,24 +12,26 @@ module.exports =
|
||||
class Directory
|
||||
path: null
|
||||
|
||||
# Public: Creates a new directory.
|
||||
### Public ###
|
||||
|
||||
# Creates a new directory.
|
||||
#
|
||||
# path - A {String} representing the file directory
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false)
|
||||
constructor: (@path, @symlink=false) ->
|
||||
|
||||
# Public: Retrieves the basename of the directory.
|
||||
# Retrieves the basename of the directory.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
fsUtils.base(@path)
|
||||
|
||||
# Public: Retrieves the directory's path.
|
||||
# Retrieves the directory's path.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Retrieves the file entries in the directory.
|
||||
# Retrieves the file entries in the directory.
|
||||
#
|
||||
# This does follow symlinks.
|
||||
#
|
||||
@@ -51,9 +53,7 @@ class Directory
|
||||
|
||||
directories.concat(files)
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
afterSubscribe: ->
|
||||
@subscribeToNativeChangeEvents() if @subscriptionCount() == 1
|
||||
|
||||
@@ -9,50 +9,47 @@ class DisplayBufferMarker
|
||||
tailScreenPosition: null
|
||||
valid: true
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@id, @displayBuffer}) ->
|
||||
@buffer = @displayBuffer.buffer
|
||||
constructor: ({@bufferMarker, @displayBuffer}) ->
|
||||
@id = @bufferMarker.id
|
||||
@observeBufferMarker()
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Gets the screen range of the display marker.
|
||||
# Gets the screen range of the display marker.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getScreenRange: ->
|
||||
@displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Public: Modifies the screen range of the display marker.
|
||||
# Modifies the screen range of the display marker.
|
||||
#
|
||||
# screenRange - The new {Range} to use
|
||||
# options - A hash of options matching those found in {BufferMarker.setRange}
|
||||
setScreenRange: (screenRange, options) ->
|
||||
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
|
||||
|
||||
# Public: Gets the buffer range of the display marker.
|
||||
# Gets the buffer range of the display marker.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ->
|
||||
@buffer.getMarkerRange(@id)
|
||||
@bufferMarker.getRange()
|
||||
|
||||
# Public: Modifies the buffer range of the display marker.
|
||||
# Modifies the buffer range of the display marker.
|
||||
#
|
||||
# screenRange - The new {Range} to use
|
||||
# options - A hash of options matching those found in {BufferMarker.setRange}
|
||||
setBufferRange: (bufferRange, options) ->
|
||||
@buffer.setMarkerRange(@id, bufferRange, options)
|
||||
@bufferMarker.setRange(bufferRange, options)
|
||||
|
||||
# Public: Retrieves the screen position of the marker's head.
|
||||
# Retrieves the screen position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadScreenPosition: ->
|
||||
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Public: Sets the screen position of the marker's head.
|
||||
# Sets the screen position of the marker's head.
|
||||
#
|
||||
# screenRange - The new {Point} to use
|
||||
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
|
||||
@@ -60,26 +57,26 @@ class DisplayBufferMarker
|
||||
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
|
||||
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
|
||||
|
||||
# Public: Retrieves the buffer position of the marker's head.
|
||||
# Retrieves the buffer position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getHeadBufferPosition: ->
|
||||
@buffer.getMarkerHeadPosition(@id)
|
||||
@bufferMarker.getHeadPosition()
|
||||
|
||||
# Public: Sets the buffer position of the marker's head.
|
||||
# Sets the buffer position of the marker's head.
|
||||
#
|
||||
# screenRange - The new {Point} to use
|
||||
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
|
||||
setHeadBufferPosition: (bufferPosition) ->
|
||||
@buffer.setMarkerHeadPosition(@id, bufferPosition)
|
||||
@bufferMarker.setHeadPosition(bufferPosition)
|
||||
|
||||
# Public: Retrieves the screen position of the marker's tail.
|
||||
# Retrieves the screen position of the marker's tail.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailScreenPosition: ->
|
||||
@tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Public: Sets the screen position of the marker's tail.
|
||||
|
||||
# Sets the screen position of the marker's tail.
|
||||
#
|
||||
# screenRange - The new {Point} to use
|
||||
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
|
||||
@@ -87,71 +84,81 @@ class DisplayBufferMarker
|
||||
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
|
||||
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
|
||||
|
||||
# Public: Retrieves the buffer position of the marker's tail.
|
||||
# Retrieves the buffer position of the marker's tail.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getTailBufferPosition: ->
|
||||
@buffer.getMarkerTailPosition(@id)
|
||||
|
||||
# Public: Sets the buffer position of the marker's tail.
|
||||
@bufferMarker.getTailPosition()
|
||||
|
||||
# Sets the buffer position of the marker's tail.
|
||||
#
|
||||
# screenRange - The new {Point} to use
|
||||
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
|
||||
setTailBufferPosition: (bufferPosition) ->
|
||||
@buffer.setMarkerTailPosition(@id, bufferPosition)
|
||||
@bufferMarker.setTailPosition(bufferPosition)
|
||||
|
||||
# Public: Sets the marker's tail to the same position as the marker's head.
|
||||
# Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
placeTail: ->
|
||||
@buffer.placeMarkerTail(@id)
|
||||
@bufferMarker.placeTail()
|
||||
|
||||
# Public: Removes the tail from the marker.
|
||||
# Removes the tail from the marker.
|
||||
clearTail: ->
|
||||
@buffer.clearMarkerTail(@id)
|
||||
@bufferMarker.clearTail()
|
||||
|
||||
# Public: Sets a callback to be fired whenever the marker is changed.
|
||||
#
|
||||
# callback - A {Function} to execute
|
||||
observe: (callback) ->
|
||||
@observeBufferMarkerIfNeeded()
|
||||
@on 'changed', callback
|
||||
cancel: => @unobserve(callback)
|
||||
# Returns whether the head precedes the tail in the buffer
|
||||
isReversed: ->
|
||||
@bufferMarker.isReversed()
|
||||
|
||||
# Public: Removes the callback that's fired whenever the marker changes.
|
||||
#
|
||||
# callback - A {Function} to remove
|
||||
unobserve: (callback) ->
|
||||
@off 'changed', callback
|
||||
@unobserveBufferMarkerIfNeeded()
|
||||
# Returns a {Boolean} indicating whether the marker is valid. Markers can be
|
||||
# invalidated when a region surrounding them in the buffer is changed.
|
||||
isValid: ->
|
||||
@bufferMarker.isValid()
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
|
||||
# can be invalid without being destroyed, in which case undoing the invalidating
|
||||
# operation would restore the marker. Once a marker is destroyed by calling
|
||||
# {BufferMarker.destroy}, no undo/redo operation can ever bring it back.
|
||||
isDestroyed: ->
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
matchesAttributes: (attributes) ->
|
||||
@bufferMarker.matchesAttributes(attributes)
|
||||
|
||||
# Destroys the marker
|
||||
destroy: ->
|
||||
@bufferMarker.destroy()
|
||||
|
||||
# Returns a {String} representation of the marker
|
||||
inspect: ->
|
||||
"DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange().inspect()})"
|
||||
|
||||
### Internal ###
|
||||
|
||||
destroyed: ->
|
||||
delete @displayBuffer.markers[@id]
|
||||
@trigger 'destroyed'
|
||||
|
||||
observeBufferMarker: ->
|
||||
@bufferMarker.on 'destroyed', => @destroyed()
|
||||
|
||||
observeBufferMarkerIfNeeded: ->
|
||||
return if @subscriptionCount()
|
||||
@getHeadScreenPosition() # memoize current value
|
||||
@getTailScreenPosition() # memoize current value
|
||||
@bufferMarkerSubscription =
|
||||
@buffer.observeMarker @id, ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) =>
|
||||
@notifyObservers
|
||||
oldHeadBufferPosition: oldHeadPosition
|
||||
newHeadBufferPosition: newHeadPosition
|
||||
oldTailBufferPosition: oldTailPosition
|
||||
newTailBufferPosition: newTailPosition
|
||||
bufferChanged: bufferChanged
|
||||
valid: valid
|
||||
@displayBuffer.markers[@id] = this
|
||||
|
||||
unobserveBufferMarkerIfNeeded: ->
|
||||
return if @subscriptionCount()
|
||||
@bufferMarkerSubscription.cancel()
|
||||
delete @displayBuffer.markers[@id]
|
||||
@bufferMarker.on 'changed', ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) =>
|
||||
@notifyObservers
|
||||
oldHeadBufferPosition: oldHeadPosition
|
||||
newHeadBufferPosition: newHeadPosition
|
||||
oldTailBufferPosition: oldTailPosition
|
||||
newTailBufferPosition: newTailPosition
|
||||
bufferChanged: bufferChanged
|
||||
valid: valid
|
||||
|
||||
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) ->
|
||||
return unless @valid or @isValid()
|
||||
|
||||
oldHeadScreenPosition = @getHeadScreenPosition()
|
||||
newHeadScreenPosition = oldHeadScreenPosition
|
||||
oldTailScreenPosition = @getTailScreenPosition()
|
||||
|
||||
+310
-420
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+236
-577
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -15,7 +15,7 @@ class EditorConfigPanel extends ConfigPanel
|
||||
@div class: 'control-group', =>
|
||||
@label class: 'control-label', "Font Size:"
|
||||
@div class: 'controls', =>
|
||||
@subview "fontSizeEditor", new Editor(mini: true, attributes: {id: 'editor.fontSize', type: 'int', style: 'width: 40px'})
|
||||
@subview "fontSizeEditor", new Editor(mini: true, attributes: {id: 'editor.fontSize', type: 'int', style: 'width: 4em'})
|
||||
|
||||
@div class: 'control-group', =>
|
||||
@label class: 'control-label', "Font Family:"
|
||||
@@ -56,7 +56,7 @@ class EditorConfigPanel extends ConfigPanel
|
||||
@div class: 'control-group', =>
|
||||
@label class: 'control-label', for: 'editor.preferredLineLength', "Preferred Line Length:"
|
||||
@div class: 'controls', =>
|
||||
@subview "preferredLineLengthEditor", new Editor(mini: true, attributes: {id: 'editor.preferredLineLength', type: 'int', style: 'width: 40px'})
|
||||
@subview "preferredLineLengthEditor", new Editor(mini: true, attributes: {id: 'editor.preferredLineLength', type: 'int', style: 'width: 4em'})
|
||||
|
||||
@div class: 'control-group', =>
|
||||
@label class: 'control-label', for: 'editor.nonWordCharacters', "Non-Word Characters:"
|
||||
|
||||
+345
-532
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -4,7 +4,7 @@ _ = require 'underscore'
|
||||
#
|
||||
# Each event can have more than one handler; that is, an event can trigger multiple functions.
|
||||
module.exports =
|
||||
# Public: Associates an event name with a function to perform.
|
||||
# Associates an event name with a function to perform.
|
||||
#
|
||||
# This is called endlessly, until the event is turned {.off}.
|
||||
#
|
||||
@@ -27,7 +27,7 @@ module.exports =
|
||||
@afterSubscribe?()
|
||||
|
||||
|
||||
# Public: Associates an event name with a function to perform only once.
|
||||
# Associates an event name with a function to perform only once.
|
||||
#
|
||||
# eventName - A {String} name identifying an event
|
||||
# handler - A {Function} that's executed when the event is triggered
|
||||
@@ -38,7 +38,7 @@ module.exports =
|
||||
|
||||
@on eventName, oneShotHandler
|
||||
|
||||
# Public: Triggers a registered event.
|
||||
# Triggers a registered event.
|
||||
#
|
||||
# eventName - A {String} name identifying an event
|
||||
# args - Any additional arguments to pass over to the event `handler`
|
||||
@@ -55,7 +55,7 @@ module.exports =
|
||||
if handlers = @eventHandlersByEventName?[eventName]
|
||||
handlers.forEach (handler) -> handler(args...)
|
||||
|
||||
# Public: Stops executing handlers for a registered event.
|
||||
# Stops executing handlers for a registered event.
|
||||
#
|
||||
# eventNames - A {String} containing one or more space-separated events.
|
||||
# handler - The {Function} to remove from the event. If not provided, all handlers are removed.
|
||||
@@ -90,20 +90,20 @@ module.exports =
|
||||
@eventHandlersByNamespace = {}
|
||||
@afterUnsubscribe?() if @subscriptionCount() < subscriptionCountBefore
|
||||
|
||||
# Public: When called, stops triggering any events.
|
||||
# When called, stops triggering any events.
|
||||
pauseEvents: ->
|
||||
@pauseCount ?= 0
|
||||
if @pauseCount++ == 0
|
||||
@queuedEvents ?= []
|
||||
|
||||
# Public: When called, resumes triggering events.
|
||||
# When called, resumes triggering events.
|
||||
resumeEvents: ->
|
||||
if --@pauseCount == 0
|
||||
queuedEvents = @queuedEvents
|
||||
@queuedEvents = null
|
||||
@trigger(event...) for event in queuedEvents
|
||||
|
||||
# Public: Identifies how many events are registered.
|
||||
# Identifies how many events are registered.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
subscriptionCount: ->
|
||||
|
||||
+10
-12
@@ -7,7 +7,7 @@ _ = require 'underscore'
|
||||
|
||||
# Public: Represents an individual file in the editor.
|
||||
#
|
||||
# The entry point for this class is in two locations:
|
||||
# The entry point for this class is in two locations:
|
||||
# * {Buffer}, which associates text contents with a file
|
||||
# * {Directory}, which associcates the children of a directory as files
|
||||
module.exports =
|
||||
@@ -15,7 +15,7 @@ class File
|
||||
path: null
|
||||
cachedContents: null
|
||||
|
||||
# Public: Creates a new file.
|
||||
# Creates a new file.
|
||||
#
|
||||
# path - A {String} representing the file path
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false)
|
||||
@@ -24,23 +24,23 @@ class File
|
||||
if fs.statSync(@path).isDirectory()
|
||||
throw new Error("#{@path} is a directory")
|
||||
|
||||
# Public: Sets the path for the file.
|
||||
# Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
setPath: (@path) ->
|
||||
|
||||
# Public: Retrieves the path for the file.
|
||||
# Retrieves the path for the file.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Gets the file's basename--that is, the file without any directory information.
|
||||
# Gets the file's basename--that is, the file without any directory information.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
fsUtils.base(@path)
|
||||
|
||||
# Public: Writes (and saves) new contents to the file.
|
||||
# Writes (and saves) new contents to the file.
|
||||
#
|
||||
# text - A {String} representing the new contents.
|
||||
write: (text) ->
|
||||
@@ -49,7 +49,7 @@ class File
|
||||
fsUtils.write(@getPath(), text)
|
||||
@subscribeToNativeChangeEvents() if not previouslyExisted and @subscriptionCount() > 0
|
||||
|
||||
# Public: Reads the file.
|
||||
# Reads the file.
|
||||
#
|
||||
# flushCache - A {Boolean} indicating if the cache should be erased--_i.e._, a force read is performed
|
||||
#
|
||||
@@ -62,19 +62,17 @@ class File
|
||||
else
|
||||
@cachedContents
|
||||
|
||||
# Public: Checks to see if a file exists.
|
||||
# Checks to see if a file exists.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
exists: ->
|
||||
fsUtils.exists(@getPath())
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
afterSubscribe: ->
|
||||
@subscribeToNativeChangeEvents() if @exists() and @subscriptionCount() == 1
|
||||
|
||||
|
||||
afterUnsubscribe: ->
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
|
||||
+43
-49
@@ -1,73 +1,69 @@
|
||||
Range = require 'range'
|
||||
Point = require 'point'
|
||||
|
||||
# Public: Represents a fold that's hiding text from the screen.
|
||||
# Public: Represents a fold that collapses multiple buffer lines into a single
|
||||
# line on the screen.
|
||||
#
|
||||
# Folds are the primary reason that screen ranges and buffer ranges vary. Their
|
||||
# creation is managed by the {DisplayBuffer}.
|
||||
# Their creation is managed by the {DisplayBuffer}.
|
||||
module.exports =
|
||||
class Fold
|
||||
@idCounter: 1
|
||||
|
||||
id: null
|
||||
displayBuffer: null
|
||||
startRow: null
|
||||
endRow: null
|
||||
marker: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
constructor: (@displayBuffer, @startRow, @endRow) ->
|
||||
@id = @constructor.idCounter++
|
||||
constructor: (@displayBuffer, @marker) ->
|
||||
@id = @marker.id
|
||||
@displayBuffer.foldsByMarkerId[@marker.id] = this
|
||||
@updateDisplayBuffer()
|
||||
@marker.on 'destroyed', => @destroyed()
|
||||
|
||||
# Returns whether this fold is contained within another fold
|
||||
isInsideLargerFold: ->
|
||||
@displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())?
|
||||
|
||||
# Destroys this fold
|
||||
destroy: ->
|
||||
@displayBuffer.destroyFold(this)
|
||||
@marker.destroy()
|
||||
|
||||
inspect: ->
|
||||
"Fold(#{@startRow}, #{@endRow})"
|
||||
|
||||
# Public: Retrieves the buffer row range that a fold occupies.
|
||||
# Returns the fold's {Range} in buffer coordinates
|
||||
#
|
||||
# includeNewline - A {Boolean} which, if `true`, includes the trailing newline
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ({includeNewline}={}) ->
|
||||
range = @marker.getRange()
|
||||
if includeNewline
|
||||
end = [@endRow + 1, 0]
|
||||
else
|
||||
end = [@endRow, Infinity]
|
||||
range.end.row++
|
||||
range.end.column = 0
|
||||
range
|
||||
|
||||
new Range([@startRow, 0], end)
|
||||
# Returns the fold's start row as a {Number}.
|
||||
getStartRow: ->
|
||||
@getBufferRange().start.row
|
||||
|
||||
# Public: Retrieves the number of buffer rows a fold occupies.
|
||||
# Retrieves the number of buffer rows a fold occupies.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getBufferRowCount: ->
|
||||
@endRow - @startRow + 1
|
||||
|
||||
handleBufferChange: (event) ->
|
||||
oldStartRow = @startRow
|
||||
# Returns the fold's end row as a {Number}.
|
||||
getEndRow: ->
|
||||
@getBufferRange().end.row
|
||||
|
||||
if @isContainedByRange(event.oldRange)
|
||||
@displayBuffer.unregisterFold(@startRow, this)
|
||||
return
|
||||
# Returns a {String} representation of the fold.
|
||||
inspect: ->
|
||||
"Fold(#{@getStartRow()}, #{@getEndRow()})"
|
||||
|
||||
@startRow += @getRowDelta(event, @startRow)
|
||||
@endRow += @getRowDelta(event, @endRow)
|
||||
|
||||
if @startRow != oldStartRow
|
||||
@displayBuffer.unregisterFold(oldStartRow, this)
|
||||
@displayBuffer.registerFold(this)
|
||||
|
||||
# Public: Identifies if a {Range} occurs within a fold.
|
||||
# Retrieves the number of buffer rows spanned by the fold.
|
||||
#
|
||||
# range - A {Range} to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isContainedByRange: (range) ->
|
||||
range.start.row <= @startRow and @endRow <= range.end.row
|
||||
# Returns a {Number}.
|
||||
getBufferRowCount: ->
|
||||
@getEndRow() - @getStartRow() + 1
|
||||
|
||||
# Public: Identifies if a fold is nested within a fold.
|
||||
# Identifies if a fold is nested within a fold.
|
||||
#
|
||||
# fold - A {Fold} to check
|
||||
#
|
||||
@@ -75,12 +71,10 @@ class Fold
|
||||
isContainedByFold: (fold) ->
|
||||
@isContainedByRange(fold.getBufferRange())
|
||||
|
||||
getRowDelta: (event, row) ->
|
||||
{ newRange, oldRange } = event
|
||||
updateDisplayBuffer: ->
|
||||
unless @isInsideLargerFold()
|
||||
@displayBuffer.updateScreenLines(@getStartRow(), @getEndRow() + 1, 0, updateMarkers: true)
|
||||
|
||||
if oldRange.end.row <= row
|
||||
newRange.end.row - oldRange.end.row
|
||||
else if newRange.end.row < row
|
||||
newRange.end.row - row
|
||||
else
|
||||
0
|
||||
destroyed: ->
|
||||
delete @displayBuffer.foldsByMarkerId[@marker.id]
|
||||
@updateDisplayBuffer()
|
||||
|
||||
+38
-44
@@ -11,33 +11,17 @@ GitUtils = require 'git-utils'
|
||||
module.exports =
|
||||
class Git
|
||||
|
||||
# Public: Creates a new `Git` instance.
|
||||
#
|
||||
# path - The git repository to open
|
||||
# options - A hash with one key:
|
||||
# :refreshOnWindowFocus - A {Boolean} that identifies if the windows should refresh
|
||||
#
|
||||
# Returns a new {Git} object.
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
null
|
||||
|
||||
statuses: null
|
||||
upstream: null
|
||||
statusTask: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
# Internal: Creates a new `Git` object.
|
||||
# Creates a new `Git` object.
|
||||
#
|
||||
# path - The {String} representing the path to your git working directory
|
||||
# options - A hash with the following keys:
|
||||
# :refreshOnWindowFocus - If `true`, {#refreshIndex} and {#refreshStatus} are called on focus
|
||||
# refreshOnWindowFocus: If `true`, {#refreshIndex} and {#refreshStatus} are called on focus
|
||||
constructor: (path, options={}) ->
|
||||
@repo = GitUtils.open(path)
|
||||
unless @repo?
|
||||
@@ -72,11 +56,23 @@ class Git
|
||||
|
||||
@unsubscribe()
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Retrieves the git repository.
|
||||
# Creates a new `Git` instance.
|
||||
#
|
||||
# path - The git repository to open
|
||||
# options - A hash with one key:
|
||||
# refreshOnWindowFocus: A {Boolean} that identifies if the windows should refresh
|
||||
#
|
||||
# Returns a new {Git} object.
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
null
|
||||
|
||||
# Retrieves the git repository.
|
||||
#
|
||||
# Returns a new `Repository`.
|
||||
getRepo: ->
|
||||
@@ -84,22 +80,22 @@ class Git
|
||||
throw new Error("Repository has been destroyed")
|
||||
@repo
|
||||
|
||||
# Public: Reread the index to update any values that have changed since the last time the index was read.
|
||||
# Reread the index to update any values that have changed since the last time the index was read.
|
||||
refreshIndex: -> @getRepo().refreshIndex()
|
||||
|
||||
# Public: Retrieves the path of the repository.
|
||||
# Retrieves the path of the repository.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: ->
|
||||
@path ?= fsUtils.absolute(@getRepo().getPath())
|
||||
|
||||
# Public: Retrieves the working directory of the repository.
|
||||
# Retrieves the working directory of the repository.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getWorkingDirectory: ->
|
||||
@getRepo().getWorkingDirectory()
|
||||
|
||||
# Public: Retrieves the reference or SHA-1 that `HEAD` points to.
|
||||
# Retrieves the reference or SHA-1 that `HEAD` points to.
|
||||
#
|
||||
# This can be `refs/heads/master`, or a full SHA-1 if the repository is in a detached `HEAD` state.
|
||||
#
|
||||
@@ -107,7 +103,7 @@ class Git
|
||||
getHead: ->
|
||||
@getRepo().getHead() ? ''
|
||||
|
||||
# Public: Retrieves the status of a single path in the repository.
|
||||
# Retrieves the status of a single path in the repository.
|
||||
#
|
||||
# path - An {String} defining a relative path
|
||||
#
|
||||
@@ -123,7 +119,7 @@ class Git
|
||||
@trigger 'status-changed', path, pathStatus
|
||||
pathStatus
|
||||
|
||||
# Public: Identifies if a path is ignored.
|
||||
# Identifies if a path is ignored.
|
||||
#
|
||||
# path - The {String} path to check
|
||||
#
|
||||
@@ -131,7 +127,7 @@ class Git
|
||||
isPathIgnored: (path) ->
|
||||
@getRepo().isIgnored(@relativize(path))
|
||||
|
||||
# Public: Identifies if a value represents a status code.
|
||||
# Identifies if a value represents a status code.
|
||||
#
|
||||
# status - The code {Number} to check
|
||||
#
|
||||
@@ -139,7 +135,7 @@ class Git
|
||||
isStatusModified: (status) ->
|
||||
@getRepo().isStatusModified(status)
|
||||
|
||||
# Public: Identifies if a path was modified.
|
||||
# Identifies if a path was modified.
|
||||
#
|
||||
# path - The {String} path to check
|
||||
#
|
||||
@@ -147,7 +143,7 @@ class Git
|
||||
isPathModified: (path) ->
|
||||
@isStatusModified(@getPathStatus(path))
|
||||
|
||||
# Public: Identifies if a status code represents a new path.
|
||||
# Identifies if a status code represents a new path.
|
||||
#
|
||||
# status - The code {Number} to check
|
||||
#
|
||||
@@ -155,7 +151,7 @@ class Git
|
||||
isStatusNew: (status) ->
|
||||
@getRepo().isStatusNew(status)
|
||||
|
||||
# Public: Identifies if a path is new.
|
||||
# Identifies if a path is new.
|
||||
#
|
||||
# path - The {String} path to check
|
||||
#
|
||||
@@ -163,7 +159,7 @@ class Git
|
||||
isPathNew: (path) ->
|
||||
@isStatusNew(@getPathStatus(path))
|
||||
|
||||
# Public: Makes a path relative to the repository's working directory.
|
||||
# Makes a path relative to the repository's working directory.
|
||||
#
|
||||
# path - The {String} path to convert
|
||||
#
|
||||
@@ -171,7 +167,7 @@ class Git
|
||||
relativize: (path) ->
|
||||
@getRepo().relativize(path)
|
||||
|
||||
# Public: Retrieves a shortened version of {.getHead}.
|
||||
# Retrieves a shortened version of {.getHead}.
|
||||
#
|
||||
# This removes the leading segments of `refs/heads`, `refs/tags`, or `refs/remotes`.
|
||||
# It also shortenes the SHA-1 of a detached `HEAD` to 7 characters.
|
||||
@@ -180,7 +176,7 @@ class Git
|
||||
getShortHead: ->
|
||||
@getRepo().getShortHead()
|
||||
|
||||
# Public: Restore the contents of a path in the working directory and index to the version at `HEAD`.
|
||||
# Restore the contents of a path in the working directory and index to the version at `HEAD`.
|
||||
#
|
||||
# This is essentially the same as running:
|
||||
# ```
|
||||
@@ -196,7 +192,7 @@ class Git
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# Public: Retrieves the number of lines added and removed to a path.
|
||||
# Retrieves the number of lines added and removed to a path.
|
||||
#
|
||||
# This compares the working directory contents of the path to the `HEAD` version.
|
||||
#
|
||||
@@ -206,7 +202,7 @@ class Git
|
||||
getDiffStats: (path) ->
|
||||
@getRepo().getDiffStats(@relativize(path))
|
||||
|
||||
# Public: Identifies if a path is a submodule.
|
||||
# Identifies if a path is a submodule.
|
||||
#
|
||||
# path - The {String} path to check
|
||||
#
|
||||
@@ -214,7 +210,7 @@ class Git
|
||||
isSubmodule: (path) ->
|
||||
@getRepo().isSubmodule(@relativize(path))
|
||||
|
||||
# Public: Retrieves the status of a directory.
|
||||
# Retrieves the status of a directory.
|
||||
#
|
||||
# path - The {String} path to check
|
||||
#
|
||||
@@ -226,7 +222,7 @@ class Git
|
||||
directoryStatus |= status if path.indexOf(directoryPath) is 0
|
||||
directoryStatus
|
||||
|
||||
# Public: Retrieves the number of commits the `HEAD` branch is ahead/behind the remote branch it is tracking.
|
||||
# Retrieves the number of commits the `HEAD` branch is ahead/behind the remote branch it is tracking.
|
||||
#
|
||||
# This is similar to the commit numbers reported by `git status` when a remote tracking branch exists.
|
||||
#
|
||||
@@ -234,7 +230,7 @@ class Git
|
||||
getAheadBehindCounts: ->
|
||||
@getRepo().getAheadBehindCount()
|
||||
|
||||
# Public: Retrieves the line diffs comparing the `HEAD` version of the given path and the given text.
|
||||
# Retrieves the line diffs comparing the `HEAD` version of the given path and the given text.
|
||||
#
|
||||
# This is similar to the commit numbers reported by `git status` when a remote tracking branch exists.
|
||||
#
|
||||
@@ -245,9 +241,7 @@ class Git
|
||||
getLineDiffs: (path, text) ->
|
||||
@getRepo().getLineDiffs(@relativize(path), text)
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
refreshStatus: ->
|
||||
if @statusTask?
|
||||
|
||||
+7
-13
@@ -9,9 +9,7 @@ _ = require 'underscore'
|
||||
module.exports =
|
||||
class Gutter extends View
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
@content: ->
|
||||
@div class: 'gutter', =>
|
||||
@@ -50,25 +48,21 @@ class Gutter extends View
|
||||
$(document).on "mousemove.gutter-#{@getEditor().id}", moveHandler
|
||||
$(document).one "mouseup.gutter-#{@getEditor().id}", => $(document).off 'mousemove', moveHandler
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Retrieves the containing {Editor}.
|
||||
# Retrieves the containing {Editor}.
|
||||
#
|
||||
# Returns an {Editor}.
|
||||
getEditor: ->
|
||||
@parentView
|
||||
|
||||
# Public: Defines whether to show the gutter or not.
|
||||
# Defines whether to show the gutter or not.
|
||||
#
|
||||
# showLineNumbers - A {Boolean} which, if `false`, hides the gutter
|
||||
setShowLineNumbers: (showLineNumbers) ->
|
||||
if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide()
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
updateLineNumbers: (changes, renderFrom, renderTo) ->
|
||||
if renderFrom < @firstScreenRow or renderTo > @lastScreenRow
|
||||
@@ -77,12 +71,12 @@ class Gutter extends View
|
||||
performUpdate = true
|
||||
else
|
||||
for change in changes
|
||||
if change.delta != 0 or (change.bufferDelta? and change.bufferDelta != 0)
|
||||
if change.screenDelta or change.bufferDelta
|
||||
performUpdate = true
|
||||
break
|
||||
|
||||
@renderLineNumbers(renderFrom, renderTo) if performUpdate
|
||||
|
||||
|
||||
renderLineNumbers: (startScreenRow, endScreenRow) ->
|
||||
editor = @getEditor()
|
||||
maxDigits = editor.getLineCount().toString().length
|
||||
|
||||
@@ -8,26 +8,19 @@ module.exports=
|
||||
class ImageEditSession
|
||||
registerDeserializer(this)
|
||||
|
||||
# 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
|
||||
# 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))
|
||||
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
|
||||
@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"
|
||||
|
||||
@@ -40,10 +33,12 @@ class ImageEditSession
|
||||
getViewClass: ->
|
||||
require 'image-view'
|
||||
|
||||
# Public: Retrieves the filename of the open file.
|
||||
### Public ###
|
||||
|
||||
# Retrieves the filename of the open file.
|
||||
#
|
||||
# This is `'untitled'` if the file is new and not saved to the disk.
|
||||
#
|
||||
#
|
||||
# Returns a {String}.
|
||||
getTitle: ->
|
||||
if path = @getPath()
|
||||
@@ -51,17 +46,17 @@ class ImageEditSession
|
||||
else
|
||||
'untitled'
|
||||
|
||||
# Public: Retrieves the URI of the current image.
|
||||
# Retrieves the URI of the current image.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getUri: -> @path
|
||||
|
||||
# Public: Retrieves the path of the current image.
|
||||
# Retrieves the path of the current image.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Compares two `ImageEditSession`s to determine equality.
|
||||
# Compares two `ImageEditSession`s to determine equality.
|
||||
#
|
||||
# Equality is based on the condition that the two URIs are the same.
|
||||
#
|
||||
|
||||
+12
-13
@@ -6,12 +6,12 @@ $ = require 'jquery'
|
||||
module.exports =
|
||||
class ImageView extends ScrollView
|
||||
|
||||
# Internal:
|
||||
### Internal ###
|
||||
|
||||
@content: ->
|
||||
@div class: 'image-view', tabindex: -1, =>
|
||||
@img outlet: 'image'
|
||||
|
||||
# Internal:
|
||||
initialize: (imageEditSession) ->
|
||||
super
|
||||
|
||||
@@ -29,7 +29,6 @@ class ImageView extends ScrollView
|
||||
@command 'image-view:zoom-out', => @zoomOut()
|
||||
@command 'image-view:reset-zoom', => @resetZoom()
|
||||
|
||||
# Internal:
|
||||
afterAttach: (onDom) ->
|
||||
return unless onDom
|
||||
|
||||
@@ -40,7 +39,9 @@ class ImageView extends ScrollView
|
||||
@active = @is(pane.activeView)
|
||||
@centerImage() if @active and not wasActive
|
||||
|
||||
# Public: Places the image in the center of the {Editor}.
|
||||
### Public ###
|
||||
|
||||
# Places the image in the center of the {Editor}.
|
||||
centerImage: ->
|
||||
return unless @loaded and @isVisible()
|
||||
|
||||
@@ -49,7 +50,7 @@ class ImageView extends ScrollView
|
||||
'left': Math.max((@width() - @image.outerWidth()) / 2, 0)
|
||||
@image.show()
|
||||
|
||||
# Public: Indicates the path of the image.
|
||||
# Indicates the path of the image.
|
||||
#
|
||||
# path - A {String} for the new image path.
|
||||
setPath: (path) ->
|
||||
@@ -60,25 +61,25 @@ class ImageView extends ScrollView
|
||||
else
|
||||
@image.hide()
|
||||
|
||||
# Public: Retrieve's the {Editor}'s pane.
|
||||
# Retrieve's the {Editor}'s pane.
|
||||
#
|
||||
# Returns a {Pane}.
|
||||
getPane: ->
|
||||
@parent('.item-views').parent('.pane').view()
|
||||
|
||||
# Public: Zooms the image out.
|
||||
# Zooms the image out.
|
||||
#
|
||||
# This is done by a factor of `0.9`.
|
||||
zoomOut: ->
|
||||
@adjustSize(0.9)
|
||||
|
||||
# Public: Zooms the image in.
|
||||
# Zooms the image in.
|
||||
#
|
||||
# This is done by a factor of `1.1`.
|
||||
zoomIn: ->
|
||||
@adjustSize(1.1)
|
||||
|
||||
# Public: Zooms the image to its normal width and height.
|
||||
# Zooms the image to its normal width and height.
|
||||
resetZoom: ->
|
||||
return unless @loaded and @isVisible()
|
||||
|
||||
@@ -86,9 +87,7 @@ class ImageView extends ScrollView
|
||||
@image.height(@originalHeight)
|
||||
@centerImage()
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
adjustSize: (factor) ->
|
||||
return unless @loaded and @isVisible()
|
||||
@@ -100,4 +99,4 @@ class ImageView extends ScrollView
|
||||
@centerImage()
|
||||
|
||||
setModel: (imageEditSession) ->
|
||||
@setPath(imageEditSession?.getPath())
|
||||
@setPath(imageEditSession?.getPath())
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -5,9 +5,7 @@ require 'underscore-extensions'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Subscriber = require 'subscriber'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class LanguageMode
|
||||
@@ -16,18 +14,20 @@ class LanguageMode
|
||||
editSession: null
|
||||
currentGrammarScore: null
|
||||
|
||||
# Public: Sets up a `LanguageMode` for the given {EditSession}.
|
||||
### Internal ###
|
||||
|
||||
destroy: ->
|
||||
@unsubscribe()
|
||||
|
||||
### Public ###
|
||||
|
||||
# Sets up a `LanguageMode` for the given {EditSession}.
|
||||
#
|
||||
# editSession - The {EditSession} to associate with
|
||||
constructor: (@editSession) ->
|
||||
@buffer = @editSession.buffer
|
||||
|
||||
# Internal:
|
||||
destroy: ->
|
||||
@unsubscribe()
|
||||
|
||||
|
||||
# Public: Wraps the lines between two rows in comments.
|
||||
# Wraps the lines between two rows in comments.
|
||||
#
|
||||
# If the language doesn't have comment, nothing happens.
|
||||
#
|
||||
@@ -74,7 +74,7 @@ class LanguageMode
|
||||
for row in [start..end]
|
||||
buffer.insert([row, 0], commentStartString)
|
||||
|
||||
# Public: Folds all the foldable lines in the buffer.
|
||||
# Folds all the foldable lines in the buffer.
|
||||
foldAll: ->
|
||||
for currentRow in [0..@buffer.getLastRow()]
|
||||
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
@@ -82,11 +82,16 @@ class LanguageMode
|
||||
|
||||
@editSession.createFold(startRow, endRow)
|
||||
|
||||
# Public: Unfolds all the foldable lines in the buffer.
|
||||
# Unfolds all the foldable lines in the buffer.
|
||||
unfoldAll: ->
|
||||
for row in [@buffer.getLastRow()..0]
|
||||
fold.destroy() for fold in @editSession.displayBuffer.foldsStartingAtBufferRow(row)
|
||||
|
||||
# Given a buffer row, creates a fold at it.
|
||||
#
|
||||
# bufferRow - A {Number} indicating the buffer row
|
||||
#
|
||||
# Returns the new {Fold}.
|
||||
foldBufferRow: (bufferRow) ->
|
||||
for currentRow in [bufferRow..0]
|
||||
rowRange = @rowRangeForCommentAtBufferRow(currentRow)
|
||||
@@ -96,7 +101,7 @@ class LanguageMode
|
||||
fold = @editSession.displayBuffer.largestFoldStartingAtBufferRow(startRow)
|
||||
return @editSession.createFold(startRow, endRow) unless fold
|
||||
|
||||
# Public: Given a buffer row, this unfolds it.
|
||||
# Given a buffer row, this unfolds it.
|
||||
#
|
||||
# bufferRow - A {Number} indicating the buffer row
|
||||
unfoldBufferRow: (bufferRow) ->
|
||||
@@ -140,7 +145,7 @@ class LanguageMode
|
||||
endRow = currentRow
|
||||
return [startRow, endRow] if startRow isnt endRow
|
||||
|
||||
# Public: Given a buffer row, this returns a suggested indentation level.
|
||||
# Given a buffer row, this returns a suggested indentation level.
|
||||
#
|
||||
# The indentation level provided is based on the current {LanguageMode}.
|
||||
#
|
||||
@@ -157,48 +162,29 @@ 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
|
||||
|
||||
# Public: Indents all the rows between two buffer row numbers.
|
||||
# Indents all the rows between two buffer row numbers.
|
||||
#
|
||||
# startRow - The row {Number} to start at
|
||||
# endRow - The row {Number} to end at
|
||||
autoIndentBufferRows: (startRow, endRow) ->
|
||||
@autoIndentBufferRow(row) for row in [startRow..endRow]
|
||||
|
||||
# Public: Given a buffer row, this indents it.
|
||||
# Given a buffer row, this indents it.
|
||||
#
|
||||
# bufferRow - The row {Number}
|
||||
autoIndentBufferRow: (bufferRow) ->
|
||||
@autoIncreaseIndentForBufferRow(bufferRow)
|
||||
@autoDecreaseIndentForBufferRow(bufferRow)
|
||||
indentLevel = @suggestedIndentForBufferRow(bufferRow)
|
||||
@editSession.setIndentationForBufferRow(bufferRow, indentLevel)
|
||||
|
||||
# Public: 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)
|
||||
|
||||
# Public: Given a buffer row, this decreases the indentation.
|
||||
# Given a buffer row, this decreases the indentation.
|
||||
#
|
||||
# bufferRow - The row {Number}
|
||||
autoDecreaseIndentForBufferRow: (bufferRow) ->
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
_ = require 'underscore'
|
||||
|
||||
# Internal: Responsible for doing the translations between screen positions and buffer positions.
|
||||
module.exports =
|
||||
class LineMap
|
||||
maxScreenLineLength: 0
|
||||
|
||||
constructor: ->
|
||||
@screenLines = []
|
||||
|
||||
insertAtScreenRow: (bufferRow, screenLines) ->
|
||||
@spliceAtScreenRow(bufferRow, 0, screenLines)
|
||||
|
||||
replaceScreenRows: (start, end, screenLines) ->
|
||||
@spliceAtScreenRow(start, end - start + 1, screenLines)
|
||||
|
||||
spliceAtScreenRow: (startRow, rowCount, screenLines) ->
|
||||
maxLengthCandidates = screenLines
|
||||
for screenLine in @screenLines[startRow...startRow+rowCount]
|
||||
if screenLine.text.length == @maxScreenLineLength
|
||||
@maxScreenLineLength = 0
|
||||
maxLengthCandidates = @screenLines
|
||||
|
||||
_.spliceWithArray(@screenLines, startRow, rowCount, screenLines)
|
||||
|
||||
for screenLine in maxLengthCandidates
|
||||
@maxScreenLineLength = Math.max(@maxScreenLineLength, screenLine.text.length)
|
||||
|
||||
# Public: Gets the line for the given screen row.
|
||||
#
|
||||
# screenRow - A {Number} indicating the screen row.
|
||||
#
|
||||
# Returns a {ScreenLine}.
|
||||
lineForScreenRow: (row) ->
|
||||
@screenLines[row]
|
||||
|
||||
# Public: Gets the lines for the given screen row boundaries.
|
||||
#
|
||||
# start - A {Number} indicating the beginning screen row.
|
||||
# end - A {Number} indicating the ending screen row.
|
||||
#
|
||||
# Returns an {Array} of {ScreenLine}s.
|
||||
linesForScreenRows: (startRow, endRow) ->
|
||||
@screenLines[startRow..endRow]
|
||||
|
||||
# Public: Given starting and ending screen rows, this returns an array of the
|
||||
# buffer rows corresponding to every screen row in the range
|
||||
#
|
||||
# startRow - The screen row {Number} to start at
|
||||
# endRow - The screen row {Integer} to end at (default: {.lastScreenRow})
|
||||
#
|
||||
# Returns an {Array} of buffer rows as {Integers}s.
|
||||
bufferRowsForScreenRows: (startRow, endRow=@lastScreenRow()) ->
|
||||
bufferRows = []
|
||||
bufferRow = 0
|
||||
for screenLine, screenRow in @screenLines
|
||||
break if screenRow > endRow
|
||||
bufferRows.push(bufferRow) if screenRow >= startRow
|
||||
bufferRow += screenLine.bufferRows
|
||||
|
||||
bufferRows
|
||||
|
||||
getScreenLineCount: ->
|
||||
@screenLines.length
|
||||
|
||||
# Retrieves the last screen row in the buffer.
|
||||
#
|
||||
# Returns an {Integer}.
|
||||
lastScreenRow: ->
|
||||
@getScreenLineCount() - 1
|
||||
|
||||
clipScreenPosition: (screenPosition, options={}) ->
|
||||
{ wrapBeyondNewlines, wrapAtSoftNewlines } = options
|
||||
{ row, column } = Point.fromObject(screenPosition)
|
||||
|
||||
if row < 0
|
||||
row = 0
|
||||
column = 0
|
||||
else if row > @lastScreenRow()
|
||||
row = @lastScreenRow()
|
||||
column = Infinity
|
||||
else if column < 0
|
||||
column = 0
|
||||
|
||||
screenLine = options.screenLine ? @lineForScreenRow(row)
|
||||
maxScreenColumn = screenLine.getMaxScreenColumn()
|
||||
|
||||
if screenLine.isSoftWrapped() and column >= maxScreenColumn
|
||||
if wrapAtSoftNewlines
|
||||
row++
|
||||
column = 0
|
||||
else
|
||||
column = screenLine.clipScreenColumn(maxScreenColumn - 1)
|
||||
else if wrapBeyondNewlines and column > maxScreenColumn and row < @lastScreenRow()
|
||||
row++
|
||||
column = 0
|
||||
else
|
||||
column = screenLine.clipScreenColumn(column, options)
|
||||
new Point(row, column)
|
||||
|
||||
# Public: Given a buffer position, this converts it into a screen position.
|
||||
#
|
||||
# bufferPosition - An object that represents a buffer position. It can be either
|
||||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||||
# options - The same options available to {.clipScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
screenPositionForBufferPosition: (bufferPosition, options={}) ->
|
||||
{ row, column } = Point.fromObject(bufferPosition)
|
||||
[screenRow, screenLines] = @screenRowAndScreenLinesForBufferRow(row)
|
||||
for screenLine in screenLines
|
||||
maxBufferColumn = screenLine.getMaxBufferColumn()
|
||||
if screenLine.isSoftWrapped() and column > maxBufferColumn
|
||||
screenRow++
|
||||
else
|
||||
if column <= maxBufferColumn
|
||||
screenColumn = screenLine.screenColumnForBufferColumn(column)
|
||||
else
|
||||
screenColumn = Infinity
|
||||
break
|
||||
|
||||
options.screenLine = screenLine
|
||||
@clipScreenPosition([screenRow, screenColumn], options)
|
||||
|
||||
screenRowAndScreenLinesForBufferRow: (bufferRow) ->
|
||||
screenLines = []
|
||||
screenRow = 0
|
||||
currentBufferRow = 0
|
||||
for screenLine in @screenLines
|
||||
nextBufferRow = currentBufferRow + screenLine.bufferRows
|
||||
if currentBufferRow > bufferRow
|
||||
break
|
||||
else if currentBufferRow == bufferRow or currentBufferRow <= bufferRow < nextBufferRow
|
||||
screenLines.push(screenLine)
|
||||
else
|
||||
screenRow++
|
||||
currentBufferRow = nextBufferRow
|
||||
|
||||
[screenRow, screenLines]
|
||||
|
||||
# Public: Given a buffer range, this converts it into a screen position.
|
||||
#
|
||||
# screenPosition - An object that represents a buffer position. It can be either
|
||||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||||
# options - The same options available to {.clipScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
bufferPositionForScreenPosition: (screenPosition, options) ->
|
||||
{ row, column } = @clipScreenPosition(Point.fromObject(screenPosition), options)
|
||||
[bufferRow, screenLine] = @bufferRowAndScreenLineForScreenRow(row)
|
||||
bufferColumn = screenLine.bufferColumnForScreenColumn(column)
|
||||
new Point(bufferRow, bufferColumn)
|
||||
|
||||
bufferRowAndScreenLineForScreenRow: (screenRow) ->
|
||||
bufferRow = 0
|
||||
for screenLine, currentScreenRow in @screenLines
|
||||
if currentScreenRow == screenRow
|
||||
break
|
||||
else
|
||||
bufferRow += screenLine.bufferRows
|
||||
|
||||
[bufferRow, screenLine]
|
||||
|
||||
# Public: Given a buffer range, this converts it into a screen position.
|
||||
#
|
||||
# bufferRange - The {Range} to convert
|
||||
#
|
||||
# Returns a {Range}.
|
||||
screenRangeForBufferRange: (bufferRange) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
start = @screenPositionForBufferPosition(bufferRange.start)
|
||||
end = @screenPositionForBufferPosition(bufferRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
# Public: Given a screen range, this converts it into a buffer position.
|
||||
#
|
||||
# screenRange - The {Range} to convert
|
||||
#
|
||||
# Returns a {Range}.
|
||||
bufferRangeForScreenRange: (screenRange) ->
|
||||
screenRange = Range.fromObject(screenRange)
|
||||
start = @bufferPositionForScreenPosition(screenRange.start)
|
||||
end = @bufferPositionForScreenPosition(screenRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
# Internal:
|
||||
logLines: (start=0, end=@screenLineCount() - 1)->
|
||||
for row in [start..end]
|
||||
line = @lineForScreenRow(row).text
|
||||
console.log row, line, line.length
|
||||
@@ -2,9 +2,7 @@ Token = require 'token'
|
||||
EventEmitter = require 'event-emitter'
|
||||
_ = require 'underscore'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
module.exports =
|
||||
class NullGrammar
|
||||
name: 'Null Grammar'
|
||||
@@ -15,6 +13,12 @@ class NullGrammar
|
||||
tokenizeLine: (line) ->
|
||||
{ tokens: [new Token(value: line, scopes: ['null-grammar.text.plain'])] }
|
||||
|
||||
tokenizeLines: (text) ->
|
||||
lines = text.split('\n')
|
||||
for line, i in lines
|
||||
{tokens} = @tokenizeLine(line)
|
||||
tokens
|
||||
|
||||
grammarUpdated: -> # noop
|
||||
|
||||
_.extend NullGrammar.prototype, EventEmitter
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
fsUtils = require 'fs-utils'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
module.exports =
|
||||
class Package
|
||||
@build: (path) ->
|
||||
|
||||
@@ -6,9 +6,7 @@ module.exports =
|
||||
class PaneContainer extends View
|
||||
registerDeserializer(this)
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
@deserialize: ({root}) ->
|
||||
container = new PaneContainer
|
||||
@@ -26,9 +24,7 @@ class PaneContainer extends View
|
||||
deserializer: 'PaneContainer'
|
||||
root: @getRoot()?.serialize()
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
focusNextPane: ->
|
||||
panes = @getPanes()
|
||||
|
||||
@@ -2,9 +2,7 @@ $ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
PaneAxis = require 'pane-axis'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class PaneRow extends PaneAxis
|
||||
|
||||
@@ -7,9 +7,7 @@ PaneColumn = require 'pane-column'
|
||||
module.exports =
|
||||
class Pane extends View
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
@content: (wrappedView) ->
|
||||
@div class: 'pane', =>
|
||||
@@ -65,9 +63,7 @@ class Pane extends View
|
||||
@attached = true
|
||||
@trigger 'pane:attached', [this]
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
makeActive: ->
|
||||
for pane in @getContainer().getPanes() when pane isnt this
|
||||
@@ -186,7 +182,7 @@ class Pane extends View
|
||||
|
||||
saveItem: (item, nextAction) ->
|
||||
if item.getUri?()
|
||||
item.save()
|
||||
item.save?()
|
||||
nextAction?()
|
||||
else
|
||||
@saveItemAs(item, nextAction)
|
||||
|
||||
@@ -5,7 +5,7 @@ module.exports =
|
||||
class Pasteboard
|
||||
signatureForMetadata: null
|
||||
|
||||
# Internal: Creates an `md5` hash of some text.
|
||||
# Creates an `md5` hash of some text.
|
||||
#
|
||||
# text - A {String} to encrypt.
|
||||
#
|
||||
|
||||
+19
-21
@@ -6,7 +6,7 @@ _ = require 'underscore'
|
||||
module.exports =
|
||||
class Point
|
||||
|
||||
# Public: Constructs a `Point` from a given object.
|
||||
# Constructs a `Point` from a given object.
|
||||
#
|
||||
# object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{row, column}`
|
||||
#
|
||||
@@ -22,7 +22,7 @@ class Point
|
||||
|
||||
new Point(row, column)
|
||||
|
||||
# Public: Identifies which `Point` is smaller.
|
||||
# Identifies which `Point` is smaller.
|
||||
#
|
||||
# "Smaller" means that both the `row` and `column` values of one `Point` are less than or equal
|
||||
# to the other.
|
||||
@@ -39,7 +39,7 @@ class Point
|
||||
else
|
||||
point2
|
||||
|
||||
# Public: Creates a new `Point` object.
|
||||
# Creates a new `Point` object.
|
||||
#
|
||||
# row - A {Number} indicating the row (default: 0)
|
||||
# column - A {Number} indicating the column (default: 0)
|
||||
@@ -47,13 +47,13 @@ class Point
|
||||
# Returns a {Point},
|
||||
constructor: (@row=0, @column=0) ->
|
||||
|
||||
# Public: Creates an identical copy of the `Point`.
|
||||
# Creates an identical copy of the `Point`.
|
||||
#
|
||||
# Returns a duplicate {Point}.
|
||||
copy: ->
|
||||
new Point(@row, @column)
|
||||
|
||||
# Public: Adds the `column`s of two `Point`s together.
|
||||
# Adds the `column`s of two `Point`s together.
|
||||
#
|
||||
# other - The {Point} to add with
|
||||
#
|
||||
@@ -68,7 +68,7 @@ class Point
|
||||
|
||||
new Point(row, column)
|
||||
|
||||
# Public: Moves a `Point`.
|
||||
# Moves a `Point`.
|
||||
#
|
||||
# In other words, the `row` values and `column` values are added to each other.
|
||||
#
|
||||
@@ -79,7 +79,7 @@ class Point
|
||||
other = Point.fromObject(other)
|
||||
new Point(@row + other.row, @column + other.column)
|
||||
|
||||
# Public: Creates two new `Point`s, split down a `column` value.
|
||||
# Creates two new `Point`s, split down a `column` value.
|
||||
#
|
||||
# In other words, given a point, this creates `Point(0, column)` and `Point(row, column)`.
|
||||
#
|
||||
@@ -94,7 +94,7 @@ class Point
|
||||
|
||||
[new Point(0, column), new Point(@row, rightColumn)]
|
||||
|
||||
# Internal: Compares two `Point`s.
|
||||
# Compares two `Point`s.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
@@ -103,7 +103,7 @@ class Point
|
||||
# * If the first `row` is less than `other.row`, returns `-1`.
|
||||
# * If the first `column` is greater than `other.column`, returns `1`.
|
||||
# * If the first `column` is less than `other.column`, returns `-1`.
|
||||
#
|
||||
#
|
||||
# Otherwise, returns `0`.
|
||||
compare: (other) ->
|
||||
if @row > other.row
|
||||
@@ -118,7 +118,7 @@ class Point
|
||||
else
|
||||
0
|
||||
|
||||
# Public: Identifies if two `Point`s are equal.
|
||||
# Identifies if two `Point`s are equal.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
@@ -128,7 +128,7 @@ class Point
|
||||
other = Point.fromObject(other)
|
||||
@row == other.row and @column == other.column
|
||||
|
||||
# Public: Identifies if one `Point` is less than another.
|
||||
# Identifies if one `Point` is less than another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
@@ -136,7 +136,7 @@ class Point
|
||||
isLessThan: (other) ->
|
||||
@compare(other) < 0
|
||||
|
||||
# Public: Identifies if one `Point` is less than or equal to another.
|
||||
# Identifies if one `Point` is less than or equal to another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
@@ -144,7 +144,7 @@ class Point
|
||||
isLessThanOrEqual: (other) ->
|
||||
@compare(other) <= 0
|
||||
|
||||
# Public: Identifies if one `Point` is greater than another.
|
||||
# Identifies if one `Point` is greater than another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
@@ -152,7 +152,7 @@ class Point
|
||||
isGreaterThan: (other) ->
|
||||
@compare(other) > 0
|
||||
|
||||
# Public: Identifies if one `Point` is greater than or equal to another.
|
||||
# Identifies if one `Point` is greater than or equal to another.
|
||||
#
|
||||
# other - The {Point} to compare against
|
||||
#
|
||||
@@ -160,25 +160,23 @@ class Point
|
||||
isGreaterThanOrEqual: (other) ->
|
||||
@compare(other) >= 0
|
||||
|
||||
# Public: Converts the {Point} to a String.
|
||||
# Converts the {Point} to a String.
|
||||
#
|
||||
# Returns a {String}.
|
||||
toString: ->
|
||||
"#{@row},#{@column}"
|
||||
|
||||
# Public: Converts the {Point} to an Array.
|
||||
# Converts the {Point} to an Array.
|
||||
#
|
||||
# Returns an {Array}.
|
||||
toArray: ->
|
||||
[@row, @column]
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
|
||||
### Internal ###
|
||||
|
||||
inspect: ->
|
||||
"(#{@row}, #{@column})"
|
||||
|
||||
|
||||
# Internal:
|
||||
serialize: ->
|
||||
@toArray()
|
||||
|
||||
+85
-80
@@ -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
|
||||
@@ -24,17 +31,7 @@ class Project
|
||||
editSessions: null
|
||||
ignoredPathRegexes: null
|
||||
|
||||
# Public: Establishes a new project at a given path.
|
||||
#
|
||||
# path - The {String} name of the path
|
||||
constructor: (path) ->
|
||||
@setPath(path)
|
||||
@editSessions = []
|
||||
@buffers = []
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'Project'
|
||||
@@ -46,17 +43,23 @@ class Project
|
||||
destroy: ->
|
||||
editSession.destroy() for editSession in @getEditSessions()
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Retrieves the project path.
|
||||
# Establishes a new project at a given path.
|
||||
#
|
||||
# path - The {String} name of the path
|
||||
constructor: (path) ->
|
||||
@setPath(path)
|
||||
@editSessions = []
|
||||
@buffers = []
|
||||
|
||||
# Retrieves the project path.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: ->
|
||||
@rootDirectory?.path
|
||||
|
||||
# Public: Sets the project path.
|
||||
# Sets the project path.
|
||||
#
|
||||
# path - A {String} representing the new path
|
||||
setPath: (path) ->
|
||||
@@ -70,13 +73,13 @@ class Project
|
||||
|
||||
@trigger "path-changed"
|
||||
|
||||
# Public: Retrieves the name of the root directory.
|
||||
# Retrieves the name of the root directory.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getRootDirectory: ->
|
||||
@rootDirectory
|
||||
|
||||
# Public: Retrieves the names of every file (that's not `git ignore`d) in the project.
|
||||
# Retrieves the names of every file (that's not `git ignore`d) in the project.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getFilePaths: ->
|
||||
@@ -88,7 +91,7 @@ class Project
|
||||
deferred.resolve(paths)
|
||||
deferred.promise()
|
||||
|
||||
# Public: Identifies if a path is ignored.
|
||||
# Identifies if a path is ignored.
|
||||
#
|
||||
# path - The {String} name of the path to check
|
||||
#
|
||||
@@ -100,7 +103,7 @@ class Project
|
||||
|
||||
@ignoreRepositoryPath(path)
|
||||
|
||||
# Public: Identifies if a path is ignored.
|
||||
# Identifies if a path is ignored.
|
||||
#
|
||||
# path - The {String} name of the path to check
|
||||
#
|
||||
@@ -108,16 +111,20 @@ class Project
|
||||
ignoreRepositoryPath: (path) ->
|
||||
config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(fsUtils.join(@getPath(), path))
|
||||
|
||||
# Public: 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
|
||||
|
||||
# Public: Given a path, this makes it relative to the project directory.
|
||||
# Given a path, this makes it relative to the project directory.
|
||||
#
|
||||
# filePath - The {String} name of the path to convert
|
||||
#
|
||||
@@ -126,87 +133,53 @@ class Project
|
||||
return fullPath unless fullPath.lastIndexOf(@getPath()) is 0
|
||||
fullPath.replace(@getPath(), "").replace(/^\//, '')
|
||||
|
||||
# Public: Identifies if the project is using soft tabs.
|
||||
# Identifies if the project is using soft tabs.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
getSoftTabs: -> @softTabs
|
||||
|
||||
# Public: Sets the project to use soft tabs.
|
||||
# Sets the project to use soft tabs.
|
||||
#
|
||||
# softTabs - A {Boolean} which, if `true`, sets soft tabs
|
||||
setSoftTabs: (@softTabs) ->
|
||||
|
||||
# Public: Identifies if the project is using soft wrapping.
|
||||
# Identifies if the project is using soft wrapping.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
getSoftWrap: -> @softWrap
|
||||
|
||||
# Public: Sets the project to use soft wrapping.
|
||||
# Sets the project to use soft wrapping.
|
||||
#
|
||||
# softTabs - A {Boolean} which, if `true`, sets soft wrapping
|
||||
setSoftWrap: (@softWrap) ->
|
||||
|
||||
# Public: Given a path to a file, this constructs and associates a new `EditSession`, showing the file.
|
||||
# Given a path to a file, this constructs and associates a new `EditSession`, showing the file.
|
||||
#
|
||||
# filePath - The {String} path of the file to associate with
|
||||
# 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)
|
||||
|
||||
# Public: Retrieves all the {EditSession}s in the project; that is, the `EditSession`s for all open files.
|
||||
@buildEditSessionForBuffer(@bufferForPath(filePath), options)
|
||||
|
||||
# Retrieves all the {EditSession}s in the project; that is, the `EditSession`s for all open files.
|
||||
#
|
||||
# Returns an {Array} of {EditSession}s.
|
||||
getEditSessions: ->
|
||||
new Array(@editSessions...)
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
buildEditSessionForBuffer: (buffer, editSessionOptions) ->
|
||||
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
|
||||
options.project = this
|
||||
options.buffer = buffer
|
||||
editSession = new EditSession(options)
|
||||
@editSessions.push editSession
|
||||
@trigger 'edit-session-created', editSession
|
||||
editSession
|
||||
|
||||
defaultEditSessionOptions: ->
|
||||
tabLength: @tabLength
|
||||
softTabs: @getSoftTabs()
|
||||
softWrap: @getSoftWrap()
|
||||
|
||||
eachEditSession: (callback) ->
|
||||
callback(editSession) for editSession in @getEditSessions()
|
||||
@on 'edit-session-created', (editSession) -> callback(editSession)
|
||||
|
||||
eachBuffer: (args...) ->
|
||||
subscriber = args.shift() if args.length > 1
|
||||
callback = args.shift()
|
||||
|
||||
callback(buffer) for buffer in @getBuffers()
|
||||
if subscriber
|
||||
subscriber.subscribe this, 'buffer-created', (buffer) -> callback(buffer)
|
||||
else
|
||||
@on 'buffer-created', (buffer) -> callback(buffer)
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
|
||||
# Public: Removes an {EditSession} association from the project.
|
||||
# Removes an {EditSession} association from the project.
|
||||
#
|
||||
# Returns the removed {EditSession}.
|
||||
removeEditSession: (editSession) ->
|
||||
_.remove(@editSessions, editSession)
|
||||
|
||||
# Public: Retrieves all the {Buffer}s in the project; that is, the buffers for all open files.
|
||||
|
||||
# Retrieves all the {Buffer}s in the project; that is, the buffers for all open files.
|
||||
#
|
||||
# Returns an {Array} of {Buffer}s.
|
||||
getBuffers: ->
|
||||
@@ -215,7 +188,7 @@ class Project
|
||||
buffers.push editSession.buffer
|
||||
buffers
|
||||
|
||||
# Public: Given a file path, this retrieves or creates a new {Buffer}.
|
||||
# Given a file path, this retrieves or creates a new {Buffer}.
|
||||
#
|
||||
# If the `filePath` already has a `buffer`, that value is used instead. Otherwise,
|
||||
# `text` is used as the contents of the new buffer.
|
||||
@@ -233,7 +206,7 @@ class Project
|
||||
else
|
||||
@buildBuffer(null, text)
|
||||
|
||||
# Public: Given a file path, this sets its {Buffer}.
|
||||
# Given a file path, this sets its {Buffer}.
|
||||
#
|
||||
# filePath - A {String} representing a path
|
||||
# text - The {String} text to use as a buffer
|
||||
@@ -245,13 +218,13 @@ class Project
|
||||
@trigger 'buffer-created', buffer
|
||||
buffer
|
||||
|
||||
# Public: Removes a {Buffer} association from the project.
|
||||
# Removes a {Buffer} association from the project.
|
||||
#
|
||||
# Returns the removed {Buffer}.
|
||||
removeBuffer: (buffer) ->
|
||||
_.remove(@buffers, buffer)
|
||||
|
||||
# Public: Performs a search across all the files in the project.
|
||||
# Performs a search across all the files in the project.
|
||||
#
|
||||
# regex - A {RegExp} to search with
|
||||
# iterator - A {Function} callback on each file found
|
||||
@@ -307,4 +280,36 @@ class Project
|
||||
new BufferedProcess({command, args, stdout, exit})
|
||||
deferred
|
||||
|
||||
### Internal ###
|
||||
|
||||
buildEditSessionForBuffer: (buffer, editSessionOptions) ->
|
||||
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
|
||||
options.project = this
|
||||
options.buffer = buffer
|
||||
editSession = new EditSession(options)
|
||||
@editSessions.push editSession
|
||||
@trigger 'edit-session-created', editSession
|
||||
editSession
|
||||
|
||||
defaultEditSessionOptions: ->
|
||||
tabLength: @tabLength
|
||||
softTabs: @getSoftTabs()
|
||||
softWrap: @getSoftWrap()
|
||||
|
||||
eachEditSession: (callback) ->
|
||||
callback(editSession) for editSession in @getEditSessions()
|
||||
@on 'edit-session-created', (editSession) -> callback(editSession)
|
||||
|
||||
eachBuffer: (args...) ->
|
||||
subscriber = args.shift() if args.length > 1
|
||||
callback = args.shift()
|
||||
|
||||
callback(buffer) for buffer in @getBuffers()
|
||||
if subscriber
|
||||
subscriber.subscribe this, 'buffer-created', (buffer) -> callback(buffer)
|
||||
else
|
||||
@on 'buffer-created', (buffer) -> callback(buffer)
|
||||
|
||||
_.extend Project.prototype, EventEmitter
|
||||
|
||||
require 'image-edit-session'
|
||||
|
||||
+47
-23
@@ -11,7 +11,7 @@ _ = require 'underscore'
|
||||
module.exports =
|
||||
class Range
|
||||
|
||||
# Public: Constructs a `Range` from a given object.
|
||||
# Constructs a `Range` from a given object.
|
||||
#
|
||||
# object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{start: Point, end: Point}`
|
||||
#
|
||||
@@ -24,7 +24,7 @@ class Range
|
||||
else
|
||||
new Range(object.start, object.end)
|
||||
|
||||
# Public: Constructs a `Range` from a {Point}, and the delta values beyond that point.
|
||||
# Constructs a `Range` from a {Point}, and the delta values beyond that point.
|
||||
#
|
||||
# point - A {Point} to start with
|
||||
# rowDelta - A {Number} indicating how far from the starting {Point} the range's row should be
|
||||
@@ -36,7 +36,7 @@ class Range
|
||||
pointB = new Point(point.row + rowDelta, point.column + columnDelta)
|
||||
new Range(pointA, pointB)
|
||||
|
||||
# Public: Creates a new `Range` object based on two {Point}s.
|
||||
# Creates a new `Range` object based on two {Point}s.
|
||||
#
|
||||
# pointA - The first {Point} (default: `0, 0`)
|
||||
# pointB - The second {Point} (default: `0, 0`)
|
||||
@@ -51,13 +51,13 @@ class Range
|
||||
@start = pointB
|
||||
@end = pointA
|
||||
|
||||
# Public: Creates an identical copy of the `Range`.
|
||||
# Creates an identical copy of the `Range`.
|
||||
#
|
||||
# Returns a duplicate {Range}.
|
||||
copy: ->
|
||||
new Range(@start.copy(), @end.copy())
|
||||
|
||||
# Public: Identifies if two `Range`s are equal.
|
||||
# Identifies if two `Range`s are equal.
|
||||
#
|
||||
# All four points (`start.row`, `start.column`, `end.row`, `end.column`) must be
|
||||
# equal for this method to return `true`.
|
||||
@@ -71,7 +71,24 @@ class Range
|
||||
|
||||
other.start.isEqual(@start) and other.end.isEqual(@end)
|
||||
|
||||
# Public: Identifies if the `Range` is on the same line.
|
||||
# Returns an integer (-1, 0, 1) indicating whether this range is less than, equal,
|
||||
# or greater than the given range when sorting.
|
||||
#
|
||||
# Ranges that start earlier are considered "less than" ranges that start later.
|
||||
# If ranges start at the same location, the larger range sorts before the smaller
|
||||
# range.
|
||||
#
|
||||
# other - A {Range} to compare against.
|
||||
#
|
||||
# Returns a {Number}, either -1, 0, or 1.
|
||||
compare: (other) ->
|
||||
other = Range.fromObject(other)
|
||||
if value = @start.compare(other.start)
|
||||
value
|
||||
else
|
||||
other.end.compare(@end)
|
||||
|
||||
# Identifies if the `Range` is on the same line.
|
||||
#
|
||||
# In other words, if `start.row` is equal to `end.row`.
|
||||
#
|
||||
@@ -79,7 +96,7 @@ class Range
|
||||
isSingleLine: ->
|
||||
@start.row == @end.row
|
||||
|
||||
# Public: Identifies if two `Range`s are on the same line.
|
||||
# Identifies if two `Range`s are on the same line.
|
||||
#
|
||||
# other - A different {Range} to check against
|
||||
#
|
||||
@@ -87,11 +104,7 @@ class Range
|
||||
coversSameRows: (other) ->
|
||||
@start.row == other.start.row && @end.row == other.end.row
|
||||
|
||||
# Internal:
|
||||
inspect: ->
|
||||
"[#{@start.inspect()} - #{@end.inspect()}]"
|
||||
|
||||
# Public: Adds a new point to the `Range`s `start` and `end`.
|
||||
# Adds a new point to the `Range`s `start` and `end`.
|
||||
#
|
||||
# point - A new {Point} to add
|
||||
#
|
||||
@@ -99,7 +112,7 @@ class Range
|
||||
add: (point) ->
|
||||
new Range(@start.add(point), @end.add(point))
|
||||
|
||||
# Public: Moves a `Range`.
|
||||
# Moves a `Range`.
|
||||
#
|
||||
# In other words, the starting and ending `row` values, and the starting and ending
|
||||
# `column` values, are added to each other.
|
||||
@@ -111,7 +124,7 @@ class Range
|
||||
translate: (startPoint, endPoint=startPoint) ->
|
||||
new Range(@start.translate(startPoint), @end.translate(endPoint))
|
||||
|
||||
# Public: Identifies if two `Range`s intersect each other.
|
||||
# Identifies if two `Range`s intersect each other.
|
||||
#
|
||||
# otherRange - A different {Range} to check against
|
||||
#
|
||||
@@ -122,22 +135,22 @@ class Range
|
||||
else
|
||||
otherRange.intersectsWith(this)
|
||||
|
||||
# Public: Identifies if a second `Range` is contained within a first.
|
||||
# Identifies if a second `Range` is contained within a first.
|
||||
#
|
||||
# otherRange - A different {Range} to check against
|
||||
# options - A hash with a single option:
|
||||
# :exclusive - A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
|
||||
# exclusive: A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsRange: (otherRange, {exclusive} = {}) ->
|
||||
{ start, end } = Range.fromObject(otherRange)
|
||||
@containsPoint(start, {exclusive}) and @containsPoint(end, {exclusive})
|
||||
|
||||
# Public: Identifies if a `Range` contains a {Point}.
|
||||
# Identifies if a `Range` contains a {Point}.
|
||||
#
|
||||
# point - A {Point} to check against
|
||||
# options - A hash with a single option:
|
||||
# :exclusive - A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
|
||||
# exclusive: A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
containsPoint: (point, {exclusive} = {}) ->
|
||||
@@ -147,7 +160,7 @@ class Range
|
||||
else
|
||||
point.isGreaterThanOrEqual(@start) and point.isLessThanOrEqual(@end)
|
||||
|
||||
# Public: Identifies if a `Range` contains a row.
|
||||
# Identifies if a `Range` contains a row.
|
||||
#
|
||||
# row - A row {Number} to check against
|
||||
# options - A hash with a single option:
|
||||
@@ -156,7 +169,7 @@ class Range
|
||||
containsRow: (row) ->
|
||||
@start.row <= row <= @end.row
|
||||
|
||||
# Public: Constructs a union between two `Range`s.
|
||||
# Constructs a union between two `Range`s.
|
||||
#
|
||||
# otherRange - A different {Range} to unionize with
|
||||
#
|
||||
@@ -166,7 +179,7 @@ class Range
|
||||
end = if @end.isGreaterThan(otherRange.end) then @end else otherRange.end
|
||||
new Range(start, end)
|
||||
|
||||
# Public: Identifies if a `Range` is empty.
|
||||
# Identifies if a `Range` is empty.
|
||||
#
|
||||
# A `Range` is empty if its start {Point} matches its end.
|
||||
#
|
||||
@@ -174,7 +187,7 @@ class Range
|
||||
isEmpty: ->
|
||||
@start.isEqual(@end)
|
||||
|
||||
# Public: Calculates the difference between a `Range`s `start` and `end` points.
|
||||
# Calculates the difference between a `Range`s `start` and `end` points.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
toDelta: ->
|
||||
@@ -185,8 +198,19 @@ class Range
|
||||
columns = @end.column
|
||||
new Point(rows, columns)
|
||||
|
||||
# Public: Calculates the number of rows a `Range`s contains.
|
||||
# Calculates the number of rows a `Range`s contains.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
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: ->
|
||||
"[#{@start.inspect()} - #{@end.inspect()}]"
|
||||
|
||||
+27
-29
@@ -25,9 +25,7 @@ class RootView extends View
|
||||
disabledPackages: []
|
||||
themes: ['atom-dark-ui', 'atom-dark-syntax']
|
||||
|
||||
###
|
||||
# Internal:
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
@content: ({panes}={}) ->
|
||||
@div id: 'root-view', =>
|
||||
@@ -98,32 +96,30 @@ class RootView extends View
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if onDom
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
### Public ###
|
||||
|
||||
# Public: Shows a dialog asking if the pane was _really_ meant to be closed.
|
||||
# Shows a dialog asking if the pane was _really_ meant to be closed.
|
||||
confirmClose: ->
|
||||
@panes.confirmClose()
|
||||
|
||||
# Public: Given a filepath, this opens it in Atom.
|
||||
# Given a filepath, this opens it in Atom.
|
||||
#
|
||||
# Returns the `EditSession` for the file URI.
|
||||
open: (path, options = {}) ->
|
||||
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)
|
||||
|
||||
activePane.focus() if changeFocus
|
||||
editSession
|
||||
|
||||
# Public: Updates the application's title, based on whichever file is open.
|
||||
# Updates the application's title, based on whichever file is open.
|
||||
updateTitle: ->
|
||||
if projectPath = project.getPath()
|
||||
if item = @getActivePaneItem()
|
||||
@@ -133,19 +129,19 @@ class RootView extends View
|
||||
else
|
||||
@setTitle('untitled')
|
||||
|
||||
# Public: Sets the application's title.
|
||||
# Sets the application's title.
|
||||
#
|
||||
# Returns a {String}.
|
||||
setTitle: (title) ->
|
||||
document.title = title
|
||||
|
||||
# Public: Retrieves all of the application's {Editor}s.
|
||||
# Retrieves all of the application's {Editor}s.
|
||||
#
|
||||
# Returns an {Array} of {Editor}s.
|
||||
getEditors: ->
|
||||
@panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
|
||||
|
||||
# Public: Retrieves all of the modified buffers that are open and unsaved.
|
||||
# Retrieves all of the modified buffers that are open and unsaved.
|
||||
#
|
||||
# Returns an {Array} of {Buffer}s.
|
||||
getModifiedBuffers: ->
|
||||
@@ -155,13 +151,13 @@ class RootView extends View
|
||||
modifiedBuffers.push item.buffer if item.buffer.isModified()
|
||||
modifiedBuffers
|
||||
|
||||
# Public: Retrieves all of the paths to open files.
|
||||
# Retrieves all of the paths to open files.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getOpenBufferPaths: ->
|
||||
_.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths()))
|
||||
|
||||
# Public: Retrieves the pane that's currently open.
|
||||
# Retrieves the pane that's currently open.
|
||||
#
|
||||
# Returns an {Pane}.
|
||||
getActivePane: ->
|
||||
@@ -177,29 +173,23 @@ class RootView extends View
|
||||
focusNextPane: -> @panes.focusNextPane()
|
||||
getFocusedPane: -> @panes.getFocusedPane()
|
||||
|
||||
# Internal: Destroys everything.
|
||||
remove: ->
|
||||
editor.remove() for editor in @getEditors()
|
||||
project.destroy()
|
||||
super
|
||||
|
||||
# Public: Saves all of the open buffers.
|
||||
# Saves all of the open buffers.
|
||||
saveAll: ->
|
||||
@panes.saveAll()
|
||||
|
||||
# Public: Fires a callback on each open {Pane}.
|
||||
# Fires a callback on each open {Pane}.
|
||||
#
|
||||
# callback - A {Function} to call
|
||||
eachPane: (callback) ->
|
||||
@panes.eachPane(callback)
|
||||
|
||||
# Public: Retrieves all of the open {Pane}s.
|
||||
# Retrieves all of the open {Pane}s.
|
||||
#
|
||||
# Returns an {Array} of {Pane}.
|
||||
getPanes: ->
|
||||
@panes.getPanes()
|
||||
|
||||
# Public: Given a {Pane}, this fetches its ID.
|
||||
# Given a {Pane}, this fetches its ID.
|
||||
#
|
||||
# pane - An open {Pane}
|
||||
#
|
||||
@@ -207,21 +197,29 @@ class RootView extends View
|
||||
indexOfPane: (pane) ->
|
||||
@panes.indexOfPane(pane)
|
||||
|
||||
# Public: Fires a callback on each open {Editor}.
|
||||
# Fires a callback on each open {Editor}.
|
||||
#
|
||||
# callback - A {Function} to call
|
||||
eachEditor: (callback) ->
|
||||
callback(editor) for editor in @getEditors()
|
||||
@on 'editor:attached', (e, editor) -> callback(editor)
|
||||
|
||||
# Public: Fires a callback on each open {EditSession}.
|
||||
# Fires a callback on each open {EditSession}.
|
||||
#
|
||||
# callback - A {Function} to call
|
||||
eachEditSession: (callback) ->
|
||||
project.eachEditSession(callback)
|
||||
|
||||
# Public: Fires a callback on each open {Buffer}.
|
||||
# Fires a callback on each open {Buffer}.
|
||||
#
|
||||
# callback - A {Function} to call
|
||||
eachBuffer: (callback) ->
|
||||
project.eachBuffer(callback)
|
||||
|
||||
### Internal ###
|
||||
|
||||
# Destroys everything.
|
||||
remove: ->
|
||||
editor.remove() for editor in @getEditors()
|
||||
project.destroy()
|
||||
super
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
## Internal ##
|
||||
|
||||
# Facilitates the mapping of screen rows to buffer rows and vice versa. All row
|
||||
# ranges dealt with by this class are end-row exclusive. For example, a fold of
|
||||
# rows 4 through 8 would be expressed as `mapBufferRowRange(4, 9, 1)`, which maps
|
||||
# the region from 4 to 9 in the buffer to a single screen row. Conversely, a
|
||||
# soft-wrapped screen line means there are multiple screen rows corresponding to
|
||||
# a single buffer row, as follows: `mapBufferRowRange(4, 5, 3)`. That says that
|
||||
# buffer row 4 maps to 3 rows on screen.
|
||||
#
|
||||
# The RowMap revolves around the `@regions` array. Each region describes a number
|
||||
# of rows in both the screen and buffer coordinate spaces. So if you inserted a
|
||||
# single fold from 5-10, the regions array would look like this:
|
||||
#
|
||||
# ```
|
||||
# [{bufferRows: 5, screenRows: 5}, {bufferRows: 5, screenRows: 1}]
|
||||
# ```
|
||||
#
|
||||
# The first region expresses an iso-mapping, a region in which one buffer row
|
||||
# is equivalent to one screen row. The second region expresses the fold, with
|
||||
# 5 buffer rows mapping to a single screen row. Position translation functions
|
||||
# by traversing through these regions and summing the number of rows traversed
|
||||
# in both the screen and the buffer.
|
||||
module.exports =
|
||||
class RowMap
|
||||
constructor: ->
|
||||
@regions = []
|
||||
|
||||
screenRowRangeForBufferRow: (targetBufferRow) ->
|
||||
{ region, screenRow, bufferRow } = @traverseToBufferRow(targetBufferRow)
|
||||
if region and region.bufferRows != region.screenRows # 1:n region
|
||||
[screenRow, screenRow + region.screenRows]
|
||||
else # 1:1 region
|
||||
screenRow += targetBufferRow - bufferRow
|
||||
[screenRow, screenRow + 1]
|
||||
|
||||
# This will return just the given buffer row if it is part of an iso region,
|
||||
# but if it is part of a fold it will return the range of the entire fold. This
|
||||
# helps the DisplayBuffer always start processing at the beginning of a fold
|
||||
# for changes that occur inside the fold.
|
||||
bufferRowRangeForBufferRow: (targetBufferRow) ->
|
||||
{ region, screenRow, bufferRow } = @traverseToBufferRow(targetBufferRow)
|
||||
if region and region.bufferRows != region.screenRows # 1:n region
|
||||
[bufferRow, bufferRow + region.bufferRows]
|
||||
else # 1:1 region
|
||||
[targetBufferRow, targetBufferRow + 1]
|
||||
|
||||
bufferRowRangeForScreenRow: (targetScreenRow) ->
|
||||
{ region, screenRow, bufferRow } = @traverseToScreenRow(targetScreenRow)
|
||||
if region and region.bufferRows != region.screenRows # 1:n region
|
||||
[bufferRow, bufferRow + region.bufferRows]
|
||||
else # 1:1 region
|
||||
bufferRow += targetScreenRow - screenRow
|
||||
[bufferRow, bufferRow + 1]
|
||||
|
||||
# This method is used to create new regions, storing a mapping between a range
|
||||
# of buffer rows to a certain number of screen rows. It will never add or remove
|
||||
# rows in either coordinate space, meaning that it never changes the position
|
||||
# of subsequent regions. It will overwrite or split existing regions that overlap
|
||||
# with the region being stored however.
|
||||
mapBufferRowRange: (startBufferRow, endBufferRow, screenRows) ->
|
||||
{ index, bufferRow, screenRow } = @traverseToBufferRow(startBufferRow)
|
||||
|
||||
overlapStartIndex = index
|
||||
overlapStartBufferRow = bufferRow
|
||||
preRows = startBufferRow - overlapStartBufferRow
|
||||
endScreenRow = screenRow + preRows + screenRows
|
||||
overlapEndIndex = index
|
||||
overlapEndBufferRow = bufferRow
|
||||
overlapEndScreenRow = screenRow
|
||||
|
||||
# determine regions that the new region overlaps. they will need replacement.
|
||||
while overlapEndIndex < @regions.length
|
||||
region = @regions[overlapEndIndex]
|
||||
overlapEndBufferRow += region.bufferRows
|
||||
overlapEndScreenRow += region.screenRows
|
||||
break if overlapEndBufferRow >= endBufferRow and overlapEndScreenRow >= endScreenRow
|
||||
overlapEndIndex++
|
||||
|
||||
# we will replace overlapStartIndex..overlapEndIndex with these regions
|
||||
newRegions = []
|
||||
|
||||
# if we straddle the first overlapping region, push a smaller region representing
|
||||
# the portion before the new region
|
||||
if preRows > 0
|
||||
newRegions.push(bufferRows: preRows, screenRows: preRows)
|
||||
|
||||
# push the new region
|
||||
newRegions.push(bufferRows: endBufferRow - startBufferRow, screenRows: screenRows)
|
||||
|
||||
# if we straddle the last overlapping region, push a smaller region representing
|
||||
# the portion after the new region
|
||||
if overlapEndBufferRow > endBufferRow
|
||||
newRegions.push(bufferRows: overlapEndBufferRow - endBufferRow, screenRows: overlapEndScreenRow - endScreenRow)
|
||||
|
||||
@regions[overlapStartIndex..overlapEndIndex] = newRegions
|
||||
@mergeIsomorphicRegions(Math.max(0, overlapStartIndex - 1), Math.min(@regions.length - 1, overlapEndIndex + 1))
|
||||
|
||||
mergeIsomorphicRegions: (startIndex, endIndex) ->
|
||||
return if startIndex == endIndex
|
||||
|
||||
region = @regions[startIndex]
|
||||
nextRegion = @regions[startIndex + 1]
|
||||
if region.bufferRows == region.screenRows and nextRegion.bufferRows == nextRegion.screenRows
|
||||
@regions[startIndex..startIndex + 1] =
|
||||
bufferRows: region.bufferRows + nextRegion.bufferRows
|
||||
screenRows: region.screenRows + nextRegion.screenRows
|
||||
@mergeIsomorphicRegions(startIndex, endIndex - 1)
|
||||
else
|
||||
@mergeIsomorphicRegions(startIndex + 1, endIndex)
|
||||
|
||||
# This method records insertion or removal of rows in the buffer, adjusting the
|
||||
# buffer dimension of regions following the start row accordingly.
|
||||
applyBufferDelta: (startBufferRow, delta) ->
|
||||
return if delta is 0
|
||||
{ index, bufferRow } = @traverseToBufferRow(startBufferRow)
|
||||
if delta > 0 and index < @regions.length
|
||||
{ bufferRows, screenRows } = @regions[index]
|
||||
bufferRows += delta
|
||||
@regions[index] = { bufferRows, screenRows }
|
||||
else
|
||||
delta = -delta
|
||||
while delta > 0 and index < @regions.length
|
||||
{ bufferRows, screenRows } = @regions[index]
|
||||
regionStartBufferRow = bufferRow
|
||||
regionEndBufferRow = bufferRow + bufferRows
|
||||
maxDelta = regionEndBufferRow - Math.max(regionStartBufferRow, startBufferRow)
|
||||
regionDelta = Math.min(delta, maxDelta)
|
||||
bufferRows -= regionDelta
|
||||
@regions[index] = { bufferRows, screenRows }
|
||||
delta -= regionDelta
|
||||
bufferRow += bufferRows
|
||||
index++
|
||||
|
||||
# This method records insertion or removal of rows on the screen, adjusting the
|
||||
# screen dimension of regions following the start row accordingly.
|
||||
applyScreenDelta: (startScreenRow, delta) ->
|
||||
return if delta is 0
|
||||
{ index, screenRow } = @traverseToScreenRow(startScreenRow)
|
||||
if delta > 0 and index < @regions.length
|
||||
{ bufferRows, screenRows } = @regions[index]
|
||||
screenRows += delta
|
||||
@regions[index] = { bufferRows, screenRows }
|
||||
else
|
||||
delta = -delta
|
||||
while delta > 0 and index < @regions.length
|
||||
{ bufferRows, screenRows } = @regions[index]
|
||||
regionStartScreenRow = screenRow
|
||||
regionEndScreenRow = screenRow + screenRows
|
||||
maxDelta = regionEndScreenRow - Math.max(regionStartScreenRow, startScreenRow)
|
||||
regionDelta = Math.min(delta, maxDelta)
|
||||
screenRows -= regionDelta
|
||||
@regions[index] = { bufferRows, screenRows }
|
||||
delta -= regionDelta
|
||||
screenRow += screenRows
|
||||
index++
|
||||
|
||||
traverseToBufferRow: (targetBufferRow) ->
|
||||
bufferRow = 0
|
||||
screenRow = 0
|
||||
for region, index in @regions
|
||||
if (bufferRow + region.bufferRows) > targetBufferRow
|
||||
return { region, index, screenRow, bufferRow }
|
||||
bufferRow += region.bufferRows
|
||||
screenRow += region.screenRows
|
||||
{ index, screenRow, bufferRow }
|
||||
|
||||
traverseToScreenRow: (targetScreenRow) ->
|
||||
bufferRow = 0
|
||||
screenRow = 0
|
||||
for region, index in @regions
|
||||
if (screenRow + region.screenRows) > targetScreenRow
|
||||
return { region, index, screenRow, bufferRow }
|
||||
bufferRow += region.bufferRows
|
||||
screenRow += region.screenRows
|
||||
{ index, screenRow, bufferRow }
|
||||
@@ -6,15 +6,15 @@ fuzzyFilter = require 'fuzzy-filter'
|
||||
module.exports =
|
||||
class SelectList extends View
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
@content: ->
|
||||
@div class: @viewClass(), =>
|
||||
@subview 'miniEditor', new Editor(mini: true)
|
||||
@div class: 'error', outlet: 'error'
|
||||
@div class: 'loading', outlet: 'loading'
|
||||
@div class: 'loading', outlet: 'loadingArea', =>
|
||||
@span class: 'loading-message', outlet: 'loading'
|
||||
@span class: 'badge', outlet: 'loadingBadge'
|
||||
@ol outlet: 'list'
|
||||
|
||||
@viewClass: -> 'select-list'
|
||||
@@ -60,8 +60,8 @@ class SelectList extends View
|
||||
@populateList()
|
||||
@setLoading()
|
||||
|
||||
setError: (message) ->
|
||||
if not message or message.length == ""
|
||||
setError: (message='') ->
|
||||
if message.length is 0
|
||||
@error.text("").hide()
|
||||
@removeClass("error")
|
||||
else
|
||||
@@ -69,12 +69,15 @@ class SelectList extends View
|
||||
@error.text(message).show()
|
||||
@addClass("error")
|
||||
|
||||
setLoading: (message) ->
|
||||
if not message or message.length == ""
|
||||
@loading.text("").hide()
|
||||
setLoading: (message='') ->
|
||||
if message.length is 0
|
||||
@loading.text("")
|
||||
@loadingBadge.text("")
|
||||
@loadingArea.hide()
|
||||
else
|
||||
@setError()
|
||||
@loading.text(message).show()
|
||||
@loading.text(message)
|
||||
@loadingArea.show()
|
||||
|
||||
populateList: ->
|
||||
return unless @array?
|
||||
|
||||
@@ -2,10 +2,10 @@ Point = require 'point'
|
||||
Range = require 'range'
|
||||
{View, $$} = require 'space-pen'
|
||||
|
||||
# Internal:
|
||||
module.exports =
|
||||
class SelectionView extends View
|
||||
|
||||
# Internal: Establishes the DOM for the selection view.
|
||||
@content: ->
|
||||
@div class: 'selection'
|
||||
|
||||
|
||||
+122
-135
@@ -13,13 +13,11 @@ class Selection
|
||||
wordwise: false
|
||||
needsAutoscroll: null
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
constructor: ({@cursor, @marker, @editSession, @goalBufferRange}) ->
|
||||
@cursor.selection = this
|
||||
@editSession.observeMarker @marker, => @screenRangeChanged()
|
||||
@marker.on 'changed', => @screenRangeChanged()
|
||||
@cursor.on 'destroyed.selection', =>
|
||||
@cursor = null
|
||||
@destroy()
|
||||
@@ -39,54 +37,54 @@ class Selection
|
||||
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
|
||||
# Public: Identifies if the selection is highlighting anything.
|
||||
### Public ###
|
||||
|
||||
# Identifies if the selection is highlighting anything.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEmpty: ->
|
||||
@getBufferRange().isEmpty()
|
||||
|
||||
# Public: Identifies if the selection is reversed, that is, it is highlighting "up."
|
||||
# Identifies if the ending position of a marker is greater than the starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {Buffer}.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isReversed: ->
|
||||
@editSession.isMarkerReversed(@marker)
|
||||
@marker.isReversed()
|
||||
|
||||
# Public: Identifies if the selection is a single line.
|
||||
# Identifies if the selection is a single line.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isSingleScreenLine: ->
|
||||
@getScreenRange().isSingleLine()
|
||||
|
||||
# Public: Retrieves the screen range for the selection.
|
||||
# Retrieves the screen range for the selection.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getScreenRange: ->
|
||||
@editSession.getMarkerScreenRange(@marker)
|
||||
@marker.getScreenRange()
|
||||
|
||||
# Public: Modifies the screen range for the selection.
|
||||
# Modifies the screen range for the selection.
|
||||
#
|
||||
# screenRange - The new {Range} to use
|
||||
# options - A hash of options matching those found in {.setBufferRange}
|
||||
setScreenRange: (screenRange, options) ->
|
||||
@setBufferRange(@editSession.bufferRangeForScreenRange(screenRange), options)
|
||||
|
||||
# Public: Retrieves the buffer range for the selection.
|
||||
# Retrieves the buffer range for the selection.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ->
|
||||
@editSession.getMarkerBufferRange(@marker)
|
||||
@marker.getBufferRange()
|
||||
|
||||
# Public: Modifies the buffer range for the selection.
|
||||
# Modifies the buffer range for the selection.
|
||||
#
|
||||
# screenRange - The new {Range} to select
|
||||
# options - A hash of options with the following keys:
|
||||
# :preserveFolds - if `true`, the fold settings are preserved after the selection moves
|
||||
# :autoscroll - if `true`, the {EditSession} scrolls to the new selection
|
||||
# preserveFolds: if `true`, the fold settings are preserved after the selection moves
|
||||
# autoscroll: if `true`, the {EditSession} scrolls to the new selection
|
||||
setBufferRange: (bufferRange, options={}) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
@needsAutoscroll = options.autoscroll
|
||||
@@ -94,9 +92,9 @@ class Selection
|
||||
@editSession.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
|
||||
@modifySelection =>
|
||||
@cursor.needsAutoscroll = false if options.autoscroll?
|
||||
@editSession.setMarkerBufferRange(@marker, bufferRange, options)
|
||||
@marker.setBufferRange(bufferRange, options)
|
||||
|
||||
# Public: Retrieves the starting and ending buffer rows the selection is highlighting.
|
||||
# Retrieves the starting and ending buffer rows the selection is highlighting.
|
||||
#
|
||||
# Returns an {Array} of two {Number}s: the starting row, and the ending row.
|
||||
getBufferRowRange: ->
|
||||
@@ -106,22 +104,17 @@ class Selection
|
||||
end = Math.max(start, end - 1) if range.end.column == 0
|
||||
[start, end]
|
||||
|
||||
# Internal:
|
||||
screenRangeChanged: ->
|
||||
screenRange = @getScreenRange()
|
||||
@trigger 'screen-range-changed', screenRange
|
||||
|
||||
# Public: Retrieves the text in the selection.
|
||||
# Retrieves the text in the selection.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getText: ->
|
||||
@editSession.buffer.getTextInRange(@getBufferRange())
|
||||
|
||||
# Public: Clears the selection, moving the marker to move to the head.
|
||||
# Clears the selection, moving the marker to move to the head.
|
||||
clear: ->
|
||||
@editSession.clearMarkerTail(@marker)
|
||||
@marker.clearTail()
|
||||
|
||||
# Public: Modifies the selection to mark the current word.
|
||||
# Modifies the selection to mark the current word.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
selectWord: ->
|
||||
@@ -135,7 +128,7 @@ class Selection
|
||||
expandOverWord: ->
|
||||
@setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))
|
||||
|
||||
# Public: Selects an entire line in the {Buffer}.
|
||||
# Selects an entire line in the {Buffer}.
|
||||
#
|
||||
# row - The line {Number} to select (default: the row of the cursor)
|
||||
selectLine: (row=@cursor.getBufferPosition().row) ->
|
||||
@@ -149,16 +142,16 @@ class Selection
|
||||
range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
|
||||
@setBufferRange(range)
|
||||
|
||||
# Public: Selects the text from the current cursor position to a given screen position.
|
||||
# Selects the text from the current cursor position to a given screen position.
|
||||
#
|
||||
# position - An instance of {Point}, with a given `row` and `column`.
|
||||
selectToScreenPosition: (position) ->
|
||||
@modifySelection =>
|
||||
if @initialScreenRange
|
||||
if position.isLessThan(@initialScreenRange.start)
|
||||
@editSession.setMarkerScreenRange(@marker, [position, @initialScreenRange.end], reverse: true)
|
||||
@marker.setScreenRange([position, @initialScreenRange.end], reverse: true)
|
||||
else
|
||||
@editSession.setMarkerScreenRange(@marker, [@initialScreenRange.start, position])
|
||||
@marker.setScreenRange([@initialScreenRange.start, position])
|
||||
else
|
||||
@cursor.setScreenPosition(position)
|
||||
|
||||
@@ -167,61 +160,61 @@ class Selection
|
||||
else if @wordwise
|
||||
@expandOverWord()
|
||||
|
||||
# Public: Selects the text from the current cursor position to a given buffer position.
|
||||
# Selects the text from the current cursor position to a given buffer position.
|
||||
#
|
||||
# position - An instance of {Point}, with a given `row` and `column`.
|
||||
selectToBufferPosition: (position) ->
|
||||
@modifySelection => @cursor.setBufferPosition(position)
|
||||
|
||||
# Public: Selects the text one position right of the cursor.
|
||||
# Selects the text one position right of the cursor.
|
||||
selectRight: ->
|
||||
@modifySelection => @cursor.moveRight()
|
||||
|
||||
# Public: Selects the text one position left of the cursor.
|
||||
# Selects the text one position left of the cursor.
|
||||
selectLeft: ->
|
||||
@modifySelection => @cursor.moveLeft()
|
||||
|
||||
# Public: Selects all the text one position above the cursor.
|
||||
# Selects all the text one position above the cursor.
|
||||
selectUp: ->
|
||||
@modifySelection => @cursor.moveUp()
|
||||
|
||||
# Public: Selects all the text one position below the cursor.
|
||||
# Selects all the text one position below the cursor.
|
||||
selectDown: ->
|
||||
@modifySelection => @cursor.moveDown()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the top of the buffer.
|
||||
# Selects all the text from the current cursor position to the top of the buffer.
|
||||
selectToTop: ->
|
||||
@modifySelection => @cursor.moveToTop()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the bottom of the buffer.
|
||||
# Selects all the text from the current cursor position to the bottom of the buffer.
|
||||
selectToBottom: ->
|
||||
@modifySelection => @cursor.moveToBottom()
|
||||
|
||||
# Public: Selects all the text in the buffer.
|
||||
# Selects all the text in the buffer.
|
||||
selectAll: ->
|
||||
@setBufferRange(@editSession.buffer.getRange(), autoscroll: false)
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the beginning of the line.
|
||||
# Selects all the text from the current cursor position to the beginning of the line.
|
||||
selectToBeginningOfLine: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfLine()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the end of the line.
|
||||
# Selects all the text from the current cursor position to the end of the line.
|
||||
selectToEndOfLine: ->
|
||||
@modifySelection => @cursor.moveToEndOfLine()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the beginning of the word.
|
||||
# Selects all the text from the current cursor position to the beginning of the word.
|
||||
selectToBeginningOfWord: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfWord()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the end of the word.
|
||||
# Selects all the text from the current cursor position to the end of the word.
|
||||
selectToEndOfWord: ->
|
||||
@modifySelection => @cursor.moveToEndOfWord()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the beginning of the next word.
|
||||
# Selects all the text from the current cursor position to the beginning of the next word.
|
||||
selectToBeginningOfNextWord: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfNextWord()
|
||||
|
||||
# Public: Moves the selection down one row.
|
||||
# Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@goalBufferRange ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
@@ -239,7 +232,7 @@ class Selection
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
|
||||
break
|
||||
|
||||
# Public: Moves the selection up one row.
|
||||
# Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
range = (@goalBufferRange ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
@@ -257,20 +250,25 @@ class Selection
|
||||
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
|
||||
break
|
||||
|
||||
# Public: Replaces text at the current selection.
|
||||
# Replaces text at the current 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
|
||||
# select: if `true`, selects the newly added text
|
||||
# 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)
|
||||
@@ -278,17 +276,43 @@ 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
|
||||
|
||||
# Public: Indents the selection.
|
||||
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
|
||||
# performed appropriately. Otherwise, {EditSession#getTabText} is used
|
||||
# options - A hash with one key, `autoIndent`. If `true`, the indentation is
|
||||
# performed appropriately. Otherwise, {EditSession.getTabText} is used
|
||||
indent: ({ autoIndent }={})->
|
||||
{ row, column } = @cursor.getBufferPosition()
|
||||
|
||||
@@ -304,68 +328,28 @@ class Selection
|
||||
else
|
||||
@indentSelectedRows()
|
||||
|
||||
# Public: If the selection spans multiple rows, indents all of them.
|
||||
# If the selection spans multiple rows, indents all of them.
|
||||
indentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
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)
|
||||
|
||||
# Public: Performs a backspace, removing the character found behind the selection.
|
||||
# Performs a backspace, removing the character found behind the selection.
|
||||
backspace: ->
|
||||
if @isEmpty() and not @editSession.isFoldedAtScreenRow(@cursor.getScreenRow())
|
||||
if @cursor.isAtBeginningOfLine() and @editSession.isFoldedAtScreenRow(@cursor.getScreenRow() - 1)
|
||||
@selectToBufferPosition([@cursor.getBufferRow() - 1, Infinity])
|
||||
else
|
||||
@selectLeft()
|
||||
|
||||
@selectLeft() if @isEmpty() and not @editSession.isFoldedAtScreenRow(@cursor.getScreenRow())
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Performs a backspace to the beginning of the current word, removing characters found there.
|
||||
# Performs a backspace to the beginning of the current word, removing characters found there.
|
||||
backspaceToBeginningOfWord: ->
|
||||
@selectToBeginningOfWord() if @isEmpty()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Performs a backspace to the beginning of the current line, removing characters found there.
|
||||
# Performs a backspace to the beginning of the current line, removing characters found there.
|
||||
backspaceToBeginningOfLine: ->
|
||||
if @isEmpty() and @cursor.isAtBeginningOfLine()
|
||||
@selectLeft()
|
||||
@@ -373,7 +357,7 @@ class Selection
|
||||
@selectToBeginningOfLine()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Performs a delete, removing the character found ahead of the cursor position.
|
||||
# Performs a delete, removing the character found ahead of the cursor position.
|
||||
delete: ->
|
||||
if @isEmpty()
|
||||
if @cursor.isAtEndOfLine() and fold = @editSession.largestFoldStartingAtScreenRow(@cursor.getScreenRow() + 1)
|
||||
@@ -382,22 +366,20 @@ class Selection
|
||||
@selectRight()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Performs a delete to the end of the current word, removing characters found there.
|
||||
# Performs a delete to the end of the current word, removing characters found there.
|
||||
deleteToEndOfWord: ->
|
||||
@selectToEndOfWord() if @isEmpty()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Deletes the selected text.
|
||||
# Deletes the selected text.
|
||||
deleteSelectedText: ->
|
||||
bufferRange = @getBufferRange()
|
||||
if fold = @editSession.largestFoldContainingBufferRow(bufferRange.end.row)
|
||||
includeNewline = bufferRange.start.column == 0 or bufferRange.start.row >= fold.startRow
|
||||
bufferRange = bufferRange.union(fold.getBufferRange({ includeNewline }))
|
||||
|
||||
if bufferRange.isEmpty() and fold = @editSession.largestFoldContainingBufferRow(bufferRange.start.row)
|
||||
bufferRange = bufferRange.union(fold.getBufferRange(includeNewline: true))
|
||||
@editSession.buffer.delete(bufferRange) unless bufferRange.isEmpty()
|
||||
@cursor?.setBufferPosition(bufferRange.start)
|
||||
|
||||
# Public: Deletes the line.
|
||||
# Deletes the line.
|
||||
deleteLine: ->
|
||||
if @isEmpty()
|
||||
start = @cursor.getScreenRow()
|
||||
@@ -414,7 +396,7 @@ class Selection
|
||||
end--
|
||||
@editSession.buffer.deleteRows(start, end)
|
||||
|
||||
# Public: Joins the current line with the one below it.
|
||||
# Joins the current line with the one below it.
|
||||
#
|
||||
# If there selection spans more than one line, all the lines are joined together.
|
||||
joinLine: ->
|
||||
@@ -438,9 +420,9 @@ class Selection
|
||||
@deleteSelectedText()
|
||||
|
||||
if joinMarker?
|
||||
newSelectedRange = @editSession.getMarkerBufferRange(joinMarker)
|
||||
newSelectedRange = joinMarker.getBufferRange()
|
||||
@setBufferRange(newSelectedRange)
|
||||
@editSession.destroyMarker(joinMarker)
|
||||
joinMarker.destroy()
|
||||
|
||||
outdentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
@@ -454,27 +436,27 @@ class Selection
|
||||
[start, end] = @getBufferRowRange()
|
||||
@editSession.autoIndentBufferRows(start, end)
|
||||
|
||||
# Public: Wraps the selected lines in comments.
|
||||
# Wraps the selected lines in comments.
|
||||
#
|
||||
# Returns an {Array} of the commented {Ranges}.
|
||||
toggleLineComments: ->
|
||||
@editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
|
||||
|
||||
# Public: Performs a cut operation on the selection, until the end of the line.
|
||||
# Performs a cut operation on the selection, until the end of the line.
|
||||
#
|
||||
# maintainPasteboard - A {Boolean} indicating TODO
|
||||
cutToEndOfLine: (maintainPasteboard) ->
|
||||
@selectToEndOfLine() if @isEmpty()
|
||||
@cut(maintainPasteboard)
|
||||
|
||||
# Public: Performs a cut operation on the selection.
|
||||
# Performs a cut operation on the selection.
|
||||
#
|
||||
# maintainPasteboard - A {Boolean} indicating TODO
|
||||
cut: (maintainPasteboard=false) ->
|
||||
@copy(maintainPasteboard)
|
||||
@delete()
|
||||
|
||||
# Public: Performs a copy operation on the selection.
|
||||
# Performs a copy operation on the selection.
|
||||
#
|
||||
# maintainPasteboard - A {Boolean} indicating TODO
|
||||
copy: (maintainPasteboard=false) ->
|
||||
@@ -488,28 +470,27 @@ class Selection
|
||||
|
||||
pasteboard.write(text, metadata)
|
||||
|
||||
# Public: Folds the selection.
|
||||
# Folds the selection.
|
||||
fold: ->
|
||||
range = @getBufferRange()
|
||||
@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()
|
||||
fn()
|
||||
@retainSelection = false
|
||||
|
||||
# Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
placeTail: ->
|
||||
@editSession.placeMarkerTail(@marker)
|
||||
@marker.placeTail()
|
||||
|
||||
# Public: Identifies if a selection intersects with a given buffer range.
|
||||
# Identifies if a selection intersects with a given buffer range.
|
||||
#
|
||||
# bufferRange - A {Range} to check against
|
||||
#
|
||||
@@ -517,7 +498,7 @@ class Selection
|
||||
intersectsBufferRange: (bufferRange) ->
|
||||
@getBufferRange().intersectsWith(bufferRange)
|
||||
|
||||
# Public: Identifies if a selection intersects with another selection.
|
||||
# Identifies if a selection intersects with another selection.
|
||||
#
|
||||
# otherSelection - A `Selection` to check against
|
||||
#
|
||||
@@ -525,7 +506,7 @@ class Selection
|
||||
intersectsWith: (otherSelection) ->
|
||||
@getBufferRange().intersectsWith(otherSelection.getBufferRange())
|
||||
|
||||
# Public: Merges two selections together.
|
||||
# Merges two selections together.
|
||||
#
|
||||
# otherSelection - A `Selection` to merge with
|
||||
# options - A hash of options matching those found in {.setBufferRange}
|
||||
@@ -537,4 +518,10 @@ class Selection
|
||||
@goalBufferRange = otherSelection.goalBufferRange
|
||||
otherSelection.destroy()
|
||||
|
||||
### Internal ###
|
||||
|
||||
screenRangeChanged: ->
|
||||
screenRange = @getScreenRange()
|
||||
@trigger 'screen-range-changed', screenRange
|
||||
|
||||
_.extend Selection.prototype, EventEmitter
|
||||
|
||||
@@ -6,9 +6,7 @@ fsUtils = require 'fs-utils'
|
||||
EventEmitter = require 'event-emitter'
|
||||
NullGrammar = require 'null-grammar'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class Syntax
|
||||
|
||||
+144
-204
@@ -29,7 +29,7 @@ class Buffer
|
||||
invalidMarkers: null
|
||||
refcount: 0
|
||||
|
||||
# Public: Creates a new buffer.
|
||||
# Creates a new buffer.
|
||||
#
|
||||
# path - A {String} representing the file path
|
||||
# initialText - A {String} setting the starting text
|
||||
@@ -55,9 +55,7 @@ class Buffer
|
||||
|
||||
@undoManager = new UndoManager(this)
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
destroy: ->
|
||||
throw new Error("Destroying buffer twice with path '#{@getPath()}'") if @destroyed
|
||||
@@ -97,19 +95,17 @@ class Buffer
|
||||
|
||||
@file.on "moved", =>
|
||||
@trigger "path-changed", this
|
||||
|
||||
###
|
||||
# Public #
|
||||
###
|
||||
|
||||
# Public: Identifies if the buffer belongs to multiple editors.
|
||||
### Public ###
|
||||
|
||||
# Identifies if the buffer belongs to multiple editors.
|
||||
#
|
||||
# For example, if the {Editor} was split.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
# Returns a {Boolean}.
|
||||
hasMultipleEditors: -> @refcount > 1
|
||||
|
||||
# Public: Reloads a file in the {EditSession}.
|
||||
# Reloads a file in the {EditSession}.
|
||||
#
|
||||
# Essentially, this performs a force read of the file.
|
||||
reload: ->
|
||||
@@ -119,25 +115,25 @@ class Buffer
|
||||
@triggerModifiedStatusChanged(false)
|
||||
@trigger 'reloaded'
|
||||
|
||||
# Public: Rereads the contents of the file, and stores them in the cache.
|
||||
# Rereads the contents of the file, and stores them in the cache.
|
||||
#
|
||||
# Essentially, this performs a force read of the file on disk.
|
||||
updateCachedDiskContents: ->
|
||||
@cachedDiskContents = @file.read()
|
||||
|
||||
# Public: Gets the file's basename--that is, the file without any directory information.
|
||||
# Gets the file's basename--that is, the file without any directory information.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getBaseName: ->
|
||||
@file?.getBaseName()
|
||||
|
||||
# Public: Retrieves the path for the file.
|
||||
# Retrieves the path for the file.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getPath: ->
|
||||
@file?.getPath()
|
||||
|
||||
# Public: Sets the path for the file.
|
||||
# Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
setPath: (path) ->
|
||||
@@ -150,7 +146,7 @@ class Buffer
|
||||
|
||||
@trigger "path-changed", this
|
||||
|
||||
# Public: Retrieves the current buffer's file extension.
|
||||
# Retrieves the current buffer's file extension.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getExtension: ->
|
||||
@@ -159,25 +155,25 @@ class Buffer
|
||||
else
|
||||
null
|
||||
|
||||
# Public: Retrieves the cached buffer contents.
|
||||
# Retrieves the cached buffer contents.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getText: ->
|
||||
@cachedMemoryContents ?= @getTextInRange(@getRange())
|
||||
|
||||
# Public: Replaces the current buffer contents.
|
||||
# Replaces the current buffer contents.
|
||||
#
|
||||
# text - A {String} containing the new buffer contents.
|
||||
setText: (text) ->
|
||||
@change(@getRange(), text, normalizeLineEndings: false)
|
||||
|
||||
# Public: Gets the range of the buffer contents.
|
||||
# Gets the range of the buffer contents.
|
||||
#
|
||||
# Returns a new {Range}, from `[0, 0]` to the end of the buffer.
|
||||
getRange: ->
|
||||
new Range([0, 0], [@getLastRow(), @getLastLine().length])
|
||||
|
||||
# Public: Given a range, returns the lines of text within it.
|
||||
# Given a range, returns the lines of text within it.
|
||||
#
|
||||
# range - A {Range} object specifying your points of interest
|
||||
#
|
||||
@@ -197,13 +193,13 @@ class Buffer
|
||||
|
||||
return multipleLines.join ''
|
||||
|
||||
# Public: Gets all the lines in a file.
|
||||
# Gets all the lines in a file.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getLines: ->
|
||||
@lines
|
||||
|
||||
# Public: Given a row, returns the line of text.
|
||||
# Given a row, returns the line of text.
|
||||
#
|
||||
# row - A {Number} indicating the row.
|
||||
#
|
||||
@@ -211,13 +207,18 @@ class Buffer
|
||||
lineForRow: (row) ->
|
||||
@lines[row]
|
||||
|
||||
# Given a row, returns its line ending.
|
||||
#
|
||||
# row - A {Number} indicating the row.
|
||||
#
|
||||
# Returns a {String}, or `undefined` if `row` is the final row.
|
||||
lineEndingForRow: (row) ->
|
||||
@lineEndings[row] unless row is @getLastRow()
|
||||
|
||||
suggestedLineEndingForRow: (row) ->
|
||||
@lineEndingForRow(row) ? @lineEndingForRow(row - 1)
|
||||
|
||||
# Public: Given a row, returns the length of the line of text.
|
||||
# Given a row, returns the length of the line of text.
|
||||
#
|
||||
# row - A {Number} indicating the row.
|
||||
#
|
||||
@@ -225,13 +226,18 @@ class Buffer
|
||||
lineLengthForRow: (row) ->
|
||||
@lines[row].length
|
||||
|
||||
# Given a row, returns the length of the line ending
|
||||
#
|
||||
# row - A {Number} indicating the row.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
lineEndingLengthForRow: (row) ->
|
||||
(@lineEndingForRow(row) ? '').length
|
||||
|
||||
# Public: Given a buffer row, this retrieves the range for that line.
|
||||
# Given a buffer row, this retrieves the range for that line.
|
||||
#
|
||||
# row - A {Number} identifying the row
|
||||
# options - A hash with one key, `includeNewline`, which specifies whether you
|
||||
# options - A hash with one key, `includeNewline`, which specifies whether you
|
||||
# want to include the trailing newline
|
||||
#
|
||||
# Returns a {Range}.
|
||||
@@ -241,25 +247,25 @@ class Buffer
|
||||
else
|
||||
new Range([row, 0], [row, @lineLengthForRow(row)])
|
||||
|
||||
# Public: Gets the number of lines in a file.
|
||||
# Gets the number of lines in a file.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLineCount: ->
|
||||
@getLines().length
|
||||
|
||||
# Public: Gets the row number of the last line.
|
||||
# Gets the row number of the last line.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLastRow: ->
|
||||
@getLines().length - 1
|
||||
|
||||
# Public: Finds the last line in the current buffer.
|
||||
# Finds the last line in the current buffer.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getLastLine: ->
|
||||
@lineForRow(@getLastRow())
|
||||
|
||||
# Public: Finds the last point in the current buffer.
|
||||
# Finds the last point in the current buffer.
|
||||
#
|
||||
# Returns a {Point} representing the last position.
|
||||
getEofPosition: ->
|
||||
@@ -282,13 +288,13 @@ class Buffer
|
||||
|
||||
new Point(row, index)
|
||||
|
||||
# Public: Given a row, this deletes it from the buffer.
|
||||
# Given a row, this deletes it from the buffer.
|
||||
#
|
||||
# row - A {Number} representing the row to delete
|
||||
deleteRow: (row) ->
|
||||
@deleteRows(row, row)
|
||||
|
||||
# Public: Deletes a range of rows from the buffer.
|
||||
# Deletes a range of rows from the buffer.
|
||||
#
|
||||
# start - A {Number} representing the starting row
|
||||
# end - A {Number} representing the ending row
|
||||
@@ -307,36 +313,29 @@ class Buffer
|
||||
|
||||
@delete(new Range(startPoint, endPoint))
|
||||
|
||||
# Public: Adds text to the end of the buffer.
|
||||
# Adds text to the end of the buffer.
|
||||
#
|
||||
# text - A {String} of text to add
|
||||
append: (text) ->
|
||||
@insert(@getEofPosition(), text)
|
||||
|
||||
# Public: Adds text to a specific point in the buffer
|
||||
# Adds text to a specific point in the buffer
|
||||
#
|
||||
# point - A {Point} in the buffer to insert into
|
||||
# text - A {String} of text to add
|
||||
insert: (point, text) ->
|
||||
@change(new Range(point, point), text)
|
||||
|
||||
# Public: Deletes text from the buffer
|
||||
# Deletes text from the buffer
|
||||
#
|
||||
# range - A {Range} whose text to delete
|
||||
delete: (range) ->
|
||||
@change(range, '')
|
||||
|
||||
# Internal:
|
||||
change: (oldRange, newText, options) ->
|
||||
oldRange = Range.fromObject(oldRange)
|
||||
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
|
||||
range = @pushOperation(operation)
|
||||
range
|
||||
|
||||
# Public: Given a position, this clips it to a real position.
|
||||
# Given a position, this clips it to a real position.
|
||||
#
|
||||
# For example, if `position`'s row exceeds the row count of the buffer,
|
||||
# or if its column goes beyond a line's length, this "sanitizes" the value
|
||||
# or if its column goes beyond a line's length, this "sanitizes" the value
|
||||
# to a real position.
|
||||
#
|
||||
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
|
||||
@@ -350,11 +349,11 @@ class Buffer
|
||||
column = Math.max(position.column, 0)
|
||||
column = Math.min(@lineLengthForRow(row), column)
|
||||
new Point(row, column)
|
||||
|
||||
# Public: Given a range, this clips it to a real range.
|
||||
|
||||
# Given a range, this clips it to a real range.
|
||||
#
|
||||
# For example, if `range`'s row exceeds the row count of the buffer,
|
||||
# or if its column goes beyond a line's length, this "sanitizes" the value
|
||||
# or if its column goes beyond a line's length, this "sanitizes" the value
|
||||
# to a real range.
|
||||
#
|
||||
# range - The {Point} to clip
|
||||
@@ -368,31 +367,21 @@ class Buffer
|
||||
prefix: @lines[range.start.row][0...range.start.column]
|
||||
suffix: @lines[range.end.row][range.end.column..]
|
||||
|
||||
# Internal:
|
||||
pushOperation: (operation, editSession) ->
|
||||
if @undoManager
|
||||
@undoManager.pushOperation(operation, editSession)
|
||||
else
|
||||
operation.do()
|
||||
|
||||
# Internal:
|
||||
transact: (fn) -> @undoManager.transact(fn)
|
||||
# Public: Undos the last operation.
|
||||
# Undos the last operation.
|
||||
#
|
||||
# editSession - The {EditSession} associated with the buffer.
|
||||
undo: (editSession) -> @undoManager.undo(editSession)
|
||||
# Public: Redos the last operation.
|
||||
|
||||
# Redos the last operation.
|
||||
#
|
||||
# editSession - The {EditSession} associated with the buffer.
|
||||
redo: (editSession) -> @undoManager.redo(editSession)
|
||||
commit: -> @undoManager.commit()
|
||||
abort: -> @undoManager.abort()
|
||||
|
||||
# Public: Saves the buffer.
|
||||
# Saves the buffer.
|
||||
save: ->
|
||||
@saveAs(@getPath()) if @isModified()
|
||||
|
||||
# Public: Saves the buffer at a specific path.
|
||||
# Saves the buffer at a specific path.
|
||||
#
|
||||
# path - The path to save at.
|
||||
saveAs: (path) ->
|
||||
@@ -405,7 +394,7 @@ class Buffer
|
||||
@triggerModifiedStatusChanged(false)
|
||||
@trigger 'saved'
|
||||
|
||||
# Public: Identifies if the buffer was modified.
|
||||
# Identifies if the buffer was modified.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isModified: ->
|
||||
@@ -414,41 +403,78 @@ class Buffer
|
||||
else
|
||||
not @isEmpty()
|
||||
|
||||
# Public: Identifies if a buffer is in a git conflict with `HEAD`.
|
||||
# Identifies if a buffer is in a git conflict with `HEAD`.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isInConflict: -> @conflict
|
||||
|
||||
# Public: Identifies if a buffer is empty.
|
||||
# Identifies if a buffer is empty.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isEmpty: -> @lines.length is 1 and @lines[0].length is 0
|
||||
|
||||
getMarkers: ->
|
||||
_.values(@validMarkers)
|
||||
# Returns all valid {BufferMarker}s on the buffer.
|
||||
getMarkers: ({includeInvalid} = {}) ->
|
||||
markers = _.values(@validMarkers)
|
||||
if includeInvalid
|
||||
markers.concat(_.values(@invalidMarkers))
|
||||
else
|
||||
markers
|
||||
|
||||
# Public: Retrieves the quantity of markers in a buffer.
|
||||
# Returns the {BufferMarker} with the given id.
|
||||
getMarker: (id) ->
|
||||
@validMarkers[id]
|
||||
|
||||
# Public: Finds the first marker satisfying the given attributes
|
||||
#
|
||||
# Returns a {String} marker-identifier
|
||||
findMarker: (attributes) ->
|
||||
@findMarkers(attributes)[0]
|
||||
|
||||
# Public: Finds all markers satisfying the given attributes
|
||||
#
|
||||
# attributes - The attributes against which to compare the markers' attributes
|
||||
# There are some reserved keys that match against derived marker properties:
|
||||
# startRow - The row at which the marker starts
|
||||
# endRow - The row at which the marker ends
|
||||
#
|
||||
# Returns an {Array} of {BufferMarker}s
|
||||
findMarkers: (attributes) ->
|
||||
markers = @getMarkers().filter (marker) -> marker.matchesAttributes(attributes)
|
||||
markers.sort (a, b) -> a.getRange().compare(b.getRange())
|
||||
|
||||
# Retrieves the quantity of markers in a buffer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
_.size(@validMarkers)
|
||||
|
||||
# Public: Constructs a new marker at a given range.
|
||||
# Constructs a new marker at a given range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {BufferMarker} constructor
|
||||
# attributes - An optional hash of serializable attributes
|
||||
# Any attributes you pass will be associated with the marker and can be retrieved
|
||||
# or used in marker queries.
|
||||
# The following attribute keys reserved, and control the marker's initial range
|
||||
# reverse - if `true`, the marker is reversed; that is, its head precedes the tail
|
||||
# noTail - if `true`, the marker is created without a tail
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markRange: (range, options={}) ->
|
||||
markRange: (range, attributes={}) ->
|
||||
optionKeys = ['invalidationStrategy', 'noTail', 'reverse']
|
||||
options = _.pick(attributes, optionKeys)
|
||||
attributes = _.omit(attributes, optionKeys)
|
||||
marker = new BufferMarker(_.defaults({
|
||||
id: (@nextMarkerId++).toString()
|
||||
buffer: this
|
||||
range
|
||||
attributes
|
||||
}, options))
|
||||
@validMarkers[marker.id] = marker
|
||||
marker.id
|
||||
@trigger 'marker-created', marker
|
||||
marker
|
||||
|
||||
# Public: Constructs a new marker at a given position.
|
||||
# Constructs a new marker at a given position.
|
||||
#
|
||||
# position - The marker {Point}; there won't be a tail
|
||||
# options - Options to pass to the {BufferMarker} constructor
|
||||
@@ -457,131 +483,16 @@ class Buffer
|
||||
markPosition: (position, options) ->
|
||||
@markRange([position, position], _.defaults({noTail: true}, options))
|
||||
|
||||
# Public: Removes the marker with the given id.
|
||||
#
|
||||
# id - The {Number} of the ID to remove
|
||||
destroyMarker: (id) ->
|
||||
delete @validMarkers[id]
|
||||
delete @invalidMarkers[id]
|
||||
|
||||
getMarkerPosition: (args...) ->
|
||||
@getMarkerHeadPosition(args...)
|
||||
|
||||
setMarkerPosition: (args...) ->
|
||||
@setMarkerHeadPosition(args...)
|
||||
|
||||
# Public: Retrieves the position of the marker's head.
|
||||
#
|
||||
# id - A {Number} representing the marker to check
|
||||
#
|
||||
# Returns a {Point}, or `null` if the marker does not exist.
|
||||
getMarkerHeadPosition: (id) ->
|
||||
@validMarkers[id]?.getHeadPosition()
|
||||
|
||||
# Public: Sets the position of the marker's head.
|
||||
#
|
||||
# id - A {Number} representing the marker to change
|
||||
# position - The new {Point} to place the head
|
||||
# options - A hash with the following keys:
|
||||
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
#
|
||||
# Returns a {Point} representing the new head position.
|
||||
setMarkerHeadPosition: (id, position, options) ->
|
||||
@validMarkers[id]?.setHeadPosition(position)
|
||||
|
||||
# Public: Retrieves the position of the marker's tail.
|
||||
#
|
||||
# id - A {Number} representing the marker to check
|
||||
#
|
||||
# Returns a {Point}, or `null` if the marker does not exist.
|
||||
getMarkerTailPosition: (id) ->
|
||||
@validMarkers[id]?.getTailPosition()
|
||||
|
||||
# Public: Sets the position of the marker's tail.
|
||||
#
|
||||
# id - A {Number} representing the marker to change
|
||||
# position - The new {Point} to place the tail
|
||||
# options - A hash with the following keys:
|
||||
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
|
||||
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
setMarkerTailPosition: (id, position, options) ->
|
||||
@validMarkers[id]?.setTailPosition(position)
|
||||
|
||||
# Public: Retrieves the {Range} between a marker's head and its tail.
|
||||
#
|
||||
# id - A {Number} representing the marker to check
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getMarkerRange: (id) ->
|
||||
@validMarkers[id]?.getRange()
|
||||
|
||||
# Public: Sets the marker's range, potentialy modifying both its head and tail.
|
||||
#
|
||||
# id - A {Number} representing the marker to change
|
||||
# range - The new {Range} the marker should cover
|
||||
# options - A hash of options with the following keys:
|
||||
# :reverse - if `true`, the marker is reversed; that is, its tail is "above" the head
|
||||
# :noTail - if `true`, the marker doesn't have a tail
|
||||
setMarkerRange: (id, range, options) ->
|
||||
@validMarkers[id]?.setRange(range, options)
|
||||
|
||||
# Public: Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
#
|
||||
# id - A {Number} representing the marker to change
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
placeMarkerTail: (id) ->
|
||||
@validMarkers[id]?.placeTail()
|
||||
|
||||
# Public: Removes the tail from the marker.
|
||||
#
|
||||
# id - A {Number} representing the marker to change
|
||||
clearMarkerTail: (id) ->
|
||||
@validMarkers[id]?.clearTail()
|
||||
|
||||
# Public: Identifies if the ending position of a marker is greater than the starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {Buffer}.
|
||||
#
|
||||
# id - A {Number} representing the marker to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isMarkerReversed: (id) ->
|
||||
@validMarkers[id]?.isReversed()
|
||||
|
||||
# Public: Identifies if the marker's head position is equal to its tail.
|
||||
#
|
||||
# id - A {Number} representing the marker to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isMarkerRangeEmpty: (id) ->
|
||||
@validMarkers[id]?.isRangeEmpty()
|
||||
|
||||
# Public: Sets a callback to be fired whenever a marker is changed.
|
||||
#
|
||||
# id - A {Number} representing the marker to watch
|
||||
# callback - A {Function} to execute
|
||||
observeMarker: (id, callback) ->
|
||||
@validMarkers[id]?.observe(callback)
|
||||
|
||||
# Public: Given a buffer position, this finds all markers that contain the position.
|
||||
# Given a buffer position, this finds all markers that contain the position.
|
||||
#
|
||||
# bufferPosition - A {Point} to check
|
||||
#
|
||||
# Returns an {Array} of {Numbers}, representing marker IDs containing `bufferPosition`.
|
||||
markersForPosition: (bufferPosition) ->
|
||||
bufferPosition = Point.fromObject(bufferPosition)
|
||||
ids = []
|
||||
for id, marker of @validMarkers
|
||||
ids.push(id) if marker.containsPoint(bufferPosition)
|
||||
ids
|
||||
markersForPosition: (position) ->
|
||||
position = Point.fromObject(position)
|
||||
@getMarkers().filter (marker) -> marker.containsPoint(position)
|
||||
|
||||
# Public: Identifies if a character sequence is within a certain range.
|
||||
# Identifies if a character sequence is within a certain range.
|
||||
#
|
||||
# regex - The {RegExp} to check
|
||||
# startIndex - The starting row {Number}
|
||||
@@ -611,14 +522,14 @@ class Buffer
|
||||
|
||||
matches
|
||||
|
||||
# Public: Scans for text in the buffer, calling a function on each match.
|
||||
# Scans for text in the buffer, calling a function on each match.
|
||||
#
|
||||
# regex - A {RegExp} representing the text to find
|
||||
# iterator - A {Function} that's called on each match
|
||||
scan: (regex, iterator) ->
|
||||
@scanInRange(regex, @getRange(), iterator)
|
||||
|
||||
# Public: Scans for text in a given range, calling a function on each match.
|
||||
# Scans for text in a given range, calling a function on each match.
|
||||
#
|
||||
# regex - A {RegExp} representing the text to find
|
||||
# range - A {Range} in the buffer to search within
|
||||
@@ -661,7 +572,7 @@ class Buffer
|
||||
|
||||
break unless global and keepLooping
|
||||
|
||||
# Public: Scans for text in a given range _backwards_, calling a function on each match.
|
||||
# Scans for text in a given range _backwards_, calling a function on each match.
|
||||
#
|
||||
# regex - A {RegExp} representing the text to find
|
||||
# range - A {Range} in the buffer to search within
|
||||
@@ -669,7 +580,7 @@ class Buffer
|
||||
backwardsScanInRange: (regex, range, iterator) ->
|
||||
@scanInRange regex, range, iterator, true
|
||||
|
||||
# Public: Given a row, identifies if it is blank.
|
||||
# Given a row, identifies if it is blank.
|
||||
#
|
||||
# row - A row {Number} to check
|
||||
#
|
||||
@@ -677,7 +588,7 @@ class Buffer
|
||||
isRowBlank: (row) ->
|
||||
not /\S/.test @lineForRow(row)
|
||||
|
||||
# Public: Given a row, this finds the next row above it that's empty.
|
||||
# Given a row, this finds the next row above it that's empty.
|
||||
#
|
||||
# startRow - A {Number} identifying the row to start checking at
|
||||
#
|
||||
@@ -691,7 +602,7 @@ class Buffer
|
||||
return row unless @isRowBlank(row)
|
||||
null
|
||||
|
||||
# Public: Given a row, this finds the next row that's blank.
|
||||
# Given a row, this finds the next row that's blank.
|
||||
#
|
||||
# startRow - A row {Number} to check
|
||||
#
|
||||
@@ -704,7 +615,7 @@ class Buffer
|
||||
return row unless @isRowBlank(row)
|
||||
null
|
||||
|
||||
# Public: Identifies if the buffer has soft tabs anywhere.
|
||||
# Identifies if the buffer has soft tabs anywhere.
|
||||
#
|
||||
# Returns a {Boolean},
|
||||
usesSoftTabs: ->
|
||||
@@ -713,22 +624,51 @@ class Buffer
|
||||
return match[0][0] != '\t'
|
||||
undefined
|
||||
|
||||
# Public: Checks out the current `HEAD` revision of the file.
|
||||
# Checks out the current `HEAD` revision of the file.
|
||||
checkoutHead: ->
|
||||
path = @getPath()
|
||||
return unless path
|
||||
git?.checkoutHead(path)
|
||||
|
||||
# Public: Checks to see if a file exists.
|
||||
# Checks to see if a file exists.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
fileExists: ->
|
||||
@file? && @file.exists()
|
||||
|
||||
### Internal ###
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
pushOperation: (operation, editSession) ->
|
||||
if @undoManager
|
||||
@undoManager.pushOperation(operation, editSession)
|
||||
else
|
||||
operation.do()
|
||||
|
||||
transact: (fn) ->
|
||||
if isNewTransaction = @undoManager.transact()
|
||||
@pushOperation(new BufferChangeOperation(buffer: this)) # restores markers on undo
|
||||
if fn
|
||||
try
|
||||
fn()
|
||||
finally
|
||||
@commit() if isNewTransaction
|
||||
|
||||
commit: ->
|
||||
@pushOperation(new BufferChangeOperation(buffer: this)) # restores markers on redo
|
||||
@undoManager.commit()
|
||||
|
||||
abort: -> @undoManager.abort()
|
||||
|
||||
change: (oldRange, newText, options) ->
|
||||
oldRange = Range.fromObject(oldRange)
|
||||
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
|
||||
range = @pushOperation(operation)
|
||||
range
|
||||
|
||||
destroyMarker: (id) ->
|
||||
if marker = @validMarkers[id] ? @invalidMarkers[id]
|
||||
delete @validMarkers[id]
|
||||
delete @invalidMarkers[id]
|
||||
|
||||
scheduleModifiedEvents: ->
|
||||
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
|
||||
|
||||
@@ -8,9 +8,7 @@ EventEmitter = require 'event-emitter'
|
||||
pathSplitRegex = new RegExp("[#{nodePath.sep}.]")
|
||||
TextMateScopeSelector = require 'text-mate-scope-selector'
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class TextMateGrammar
|
||||
@@ -502,9 +500,7 @@ class Pattern
|
||||
|
||||
tokens
|
||||
|
||||
###
|
||||
# Internal #
|
||||
###
|
||||
### Internal ###
|
||||
|
||||
shiftCapture = (captureIndices) ->
|
||||
[captureIndices.shift(), captureIndices.shift(), captureIndices.shift()]
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário