mirror of
https://github.com/atom/atom.git
synced 2026-02-02 10:45:14 -05:00
44
.atom/snippets/coffee.cson
Normal file
44
.atom/snippets/coffee.cson
Normal file
@@ -0,0 +1,44 @@
|
||||
".source.coffee":
|
||||
"Describe block":
|
||||
prefix: "de"
|
||||
body: """
|
||||
describe "${1:description}", ->
|
||||
${2:body}
|
||||
"""
|
||||
"It block":
|
||||
prefix: "i"
|
||||
body: """
|
||||
it "$1", ->
|
||||
$2
|
||||
"""
|
||||
"Before each":
|
||||
prefix: "be"
|
||||
body: """
|
||||
beforeEach ->
|
||||
$1
|
||||
"""
|
||||
"After each":
|
||||
prefix: "af"
|
||||
body: """
|
||||
afterEach ->
|
||||
$1
|
||||
"""
|
||||
"Expectation":
|
||||
prefix: "ex"
|
||||
body: "expect($1).to$2"
|
||||
"Console log":
|
||||
prefix: "log"
|
||||
body: "console.log $1"
|
||||
"Range array":
|
||||
prefix: "ra"
|
||||
body: "[[$1, $2], [$3, $4]]"
|
||||
"Point array":
|
||||
prefix: "pt"
|
||||
body: "[$1, $2]"
|
||||
|
||||
"Key-value pair":
|
||||
prefix: ":"
|
||||
body: '${1:"${2:key}"}: ${3:value}'
|
||||
"Create Jasmine spy":
|
||||
prefix: "spy"
|
||||
body: 'jasmine.createSpy("${1:description}")$2'
|
||||
@@ -1,34 +0,0 @@
|
||||
snippet de "Describe block"
|
||||
describe "${1:description}", ->
|
||||
${2:body}
|
||||
endsnippet
|
||||
|
||||
snippet i "It block"
|
||||
it "$1", ->
|
||||
$2
|
||||
endsnippet
|
||||
|
||||
snippet be "Before each"
|
||||
beforeEach ->
|
||||
$1
|
||||
endsnippet
|
||||
|
||||
snippet ex "Expectation"
|
||||
expect($1).to$2
|
||||
endsnippet
|
||||
|
||||
snippet log "Console log"
|
||||
console.log $1
|
||||
endsnippet
|
||||
|
||||
snippet ra "Range array"
|
||||
[[$1, $2], [$3, $4]]
|
||||
endsnippet
|
||||
|
||||
snippet pt "Point array"
|
||||
[$1, $2]
|
||||
endsnippet
|
||||
|
||||
snippet spy "Jasmine spy"
|
||||
jasmine.createSpy("${1:description}")$2
|
||||
endsnippet
|
||||
2
.github
2
.github
@@ -1,3 +1,3 @@
|
||||
[docs]
|
||||
title = The Guide to Atom
|
||||
manifest = intro.md, configuring-and-extending.md, styling.md, extensions/intro.md, extensions/markdown-preview.md, extensions/wrap-guide.md
|
||||
manifest = intro.md, features.md, configuring-and-extending.md, styling.md, packages/intro.md, packages/installing.md, packages/markdown-preview.md, packages/wrap-guide.md
|
||||
|
||||
@@ -157,153 +157,6 @@ directory, it will automatically be translated from TextMate's format to CSS
|
||||
so it works with Atom. There are a few slight differences between TextMate's
|
||||
semantics and those of stylesheets, but they should be negligible in practice.
|
||||
|
||||
|
||||
# Packages
|
||||
|
||||
## Installing Packages (Partially Implemented)
|
||||
|
||||
To install a package, clone it into the `~/.atom/packages` directory.
|
||||
If you want to disable a package without removing it from the packages
|
||||
directory, insert its name into `config.core.disabledPackages`:
|
||||
|
||||
config.cson:
|
||||
```coffeescript
|
||||
core:
|
||||
disabledPackages: [
|
||||
"fuzzy-finder",
|
||||
"tree-view"
|
||||
]
|
||||
```
|
||||
|
||||
## Anatomy of a Package
|
||||
|
||||
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.
|
||||
|
||||
### Config Settings
|
||||
|
||||
### Stylesheets
|
||||
|
||||
### Keymaps (Not Implemented)
|
||||
|
||||
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** (todo) for more information.
|
||||
|
||||
### Snippets (Not Implemented)
|
||||
|
||||
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.
|
||||
|
||||
### Grammars
|
||||
|
||||
## TextMate Compatibility
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
## Extensions
|
||||
1
docs/features.md
Normal file
1
docs/features.md
Normal file
@@ -0,0 +1 @@
|
||||
# Features
|
||||
@@ -1,3 +1,3 @@
|
||||
## The Definitive Guide to Atom
|
||||
# The Atom Guide
|
||||
|
||||
Welcome!
|
||||
|
||||
14
docs/packages/installing.md
Normal file
14
docs/packages/installing.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## Installing Packages (Partially Implemented)
|
||||
|
||||
To install a package, clone it into the `~/.atom/packages` directory.
|
||||
If you want to disable a package without removing it from the packages
|
||||
directory, insert its name into `config.core.disabledPackages`:
|
||||
|
||||
config.cson:
|
||||
```coffeescript
|
||||
core:
|
||||
disabledPackages: [
|
||||
"fuzzy-finder",
|
||||
"tree-view"
|
||||
]
|
||||
```
|
||||
150
docs/packages/intro.md
Normal file
150
docs/packages/intro.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Packages
|
||||
|
||||
### Package Layout
|
||||
|
||||
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.
|
||||
|
||||
#### Config Settings
|
||||
|
||||
#### Stylesheets
|
||||
|
||||
#### Keymaps (Not Implemented)
|
||||
|
||||
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** (todo) for more information.
|
||||
|
||||
#### Snippets (Not Implemented)
|
||||
|
||||
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.
|
||||
|
||||
### Included Packages
|
||||
|
||||
Atom comes with several built-in packages that add features to the default
|
||||
editor.
|
||||
|
||||
The current built-in packages are:
|
||||
|
||||
* Autocomplete
|
||||
* Command Logger
|
||||
* Command Palette
|
||||
* Fuzzy finder
|
||||
* [Markdown Preview](#markdown-preview)
|
||||
* Outline View
|
||||
* Snippets
|
||||
* Status Bar
|
||||
* Strip Trailing Whitespace
|
||||
* Tabs
|
||||
* Tree View
|
||||
* [Wrap Guide](#wrap-guide)
|
||||
@@ -10,14 +10,16 @@ var $git = {};
|
||||
native function getDiffStats(path);
|
||||
native function isSubmodule(path);
|
||||
native function refreshIndex();
|
||||
native function destroy();
|
||||
|
||||
function GitRepository(path) {
|
||||
var repo = getRepository(path);
|
||||
if (repo) {
|
||||
repo.constructor = GitRepository;
|
||||
repo.__proto__ = GitRepository.prototype;
|
||||
return repo;
|
||||
}
|
||||
if (!repo)
|
||||
throw new Error("No Git repository found searching path: " + path);
|
||||
|
||||
repo.constructor = GitRepository;
|
||||
repo.__proto__ = GitRepository.prototype;
|
||||
return repo;
|
||||
}
|
||||
|
||||
GitRepository.prototype.getHead = getHead;
|
||||
@@ -28,5 +30,6 @@ var $git = {};
|
||||
GitRepository.prototype.getDiffStats = getDiffStats;
|
||||
GitRepository.prototype.isSubmodule = isSubmodule;
|
||||
GitRepository.prototype.refreshIndex = refreshIndex;
|
||||
GitRepository.prototype.destroy = destroy;
|
||||
this.GitRepository = GitRepository;
|
||||
})();
|
||||
|
||||
@@ -16,10 +16,17 @@ public:
|
||||
}
|
||||
|
||||
~GitRepository() {
|
||||
git_repository_free(repo);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
BOOL exists() {
|
||||
void Destroy() {
|
||||
if (Exists()) {
|
||||
git_repository_free(repo);
|
||||
repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL Exists() {
|
||||
return repo != NULL;
|
||||
}
|
||||
|
||||
@@ -190,7 +197,7 @@ bool Git::Execute(const CefString& name,
|
||||
CefString& exception) {
|
||||
if (name == "getRepository") {
|
||||
GitRepository *repository = new GitRepository(arguments[0]->GetStringValue().ToString().c_str());
|
||||
if (repository->exists()) {
|
||||
if (repository->Exists()) {
|
||||
CefRefPtr<CefBase> userData = repository;
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData(userData);
|
||||
@@ -248,6 +255,12 @@ bool Git::Execute(const CefString& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "destroy") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
userData->Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,18 @@ namespace v8_extensions {
|
||||
class Native : public CefV8Handler {
|
||||
public:
|
||||
Native();
|
||||
|
||||
|
||||
virtual bool Execute(const CefString& name,
|
||||
CefRefPtr<CefV8Value> object,
|
||||
const CefV8ValueList& arguments,
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) OVERRIDE;
|
||||
|
||||
|
||||
// Provide the reference counting implementation for this class.
|
||||
IMPLEMENT_REFCOUNTING(Native);
|
||||
|
||||
private:
|
||||
std::string windowState;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -76,4 +76,10 @@ var $native = {};
|
||||
native function getPlatform();
|
||||
$native.getPlatform = getPlatform;
|
||||
|
||||
native function setWindowState(state);
|
||||
$native.setWindowState = setWindowState;
|
||||
|
||||
native function getWindowState();
|
||||
$native.getWindowState = getWindowState;
|
||||
|
||||
})();
|
||||
|
||||
@@ -31,6 +31,7 @@ void throwException(const CefRefPtr<CefV8Value>& global, CefRefPtr<CefV8Exceptio
|
||||
Native::Native() : CefV8Handler() {
|
||||
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/native.js"];
|
||||
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
|
||||
windowState = "{}";
|
||||
CefRegisterExtension("v8/native", [extensionCode UTF8String], this);
|
||||
}
|
||||
|
||||
@@ -486,6 +487,16 @@ bool Native::Execute(const CefString& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
else if (name == "setWindowState") {
|
||||
windowState = arguments[0]->GetStringValue().ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
else if (name == "getWindowState") {
|
||||
retval = CefV8Value::CreateString(windowState);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ describe "the `atom` global", ->
|
||||
atom.loadPackage("package-with-module")
|
||||
expect(rootView.activatePackage).toHaveBeenCalledWith('package-with-module', extension)
|
||||
|
||||
it "logs warning instead of throwing an exception if a package fails to load", ->
|
||||
spyOn(console, "warn")
|
||||
expect(-> atom.loadPackage("package-that-throws-an-exception")).not.toThrow()
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
|
||||
describe "keymap loading", ->
|
||||
describe "when package.json does not contain a 'keymaps' manifest", ->
|
||||
it "loads all keymaps in the directory", ->
|
||||
|
||||
@@ -307,6 +307,36 @@ describe "EditSession", ->
|
||||
editSession.moveCursorToEndOfWord()
|
||||
expect(editSession.getCursorBufferPosition()).toEqual endPosition
|
||||
|
||||
describe ".getCurrentParagraphBufferRange()", ->
|
||||
it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", ->
|
||||
buffer.setText """
|
||||
I am the first paragraph,
|
||||
bordered by the beginning of
|
||||
the file
|
||||
#{' '}
|
||||
|
||||
I am the second paragraph
|
||||
with blank lines above and below
|
||||
me.
|
||||
|
||||
I am the last paragraph,
|
||||
bordered by the end of the file.
|
||||
"""
|
||||
|
||||
# in a paragraph
|
||||
editSession.setCursorBufferPosition([1, 7])
|
||||
expect(editSession.getCurrentParagraphBufferRange()).toEqual [[0, 0], [2, 8]]
|
||||
|
||||
editSession.setCursorBufferPosition([7, 1])
|
||||
expect(editSession.getCurrentParagraphBufferRange()).toEqual [[5, 0], [7, 3]]
|
||||
|
||||
editSession.setCursorBufferPosition([9, 10])
|
||||
expect(editSession.getCurrentParagraphBufferRange()).toEqual [[9, 0], [10, 32]]
|
||||
|
||||
# between paragraphs
|
||||
editSession.setCursorBufferPosition([3, 1])
|
||||
expect(editSession.getCurrentParagraphBufferRange()).toBeUndefined()
|
||||
|
||||
describe "selection", ->
|
||||
selection = null
|
||||
|
||||
@@ -675,10 +705,7 @@ describe "EditSession", ->
|
||||
editSession.insertText('holy cow')
|
||||
expect(editSession.lineForScreenRow(2).fold).toBeUndefined()
|
||||
|
||||
describe "when auto-indent is enabled and the `autoIndent` option is true", ->
|
||||
beforeEach ->
|
||||
editSession.setAutoIndent(true)
|
||||
|
||||
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", ->
|
||||
@@ -739,16 +766,13 @@ describe "EditSession", ->
|
||||
removeLeadingWhitespace = (text) -> text.replace(/^\s*/, '')
|
||||
|
||||
describe "when the cursor is preceded only by whitespace", ->
|
||||
describe "when auto-indent is enabled", ->
|
||||
beforeEach ->
|
||||
editSession.setAutoIndent(true)
|
||||
|
||||
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')
|
||||
editSession.insertText('\n xx', autoIndent: true)
|
||||
editSession.setCursorBufferPosition([3, 1])
|
||||
editSession.insertText(text, normalizeIndent: true)
|
||||
editSession.insertText(text, normalizeIndent: true, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
@@ -759,7 +783,7 @@ describe "EditSession", ->
|
||||
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)
|
||||
editSession.insertText(removeLeadingWhitespace(text), normalizeIndent: true, indentBasis: 2, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(3)).toBe " while (true) {"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " foo();"
|
||||
@@ -775,7 +799,7 @@ describe "EditSession", ->
|
||||
"""
|
||||
|
||||
editSession.setCursorBufferPosition([1, 0])
|
||||
editSession.insertText(text, normalizeIndent: true)
|
||||
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();"
|
||||
@@ -791,7 +815,7 @@ describe "EditSession", ->
|
||||
"""
|
||||
|
||||
editSession.setCursorBufferPosition([1, 0])
|
||||
editSession.insertText(text, normalizeIndent: true)
|
||||
editSession.insertText(text, normalizeIndent: true, autoIndent: true)
|
||||
|
||||
expect(editSession.lineForBufferRow(1)).toBe "\t\twhile (true) {"
|
||||
expect(editSession.lineForBufferRow(2)).toBe "\t\t\tfoo();"
|
||||
@@ -820,9 +844,6 @@ describe "EditSession", ->
|
||||
expect(editSession.lineForBufferRow(6)).toBe " bar();"
|
||||
|
||||
describe "if auto-indent is disabled", ->
|
||||
beforeEach ->
|
||||
expect(editSession.autoIndent).toBeFalsy()
|
||||
|
||||
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 ')
|
||||
@@ -845,7 +866,6 @@ describe "EditSession", ->
|
||||
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.setAutoIndent(true)
|
||||
editSession.buffer.delete([[2, 0], [2, 2]])
|
||||
editSession.insertText(text, normalizeIndent:true)
|
||||
|
||||
@@ -856,7 +876,6 @@ describe "EditSession", ->
|
||||
|
||||
describe "when an indentBasis is provided", ->
|
||||
it "normalizes the indentation level of all lines based on the level of the existing first line", ->
|
||||
editSession.setAutoIndent(true)
|
||||
editSession.buffer.delete([[2, 0], [2, 2]])
|
||||
editSession.insertText(removeLeadingWhitespace(text), normalizeIndent:true, indentBasis: 2)
|
||||
|
||||
@@ -1311,8 +1330,7 @@ describe "EditSession", ->
|
||||
it "moves the cursor to the end of the leading whitespace and inserts enough whitespace to bring the line to the suggested level of indentaion", ->
|
||||
buffer.insert([5, 0], " \n")
|
||||
editSession.setCursorBufferPosition [5, 0]
|
||||
editSession.setAutoIndent(true)
|
||||
editSession.indent()
|
||||
editSession.indent(autoIndent: true)
|
||||
expect(buffer.lineForRow(5)).toMatch /^\s+$/
|
||||
expect(buffer.lineForRow(5).length).toBe 6
|
||||
expect(editSession.getCursorBufferPosition()).toEqual [5, 6]
|
||||
@@ -1323,8 +1341,7 @@ describe "EditSession", ->
|
||||
editSession.softTabs = false
|
||||
buffer.insert([5, 0], "\t\n")
|
||||
editSession.setCursorBufferPosition [5, 0]
|
||||
editSession.setAutoIndent(true)
|
||||
editSession.indent()
|
||||
editSession.indent(autoIndent: true)
|
||||
expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/
|
||||
expect(editSession.getCursorBufferPosition()).toEqual [5, 3]
|
||||
|
||||
@@ -1333,8 +1350,7 @@ describe "EditSession", ->
|
||||
it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", ->
|
||||
buffer.insert([7, 0], " \n")
|
||||
editSession.setCursorBufferPosition [7, 2]
|
||||
editSession.setAutoIndent(true)
|
||||
editSession.indent()
|
||||
editSession.indent(autoIndent: true)
|
||||
expect(buffer.lineForRow(7)).toMatch /^\s+$/
|
||||
expect(buffer.lineForRow(7).length).toBe 8
|
||||
expect(editSession.getCursorBufferPosition()).toEqual [7, 8]
|
||||
@@ -1345,8 +1361,7 @@ describe "EditSession", ->
|
||||
editSession.softTabs = false
|
||||
buffer.insert([7, 0], "\t\t\t\n")
|
||||
editSession.setCursorBufferPosition [7, 1]
|
||||
editSession.setAutoIndent(true)
|
||||
editSession.indent()
|
||||
editSession.indent(autoIndent: true)
|
||||
expect(buffer.lineForRow(7)).toMatch /^\t\t\t\t$/
|
||||
expect(editSession.getCursorBufferPosition()).toEqual [7, 4]
|
||||
|
||||
@@ -1373,11 +1388,7 @@ describe "EditSession", ->
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.getTabLength() * 2]
|
||||
|
||||
describe "pasteboard operations", ->
|
||||
pasteboard = null
|
||||
beforeEach ->
|
||||
pasteboard = 'first'
|
||||
spyOn($native, 'writeToPasteboard').andCallFake (text) -> pasteboard = text
|
||||
spyOn($native, 'readFromPasteboard').andCallFake -> pasteboard
|
||||
editSession.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
|
||||
|
||||
describe ".cutSelectedText()", ->
|
||||
@@ -1396,7 +1407,7 @@ describe "EditSession", ->
|
||||
editSession.cutToEndOfLine()
|
||||
expect(buffer.lineForRow(2)).toBe ' if (items.length'
|
||||
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
|
||||
expect(pasteboard).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
|
||||
expect(pasteboard.read()[0]).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
|
||||
|
||||
describe "when text is selected", ->
|
||||
it "only cuts the selected text, not to the end of the line", ->
|
||||
@@ -1406,7 +1417,7 @@ describe "EditSession", ->
|
||||
|
||||
expect(buffer.lineForRow(2)).toBe ' if (items.lengthurn items;'
|
||||
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
|
||||
expect(pasteboard).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
|
||||
expect(pasteboard.read()[0]).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
|
||||
|
||||
describe ".copySelectedText()", ->
|
||||
it "copies selected text onto the clipboard", ->
|
||||
@@ -1417,21 +1428,21 @@ describe "EditSession", ->
|
||||
|
||||
describe ".pasteText()", ->
|
||||
it "pastes text into the buffer", ->
|
||||
pasteboard.write('first')
|
||||
editSession.pasteText()
|
||||
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.setAutoIndent(true)
|
||||
editSession.setSelectedBufferRange([[4, 4], [7, 5]])
|
||||
editSession.copySelectedText()
|
||||
editSession.setCursorBufferPosition([10, 0])
|
||||
editSession.pasteText()
|
||||
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 " }"
|
||||
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", ->
|
||||
@@ -1931,3 +1942,57 @@ describe "EditSession", ->
|
||||
editSession.setCursorScreenPosition([0, 1])
|
||||
editSession.buffer.reload()
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [0,1]
|
||||
|
||||
describe "auto-indent", ->
|
||||
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 " "
|
||||
|
||||
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 ""
|
||||
|
||||
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 " "
|
||||
|
||||
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 "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()
|
||||
|
||||
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()
|
||||
|
||||
editSession.pasteText()
|
||||
expect(editSession.lineForBufferRow(2)).toBe " 0"
|
||||
expect(editSession.lineForBufferRow(3)).toBe " 2"
|
||||
expect(editSession.lineForBufferRow(4)).toBe " 4"
|
||||
|
||||
@@ -2136,3 +2136,70 @@ describe "Editor", ->
|
||||
expect(editor.reloadGrammar()).toBeFalsy()
|
||||
expect(editor.updateDisplay).not.toHaveBeenCalled()
|
||||
expect(editor.getGrammar().name).toBe 'JavaScript'
|
||||
|
||||
it "emits an editor:grammar-changed event when updated", ->
|
||||
rootView.open(path)
|
||||
editor = rootView.getActiveEditor()
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
editor.on('editor:grammar-changed', eventHandler)
|
||||
editor.reloadGrammar()
|
||||
|
||||
expect(eventHandler).not.toHaveBeenCalled()
|
||||
|
||||
jsGrammar = syntax.grammarForFilePath('/tmp/js.js')
|
||||
rootView.project.addGrammarOverrideForPath(path, jsGrammar)
|
||||
editor.reloadGrammar()
|
||||
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
describe ".replaceSelectedText()", ->
|
||||
it "doesn't call the replace function when the selection is empty", ->
|
||||
replaced = false
|
||||
edited = false
|
||||
replacer = (text) ->
|
||||
replaced = true
|
||||
'new'
|
||||
|
||||
editor.moveCursorToTop()
|
||||
edited = editor.replaceSelectedText(replacer)
|
||||
expect(replaced).toBe false
|
||||
expect(edited).toBe false
|
||||
|
||||
it "returns true when transformed text is non-empty", ->
|
||||
replaced = false
|
||||
edited = false
|
||||
replacer = (text) ->
|
||||
replaced = true
|
||||
'new'
|
||||
|
||||
editor.moveCursorToTop()
|
||||
editor.selectToEndOfLine()
|
||||
edited = editor.replaceSelectedText(replacer)
|
||||
expect(replaced).toBe true
|
||||
expect(edited).toBe true
|
||||
|
||||
it "returns false when transformed text is null", ->
|
||||
replaced = false
|
||||
edited = false
|
||||
replacer = (text) ->
|
||||
replaced = true
|
||||
null
|
||||
|
||||
editor.moveCursorToTop()
|
||||
editor.selectToEndOfLine()
|
||||
edited = editor.replaceSelectedText(replacer)
|
||||
expect(replaced).toBe true
|
||||
expect(edited).toBe false
|
||||
|
||||
it "returns false when transformed text is undefined", ->
|
||||
replaced = false
|
||||
edited = false
|
||||
replacer = (text) ->
|
||||
replaced = true
|
||||
undefined
|
||||
|
||||
editor.moveCursorToTop()
|
||||
editor.selectToEndOfLine()
|
||||
edited = editor.replaceSelectedText(replacer)
|
||||
expect(replaced).toBe true
|
||||
expect(edited).toBe false
|
||||
|
||||
@@ -8,7 +8,7 @@ describe "Git", ->
|
||||
|
||||
describe "@open(path)", ->
|
||||
it "returns null when no repository is found", ->
|
||||
expect(Git.open('/tmp/nogit.txt')).toBeNull(0)
|
||||
expect(Git.open('/tmp/nogit.txt')).toBeNull()
|
||||
|
||||
describe "new Git(path)", ->
|
||||
it "throws an exception when no repository is found", ->
|
||||
@@ -121,3 +121,9 @@ describe "Git", ->
|
||||
expect(repo.checkoutHead(path1)).toBeTruthy()
|
||||
expect(fs.read(path2)).toBe('path 2 is edited')
|
||||
expect(repo.isPathModified(path2)).toBeTruthy()
|
||||
|
||||
describe ".destroy()", ->
|
||||
it "throws an exception when any method is called after it is called", ->
|
||||
repo = new Git(require.resolve('fixtures/git/master.git/HEAD'))
|
||||
repo.destroy()
|
||||
expect(-> repo.getHead()).toThrow()
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
describe "Pasteboard", ->
|
||||
nativePasteboard = null
|
||||
beforeEach ->
|
||||
nativePasteboard = 'first'
|
||||
spyOn($native, 'writeToPasteboard').andCallFake (text) -> nativePasteboard = text
|
||||
spyOn($native, 'readFromPasteboard').andCallFake -> nativePasteboard
|
||||
|
||||
describe "write(text, metadata) and read()", ->
|
||||
it "writes and reads text to/from the native pasteboard", ->
|
||||
expect(pasteboard.read()).toEqual ['first']
|
||||
expect(pasteboard.read()).toEqual ['initial pasteboard content']
|
||||
pasteboard.write('next')
|
||||
expect(nativePasteboard).toBe 'next'
|
||||
expect(pasteboard.read()[0]).toBe 'next'
|
||||
|
||||
it "returns metadata if the item on the native pasteboard matches the last written item", ->
|
||||
pasteboard.write('next', {meta: 'data'})
|
||||
expect(nativePasteboard).toBe 'next'
|
||||
expect(pasteboard.read()).toEqual ['next', {meta: 'data'}]
|
||||
|
||||
@@ -69,52 +69,67 @@ describe "RootView", ->
|
||||
path = require.resolve 'fixtures'
|
||||
rootView.remove()
|
||||
rootView = new RootView(path)
|
||||
rootView.open('dir/a')
|
||||
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor2 = editor1.splitRight()
|
||||
editor3 = editor2.splitRight()
|
||||
editor4 = editor2.splitDown()
|
||||
editor2.edit(rootView.project.buildEditSessionForPath('dir/b'))
|
||||
editor3.edit(rootView.project.buildEditSessionForPath('sample.js'))
|
||||
editor3.setCursorScreenPosition([2, 4])
|
||||
editor4.edit(rootView.project.buildEditSessionForPath('sample.txt'))
|
||||
editor4.setCursorScreenPosition([0, 2])
|
||||
rootView.attachToDom()
|
||||
editor2.focus()
|
||||
viewState = rootView.serialize()
|
||||
rootView.remove()
|
||||
describe "when there are open editors", ->
|
||||
beforeEach ->
|
||||
rootView.open('dir/a')
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor2 = editor1.splitRight()
|
||||
editor3 = editor2.splitRight()
|
||||
editor4 = editor2.splitDown()
|
||||
editor2.edit(rootView.project.buildEditSessionForPath('dir/b'))
|
||||
editor3.edit(rootView.project.buildEditSessionForPath('sample.js'))
|
||||
editor3.setCursorScreenPosition([2, 4])
|
||||
editor4.edit(rootView.project.buildEditSessionForPath('sample.txt'))
|
||||
editor4.setCursorScreenPosition([0, 2])
|
||||
rootView.attachToDom()
|
||||
editor2.focus()
|
||||
viewState = rootView.serialize()
|
||||
rootView.remove()
|
||||
|
||||
it "constructs the view with the same project and panes", ->
|
||||
rootView = RootView.deserialize(viewState)
|
||||
rootView.attachToDom()
|
||||
it "constructs the view with the same project and panes", ->
|
||||
rootView = RootView.deserialize(viewState)
|
||||
rootView.attachToDom()
|
||||
|
||||
expect(rootView.getEditors().length).toBe 4
|
||||
editor1 = rootView.panes.find('.row > .pane .editor:eq(0)').view()
|
||||
editor3 = rootView.panes.find('.row > .pane .editor:eq(1)').view()
|
||||
editor2 = rootView.panes.find('.row > .column > .pane .editor:eq(0)').view()
|
||||
editor4 = rootView.panes.find('.row > .column > .pane .editor:eq(1)').view()
|
||||
expect(rootView.getEditors().length).toBe 4
|
||||
editor1 = rootView.panes.find('.row > .pane .editor:eq(0)').view()
|
||||
editor3 = rootView.panes.find('.row > .pane .editor:eq(1)').view()
|
||||
editor2 = rootView.panes.find('.row > .column > .pane .editor:eq(0)').view()
|
||||
editor4 = rootView.panes.find('.row > .column > .pane .editor:eq(1)').view()
|
||||
|
||||
expect(editor1.getPath()).toBe require.resolve('fixtures/dir/a')
|
||||
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
|
||||
expect(editor3.getPath()).toBe require.resolve('fixtures/sample.js')
|
||||
expect(editor3.getCursorScreenPosition()).toEqual [2, 4]
|
||||
expect(editor4.getPath()).toBe require.resolve('fixtures/sample.txt')
|
||||
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]
|
||||
expect(editor1.getPath()).toBe require.resolve('fixtures/dir/a')
|
||||
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
|
||||
expect(editor3.getPath()).toBe require.resolve('fixtures/sample.js')
|
||||
expect(editor3.getCursorScreenPosition()).toEqual [2, 4]
|
||||
expect(editor4.getPath()).toBe require.resolve('fixtures/sample.txt')
|
||||
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]
|
||||
|
||||
# ensure adjust pane dimensions is called
|
||||
expect(editor1.width()).toBeGreaterThan 0
|
||||
expect(editor2.width()).toBeGreaterThan 0
|
||||
expect(editor3.width()).toBeGreaterThan 0
|
||||
expect(editor4.width()).toBeGreaterThan 0
|
||||
# ensure adjust pane dimensions is called
|
||||
expect(editor1.width()).toBeGreaterThan 0
|
||||
expect(editor2.width()).toBeGreaterThan 0
|
||||
expect(editor3.width()).toBeGreaterThan 0
|
||||
expect(editor4.width()).toBeGreaterThan 0
|
||||
|
||||
# ensure correct editor is focused again
|
||||
expect(editor2.isFocused).toBeTruthy()
|
||||
expect(editor1.isFocused).toBeFalsy()
|
||||
expect(editor3.isFocused).toBeFalsy()
|
||||
expect(editor4.isFocused).toBeFalsy()
|
||||
# ensure correct editor is focused again
|
||||
expect(editor2.isFocused).toBeTruthy()
|
||||
expect(editor1.isFocused).toBeFalsy()
|
||||
expect(editor3.isFocused).toBeFalsy()
|
||||
expect(editor4.isFocused).toBeFalsy()
|
||||
|
||||
expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{rootView.project.getPath()}"
|
||||
|
||||
describe "where there are no open editors", ->
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
viewState = rootView.serialize()
|
||||
rootView.remove()
|
||||
|
||||
it "constructs the view with no open editors", ->
|
||||
rootView = RootView.deserialize(viewState)
|
||||
rootView.attachToDom()
|
||||
|
||||
expect(rootView.getEditors().length).toBe 0
|
||||
|
||||
expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{rootView.project.getPath()}"
|
||||
|
||||
describe "when called with no pathToOpen", ->
|
||||
it "opens an empty buffer", ->
|
||||
@@ -710,3 +725,59 @@ describe "RootView", ->
|
||||
|
||||
lowerRightEditor = rightEditor.splitDown()
|
||||
expect(lowerRightEditor.find(".line:first").text()).toBe " "
|
||||
|
||||
describe ".eachEditor(callback)", ->
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
|
||||
it "invokes the callback for existing editor", ->
|
||||
count = 0
|
||||
callbackEditor = null
|
||||
callback = (editor) ->
|
||||
callbackEditor = editor
|
||||
count++
|
||||
rootView.eachEditor(callback)
|
||||
expect(count).toBe 1
|
||||
expect(callbackEditor).toBe rootView.getActiveEditor()
|
||||
|
||||
it "invokes the callback for new editor", ->
|
||||
count = 0
|
||||
callbackEditor = null
|
||||
callback = (editor) ->
|
||||
callbackEditor = editor
|
||||
count++
|
||||
|
||||
rootView.eachEditor(callback)
|
||||
count = 0
|
||||
callbackEditor = null
|
||||
rootView.getActiveEditor().splitRight()
|
||||
expect(count).toBe 1
|
||||
expect(callbackEditor).toBe rootView.getActiveEditor()
|
||||
|
||||
describe ".eachBuffer(callback)", ->
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
|
||||
it "invokes the callback for existing buffer", ->
|
||||
count = 0
|
||||
callbackBuffer = null
|
||||
callback = (buffer) ->
|
||||
callbackBuffer = buffer
|
||||
count++
|
||||
rootView.eachBuffer(callback)
|
||||
expect(count).toBe 1
|
||||
expect(callbackBuffer).toBe rootView.getActiveEditor().getBuffer()
|
||||
|
||||
it "invokes the callback for new buffer", ->
|
||||
count = 0
|
||||
callbackBuffer = null
|
||||
callback = (buffer) ->
|
||||
callbackBuffer = buffer
|
||||
count++
|
||||
|
||||
rootView.eachBuffer(callback)
|
||||
count = 0
|
||||
callbackBuffer = null
|
||||
rootView.open(require.resolve('fixtures/sample.txt'))
|
||||
expect(count).toBe 1
|
||||
expect(callbackBuffer).toBe rootView.getActiveEditor().getBuffer()
|
||||
|
||||
1
spec/fixtures/packages/package-that-throws-an-exception/index.coffee
vendored
Normal file
1
spec/fixtures/packages/package-that-throws-an-exception/index.coffee
vendored
Normal file
@@ -0,0 +1 @@
|
||||
throw new Error("This package throws an exception")
|
||||
4
spec/fixtures/packages/package-with-snippets/snippets/test.cson
vendored
Normal file
4
spec/fixtures/packages/package-with-snippets/snippets/test.cson
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
".test":
|
||||
"Test Snippet":
|
||||
prefix: "test"
|
||||
body: "testing 123"
|
||||
@@ -13,11 +13,12 @@ TokenizedBuffer = require 'tokenized-buffer'
|
||||
fs = require 'fs'
|
||||
require 'window'
|
||||
requireStylesheet "jasmine.css"
|
||||
require.paths.unshift(require.resolve('fixtures/packages'))
|
||||
fixturePackagesPath = require.resolve('fixtures/packages')
|
||||
require.paths.unshift(fixturePackagesPath)
|
||||
[bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = []
|
||||
|
||||
# Load TextMate bundles, which specs rely on (but not other packages)
|
||||
atom.loadPackages(atom.getAvailableTextMateBundles())
|
||||
atom.loadTextMatePackages()
|
||||
|
||||
beforeEach ->
|
||||
window.fixturesProject = new Project(require.resolve('fixtures'))
|
||||
@@ -29,9 +30,11 @@ beforeEach ->
|
||||
|
||||
# reset config before each spec; don't load or save from/to `config.json`
|
||||
window.config = new Config()
|
||||
config.packageDirPaths.unshift(fixturePackagesPath)
|
||||
spyOn(config, 'load')
|
||||
spyOn(config, 'save')
|
||||
config.set "editor.fontSize", 16
|
||||
config.set "editor.autoIndent", false
|
||||
|
||||
# make editor display updates synchronous
|
||||
spyOn(Editor.prototype, 'requestDisplayUpdate').andCallFake -> @updateDisplay()
|
||||
@@ -44,6 +47,10 @@ beforeEach ->
|
||||
TokenizedBuffer.prototype.chunkSize = Infinity
|
||||
spyOn(TokenizedBuffer.prototype, "tokenizeInBackground").andCallFake -> @tokenizeNextChunk()
|
||||
|
||||
pasteboardContent = 'initial pasteboard content'
|
||||
spyOn($native, 'writeToPasteboard').andCallFake (text) -> pasteboardContent = text
|
||||
spyOn($native, 'readFromPasteboard').andCallFake -> pasteboardContent
|
||||
|
||||
afterEach ->
|
||||
keymap.bindingSets = bindingSetsToRestore
|
||||
keymap.bindingSetsByFirstKeystrokeToRestore = bindingSetsByFirstKeystrokeToRestore
|
||||
@@ -193,8 +200,5 @@ $.fn.textInput = (data) ->
|
||||
event = jQuery.event.fix(event)
|
||||
$(this).trigger(event)
|
||||
|
||||
$.fn.simulateDomAttachment = ->
|
||||
$('<html>').append(this)
|
||||
|
||||
unless fs.md5ForPath(require.resolve('fixtures/sample.js')) == "dd38087d0d7e3e4802a6d3f9b9745f2b"
|
||||
throw "Sample.js is modified"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
fs = require 'fs'
|
||||
require 'spec-helper'
|
||||
|
||||
|
||||
# Run core specs
|
||||
for path in fs.listTree(require.resolve("spec")) when /-spec\.coffee$/.test path
|
||||
require path
|
||||
|
||||
# Run extension specs
|
||||
for packagePath in fs.listTree(require.resolve("src/packages"))
|
||||
for path in fs.listTree(fs.join(packagePath, "spec")) when /-spec\.coffee$/.test path
|
||||
require path
|
||||
for packageDirPath in config.packageDirPaths
|
||||
for packagePath in fs.listTree(packageDirPath)
|
||||
for path in fs.listTree(fs.join(packagePath, "spec")) when /-spec\.coffee$/.test path
|
||||
require path
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
$ = require 'jquery'
|
||||
{$$} = require 'space-pen'
|
||||
{View, $$} = require 'space-pen'
|
||||
|
||||
describe 'jQuery extensions', ->
|
||||
describe '$.fn.preempt(eventName, handler)', ->
|
||||
@@ -75,3 +75,33 @@ describe 'jQuery extensions', ->
|
||||
'b2': "B2: Looks evil. Kinda is."
|
||||
'a1': "A1: Waste perfectly-good steak"
|
||||
'a2': null
|
||||
|
||||
describe "Event.prototype", ->
|
||||
class GrandchildView extends View
|
||||
@content: -> @div class: 'grandchild'
|
||||
|
||||
class ChildView extends View
|
||||
@content: ->
|
||||
@div class: 'child', =>
|
||||
@subview 'grandchild', new GrandchildView
|
||||
|
||||
class ParentView extends View
|
||||
@content: ->
|
||||
@div class: 'parent', =>
|
||||
@subview 'child', new ChildView
|
||||
|
||||
[parentView, event] = []
|
||||
beforeEach ->
|
||||
parentView = new ParentView
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
parentView.on 'foo', '.child', eventHandler
|
||||
parentView.child.grandchild.trigger 'foo'
|
||||
event = eventHandler.argsForCall[0][0]
|
||||
|
||||
describe ".currentTargetView()", ->
|
||||
it "returns the current target's space pen view", ->
|
||||
expect(event.currentTargetView()).toBe parentView.child
|
||||
|
||||
describe ".targetView()", ->
|
||||
it "returns the target's space pen view", ->
|
||||
expect(event.targetView()).toBe parentView.child.grandchild
|
||||
|
||||
49
spec/time-reporter.coffee
Normal file
49
spec/time-reporter.coffee
Normal file
@@ -0,0 +1,49 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class TimeReporter extends jasmine.Reporter
|
||||
|
||||
timedSpecs: []
|
||||
timedSuites: {}
|
||||
|
||||
constructor: ->
|
||||
window.logLongestSpec = -> window.logLongestSpecs(1)
|
||||
window.logLongestSpecs = (number=10) =>
|
||||
console.log "#{number} longest running specs:"
|
||||
for spec in _.sortBy(@timedSpecs, (spec) -> -spec.time)[0...number]
|
||||
console.log "#{spec.time}ms"
|
||||
console.log spec.description
|
||||
|
||||
window.logLongestSuite = -> window.logLongestSuites(1)
|
||||
window.logLongestSuites = (number=10) =>
|
||||
console.log "#{number} longest running suites:"
|
||||
suites = _.map(@timedSuites, (key, value) -> [value, key])
|
||||
for suite in _.sortBy(suites, (suite) => -suite[1])[0...number]
|
||||
console.log suite[0], suite[1]
|
||||
|
||||
reportSpecStarting: (spec) ->
|
||||
stack = [spec.description]
|
||||
suite = spec.suite
|
||||
while suite
|
||||
stack.unshift suite.description
|
||||
@suite = suite.description
|
||||
suite = suite.parentSuite
|
||||
|
||||
@time = new Date().getTime()
|
||||
reducer = (memo, description, index) ->
|
||||
"#{memo}#{_.multiplyString(' ', index)}#{description}\n"
|
||||
@description = _.reduce(stack, reducer, "")
|
||||
|
||||
reportSpecResults: ->
|
||||
return unless @time? and @description?
|
||||
|
||||
duration = new Date().getTime() - @time
|
||||
@timedSpecs.push
|
||||
description: @description
|
||||
time: duration
|
||||
if @timedSuites[@suite]
|
||||
@timedSuites[@suite] += duration
|
||||
else
|
||||
@timedSuites[@suite] = duration
|
||||
@time = null
|
||||
@description = null
|
||||
@@ -1,4 +1,7 @@
|
||||
Range = require 'range'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Subscriber = require 'subscriber'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class AnchorRange
|
||||
@@ -6,11 +9,14 @@ class AnchorRange
|
||||
end: null
|
||||
buffer: null
|
||||
editSession: null # optional
|
||||
destroyed: false
|
||||
|
||||
constructor: (bufferRange, @buffer, @editSession) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
@startAnchor = @buffer.addAnchorAtPosition(bufferRange.start, ignoreChangesStartingOnAnchor: true)
|
||||
@endAnchor = @buffer.addAnchorAtPosition(bufferRange.end)
|
||||
@subscribe @startAnchor, 'destroyed', => @destroy()
|
||||
@subscribe @endAnchor, 'destroyed', => @destroy()
|
||||
|
||||
getBufferRange: ->
|
||||
new Range(@startAnchor.getBufferPosition(), @endAnchor.getBufferPosition())
|
||||
@@ -22,7 +28,14 @@ class AnchorRange
|
||||
@getBufferRange().containsPoint(bufferPosition)
|
||||
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@unsubscribe()
|
||||
@startAnchor.destroy()
|
||||
@endAnchor.destroy()
|
||||
@buffer.removeAnchorRange(this)
|
||||
@editSession?.removeAnchorRange(this)
|
||||
@destroyed = true
|
||||
@trigger 'destroyed'
|
||||
|
||||
_.extend(AnchorRange.prototype, EventEmitter)
|
||||
_.extend(AnchorRange.prototype, Subscriber)
|
||||
|
||||
@@ -10,6 +10,7 @@ class Anchor
|
||||
screenPosition: null
|
||||
ignoreChangesStartingOnAnchor: false
|
||||
strong: false
|
||||
destroyed: false
|
||||
|
||||
constructor: (@buffer, options = {}) ->
|
||||
{ @editSession, @ignoreChangesStartingOnAnchor, @strong } = options
|
||||
@@ -81,8 +82,10 @@ class Anchor
|
||||
@setScreenPosition(screenPosition, bufferChange: options.bufferChange, clip: false, assignBufferPosition: false, autoscroll: options.autoscroll)
|
||||
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@buffer.removeAnchor(this)
|
||||
@editSession?.removeAnchor(this)
|
||||
@destroyed = true
|
||||
@trigger 'destroyed'
|
||||
|
||||
_.extend(Anchor.prototype, EventEmitter)
|
||||
|
||||
@@ -9,18 +9,18 @@ class AtomPackage extends Package
|
||||
constructor: (@name) ->
|
||||
super
|
||||
@keymapsDirPath = fs.join(@path, 'keymaps')
|
||||
if @requireModule
|
||||
@module = require(@path)
|
||||
@module.name = @name
|
||||
|
||||
load: ->
|
||||
try
|
||||
if @requireModule
|
||||
@module = require(@path)
|
||||
@module.name = @name
|
||||
@loadMetadata()
|
||||
@loadKeymaps()
|
||||
@loadStylesheets()
|
||||
rootView.activatePackage(@name, @module) if @module
|
||||
catch e
|
||||
console.error "Failed to load package named '#{@name}'", e.stack
|
||||
console.warn "Failed to load package named '#{@name}'", e.stack
|
||||
|
||||
loadMetadata: ->
|
||||
if metadataPath = fs.resolveExtension(fs.join(@path, "package"), ['cson', 'json'])
|
||||
|
||||
@@ -12,7 +12,23 @@ _.extend atom,
|
||||
|
||||
pendingBrowserProcessCallbacks: {}
|
||||
|
||||
getAvailablePackages: ->
|
||||
loadPackages: ->
|
||||
pack.load() for pack in @getPackages()
|
||||
|
||||
getPackages: ->
|
||||
@getPackageNames().map (name) -> Package.build(name)
|
||||
|
||||
loadTextMatePackages: ->
|
||||
pack.load() for pack in @getTextMatePackages()
|
||||
|
||||
getTextMatePackages: ->
|
||||
@getPackages().filter (pack) -> pack instanceof TextMatePackage
|
||||
|
||||
loadPackage: (name) ->
|
||||
Package.build(name).load()
|
||||
|
||||
getPackageNames: ->
|
||||
disabledPackages = config.get("core.disabledPackages") ? []
|
||||
allPackageNames = []
|
||||
for packageDirPath in config.packageDirPaths
|
||||
packageNames = fs.list(packageDirPath)
|
||||
@@ -20,17 +36,7 @@ _.extend atom,
|
||||
.map((packagePath) -> fs.base(packagePath))
|
||||
allPackageNames.push(packageNames...)
|
||||
_.unique(allPackageNames)
|
||||
|
||||
getAvailableTextMateBundles: ->
|
||||
@getAvailablePackages().filter (packageName) => TextMatePackage.testName(packageName)
|
||||
|
||||
loadPackages: (packageNames=@getAvailablePackages()) ->
|
||||
disabledPackages = config.get("core.disabledPackages") ? []
|
||||
for packageName in packageNames
|
||||
@loadPackage(packageName) unless _.contains(disabledPackages, packageName)
|
||||
|
||||
loadPackage: (name) ->
|
||||
Package.load(name)
|
||||
.filter (name) -> not _.contains(disabledPackages, name)
|
||||
|
||||
loadThemes: ->
|
||||
themeNames = config.get("core.themes") ? ['IR_Black']
|
||||
@@ -103,3 +109,16 @@ _.extend atom,
|
||||
if name is 'reply'
|
||||
[messageId, callbackIndex] = data.shift()
|
||||
@pendingBrowserProcessCallbacks[messageId]?[callbackIndex]?(data...)
|
||||
|
||||
setWindowState: (keyPath, value) ->
|
||||
windowState = @getWindowState()
|
||||
_.setValueForKeyPath(windowState, keyPath, value)
|
||||
$native.setWindowState(JSON.stringify(windowState))
|
||||
windowState
|
||||
|
||||
getWindowState: (keyPath) ->
|
||||
windowState = JSON.parse($native.getWindowState())
|
||||
if keyPath
|
||||
_.valueForKeyPath(windowState, keyPath)
|
||||
else
|
||||
windowState
|
||||
|
||||
@@ -47,14 +47,7 @@ class Config
|
||||
_.valueForKeyPath(@defaultSettings, keyPath)
|
||||
|
||||
set: (keyPath, value) ->
|
||||
keys = keyPath.split('.')
|
||||
hash = @settings
|
||||
while keys.length > 1
|
||||
key = keys.shift()
|
||||
hash[key] ?= {}
|
||||
hash = hash[key]
|
||||
hash[keys.shift()] = value
|
||||
|
||||
_.setValueForKeyPath(@settings, keyPath, value)
|
||||
@update()
|
||||
value
|
||||
|
||||
|
||||
@@ -175,6 +175,23 @@ class Cursor
|
||||
getCurrentLineBufferRange: (options) ->
|
||||
@editSession.bufferRangeForBufferRow(@getBufferRow(), options)
|
||||
|
||||
getCurrentParagraphBufferRange: ->
|
||||
row = @getBufferRow()
|
||||
return unless /\w/.test(@editSession.lineForBufferRow(row))
|
||||
|
||||
startRow = row
|
||||
while startRow > 0
|
||||
break unless /\w/.test(@editSession.lineForBufferRow(startRow - 1))
|
||||
startRow--
|
||||
|
||||
endRow = row
|
||||
lastRow = @editSession.getLastBufferRow()
|
||||
while endRow < lastRow
|
||||
break unless /\w/.test(@editSession.lineForBufferRow(endRow + 1))
|
||||
endRow++
|
||||
|
||||
new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)])
|
||||
|
||||
getCurrentWordPrefix: ->
|
||||
@editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
|
||||
|
||||
|
||||
@@ -33,11 +33,10 @@ class EditSession
|
||||
anchorRanges: null
|
||||
cursors: null
|
||||
selections: null
|
||||
autoIndent: false # TODO: re-enabled auto-indent after fixing the rest of tokenization
|
||||
softTabs: true
|
||||
softWrap: false
|
||||
|
||||
constructor: ({@project, @buffer, tabLength, @autoIndent, softTabs, @softWrap }) ->
|
||||
constructor: ({@project, @buffer, tabLength, softTabs, @softWrap }) ->
|
||||
@softTabs = @buffer.usesSoftTabs() ? softTabs ? true
|
||||
@languageMode = new LanguageMode(this, @buffer.getExtension())
|
||||
@displayBuffer = new DisplayBuffer(@buffer, { @languageMode, tabLength })
|
||||
@@ -93,7 +92,6 @@ class EditSession
|
||||
getScrollLeft: -> @scrollLeft
|
||||
|
||||
setSoftWrapColumn: (@softWrapColumn) -> @displayBuffer.setSoftWrapColumn(@softWrapColumn)
|
||||
setAutoIndent: (@autoIndent) ->
|
||||
setSoftTabs: (@softTabs) ->
|
||||
|
||||
getSoftWrap: -> @softWrap
|
||||
@@ -141,6 +139,7 @@ class EditSession
|
||||
getLastBufferRow: -> @buffer.getLastRow()
|
||||
bufferRangeForBufferRow: (row, options) -> @buffer.rangeForRow(row, options)
|
||||
lineForBufferRow: (row) -> @buffer.lineForRow(row)
|
||||
lineLengthForBufferRow: (row) -> @buffer.lineLengthForRow(row)
|
||||
scanInRange: (args...) -> @buffer.scanInRange(args...)
|
||||
backwardsScanInRange: (args...) -> @buffer.backwardsScanInRange(args...)
|
||||
|
||||
@@ -159,18 +158,26 @@ class EditSession
|
||||
getCursorScopes: -> @getCursor().getScopes()
|
||||
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
|
||||
|
||||
insertText: (text, options) ->
|
||||
shouldAutoIndent: ->
|
||||
config.get("editor.autoIndent")
|
||||
|
||||
shouldAutoIndentPastedText: ->
|
||||
config.get("editor.autoIndentOnPaste")
|
||||
|
||||
insertText: (text, options={}) ->
|
||||
options.autoIndent ?= @shouldAutoIndent()
|
||||
@mutateSelectedText (selection) -> selection.insertText(text, options)
|
||||
|
||||
insertNewline: ->
|
||||
@insertText('\n', autoIndent: true)
|
||||
@insertText('\n')
|
||||
|
||||
insertNewlineBelow: ->
|
||||
@moveCursorToEndOfLine()
|
||||
@insertNewline()
|
||||
|
||||
indent: ->
|
||||
@mutateSelectedText (selection) -> selection.indent()
|
||||
indent: (options={})->
|
||||
options.autoIndent ?= @shouldAutoIndent()
|
||||
@mutateSelectedText (selection) -> selection.indent(options)
|
||||
|
||||
backspace: ->
|
||||
@mutateSelectedText (selection) -> selection.backspace()
|
||||
@@ -217,9 +224,14 @@ class EditSession
|
||||
selection.copy(maintainPasteboard)
|
||||
maintainPasteboard = true
|
||||
|
||||
pasteText: ->
|
||||
pasteText: (options={}) ->
|
||||
options.normalizeIndent ?= true
|
||||
options.autoIndent ?= @shouldAutoIndentPastedText()
|
||||
|
||||
[text, metadata] = pasteboard.read()
|
||||
@insertText(text, _.extend(metadata ? {}, normalizeIndent: true))
|
||||
_.extend(options, metadata) if metadata
|
||||
|
||||
@insertText(text, options)
|
||||
|
||||
undo: ->
|
||||
@buffer.undo(this)
|
||||
@@ -478,6 +490,9 @@ class EditSession
|
||||
getTextInBufferRange: (range) ->
|
||||
@buffer.getTextInRange(range)
|
||||
|
||||
getCurrentParagraphBufferRange: ->
|
||||
@getCursor().getCurrentParagraphBufferRange()
|
||||
|
||||
moveCursorUp: (lineCount) ->
|
||||
@moveCursors (cursor) -> cursor.moveUp(lineCount)
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ class Editor extends View
|
||||
fontSize: 20
|
||||
showInvisibles: false
|
||||
autosave: false
|
||||
autoIndent: true
|
||||
autoIndentOnPaste: false
|
||||
|
||||
@content: (params) ->
|
||||
@div class: @classes(params), tabindex: -1, =>
|
||||
@@ -80,7 +82,6 @@ class Editor extends View
|
||||
buffer: new Buffer()
|
||||
softWrap: false
|
||||
tabLength: 2
|
||||
autoIndent: false
|
||||
softTabs: true
|
||||
|
||||
@editSessions.push editSession
|
||||
@@ -176,8 +177,8 @@ class Editor extends View
|
||||
'editor:toggle-line-comments': @toggleLineCommentsInSelection
|
||||
'editor:log-cursor-scope': @logCursorScope
|
||||
'editor:checkout-head-revision': @checkoutHead
|
||||
'editor:close-other-editors': @destroyInactiveEditSessions
|
||||
'editor:close-all-editors': @destroyAllEditSessions
|
||||
'editor:close-other-edit-sessions': @destroyInactiveEditSessions
|
||||
'editor:close-all-edit-sessions': @destroyAllEditSessions
|
||||
'editor:select-grammar': @selectGrammar
|
||||
|
||||
documentation = {}
|
||||
@@ -205,6 +206,7 @@ class Editor extends View
|
||||
getCursorScreenRow: -> @activeEditSession.getCursorScreenRow()
|
||||
setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options)
|
||||
getCursorBufferPosition: -> @activeEditSession.getCursorBufferPosition()
|
||||
getCurrentParagraphBufferRange: -> @activeEditSession.getCurrentParagraphBufferRange()
|
||||
|
||||
getSelection: (index) -> @activeEditSession.getSelection(index)
|
||||
getSelections: -> @activeEditSession.getSelections()
|
||||
@@ -244,7 +246,7 @@ class Editor extends View
|
||||
insertText: (text, options) -> @activeEditSession.insertText(text, options)
|
||||
insertNewline: -> @activeEditSession.insertNewline()
|
||||
insertNewlineBelow: -> @activeEditSession.insertNewlineBelow()
|
||||
indent: -> @activeEditSession.indent()
|
||||
indent: (options) -> @activeEditSession.indent(options)
|
||||
indentSelectedRows: -> @activeEditSession.indentSelectedRows()
|
||||
outdentSelectedRows: -> @activeEditSession.outdentSelectedRows()
|
||||
cutSelection: -> @activeEditSession.cutSelectedText()
|
||||
@@ -381,7 +383,7 @@ class Editor extends View
|
||||
@selectOnMousemoveUntilMouseup()
|
||||
|
||||
@on "textInput", (e) =>
|
||||
@insertText(e.originalEvent.data, autoIndent: true)
|
||||
@insertText(e.originalEvent.data)
|
||||
false
|
||||
|
||||
@scrollView.on 'mousewheel', (e) =>
|
||||
@@ -1126,4 +1128,22 @@ class Editor extends View
|
||||
if grammarChanged
|
||||
@clearRenderedLines()
|
||||
@updateDisplay()
|
||||
@trigger 'editor:grammar-changed'
|
||||
grammarChanged
|
||||
|
||||
bindToKeyedEvent: (key, event, callback) ->
|
||||
binding = {}
|
||||
binding[key] = event
|
||||
window.keymap.bindKeys '.editor', binding
|
||||
@on event, =>
|
||||
callback(this, event)
|
||||
|
||||
replaceSelectedText: (replaceFn) ->
|
||||
selection = @getSelection()
|
||||
return false if selection.isEmpty()
|
||||
|
||||
text = replaceFn(@getTextInRange(selection.getBufferRange()))
|
||||
return false if text is null or text is undefined
|
||||
|
||||
@insertText(text, select: true)
|
||||
true
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
Subscriber = require 'subscriber'
|
||||
|
||||
module.exports =
|
||||
class Git
|
||||
@@ -23,26 +25,34 @@ class Git
|
||||
|
||||
constructor: (path) ->
|
||||
@repo = new GitRepository(path)
|
||||
@subscribe $(window), 'focus', => @refreshIndex()
|
||||
|
||||
getRepo: ->
|
||||
unless @repo?
|
||||
throw new Error("No Git repository found searching path: #{path}")
|
||||
$(window).on 'focus', => @refreshIndex()
|
||||
throw new Error("Repository has been destroyed")
|
||||
@repo
|
||||
|
||||
refreshIndex: -> @repo.refreshIndex()
|
||||
refreshIndex: -> @getRepo().refreshIndex()
|
||||
|
||||
getPath: -> @repo.getPath()
|
||||
getPath: -> @getRepo().getPath()
|
||||
|
||||
destroy: ->
|
||||
@getRepo().destroy()
|
||||
@repo = null
|
||||
@unsubscribe()
|
||||
|
||||
getWorkingDirectory: ->
|
||||
repoPath = @getPath()
|
||||
repoPath?.substring(0, repoPath.length - 6)
|
||||
|
||||
getHead: ->
|
||||
@repo.getHead() or ''
|
||||
@getRepo().getHead() or ''
|
||||
|
||||
getPathStatus: (path) ->
|
||||
pathStatus = @repo.getStatus(@relativize(path))
|
||||
pathStatus = @getRepo().getStatus(@relativize(path))
|
||||
|
||||
isPathIgnored: (path) ->
|
||||
@repo.isIgnored(@relativize(path))
|
||||
@getRepo().isIgnored(@relativize(path))
|
||||
|
||||
isStatusModified: (status) ->
|
||||
modifiedFlags = @statusFlags.working_dir_modified |
|
||||
@@ -80,10 +90,12 @@ class Git
|
||||
return head
|
||||
|
||||
checkoutHead: (path) ->
|
||||
@repo.checkoutHead(@relativize(path))
|
||||
@getRepo().checkoutHead(@relativize(path))
|
||||
|
||||
getDiffStats: (path) ->
|
||||
@repo.getDiffStats(@relativize(path)) or added: 0, deleted: 0
|
||||
@getRepo().getDiffStats(@relativize(path)) or added: 0, deleted: 0
|
||||
|
||||
isSubmodule: (path) ->
|
||||
@repo.isSubmodule(@relativize(path))
|
||||
@getRepo().isSubmodule(@relativize(path))
|
||||
|
||||
_.extend Git.prototype, Subscriber
|
||||
|
||||
@@ -33,3 +33,4 @@
|
||||
'.tool-panel':
|
||||
'meta-escape': 'tool-panel:unfocus'
|
||||
'escape': 'core:close'
|
||||
'meta-w': 'noop'
|
||||
@@ -32,6 +32,6 @@
|
||||
'meta-alt-p': 'editor:log-cursor-scope'
|
||||
'meta-u': 'editor:upper-case'
|
||||
'meta-U': 'editor:lower-case'
|
||||
'alt-meta-w': 'editor:close-other-editors'
|
||||
'meta-P': 'editor:close-all-editors'
|
||||
'alt-meta-w': 'editor:close-other-edit-sessions'
|
||||
'meta-P': 'editor:close-all-edit-sessions'
|
||||
'meta-l': 'editor:select-grammar'
|
||||
|
||||
@@ -2,15 +2,13 @@ fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
class Package
|
||||
@load: (name) ->
|
||||
@build: (name) ->
|
||||
AtomPackage = require 'atom-package'
|
||||
TextMatePackage = require 'text-mate-package'
|
||||
|
||||
if TextMatePackage.testName(name)
|
||||
new TextMatePackage(name).load()
|
||||
new TextMatePackage(name)
|
||||
else
|
||||
new AtomPackage(name).load()
|
||||
|
||||
new AtomPackage(name)
|
||||
|
||||
name: null
|
||||
path: null
|
||||
@@ -26,10 +24,3 @@ class Package
|
||||
else
|
||||
@requireModule = true
|
||||
@path = fs.directory(@path)
|
||||
|
||||
load: ->
|
||||
for grammar in @getGrammars()
|
||||
syntax.addGrammar(grammar)
|
||||
|
||||
for { selector, properties } in @getScopedProperties()
|
||||
syntax.addProperties(selector, properties)
|
||||
|
||||
@@ -16,7 +16,6 @@ class Project
|
||||
new Project(state.path, state.grammarOverridesByPath)
|
||||
|
||||
tabLength: 2
|
||||
autoIndent: true
|
||||
softTabs: true
|
||||
softWrap: false
|
||||
rootDirectory: null
|
||||
@@ -34,6 +33,8 @@ class Project
|
||||
grammarOverridesByPath: @grammarOverridesByPath
|
||||
|
||||
destroy: ->
|
||||
@repo?.destroy()
|
||||
@repo = null
|
||||
editSession.destroy() for editSession in @getEditSessions()
|
||||
|
||||
addGrammarOverrideForPath: (path, grammar) ->
|
||||
@@ -91,9 +92,6 @@ class Project
|
||||
relativize: (fullPath) ->
|
||||
fullPath.replace(@getPath(), "").replace(/^\//, '')
|
||||
|
||||
getAutoIndent: -> @autoIndent
|
||||
setAutoIndent: (@autoIndent) ->
|
||||
|
||||
getSoftTabs: -> @softTabs
|
||||
setSoftTabs: (@softTabs) ->
|
||||
|
||||
@@ -114,7 +112,6 @@ class Project
|
||||
|
||||
defaultEditSessionOptions: ->
|
||||
tabLength: @tabLength
|
||||
autoIndent: @getAutoIndent()
|
||||
softTabs: @getSoftTabs()
|
||||
softWrap: @getSoftWrap()
|
||||
|
||||
|
||||
@@ -23,33 +23,39 @@ class RootView extends View
|
||||
@div id: 'vertical', outlet: 'vertical', =>
|
||||
@div id: 'panes', outlet: 'panes'
|
||||
|
||||
@deserialize: ({ projectState, panesViewState, packageStates }) ->
|
||||
project = Project.deserialize(projectState) if projectState
|
||||
rootView = new RootView(project, packageStates: packageStates, suppressOpen: true)
|
||||
@deserialize: ({ projectState, panesViewState, packageStates, projectPath }) ->
|
||||
if projectState
|
||||
projectOrPathToOpen = Project.deserialize(projectState)
|
||||
else
|
||||
projectOrPathToOpen = projectPath # This will migrate people over to the new project serialization scheme. It should be removed eventually.
|
||||
|
||||
rootView = new RootView(projectOrPathToOpen , packageStates: packageStates, suppressOpen: true)
|
||||
rootView.setRootPane(rootView.deserializeView(panesViewState)) if panesViewState
|
||||
rootView
|
||||
|
||||
packageModules: null
|
||||
packageStates: null
|
||||
title: null
|
||||
pathToOpenIsFile: false
|
||||
|
||||
initialize: (projectOrPathToOpen, { @packageStates, suppressOpen } = {}) ->
|
||||
window.rootView = this
|
||||
@packageStates ?= {}
|
||||
@packageModules = {}
|
||||
@handleEvents()
|
||||
|
||||
if not projectOrPathToOpen or _.isString(projectOrPathToOpen)
|
||||
pathToOpen = projectOrPathToOpen
|
||||
@project = new Project(projectOrPathToOpen)
|
||||
else
|
||||
@project = projectOrPathToOpen
|
||||
pathToOpen = @project?.getPath()
|
||||
@pathToOpenIsFile = pathToOpen and fs.isFile(pathToOpen)
|
||||
|
||||
config.load()
|
||||
|
||||
@handleEvents()
|
||||
|
||||
if pathToOpen
|
||||
@open(pathToOpen) if fs.isFile(pathToOpen) and not suppressOpen
|
||||
@open(pathToOpen) if @pathToOpenIsFile and not suppressOpen
|
||||
else
|
||||
@open()
|
||||
|
||||
@@ -74,12 +80,12 @@ class RootView extends View
|
||||
handleEvents: ->
|
||||
@command 'toggle-dev-tools', => atom.toggleDevTools()
|
||||
@on 'focus', (e) => @handleFocus(e)
|
||||
$(window).on 'focus', (e) =>
|
||||
@subscribe $(window), 'focus', (e) =>
|
||||
@handleFocus(e) if document.activeElement is document.body
|
||||
|
||||
@on 'root-view:active-path-changed', (e, path) =>
|
||||
@project.setPath(path) unless @project.getRootDirectory()
|
||||
if path
|
||||
@project.setPath(path) unless @project.getRootDirectory()
|
||||
@setTitle(fs.base(path))
|
||||
else
|
||||
@setTitle("untitled")
|
||||
@@ -98,6 +104,10 @@ class RootView extends View
|
||||
config.set("editor.showInvisibles", !config.get("editor.showInvisibles"))
|
||||
@command 'window:toggle-ignored-files', =>
|
||||
config.set("core.hideGitIgnoredFiles", not config.core.hideGitIgnoredFiles)
|
||||
@command 'window:toggle-auto-indent', =>
|
||||
config.set("editor.autoIndent", !config.get("editor.autoIndent"))
|
||||
@command 'window:toggle-auto-indent-on-paste', =>
|
||||
config.set("editor.autoIndentOnPaste", !config.get("editor.autoIndentOnPaste"))
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if onDom
|
||||
@@ -254,3 +264,17 @@ class RootView extends View
|
||||
|
||||
saveAll: ->
|
||||
editor.save() for editor in @getEditors()
|
||||
|
||||
eachEditor: (callback) ->
|
||||
for editor in @getEditors()
|
||||
callback(editor)
|
||||
|
||||
@on 'editor:attached', (e, editor) ->
|
||||
callback(editor)
|
||||
|
||||
eachBuffer: (callback) ->
|
||||
for buffer in @project.getBuffers()
|
||||
callback(buffer)
|
||||
|
||||
@project.on 'buffer-created', (buffer) ->
|
||||
callback(buffer)
|
||||
|
||||
@@ -174,15 +174,18 @@ class Selection
|
||||
text = @normalizeIndent(text, options) if options.normalizeIndent
|
||||
@clear()
|
||||
newBufferRange = @editSession.buffer.change(oldBufferRange, text)
|
||||
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
|
||||
if options.select
|
||||
@setBufferRange(newBufferRange, reverse: wasReversed)
|
||||
else
|
||||
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
|
||||
|
||||
if @editSession.autoIndent and options.autoIndent
|
||||
if options.autoIndent
|
||||
if text == '\n'
|
||||
@editSession.autoIndentBufferRow(newBufferRange.end.row)
|
||||
else
|
||||
@editSession.autoDecreaseIndentForRow(newBufferRange.start.row)
|
||||
|
||||
indent: ->
|
||||
indent: ({ autoIndent }={})->
|
||||
{ row, column } = @cursor.getBufferPosition()
|
||||
|
||||
if @isEmpty()
|
||||
@@ -190,7 +193,7 @@ class Selection
|
||||
desiredIndent = @editSession.suggestedIndentForBufferRow(row)
|
||||
delta = desiredIndent - @cursor.getIndentLevel()
|
||||
|
||||
if @editSession.autoIndent and delta > 0
|
||||
if autoIndent and delta > 0
|
||||
@insertText(@editSession.buildIndentString(delta))
|
||||
else
|
||||
@insertText(@editSession.getTabText())
|
||||
@@ -218,7 +221,7 @@ class Selection
|
||||
|
||||
if insideExistingLine
|
||||
desiredBasis = @editSession.indentationForBufferRow(currentBufferRow)
|
||||
else if @editSession.autoIndent
|
||||
else if options.autoIndent
|
||||
desiredBasis = @editSession.suggestedIndentForBufferRow(currentBufferRow)
|
||||
else
|
||||
desiredBasis = @cursor.getIndentLevel()
|
||||
|
||||
@@ -23,6 +23,16 @@ class TextMatePackage extends Package
|
||||
@preferencesPath = fs.join(@path, "Preferences")
|
||||
@syntaxesPath = fs.join(@path, "Syntaxes")
|
||||
|
||||
load: ->
|
||||
try
|
||||
for grammar in @getGrammars()
|
||||
syntax.addGrammar(grammar)
|
||||
|
||||
for { selector, properties } in @getScopedProperties()
|
||||
syntax.addProperties(selector, properties)
|
||||
catch e
|
||||
console.warn "Failed to load package named '#{@name}'", e.stack
|
||||
|
||||
getGrammars: ->
|
||||
return @grammars if @grammars
|
||||
@grammars = []
|
||||
|
||||
@@ -47,8 +47,10 @@ windowAdditions =
|
||||
false
|
||||
|
||||
shutdown: ->
|
||||
@rootView?.deactivate()
|
||||
@rootView = null
|
||||
if @rootView
|
||||
atom.setWindowState('pathToOpen', @rootView.project.getPath())
|
||||
@rootView.deactivate()
|
||||
@rootView = null
|
||||
$(window).unbind('focus')
|
||||
$(window).unbind('blur')
|
||||
$(window).off('before')
|
||||
|
||||
1
src/packages/autoflow/index.coffee
Normal file
1
src/packages/autoflow/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require './lib/autoflow'
|
||||
33
src/packages/autoflow/lib/autoflow.coffee
Normal file
33
src/packages/autoflow/lib/autoflow.coffee
Normal file
@@ -0,0 +1,33 @@
|
||||
module.exports =
|
||||
activate: (rootView) ->
|
||||
rootView.command 'autoflow:reflow-paragraph', '.editor', (e) =>
|
||||
@reflowParagraph(e.currentTargetView())
|
||||
|
||||
reflowParagraph: (editor) ->
|
||||
if range = editor.getCurrentParagraphBufferRange()
|
||||
editor.getBuffer().change(range, @reflow(editor.getTextInRange(range)))
|
||||
|
||||
reflow: (text) ->
|
||||
wrapColumn = config.get('editor.preferredLineLength') ? 80
|
||||
lines = []
|
||||
|
||||
currentLine = []
|
||||
currentLineLength = 0
|
||||
for segment in @segmentText(text.replace(/\n/g, ' '))
|
||||
if /\w/.test(segment) and
|
||||
(currentLineLength + segment.length > wrapColumn) and
|
||||
(currentLineLength > 0 or segment.length < wrapColumn)
|
||||
lines.push(currentLine.join(''))
|
||||
currentLine = []
|
||||
currentLineLength = 0
|
||||
currentLine.push(segment)
|
||||
currentLineLength += segment.length
|
||||
lines.push(currentLine.join(''))
|
||||
|
||||
lines.join('\n').replace(/\s+\n/g, '\n')
|
||||
|
||||
segmentText: (text) ->
|
||||
segments = []
|
||||
re = /[\s]+|[^\s]+/g
|
||||
segments.push(match[0]) while match = re.exec(text)
|
||||
segments
|
||||
53
src/packages/autoflow/spec/autoflow-spec.coffee
Normal file
53
src/packages/autoflow/spec/autoflow-spec.coffee
Normal file
@@ -0,0 +1,53 @@
|
||||
RootView = require 'root-view'
|
||||
|
||||
describe "Autoflow package", ->
|
||||
editor = null
|
||||
|
||||
beforeEach ->
|
||||
rootView = new RootView
|
||||
atom.loadPackage 'autoflow'
|
||||
editor = rootView.getActiveEditor()
|
||||
config.set('editor.preferredLineLength', 30)
|
||||
|
||||
describe "autoflow:reflow-paragraph", ->
|
||||
it "rearranges line breaks in the current paragraph to ensure lines are shorter than config.editor.preferredLineLength", ->
|
||||
editor.setText """
|
||||
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
|
||||
|
||||
The quick brown fox jumps over the lazy
|
||||
dog. The preceding sentence contains every letter
|
||||
in the entire English alphabet, which has absolutely no relevance
|
||||
to this test.
|
||||
|
||||
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
|
||||
|
||||
"""
|
||||
|
||||
editor.setCursorBufferPosition([3, 5])
|
||||
editor.trigger 'autoflow:reflow-paragraph'
|
||||
|
||||
expect(editor.getText()).toBe """
|
||||
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
|
||||
|
||||
The quick brown fox jumps over
|
||||
the lazy dog. The preceding
|
||||
sentence contains every letter
|
||||
in the entire English
|
||||
alphabet, which has absolutely
|
||||
no relevance to this test.
|
||||
|
||||
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
|
||||
|
||||
"""
|
||||
|
||||
it "allows for single words that exceed the preferred wrap column length", ->
|
||||
editor.setText("this-is-a-super-long-word-that-shouldn't-break-autoflow and these are some smaller words")
|
||||
|
||||
editor.setCursorBufferPosition([0, 4])
|
||||
editor.trigger 'autoflow:reflow-paragraph'
|
||||
|
||||
expect(editor.getText()).toBe """
|
||||
this-is-a-super-long-word-that-shouldn't-break-autoflow
|
||||
and these are some smaller
|
||||
words
|
||||
"""
|
||||
@@ -9,7 +9,6 @@ describe "CommandLogger", ->
|
||||
atom.loadPackage 'command-logger'
|
||||
editor = rootView.getActiveEditor()
|
||||
commandLogger = CommandLogger.instance
|
||||
rootView.attachToDom()
|
||||
|
||||
afterEach ->
|
||||
rootView.deactivate()
|
||||
|
||||
@@ -24,6 +24,10 @@ class CommandLogger extends ScrollView
|
||||
'core:cancel'
|
||||
'core:confirm'
|
||||
'core:delete'
|
||||
'core:move-down'
|
||||
'core:move-left'
|
||||
'core:move-right'
|
||||
'core:move-up'
|
||||
'editor:newline'
|
||||
'tree-view:directory-modified'
|
||||
]
|
||||
@@ -82,6 +86,28 @@ class CommandLogger extends ScrollView
|
||||
@div style: "height:#{node.dy - 1}px;width:#{node.dx - 1}px", =>
|
||||
@span node.name
|
||||
|
||||
updateCategoryHeader: (node) ->
|
||||
@categoryHeader.text("#{node.name} Commands")
|
||||
reduceRunCount = (previous, current) ->
|
||||
if current.size?
|
||||
previous + current.size
|
||||
else if current.children?.length > 0
|
||||
current.children.reduce(reduceRunCount, previous)
|
||||
else
|
||||
previous
|
||||
runCount = node.children.reduce(reduceRunCount, 0)
|
||||
reduceCommandCount = (previous, current) ->
|
||||
if current.children?.length > 0
|
||||
current.children.reduce(reduceCommandCount, previous)
|
||||
else
|
||||
previous + 1
|
||||
commandCount = node.children.reduce(reduceCommandCount, 0)
|
||||
@categorySummary.text("#{_.pluralize(commandCount, 'command')}, #{_.pluralize(runCount, 'invocation')}")
|
||||
|
||||
updateTreeMapSize: ->
|
||||
@treeMap.width(@width() - 20)
|
||||
@treeMap.height(@height() - @categoryHeader.outerHeight() - @categorySummary.outerHeight() - 20)
|
||||
|
||||
addTreeMap: ->
|
||||
root =
|
||||
name: 'All'
|
||||
@@ -90,33 +116,17 @@ class CommandLogger extends ScrollView
|
||||
|
||||
@treeMap.empty()
|
||||
|
||||
@updateCategoryHeader(root)
|
||||
@updateTreeMapSize()
|
||||
w = @treeMap.width()
|
||||
h = @treeMap.height()
|
||||
|
||||
x = d3.scale.linear().range([0, w])
|
||||
y = d3.scale.linear().range([0, h])
|
||||
color = d3.scale.category20()
|
||||
|
||||
updateCategoryHeader = (node) =>
|
||||
@categoryHeader.text("#{node.name} Commands")
|
||||
reduceRunCount = (previous, current) ->
|
||||
if current.size?
|
||||
previous + current.size
|
||||
else if current.children?.length > 0
|
||||
current.children.reduce(reduceRunCount, previous)
|
||||
else
|
||||
previous
|
||||
runCount = node.children.reduce(reduceRunCount, 0)
|
||||
reduceCommandCount = (previous, current) ->
|
||||
if current.children?.length > 0
|
||||
current.children.reduce(reduceCommandCount, previous)
|
||||
else
|
||||
previous + 1
|
||||
commandCount = node.children.reduce(reduceCommandCount, 0)
|
||||
@categorySummary.text("#{_.pluralize(commandCount, 'command')}, #{_.pluralize(runCount, 'invocation')}")
|
||||
updateCategoryHeader(root)
|
||||
|
||||
zoom = (d) ->
|
||||
updateCategoryHeader(d)
|
||||
zoom = (d) =>
|
||||
@updateCategoryHeader(d)
|
||||
kx = w / d.dx
|
||||
ky = h / d.dy
|
||||
x.domain([d.x, d.x + d.dx])
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
color: #eee;
|
||||
overflow: auto;
|
||||
z-index: 99;
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.command-logger .category-header {
|
||||
@@ -25,9 +26,6 @@
|
||||
|
||||
.command-logger .tree-map {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
width: 960px;
|
||||
height: 700px;
|
||||
background-color: #efefef;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ describe "CommandPanel", ->
|
||||
describe "when the preview list is/was previously visible", ->
|
||||
beforeEach ->
|
||||
rootView.trigger 'command-panel:toggle'
|
||||
waitsForPromise -> commandPanel.execute('X x/a+/')
|
||||
waitsForPromise -> commandPanel.execute('X x/quicksort/')
|
||||
|
||||
describe "when the command panel is visible", ->
|
||||
beforeEach ->
|
||||
@@ -290,22 +290,22 @@ describe "CommandPanel", ->
|
||||
rootView.attachToDom()
|
||||
editor.remove()
|
||||
rootView.trigger 'command-panel:toggle'
|
||||
waitsForPromise -> commandPanel.execute('X x/a+/')
|
||||
waitsForPromise -> commandPanel.execute('X x/quicksort/')
|
||||
|
||||
it "displays and focuses the operation preview list", ->
|
||||
expect(commandPanel).toBeVisible()
|
||||
expect(commandPanel.previewList).toBeVisible()
|
||||
expect(commandPanel.previewList).toMatchSelector ':focus'
|
||||
previewItem = commandPanel.previewList.find("li:contains(dir/a):first")
|
||||
expect(previewItem.text()).toBe "dir/a"
|
||||
expect(previewItem.next().find('.preview').text()).toBe "aaa bbb"
|
||||
expect(previewItem.next().find('.preview > .match').text()).toBe "aaa"
|
||||
previewItem = commandPanel.previewList.find("li:contains(sample.js):first")
|
||||
expect(previewItem.text()).toBe "sample.js"
|
||||
expect(previewItem.next().find('.preview').text()).toBe "var quicksort = function () {"
|
||||
expect(previewItem.next().find('.preview > .match').text()).toBe "quicksort"
|
||||
|
||||
rootView.trigger 'command-panel:toggle-preview' # ensure we can close panel without problems
|
||||
expect(commandPanel).toBeHidden()
|
||||
|
||||
it "destroys previously previewed operations if there are any", ->
|
||||
waitsForPromise -> commandPanel.execute('X x/b+/')
|
||||
waitsForPromise -> commandPanel.execute('X x/pivot/')
|
||||
# there shouldn't be any dangling operations after this
|
||||
|
||||
describe "if the command is malformed", ->
|
||||
@@ -375,7 +375,7 @@ describe "CommandPanel", ->
|
||||
beforeEach ->
|
||||
previewList = commandPanel.previewList
|
||||
rootView.trigger 'command-panel:toggle'
|
||||
waitsForPromise -> commandPanel.execute('X x/a/')
|
||||
waitsForPromise -> commandPanel.execute('X x/sort/')
|
||||
|
||||
describe "when move-down and move-up are triggered on the preview list", ->
|
||||
it "selects the next/previous operation (if there is one), and scrolls the list if needed", ->
|
||||
|
||||
@@ -19,7 +19,7 @@ describe 'FuzzyFinder', ->
|
||||
describe "file-finder behavior", ->
|
||||
describe "toggling", ->
|
||||
describe "when the root view's project has a path", ->
|
||||
it "shows the FuzzyFinder or hides it nad returns focus to the active editor if it already showing", ->
|
||||
it "shows the FuzzyFinder or hides it and returns focus to the active editor if it already showing", ->
|
||||
rootView.attachToDom()
|
||||
expect(rootView.find('.fuzzy-finder')).not.toExist()
|
||||
rootView.find('.editor').trigger 'editor:split-right'
|
||||
@@ -86,6 +86,18 @@ describe 'FuzzyFinder', ->
|
||||
expect(editor2.getPath()).toBe expectedPath
|
||||
expect(editor2.isFocused).toBeTruthy()
|
||||
|
||||
describe "when the selected path isn't a file that exists", ->
|
||||
it "leaves the the tree view open, doesn't open the path in the editor, and displays an error", ->
|
||||
rootView.attachToDom()
|
||||
path = rootView.getActiveEditor().getPath()
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
finder.confirmed('dir/this/is/not/a/file.txt')
|
||||
expect(finder.hasParent()).toBeTruthy()
|
||||
expect(rootView.getActiveEditor().getPath()).toBe path
|
||||
expect(finder.find('.error').text().length).toBeGreaterThan 0
|
||||
advanceClock(2000)
|
||||
expect(finder.find('.error').text().length).toBe 0
|
||||
|
||||
describe "buffer-finder behavior", ->
|
||||
describe "toggling", ->
|
||||
describe "when the active editor contains edit sessions for buffers with paths", ->
|
||||
@@ -243,36 +255,20 @@ describe 'FuzzyFinder', ->
|
||||
$(window).trigger 'focus'
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
|
||||
waitsFor ->
|
||||
finder.list.children('li').length > 0
|
||||
|
||||
runs ->
|
||||
expect(rootView.project.getFilePaths).toHaveBeenCalled()
|
||||
|
||||
describe "path ignoring", ->
|
||||
it "ignores paths that match entries in config.fuzzy-finder.ignoredNames", ->
|
||||
it "ignores paths that match entries in config.fuzzyFinder.ignoredNames", ->
|
||||
spyOn(rootView.project, "getFilePaths").andCallThrough()
|
||||
config.set("fuzzy-finder.ignoredNames", ["tree-view"])
|
||||
config.set("fuzzyFinder.ignoredNames", ["tree-view.js"])
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
finder.maxItems = Infinity
|
||||
finder.miniEditor.setText("file1")
|
||||
|
||||
waitsFor ->
|
||||
finder.list.children('li').length > 0
|
||||
|
||||
runs ->
|
||||
expect(rootView.project.getFilePaths).toHaveBeenCalled()
|
||||
rootView.project.getFilePaths.reset()
|
||||
$(window).trigger 'focus'
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
rootView.trigger 'fuzzy-finder:toggle-file-finder'
|
||||
|
||||
waitsFor ->
|
||||
finder.list.children('li').length > 0
|
||||
|
||||
runs ->
|
||||
expect(rootView.project.getFilePaths).toHaveBeenCalled()
|
||||
expect(finder.list.find("li:contains(tree-view.js)")).not.toExist()
|
||||
|
||||
describe "opening a path into a split", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -17,12 +17,13 @@ class FuzzyFinder extends SelectList
|
||||
allowActiveEditorChange: null
|
||||
maxItems: 10
|
||||
projectPaths: null
|
||||
reloadProjectPaths: true
|
||||
|
||||
initialize: (@rootView) ->
|
||||
super
|
||||
@subscribe $(window), 'focus', => @projectPaths = null
|
||||
@observeConfig 'fuzzy-finder.ignoredNames', (ignoredNames) =>
|
||||
@projectPaths = null
|
||||
|
||||
@subscribe $(window), 'focus', => @reloadProjectPaths = true
|
||||
@observeConfig 'fuzzy-finder.ignoredNames', => @reloadProjectPaths = true
|
||||
|
||||
@miniEditor.command 'editor:split-left', =>
|
||||
@splitOpenPath (editor, session) -> editor.splitLeft(session)
|
||||
@@ -64,8 +65,12 @@ class FuzzyFinder extends SelectList
|
||||
|
||||
confirmed : (path) ->
|
||||
return unless path.length
|
||||
@cancel()
|
||||
@openPath(path)
|
||||
if fs.isFile(rootView.project.resolve(path))
|
||||
@cancel()
|
||||
@openPath(path)
|
||||
else
|
||||
@setError('Selected path does not exist')
|
||||
setTimeout((=> @setError()), 2000)
|
||||
|
||||
cancelled: ->
|
||||
@miniEditor.setText('')
|
||||
@@ -93,6 +98,8 @@ class FuzzyFinder extends SelectList
|
||||
@setArray(@projectPaths)
|
||||
else
|
||||
@setLoading("Indexing...")
|
||||
|
||||
if @reloadProjectPaths
|
||||
@rootView.project.getFilePaths().done (paths) =>
|
||||
ignoredNames = config.get("fuzzyFinder.ignoredNames") or []
|
||||
ignoredNames = ignoredNames.concat(config.get("core.ignoredNames") or [])
|
||||
@@ -103,6 +110,7 @@ class FuzzyFinder extends SelectList
|
||||
return false if _.contains(ignoredNames, segment)
|
||||
return true
|
||||
|
||||
@reloadProjectPaths = false
|
||||
@setArray(@projectPaths)
|
||||
|
||||
populateOpenBufferPaths: ->
|
||||
|
||||
@@ -9,7 +9,6 @@ describe "MarkdownPreview", ->
|
||||
rootView = new RootView(require.resolve('fixtures/markdown'))
|
||||
atom.loadPackage("markdown-preview")
|
||||
markdownPreview = MarkdownPreview.instance
|
||||
rootView.attachToDom()
|
||||
|
||||
afterEach ->
|
||||
rootView.deactivate()
|
||||
|
||||
@@ -1,39 +1,15 @@
|
||||
{
|
||||
var Snippet = require('snippets/src/snippet');
|
||||
var Point = require('point');
|
||||
}
|
||||
bodyContent = content:(tabStop / bodyContentText)* { return content; }
|
||||
bodyContentText = text:bodyContentChar+ { return text.join(''); }
|
||||
bodyContentChar = !tabStop char:. { return char; }
|
||||
|
||||
snippets = snippets:snippet+ ws? {
|
||||
var snippetsByPrefix = {};
|
||||
snippets.forEach(function(snippet) {
|
||||
snippetsByPrefix[snippet.prefix] = snippet
|
||||
});
|
||||
return snippetsByPrefix;
|
||||
}
|
||||
placeholderContent = content:(tabStop / placeholderContentText)* { return content; }
|
||||
placeholderContentText = text:placeholderContentChar+ { return text.join(''); }
|
||||
placeholderContentChar = !tabStop char:[^}] { return char; }
|
||||
|
||||
snippet = ws? start ws prefix:prefix ws description:string bodyPosition:beforeBody body:body end {
|
||||
return new Snippet({ bodyPosition: bodyPosition, prefix: prefix, description: description, body: body });
|
||||
}
|
||||
|
||||
start = 'snippet'
|
||||
prefix = prefix:[A-Za-z0-9_]+ { return prefix.join(''); }
|
||||
string = ['] body:[^']* ['] { return body.join(''); }
|
||||
/ ["] body:[^"]* ["] { return body.join(''); }
|
||||
|
||||
beforeBody = [ ]* '\n' { return new Point(line, 0); } // return start position of body: body begins on next line, so don't subtract 1 from line
|
||||
|
||||
body = bodyLine+
|
||||
bodyLine = content:(tabStop / bodyText)* '\n' { return content; }
|
||||
bodyText = text:bodyChar+ { return text.join(''); }
|
||||
bodyChar = !(end / tabStop) char:[^\n] { return char; }
|
||||
tabStop = simpleTabStop / tabStopWithPlaceholder
|
||||
simpleTabStop = '$' index:[0-9]+ {
|
||||
return { index: parseInt(index), placeholderText: '' };
|
||||
return { index: parseInt(index), content: [] };
|
||||
}
|
||||
tabStopWithPlaceholder = '${' index:[0-9]+ ':' placeholderText:[^}]* '}' {
|
||||
return { index: parseInt(index), placeholderText: placeholderText.join('') };
|
||||
tabStopWithPlaceholder = '${' index:[0-9]+ ':' content:placeholderContent '}' {
|
||||
return { index: parseInt(index), content: content };
|
||||
}
|
||||
|
||||
end = 'endsnippet'
|
||||
ws = ([ \n] / comment)+
|
||||
comment = '#' [^\n]*
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
Snippets = require 'snippets'
|
||||
Snippet = require 'snippets/src/snippet'
|
||||
RootView = require 'root-view'
|
||||
Buffer = require 'buffer'
|
||||
Editor = require 'editor'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
AtomPackage = require 'atom-package'
|
||||
TextMatePackage = require 'text-mate-package'
|
||||
|
||||
describe "Snippets extension", ->
|
||||
[buffer, editor] = []
|
||||
beforeEach ->
|
||||
rootView = new RootView(require.resolve('fixtures/sample.js'))
|
||||
spyOn(AtomPackage.prototype, 'loadSnippets')
|
||||
spyOn(TextMatePackage.prototype, 'loadSnippets')
|
||||
atom.loadPackage("snippets")
|
||||
editor = rootView.getActiveEditor()
|
||||
buffer = editor.getBuffer()
|
||||
@@ -17,36 +22,50 @@ describe "Snippets extension", ->
|
||||
|
||||
afterEach ->
|
||||
rootView.remove()
|
||||
delete window.snippets
|
||||
|
||||
describe "when 'tab' is triggered on the editor", ->
|
||||
beforeEach ->
|
||||
Snippets.evalSnippets 'js', """
|
||||
snippet t1 "Snippet without tab stops"
|
||||
this is a test
|
||||
endsnippet
|
||||
snippets.add
|
||||
".source.js":
|
||||
"without tab stops":
|
||||
prefix: "t1"
|
||||
body: "this is a test"
|
||||
|
||||
snippet t2 "With tab stops"
|
||||
go here next:($2) and finally go here:($3)
|
||||
go here first:($1)
|
||||
"tab stops":
|
||||
prefix: "t2"
|
||||
body: """
|
||||
go here next:($2) and finally go here:($0)
|
||||
go here first:($1)
|
||||
|
||||
endsnippet
|
||||
"""
|
||||
|
||||
snippet t3 "With indented second line"
|
||||
line 1
|
||||
line 2$1
|
||||
"indented second line":
|
||||
prefix: "t3"
|
||||
body: """
|
||||
line 1
|
||||
line 2$1
|
||||
|
||||
endsnippet
|
||||
"""
|
||||
|
||||
snippet t4 "With tab stop placeholders"
|
||||
go here ${1:first} and then here ${2:second}
|
||||
"tab stop placeholders":
|
||||
prefix: "t4"
|
||||
body: """
|
||||
go here ${1:first
|
||||
think a while}, and then here ${2:second}
|
||||
|
||||
endsnippet
|
||||
"""
|
||||
|
||||
snippet t5 "Caused problems with undo"
|
||||
first line$1
|
||||
${2:placeholder ending second line}
|
||||
endsnippet
|
||||
"""
|
||||
"nested tab stops":
|
||||
prefix: "t5"
|
||||
body: '${1:"${2:key}"}: ${3:value}'
|
||||
|
||||
"caused problems with undo":
|
||||
prefix: "t6"
|
||||
body: """
|
||||
first line$1
|
||||
${2:placeholder ending second line}
|
||||
"""
|
||||
|
||||
describe "when the letters preceding the cursor trigger a snippet", ->
|
||||
describe "when the snippet contains no tab stops", ->
|
||||
@@ -98,8 +117,22 @@ describe "Snippets extension", ->
|
||||
it "auto-fills the placeholder text and highlights it when navigating to that tab stop", ->
|
||||
editor.insertText 't4'
|
||||
editor.trigger 'snippets:expand'
|
||||
expect(buffer.lineForRow(0)).toBe 'go here first and then here second'
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0, 8], [0, 13]]
|
||||
expect(buffer.lineForRow(0)).toBe 'go here first'
|
||||
expect(buffer.lineForRow(1)).toBe 'think a while, and then here second'
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0, 8], [1, 13]]
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[1, 29], [1, 35]]
|
||||
|
||||
describe "when tab stops are nested", ->
|
||||
it "destroys the inner tab stop if the outer tab stop is modified", ->
|
||||
buffer.setText('')
|
||||
editor.insertText 't5'
|
||||
editor.trigger 'snippets:expand'
|
||||
expect(buffer.lineForRow(0)).toBe '"key": value'
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 5]]
|
||||
editor.insertText("foo")
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0, 5], [0, 10]]
|
||||
|
||||
describe "when the cursor is moved beyond the bounds of a tab stop", ->
|
||||
it "terminates the snippet", ->
|
||||
@@ -152,92 +185,68 @@ describe "Snippets extension", ->
|
||||
|
||||
describe "when a previous snippet expansion has just been undone", ->
|
||||
it "expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", ->
|
||||
editor.insertText 't5\n'
|
||||
editor.insertText 't6\n'
|
||||
editor.setCursorBufferPosition [0, 2]
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(buffer.lineForRow(0)).toBe "first line"
|
||||
editor.undo()
|
||||
expect(buffer.lineForRow(0)).toBe "t5"
|
||||
expect(buffer.lineForRow(0)).toBe "t6"
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(buffer.lineForRow(0)).toBe "first line"
|
||||
|
||||
describe "when a snippet expansion is undone and redone", ->
|
||||
it "recreates the snippet's tab stops", ->
|
||||
editor.insertText ' t5\n'
|
||||
editor.insertText ' t6\n'
|
||||
editor.setCursorBufferPosition [0, 6]
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(buffer.lineForRow(0)).toBe " first line"
|
||||
editor.undo()
|
||||
editor.redo()
|
||||
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 14]
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[1, 6], [1, 36]]
|
||||
|
||||
it "restores tabs stops in active edit session even when the initial expansion was in a different edit session", ->
|
||||
anotherEditor = editor.splitRight()
|
||||
describe "snippet loading", ->
|
||||
it "loads snippets from all atom packages with a snippets directory", ->
|
||||
jasmine.unspy(AtomPackage.prototype, 'loadSnippets')
|
||||
snippets.loadAll()
|
||||
|
||||
editor.insertText ' t5\n'
|
||||
editor.setCursorBufferPosition [0, 6]
|
||||
editor.trigger keydownEvent('tab', target: editor[0])
|
||||
expect(buffer.lineForRow(0)).toBe " first line"
|
||||
editor.undo()
|
||||
expect(syntax.getProperty(['.test'], 'snippets.test')?.constructor).toBe Snippet
|
||||
|
||||
anotherEditor.redo()
|
||||
expect(anotherEditor.getCursorBufferPosition()).toEqual [0, 14]
|
||||
anotherEditor.trigger keydownEvent('tab', target: anotherEditor[0])
|
||||
expect(anotherEditor.getSelectedBufferRange()).toEqual [[1, 6], [1, 36]]
|
||||
it "loads snippets from all TextMate packages with snippets", ->
|
||||
jasmine.unspy(TextMatePackage.prototype, 'loadSnippets')
|
||||
snippets.loadAll()
|
||||
|
||||
describe ".loadSnippetsFile(path)", ->
|
||||
it "loads the snippets in the given file", ->
|
||||
spyOn(fs, 'read').andReturn """
|
||||
snippet t1 "Test snippet 1"
|
||||
this is a test 1
|
||||
endsnippet
|
||||
snippet = syntax.getProperty(['.source.js'], 'snippets.fun')
|
||||
expect(snippet.constructor).toBe Snippet
|
||||
expect(snippet.prefix).toBe 'fun'
|
||||
expect(snippet.name).toBe 'Function'
|
||||
expect(snippet.body).toBe """
|
||||
function function_name (argument) {
|
||||
\t// body...
|
||||
}
|
||||
"""
|
||||
|
||||
Snippets.loadSnippetsFile('/tmp/foo/js.snippets')
|
||||
expect(fs.read).toHaveBeenCalledWith('/tmp/foo/js.snippets')
|
||||
|
||||
editor.insertText("t1")
|
||||
editor.trigger 'snippets:expand'
|
||||
expect(buffer.lineForRow(0)).toBe "this is a test 1var quicksort = function () {"
|
||||
|
||||
describe "Snippets parser", ->
|
||||
it "can parse multiple snippets", ->
|
||||
snippets = Snippets.snippetsParser.parse """
|
||||
snippet t1 "Test snippet 1"
|
||||
this is a test 1
|
||||
endsnippet
|
||||
|
||||
snippet t2 "Test snippet 2"
|
||||
this is a test 2
|
||||
endsnippet
|
||||
"""
|
||||
expect(_.keys(snippets).length).toBe 2
|
||||
snippet = snippets['t1']
|
||||
expect(snippet.prefix).toBe 't1'
|
||||
expect(snippet.description).toBe "Test snippet 1"
|
||||
expect(snippet.body).toBe "this is a test 1"
|
||||
|
||||
snippet = snippets['t2']
|
||||
expect(snippet.prefix).toBe 't2'
|
||||
expect(snippet.description).toBe "Test snippet 2"
|
||||
expect(snippet.body).toBe "this is a test 2"
|
||||
|
||||
it "can parse snippets with tabstops", ->
|
||||
snippets = Snippets.snippetsParser.parse """
|
||||
# this line intentially left blank.
|
||||
snippet t1 "Snippet with tab stops"
|
||||
go here next:($2) and finally go here:($3)
|
||||
go here first:($1)
|
||||
endsnippet
|
||||
it "breaks a snippet body into lines, with each line containing tab stops at the appropriate position", ->
|
||||
bodyTree = Snippets.parser.parse """
|
||||
the quick brown $1fox ${2:jumped ${3:over}
|
||||
}the ${4:lazy} dog
|
||||
"""
|
||||
|
||||
snippet = snippets['t1']
|
||||
expect(snippet.body).toBe """
|
||||
go here next:() and finally go here:()
|
||||
go here first:()
|
||||
"""
|
||||
|
||||
expect(snippet.tabStops).toEqual [[[1, 15], [1, 15]], [[0, 14], [0, 14]], [[0, 37], [0, 37]]]
|
||||
expect(bodyTree).toEqual [
|
||||
"the quick brown ",
|
||||
{ index: 1, content: [] },
|
||||
"fox ",
|
||||
{
|
||||
index: 2,
|
||||
content: [
|
||||
"jumped ",
|
||||
{ index: 3, content: ["over"]},
|
||||
"\n"
|
||||
],
|
||||
}
|
||||
"the "
|
||||
{ index: 4, content: ["lazy"] },
|
||||
" dog"
|
||||
]
|
||||
|
||||
27
src/packages/snippets/src/package-extensions.coffee
Normal file
27
src/packages/snippets/src/package-extensions.coffee
Normal file
@@ -0,0 +1,27 @@
|
||||
AtomPackage = require 'atom-package'
|
||||
TextMatePackage = require 'text-mate-package'
|
||||
fs = require 'fs'
|
||||
|
||||
AtomPackage.prototype.loadSnippets = ->
|
||||
snippetsDirPath = fs.join(@path, 'snippets')
|
||||
if fs.exists(snippetsDirPath)
|
||||
for snippetsPath in fs.list(snippetsDirPath)
|
||||
snippets.load(snippetsPath)
|
||||
|
||||
TextMatePackage.prototype.loadSnippets = ->
|
||||
snippetsDirPath = fs.join(@path, 'Snippets')
|
||||
if fs.exists(snippetsDirPath)
|
||||
tmSnippets = fs.list(snippetsDirPath).map (snippetPath) -> fs.readPlist(snippetPath)
|
||||
snippets.add(@translateSnippets(tmSnippets))
|
||||
|
||||
TextMatePackage.prototype.translateSnippets = (tmSnippets) ->
|
||||
atomSnippets = {}
|
||||
for { scope, name, content, tabTrigger } in tmSnippets
|
||||
if scope
|
||||
scope = TextMatePackage.cssSelectorFromScopeSelector(scope)
|
||||
else
|
||||
scope = '*'
|
||||
|
||||
snippetsForScope = (atomSnippets[scope] ?= {})
|
||||
snippetsForScope[name] = { prefix: tabTrigger, body: content }
|
||||
atomSnippets
|
||||
@@ -1,33 +1,44 @@
|
||||
Subscriber = require 'subscriber'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class SnippetExpansion
|
||||
snippet: null
|
||||
tabStopAnchorRanges: null
|
||||
settingTabStop: false
|
||||
|
||||
constructor: (snippet, @editSession) ->
|
||||
constructor: (@snippet, @editSession) ->
|
||||
@editSession.selectToBeginningOfWord()
|
||||
startPosition = @editSession.getCursorBufferPosition()
|
||||
@editSession.transact =>
|
||||
@editSession.insertText(snippet.body, autoIndent: false)
|
||||
if snippet.tabStops.length
|
||||
@placeTabStopAnchorRanges(startPosition, snippet.tabStops)
|
||||
if snippet.lineCount > 1
|
||||
@indentSubsequentLines(startPosition.row, snippet)
|
||||
editSession.pushOperation
|
||||
do: =>
|
||||
@subscribe @editSession, 'cursor-moved.snippet-expansion', (e) => @cursorMoved(e)
|
||||
@placeTabStopAnchorRanges(startPosition, snippet.tabStops)
|
||||
@editSession.snippetExpansion = this
|
||||
undo: => @destroy()
|
||||
@indentSubsequentLines(startPosition.row, snippet) if snippet.lineCount > 1
|
||||
|
||||
@editSession.on 'cursor-moved.snippet-expansion', ({oldBufferPosition, newBufferPosition}) =>
|
||||
return if @settingTabStop
|
||||
cursorMoved: ({oldBufferPosition, newBufferPosition}) ->
|
||||
return if @settingTabStop
|
||||
|
||||
oldTabStops = @tabStopsForBufferPosition(oldBufferPosition)
|
||||
newTabStops = @tabStopsForBufferPosition(newBufferPosition)
|
||||
oldTabStops = @tabStopsForBufferPosition(oldBufferPosition)
|
||||
newTabStops = @tabStopsForBufferPosition(newBufferPosition)
|
||||
|
||||
@destroy() unless _.intersect(oldTabStops, newTabStops).length
|
||||
@destroy() unless _.intersect(oldTabStops, newTabStops).length
|
||||
|
||||
placeTabStopAnchorRanges: (startPosition, tabStopRanges) ->
|
||||
return unless @snippet.tabStops.length > 0
|
||||
|
||||
@tabStopAnchorRanges = tabStopRanges.map ({start, end}) =>
|
||||
@editSession.addAnchorRange([startPosition.add(start), startPosition.add(end)])
|
||||
anchorRange = @editSession.addAnchorRange([startPosition.add(start), startPosition.add(end)])
|
||||
@subscribe anchorRange, 'destroyed', =>
|
||||
_.remove(@tabStopAnchorRanges, anchorRange)
|
||||
anchorRange
|
||||
@setTabStopIndex(0)
|
||||
|
||||
|
||||
indentSubsequentLines: (startRow, snippet) ->
|
||||
initialIndent = @editSession.lineForBufferRow(startRow).match(/^\s*/)[0]
|
||||
for row in [startRow + 1...startRow + snippet.lineCount]
|
||||
@@ -68,11 +79,13 @@ class SnippetExpansion
|
||||
_.intersection(@tabStopAnchorRanges, @editSession.anchorRangesForBufferPosition(bufferPosition))
|
||||
|
||||
destroy: ->
|
||||
@unsubscribe()
|
||||
anchorRange.destroy() for anchorRange in @tabStopAnchorRanges
|
||||
@editSession.off '.snippet-expansion'
|
||||
@editSession.snippetExpansion = null
|
||||
|
||||
restore: (@editSession) ->
|
||||
@editSession.snippetExpansion = this
|
||||
@tabStopAnchorRanges = @tabStopAnchorRanges.map (anchorRange) =>
|
||||
@editSession.addAnchorRange(anchorRange.getBufferRange())
|
||||
|
||||
_.extend(SnippetExpansion.prototype, Subscriber)
|
||||
|
||||
@@ -3,34 +3,41 @@ Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
class Snippet
|
||||
name: null
|
||||
prefix: null
|
||||
body: null
|
||||
lineCount: null
|
||||
tabStops: null
|
||||
|
||||
constructor: ({@bodyPosition, @prefix, @description, body}) ->
|
||||
@body = @extractTabStops(body)
|
||||
constructor: ({@name, @prefix, bodyTree}) ->
|
||||
@body = @extractTabStops(bodyTree)
|
||||
|
||||
extractTabStops: (bodyLines) ->
|
||||
extractTabStops: (bodyTree) ->
|
||||
tabStopsByIndex = {}
|
||||
bodyText = []
|
||||
|
||||
[row, column] = [0, 0]
|
||||
for bodyLine, i in bodyLines
|
||||
lineText = []
|
||||
for segment in bodyLine
|
||||
if segment.index
|
||||
{ index, placeholderText } = segment
|
||||
tabStopsByIndex[index] = new Range([row, column], [row, column + placeholderText.length])
|
||||
lineText.push(placeholderText)
|
||||
else
|
||||
lineText.push(segment)
|
||||
column += segment.length
|
||||
bodyText.push(lineText.join(''))
|
||||
row++; column = 0
|
||||
@lineCount = row
|
||||
|
||||
# recursive helper function; mutates vars above
|
||||
extractTabStops = (bodyTree) ->
|
||||
for segment in bodyTree
|
||||
if segment.index?
|
||||
{ index, content } = segment
|
||||
index = Infinity if index == 0
|
||||
start = [row, column]
|
||||
extractTabStops(content)
|
||||
tabStopsByIndex[index] = new Range(start, [row, column])
|
||||
else if _.isString(segment)
|
||||
bodyText.push(segment)
|
||||
segmentLines = segment.split('\n')
|
||||
column += segmentLines.shift().length
|
||||
while nextLine = segmentLines.shift()
|
||||
row += 1
|
||||
column = nextLine.length
|
||||
|
||||
extractTabStops(bodyTree)
|
||||
@lineCount = row + 1
|
||||
@tabStops = []
|
||||
for index in _.keys(tabStopsByIndex).sort()
|
||||
@tabStops.push tabStopsByIndex[index]
|
||||
|
||||
bodyText.join('\n')
|
||||
bodyText.join('')
|
||||
|
||||
@@ -2,38 +2,46 @@ fs = require 'fs'
|
||||
PEG = require 'pegjs'
|
||||
_ = require 'underscore'
|
||||
SnippetExpansion = require 'snippets/src/snippet-expansion'
|
||||
Snippet = require './snippet'
|
||||
require './package-extensions'
|
||||
|
||||
module.exports =
|
||||
name: 'Snippets'
|
||||
snippetsByExtension: {}
|
||||
snippetsParser: PEG.buildParser(fs.read(require.resolve 'snippets/snippets.pegjs'), trackLineAndColumn: true)
|
||||
parser: PEG.buildParser(fs.read(require.resolve 'snippets/snippets.pegjs'), trackLineAndColumn: true)
|
||||
userSnippetsDir: fs.join(config.configDirPath, 'snippets')
|
||||
|
||||
activate: (@rootView) ->
|
||||
@loadSnippets()
|
||||
window.snippets = this
|
||||
@loadAll()
|
||||
@rootView.on 'editor:attached', (e, editor) => @enableSnippetsInEditor(editor)
|
||||
|
||||
loadSnippets: ->
|
||||
snippetsDir = fs.join(config.configDirPath, 'snippets')
|
||||
if fs.exists(snippetsDir)
|
||||
@loadSnippetsFile(path) for path in fs.list(snippetsDir) when fs.extension(path) == '.snippets'
|
||||
loadAll: ->
|
||||
for pack in atom.getPackages()
|
||||
pack.loadSnippets()
|
||||
|
||||
loadSnippetsFile: (path) ->
|
||||
@evalSnippets(fs.base(path, '.snippets'), fs.read(path))
|
||||
for snippetsPath in fs.list(@userSnippetsDir)
|
||||
@load(snippetsPath)
|
||||
|
||||
evalSnippets: (extension, text) ->
|
||||
@snippetsByExtension[extension] = @snippetsParser.parse(text)
|
||||
load: (snippetsPath) ->
|
||||
@add(fs.readObject(snippetsPath))
|
||||
|
||||
add: (snippetsBySelector) ->
|
||||
for selector, snippetsByName of snippetsBySelector
|
||||
snippetsByPrefix = {}
|
||||
for name, attributes of snippetsByName
|
||||
{ prefix, body } = attributes
|
||||
bodyTree = @parser.parse(body)
|
||||
snippet = new Snippet({name, prefix, bodyTree})
|
||||
snippetsByPrefix[snippet.prefix] = snippet
|
||||
syntax.addProperties(selector, snippets: snippetsByPrefix)
|
||||
|
||||
enableSnippetsInEditor: (editor) ->
|
||||
editor.command 'snippets:expand', (e) =>
|
||||
editSession = editor.activeEditSession
|
||||
prefix = editSession.getCursor().getCurrentWordPrefix()
|
||||
if snippet = @snippetsByExtension[editSession.getFileExtension()]?[prefix]
|
||||
if snippet = syntax.getProperty(editSession.getCursorScopes(), "snippets.#{prefix}")
|
||||
editSession.transact ->
|
||||
snippetExpansion = new SnippetExpansion(snippet, editSession)
|
||||
editSession.snippetExpansion = snippetExpansion
|
||||
editSession.pushOperation
|
||||
undo: -> snippetExpansion.destroy()
|
||||
redo: (editSession) -> snippetExpansion.restore(editSession)
|
||||
new SnippetExpansion(snippet, editSession)
|
||||
else
|
||||
e.abortKeyBinding()
|
||||
|
||||
|
||||
@@ -176,3 +176,21 @@ describe "StatusBar", ->
|
||||
it "displays the diff stat for new files", ->
|
||||
rootView.open(newPath)
|
||||
expect(statusBar.gitStatusIcon).toHaveText('+1')
|
||||
|
||||
describe "grammar label", ->
|
||||
it "displays the name of the current grammar", ->
|
||||
expect(statusBar.find('.grammar-name').text()).toBe 'JavaScript'
|
||||
|
||||
describe "when the editor's grammar changes", ->
|
||||
it "displays the new grammar of the editor", ->
|
||||
textGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'Plain Text'
|
||||
rootView.project.addGrammarOverrideForPath(editor.getPath(), textGrammar)
|
||||
editor.reloadGrammar()
|
||||
expect(statusBar.find('.grammar-name').text()).toBe textGrammar.name
|
||||
|
||||
describe "when clicked", ->
|
||||
it "toggles the editor:select-grammar event", ->
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
editor.on 'editor:select-grammar', eventHandler
|
||||
statusBar.find('.grammar-name').click()
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
@@ -25,7 +25,7 @@ class StatusBar extends View
|
||||
@span class: 'current-path', outlet: 'currentPath'
|
||||
@span class: 'buffer-modified', outlet: 'bufferModified'
|
||||
@span class: 'cursor-position', outlet: 'cursorPosition'
|
||||
|
||||
@span class: 'grammar-name', outlet: 'grammarName'
|
||||
|
||||
initialize: (@rootView, @editor) ->
|
||||
@updatePathText()
|
||||
@@ -36,6 +36,8 @@ class StatusBar extends View
|
||||
@updateCursorPositionText()
|
||||
@subscribe @editor, 'cursor:moved', => @updateCursorPositionText()
|
||||
@subscribe $(window), 'focus', => @updateStatusBar()
|
||||
@subscribe @grammarName, 'click', => @editor.trigger 'editor:select-grammar'
|
||||
@subscribe @editor, 'editor:grammar-changed', => @updateGrammarText()
|
||||
|
||||
@subscribeToBuffer()
|
||||
|
||||
@@ -48,10 +50,14 @@ class StatusBar extends View
|
||||
@updateStatusBar()
|
||||
|
||||
updateStatusBar: ->
|
||||
@updateGrammarText()
|
||||
@updateBranchText()
|
||||
@updateBufferHasModifiedText(@buffer.isModified())
|
||||
@updateStatusText()
|
||||
|
||||
updateGrammarText: ->
|
||||
@grammarName.text(@editor.getGrammar().name)
|
||||
|
||||
updateBufferHasModifiedText: (differsFromDisk)->
|
||||
if differsFromDisk
|
||||
@bufferModified.text('*') unless @isModified
|
||||
|
||||
@@ -6,12 +6,19 @@
|
||||
line-height: 14px;
|
||||
color: #969696;
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.status-bar .cursor-position {
|
||||
.status-bar .cursor-position,
|
||||
.status-bar .grammar-name {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.status-bar .grammar-name {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.status-bar .git-branch {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
module.exports =
|
||||
name: "strip trailing whitespace"
|
||||
|
||||
activate: (rootView) ->
|
||||
for buffer in rootView.project.getBuffers()
|
||||
@stripTrailingWhitespaceBeforeSave(buffer)
|
||||
|
||||
rootView.project.on 'buffer-created', (buffer) =>
|
||||
@stripTrailingWhitespaceBeforeSave(buffer)
|
||||
rootView.eachBuffer (buffer) => @stripTrailingWhitespaceBeforeSave(buffer)
|
||||
|
||||
stripTrailingWhitespaceBeforeSave: (buffer) ->
|
||||
buffer.on 'will-be-saved', ->
|
||||
|
||||
@@ -14,7 +14,7 @@ describe "TreeView", ->
|
||||
project = rootView.project
|
||||
|
||||
atom.loadPackage("tree-view")
|
||||
treeView = rootView.find(".tree-view").view()
|
||||
treeView = TreeView.instance
|
||||
treeView.root = treeView.find('> li:first').view()
|
||||
sampleJs = treeView.find('.file:contains(tree-view.js)')
|
||||
sampleTxt = treeView.find('.file:contains(tree-view.txt)')
|
||||
@@ -52,24 +52,38 @@ describe "TreeView", ->
|
||||
|
||||
rootView = new RootView
|
||||
atom.loadPackage 'tree-view'
|
||||
treeView = rootView.find(".tree-view").view()
|
||||
treeView = TreeView.instance
|
||||
|
||||
it "does not create a root node", ->
|
||||
it "does not attach to the root view or create a root node when initialized", ->
|
||||
expect(treeView.hasParent()).toBeFalsy()
|
||||
expect(treeView.root).not.toExist()
|
||||
|
||||
it "does not attach to the root view or create a root node when attach() is called", ->
|
||||
treeView.attach()
|
||||
expect(treeView.hasParent()).toBeFalsy()
|
||||
expect(treeView.root).not.toExist()
|
||||
|
||||
it "serializes without throwing an exception", ->
|
||||
expect(-> treeView.serialize()).not.toThrow()
|
||||
|
||||
it "creates a root view when the project path is created", ->
|
||||
rootView.open(require.resolve('fixtures/sample.js'))
|
||||
expect(treeView.root.getPath()).toBe require.resolve('fixtures')
|
||||
expect(treeView.root.parent()).toMatchSelector(".tree-view")
|
||||
describe "when the project is assigned a path because a new buffer is saved", ->
|
||||
it "creates a root directory view but does not attach to the root view", ->
|
||||
rootView.getActiveEditSession().saveAs("/tmp/test.txt")
|
||||
expect(treeView.hasParent()).toBeFalsy()
|
||||
expect(treeView.root.getPath()).toBe require.resolve('/tmp')
|
||||
expect(treeView.root.parent()).toMatchSelector(".tree-view")
|
||||
|
||||
oldRoot = treeView.root
|
||||
describe "when the root view is opened to a file path", ->
|
||||
beforeEach ->
|
||||
rootView.deactivate()
|
||||
|
||||
rootView.project.setPath('/tmp')
|
||||
expect(treeView.root).not.toEqual oldRoot
|
||||
expect(oldRoot.hasParent()).toBeFalsy()
|
||||
rootView = new RootView(require.resolve('fixtures/tree-view/tree-view.js'))
|
||||
atom.loadPackage 'tree-view'
|
||||
treeView = TreeView.instance
|
||||
|
||||
it "does not attach to the root view but does create a root node when initialized", ->
|
||||
expect(treeView.hasParent()).toBeFalsy()
|
||||
expect(treeView.root).toExist()
|
||||
|
||||
describe "serialization", ->
|
||||
[newRootView, newTreeView] = []
|
||||
|
||||
@@ -16,7 +16,9 @@ class TreeView extends ScrollView
|
||||
@instance = TreeView.deserialize(state, rootView)
|
||||
else
|
||||
@instance = new TreeView(rootView)
|
||||
@instance.attach()
|
||||
|
||||
if rootView.project.getPath() and not rootView.pathToOpenIsFile
|
||||
@instance.attach()
|
||||
|
||||
@deactivate: ->
|
||||
@instance.deactivate()
|
||||
@@ -91,6 +93,7 @@ class TreeView extends ScrollView
|
||||
@attach()
|
||||
|
||||
attach: ->
|
||||
return unless rootView.project.getPath()
|
||||
@rootView.horizontal.prepend(this)
|
||||
@focus()
|
||||
|
||||
@@ -118,8 +121,8 @@ class TreeView extends ScrollView
|
||||
|
||||
updateRoot: ->
|
||||
@root?.remove()
|
||||
if @rootView.project.getRootDirectory()
|
||||
@root = new DirectoryView(directory: @rootView.project.getRootDirectory(), isExpanded: true, project: @rootView.project)
|
||||
if rootDirectory = @rootView.project.getRootDirectory()
|
||||
@root = new DirectoryView(directory: rootDirectory, isExpanded: true, project: @rootView.project)
|
||||
@append(@root)
|
||||
else
|
||||
@root = null
|
||||
@@ -130,7 +133,6 @@ class TreeView extends ScrollView
|
||||
|
||||
revealActiveFile: ->
|
||||
@attach()
|
||||
@focus()
|
||||
|
||||
return unless activeFilePath = @rootView.getActiveEditor()?.getPath()
|
||||
|
||||
|
||||
@@ -184,3 +184,11 @@ module.exports =
|
||||
CoffeeScript.eval(contents, bare: true)
|
||||
else
|
||||
JSON.parse(contents)
|
||||
|
||||
readPlist: (path) ->
|
||||
plist = require 'plist'
|
||||
object = null
|
||||
plist.parseString @read(path), (e, data) ->
|
||||
throw new Error(e) if e
|
||||
object = data[0]
|
||||
object
|
||||
|
||||
@@ -74,3 +74,5 @@ $.fn.command = (args...) ->
|
||||
@on(args...)
|
||||
|
||||
$.Event.prototype.abortKeyBinding = ->
|
||||
$.Event.prototype.currentTargetView = -> $(this.currentTarget).view()
|
||||
$.Event.prototype.targetView = -> $(this.target).view()
|
||||
|
||||
@@ -99,6 +99,14 @@ _.mixin
|
||||
return unless object?
|
||||
object
|
||||
|
||||
setValueForKeyPath: (object, keyPath, value) ->
|
||||
keys = keyPath.split('.')
|
||||
while keys.length > 1
|
||||
key = keys.shift()
|
||||
object[key] ?= {}
|
||||
object = object[key]
|
||||
object[keys.shift()] = value
|
||||
|
||||
compactObject: (object) ->
|
||||
newObject = {}
|
||||
for key, value of object
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Like sands through the hourglass, so are the days of our lives.
|
||||
require 'atom'
|
||||
require 'window'
|
||||
window.attachRootView(window.location.params.pathToOpen)
|
||||
|
||||
pathToOpen = atom.getWindowState('pathToOpen') ? window.location.params.pathToOpen
|
||||
window.attachRootView(pathToOpen)
|
||||
|
||||
@@ -87,7 +87,7 @@ html, body {
|
||||
|
||||
@font-face {
|
||||
font-family: 'Octicons Regular';
|
||||
src: url(octicons-regular-webfont.ttf) format(truetype);
|
||||
src: url("octicons-regular-webfont.woff") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
static/octicons-regular-webfont.woff
Normal file
BIN
static/octicons-regular-webfont.woff
Normal file
Binary file not shown.
3
vendor/jasmine-helper.coffee
vendored
3
vendor/jasmine-helper.coffee
vendored
@@ -6,6 +6,7 @@ module.exports.runSpecSuite = (specSuite, logErrors=true) ->
|
||||
nakedLoad 'jasmine-focused'
|
||||
|
||||
$ = require 'jquery'
|
||||
TimeReporter = require 'time-reporter'
|
||||
|
||||
$('body').append $$ ->
|
||||
@div id: 'jasmine-content'
|
||||
@@ -18,5 +19,7 @@ module.exports.runSpecSuite = (specSuite, logErrors=true) ->
|
||||
require specSuite
|
||||
jasmineEnv = jasmine.getEnv()
|
||||
jasmineEnv.addReporter(reporter)
|
||||
|
||||
jasmineEnv.addReporter(new TimeReporter())
|
||||
jasmineEnv.specFilter = (spec) -> reporter.specFilter(spec)
|
||||
jasmineEnv.execute()
|
||||
|
||||
Reference in New Issue
Block a user