Merge branch 'master' into sm-flexible-modals

This commit is contained in:
simurai
2016-05-19 10:18:04 +09:00
179 changed files with 6476 additions and 7474 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ debug.log
docs/output
docs/includes
spec/fixtures/evil-files/
out/

View File

@@ -1,24 +1,46 @@
# Contributor Code of Conduct
# Contributor Covenant Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
## Our Pledge
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery
- Personal attacks
- Trolling or insulting/derogatory comments
- Public or private harassment
- Publishing other's private information, such as physical or electronic addresses, without explicit permission
- Other unethical or unprofessional conduct
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
## Enforcement
This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -50,7 +50,7 @@ To get a sense for the packages that are bundled with Atom, you can go to Settin
Here's a list of the big ones:
* [atom/atom](https://github.com/atom/atom) - Atom Core! The core editor component is responsible for basic text editing (e.g. cursors, selections, scrolling), text indentation, wrapping, and folding, text rendering, editor rendering, file system operations (e.g. saving), and installation and auto-updating. You should also use this repository for feedback related to the [core API](https://atom.io/docs/api/latest/Notification) and for large, overarching design proposals.
* [atom/atom](https://github.com/atom/atom) - Atom Core! The core editor component is responsible for basic text editing (e.g. cursors, selections, scrolling), text indentation, wrapping, and folding, text rendering, editor rendering, file system operations (e.g. saving), and installation and auto-updating. You should also use this repository for feedback related to the [core API](https://atom.io/docs/api/latest) and for large, overarching design proposals.
* [tree-view](https://github.com/atom/tree-view) - file and directory listing on the left of the UI.
* [fuzzy-finder](https://github.com/atom/fuzzy-finder) - the quick file opener.
* [find-and-replace](https://github.com/atom/find-and-replace) - all search and replace functionality.
@@ -62,7 +62,7 @@ Here's a list of the big ones:
* [git-diff](https://github.com/atom/git-diff) - Git change indicators shown in the editor's gutter.
* [language-javascript](https://github.com/atom/language-javascript) - all bundled languages are packages too, and each one has a separate package `language-[name]`. Use these for feedback on syntax highlighting issues that only appear for a specific language.
* [one-dark-ui](https://github.com/atom/one-dark-ui) - the default UI styling for anything but the text editor. UI theme packages (i.e. packages with a `-ui` suffix) provide only styling and it's possible that a bundled package is responsible for a UI issue. There are other other bundled UI themes, such as [one-light-ui](https://github.com/atom/one-light-ui).
* [one-dark-syntax](https://github.com/atom/one-dark-syntax) - the default syntax highlighting styles applied for all languages. There are other other bundled syntax themes, such as [solarized-dark](https://github.com/atom/solarized-dark). You should use these packages for reporting issues that appear in many languages, but disappear if you change to another syntax theme.
* [one-dark-syntax](https://github.com/atom/one-dark-syntax) - the default syntax highlighting styles applied for all languages. There are other other bundled syntax themes, such as [solarized-dark-syntax](https://github.com/atom/solarized-dark-syntax). You should use these packages for reporting issues that appear in many languages, but disappear if you change to another syntax theme.
* [apm](https://github.com/atom/apm) - the `apm` command line tool (Atom Package Manager). You should use this repository for any contributions related to the `apm` tool and to publishing packages.
* [atom.io](https://github.com/atom/atom.io) - the repository for feedback on the [Atom.io website](https://atom.io) and the [Atom.io package API](https://github.com/atom/atom/blob/master/docs/apm-rest-api.md) used by [apm](https://github.com/atom/apm).
@@ -82,7 +82,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
#### Before Submitting A Bug Report
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings).
* **Check the [FAQs on the forum](https://discuss.atom.io/c/faq)** for a list of common questions and problems.
* **Determine [which repository the problem should be reported in](#atom-and-packages)**.
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one.
@@ -100,13 +100,13 @@ Explain the problem and include additional details to help maintainers reproduce
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On OSX, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](https://atom.io/docs/latest/hacking-atom-debugging#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
* **If the Chrome's developer tools pane is shown without you triggering it**, that normally means that an exception was thrown. The Console tab will include an entry for the exception. Expand the exception so that the stack trace is visible, and provide the full exception and stack trace in a [code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines) and as a screenshot.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
Provide more context by answering these questions:
* **Can you reproduce the problem in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode)?**
* **Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance-problems-with-the-dev-tools-cpu-profiler)?**
* **Did the problem start happening recently** (e.g. after updating to a new version of Atom) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Atom?** What's the most recent version in which the problem doesn't happen? You can download older versions of Atom from [the releases page](https://github.com/atom/atom/releases).
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
@@ -118,7 +118,7 @@ Include details about your configuration and environment:
* **What's the name and version of the OS you're using**?
* **Are you running Atom in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest?
* **Which [packages](#atom-and-packages) do you have installed?** You can get that list by running `apm list --installed`.
* **Are you using [local configuration files](https://atom.io/docs/latest/using-atom-basic-customization)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
* **Are you using [local configuration files](http://flight-manual.atom.io/using-atom/sections/basic-customization/)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
* **Are you using Atom with multiple monitors?** If so, can you reproduce the problem when you use a single monitor?
* **Which keyboard layout are you using?** Are you using a US layout or some other layout?
@@ -166,7 +166,7 @@ Before creating enhancement suggestions, please check [this list](#before-submit
#### Before Submitting An Enhancement Suggestion
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings).
* **Check if there's already [a package](https://atom.io/packages) which provides that enhancement.**
* **Determine [which repository the enhancement should be suggested in](#atom-and-packages).**
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
@@ -365,7 +365,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
| `blocked` | [search][search-atom-repo-label-blocked] | [search][search-atom-org-label-blocked] | Issues blocked on other issues. |
| `duplicate` | [search][search-atom-repo-label-duplicate] | [search][search-atom-org-label-duplicate] | Issues which are duplicates of other issues, i.e. they have been reported before. |
| `wontfix` | [search][search-atom-repo-label-wontfix] | [search][search-atom-org-label-wontfix] | The Atom core team has decided not to fix these issues for now, either because they're working as intended or for some other reason. |
| `invalid` | [search][search-atom-repo-label-invalid] | [search][search-atom-org-label-invalid] | Issues which are't valid (e.g. user errors). |
| `invalid` | [search][search-atom-repo-label-invalid] | [search][search-atom-org-label-invalid] | Issues which aren't valid (e.g. user errors). |
| `package-idea` | [search][search-atom-repo-label-package-idea] | [search][search-atom-org-label-package-idea] | Feature request which might be good candidates for new packages, instead of extending Atom or core Atom packages. |
| `wrong-repo` | [search][search-atom-repo-label-wrong-repo] | [search][search-atom-org-label-wrong-repo] | Issues reported on the wrong repository (e.g. a bug related to the [Settings View package](https://github.com/atom/settings-view) was reported on [Atom core](https://github.com/atom/atom)). |
@@ -376,7 +376,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
| `windows` | [search][search-atom-repo-label-windows] | [search][search-atom-org-label-windows] | Related to Atom running on Windows. |
| `linux` | [search][search-atom-repo-label-linux] | [search][search-atom-org-label-linux] | Related to Atom running on Linux. |
| `mac` | [search][search-atom-repo-label-mac] | [search][search-atom-org-label-mac] | Related to Atom running on OSX. |
| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/Atom) and the [flight manual](https://atom.io/docs/latest/)). |
| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/) and the [flight manual](http://flight-manual.atom.io/)). |
| `performance` | [search][search-atom-repo-label-performance] | [search][search-atom-org-label-performance] | Related to performance. |
| `security` | [search][search-atom-repo-label-security] | [search][search-atom-org-label-security] | Related to security. |
| `ui` | [search][search-atom-repo-label-ui] | [search][search-atom-org-label-ui] | Related to visual design. |
@@ -406,10 +406,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
| Label name | `atom/atom` :mag_right: | `atom`org :mag_right: | Description |
| --- | --- | --- | --- |
| `in-progress` | [search][search-atom-repo-label-in-progress] | [search][search-atom-org-label-in-progress] | Tasks which the Atom core team is working on currently. |
| `on-deck` | [search][search-atom-repo-label-on-deck] | [search][search-atom-org-label-on-deck] | Tasks which the Atom core team plans to work on next. |
| `shipping` | [search][search-atom-repo-label-shipping] | [search][search-atom-org-label-shipping] | Tasks which the Atom core team completed and will be released in one of the next releases. |
| `post-1.0-roadmap` | [search][search-atom-repo-label-post-1.0-roadmap] | [search][search-atom-org-label-post-1.0-roadmap] | The Atom core team's roadmap post version 1.0.0. |
| `atom` | [search][search-atom-repo-label-atom] | [search][search-atom-org-label-atom] | Topics discussed for prioritization at the next meeting of Atom core team members. |
#### Pull Request Labels
@@ -498,14 +494,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
[search-atom-org-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Adeprecation-help
[search-atom-repo-label-electron]: https://github.com/issues?q=is%3Aissue+repo%3Aatom%2Fatom+is%3Aopen+label%3Aelectron
[search-atom-org-label-electron]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aelectron
[search-atom-repo-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aon-deck
[search-atom-org-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aon-deck
[search-atom-repo-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ain-progress
[search-atom-org-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Ain-progress
[search-atom-repo-label-shipping]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ashipping
[search-atom-org-label-shipping]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Ashipping
[search-atom-repo-label-post-1.0-roadmap]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Apost-1.0-roadmap
[search-atom-org-label-post-1.0-roadmap]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Apost-1.0-roadmap
[search-atom-repo-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aatom
[search-atom-org-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aatom
[search-atom-repo-label-work-in-progress]: https://github.com/pulls?q=is%3Aopen+is%3Apr+repo%3Aatom%2Fatom+label%3Awork-in-progress

28
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,28 @@
### Prerequisites
* [ ] Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode)?
* [ ] Are you running the [latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version)?
* [ ] Did you check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)?
* [ ] Did you check the [FAQs on Discuss](https://discuss.atom.io/c/faq)?
* [ ] Are you reporting to the [correct repository](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#atom-and-packages)?
* [ ] Did you [perform a cursory search](https://github.com/issues?q=is%3Aissue+user%3Aatom+-repo%3Aatom%2Felectron) to see if your bug or enhancement is already reported?
For more information on how to write a good [bug report](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#how-do-i-submit-a-good-bug-report) or [enhancement request](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#how-do-i-submit-a-good-enhancement-suggestion), see the `CONTRIBUTING` guide.
### Description
[Description of the bug or feature]
### Steps to Reproduce
1. [First Step]
2. [Second Step]
3. [and so on...]
**Expected behavior:** [What you expected to happen]
**Actual behavior:** [What actually happened]
### Versions
You can get this information from executing `atom --version` and `apm --version` at the command line. Also, please include the OS and what version of the OS you're running.

View File

@@ -76,6 +76,22 @@ Currently only a 64-bit version is available.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
### Archive extraction
An archive is available for people who don't want to install `atom` as root.
This version enables you to install multiple Atom versions in parallel. It has been built on Ubuntu 64-bit,
but should be compatible with other Linux distributions.
1. Install dependencies (on Ubuntu): `sudo apt install git gconf2 gconf-service libgtk2.0-0 libudev1 libgcrypt20
libnotify4 libxtst6 libnss3 python gvfs-bin xdg-utils libcap2`
2. Download `atom-amd64.tar.gz` from the [Atom releases page](https://github.com/atom/atom/releases/latest).
3. Run `tar xf atom-amd64.tar.gz` in the directory where you want to extract the Atom folder.
4. Launch Atom using the installed `atom` command from the newly extracted directory.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
## Building
* [Linux](docs/build-instructions/linux.md)

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.6.0"
"atom-package-manager": "1.10.0"
}
}

View File

@@ -4,8 +4,6 @@ if [ "$(uname)" == 'Darwin' ]; then
OS='Mac'
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
OS='Linux'
elif [ "$(expr substr $(uname -s) 1 10)" == 'MINGW32_NT' ]; then
OS='Cygwin'
else
echo "Your platform ($(uname -a)) is not supported."
exit 1

View File

@@ -34,23 +34,10 @@ module.exports = (grunt) ->
grunt.file.setBase(path.resolve('..'))
# Options
[defaultChannel, releaseBranch] = getDefaultChannelAndReleaseBranch(packageJson.version)
installDir = grunt.option('install-dir')
buildDir = grunt.option('build-dir')
buildDir ?= 'out'
buildDir = path.resolve(buildDir)
channel = grunt.option('channel')
releasableBranches = ['stable', 'beta']
if process.env.APPVEYOR and not process.env.APPVEYOR_PULL_REQUEST_NUMBER
channel ?= process.env.APPVEYOR_REPO_BRANCH if process.env.APPVEYOR_REPO_BRANCH in releasableBranches
if process.env.TRAVIS and not process.env.TRAVIS_PULL_REQUEST
channel ?= process.env.TRAVIS_BRANCH if process.env.TRAVIS_BRANCH in releasableBranches
if process.env.JANKY_BRANCH
channel ?= process.env.JANKY_BRANCH if process.env.JANKY_BRANCH in releasableBranches
channel ?= 'dev'
buildDir = path.resolve(grunt.option('build-dir') ? 'out')
channel = grunt.option('channel') ? defaultChannel
metadata = packageJson
appName = packageJson.productName
@@ -70,7 +57,7 @@ module.exports = (grunt) ->
homeDir = process.env.USERPROFILE
contentsDir = shellAppDir
appDir = path.join(shellAppDir, 'resources', 'app')
installDir ?= path.join(process.env.ProgramFiles, appName)
installDir ?= path.join(process.env.LOCALAPPDATA, appName, 'app-dev')
killCommand = 'taskkill /F /IM atom.exe'
else if process.platform is 'darwin'
homeDir = process.env.HOME
@@ -189,7 +176,7 @@ module.exports = (grunt) ->
pkg: grunt.file.readJSON('package.json')
atom: {
appName, channel, metadata,
appName, channel, metadata, releaseBranch,
appFileName, apmFileName,
appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir,
}
@@ -298,6 +285,7 @@ module.exports = (grunt) ->
ciTasks.push('dump-symbols') if process.platform is 'darwin'
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
ciTasks.push('mkdeb') if process.platform is 'linux'
ciTasks.push('mktar') if process.platform is 'linux'
ciTasks.push('codesign:exe') if process.platform is 'win32' and not process.env.CI
ciTasks.push('create-windows-installer:installer') if process.platform is 'win32'
ciTasks.push('test') if process.platform is 'darwin'
@@ -310,3 +298,21 @@ module.exports = (grunt) ->
unless process.platform is 'linux' or grunt.option('no-install')
defaultTasks.push 'install'
grunt.registerTask('default', defaultTasks)
grunt.registerTask('build-and-sign', ['download-electron', 'download-electron-chromedriver', 'build', 'set-version', 'generate-asar', 'codesign:app', 'install'])
getDefaultChannelAndReleaseBranch = (version) ->
if version.match(/dev/) or isBuildingPR()
channel = 'dev'
releaseBranch = null
else
if version.match(/beta/)
channel = 'beta'
else
channel = 'stable'
minorVersion = version.match(/^\d\.\d/)[0]
releaseBranch = "#{minorVersion}-releases"
[channel, releaseBranch]
isBuildingPR = ->
process.env.APPVEYOR_PULL_REQUEST_NUMBER? or process.env.TRAVIS_PULL_REQUEST?

Binary file not shown.

View File

@@ -25,7 +25,7 @@
"grunt-contrib-less": "~0.8.0",
"grunt-cson": "0.16.0",
"grunt-download-electron": "^2.1.1",
"grunt-electron-installer": "1.0.6",
"grunt-electron-installer": "1.2.2",
"grunt-lesslint": "0.17.0",
"grunt-peg": "~1.1.0",
"grunt-shell": "~0.3.1",

View File

@@ -54,9 +54,9 @@ module.exports = (grunt) ->
# so that it doesn't becomes larger than it needs to be.
ignoredPaths = [
path.join('git-utils', 'deps')
path.join('nodegit', 'vendor')
path.join('nodegit', 'node_modules', 'node-pre-gyp')
path.join('nodegit', 'node_modules', '.bin')
path.join('ohnogit', 'node_modules', 'nodegit', 'vendor')
path.join('ohnogit', 'node_modules', 'nodegit', 'node_modules', 'node-pre-gyp')
path.join('ohnogit', 'node_modules', 'nodegit', 'node_modules', '.bin')
path.join('oniguruma', 'deps')
path.join('less', 'dist')
path.join('bootstrap', 'docs')
@@ -122,9 +122,9 @@ module.exports = (grunt) ->
# Ignore *.cc and *.h files from native modules
ignoredPaths.push "#{_.escapeRegExp(path.join('ctags', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('git-utils', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'include') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'include') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('keytar', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('nslog', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('oniguruma', 'src') + path.sep)}.*\\.(cc|h)*"
@@ -133,7 +133,6 @@ module.exports = (grunt) ->
ignoredPaths.push "#{_.escapeRegExp(path.join('scrollbar-style', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('spellchecker', 'src') + path.sep)}.*\\.(cc|h)*"
ignoredPaths.push "#{_.escapeRegExp(path.join('cached-run-in-this-context', 'src') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('marker-index', 'src') + path.sep)}.*\\.(cc|h)?"
ignoredPaths.push "#{_.escapeRegExp(path.join('keyboard-layout', 'src') + path.sep)}.*\\.(cc|h|mm)*"
# Ignore build files

View File

@@ -1,39 +1,71 @@
path = require 'path'
fs = require 'fs'
request = require 'request'
module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
grunt.registerTask 'codesign:exe', 'Codesign atom.exe and Update.exe', ->
signUsingWindowsSDK = (exeToSign, callback) ->
{WIN_P12KEY_PASSWORD, WIN_P12KEY_URL} = process.env
if WIN_P12KEY_URL?
grunt.log.ok("Obtaining signing key")
downloadedKeyFile = path.resolve(__dirname, 'DownloadedSignKey.p12')
downloadFile WIN_P12KEY_URL, downloadedKeyFile, (done) ->
signUsingWindowsSDKTool exeToSign, downloadedKeyFile, WIN_P12KEY_PASSWORD, (done) ->
fs.unlinkSync(downloadedKeyFile)
callback()
else
signUsingWindowsSDKTool exeToSign, path.resolve(__dirname, '..', 'certs', 'AtomDevTestSignKey.p12'), 'password', callback
signUsingWindowsSDKTool = (exeToSign, keyFilePath, password, callback) ->
grunt.log.ok("Signing #{exeToSign}")
args = ['sign', '/v', '/p', password, '/f', keyFilePath, exeToSign]
spawn {cmd: 'C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.1A\\bin\\signtool.exe', args: args}, callback
signUsingJanky = (exeToSign, callback) ->
spawn {cmd: process.env.JANKY_SIGNTOOL, args: [exeToSign]}, callback
signWindowsExecutable = if process.env.JANKY_SIGNTOOL then signUsingJanky else signUsingWindowsSDK
grunt.registerTask 'codesign:exe', 'CodeSign Atom.exe and Update.exe', ->
done = @async()
spawn {cmd: 'taskkill', args: ['/F', '/IM', 'atom.exe']}, ->
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
atomExePath = path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')
spawn {cmd, args: [atomExePath]}, (error) ->
signWindowsExecutable atomExePath, (error) ->
return done(error) if error?
updateExePath = path.resolve(__dirname, '..', 'node_modules', 'grunt-electron-installer', 'vendor', 'Update.exe')
spawn {cmd, args: [updateExePath]}, (error) -> done(error)
signWindowsExecutable updateExePath, (error) -> done(error)
grunt.registerTask 'codesign:installer', 'Codesign AtomSetup.exe', ->
grunt.registerTask 'codesign:installer', 'CodeSign AtomSetup.exe', ->
done = @async()
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
atomSetupExePath = path.resolve(grunt.config.get('atom.buildDir'), 'installer', 'AtomSetup.exe')
spawn {cmd, args: [atomSetupExePath]}, (error) -> done(error)
signWindowsExecutable atomSetupExePath, (error) -> done(error)
grunt.registerTask 'codesign:app', 'Codesign Atom.app', ->
grunt.registerTask 'codesign:app', 'CodeSign Atom.app', ->
done = @async()
unlockKeychain (error) ->
return done(error) if error?
cmd = 'codesign'
args = ['--deep', '--force', '--verbose', '--sign', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
spawn {cmd, args}, (error) -> done(error)
spawn {cmd: 'codesign', args: args}, (error) -> done(error)
unlockKeychain = (callback) ->
return callback() unless process.env.XCODE_KEYCHAIN
cmd = 'security'
{XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env
args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN]
spawn {cmd, args}, (error) -> callback(error)
spawn {cmd: 'security', args: args}, (error) -> callback(error)
downloadFile = (sourceUrl, targetPath, callback) ->
options = {
url: sourceUrl
headers: {
'User-Agent': 'Atom Signing Key build task',
'Accept': 'application/vnd.github.VERSION.raw'
}
}
request(options)
.pipe(fs.createWriteStream(targetPath))
.on('finish', callback)

View File

@@ -16,10 +16,22 @@ module.exports = (grunt) ->
{description} = grunt.config.get('atom.metadata')
if process.platform is 'win32'
runas ?= require 'runas'
copyFolder = path.resolve 'script', 'copy-folder.cmd'
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: true) isnt 0
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
done = @async()
fs.access(installDir, fs.W_OK, (err) ->
adminRequired = true if err
if adminRequired
grunt.log.ok("User does not have write access to #{installDir}, elevating to admin")
runas ?= require 'runas'
copyFolder = path.resolve 'script', 'copy-folder.cmd'
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: adminRequired) isnt 0
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
else
grunt.log.ok("Installed into #{installDir}")
done()
)
else if process.platform is 'darwin'
rm installDir
mkdir path.dirname(installDir)

View File

@@ -123,10 +123,10 @@ module.exports =
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
'tweetnacl@0.13.2':
'tweetnacl@0.14.3':
repository: 'https://github.com/dchest/tweetnacl-js'
license: 'Public Domain'
source: 'https://github.com/dchest/tweetnacl-js/blob/2f328394f74d83564634fb89ea2798caa3a4edb9/README.md says public domain.'
source: 'https://github.com/dchest/tweetnacl-js/blob/1042c9c65dc8f1dcb9e981d962d7dbbcf58f1fdc/COPYING.txt says public domain.'
'json-schema@0.2.2':
repository: 'https://github.com/kriszyp/json-schema'
license: 'BSD'

View File

@@ -0,0 +1,30 @@
path = require 'path'
module.exports = (grunt) ->
{spawn, fillTemplate} = require('./task-helpers')(grunt)
grunt.registerTask 'mktar', 'Create an archive', ->
done = @async()
appFileName = grunt.config.get('atom.appFileName')
buildDir = grunt.config.get('atom.buildDir')
shellAppDir = grunt.config.get('atom.shellAppDir')
{version, description} = grunt.config.get('atom.metadata')
if process.arch is 'ia32'
arch = 'i386'
else if process.arch is 'x64'
arch = 'amd64'
else
return done("Unsupported arch #{process.arch}")
iconPath = path.join(shellAppDir, 'resources', 'app.asar.unpacked', 'resources', 'atom.png')
cmd = path.join('script', 'mktar')
args = [appFileName, version, arch, iconPath, buildDir]
spawn {cmd, args}, (error) ->
if error?
done(error)
else
grunt.log.ok "Created " + path.join(buildDir, "#{appFileName}-#{version}-#{arch}.tar.gz")
done()

View File

@@ -31,14 +31,9 @@ module.exports = (gruntObject) ->
cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
channel = grunt.config.get('atom.channel')
switch channel
when 'stable'
isPrerelease = false
when 'beta'
isPrerelease = true
else
return
releaseBranch = grunt.config.get('atom.releaseBranch')
isPrerelease = grunt.config.get('atom.channel') is 'beta'
return unless releaseBranch?
doneCallback = @async()
startTime = Date.now()
@@ -55,7 +50,7 @@ module.exports = (gruntObject) ->
zipAssets buildDir, assets, (error) ->
return done(error) if error?
getAtomDraftRelease isPrerelease, channel, (error, release) ->
getAtomDraftRelease isPrerelease, releaseBranch, (error, release) ->
return done(error) if error?
assetNames = (asset.assetName for asset in assets)
deleteExistingAssets release, assetNames, (error) ->
@@ -79,7 +74,7 @@ getAssets = ->
]
when 'win32'
assets = [{assetName: 'atom-windows.zip', sourcePath: appName}]
for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{version}-full.nupkg", "atom-#{version}-delta.nupkg"]
for squirrelAsset in ['AtomSetup.exe', 'AtomSetup.msi', 'RELEASES', "atom-#{version}-full.nupkg", "atom-#{version}-delta.nupkg"]
cp path.join(buildDir, 'installer', squirrelAsset), path.join(buildDir, squirrelAsset)
assets.push({assetName: squirrelAsset, sourcePath: assetName})
assets
@@ -90,13 +85,13 @@ getAssets = ->
arch = 'amd64'
# Check for a Debian build
sourcePath = "#{buildDir}/#{appFileName}-#{version}-#{arch}.deb"
sourcePath = path.join(buildDir, "#{appFileName}-#{version}-#{arch}.deb")
assetName = "atom-#{arch}.deb"
# Check for a Fedora build
unless fs.isFileSync(sourcePath)
rpmName = fs.readdirSync("#{buildDir}/rpm")[0]
sourcePath = "#{buildDir}/rpm/#{rpmName}"
sourcePath = path.join(buildDir, "rpm", rpmName)
if process.arch is 'ia32'
arch = 'i386'
else
@@ -104,10 +99,17 @@ getAssets = ->
assetName = "atom.#{arch}.rpm"
cp sourcePath, path.join(buildDir, assetName)
assets = [{assetName, sourcePath}]
[
{assetName, sourcePath}
]
# Check for an archive build on a debian build machine.
# We could provide a Fedora version if some libraries are not compatible
sourcePath = path.join(buildDir, "#{appFileName}-#{version}-#{arch}.tar.gz")
if fs.isFileSync(sourcePath)
assetName = "atom-#{arch}.tar.gz"
cp sourcePath, path.join(buildDir, assetName)
assets.push({assetName, sourcePath})
assets
logError = (message, error, details) ->
grunt.log.error(message)

View File

@@ -5,9 +5,7 @@ module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
getVersion = (callback) ->
releasableBranches = ['stable', 'beta']
channel = grunt.config.get('atom.channel')
shouldUseCommitHash = if channel in releasableBranches then false else true
shouldUseCommitHash = grunt.config.get('atom.channel') is 'dev'
inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git'))
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
if shouldUseCommitHash and inRepository

View File

@@ -1,5 +1,6 @@
fs = require 'fs'
path = require 'path'
temp = require('temp').track()
_ = require 'underscore-plus'
async = require 'async'
@@ -16,20 +17,6 @@ module.exports = (grunt) ->
packageSpecQueue = null
logDeprecations = (label, {stderr}={}) ->
return unless process.env.JANKY_SHA1 or process.env.CI
stderr ?= ''
deprecatedStart = stderr.indexOf('Calls to deprecated functions')
return if deprecatedStart is -1
grunt.log.error(label)
stderr = stderr.substring(deprecatedStart)
stderr = stderr.replace(/^\s*\[[^\]]+\]\s+/gm, '')
stderr = stderr.replace(/source: .*$/gm, '')
stderr = stderr.replace(/^"/gm, '')
stderr = stderr.replace(/",\s*$/gm, '')
grunt.log.error(stderr)
getAppPath = ->
contentsDir = grunt.config.get('atom.contentsDir')
switch process.platform
@@ -56,14 +43,14 @@ module.exports = (grunt) ->
args: ['--test', "--resource-path=#{resourcePath}", path.join(packagePath, 'spec')]
opts:
cwd: packagePath
env: _.extend({}, process.env, ATOM_PATH: rootDir)
env: _.extend({}, process.env, ELECTRON_ENABLE_LOGGING: true, ATOM_PATH: rootDir)
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--log-file=ci.log", path.join(packagePath, 'spec')]
opts:
cwd: packagePath
env: _.extend({}, process.env, ATOM_PATH: rootDir)
env: _.extend({}, process.env, ELECTRON_ENABLE_LOGGING: true, ATOM_PATH: rootDir)
grunt.log.ok "Launching #{path.basename(packagePath)} specs."
spawn options, (error, results, code) ->
@@ -73,7 +60,6 @@ module.exports = (grunt) ->
fs.unlinkSync(path.join(packagePath, 'ci.log'))
failedPackages.push path.basename(packagePath) if error
logDeprecations("#{path.basename(packagePath)} Specs", results)
callback()
modulesDirectory = path.resolve('node_modules')
@@ -94,20 +80,18 @@ module.exports = (grunt) ->
if process.platform in ['darwin', 'linux']
options =
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath]
args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath, "--user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
)
env: _.extend({}, process.env, {ELECTRON_ENABLE_LOGGING: true, ATOM_INTEGRATION_TESTS_ENABLED: true})
stdio: 'inherit'
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", '--log-file=ci.log', coreSpecsPath]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
)
env: _.extend({}, process.env, {ELECTRON_ENABLE_LOGGING: true, ATOM_INTEGRATION_TESTS_ENABLED: true})
stdio: 'inherit'
grunt.log.ok "Launching core specs."
spawn options, (error, results, code) ->
@@ -117,7 +101,6 @@ module.exports = (grunt) ->
else
# TODO: Restore concurrency on Windows
packageSpecQueue?.concurrency = concurrency
logDeprecations('Core Specs', results)
callback(null, error)

View File

@@ -52,8 +52,10 @@ module.exports = (grunt) ->
stderr = []
error = null
proc = childProcess.spawn(options.cmd, options.args, options.opts)
proc.stdout.on 'data', (data) -> stdout.push(data.toString())
proc.stderr.on 'data', (data) -> stderr.push(data.toString())
if proc.stdout?
proc.stdout.on 'data', (data) -> stdout.push(data.toString())
if proc.stderr?
proc.stderr.on 'data', (data) -> stderr.push(data.toString())
proc.on 'error', (processError) -> error ?= processError
proc.on 'close', (exitCode, signal) ->
error ?= new Error(signal) if exitCode isnt 0

4
circle.yml Normal file
View File

@@ -0,0 +1,4 @@
general:
branches:
only:
- io-circle-ci

View File

@@ -6,8 +6,8 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++ toolchain
* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* [Git](https://git-scm.com/)
* [Node.js](https://nodejs.org/en/download/) (0.10.x or above)
* [npm](https://www.npmjs.com/) v1.4.x or above (automatically bundled with Node.js)
* `npm -v` to check the version.
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
@@ -64,7 +64,7 @@ If you have problems with permissions don't forget to prefix with `sudo`
script/build
```
This will create the atom application at `$TMPDIR/atom-build/Atom`.
This will create the atom application at `out/Atom`.
4. Install the `atom` and `apm` commands to `/usr/local/bin` by executing:
@@ -74,18 +74,24 @@ If you have problems with permissions don't forget to prefix with `sudo`
To use the newly installed Atom, quit and restart all running Atom instances.
5. *Optionally*, you may generate distributable packages of Atom at `$TMPDIR/atom-build`. Currently, `.deb` and `.rpm` package types are supported. To create a `.deb` package run:
5. *Optionally*, you may generate distributable packages of Atom at `out`. Currently, `.deb` and `.rpm` package types are supported, as well as a `.tar.gz` archive. To create a `.deb` package run:
```sh
script/grunt mkdeb
```
To create an `.rpm` package run
To create a `.rpm` package run
```sh
script/grunt mkrpm
```
To create a `.tar.gz` archive run
```sh
script/grunt mktar
```
## Advanced Options
### Custom build directory

View File

@@ -3,7 +3,7 @@
## Requirements
* OS X 10.8 or later
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* [Node.js](https://nodejs.org/en/download/) (0.10.x or above)
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
## Instructions

View File

@@ -3,7 +3,7 @@
## Requirements
### General
* [Node.js](http://nodejs.org/en/download/) v4.x
* [Node.js](https://nodejs.org/en/download/) v4.x
* [Python](https://www.python.org/downloads/) v2.7.x
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`.
If it is installed elsewhere, you can create a symbolic link to the
@@ -14,29 +14,27 @@
You can use either:
* [Visual Studio 2013 Update 5](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express or better) on Windows 7, 8 or 10
* [Visual Studio 2015](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community or better) with Windows 8 or 10
* [Visual Studio 2013 Update 5](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express or better) on Windows 7, 8 or 10
* [Visual Studio 2015](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community or better) with Windows 8 or 10
Whichever version you use, ensure that:
* The default installation folder is chosen so the build tools can find it
* Visual C++ support is installed
* You set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (`2013` or `2015`), e.g. , e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell or set it in Windows advanced system settings control panel.
* The git command is in your path
* A `git` command is in your path
* If you have both VS2013 and VS2015 installed set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (`2013` or `2015`) you wish to use, e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell or set it in Windows advanced system settings control panel.
## Instructions
You can run these commands using Command Prompt, PowerShell or Git Shell via [GitHub Desktop](https://desktop.github.com/). These instructions will assume the use of Bash from Git Shell - if you are using Command Prompt use a backslash instead: i.e. `script\build`.
**VS2015 + Git Shell users** should note that the default path supplied with Git Shell includes reference to an older version of msbuild that will fail. It is recommended you use a PowerShell window that has git in the path at this time.
```bash
cd C:\
git clone https://github.com/atom/atom/
cd atom
script/build
```
This will create the Atom application in the `Program Files` folder.
This will create the Atom application in the `out\Atom` folder as well as copy it to a subfolder of your user profile (e.g. `c:\Users\Bob`) called `AppData\Local\atom\app-dev`.
### `script/build` Options
* `--install-dir` - Creates the final built application in this directory. Example (trailing slash is optional):
@@ -47,6 +45,7 @@ This will create the Atom application in the `Program Files` folder.
```bash
./script/build --build-dir Z:\Some\Temporary\Directory\
```
* `--no-install` - Skips the installation task after building.
* `--verbose` - Verbose mode. A lot more information output.
## Do I have to use GitHub Desktop?
@@ -63,37 +62,34 @@ If none of this works, do install Github Desktop and use its Git Shell as it mak
### Common Errors
* `node is not recognized`
* If you just installed Node.js, you'll need to restart your PowerShell/Command Prompt/Git Shell before the node
command is available on your Path.
* `script/build` outputs only the Node.js and Python versions before returning
* `msbuild.exe failed with exit code: 1`
* Ensure you have Visual C++ support installed. Go into Add/Remove Programs, select Visual Studio and press Modify and then check the Visual C++ box.
* `script/build` stops with no error or warning shortly after displaying the versions of node, npm and Python
* Make sure that the path where you have checked out Atom does not include a space. e.g. use `c:\atom` and not `c:\my stuff\atom`
* `script/build` outputs only the Node.js and Python versions before returning
* Try moving the repository to `C:\atom`. Most likely, the path is too long.
See [issue #2200](https://github.com/atom/atom/issues/2200).
* `error MSB4025: The project file could not be loaded. Invalid character in the given encoding.`
* This can occur because your home directory (`%USERPROFILE%`) has non-ASCII
characters in it. This is a bug in [gyp](https://code.google.com/p/gyp/)
which is used to build native Node.js modules and there is no known workaround.
* https://github.com/TooTallNate/node-gyp/issues/297
* https://code.google.com/p/gyp/issues/detail?id=393
* `script/build` stops at installing runas with `Failed at the runas@x.y.z install script.`
* `'node_modules\.bin\npm' is not recognized as an internal or external command, operable program or batch file.`
* This occurs if the previous build left things in a bad state. Run `script\clean` and then `script\build` again.
* `script/build` stops at installing runas with `Failed at the runas@x.y.z install script.`
* See the next item.
* `error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.`
* If you're building Atom with Visual Studio 2013 or above make sure the `GYP_MSVS_VERSION` environment variable is set, and then re-run `script/build` after a clean:
```bash
$env:GYP_MSVS_VERSION='2013' # '2015' if using Visual Studio 2015, and so on
script/clean
script/build
```
* If you are using Visual Studio 2013 or above and the build fails with some other error message this environment variable might still be required and ensure you have Visual C++ language support installed.
* If you're building Atom with Visual Studio 2013 try setting the `GYP_MSVS_VERSION` environment variable to 2013 and then `script/clean` followed by `script/build` (re-open your command prompt or Powershell window if you set it using the GUI)
* Other `node-gyp` errors on first build attempt, even though the right Node.js and Python versions are installed.
* Do try the build command one more time, as experience shows it often works on second try in many of these cases.

18
docs/native-profiling.md Normal file
View File

@@ -0,0 +1,18 @@
# Profiling the Atom Render Process on OS X with Instruments
![Instruments](https://cloud.githubusercontent.com/assets/1789/14193295/d503db7a-f760-11e5-88bf-fe417c0cd913.png)
* Determine the version of Electron for your version of Atom.
* Open the dev tools with `alt-cmd-i`
* Evaluate `process.versions.electron` in the console.
* Based on this version, download the appropriate Electron symbols from the [releases](https://github.com/atom/electron/releases) page.
* The file name should look like `electron-v0.X.Y-darwin-x64-dsym.zip`.
* Decompress these symbols in your `~/Downloads` directory.
* Now create a time profile in Instruments.
* Open `Instruments.app`.
* Select `Time Profiler`
* In Atom, determine the pid to attach to by evaluating `process.pid` in the dev tools console.
* Attach to this pid via the menu at the upper left corner of the Instruments profiler.
* Click record, do your thing.
* Click stop.
* The symbols should have been automatically located by Instruments (via Spotlight or something?), giving you a readable profile.

View File

@@ -18,15 +18,15 @@
# 'ctrl-p': 'core:move-down'
#
# You can find more information about keymaps in these guides:
# * https://atom.io/docs/latest/using-atom-basic-customization#customizing-key-bindings
# * https://atom.io/docs/latest/behind-atom-keymaps-in-depth
# * http://flight-manual.atom.io/using-atom/sections/basic-customization/#_customizing_keybindings
# * http://flight-manual.atom.io/behind-atom/sections/keymaps-in-depth/
#
# If you're having trouble with your keybindings not working, try the
# Keybinding Resolver: `Cmd+.` on OS X and `Ctrl+.` on other platforms. See the
# Debugging Guide for more information:
# * https://atom.io/docs/latest/hacking-atom-debugging#check-the-keybindings
# * http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-the-keybindings
#
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it in the
# Atom Flight Manual:
# https://atom.io/docs/latest/using-atom-basic-customization#cson
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson

View File

@@ -18,4 +18,4 @@
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it in the
# Atom Flight Manual:
# https://atom.io/docs/latest/using-atom-basic-customization#cson
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson

View File

@@ -73,8 +73,10 @@
'cmd-alt-right': 'pane:show-next-item'
'ctrl-pageup': 'pane:show-previous-item'
'ctrl-pagedown': 'pane:show-next-item'
'ctrl-tab': 'pane:show-next-item'
'ctrl-shift-tab': 'pane:show-previous-item'
'ctrl-tab': 'pane:show-next-recently-used-item'
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'cmd-=': 'window:increase-font-size'
'cmd-+': 'window:increase-font-size'
'cmd--': 'window:decrease-font-size'
@@ -128,6 +130,8 @@
# Atom Specific
'ctrl-W': 'editor:select-word'
'cmd-ctrl-left': 'editor:move-selection-left'
'cmd-ctrl-right': 'editor:move-selection-right'
# Sublime Parity
'cmd-a': 'core:select-all'

View File

@@ -14,6 +14,8 @@
'ctrl-shift-pageup': 'pane:move-item-left'
'ctrl-shift-pagedown': 'pane:move-item-right'
'f11': 'window:toggle-full-screen'
'alt-shift-left': 'editor:move-selection-left'
'alt-shift-right': 'editor:move-selection-right'
# Sublime Parity
'ctrl-,': 'application:show-settings'
@@ -25,6 +27,7 @@
'ctrl-n': 'application:new-file'
'ctrl-s': 'core:save'
'ctrl-S': 'core:save-as'
'ctrl-f4': 'core:close'
'ctrl-w': 'core:close'
'ctrl-z': 'core:undo'
'ctrl-y': 'core:redo'
@@ -46,8 +49,10 @@
'pagedown': 'core:page-down'
'backspace': 'core:backspace'
'shift-backspace': 'core:backspace'
'ctrl-tab': 'pane:show-next-item'
'ctrl-shift-tab': 'pane:show-previous-item'
'ctrl-tab': 'pane:show-next-recently-used-item'
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-pageup': 'pane:show-previous-item'
'ctrl-pagedown': 'pane:show-next-item'
'ctrl-up': 'core:move-up'

View File

@@ -20,6 +20,8 @@
'ctrl-shift-left': 'pane:move-item-left'
'ctrl-shift-right': 'pane:move-item-right'
'f11': 'window:toggle-full-screen'
'alt-shift-left': 'editor:move-selection-left'
'alt-shift-right': 'editor:move-selection-right'
# Sublime Parity
'ctrl-,': 'application:show-settings'
@@ -52,8 +54,10 @@
'pagedown': 'core:page-down'
'backspace': 'core:backspace'
'shift-backspace': 'core:backspace'
'ctrl-tab': 'pane:show-next-item'
'ctrl-shift-tab': 'pane:show-previous-item'
'ctrl-tab': 'pane:show-next-recently-used-item'
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-pageup': 'pane:show-previous-item'
'ctrl-pagedown': 'pane:show-next-item'
'ctrl-shift-up': 'core:move-up'

View File

@@ -75,6 +75,13 @@
{ label: 'Join Lines', command: 'editor:join-lines' }
]
}
{
label: 'Columns',
submenu: [
{ label: 'Move Selection Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection Right', command: 'editor:move-selection-right' }
]
}
{
label: 'Text',
submenu: [
@@ -200,7 +207,6 @@
submenu: [
{ label: 'Terms of Use', command: 'application:open-terms-of-use' }
{ label: 'Documentation', command: 'application:open-documentation' }
{ label: 'Roadmap', command: 'application:open-roadmap' }
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
{ type: 'separator' }
{ label: 'Community Discussions', command: 'application:open-discussions' }
@@ -222,10 +228,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -48,6 +48,13 @@
{ label: '&Join Lines', command: 'editor:join-lines' }
]
}
{
label: 'Columns',
submenu: [
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
]
}
{
label: 'Text',
submenu: [
@@ -174,7 +181,6 @@
{ label: "VERSION", enabled: false }
{ type: 'separator' }
{ label: '&Documentation', command: 'application:open-documentation' }
{ label: 'Roadmap', command: 'application:open-roadmap' }
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
{ type: 'separator' }
{ label: 'Community Discussions', command: 'application:open-discussions' }
@@ -198,10 +204,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -56,6 +56,13 @@
{ label: '&Join Lines', command: 'editor:join-lines' }
]
}
{
label: 'Columns',
submenu: [
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
]
}
{
label: 'Text',
submenu: [
@@ -177,7 +184,6 @@
{ label: 'Downloading Update', enabled: false, visible: false}
{ type: 'separator' }
{ label: '&Documentation', command: 'application:open-documentation' }
{ label: 'Roadmap', command: 'application:open-roadmap' }
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
{ type: 'separator' }
{ label: 'Community Discussions', command: 'application:open-discussions' }
@@ -201,10 +207,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "1.7.0-dev",
"version": "1.9.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@@ -12,16 +12,17 @@
"url": "https://github.com/atom/atom/issues"
},
"license": "MIT",
"electronVersion": "0.34.5",
"electronVersion": "0.36.12",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^6.2.0",
"atom-keymap": "6.3.2",
"babel-core": "^5.8.21",
"bootstrap": "^3.3.4",
"cached-run-in-this-context": "0.4.1",
"clear-cut": "^2.0.1",
"coffee-script": "1.8.0",
"color": "^0.7.3",
"devtron": "1.1.0",
"event-kit": "^1.5.0",
"find-parent-dir": "^0.3.0",
"first-mate": "^5.1.1",
@@ -36,12 +37,12 @@
"key-path-helpers": "^0.4.0",
"less-cache": "0.23",
"line-top-index": "0.2.0",
"marked": "^0.3.4",
"nodegit": "0.9.0",
"marked": "^0.3.5",
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"ohnogit": "0.0.11",
"oniguruma": "^5",
"pathwatcher": "~6.2",
"pathwatcher": "~6.5",
"property-accessors": "^1.1.3",
"random-words": "0.0.1",
"resolve": "^1.1.6",
@@ -54,7 +55,7 @@
"service-hub": "^0.7.0",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "8.2.1",
"text-buffer": "9.1.0",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"yargs": "^3.23.0"
@@ -66,89 +67,89 @@
"atom-light-ui": "0.43.0",
"base16-tomorrow-dark-theme": "1.1.0",
"base16-tomorrow-light-theme": "1.1.1",
"one-dark-ui": "1.1.9",
"one-light-ui": "1.1.9",
"one-dark-ui": "1.3.1",
"one-light-ui": "1.3.1",
"one-dark-syntax": "1.2.0",
"one-light-syntax": "1.2.0",
"solarized-dark-syntax": "1.0.0",
"solarized-light-syntax": "1.0.0",
"about": "1.3.0",
"archive-view": "0.61.0",
"solarized-dark-syntax": "1.0.2",
"solarized-light-syntax": "1.0.2",
"about": "1.5.2",
"archive-view": "0.61.1",
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.11.0",
"autocomplete-css": "0.11.1",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.25.0",
"autocomplete-snippets": "1.10.0",
"autocomplete-plus": "2.31.0",
"autocomplete-snippets": "1.11.0",
"autoflow": "0.27.0",
"autosave": "0.23.0",
"autosave": "0.23.1",
"background-tips": "0.26.0",
"bookmarks": "0.38.2",
"bracket-matcher": "0.79.0",
"bookmarks": "0.41.0",
"bracket-matcher": "0.82.1",
"command-palette": "0.38.0",
"deprecation-cop": "0.54.0",
"deprecation-cop": "0.54.1",
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.37.0",
"find-and-replace": "0.197.1",
"fuzzy-finder": "0.94.0",
"git-diff": "0.57.0",
"encoding-selector": "0.22.0",
"exception-reporting": "0.38.1",
"fuzzy-finder": "1.2.0",
"git-diff": "1.0.1",
"find-and-replace": "0.198.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.48.0",
"image-view": "0.56.0",
"incompatible-packages": "0.25.0",
"keybinding-resolver": "0.33.0",
"line-ending-selector": "0.3.0",
"link": "0.31.0",
"markdown-preview": "0.157.2",
"grammar-selector": "0.48.1",
"image-view": "0.57.0",
"incompatible-packages": "0.26.1",
"keybinding-resolver": "0.35.0",
"line-ending-selector": "0.5.0",
"link": "0.31.1",
"markdown-preview": "0.158.0",
"metrics": "0.53.1",
"notifications": "0.62.1",
"open-on-github": "0.41.0",
"package-generator": "0.41.0",
"settings-view": "0.232.3",
"snippets": "1.0.1",
"spell-check": "0.65.0",
"status-bar": "0.83.0",
"styleguide": "0.45.1",
"symbols-view": "0.111.0",
"tabs": "0.90.0",
"timecop": "0.33.0",
"tree-view": "0.201.0",
"notifications": "0.64.1",
"open-on-github": "1.1.0",
"package-generator": "1.0.0",
"settings-view": "0.237.0",
"snippets": "1.0.2",
"spell-check": "0.67.1",
"status-bar": "1.2.6",
"styleguide": "0.45.2",
"symbols-view": "0.113.0",
"tabs": "0.95.0",
"timecop": "0.33.1",
"tree-view": "0.207.0",
"update-package-dependencies": "0.10.0",
"welcome": "0.33.0",
"whitespace": "0.32.1",
"welcome": "0.34.0",
"whitespace": "0.32.2",
"wrap-guide": "0.38.1",
"language-c": "0.51.1",
"language-clojure": "0.19.1",
"language-coffee-script": "0.46.1",
"language-csharp": "0.11.0",
"language-css": "0.36.0",
"language-gfm": "0.84.0",
"language-git": "0.12.1",
"language-c": "0.52.0",
"language-clojure": "0.20.0",
"language-coffee-script": "0.47.0",
"language-csharp": "0.12.1",
"language-css": "0.36.1",
"language-gfm": "0.86.0",
"language-git": "0.13.0",
"language-go": "0.42.0",
"language-html": "0.44.0",
"language-html": "0.44.1",
"language-hyperlink": "0.16.0",
"language-java": "0.17.0",
"language-java": "0.19.0",
"language-javascript": "0.110.0",
"language-json": "0.17.4",
"language-less": "0.29.0",
"language-make": "0.21.0",
"language-json": "0.18.0",
"language-less": "0.29.3",
"language-make": "0.22.0",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.1",
"language-perl": "0.32.0",
"language-perl": "0.35.0",
"language-php": "0.37.0",
"language-property-list": "0.8.0",
"language-python": "0.43.0",
"language-ruby": "0.68.1",
"language-python": "0.44.0",
"language-ruby": "0.68.5",
"language-ruby-on-rails": "0.25.0",
"language-sass": "0.45.0",
"language-shellscript": "0.21.0",
"language-sass": "0.52.0",
"language-shellscript": "0.22.2",
"language-source": "0.9.0",
"language-sql": "0.20.0",
"language-text": "0.7.0",
"language-sql": "0.21.0",
"language-text": "0.7.1",
"language-todo": "0.27.0",
"language-toml": "0.18.0",
"language-xml": "0.34.3",
"language-yaml": "0.25.1"
"language-xml": "0.34.6",
"language-yaml": "0.26.0"
},
"private": true,
"scripts": {
@@ -173,7 +174,8 @@
"runs",
"spyOn",
"waitsFor",
"waitsForPromise"
"waitsForPromise",
"indexedDB"
]
}
}

View File

@@ -2,6 +2,7 @@
SET EXPECT_OUTPUT=
SET WAIT=
SET PSARGS=%*
FOR %%a IN (%*) DO (
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
@@ -22,31 +23,14 @@ FOR %%a IN (%*) DO (
)
)
rem Getting the process ID in cmd of the current cmd process: http://superuser.com/questions/881789/identify-and-kill-batch-script-started-before
set T=%TEMP%\atomCmdProcessId-%time::=%.tmp
wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") get ParentProcessId /value | find "ParentProcessId" >%T%
set /P A=<%T%
set PID=%A:~16%
del %T%
IF "%EXPECT_OUTPUT%"=="YES" (
SET ELECTRON_ENABLE_LOGGING=YES
IF "%WAIT%"=="YES" (
"%~dp0\..\..\atom.exe" --pid=%PID% %*
rem If the wait flag is set, don't exit this process until Atom tells it to.
goto waitLoop
)
ELSE (
powershell -noexit "Start-Process -FilePath \"%~dp0\..\..\atom.exe\" -ArgumentList \"--pid=$pid $env:PSARGS\" ; wait-event"
exit 0
) ELSE (
"%~dp0\..\..\atom.exe" %*
)
) ELSE (
"%~dp0\..\app\apm\bin\node.exe" "%~dp0\atom.js" %*
)
goto end
:waitLoop
sleep 1
goto waitLoop
:end

View File

@@ -1,49 +1,5 @@
#!/bin/sh
while getopts ":fhtvw-:" opt; do
case "$opt" in
-)
case "${OPTARG}" in
wait)
WAIT=1
;;
help|version)
REDIRECT_STDERR=1
EXPECT_OUTPUT=1
;;
foreground|test)
EXPECT_OUTPUT=1
;;
esac
;;
w)
WAIT=1
;;
h|v)
REDIRECT_STDERR=1
EXPECT_OUTPUT=1
;;
f|t)
EXPECT_OUTPUT=1
;;
esac
done
directory=$(dirname "$0")
WINPS=`ps | grep -i $$`
PID=`echo $WINPS | cut -d' ' -f 4`
if [ $EXPECT_OUTPUT ]; then
export ELECTRON_ENABLE_LOGGING=1
"$directory/../../atom.exe" --executed-from="$(pwd)" --pid=$PID "$@"
else
"$directory/../app/apm/bin/node.exe" "$directory/atom.js" "$@"
fi
# If the wait flag is set, don't exit this process until Atom tells it to.
if [ $WAIT ]; then
while true; do
sleep 1
done
fi
pushd "$(dirname "$0")" > /dev/null
ATOMCMD=""$(pwd -W)"/atom.cmd"
popd > /dev/null
cmd.exe //c "$ATOMCMD" "$@"

View File

@@ -2,9 +2,24 @@
var cp = require('./utils/child-process-wrapper.js');
var runGrunt = require('./utils/run-grunt.js');
var path = require('path');
var fs = require('fs');
process.chdir(path.dirname(__dirname));
if (process.platform === 'win32') {
process.env['PATH'] = process.env['PATH']
.split(';')
.filter(function(p) {
if (fs.existsSync(path.resolve(p, 'msbuild.exe'))) {
console.log('Excluding "' + p + '" from PATH to avoid msbuild.exe mismatch')
return false;
} else {
return true;
}
})
.join(';');
}
cp.safeExec('node script/bootstrap', function() {
// build/node_modules/.bin/grunt "$@"
var args = process.argv.slice(2);

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env node
var cp = require('./utils/child-process-wrapper.js');
var childProcess = require('./utils/child-process-wrapper.js');
var fs = require('fs');
var path = require('path');
var os = require('os');
var removeCommand = process.platform === 'win32' ? 'rmdir /S /Q ' : 'rm -rf ';
var isWindows = process.platform === 'win32';
var productName = require('../package.json').productName;
process.chdir(path.dirname(__dirname));
var home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
var home = process.env[isWindows ? 'USERPROFILE' : 'HOME'];
var tmpdir = os.tmpdir();
// Windows: Use START as a way to ignore error if Atom.exe isnt running
var killatom = process.platform === 'win32' ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true';
var killAtomCommand = isWindows ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true';
//childProcess.safeExec(killAtomCommand);
var commands = [
killatom,
var pathsToRemove = [
[__dirname, '..', 'node_modules'],
[__dirname, '..', 'build', 'node_modules'],
[__dirname, '..', 'apm', 'node_modules'],
@@ -31,20 +31,33 @@ var commands = [
[home, '.atom', 'electron'],
[tmpdir, 'atom-build'],
[tmpdir, 'atom-cached-atom-shells'],
];
var run = function() {
var next = commands.shift();
if (!next)
process.exit(0);
].map(function(pathSegments) {
return path.resolve.apply(null, pathSegments);
});
if (Array.isArray(next)) {
var pathToRemove = path.resolve.apply(path.resolve, next);
if (fs.existsSync(pathToRemove))
next = removeCommand + pathToRemove;
else
return run();
pathsToRemove.forEach(function(pathToRemove) {
if (fs.existsSync(pathToRemove)) {
removePath(pathToRemove);
}
});
cp.safeExec(next, run);
function removePath(pathToRemove) {
if (isWindows) {
removePathOnWindows(pathToRemove);
} else {
childProcess.safeExec('rm -rf ' + pathToRemove);
}
}
// Windows has a 260-char path limit for rmdir etc. Just recursively delete in Node.
function removePathOnWindows(folderPath) {
fs.readdirSync(folderPath).forEach(function(entry, index) {
var entryPath = path.join(folderPath, entry);
if (fs.lstatSync(entryPath).isDirectory()) {
removePathOnWindows(entryPath);
} else {
fs.unlinkSync(entryPath);
}
});
fs.rmdirSync(folderPath);
};
run();

39
script/mktar Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# mktar name version arch icon-path build-root-path
set -e
SCRIPT=`readlink -f "$0"`
ROOT=`readlink -f $(dirname $SCRIPT)/..`
cd $ROOT
NAME="$1"
VERSION="$2"
ARCH="$3"
ICON_FILE="$4"
BUILD_ROOT_PATH="$5"
FILE_MODE=755
TAR_PATH=$BUILD_ROOT_PATH
ATOM_PATH="$BUILD_ROOT_PATH/Atom"
TARGET_ROOT="`mktemp -d`"
chmod $FILE_MODE "$TARGET_ROOT"
NAME_IN_TAR="$NAME-$VERSION-$ARCH"
TARGET="$TARGET_ROOT/$NAME_IN_TAR"
# Copy executable and resources
cp -a "$ATOM_PATH" "$TARGET"
# Copy icon file
cp "$ICON_FILE" "$TARGET/$NAME.png"
# Remove executable bit from .node files
find "$TARGET" -type f -name "*.node" -exec chmod a-x {} \;
# Create the archive
pushd "$TARGET_ROOT"
tar caf "$TAR_PATH/$NAME_IN_TAR.tar.gz" "$NAME_IN_TAR"
popd
rm -rf "$TARGET_ROOT"

View File

@@ -17,7 +17,7 @@ exports.safeExec = function(command, options, callback) {
var child = childProcess.exec(command, options, function(error, stdout, stderr) {
if (error)
process.exit(error.code || 1);
else
else if (callback)
callback(null);
});
child.stderr.pipe(process.stderr);

View File

@@ -19,7 +19,9 @@ exports.afterEach = (fn) ->
waitsForPromise = (fn) ->
promise = fn()
waitsFor 'spec promise to resolve', 30000, (done) ->
# This timeout is 3 minutes. We need to bump it back down once we fix backgrounding
# of the renderer process on CI. See https://github.com/atom/electron/issues/4317
waitsFor 'spec promise to resolve', 3 * 60 * 1000, (done) ->
promise.then(
done,
(error) ->

View File

@@ -4,6 +4,7 @@ temp = require 'temp'
Package = require '../src/package'
ThemeManager = require '../src/theme-manager'
AtomEnvironment = require '../src/atom-environment'
StorageFolder = require '../src/storage-folder'
describe "AtomEnvironment", ->
describe 'window sizing methods', ->
@@ -27,8 +28,10 @@ describe "AtomEnvironment", ->
atom.setSize(originalSize.width, originalSize.height)
it 'sets the size of the window, and can retrieve the size just set', ->
atom.setSize(100, 400)
expect(atom.getSize()).toEqual width: 100, height: 400
newWidth = originalSize.width + 12
newHeight = originalSize.height + 23
atom.setSize(newWidth, newHeight)
expect(atom.getSize()).toEqual width: newWidth, height: newHeight
describe ".isReleasedVersion()", ->
it "returns false if the version is a SHA and true otherwise", ->
@@ -152,6 +155,8 @@ describe "AtomEnvironment", ->
atom.enablePersistence = false
it "selects the state based on the current project paths", ->
jasmine.useRealClock()
[dir1, dir2] = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
loadSettings = _.extend atom.getLoadSettings(),
@@ -159,20 +164,100 @@ describe "AtomEnvironment", ->
windowState: null
spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings
spyOn(atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
spyOn(atom, 'serialize').andReturn({stuff: 'cool'})
atom.state.stuff = "cool"
atom.project.setPaths([dir1, dir2])
atom.saveStateSync()
# State persistence will fail if other Atom instances are running
waitsForPromise ->
atom.stateStore.connect().then (isConnected) ->
expect(isConnected).toBe true
atom.state = {}
atom.loadStateSync()
expect(atom.state.stuff).toBeUndefined()
waitsForPromise ->
atom.saveState().then ->
atom.loadState().then (state) ->
expect(state).toBeFalsy()
loadSettings.initialPaths = [dir2, dir1]
atom.state = {}
atom.loadStateSync()
expect(atom.state.stuff).toBe("cool")
waitsForPromise ->
loadSettings.initialPaths = [dir2, dir1]
atom.loadState().then (state) ->
expect(state).toEqual({stuff: 'cool'})
it "loads state from the storage folder when it can't be found in atom.stateStore", ->
jasmine.useRealClock()
storageFolderState = {foo: 1, bar: 2}
serializedState = {someState: 42}
loadSettings = _.extend(atom.getLoadSettings(), {initialPaths: [temp.mkdirSync("project-directory")]})
spyOn(atom, 'getLoadSettings').andReturn(loadSettings)
spyOn(atom, 'serialize').andReturn(serializedState)
spyOn(atom, 'getStorageFolder').andReturn(new StorageFolder(temp.mkdirSync("config-directory")))
atom.project.setPaths(atom.getLoadSettings().initialPaths)
waitsForPromise ->
atom.stateStore.connect()
runs ->
atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState)
waitsForPromise ->
atom.loadState().then (state) -> expect(state).toEqual(storageFolderState)
waitsForPromise ->
atom.saveState()
waitsForPromise ->
atom.loadState().then (state) -> expect(state).toEqual(serializedState)
it "saves state when the CPU is idle after a keydown or mousedown event", ->
spyOn(atom, 'saveState')
idleCallbacks = []
spyOn(window, 'requestIdleCallback').andCallFake (callback) -> idleCallbacks.push(callback)
keydown = new KeyboardEvent('keydown')
atom.document.dispatchEvent(keydown)
advanceClock atom.saveStateDebounceInterval
idleCallbacks.shift()()
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
atom.saveState.reset()
mousedown = new MouseEvent('mousedown')
atom.document.dispatchEvent(mousedown)
advanceClock atom.saveStateDebounceInterval
idleCallbacks.shift()()
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
it "saves state immediately when unloading the editor window, ignoring pending and successive mousedown/keydown events", ->
spyOn(atom, 'saveState')
idleCallbacks = []
spyOn(window, 'requestIdleCallback').andCallFake (callback) -> idleCallbacks.push(callback)
mousedown = new MouseEvent('mousedown')
atom.document.dispatchEvent(mousedown)
atom.unloadEditorWindow()
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: true})
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: false})
atom.saveState.reset()
advanceClock atom.saveStateDebounceInterval
idleCallbacks.shift()()
expect(atom.saveState).not.toHaveBeenCalled()
atom.saveState.reset()
mousedown = new MouseEvent('mousedown')
atom.document.dispatchEvent(mousedown)
advanceClock atom.saveStateDebounceInterval
idleCallbacks.shift()()
expect(atom.saveState).not.toHaveBeenCalled()
it "serializes the project state with all the options supplied in saveState", ->
spyOn(atom.project, 'serialize').andReturn({foo: 42})
waitsForPromise -> atom.saveState({anyOption: 'any option'})
runs ->
expect(atom.project.serialize.calls.length).toBe(1)
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'})
describe "openInitialEmptyEditorIfNecessary", ->
describe "when there are no paths set", ->
@@ -230,23 +315,6 @@ describe "AtomEnvironment", ->
atomEnvironment.destroy()
it "saves the serialized state of the window so it can be deserialized after reload", ->
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
spyOn(atomEnvironment, 'saveStateSync')
workspaceState = atomEnvironment.workspace.serialize()
grammarsState = {grammarOverridesByPath: atomEnvironment.grammars.grammarOverridesByPath}
projectState = atomEnvironment.project.serialize()
atomEnvironment.unloadEditorWindow()
expect(atomEnvironment.state.workspace).toEqual workspaceState
expect(atomEnvironment.state.grammars).toEqual grammarsState
expect(atomEnvironment.state.project).toEqual projectState
expect(atomEnvironment.saveStateSync).toHaveBeenCalled()
atomEnvironment.destroy()
describe "::destroy()", ->
it "does not throw exceptions when unsubscribing from ipc events (regression)", ->
configDirPath = temp.mkdirSync()
@@ -258,6 +326,7 @@ describe "AtomEnvironment", ->
}
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document: fakeDocument})
spyOn(atomEnvironment.packages, 'getAvailablePackagePaths').andReturn []
spyOn(atomEnvironment, 'displayWindow').andReturn Promise.resolve()
atomEnvironment.startEditorWindow()
atomEnvironment.unloadEditorWindow()
atomEnvironment.destroy()
@@ -273,6 +342,14 @@ describe "AtomEnvironment", ->
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe __dirname
describe "then a second path is opened with forceAddToWindow", ->
it "adds the second path to the project's paths", ->
firstPathToOpen = __dirname
secondPathToOpen = path.resolve(__dirname, './fixtures')
atom.openLocations([{pathToOpen: firstPathToOpen}])
atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])
describe "when the opened path does not exist but its parent directory does", ->
it "adds the parent directory to the project paths", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
@@ -309,7 +386,7 @@ describe "AtomEnvironment", ->
updateAvailableHandler = jasmine.createSpy("update-available-handler")
subscription = atom.onUpdateAvailable updateAvailableHandler
autoUpdater = require('remote').require('auto-updater')
autoUpdater = require('electron').remote.require('auto-updater')
autoUpdater.emit 'update-downloaded', null, "notes", "version"
waitsFor ->
@@ -318,3 +395,18 @@ describe "AtomEnvironment", ->
runs ->
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
expect(releaseVersion).toBe 'version'
describe "::getReleaseChannel()", ->
[version] = []
beforeEach ->
spyOn(atom, 'getVersion').andCallFake -> version
it "returns the correct channel based on the version number", ->
version = '1.5.6'
expect(atom.getReleaseChannel()).toBe 'stable'
version = '1.5.0-beta10'
expect(atom.getReleaseChannel()).toBe 'beta'
version = '1.7.0-dev-5340c91'
expect(atom.getReleaseChannel()).toBe 'dev'

View File

@@ -172,7 +172,7 @@ class AtomReporter
listen document, 'click', '.stack-trace', (event) ->
event.currentTarget.classList.toggle('expanded')
@reloadButton.addEventListener('click', -> require('ipc').send('call-window-method', 'restart'))
@reloadButton.addEventListener('click', -> require('electron').ipcRenderer.send('call-window-method', 'restart'))
updateSpecCounts: ->
if @skippedCount

View File

@@ -0,0 +1,125 @@
'use babel'
import AutoUpdateManager from '../src/auto-update-manager'
import {remote} from 'electron'
const electronAutoUpdater = remote.require('electron').autoUpdater
describe('AutoUpdateManager (renderer)', () => {
let autoUpdateManager
beforeEach(() => {
autoUpdateManager = new AutoUpdateManager({
applicationDelegate: atom.applicationDelegate
})
})
afterEach(() => {
autoUpdateManager.destroy()
})
describe('::onDidBeginCheckingForUpdate', () => {
it('subscribes to "did-begin-checking-for-update" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
electronAutoUpdater.emit('checking-for-update')
waitsFor(() => {
return spy.callCount === 1
})
})
})
describe('::onDidBeginDownloadingUpdate', () => {
it('subscribes to "did-begin-downloading-update" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
electronAutoUpdater.emit('update-available')
waitsFor(() => {
return spy.callCount === 1
})
})
})
describe('::onDidCompleteDownloadingUpdate', () => {
it('subscribes to "did-complete-downloading-update" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
waitsFor(() => {
return spy.callCount === 1
})
runs(() => {
expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3')
})
})
})
describe('::onUpdateNotAvailable', () => {
it('subscribes to "update-not-available" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onUpdateNotAvailable(spy)
electronAutoUpdater.emit('update-not-available')
waitsFor(() => {
return spy.callCount === 1
})
})
})
describe('::onUpdateError', () => {
it('subscribes to "update-error" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onUpdateError(spy)
electronAutoUpdater.emit('error', {}, 'an error message')
waitsFor(() => spy.callCount === 1)
runs(() => expect(autoUpdateManager.getErrorMessage()).toBe('an error message'))
})
})
describe('::platformSupportsUpdates', () => {
let state, releaseChannel
it('returns true on OS X and Windows when in stable', () => {
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel)
state = 'idle'
releaseChannel = 'stable'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(true)
state = 'idle'
releaseChannel = 'dev'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
state = 'unsupported'
releaseChannel = 'stable'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
state = 'unsupported'
releaseChannel = 'dev'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
})
})
describe('::destroy', () => {
it('unsubscribes from all events', () => {
const spy = jasmine.createSpy('spy')
const doneIndicator = jasmine.createSpy('spy')
atom.applicationDelegate.onUpdateNotAvailable(doneIndicator)
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
autoUpdateManager.onUpdateNotAvailable(spy)
autoUpdateManager.destroy()
electronAutoUpdater.emit('checking-for-update')
electronAutoUpdater.emit('update-available')
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
electronAutoUpdater.emit('update-not-available')
waitsFor(() => {
return doneIndicator.callCount === 1
})
runs(() => {
expect(spy.callCount).toBe(0)
})
})
})
})

View File

@@ -15,7 +15,6 @@ describe "Babel transpiler support", ->
CompileCache.setCacheDirectory(temp.mkdirSync('compile-cache'))
for cacheKey in Object.keys(require.cache)
if cacheKey.startsWith(path.join(__dirname, 'fixtures', 'babel'))
console.log('deleting', cacheKey)
delete require.cache[cacheKey]
afterEach ->

View File

@@ -1,5 +1,6 @@
ChildProcess = require 'child_process'
path = require 'path'
fs = require 'fs-plus'
BufferedProcess = require '../src/buffered-process'
describe "BufferedProcess", ->
@@ -15,20 +16,20 @@ describe "BufferedProcess", ->
describe "when there is an error handler specified", ->
describe "when an error event is emitted by the process", ->
it "calls the error handler and does not throw an exception", ->
process = new BufferedProcess
command: 'bad-command-nope'
bufferedProcess = new BufferedProcess
command: 'bad-command-nope1'
args: ['nothing']
options: {}
options: {shell: false}
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
process.onWillThrowError(errorSpy)
bufferedProcess.onWillThrowError(errorSpy)
waitsFor -> errorSpy.callCount > 0
runs ->
expect(window.onerror).not.toHaveBeenCalled()
expect(errorSpy).toHaveBeenCalled()
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope ENOENT'
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope1 ENOENT'
describe "when an error is thrown spawning the process", ->
it "calls the error handler and does not throw an exception", ->
@@ -37,13 +38,13 @@ describe "BufferedProcess", ->
error.code = 'EAGAIN'
throw error
process = new BufferedProcess
bufferedProcess = new BufferedProcess
command: 'ls'
args: []
options: {}
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
process.onWillThrowError(errorSpy)
bufferedProcess.onWillThrowError(errorSpy)
waitsFor -> errorSpy.callCount > 0
@@ -53,55 +54,24 @@ describe "BufferedProcess", ->
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'Something is really wrong'
describe "when there is not an error handler specified", ->
it "calls the error handler and does not throw an exception", ->
process = new BufferedProcess
command: 'bad-command-nope'
it "does throw an exception", ->
new BufferedProcess
command: 'bad-command-nope2'
args: ['nothing']
options: {}
options: {shell: false}
waitsFor -> window.onerror.callCount > 0
runs ->
expect(window.onerror).toHaveBeenCalled()
expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope`'
expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope2`'
expect(window.onerror.mostRecentCall.args[4].name).toBe 'BufferedProcessError'
describe "on Windows", ->
originalPlatform = null
beforeEach ->
# Prevent any commands from actually running and affecting the host
originalSpawn = ChildProcess.spawn
spyOn(ChildProcess, 'spawn').andCallFake ->
# Just spawn something that won't actually modify the host
if originalPlatform is 'win32'
originalSpawn('dir')
else
originalSpawn('ls')
originalPlatform = process.platform
Object.defineProperty process, 'platform', value: 'win32'
afterEach ->
Object.defineProperty process, 'platform', value: originalPlatform
describe "when the explorer command is spawned on Windows", ->
it "doesn't quote arguments of the form /root,C...", ->
new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"explorer.exe /root,C:\\foo"'
it "spawns the command using a cmd.exe wrapper", ->
new BufferedProcess({command: 'dir'})
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/c'
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"dir"'
it "calls the specified stdout, stderr, and exit callbacks ", ->
it "calls the specified stdout, stderr, and exit callbacks", ->
stdout = ''
stderr = ''
exitCallback = jasmine.createSpy('exit callback')
process = new BufferedProcess
new BufferedProcess
command: atom.packages.getApmPath()
args: ['-h']
options: {}
@@ -114,3 +84,52 @@ describe "BufferedProcess", ->
runs ->
expect(stderr).toContain 'apm - Atom Package Manager'
expect(stdout).toEqual ''
it "calls the specified stdout callback with whole lines", ->
exitCallback = jasmine.createSpy('exit callback')
loremPath = require.resolve("./fixtures/lorem.txt")
content = fs.readFileSync(loremPath).toString()
baseContent = content.split('\n')
stdout = ''
allLinesEndWithNewline = true
new BufferedProcess
command: if process.platform is 'win32' then 'type' else 'cat'
args: [loremPath]
options: {}
stdout: (lines) ->
endsWithNewline = (lines.charAt lines.length - 1) is '\n'
if not endsWithNewline then allLinesEndWithNewline = false
stdout += lines
exit: exitCallback
waitsFor -> exitCallback.callCount is 1
runs ->
expect(allLinesEndWithNewline).toBeTrue
expect(stdout).toBe content
describe "on Windows", ->
originalPlatform = null
beforeEach ->
# Prevent any commands from actually running and affecting the host
originalSpawn = ChildProcess.spawn
spyOn(ChildProcess, 'spawn')
originalPlatform = process.platform
Object.defineProperty process, 'platform', value: 'win32'
afterEach ->
Object.defineProperty process, 'platform', value: originalPlatform
describe "when the explorer command is spawned on Windows", ->
it "doesn't quote arguments of the form /root,C...", ->
new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"explorer.exe /root,C:\\foo"'
it "spawns the command using a cmd.exe wrapper when options.shell is undefined", ->
new BufferedProcess({command: 'dir'})
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/d'
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '/c'
expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"dir"'

View File

@@ -74,6 +74,13 @@ describe "CommandRegistry", ->
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['.foo.bar', '.bar', '.foo']
it "orders inline listeners by reverse registration order", ->
calls = []
registry.add child, 'command', -> calls.push('child1')
registry.add child, 'command', -> calls.push('child2')
child.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child2', 'child1']
it "stops bubbling through ancestors when .stopPropagation() is called on the event", ->
calls = []

View File

@@ -1621,6 +1621,16 @@ describe "Config", ->
expect(color.toHexString()).toBe '#ff0000'
expect(color.toRGBAString()).toBe 'rgba(255, 0, 0, 1)'
color.red = 11
color.green = 11
color.blue = 124
color.alpha = 1
atom.config.set('foo.bar.aColor', color)
color = atom.config.get('foo.bar.aColor')
expect(color.toHexString()).toBe '#0b0b7c'
expect(color.toRGBAString()).toBe 'rgba(11, 11, 124, 1)'
it 'coerces various types to a color object', ->
atom.config.set('foo.bar.aColor', 'red')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}

View File

@@ -0,0 +1,85 @@
DecorationManager = require '../src/decoration-manager'
_ = require 'underscore-plus'
describe "DecorationManager", ->
[decorationManager, buffer, defaultMarkerLayer] = []
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
displayLayer = buffer.addDisplayLayer()
defaultMarkerLayer = displayLayer.addMarkerLayer()
decorationManager = new DecorationManager(displayLayer, defaultMarkerLayer)
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
afterEach ->
decorationManager.destroy()
buffer.release()
describe "decorations", ->
[marker, decoration, decorationProperties] = []
beforeEach ->
marker = defaultMarkerLayer.markBufferRange([[2, 13], [3, 15]])
decorationProperties = {type: 'line-number', class: 'one'}
decoration = decorationManager.decorateMarker(marker, decorationProperties)
it "can add decorations associated with markers and remove them", ->
expect(decoration).toBeDefined()
expect(decoration.getProperties()).toBe decorationProperties
expect(decorationManager.decorationForId(decoration.id)).toBe decoration
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
decoration.destroy()
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
it "will not fail if the decoration is removed twice", ->
decoration.destroy()
decoration.destroy()
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
it "does not allow destroyed markers to be decorated", ->
marker.destroy()
expect(->
decorationManager.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
).toThrow("Cannot decorate a destroyed marker")
expect(decorationManager.getOverlayDecorations()).toEqual []
describe "when a decoration is updated via Decoration::update()", ->
it "emits an 'updated' event containing the new and old params", ->
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
decoration.setProperties type: 'line-number', class: 'two'
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
expect(oldProperties).toEqual decorationProperties
expect(newProperties).toEqual {type: 'line-number', gutterName: 'line-number', class: 'two'}
describe "::getDecorations(properties)", ->
it "returns decorations matching the given optional properties", ->
expect(decorationManager.getDecorations()).toEqual [decoration]
expect(decorationManager.getDecorations(class: 'two').length).toEqual 0
expect(decorationManager.getDecorations(class: 'one').length).toEqual 1
describe "::decorateMarker", ->
describe "when decorating gutters", ->
[marker] = []
beforeEach ->
marker = defaultMarkerLayer.markBufferRange([[1, 0], [1, 0]])
it "creates a decoration that is both of 'line-number' and 'gutter' type when called with the 'line-number' type", ->
decorationProperties = {type: 'line-number', class: 'one'}
decoration = decorationManager.decorateMarker(marker, decorationProperties)
expect(decoration.isType('line-number')).toBe true
expect(decoration.isType('gutter')).toBe true
expect(decoration.getProperties().gutterName).toBe 'line-number'
expect(decoration.getProperties().class).toBe 'one'
it "creates a decoration that is only of 'gutter' type if called with the 'gutter' type and a 'gutterName'", ->
decorationProperties = {type: 'gutter', gutterName: 'test-gutter', class: 'one'}
decoration = decorationManager.decorateMarker(marker, decorationProperties)
expect(decoration.isType('gutter')).toBe true
expect(decoration.isType('line-number')).toBe false
expect(decoration.getProperties().gutterName).toBe 'test-gutter'
expect(decoration.getProperties().class).toBe 'one'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
'use babel'
/* eslint-env jasmine */
import child_process from 'child_process'
import environmentHelpers from '../src/environment-helpers'
import os from 'os'
describe('Environment handling', () => {
let originalEnv
let options
beforeEach(() => {
originalEnv = process.env
delete process._originalEnv
options = {
platform: process.platform,
env: Object.assign({}, process.env)
}
})
afterEach(() => {
process.env = originalEnv
delete process._originalEnv
})
describe('on OSX, when PWD is not set', () => {
beforeEach(() => {
options.platform = 'darwin'
})
describe('needsPatching', () => {
it('returns true if PWD is unset', () => {
delete options.env.PWD
expect(environmentHelpers.needsPatching(options)).toBe(true)
options.env.PWD = undefined
expect(environmentHelpers.needsPatching(options)).toBe(true)
options.env.PWD = null
expect(environmentHelpers.needsPatching(options)).toBe(true)
options.env.PWD = false
expect(environmentHelpers.needsPatching(options)).toBe(true)
})
it('returns false if PWD is set', () => {
options.env.PWD = 'xterm'
expect(environmentHelpers.needsPatching(options)).toBe(false)
})
})
describe('normalize', () => {
it('changes process.env if PWD is unset', () => {
if (process.platform === 'win32') {
return
}
delete options.env.PWD
environmentHelpers.normalize(options)
expect(process._originalEnv).toBeDefined()
expect(process._originalEnv).toBeTruthy()
expect(process.env).toBeDefined()
expect(process.env).toBeTruthy()
expect(process.env.PWD).toBeDefined()
expect(process.env.PWD).toBeTruthy()
expect(process.env.PATH).toBeDefined()
expect(process.env.PATH).toBeTruthy()
expect(process.env.ATOM_HOME).toBeDefined()
expect(process.env.ATOM_HOME).toBeTruthy()
})
})
})
describe('on a platform other than OSX', () => {
beforeEach(() => {
options.platform = 'penguin'
})
describe('needsPatching', () => {
it('returns false if PWD is set or unset', () => {
delete options.env.PWD
expect(environmentHelpers.needsPatching(options)).toBe(false)
options.env.PWD = undefined
expect(environmentHelpers.needsPatching(options)).toBe(false)
options.env.PWD = null
expect(environmentHelpers.needsPatching(options)).toBe(false)
options.env.PWD = false
expect(environmentHelpers.needsPatching(options)).toBe(false)
options.env.PWD = '/'
expect(environmentHelpers.needsPatching(options)).toBe(false)
})
it('returns false for linux', () => {
options.platform = 'linux'
options.PWD = '/'
expect(environmentHelpers.needsPatching(options)).toBe(false)
})
it('returns false for windows', () => {
options.platform = 'win32'
options.PWD = 'c:\\'
expect(environmentHelpers.needsPatching(options)).toBe(false)
})
})
describe('normalize', () => {
it('does not change the environment', () => {
if (process.platform === 'win32') {
return
}
delete options.env.PWD
environmentHelpers.normalize(options)
expect(process._originalEnv).toBeUndefined()
expect(process.env).toBeDefined()
expect(process.env).toBeTruthy()
expect(process.env.PATH).toBeDefined()
expect(process.env.PATH).toBeTruthy()
expect(process.env.PWD).toBeUndefined()
expect(process.env.PATH).toBe(originalEnv.PATH)
expect(process.env.ATOM_HOME).toBeDefined()
expect(process.env.ATOM_HOME).toBeTruthy()
})
})
})
describe('getFromShell', () => {
describe('when things are configured properly', () => {
beforeEach(() => {
spyOn(child_process, 'spawnSync').andReturn({
stdout: 'FOO=BAR' + os.EOL + 'TERM=xterm-something' + os.EOL +
'PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path'
})
})
it('returns an object containing the information from the user\'s shell environment', () => {
let env = environmentHelpers.getFromShell()
expect(env.FOO).toEqual('BAR')
expect(env.TERM).toEqual('xterm-something')
expect(env.PATH).toEqual('/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path')
})
})
describe('when an error occurs launching the shell', () => {
beforeEach(() => {
spyOn(child_process, 'spawnSync').andReturn({
error: new Error('testing when an error occurs')
})
})
it('returns undefined', () => {
expect(environmentHelpers.getFromShell()).toBeUndefined()
})
it('leaves the environment as-is when normalize() is called', () => {
options.platform = 'darwin'
delete options.env.PWD
expect(environmentHelpers.needsPatching(options)).toBe(true)
environmentHelpers.normalize(options)
expect(process.env).toBeDefined()
expect(process._originalEnv).toBeUndefined()
})
})
})
})

View File

@@ -1,8 +1,10 @@
{Point} = require 'text-buffer'
{isPairedCharacter} = require '../src/text-utils'
module.exports =
class FakeLinesYardstick
constructor: (@model, @lineTopIndex) ->
{@displayLayer} = @model
@characterWidthsByScope = {}
getScopedCharacterWidth: (scopeNames, char) ->
@@ -24,31 +26,38 @@ class FakeLinesYardstick
targetRow = screenPosition.row
targetColumn = screenPosition.column
baseCharacterWidth = @model.getDefaultCharWidth()
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
left = 0
column = 0
iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator()
while iterator.next()
characterWidths = @getScopedCharacterWidths(iterator.getScopes())
scopes = []
startIndex = 0
{tagCodes, lineText} = @model.screenLineForScreenRow(targetRow)
for tagCode in tagCodes
if @displayLayer.isOpenTagCode(tagCode)
scopes.push(@displayLayer.tagForCode(tagCode))
else if @displayLayer.isCloseTagCode(tagCode)
scopes.splice(scopes.lastIndexOf(@displayLayer.tagForCode(tagCode)), 1)
else
text = lineText.substr(startIndex, tagCode)
startIndex += tagCode
characterWidths = @getScopedCharacterWidths(scopes)
valueIndex = 0
text = iterator.getText()
while valueIndex < text.length
if iterator.isPairedCharacter()
char = text
charLength = 2
valueIndex += 2
else
char = text[valueIndex]
charLength = 1
valueIndex++
valueIndex = 0
while valueIndex < text.length
if isPairedCharacter(text, valueIndex)
char = text[valueIndex...valueIndex + 2]
charLength = 2
valueIndex += 2
else
char = text[valueIndex]
charLength = 1
valueIndex++
break if column is targetColumn
break if column is targetColumn
left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
column += charLength
left += characterWidths[char] ? @model.getDefaultCharWidth() unless char is '\0'
column += charLength
{top, left}

View File

@@ -5,6 +5,12 @@
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[branch "master"]
remote = origin
merge = refs/heads/master
[remote "origin"]
url = git@github.com:atom/some-repo-i-guess.git
fetch = +refs/heads/*:refs/remotes/origin/*
[submodule "jstips"]
url = https://github.com/loverajoel/jstips
[submodule "You-Dont-Need-jQuery"]

View File

@@ -0,0 +1 @@
d2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5

3
spec/fixtures/lorem.txt vendored Normal file
View File

@@ -0,0 +1,3 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ultricies nulla id nibh aliquam, vitae euismod ipsum scelerisque. Vestibulum vulputate facilisis nisi, eu rhoncus turpis pretium ut. Curabitur facilisis urna in diam efficitur, vel maximus tellus consectetur. Suspendisse pulvinar felis sed metus tristique, a posuere dui suscipit. Ut vehicula, tellus ac blandit consequat, libero dui hendrerit elit, non pretium metus odio sed dolor. Vivamus quis volutpat ipsum. In convallis magna nec nunc tristique malesuada. Sed sed hendrerit lacus. Etiam arcu dui, consequat vel neque vitae, iaculis egestas justo. Donec lacinia odio nulla, condimentum porta erat accumsan at. Nunc vulputate nulla vel nunc fermentum egestas.
Duis ultricies libero elit, nec facilisis mi rhoncus ornare. Aliquam aliquet libero vitae arcu porttitor mattis. Vestibulum ultricies consectetur arcu, non gravida magna eleifend vel. Phasellus varius mattis ultricies. Vestibulum placerat lacus non consectetur fringilla. Duis congue, arcu iaculis vehicula hendrerit, purus odio faucibus ipsum, et fermentum massa tellus euismod nulla. Vivamus pellentesque blandit massa, sit amet hendrerit turpis congue eu. Suspendisse diam dui, vestibulum nec semper varius, maximus eu nunc. Vivamus facilisis pulvinar viverra. Praesent luctus lectus id est porttitor volutpat. Suspendisse est augue, mattis a tincidunt id, condimentum in turpis. Curabitur at erat commodo orci interdum tincidunt. Sed sodales elit odio, a placerat ipsum luctus nec. Sed maximus, justo ut pharetra pellentesque, orci mi faucibus enim, quis viverra arcu dui sed nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent quis velit libero.
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus a rutrum tortor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce bibendum odio et neque vestibulum rutrum. Vestibulum commodo, nibh non sodales lobortis, dui ex consectetur leo, a finibus libero lectus ac diam. Etiam dui nunc, bibendum a tempor vel, vestibulum lacinia neque. Mauris consectetur odio sit amet maximus pretium. Sed rutrum nunc at ante ullamcorper fermentum. Proin at quam a mauris pellentesque viverra. Nunc pretium pulvinar ipsum. Vestibulum eu nibh ut ex gravida tempus. Praesent ut elit ut ligula tristique dapibus ut sit amet leo. Proin non molestie erat.

View File

@@ -1,6 +0,0 @@
module.exports = function (state) {
return {
wasDeserializedBy: 'Deserializer1',
state: state
}
}

View File

@@ -1,6 +0,0 @@
module.exports = function (state) {
return {
wasDeserializedBy: 'Deserializer2',
state: state
}
}

View File

@@ -1,3 +1,17 @@
module.exports = {
activate: function() {}
activate () {},
deserializeMethod1 (state) {
return {
wasDeserializedBy: 'deserializeMethod1',
state: state
}
},
deserializeMethod2 (state) {
return {
wasDeserializedBy: 'deserializeMethod2',
state: state
}
}
}

View File

@@ -3,7 +3,7 @@
"version": "1.0.0",
"main": "./index",
"deserializers": {
"Deserializer1": "./deserializer-1.js",
"Deserializer2": "./deserializer-2.js"
"Deserializer1": "deserializeMethod1",
"Deserializer2": "deserializeMethod2"
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "package-with-a-git-prefixed-git-repo-url",
"repository": {
"type": "git",
"url": "git+https://github.com/example/repo.git"
},
"_id": "this is here to simulate the URL being already normalized by npm. we still need to stript git+ from the beginning and .git from the end."
}

View File

@@ -1,3 +0,0 @@
module.exports = function (state) {
return {state: state}
}

View File

@@ -1,3 +1,25 @@
'use strict'
module.exports = {
activate: function() {}
activate () {},
theDeserializerMethod (state) {
return {state: state}
},
viewProviderMethod1 (model) {
if (model.worksWithViewProvider1) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-1'
return element
}
},
viewProviderMethod2 (model) {
if (model.worksWithViewProvider2) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-2'
return element
}
}
}

View File

@@ -3,10 +3,10 @@
"main": "./index",
"version": "1.0.0",
"deserializers": {
"DeserializerFromPackageWithViewProviders": "./deserializer"
"DeserializerFromPackageWithViewProviders": "theDeserializerMethod"
},
"viewProviders": [
"./view-provider-1",
"./view-provider-2"
"viewProviderMethod1",
"viewProviderMethod2"
]
}

View File

@@ -1,9 +0,0 @@
'use strict'
module.exports = function (model) {
if (model.worksWithViewProvider1) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-1'
return element
}
}

View File

@@ -1,9 +0,0 @@
'use strict'
module.exports = function (model) {
if (model.worksWithViewProvider2) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-2'
return element
}
}

View File

@@ -9,12 +9,23 @@ var quicksort = function () {
// Wowza
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
/*
This is a multiline comment block with
an empty line inside of it.
Awesome.
*/
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
// This is a collection of
// single line comments
// ...with an empty line
// among it, geez!
return sort(left).concat(pivot).concat(sort(right));
};
// this is a single-line comment
return sort(Array.apply(this, arguments));
};
};

View File

@@ -1,3 +1,3 @@
#!/usr/bin/ruby
puts "America fuck yeah!"
puts "Atom fixture test"

View File

@@ -3,7 +3,6 @@
import fs from 'fs-plus'
import path from 'path'
import temp from 'temp'
import Git from 'nodegit'
import {it, beforeEach, afterEach} from './async-spec-helpers'
@@ -47,7 +46,7 @@ describe('GitRepositoryAsync', () => {
let threw = false
try {
await repo.repoPromise
await repo.getRepo()
} catch (e) {
threw = true
}
@@ -56,6 +55,14 @@ describe('GitRepositoryAsync', () => {
})
})
describe('openedPath', () => {
it('is the path passed to .open', () => {
const workingDirPath = copyRepository()
repo = GitRepositoryAsync.open(workingDirPath)
expect(repo.openedPath).toBe(workingDirPath)
})
})
describe('.getRepo()', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
@@ -64,19 +71,19 @@ describe('GitRepositoryAsync', () => {
})
it('returns the repository when not given a path', async () => {
const nodeGitRepo1 = await repo.repoPromise
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo()
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
})
it('returns the repository when given a non-submodule path', async () => {
const nodeGitRepo1 = await repo.repoPromise
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo('README')
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
})
it('returns the submodule repository when given a submodule path', async () => {
const nodeGitRepo1 = await repo.repoPromise
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo('jstips')
expect(nodeGitRepo1.workdir()).not.toBe(nodeGitRepo2.workdir())
@@ -103,7 +110,7 @@ describe('GitRepositoryAsync', () => {
it('returns the repository path for a repository path', async () => {
repo = openFixture('master.git')
const repoPath = await repo.getPath()
expect(repoPath).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
expect(repoPath).toEqualPath(path.join(__dirname, 'fixtures', 'git', 'master.git'))
})
})
@@ -230,9 +237,7 @@ describe('GitRepositoryAsync', () => {
})
})
// @joshaber: Disabling for now. There seems to be some race with path
// subscriptions leading to intermittent test failures, e.g.: https://travis-ci.org/atom/atom/jobs/102702554
xdescribe('.checkoutHeadForEditor(editor)', () => {
describe('.checkoutHeadForEditor(editor)', () => {
let filePath
let editor
@@ -305,7 +310,7 @@ describe('GitRepositoryAsync', () => {
await repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe(1)
const status = Git.Status.STATUS.WT_MODIFIED
const status = GitRepositoryAsync.Git.Status.STATUS.WT_MODIFIED
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
fs.writeFileSync(filePath, 'abc')
@@ -338,10 +343,10 @@ describe('GitRepositoryAsync', () => {
})
describe('.refreshStatus()', () => {
let newPath, modifiedPath, cleanPath
let newPath, modifiedPath, cleanPath, workingDirectory
beforeEach(() => {
const workingDirectory = copyRepository()
workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
modifiedPath = path.join(workingDirectory, 'file.txt')
newPath = path.join(workingDirectory, 'untracked.txt')
@@ -362,7 +367,7 @@ describe('GitRepositoryAsync', () => {
describe('in a repository with submodules', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
modifiedPath = path.join(workingDirectory, 'jstips', 'README.md')
newPath = path.join(workingDirectory, 'You-Dont-Need-jQuery', 'untracked.txt')
@@ -380,6 +385,86 @@ describe('GitRepositoryAsync', () => {
expect(repo.isStatusModified(await repo.getCachedPathStatus(modifiedPath))).toBe(true)
})
})
it('caches the proper statuses when a subdir is open', async () => {
const subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
const filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, '')
atom.project.setPaths([subDir])
await atom.workspace.open('b.txt')
const repo = atom.project.getRepositories()[0].async
await repo.refreshStatus()
const status = await repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe(false)
expect(repo.isStatusNew(status)).toBe(false)
})
it('caches the proper statuses when multiple project are open', async () => {
const otherWorkingDirectory = copyRepository()
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
await atom.workspace.open('b.txt')
const repo = atom.project.getRepositories()[0].async
await repo.refreshStatus()
const subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
const filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, 'some content!')
const status = await repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe(true)
expect(repo.isStatusNew(status)).toBe(false)
})
it('emits did-change-statuses if the status changes', async () => {
const someNewPath = path.join(workingDirectory, 'MyNewJSFramework.md')
fs.writeFileSync(someNewPath, '')
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
it('emits did-change-statuses if the branch changes', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
repo._refreshBranch = jasmine.createSpy('_refreshBranch').andCallFake(() => {
return Promise.resolve(true)
})
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
it('emits did-change-statuses if the ahead/behind changes', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
repo._refreshAheadBehindCount = jasmine.createSpy('_refreshAheadBehindCount').andCallFake(() => {
return Promise.resolve(true)
})
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
})
describe('.isProjectAtRoot()', () => {
@@ -499,7 +584,7 @@ describe('GitRepositoryAsync', () => {
await atom.workspace.open('file.txt')
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
project2.deserialize(atom.project.serialize(), atom.deserializers)
project2.deserialize(atom.project.serialize({isUnloading: true}))
const repo = project2.getRepositories()[0].async
waitsForPromise(() => repo.refreshStatus())
@@ -548,6 +633,14 @@ describe('GitRepositoryAsync', () => {
const relativizedPath = repo.relativize(`${workdir}/a/b.txt`, workdir)
expect(relativizedPath).toBe('a/b.txt')
})
it('preserves file case', () => {
repo.isCaseInsensitive = true
const workdir = '/tmp/foo/bar/baz/'
const relativizedPath = repo.relativize(`${workdir}a/README.txt`, workdir)
expect(relativizedPath).toBe('a/README.txt')
})
})
describe('.getShortHead(path)', () => {
@@ -626,7 +719,7 @@ describe('GitRepositoryAsync', () => {
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns 0, 0 for a branch with no upstream', async () => {
it('returns 1, 0 for a branch which is ahead by 1', async () => {
await repo.refreshStatus()
const {ahead, behind} = await repo.getCachedUpstreamAheadBehindCount('You-Dont-Need-jQuery')
@@ -792,4 +885,34 @@ describe('GitRepositoryAsync', () => {
})
})
})
describe('.getOriginURL()', () => {
beforeEach(() => {
const workingDirectory = copyRepository('repo-with-submodules')
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the origin URL', async () => {
const url = await repo.getOriginURL()
expect(url).toBe('git@github.com:atom/some-repo-i-guess.git')
})
})
describe('.getUpstreamBranch()', () => {
it('returns null when there is no upstream branch', async () => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
const upstream = await repo.getUpstreamBranch()
expect(upstream).toBe(null)
})
it('returns the upstream branch', async () => {
const workingDirectory = copyRepository('repo-with-submodules')
repo = GitRepositoryAsync.open(workingDirectory)
const upstream = await repo.getUpstreamBranch()
expect(upstream).toBe('refs/remotes/origin/master')
})
})
})

View File

@@ -33,7 +33,7 @@ describe "GitRepository", ->
waitsForPromise ->
repo.async.getPath().then(onSuccess)
runs ->
expect(onSuccess.mostRecentCall.args[0]).toBe(repoPath)
expect(onSuccess.mostRecentCall.args[0]).toEqualPath(repoPath)
describe "new GitRepository(path)", ->
it "throws an exception when no repository is found", ->
@@ -259,6 +259,46 @@ describe "GitRepository", ->
expect(repo.isStatusModified(status)).toBe false
expect(repo.isStatusNew(status)).toBe false
it 'caches the proper statuses when multiple project are open', ->
otherWorkingDirectory = copyRepository()
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
waitsForPromise ->
atom.workspace.open('b.txt')
statusHandler = null
runs ->
repo = atom.project.getRepositories()[0]
statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses statusHandler
repo.refreshStatus()
waitsFor ->
statusHandler.callCount > 0
runs ->
subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, '')
status = repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe true
expect(repo.isStatusNew(status)).toBe false
it 'caches statuses that were looked up synchronously', ->
originalContent = 'undefined'
fs.writeFileSync(modifiedPath, 'making this path modified')
repo.getPathStatus('file.txt')
fs.writeFileSync(modifiedPath, originalContent)
waitsForPromise -> repo.refreshStatus()
runs ->
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeFalsy()
describe "buffer events", ->
[editor] = []
@@ -317,7 +357,7 @@ describe "GitRepository", ->
runs ->
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
project2.deserialize(atom.project.serialize(), atom.deserializers)
project2.deserialize(atom.project.serialize({isUnloading: false}))
buffer = project2.getBuffers()[0]
waitsFor ->

View File

@@ -15,6 +15,8 @@ ChromedriverPort = 9515
ChromedriverURLBase = "/wd/hub"
ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status"
userDataDir = temp.mkdirSync('atom-user-data-dir')
chromeDriverUp = (done) ->
checkStatus = ->
http
@@ -48,7 +50,7 @@ buildAtomClient = (args, env) ->
"atom-env=#{map(env, (value, key) -> "#{key}=#{value}").join(" ")}"
"dev"
"safe"
"user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"
"user-data-dir=#{userDataDir}"
"socket-path=#{SocketPath}"
])
@@ -124,7 +126,7 @@ buildAtomClient = (args, env) ->
.addCommand "simulateQuit", (done) ->
@execute -> atom.unloadEditorWindow()
.execute -> require("remote").require("app").emit("before-quit")
.execute -> require("electron").remote.app.emit("before-quit")
.call(done)
module.exports = (args, env, fn) ->

View File

@@ -28,13 +28,12 @@ describe "Starting Atom", ->
it "opens the parent directory and creates an empty text editor", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath])
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.waitForPaneItemCount(1, 1000)
.click("atom-text-editor")
.keys("Hello!")
.execute -> atom.workspace.getActiveTextEditor().getText()
@@ -124,6 +123,34 @@ describe "Starting Atom", ->
.waitForPaneItemCount(0, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
describe "when using the -a, --add option", ->
it "reuses that window and add the folder to project paths", ->
fourthTempDir = temp.mkdirSync("a-fourth-dir")
fourthTempFilePath = path.join(fourthTempDir, "a-file")
fs.writeFileSync(fourthTempFilePath, "4 - This file was already here.")
fifthTempDir = temp.mkdirSync("a-fifth-dir")
fifthTempFilePath = path.join(fifthTempDir, "a-file")
fs.writeFileSync(fifthTempFilePath, "5 - This file was already here.")
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 5000)
# Opening another file reuses the same window and add parent dir to
# project paths.
.startAnotherAtom(['-a', fourthTempFilePath], ATOM_HOME: atomHome)
.waitForPaneItemCount(2, 5000)
.waitForWindowCount(1, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir])
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value: text}) -> expect(text).toBe "4 - This file was already here."
# Opening another directory resuses the same window and add the folder to project paths.
.startAnotherAtom(['--add', fifthTempDir], ATOM_HOME: atomHome)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir, fifthTempDir])
it "opens the new window offset from the other window", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
@@ -153,6 +180,8 @@ describe "Starting Atom", ->
.waitForPaneItemCount(0, 3000)
.execute -> atom.workspace.open()
.waitForPaneItemCount(1, 3000)
.keys("Hello!")
.waitUntil((-> Promise.resolve(false)), 1100)
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
@@ -239,6 +268,36 @@ describe "Starting Atom", ->
[otherTempDirPath]
].sort()
it "doesn't reopen any previously opened windows if restorePreviousWindowsOnStart is disabled", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
configPath = path.join(atomHome, 'config.cson')
config = CSON.readFileSync(configPath)
config['*'].core = {restorePreviousWindowsOnStart: false}
CSON.writeFileSync(configPath, config)
runAtom [], {ATOM_HOME: atomHome}, (client) ->
windowProjectPaths = []
client
.waitForWindowCount(1, 10000)
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
.call ->
expect(windowProjectPaths).toEqual [
[]
]
describe "opening a remote directory", ->
it "opens the parent directory and creates an empty text editor", ->
remoteDirectory = 'remote://server:3437/some/directory/path'

View File

@@ -1,7 +1,8 @@
Grim = require 'grim'
_ = require 'underscore-plus'
fs = require 'fs-plus'
path = require 'path'
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
window[key] = value for key, value of require '../vendor/jasmine'
@@ -88,7 +89,7 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
if logStream?
fs.writeSync(logStream, str)
else
ipc.send 'write-to-stderr', str
ipcRenderer.send 'write-to-stderr', str
{TerminalReporter} = require 'jasmine-tagged'
new TerminalReporter
@@ -96,13 +97,10 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
log(str)
onComplete: (runner) ->
fs.closeSync(logStream) if logStream?
if process.env.JANKY_SHA1 or process.env.CI
grim = require 'grim'
if grim.getDeprecationsLength() > 0
grim.logDeprecations()
resolveWithExitCode(1)
return
if Grim.getDeprecationsLength() > 0
Grim.logDeprecations()
resolveWithExitCode(1)
return
if runner.results().failedCount > 0
resolveWithExitCode(1)

View File

@@ -334,66 +334,56 @@ describe "LanguageMode", ->
it "folds every foldable line", ->
languageMode.foldAll()
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
fold2.destroy()
fold3 = editor.tokenizedLineForScreenRow(4).fold
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]
[fold1, fold2, fold3] = languageMode.unfoldAll()
expect([fold1.start.row, fold1.end.row]).toEqual [0, 12]
expect([fold2.start.row, fold2.end.row]).toEqual [1, 9]
expect([fold3.start.row, fold3.end.row]).toEqual [4, 7]
describe ".foldBufferRow(bufferRow)", ->
describe "when bufferRow can be folded", ->
it "creates a fold based on the syntactic region starting at the given row", ->
languageMode.foldBufferRow(1)
fold = editor.tokenizedLineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
[fold] = languageMode.unfoldAll()
expect([fold.start.row, fold.end.row]).toEqual [1, 9]
describe "when bufferRow can't be folded", ->
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
languageMode.foldBufferRow(8)
fold = editor.tokenizedLineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
[fold] = languageMode.unfoldAll()
expect([fold.start.row, fold.end.row]).toEqual [1, 9]
describe "when the bufferRow is already folded", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
languageMode.foldBufferRow(2)
expect(editor.tokenizedLineForScreenRow(1).fold).toBeDefined()
expect(editor.tokenizedLineForScreenRow(0).fold).not.toBeDefined()
expect(editor.isFoldedAtBufferRow(0)).toBe(false)
expect(editor.isFoldedAtBufferRow(1)).toBe(true)
languageMode.foldBufferRow(1)
expect(editor.tokenizedLineForScreenRow(0).fold).toBeDefined()
expect(editor.isFoldedAtBufferRow(0)).toBe(true)
describe "when the bufferRow is in a multi-line comment", ->
it "searches upward and downward for surrounding comment lines and folds them as a single fold", ->
buffer.insert([1, 0], " //this is a comment\n // and\n //more docs\n\n//second comment")
languageMode.foldBufferRow(1)
fold = editor.tokenizedLineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 3
[fold] = languageMode.unfoldAll()
expect([fold.start.row, fold.end.row]).toEqual [1, 3]
describe "when the bufferRow is a single-line comment", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
buffer.insert([1, 0], " //this is a single line comment\n")
languageMode.foldBufferRow(1)
fold = editor.tokenizedLineForScreenRow(0).fold
expect(fold.getStartRow()).toBe 0
expect(fold.getEndRow()).toBe 13
[fold] = languageMode.unfoldAll()
expect([fold.start.row, fold.end.row]).toEqual [0, 13]
describe ".foldAllAtIndentLevel(indentLevel)", ->
it "folds blocks of text at the given indentation level", ->
languageMode.foldAllAtIndentLevel(0)
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {"
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {" + editor.displayLayer.foldCharacter
expect(editor.getLastScreenRow()).toBe 0
languageMode.foldAllAtIndentLevel(1)
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {"
expect(editor.lineTextForScreenRow(1)).toBe " var sort = function(items) {"
expect(editor.lineTextForScreenRow(1)).toBe " var sort = function(items) {" + editor.displayLayer.foldCharacter
expect(editor.getLastScreenRow()).toBe 4
languageMode.foldAllAtIndentLevel(2)
@@ -429,45 +419,47 @@ describe "LanguageMode", ->
it "folds every foldable line", ->
languageMode.foldAll()
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 4]
fold3 = editor.tokenizedLineForScreenRow(2).fold.destroy()
fold4 = editor.tokenizedLineForScreenRow(3).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
folds = languageMode.unfoldAll()
expect(folds.length).toBe 8
expect([folds[0].start.row, folds[0].end.row]).toEqual [0, 30]
expect([folds[1].start.row, folds[1].end.row]).toEqual [1, 4]
expect([folds[2].start.row, folds[2].end.row]).toEqual [5, 27]
expect([folds[3].start.row, folds[3].end.row]).toEqual [6, 8]
expect([folds[4].start.row, folds[4].end.row]).toEqual [11, 16]
expect([folds[5].start.row, folds[5].end.row]).toEqual [17, 20]
expect([folds[6].start.row, folds[6].end.row]).toEqual [21, 22]
expect([folds[7].start.row, folds[7].end.row]).toEqual [24, 25]
describe ".foldAllAtIndentLevel()", ->
it "folds every foldable range at a given indentLevel", ->
languageMode.foldAllAtIndentLevel(2)
fold1 = editor.tokenizedLineForScreenRow(6).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [6, 8]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(11).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
fold2.destroy()
folds = languageMode.unfoldAll()
expect(folds.length).toBe 5
expect([folds[0].start.row, folds[0].end.row]).toEqual [6, 8]
expect([folds[1].start.row, folds[1].end.row]).toEqual [11, 16]
expect([folds[2].start.row, folds[2].end.row]).toEqual [17, 20]
expect([folds[3].start.row, folds[3].end.row]).toEqual [21, 22]
expect([folds[4].start.row, folds[4].end.row]).toEqual [24, 25]
it "does not fold anything but the indentLevel", ->
languageMode.foldAllAtIndentLevel(0)
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(5).fold
expect(fold2).toBeFalsy()
folds = languageMode.unfoldAll()
expect(folds.length).toBe 1
expect([folds[0].start.row, folds[0].end.row]).toEqual [0, 30]
describe ".isFoldableAtBufferRow(bufferRow)", ->
it "returns true if the line starts a multi-line comment", ->
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
expect(languageMode.isFoldableAtBufferRow(6)).toBe true
expect(languageMode.isFoldableAtBufferRow(17)).toBe false
expect(languageMode.isFoldableAtBufferRow(8)).toBe false
expect(languageMode.isFoldableAtBufferRow(11)).toBe true
expect(languageMode.isFoldableAtBufferRow(15)).toBe false
expect(languageMode.isFoldableAtBufferRow(17)).toBe true
expect(languageMode.isFoldableAtBufferRow(21)).toBe true
expect(languageMode.isFoldableAtBufferRow(24)).toBe true
expect(languageMode.isFoldableAtBufferRow(28)).toBe false
it "does not return true for a line in the middle of a comment that's followed by an indented line", ->
expect(languageMode.isFoldableAtBufferRow(7)).toBe false

View File

@@ -19,36 +19,45 @@ describe "LinesYardstick", ->
screenRowsToMeasure = []
buildLineNode = (screenRow) ->
tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
iterator = tokenizedLine.getTokenIterator()
startIndex = 0
scopes = []
screenLine = editor.screenLineForScreenRow(screenRow)
lineNode = document.createElement("div")
lineNode.style.whiteSpace = "pre"
while iterator.next()
span = document.createElement("span")
span.className = iterator.getScopes().join(' ').replace(/\.+/g, ' ')
span.textContent = iterator.getText()
lineNode.appendChild(span)
for tagCode in screenLine.tagCodes when tagCode isnt 0
if editor.displayLayer.isCloseTagCode(tagCode)
scopes.pop()
else if editor.displayLayer.isOpenTagCode(tagCode)
scopes.push(editor.displayLayer.tagForCode(tagCode))
else
text = screenLine.lineText.substr(startIndex, tagCode)
startIndex += tagCode
span = document.createElement("span")
span.className = scopes.join(' ').replace(/\.+/g, ' ')
span.textContent = text
lineNode.appendChild(span)
jasmine.attachToDOM(lineNode)
createdLineNodes.push(lineNode)
lineNode
mockLineNodesProvider =
lineNodeForLineIdAndScreenRow: (lineId, screenRow) ->
buildLineNode(screenRow)
lineNodesById: {}
lineIdForScreenRow: (screenRow) ->
editor.screenLineForScreenRow(screenRow).id
textNodesForLineIdAndScreenRow: (lineId, screenRow) ->
lineNode = @lineNodeForLineIdAndScreenRow(lineId, screenRow)
lineNodeForScreenRow: (screenRow) ->
@lineNodesById[@lineIdForScreenRow(screenRow)] ?= buildLineNode(screenRow)
textNodesForScreenRow: (screenRow) ->
lineNode = @lineNodeForScreenRow(screenRow)
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT)
textNodes = []
while textNode = iterator.nextNode()
textNodes.push(textNode)
textNodes.push(textNode) while textNode = iterator.nextNode()
textNodes
editor.setLineHeightInPixels(14)
lineTopIndex = new LineTopIndex({
defaultLineHeight: editor.getLineHeightInPixels()
})
lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
afterEach ->
@@ -69,9 +78,9 @@ describe "LinesYardstick", ->
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 37.78125, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43.171875, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72.171875, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 38, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})
it "reuses already computed pixel positions unless it is invalidated", ->
@@ -82,9 +91,9 @@ describe "LinesYardstick", ->
}
"""
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70})
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 96, top: 70})
atom.styles.addStyleSheet """
* {
@@ -92,9 +101,9 @@ describe "LinesYardstick", ->
}
"""
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70})
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 96, top: 70})
linesYardstick.invalidateCache()
@@ -102,23 +111,6 @@ describe "LinesYardstick", ->
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 72, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 120, top: 70})
it "correctly handles RTL characters", ->
atom.styles.addStyleSheet """
* {
font-size: 14px;
font-family: monospace;
}
"""
editor.setText("السلام عليكم")
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0)).left).toBe 0
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1)).left).toBe 8
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 2)).left).toBe 16
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5)).left).toBe 33
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 7)).left).toBe 50
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 9)).left).toBe 67
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 11)).left).toBe 84
it "doesn't report a width greater than 0 when the character to measure is at the beginning of a text node", ->
# This spec documents what seems to be a bug in Chromium, because we'd
# expect that Range(0, 0).getBoundingClientRect().width to always be zero.
@@ -163,9 +155,38 @@ describe "LinesYardstick", ->
expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100})).toEqual([2, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 99.9})).toEqual([5, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 224.2365234375})).toEqual([5, 29])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 225})).toEqual([5, 30])
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
it "overshoots to the nearest character when text nodes are not spatially contiguous", ->
atom.styles.addStyleSheet """
* {
font-size: 12px;
font-family: monospace;
}
"""
buildLineNode = (screenRow) ->
lineNode = document.createElement("div")
lineNode.style.whiteSpace = "pre"
lineNode.innerHTML = '<span>foo</span><span style="margin-left: 40px">bar</span>'
jasmine.attachToDOM(lineNode)
createdLineNodes.push(lineNode)
lineNode
editor.setText("foobar")
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 7})).toEqual([0, 1])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 14})).toEqual([0, 2])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 21})).toEqual([0, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 30})).toEqual([0, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 50})).toEqual([0, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 62})).toEqual([0, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 69})).toEqual([0, 4])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 76})).toEqual([0, 5])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 100})).toEqual([0, 6])
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 200})).toEqual([0, 6])
it "clips pixel positions above buffer start", ->
expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
@@ -178,3 +199,7 @@ describe "LinesYardstick", ->
expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
expect(linesYardstick.screenPositionForPixelPosition(top: (editor.getLastScreenRow() + 1) * 14, left: 0)).toEqual [12, 2]
expect(linesYardstick.screenPositionForPixelPosition(top: editor.getLastScreenRow() * 14, left: 0)).toEqual [12, 0]
it "clips negative horizontal pixel positions", ->
expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: -10)).toEqual [0, 0]
expect(linesYardstick.screenPositionForPixelPosition(top: 1 * 14, left: -10)).toEqual [1, 0]

View File

@@ -8,13 +8,13 @@ describe 'ModuleCache', ->
beforeEach ->
spyOn(Module, '_findPath').andCallThrough()
it 'resolves atom shell module paths without hitting the filesystem', ->
it 'resolves Electron module paths without hitting the filesystem', ->
builtins = ModuleCache.cache.builtins
expect(Object.keys(builtins).length).toBeGreaterThan 0
for builtinName, builtinPath of builtins
expect(require.resolve(builtinName)).toBe builtinPath
expect(fs.isFileSync(require.resolve(builtinName)))
expect(fs.isFileSync(require.resolve(builtinName))).toBeTruthy()
expect(Module._findPath.callCount).toBe 0

View File

@@ -17,6 +17,20 @@ describe "PackageManager", ->
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
describe "::getApmPath()", ->
it "returns the path to the apm command", ->
apmPath = path.join(process.resourcesPath, "app", "apm", "bin", "apm")
if process.platform is 'win32'
apmPath += ".cmd"
expect(atom.packages.getApmPath()).toBe apmPath
describe "when the core.apmPath setting is set", ->
beforeEach ->
atom.config.set("core.apmPath", "/path/to/apm")
it "returns the value of the core.apmPath config setting", ->
expect(atom.packages.getApmPath()).toBe "/path/to/apm"
describe "::loadPackage(name)", ->
beforeEach ->
atom.config.set("core.disabledPackages", [])
@@ -52,15 +66,23 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-with-broken-package-json package")
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-with-broken-package-json"
it "returns null if the package name or path starts with a dot", ->
expect(atom.packages.loadPackage("/Users/user/.atom/packages/.git")).toBeNull()
it "normalizes short repository urls in package.json", ->
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "https://github.com/example/repo.git"
expect(metadata.repository.url).toBe "https://github.com/example/repo"
{metadata} = atom.packages.loadPackage("package-with-invalid-url-package-json")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "foo"
it "trims git+ from the beginning and .git from the end of repository URLs, even if npm already normalized them ", ->
{metadata} = atom.packages.loadPackage("package-with-prefixed-and-suffixed-repo-url")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "https://github.com/example/repo"
it "returns null if the package is not found in any package directory", ->
spyOn(console, 'warn')
expect(atom.packages.loadPackage("this-package-cannot-be-found")).toBeNull()
@@ -88,18 +110,16 @@ describe "PackageManager", ->
state1 = {deserializer: 'Deserializer1', a: 'b'}
expect(atom.deserializers.deserialize(state1)).toEqual {
wasDeserializedBy: 'Deserializer1'
wasDeserializedBy: 'deserializeMethod1'
state: state1
}
state2 = {deserializer: 'Deserializer2', c: 'd'}
expect(atom.deserializers.deserialize(state2)).toEqual {
wasDeserializedBy: 'Deserializer2'
wasDeserializedBy: 'deserializeMethod2'
state: state2
}
expect(pack.mainModule).toBeNull()
describe "when there are view providers specified in the package's package.json", ->
model1 = {worksWithViewProvider1: true}
model2 = {worksWithViewProvider2: true}
@@ -448,16 +468,15 @@ describe "PackageManager", ->
pack = null
waitsForPromise ->
atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p
runs ->
expect(pack.mainModule.someNumber).not.toBe 77
pack.mainModule.someNumber = 77
atom.packages.deactivatePackage("package-with-serialization")
spyOn(pack.mainModule, 'activate').andCallThrough()
waitsForPromise ->
atom.packages.activatePackage("package-with-serialization")
runs ->
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
waitsForPromise ->
atom.packages.activatePackage("package-with-serialization")
runs ->
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
it "invokes ::onDidActivatePackage listeners with the activated package", ->
activatedPackage = null
@@ -821,6 +840,34 @@ describe "PackageManager", ->
expect(atom.packages.isPackageActive("package-with-missing-provided-services")).toBe true
expect(addErrorHandler.callCount).toBe 0
describe "::serialize", ->
it "does not serialize packages that threw an error during activation", ->
spyOn(console, 'warn')
badPack = null
waitsForPromise ->
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
runs ->
spyOn(badPack.mainModule, 'serialize').andCallThrough()
atom.packages.serialize()
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's serialize method", ->
spyOn(console, 'error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialize-error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialization')
runs ->
atom.packages.serialize()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
describe "::deactivatePackage(id)", ->
afterEach ->
atom.packages.unloadPackages()
@@ -852,33 +899,6 @@ describe "PackageManager", ->
expect(badPack.mainModule.deactivate).not.toHaveBeenCalled()
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy()
it "does not serialize packages that have not been activated called on their main module", ->
spyOn(console, 'warn')
badPack = null
waitsForPromise ->
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
runs ->
spyOn(badPack.mainModule, 'serialize').andCallThrough()
atom.packages.deactivatePackage("package-that-throws-on-activate")
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's serialize method", ->
spyOn(console, 'error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialize-error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialization')
runs ->
atom.packages.deactivatePackages()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's deactivate method", ->
spyOn(console, 'error')

View File

@@ -0,0 +1,34 @@
PaneAxis = require '../src/pane-axis'
PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
buildPane = ->
new Pane({
applicationDelegate: atom.applicationDelegate,
config: atom.config,
deserializerManager: atom.deserializers,
notificationManager: atom.notifications
})
describe "PaneAxisElement", ->
it "correctly subscribes and unsubscribes to the underlying model events on attach/detach", ->
container = new PaneContainer(config: atom.config, applicationDelegate: atom.applicationDelegate)
axis = new PaneAxis
axis.setContainer(container)
axisElement = atom.views.getView(axis)
panes = [buildPane(), buildPane(), buildPane()]
jasmine.attachToDOM(axisElement)
axis.addChild(panes[0])
expect(axisElement.children[0]).toBe(atom.views.getView(panes[0]))
axisElement.remove()
axis.addChild(panes[1])
expect(axisElement.children[2]).toBeUndefined()
jasmine.attachToDOM(axisElement)
expect(axisElement.children[2]).toBe(atom.views.getView(panes[1]))
axis.addChild(panes[2])
expect(axisElement.children[4]).toBe(atom.views.getView(panes[2]))

View File

@@ -1,5 +1,6 @@
{extend} = require 'underscore-plus'
{Emitter} = require 'event-kit'
Grim = require 'grim'
Pane = require '../src/pane'
PaneAxis = require '../src/pane-axis'
PaneContainer = require '../src/pane-container'
@@ -18,8 +19,8 @@ describe "Pane", ->
onDidDestroy: (fn) -> @emitter.on('did-destroy', fn)
destroy: -> @destroyed = true; @emitter.emit('did-destroy')
isDestroyed: -> @destroyed
isPending: -> @pending
pending: false
onDidTerminatePendingState: (callback) -> @emitter.on 'terminate-pending-state', callback
terminatePendingState: -> @emitter.emit 'terminate-pending-state'
beforeEach ->
confirm = spyOn(atom.applicationDelegate, 'confirm')
@@ -92,7 +93,7 @@ describe "Pane", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
[item1, item2] = pane.getItems()
item3 = new Item("C")
pane.addItem(item3, 1)
pane.addItem(item3, index: 1)
expect(pane.getItems()).toEqual [item1, item3, item2]
it "adds the item after the active item if no index is provided", ->
@@ -115,7 +116,7 @@ describe "Pane", ->
pane.onDidAddItem (event) -> events.push(event)
item = new Item("C")
pane.addItem(item, 1)
pane.addItem(item, index: 1)
expect(events).toEqual [{item, index: 1, moved: false}]
it "throws an exception if the item is already present on a pane", ->
@@ -132,15 +133,56 @@ describe "Pane", ->
expect(-> pane.addItem('foo')).toThrow()
expect(-> pane.addItem(1)).toThrow()
it "destroys any existing pending item if the new item is pending", ->
it "destroys any existing pending item", ->
pane = new Pane(paneParams(items: []))
itemA = new Item("A")
itemB = new Item("B")
itemA.pending = true
itemB.pending = true
pane.addItem(itemA)
itemC = new Item("C")
pane.addItem(itemA, pending: false)
pane.addItem(itemB, pending: true)
pane.addItem(itemC, pending: false)
expect(itemB.isDestroyed()).toBe true
it "adds the new item before destroying any existing pending item", ->
eventOrder = []
pane = new Pane(paneParams(items: []))
itemA = new Item("A")
itemB = new Item("B")
pane.addItem(itemA, pending: true)
pane.onDidAddItem ({item}) ->
eventOrder.push("add") if item is itemB
pane.onDidRemoveItem ({item}) ->
eventOrder.push("remove") if item is itemA
pane.addItem(itemB)
expect(itemA.isDestroyed()).toBe true
waitsFor ->
eventOrder.length is 2
runs ->
expect(eventOrder).toEqual ["add", "remove"]
describe "when using the old API of ::addItem(item, index)", ->
beforeEach ->
spyOn Grim, "deprecate"
it "supports the older public API", ->
pane = new Pane(paneParams(items: []))
itemA = new Item("A")
itemB = new Item("B")
itemC = new Item("C")
pane.addItem(itemA, 0)
pane.addItem(itemB, 0)
pane.addItem(itemC, 0)
expect(pane.getItems()).toEqual [itemC, itemB, itemA]
it "shows a deprecation warning", ->
pane = new Pane(paneParams(items: []))
pane.addItem(new Item(), 2)
expect(Grim.deprecate).toHaveBeenCalledWith "Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})"
describe "::activateItem(item)", ->
pane = null
@@ -172,21 +214,103 @@ describe "Pane", ->
beforeEach ->
itemC = new Item("C")
itemD = new Item("D")
itemC.pending = true
itemD.pending = true
it "replaces the active item if it is pending", ->
pane.activateItem(itemC)
pane.activateItem(itemC, pending: true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'C', 'B']
pane.activateItem(itemD)
pane.activateItem(itemD, pending: true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'D', 'B']
it "adds the item after the active item if it is not pending", ->
pane.activateItem(itemC)
pane.activateItem(itemC, pending: true)
pane.activateItemAtIndex(2)
pane.activateItem(itemD)
pane.activateItem(itemD, pending: true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D']
describe "::setPendingItem", ->
pane = null
beforeEach ->
pane = atom.workspace.getActivePane()
it "changes the pending item", ->
expect(pane.getPendingItem()).toBeNull()
pane.setPendingItem("fake item")
expect(pane.getPendingItem()).toEqual "fake item"
describe "::onItemDidTerminatePendingState callback", ->
pane = null
callbackCalled = false
beforeEach ->
pane = atom.workspace.getActivePane()
callbackCalled = false
it "is called when the pending item changes", ->
pane.setPendingItem("fake item one")
pane.onItemDidTerminatePendingState (item) ->
callbackCalled = true
expect(item).toEqual "fake item one"
pane.setPendingItem("fake item two")
expect(callbackCalled).toBeTruthy()
it "has access to the new pending item via ::getPendingItem", ->
pane.setPendingItem("fake item one")
pane.onItemDidTerminatePendingState (item) ->
callbackCalled = true
expect(pane.getPendingItem()).toEqual "fake item two"
pane.setPendingItem("fake item two")
expect(callbackCalled).toBeTruthy()
it "isn't called when a pending item is replaced with a new one", ->
pane = null
pendingSpy = jasmine.createSpy("onItemDidTerminatePendingState")
destroySpy = jasmine.createSpy("onWillDestroyItem")
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then ->
pane = atom.workspace.getActivePane()
runs ->
pane.onItemDidTerminatePendingState pendingSpy
pane.onWillDestroyItem destroySpy
waitsForPromise ->
atom.workspace.open('sample.js', pending: true)
runs ->
expect(destroySpy).toHaveBeenCalled()
expect(pendingSpy).not.toHaveBeenCalled()
describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))
[item1, item2, item3, item4, item5] = pane.getItems()
pane.itemStack = [item3, item1, item2, item5, item4]
pane.activateItem(item4)
expect(pane.getActiveItem()).toBe item4
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item5
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item2
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item5
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item4
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item3
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item1
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item3
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item4
pane.activateNextRecentlyUsedItem()
pane.moveActiveItemToTopOfStack()
expect(pane.getActiveItem()).toBe item5
expect(pane.itemStack[4]).toBe item5
describe "::activateNextItem() and ::activatePreviousItem()", ->
it "sets the active item to the next/previous item, looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
@@ -253,7 +377,7 @@ describe "Pane", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
it "removes the item from the items list and destroyes it", ->
it "removes the item from the items list and destroys it", ->
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item2)
expect(item2 in pane.getItems()).toBe false
@@ -264,6 +388,23 @@ describe "Pane", ->
expect(item1 in pane.getItems()).toBe false
expect(item1.isDestroyed()).toBe true
it "removes the item from the itemStack", ->
pane.itemStack = [item2, item3, item1]
pane.activateItem(item1)
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item3)
expect(pane.itemStack).toEqual [item2, item1]
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item1)
expect(pane.itemStack).toEqual [item2]
expect(pane.getActiveItem()).toBe item2
pane.destroyItem(item2)
expect(pane.itemStack).toEqual []
expect(pane.getActiveItem()).toBeUndefined()
it "invokes ::onWillDestroyItem() observers before destroying the item", ->
events = []
pane.onWillDestroyItem (event) ->
@@ -605,6 +746,23 @@ describe "Pane", ->
expect(pane2.isDestroyed()).toBe true
expect(item4.isDestroyed()).toBe false
describe "when the item being moved is pending", ->
it "is made permanent in the new pane", ->
item6 = new Item("F")
pane1.addItem(item6, pending: true)
expect(pane1.getPendingItem()).toEqual item6
pane1.moveItemToPane(item6, pane2, 0)
expect(pane2.getPendingItem()).not.toEqual item6
describe "when the target pane has a pending item", ->
it "does not destroy the pending item", ->
item6 = new Item("F")
pane1.addItem(item6, pending: true)
expect(pane1.getPendingItem()).toEqual item6
pane2.moveItemToPane(item5, pane1, 0)
expect(pane1.getPendingItem()).toEqual item6
describe "split methods", ->
[pane1, item1, container] = []
@@ -759,6 +917,82 @@ describe "Pane", ->
expect(item1.save).not.toHaveBeenCalled()
expect(pane.isDestroyed()).toBe false
describe "when item fails to save", ->
[pane, item1, item2] = []
beforeEach ->
pane = new Pane({items: [new Item("A"), new Item("B")], applicationDelegate: atom.applicationDelegate, config: atom.config})
[item1, item2] = pane.getItems()
item1.shouldPromptToSave = -> true
item1.getURI = -> "/test/path"
item1.save = jasmine.createSpy("save").andCallFake ->
error = new Error("EACCES, permission denied '/test/path'")
error.path = '/test/path'
error.code = 'EACCES'
throw error
it "does not destroy the pane if save fails and user clicks cancel", ->
confirmations = 0
confirm.andCallFake ->
confirmations++
if confirmations is 1
return 0 # click save
else
return 1 # click cancel
pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(2)
expect(item1.save).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe false
it "does destroy the pane if the user saves the file under a new name", ->
item1.saveAs = jasmine.createSpy("saveAs").andReturn(true)
confirmations = 0
confirm.andCallFake ->
confirmations++
return 0 # save and then save as
showSaveDialog.andReturn("new/path")
pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(2)
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
expect(item1.save).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe true
it "asks again if the saveAs also fails", ->
item1.saveAs = jasmine.createSpy("saveAs").andCallFake ->
error = new Error("EACCES, permission denied '/test/path'")
error.path = '/test/path'
error.code = 'EACCES'
throw error
confirmations = 0
confirm.andCallFake ->
confirmations++
if confirmations < 3
return 0 # save, save as, save as
return 2 # don't save
showSaveDialog.andReturn("new/path")
pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(3)
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
expect(item1.save).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe true
describe "::destroy()", ->
[container, pane1, pane2] = []
@@ -806,6 +1040,67 @@ describe "Pane", ->
pane2.destroy()
expect(container.root).toBe pane1
describe "pending state", ->
editor1 = null
pane = null
eventCount = null
beforeEach ->
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) ->
editor1 = o
pane = atom.workspace.getActivePane()
runs ->
eventCount = 0
editor1.onDidTerminatePendingState -> eventCount++
it "does not open file in pending state by default", ->
waitsForPromise ->
atom.workspace.open('sample.js').then (o) ->
editor1 = o
pane = atom.workspace.getActivePane()
runs ->
expect(pane.getPendingItem()).toBeNull()
it "opens file in pending state if 'pending' option is true", ->
expect(pane.getPendingItem()).toEqual editor1
it "terminates pending state if ::terminatePendingState is invoked", ->
editor1.terminatePendingState()
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe 1
it "terminates pending state when buffer is changed", ->
editor1.insertText('I\'ll be back!')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe 1
it "only calls terminate handler once when text is modified twice", ->
editor1.insertText('Some text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
editor1.save()
editor1.insertText('More text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe 1
it "only calls clearPendingItem if there is a pending item to clear", ->
spyOn(pane, "clearPendingItem").andCallThrough()
editor1.terminatePendingState()
editor1.terminatePendingState()
expect(pane.getPendingItem()).toBeNull()
expect(pane.clearPendingItem.callCount).toBe 1
describe "serialization", ->
pane = null
@@ -837,3 +1132,30 @@ describe "Pane", ->
pane.focus()
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.focused).toBe true
it "can serialize and deserialize the order of the items in the itemStack", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item3, item1, item2]
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual pane.itemStack
expect(newPane.itemStack[2]).toEqual item2
it "builds the itemStack if the itemStack is not serialized", ->
[item1, item2, item3] = pane.getItems()
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual newPane.itemStack
it "rebuilds the itemStack if items.length does not match itemStack.length", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item3]
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual newPane.itemStack
it "does not serialize the reference to the items in the itemStack for pane items that will not be serialized", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item1, item3]
unserializable = {}
pane.activateItem(unserializable)
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual [item2, item1, item3]

View File

@@ -21,6 +21,14 @@ describe "Project", ->
afterEach ->
deserializedProject?.destroy()
it "does not deserialize paths to non directories", ->
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
state = atom.project.serialize()
state.paths.push('/directory/that/does/not/exist')
state.paths.push(path.join(__dirname, 'fixtures', 'sample.js'))
deserializedProject.deserialize(state, atom.deserializers)
expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths())
it "does not include unretained buffers in the serialized state", ->
waitsForPromise ->
atom.project.bufferForPath('a')
@@ -29,7 +37,7 @@ describe "Project", ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 0
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
@@ -39,7 +47,7 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 1
deserializedProject.getBuffers()[0].destroy()
@@ -56,7 +64,7 @@ describe "Project", ->
expect(atom.project.getBuffers().length).toBe 1
fs.mkdirSync(pathToOpen)
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 0
it "does not deserialize buffers when their path is inaccessible", ->
@@ -70,9 +78,26 @@ describe "Project", ->
expect(atom.project.getBuffers().length).toBe 1
fs.chmodSync(pathToOpen, '000')
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 0
it "serializes marker layers only if Atom is quitting", ->
waitsForPromise ->
atom.workspace.open('a')
runs ->
bufferA = atom.project.getBuffers()[0]
layerA = bufferA.addMarkerLayer(persistent: true)
markerA = layerA.markPosition([0, 3])
notQuittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
notQuittingProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).toBeUndefined()
quittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
quittingProject.deserialize(atom.project.serialize({isUnloading: true}))
expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).not.toBeUndefined()
describe "when an editor is saved and the project has no path", ->
it "sets the project's path to the saved file's parent directory", ->
tempFile = temp.openSync().path
@@ -501,7 +526,7 @@ describe "Project", ->
expect(atom.project.getDirectories()[1].contains(inputPath)).toBe true
expect(atom.project.relativizePath(inputPath)).toEqual [
atom.project.getPaths()[1],
'somewhere/something.txt'
path.join('somewhere', 'something.txt')
]
describe ".contains(path)", ->

View File

@@ -17,7 +17,7 @@ describe "TextEditor", ->
buffer = new TextBuffer
editor = atom.workspace.buildTextEditor({buffer})
editor.setEditorWidthInChars(80)
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
tokenizedBuffer = editor.tokenizedBuffer
steps = []
times 30, ->
@@ -33,8 +33,8 @@ describe "TextEditor", ->
logLines()
throw new Error("Invalid buffer row #{actualBufferRow} for screen row #{screenRow}", )
actualScreenLine = editor.tokenizedLineForScreenRow(screenRow)
unless actualScreenLine.text is referenceScreenLine.text
actualScreenLine = editor.lineTextForScreenRow(screenRow)
unless actualScreenLine is referenceScreenLine
logLines()
throw new Error("Invalid line text at screen row #{screenRow}")
@@ -84,7 +84,8 @@ describe "TextEditor", ->
referenceEditor.setEditorWidthInChars(80)
referenceEditor.setText(editor.getText())
referenceEditor.setSoftWrapped(editor.isSoftWrapped())
screenLines = referenceEditor.tokenizedLinesForScreenRows(0, referenceEditor.getLastScreenRow())
screenLines = [0..referenceEditor.getLastScreenRow()].map (row) => referenceEditor.lineTextForScreenRow(row)
bufferRows = referenceEditor.bufferRowsForScreenRows(0, referenceEditor.getLastScreenRow())
{screenLines, bufferRows}

View File

@@ -83,3 +83,40 @@ describe "Selection", ->
selection.setBufferRange([[2, 0], [2, 10]])
selection.destroy()
expect(selection.marker.isDestroyed()).toBeTruthy()
describe ".insertText(text, options)", ->
it "allows pasting white space only lines when autoIndent is enabled", ->
selection.setBufferRange [[0, 0], [0, 0]]
selection.insertText(" \n \n\n", autoIndent: true)
expect(buffer.lineForRow(0)).toBe " "
expect(buffer.lineForRow(1)).toBe " "
expect(buffer.lineForRow(2)).toBe ""
it "auto-indents if only a newline is inserted", ->
selection.setBufferRange [[2, 0], [3, 0]]
selection.insertText("\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "
it "auto-indents if only a carriage return + newline is inserted", ->
selection.setBufferRange [[2, 0], [3, 0]]
selection.insertText("\r\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "
describe ".fold()", ->
it "folds the buffer range spanned by the selection", ->
selection.setBufferRange([[0, 3], [1, 6]])
selection.fold()
expect(selection.getScreenRange()).toEqual([[0, 4], [0, 4]])
expect(selection.getBufferRange()).toEqual([[1, 6], [1, 6]])
expect(editor.lineTextForScreenRow(0)).toBe "var#{editor.displayLayer.foldCharacter}sort = function(items) {"
expect(editor.isFoldedAtBufferRow(0)).toBe(true)
it "doesn't create a fold when the selection is empty", ->
selection.setBufferRange([[0, 3], [0, 3]])
selection.fold()
expect(selection.getScreenRange()).toEqual([[0, 3], [0, 3]])
expect(selection.getBufferRange()).toEqual([[0, 3], [0, 3]])
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {"
expect(editor.isFoldedAtBufferRow(0)).toBe(false)

57
spec/spawner-spec.coffee Normal file
View File

@@ -0,0 +1,57 @@
ChildProcess = require 'child_process'
Spawner = require '../src/browser/spawner'
describe "Spawner", ->
beforeEach ->
# Prevent any commands from actually running and affecting the host
originalSpawn = ChildProcess.spawn
harmlessSpawn =
# Just spawn something that won't actually modify the host
if process.platform is 'win32'
originalSpawn('dir')
else
originalSpawn('ls')
spyOn(ChildProcess, 'spawn').andCallFake (command, args, callback) ->
harmlessSpawn
it "invokes passed callback", ->
someCallback = jasmine.createSpy('someCallback')
Spawner.spawn('some-command', 'some-args', someCallback)
waitsFor ->
someCallback.callCount is 1
it "spawns passed command with arguments", ->
actualCommand = null
actualArgs = null
# Redefine fake invocation, so to remember passed arguments
jasmine.unspy(ChildProcess, 'spawn')
spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
actualCommand = command
actualArgs = args
harmlessSpawn
expectedCommand = 'some-command'
expectedArgs = 'some-args'
someCallback = jasmine.createSpy('someCallback')
Spawner.spawn(expectedCommand, expectedArgs, someCallback)
expect(actualCommand).toBe expectedCommand
expect(actualArgs).toBe expectedArgs
it "ignores errors by spawned process", ->
# Redefine fake invocation, so to cause an error
jasmine.unspy(ChildProcess, 'spawn')
spyOn(ChildProcess, 'spawn').andCallFake -> throw new Error("EBUSY")
someCallback = jasmine.createSpy('someCallback')
expect(Spawner.spawn('some-command', 'some-args', someCallback)).toBe undefined
waitsFor ->
someCallback.callCount is 1

View File

@@ -112,14 +112,14 @@ afterEach ->
document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent
ensureNoPathSubscriptions()
warnIfLeakingPathSubscriptions()
waits(0) # yield to ui thread to make screen update more frequently
ensureNoPathSubscriptions = ->
warnIfLeakingPathSubscriptions = ->
watchedPaths = pathwatcher.getWatchedPaths()
pathwatcher.closeAllWatchers()
if watchedPaths.length > 0
throw new Error("Leaking subscriptions for paths: " + watchedPaths.join(", "))
console.error("WARNING: Leaking subscriptions for paths: " + watchedPaths.join(", "))
pathwatcher.closeAllWatchers()
ensureNoDeprecatedFunctionsCalled = ->
deprecations = Grim.getDeprecations()
@@ -172,8 +172,8 @@ jasmine.useRealClock = ->
addCustomMatchers = (spec) ->
spec.addMatchers
toBeInstanceOf: (expected) ->
notText = if @isNot then " not" else ""
this.message = => "Expected #{jasmine.pp(@actual)} to#{notText} be instance of #{expected.name} class"
beOrNotBe = if @isNot then "not be" else "be"
this.message = => "Expected #{jasmine.pp(@actual)} to #{beOrNotBe} instance of #{expected.name} class"
@actual instanceof expected
toHaveLength: (expected) ->
@@ -181,32 +181,38 @@ addCustomMatchers = (spec) ->
this.message = => "Expected object #{@actual} has no length method"
false
else
notText = if @isNot then " not" else ""
this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
haveOrNotHave = if @isNot then "not have" else "have"
this.message = => "Expected object with length #{@actual.length} to #{haveOrNotHave} length #{expected}"
@actual.length is expected
toExistOnDisk: (expected) ->
notText = this.isNot and " not" or ""
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
toOrNotTo = this.isNot and "not to" or "to"
@message = -> return "Expected path '#{@actual}' #{toOrNotTo} exist."
fs.existsSync(@actual)
toHaveFocus: ->
notText = this.isNot and " not" or ""
toOrNotTo = this.isNot and "not to" or "to"
if not document.hasFocus()
console.error "Specs will fail because the Dev Tools have focus. To fix this close the Dev Tools or click the spec runner."
@message = -> return "Expected element '" + @actual + "' or its descendants" + notText + " to have focus."
@message = -> return "Expected element '#{@actual}' or its descendants #{toOrNotTo} have focus."
element = @actual
element = element.get(0) if element.jquery
element is document.activeElement or element.contains(document.activeElement)
toShow: ->
notText = if @isNot then " not" else ""
toOrNotTo = this.isNot and "not to" or "to"
element = @actual
element = element.get(0) if element.jquery
@message = -> return "Expected element '#{element}' or its descendants#{notText} to show."
@message = -> return "Expected element '#{element}' or its descendants #{toOrNotTo} show."
element.style.display in ['block', 'inline-block', 'static', 'fixed']
toEqualPath: (expected) ->
actualPath = path.normalize(@actual)
expectedPath = path.normalize(expected)
@message = -> return "Expected path '#{actualPath}' to be equal to '#{expectedPath}'."
actualPath is expectedPath
window.waitsForPromise = (args...) ->
label = null
if args.length > 1

View File

@@ -1,39 +1,37 @@
ChildProcess = require 'child_process'
{EventEmitter} = require 'events'
fs = require 'fs-plus'
path = require 'path'
temp = require 'temp'
SquirrelUpdate = require '../src/browser/squirrel-update'
Spawner = require '../src/browser/spawner'
WinPowerShell = require '../src/browser/win-powershell'
WinRegistry = require '../src/browser/win-registry'
describe "Windows squirrel updates", ->
# Run passed callback as Spawner.spawn() would do
invokeCallback = (callback) ->
error = null
stdout = ''
callback?(error, stdout)
describe "Windows Squirrel Update", ->
tempHomeDirectory = null
beforeEach ->
# Prevent the actually home directory from being manipulated
# Prevent the actual home directory from being manipulated
tempHomeDirectory = temp.mkdirSync('atom-temp-home-')
spyOn(fs, 'getHomeDirectory').andReturn(tempHomeDirectory)
# Prevent any commands from actually running and affecting the host
originalSpawn = ChildProcess.spawn
spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
if path.basename(command) is 'Update.exe' and args?[0] is '--createShortcut'
fs.writeFileSync(path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk'), '')
# Prevent any spawned command from actually running and affecting the host
spyOn(Spawner, 'spawn').andCallFake (command, args, callback) ->
# do nothing on command, just run passed callback
invokeCallback callback
# Just spawn something that won't actually modify the host
if process.platform is 'win32'
originalSpawn('dir')
else
originalSpawn('ls')
it "ignores errors spawning Squirrel", ->
jasmine.unspy(ChildProcess, 'spawn')
spyOn(ChildProcess, 'spawn').andCallFake -> throw new Error("EBUSY")
app = quit: jasmine.createSpy('quit')
expect(SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')).toBe true
waitsFor ->
app.quit.callCount is 1
# Prevent any actual change to Windows registry
for own method of WinRegistry
# all WinRegistry APIs share the same signature
spyOn(WinRegistry, method).andCallFake (callback) ->
# do nothing on registry, just run passed callback
invokeCallback callback
it "quits the app on all squirrel events", ->
app = quit: jasmine.createSpy('quit')
@@ -67,28 +65,56 @@ describe "Windows squirrel updates", ->
runs ->
expect(SquirrelUpdate.handleStartupEvent(app, '--not-squirrel')).toBe false
it "keeps the desktop shortcut deleted on updates if it was previously deleted after install", ->
desktopShortcutPath = path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk')
expect(fs.existsSync(desktopShortcutPath)).toBe false
describe "Desktop shortcut", ->
desktopShortcutPath = '/non/existing/path'
app = quit: jasmine.createSpy('quit')
expect(SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')).toBe true
beforeEach ->
desktopShortcutPath = path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk')
jasmine.unspy(Spawner, 'spawn')
spyOn(Spawner, 'spawn').andCallFake (command, args, callback) ->
if path.basename(command) is 'Update.exe' and args?[0] is '--createShortcut'
fs.writeFileSync(desktopShortcutPath, '')
else
# simply ignore other commands
invokeCallback callback
waitsFor ->
app.quit.callCount is 1
runs ->
app.quit.reset()
expect(fs.existsSync(desktopShortcutPath)).toBe true
fs.removeSync(desktopShortcutPath)
it "does not exist before install", ->
expect(fs.existsSync(desktopShortcutPath)).toBe false
expect(SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')).toBe true
waitsFor ->
app.quit.callCount is 1
describe "on install", ->
beforeEach ->
app = quit: jasmine.createSpy('quit')
SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')
waitsFor ->
app.quit.callCount is 1
runs ->
expect(fs.existsSync(desktopShortcutPath)).toBe false
it "creates desktop shortcut", ->
expect(fs.existsSync(desktopShortcutPath)).toBe true
describe "when shortcut is deleted and then app is updated", ->
beforeEach ->
fs.removeSync(desktopShortcutPath)
expect(fs.existsSync(desktopShortcutPath)).toBe false
app = quit: jasmine.createSpy('quit')
SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')
waitsFor ->
app.quit.callCount is 1
it "does not recreate shortcut", ->
expect(fs.existsSync(desktopShortcutPath)).toBe false
describe "when shortcut is kept and app is updated", ->
beforeEach ->
app = quit: jasmine.createSpy('quit')
SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')
waitsFor ->
app.quit.callCount is 1
it "still has desktop shortcut", ->
expect(fs.existsSync(desktopShortcutPath)).toBe true
describe ".restartAtom", ->
it "quits the app and spawns a new one", ->
@@ -98,7 +124,7 @@ describe "Windows squirrel updates", ->
SquirrelUpdate.restartAtom(app)
expect(app.quit.callCount).toBe 1
expect(ChildProcess.spawn.callCount).toBe 0
expect(Spawner.spawn.callCount).toBe 0
app.emit('will-quit')
expect(ChildProcess.spawn.callCount).toBe 1
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'atom.cmd'
expect(Spawner.spawn.callCount).toBe 1
expect(path.basename(Spawner.spawn.argsForCall[0][0])).toBe 'atom.cmd'

61
spec/state-store-spec.js Normal file
View File

@@ -0,0 +1,61 @@
/** @babel */
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
const StateStore = require('../src/state-store.js')
describe("StateStore", () => {
let databaseName = `test-database-${Date.now()}`
let version = 1
it("can save and load states", () => {
const store = new StateStore(databaseName, version)
return store.save('key', {foo:'bar'})
.then(() => store.load('key'))
.then((state) => {
expect(state).toEqual({foo:'bar'})
})
})
it("resolves with null when a non-existent key is loaded", () => {
const store = new StateStore(databaseName, version)
return store.load('no-such-key').then((value) => {
expect(value).toBeNull()
})
})
it("can clear the state object store", () => {
const store = new StateStore(databaseName, version)
return store.save('key', {foo:'bar'})
.then(() => store.count())
.then((count) =>
expect(count).toBe(1)
)
.then(() => store.clear())
.then(() => store.count())
.then((count) => {
expect(count).toBe(0)
})
})
describe("when there is an error reading from the database", () => {
it("rejects the promise returned by load", () => {
const store = new StateStore(databaseName, version)
const fakeErrorEvent = {target: {errorCode: "Something bad happened"}}
spyOn(IDBObjectStore.prototype, 'get').andCallFake((key) => {
let request = {}
process.nextTick(() => request.onerror(fakeErrorEvent))
return request
})
return store.load('nonexistentKey')
.then(() => {
throw new Error("Promise should have been rejected")
})
.catch((event) => {
expect(event).toBe(fakeErrorEvent)
})
})
})
})

View File

@@ -1,6 +1,6 @@
/** @babel */
import {it, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
import TextEditorElement from '../src/text-editor-element'
import _, {extend, flatten, last, toArray} from 'underscore-plus'
@@ -69,13 +69,12 @@ describe('TextEditorComponent', function () {
describe('line rendering', async function () {
function expectTileContainsRow (tileNode, screenRow, {top}) {
let lineNode = tileNode.querySelector('[data-screen-row="' + screenRow + '"]')
let tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
let text = editor.lineTextForScreenRow(screenRow)
expect(lineNode.offsetTop).toBe(top)
if (tokenizedLine.text === '') {
expect(lineNode.innerHTML).toBe('&nbsp;')
if (text === '') {
expect(lineNode.textContent).toBe(' ')
} else {
expect(lineNode.textContent).toBe(tokenizedLine.text)
expect(lineNode.textContent).toBe(text)
}
}
@@ -294,12 +293,12 @@ describe('TextEditorComponent', function () {
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.tokenizedLineForScreenRow(3).text)
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.lineTextForScreenRow(3))
buffer.delete([[0, 0], [3, 0]])
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.tokenizedLineForScreenRow(3).text)
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.lineTextForScreenRow(3))
})
it('updates the top position of lines when the line height changes', async function () {
@@ -361,9 +360,9 @@ describe('TextEditorComponent', function () {
}
})
it('renders an nbsp on empty lines when no line-ending character is defined', function () {
it('renders an placeholder space on empty lines when no line-ending character is defined', function () {
atom.config.set('editor.showInvisibles', false)
expect(component.lineNodeForScreenRow(10).textContent).toBe(NBSP)
expect(component.lineNodeForScreenRow(10).textContent).toBe(' ')
})
it('gives the lines and tiles divs the same background color as the editor to improve GPU performance', async function () {
@@ -429,13 +428,14 @@ describe('TextEditorComponent', function () {
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false)
})
it('keeps rebuilding lines when continuous reflow is on', function () {
it('keeps rebuilding lines when continuous reflow is on', async function () {
wrapperNode.setContinuousReflow(true)
let oldLineNode = componentNode.querySelector('.line')
let oldLineNode = componentNode.querySelectorAll('.line')[1]
waitsFor(function () {
return componentNode.querySelector('.line') !== oldLineNode
})
while (true) {
await nextViewUpdatePromise()
if (componentNode.querySelectorAll('.line')[1] !== oldLineNode) break
}
})
describe('when showInvisibles is enabled', function () {
@@ -484,7 +484,7 @@ describe('TextEditorComponent', function () {
it('displays newlines as their own token outside of the other tokens\' scopeDescriptor', async function () {
editor.setText('let\n')
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage type var js">let</span></span><span class="invisible-character">' + invisibles.eol + '</span>')
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage type var js">let</span><span class="invisible-character eol">' + invisibles.eol + '</span></span>')
})
it('displays trailing carriage returns using a visible, non-empty value', async function () {
@@ -497,20 +497,20 @@ describe('TextEditorComponent', function () {
expect(component.lineNodeForScreenRow(10).textContent).toBe(invisibles.eol)
})
it('renders an nbsp on empty lines when the line-ending character is an empty string', async function () {
it('renders a placeholder space on empty lines when the line-ending character is an empty string', async function () {
atom.config.set('editor.invisibles', {
eol: ''
})
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(10).textContent).toBe(NBSP)
expect(component.lineNodeForScreenRow(10).textContent).toBe(' ')
})
it('renders an nbsp on empty lines when the line-ending character is false', async function () {
it('renders an placeholder space on empty lines when the line-ending character is false', async function () {
atom.config.set('editor.invisibles', {
eol: false
})
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(10).textContent).toBe(NBSP)
expect(component.lineNodeForScreenRow(10).textContent).toBe(' ')
})
it('interleaves invisible line-ending characters with indent guides on empty lines', async function () {
@@ -518,24 +518,25 @@ describe('TextEditorComponent', function () {
await nextViewUpdatePromise()
editor.setTabLength(2)
editor.setTextInBufferRange([[10, 0], [11, 0]], '\r\n', {
normalizeLineEndings: false
})
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span></span>')
editor.setTabLength(3)
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span> </span>')
editor.setTabLength(1)
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span></span><span class="indent-guide"><span class="invisible-character">E</span></span>')
editor.setTextInBufferRange([[9, 0], [9, Infinity]], ' ')
editor.setTextInBufferRange([[11, 0], [11, Infinity]], ' ')
await nextViewUpdatePromise()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span></span><span class="invisible-character">E</span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
})
describe('when soft wrapping is enabled', function () {
@@ -550,8 +551,8 @@ describe('TextEditorComponent', function () {
})
it('does not show end of line invisibles at the end of wrapped lines', function () {
expect(component.lineNodeForScreenRow(0).textContent).toBe('a line that ')
expect(component.lineNodeForScreenRow(1).textContent).toBe('wraps' + invisibles.space + invisibles.eol)
expect(component.lineNodeForScreenRow(0).textContent).toBe('a line ')
expect(component.lineNodeForScreenRow(1).textContent).toBe('that wraps' + invisibles.space + invisibles.eol)
})
})
})
@@ -986,13 +987,14 @@ describe('TextEditorComponent', function () {
expect(component.lineNumberNodeForScreenRow(3) != null).toBe(true)
})
it('keeps rebuilding line numbers when continuous reflow is on', function () {
it('keeps rebuilding line numbers when continuous reflow is on', async function () {
wrapperNode.setContinuousReflow(true)
let oldLineNode = componentNode.querySelectorAll('.line-number')[1]
waitsFor(function () {
return componentNode.querySelectorAll('.line-number')[1] !== oldLineNode
})
while (true) {
await nextViewUpdatePromise()
if (componentNode.querySelectorAll('.line-number')[1] !== oldLineNode) break
}
})
describe('fold decorations', function () {
@@ -1051,7 +1053,7 @@ describe('TextEditorComponent', function () {
beforeEach(async function () {
editor.setSoftWrapped(true)
await nextViewUpdatePromise()
componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
componentNode.style.width = 20 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
await nextViewUpdatePromise()
})
@@ -1060,6 +1062,14 @@ describe('TextEditorComponent', function () {
expect(lineNumberHasClass(0, 'foldable')).toBe(true)
expect(lineNumberHasClass(1, 'foldable')).toBe(false)
})
it('does not add the folded class for soft-wrapped lines that contain a fold', async function () {
editor.foldBufferRange([[3, 19], [3, 21]])
await nextViewUpdatePromise()
expect(lineNumberHasClass(11, 'folded')).toBe(true)
expect(lineNumberHasClass(12, 'folded')).toBe(false)
})
})
})
@@ -1082,7 +1092,7 @@ describe('TextEditorComponent', function () {
component.destroy()
lineNumber = component.lineNumberNodeForScreenRow(1)
target = lineNumber.querySelector('.icon-right')
return target.dispatchEvent(buildClickEvent(target))
target.dispatchEvent(buildClickEvent(target))
})
})
@@ -1106,6 +1116,37 @@ describe('TextEditorComponent', function () {
expect(lineNumberHasClass(1, 'folded')).toBe(false)
})
it('unfolds all the free-form folds intersecting the buffer row when clicked', async function () {
expect(lineNumberHasClass(3, 'foldable')).toBe(false)
editor.foldBufferRange([[3, 4], [5, 4]])
editor.foldBufferRange([[5, 5], [8, 10]])
await nextViewUpdatePromise()
expect(lineNumberHasClass(3, 'folded')).toBe(true)
expect(lineNumberHasClass(5, 'folded')).toBe(false)
let lineNumber = component.lineNumberNodeForScreenRow(3)
let target = lineNumber.querySelector('.icon-right')
target.dispatchEvent(buildClickEvent(target))
await nextViewUpdatePromise()
expect(lineNumberHasClass(3, 'folded')).toBe(false)
expect(lineNumberHasClass(5, 'folded')).toBe(true)
editor.setSoftWrapped(true)
componentNode.style.width = 20 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
await nextViewUpdatePromise()
editor.foldBufferRange([[3, 19], [3, 21]]) // fold starting on a soft-wrapped portion of the line
await nextViewUpdatePromise()
expect(lineNumberHasClass(11, 'folded')).toBe(true)
lineNumber = component.lineNumberNodeForScreenRow(11)
target = lineNumber.querySelector('.icon-right')
target.dispatchEvent(buildClickEvent(target))
await nextViewUpdatePromise()
expect(lineNumberHasClass(11, 'folded')).toBe(false)
})
it('does not fold when the line number componentNode is clicked', function () {
let lineNumber = component.lineNumberNodeForScreenRow(1)
lineNumber.dispatchEvent(buildClickEvent(lineNumber))
@@ -1200,7 +1241,7 @@ describe('TextEditorComponent', function () {
let cursor = componentNode.querySelector('.cursor')
let cursorRect = cursor.getBoundingClientRect()
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').childNodes[2]
let range = document.createRange()
let range = document.createRange(cursorLocationTextNode)
range.setStart(cursorLocationTextNode, 0)
range.setEnd(cursorLocationTextNode, 1)
let rangeRect = range.getBoundingClientRect()
@@ -1208,6 +1249,17 @@ describe('TextEditorComponent', function () {
expect(cursorRect.width).toBeCloseTo(rangeRect.width, 0)
})
it('positions cursors after the fold-marker when a fold ends the line', async function () {
editor.foldBufferRow(0)
await nextViewUpdatePromise()
editor.setCursorScreenPosition([0, 30])
await nextViewUpdatePromise()
let cursorRect = componentNode.querySelector('.cursor').getBoundingClientRect()
let foldMarkerRect = componentNode.querySelector('.fold-marker').getBoundingClientRect()
expect(cursorRect.left).toBeCloseTo(foldMarkerRect.right, 0)
})
it('positions cursors correctly after character widths are changed via a stylesheet change', async function () {
atom.config.set('editor.fontFamily', 'sans-serif')
editor.setCursorScreenPosition([0, 16])
@@ -1475,7 +1527,7 @@ describe('TextEditorComponent', function () {
component.measureDimensions()
await nextViewUpdatePromise()
let marker2 = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]])
let marker2 = editor.markBufferRange([[9, 0], [9, 0]])
editor.decorateMarker(marker2, {
type: ['line-number', 'line'],
'class': 'b'
@@ -1840,17 +1892,22 @@ describe('TextEditorComponent', function () {
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
})
it('measures block decorations taking into account both top and bottom margins', async function () {
it('measures block decorations taking into account both top and bottom margins of the element and its children', async function () {
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
let child = document.createElement("div")
child.style.height = "7px"
child.style.width = "30px"
child.style.marginBottom = "20px"
item.appendChild(child)
atom.styles.addStyleSheet(
'atom-text-editor .decoration-1 { width: 30px; height: 30px; margin-top: 10px; margin-bottom: 5px; }',
'atom-text-editor .decoration-1 { width: 30px; margin-top: 10px; }',
{context: 'atom-text-editor'}
)
await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles
await nextAnimationFramePromise() // applies the changes
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 30 + 10 + 5 + "px")
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 10 + 7 + 20 + "px")
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
@@ -1882,7 +1939,7 @@ describe('TextEditorComponent', function () {
component.measureDimensions()
await nextViewUpdatePromise()
marker = editor.displayBuffer.markBufferRange([[9, 2], [9, 4]], {
marker = editor.markBufferRange([[9, 2], [9, 4]], {
invalidate: 'inside'
})
editor.decorateMarker(marker, {
@@ -2077,7 +2134,7 @@ describe('TextEditorComponent', function () {
describe('when the marker is empty', function () {
it('renders an overlay decoration when added and removes the overlay when the decoration is destroyed', async function () {
let marker = editor.displayBuffer.markBufferRange([[2, 13], [2, 13]], {
let marker = editor.markBufferRange([[2, 13], [2, 13]], {
invalidate: 'never'
})
let decoration = editor.decorateMarker(marker, {
@@ -2099,7 +2156,7 @@ describe('TextEditorComponent', function () {
})
it('renders the overlay element with the CSS class specified by the decoration', async function () {
let marker = editor.displayBuffer.markBufferRange([[2, 13], [2, 13]], {
let marker = editor.markBufferRange([[2, 13], [2, 13]], {
invalidate: 'never'
})
let decoration = editor.decorateMarker(marker, {
@@ -2120,7 +2177,7 @@ describe('TextEditorComponent', function () {
describe('when the marker is not empty', function () {
it('renders at the head of the marker by default', async function () {
let marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], {
let marker = editor.markBufferRange([[2, 5], [2, 10]], {
invalidate: 'never'
})
let decoration = editor.decorateMarker(marker, {
@@ -2151,10 +2208,11 @@ describe('TextEditorComponent', function () {
item.style.height = itemHeight + 'px'
wrapperNode.style.width = windowWidth + 'px'
wrapperNode.style.height = windowHeight + 'px'
atom.setWindowDimensions({
await atom.setWindowDimensions({
width: windowWidth,
height: windowHeight
})
component.measureDimensions()
component.measureWindowSize()
await nextViewUpdatePromise()
@@ -2165,7 +2223,7 @@ describe('TextEditorComponent', function () {
})
it('slides horizontally left when near the right edge on #win32 and #darwin', async function () {
let marker = editor.displayBuffer.markBufferRange([[0, 26], [0, 26]], {
let marker = editor.markBufferRange([[0, 26], [0, 26]], {
invalidate: 'never'
})
let decoration = editor.decorateMarker(marker, {
@@ -2747,20 +2805,60 @@ describe('TextEditorComponent', function () {
})
})
describe('when a line is folded', function () {
beforeEach(async function () {
editor.foldBufferRow(4)
describe('when a fold marker is clicked', function () {
function clickElementAtPosition (marker, position) {
linesNode.dispatchEvent(
buildMouseEvent('mousedown', clientCoordinatesForScreenPosition(position), {target: marker})
)
}
it('unfolds only the selected fold when other folds are on the same line', async function () {
editor.foldBufferRange([[4, 6], [4, 10]])
editor.foldBufferRange([[4, 15], [4, 20]])
await nextViewUpdatePromise()
let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(2)
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
clickElementAtPosition(foldMarkers[0], [4, 6])
await nextViewUpdatePromise()
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(1)
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
clickElementAtPosition(foldMarkers[0], [4, 15])
await nextViewUpdatePromise()
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(0)
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
})
describe('when the folded line\'s fold-marker is clicked', function () {
it('unfolds the buffer row', function () {
let target = component.lineNodeForScreenRow(4).querySelector('.fold-marker')
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8]), {
target: target
}))
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
})
it('unfolds only the selected fold when other folds are inside it', async function () {
editor.foldBufferRange([[4, 10], [4, 15]])
editor.foldBufferRange([[4, 4], [4, 5]])
editor.foldBufferRange([[4, 4], [4, 20]])
await nextViewUpdatePromise()
let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(1)
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
clickElementAtPosition(foldMarkers[0], [4, 4])
await nextViewUpdatePromise()
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(1)
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
clickElementAtPosition(foldMarkers[0], [4, 4])
await nextViewUpdatePromise()
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(1)
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
clickElementAtPosition(foldMarkers[0], [4, 10])
await nextViewUpdatePromise()
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(0)
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
})
})
@@ -3095,7 +3193,7 @@ describe('TextEditorComponent', function () {
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(11), {
shiftKey: true
}))
expect(editor.getSelectedScreenRange()).toEqual([[7, 4], [16, 0]])
expect(editor.getSelectedScreenRange()).toEqual([[7, 4], [17, 0]])
})
})
})
@@ -3169,7 +3267,7 @@ describe('TextEditorComponent', function () {
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(11), {
metaKey: true
}))
expect(editor.getSelectedScreenRanges()).toEqual([[[7, 4], [7, 6]], [[11, 4], [19, 0]]])
expect(editor.getSelectedScreenRanges()).toEqual([[[7, 4], [7, 6]], [[11, 4], [20, 0]]])
})
it('merges overlapping selections on mouseup', async function () {
@@ -3183,7 +3281,7 @@ describe('TextEditorComponent', function () {
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(5), {
metaKey: true
}))
expect(editor.getSelectedScreenRanges()).toEqual([[[5, 0], [19, 0]]])
expect(editor.getSelectedScreenRanges()).toEqual([[[5, 0], [20, 0]]])
})
})
})
@@ -3198,7 +3296,7 @@ describe('TextEditorComponent', function () {
}))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(11)))
await nextAnimationFramePromise()
expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [11, 14]])
expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [11, 5]])
})
})
@@ -3739,6 +3837,21 @@ describe('TextEditorComponent', function () {
return event
}
function buildKeydownEvent ({keyCode, target}) {
let event = new KeyboardEvent('keydown')
Object.defineProperty(event, 'keyCode', {
get: function () {
return keyCode
}
})
Object.defineProperty(event, 'target', {
get: function () {
return target
}
})
return event
}
let inputNode
beforeEach(function () {
@@ -3761,11 +3874,12 @@ describe('TextEditorComponent', function () {
expect(editor.lineTextForBufferRow(0)).toBe('xyvar quicksort = function () {')
})
it('replaces the last character if the length of the input\'s value does not increase, as occurs with the accented character menu', async function () {
componentNode.dispatchEvent(buildTextInputEvent({
data: 'u',
target: inputNode
}))
it('replaces the last character if a keypress event is bracketed by keydown events with matching keyCodes, which occurs when the accented character menu is shown', async function () {
componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode}))
componentNode.dispatchEvent(buildTextInputEvent({data: 'u', target: inputNode}))
componentNode.dispatchEvent(new KeyboardEvent('keypress'))
componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode}))
componentNode.dispatchEvent(new KeyboardEvent('keyup'))
await nextViewUpdatePromise()
expect(editor.lineTextForBufferRow(0)).toBe('uvar quicksort = function () {')
@@ -4000,15 +4114,33 @@ describe('TextEditorComponent', function () {
})
})
describe('when changing the font', async function () {
it('measures the default char, the korean char, the double width char and the half width char widths', async function () {
expect(editor.getDefaultCharWidth()).toBeCloseTo(12, 0)
describe('when decreasing the fontSize', async function () {
it('decreases the widths of the korean char, the double width char and the half width char', async function () {
originalDefaultCharWidth = editor.getDefaultCharWidth()
koreanDefaultCharWidth = editor.getKoreanCharWidth()
doubleWidthDefaultCharWidth = editor.getDoubleWidthCharWidth()
halfWidthDefaultCharWidth = editor.getHalfWidthCharWidth()
component.setFontSize(10)
await nextViewUpdatePromise()
expect(editor.getDefaultCharWidth()).toBeCloseTo(6, 0)
expect(editor.getKoreanCharWidth()).toBeCloseTo(9, 0)
expect(editor.getDoubleWidthCharWidth()).toBe(10)
expect(editor.getHalfWidthCharWidth()).toBe(5)
expect(editor.getDefaultCharWidth()).toBeLessThan(originalDefaultCharWidth)
expect(editor.getKoreanCharWidth()).toBeLessThan(koreanDefaultCharWidth)
expect(editor.getDoubleWidthCharWidth()).toBeLessThan(doubleWidthDefaultCharWidth)
expect(editor.getHalfWidthCharWidth()).toBeLessThan(halfWidthDefaultCharWidth)
})
})
describe('when increasing the fontSize', function() {
it('increases the widths of the korean char, the double width char and the half width char', async function () {
originalDefaultCharWidth = editor.getDefaultCharWidth()
koreanDefaultCharWidth = editor.getKoreanCharWidth()
doubleWidthDefaultCharWidth = editor.getDoubleWidthCharWidth()
halfWidthDefaultCharWidth = editor.getHalfWidthCharWidth()
component.setFontSize(25)
await nextViewUpdatePromise()
expect(editor.getDefaultCharWidth()).toBeGreaterThan(originalDefaultCharWidth)
expect(editor.getKoreanCharWidth()).toBeGreaterThan(koreanDefaultCharWidth)
expect(editor.getDoubleWidthCharWidth()).toBeGreaterThan(doubleWidthDefaultCharWidth)
expect(editor.getHalfWidthCharWidth()).toBeGreaterThan(halfWidthDefaultCharWidth)
})
})
@@ -4834,7 +4966,7 @@ describe('TextEditorComponent', function () {
it('pastes the previously selected text at the clicked location', async function () {
let clipboardWrittenTo = false
spyOn(require('ipc'), 'send').andCallFake(function (eventName, selectedText) {
spyOn(require('electron').ipcRenderer, 'send').andCallFake(function (eventName, selectedText) {
if (eventName === 'write-text-to-selection-clipboard') {
require('../src/safe-clipboard').writeText(selectedText, 'selection')
clipboardWrittenTo = true
@@ -4926,7 +5058,7 @@ describe('TextEditorComponent', function () {
function lineNumberForBufferRowHasClass (bufferRow, klass) {
let screenRow
screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
screenRow = editor.screenRowForBufferRow(bufferRow)
return component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
}

View File

@@ -91,11 +91,13 @@ describe "TextEditorPresenter", ->
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
waitsForStateToUpdate = (presenter, fn) ->
waitsFor "presenter state to update", 1000, (done) ->
fn?()
line = new Error().stack.split('\n')[2].split(':')[1]
waitsFor "presenter state to update at line #{line}", 1000, (done) ->
disposable = presenter.onDidUpdateState ->
disposable.dispose()
process.nextTick(done)
fn?()
tiledContentContract = (stateFn) ->
it "contains states for tiles that are visible on screen", ->
@@ -633,16 +635,28 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setExplicitHeight(500)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
expectStateUpdate presenter, -> presenter.setScrollTop(300)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
describe "scrollPastEnd", ->
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
expectStateUpdate presenter, -> presenter.setScrollTop(300)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
it "doesn't add the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true but the presenter is created with scrollPastEnd as false", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10, scrollPastEnd: false)
expectStateUpdate presenter, -> presenter.setScrollTop(300)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
describe ".scrollTop", ->
it "tracks the value of ::scrollTop", ->
@@ -1129,53 +1143,6 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setScrollLeft(-300)
expect(getState(presenter).content.scrollLeft).toBe 0
describe ".indentGuidesVisible", ->
it "is initialized based on the editor.showIndentGuide config setting", ->
presenter = buildPresenter()
expect(getState(presenter).content.indentGuidesVisible).toBe false
atom.config.set('editor.showIndentGuide', true)
presenter = buildPresenter()
expect(getState(presenter).content.indentGuidesVisible).toBe true
it "updates when the editor.showIndentGuide config setting changes", ->
presenter = buildPresenter()
expect(getState(presenter).content.indentGuidesVisible).toBe false
expectStateUpdate presenter, -> atom.config.set('editor.showIndentGuide', true)
expect(getState(presenter).content.indentGuidesVisible).toBe true
expectStateUpdate presenter, -> atom.config.set('editor.showIndentGuide', false)
expect(getState(presenter).content.indentGuidesVisible).toBe false
it "updates when the editor's grammar changes", ->
atom.config.set('editor.showIndentGuide', true, scopeSelector: ".source.js")
presenter = buildPresenter()
expect(getState(presenter).content.indentGuidesVisible).toBe false
stateUpdated = false
presenter.onDidUpdateState -> stateUpdated = true
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
expect(stateUpdated).toBe true
expect(getState(presenter).content.indentGuidesVisible).toBe true
expectStateUpdate presenter, -> editor.setGrammar(atom.grammars.selectGrammar('.txt'))
expect(getState(presenter).content.indentGuidesVisible).toBe false
it "is always false when the editor is mini", ->
atom.config.set('editor.showIndentGuide', true)
editor.setMini(true)
presenter = buildPresenter()
expect(getState(presenter).content.indentGuidesVisible).toBe false
editor.setMini(false)
expect(getState(presenter).content.indentGuidesVisible).toBe true
editor.setMini(true)
expect(getState(presenter).content.indentGuidesVisible).toBe false
describe ".backgroundColor", ->
it "is assigned to ::backgroundColor unless the editor is mini", ->
presenter = buildPresenter()
@@ -1215,9 +1182,19 @@ describe "TextEditorPresenter", ->
describe ".tiles", ->
lineStateForScreenRow = (presenter, row) ->
lineId = presenter.model.tokenizedLineForScreenRow(row).id
tileRow = presenter.tileForRow(row)
getState(presenter).content.tiles[tileRow]?.lines[lineId]
tilesState = getState(presenter).content.tiles
lineId = presenter.linesByScreenRow.get(row)?.id
tilesState[presenter.tileForRow(row)]?.lines[lineId]
tagsForCodes = (presenter, tagCodes) ->
openTags = []
closeTags = []
for tagCode in tagCodes when tagCode < 0 # skip text codes
if presenter.isOpenTagCode(tagCode)
openTags.push(presenter.tagForCode(tagCode))
else
closeTags.push(presenter.tagForCode(tagCode))
{openTags, closeTags}
tiledContentContract (presenter) -> getState(presenter).content
@@ -1227,73 +1204,12 @@ describe "TextEditorPresenter", ->
presenter.setExplicitHeight(3)
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
line3 = editor.tokenizedLineForScreenRow(3)
expectValues lineStateForScreenRow(presenter, 3), {
screenRow: 3
text: line3.text
tags: line3.tags
specialTokens: line3.specialTokens
firstNonWhitespaceIndex: line3.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line3.firstTrailingWhitespaceIndex
invisibles: line3.invisibles
}
line4 = editor.tokenizedLineForScreenRow(4)
expectValues lineStateForScreenRow(presenter, 4), {
screenRow: 4
text: line4.text
tags: line4.tags
specialTokens: line4.specialTokens
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
invisibles: line4.invisibles
}
line5 = editor.tokenizedLineForScreenRow(5)
expectValues lineStateForScreenRow(presenter, 5), {
screenRow: 5
text: line5.text
tags: line5.tags
specialTokens: line5.specialTokens
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
invisibles: line5.invisibles
}
line6 = editor.tokenizedLineForScreenRow(6)
expectValues lineStateForScreenRow(presenter, 6), {
screenRow: 6
text: line6.text
tags: line6.tags
specialTokens: line6.specialTokens
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
invisibles: line6.invisibles
}
line7 = editor.tokenizedLineForScreenRow(7)
expectValues lineStateForScreenRow(presenter, 7), {
screenRow: 7
text: line7.text
tags: line7.tags
specialTokens: line7.specialTokens
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
invisibles: line7.invisibles
}
line8 = editor.tokenizedLineForScreenRow(8)
expectValues lineStateForScreenRow(presenter, 8), {
screenRow: 8
text: line8.text
tags: line8.tags
specialTokens: line8.specialTokens
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
invisibles: line8.invisibles
}
expectValues lineStateForScreenRow(presenter, 3), {screenRow: 3, tagCodes: editor.screenLineForScreenRow(3).tagCodes}
expectValues lineStateForScreenRow(presenter, 4), {screenRow: 4, tagCodes: editor.screenLineForScreenRow(4).tagCodes}
expectValues lineStateForScreenRow(presenter, 5), {screenRow: 5, tagCodes: editor.screenLineForScreenRow(5).tagCodes}
expectValues lineStateForScreenRow(presenter, 6), {screenRow: 6, tagCodes: editor.screenLineForScreenRow(6).tagCodes}
expectValues lineStateForScreenRow(presenter, 7), {screenRow: 7, tagCodes: editor.screenLineForScreenRow(7).tagCodes}
expectValues lineStateForScreenRow(presenter, 8), {screenRow: 8, tagCodes: editor.screenLineForScreenRow(8).tagCodes}
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
it "updates when the editor's content changes", ->
@@ -1301,34 +1217,20 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n")
line1 = editor.tokenizedLineForScreenRow(1)
expectValues lineStateForScreenRow(presenter, 1), {
text: line1.text
tags: line1.tags
}
line2 = editor.tokenizedLineForScreenRow(2)
expectValues lineStateForScreenRow(presenter, 2), {
text: line2.text
tags: line2.tags
}
line3 = editor.tokenizedLineForScreenRow(3)
expectValues lineStateForScreenRow(presenter, 3), {
text: line3.text
tags: line3.tags
}
expectValues lineStateForScreenRow(presenter, 1), {screenRow: 1, tagCodes: editor.screenLineForScreenRow(1).tagCodes}
expectValues lineStateForScreenRow(presenter, 2), {screenRow: 2, tagCodes: editor.screenLineForScreenRow(2).tagCodes}
expectValues lineStateForScreenRow(presenter, 3), {screenRow: 3, tagCodes: editor.screenLineForScreenRow(3).tagCodes}
it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", ->
editor.setText("hello\nworld\r\n")
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10)
expect(lineStateForScreenRow(presenter, 0).endOfLineInvisibles).toBeNull()
expect(lineStateForScreenRow(presenter, 1).endOfLineInvisibles).toBeNull()
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 0).tagCodes).openTags).not.toContain('invisible-character eol')
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 1).tagCodes).openTags).not.toContain('invisible-character eol')
atom.config.set('editor.showInvisibles', true)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10)
expect(lineStateForScreenRow(presenter, 0).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.eol')]
expect(lineStateForScreenRow(presenter, 1).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.cr'), atom.config.get('editor.invisibles.eol')]
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 0).tagCodes).openTags).toContain('invisible-character eol')
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 1).tagCodes).openTags).toContain('invisible-character eol')
describe ".blockDecorations", ->
it "contains all block decorations that are present before/after a line, both initially and when decorations change", ->
@@ -1336,9 +1238,11 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter()
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
blockDecoration4 = null
waitsForStateToUpdate presenter, ->
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
waitsForStateToUpdate presenter
runs ->
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
@@ -1472,9 +1376,9 @@ describe "TextEditorPresenter", ->
decoration1 = editor.decorateMarker(marker1, type: 'line', class: 'a')
presenter = buildPresenter()
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
decoration2 = null
waitsForStateToUpdate presenter
waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
runs ->
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
@@ -2150,31 +2054,40 @@ describe "TextEditorPresenter", ->
}
# becoming empty
waitsForStateToUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
runs ->
editor.getSelections()[1].clear(autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectUndefinedStateForSelection(presenter, 1)
# becoming non-empty
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
runs ->
editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectValues stateForSelectionInTile(presenter, 1, 2), {
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
# moving out of view
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
runs ->
editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectUndefinedStateForSelection(presenter, 1)
# adding
waitsForStateToUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
runs -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectValues stateForSelectionInTile(presenter, 2, 0), {
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
}
# moving added selection
waitsForStateToUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
runs ->
editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
waitsForStateToUpdate presenter
destroyedSelection = null
runs ->
@@ -2208,8 +2121,9 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
marker = editor.markBufferPosition([2, 2])
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
highlight = null
waitsForStateToUpdate presenter, ->
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
marker.setBufferRange([[2, 2], [5, 2]])
highlight.flash('b', 500)
runs ->
@@ -2879,12 +2793,9 @@ describe "TextEditorPresenter", ->
describe ".content.tiles", ->
lineNumberStateForScreenRow = (presenter, screenRow) ->
editor = presenter.model
tileRow = presenter.tileForRow(screenRow)
line = editor.tokenizedLineForScreenRow(screenRow)
gutterState = getLineNumberGutterState(presenter)
gutterState.content.tiles[tileRow]?.lineNumbers[line?.id]
tilesState = getLineNumberGutterState(presenter).content.tiles
line = presenter.linesByScreenRow.get(screenRow)
tilesState[presenter.tileForRow(screenRow)]?.lineNumbers[line?.id]
tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content
@@ -2893,17 +2804,21 @@ describe "TextEditorPresenter", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 2)
editor.setEditorWidthInChars(51)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 3)
presenter.setScreenRowsToMeasure([9, 11])
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false}
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true}
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false}
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 8), {screenRow: 8, bufferRow: 8, softWrapped: true}
expect(lineNumberStateForScreenRow(presenter, 9)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 10)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 11)).toBeUndefined()
expect(lineNumberStateForScreenRow(presenter, 12)).toBeUndefined()
it "updates when the editor's content changes", ->
editor.foldBufferRow(4)
@@ -2969,9 +2884,8 @@ describe "TextEditorPresenter", ->
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 35)
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40)
presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50)
presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
waitsForStateToUpdate presenter
waitsForStateToUpdate presenter, -> presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
runs ->
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(10)
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
@@ -3159,6 +3073,29 @@ describe "TextEditorPresenter", ->
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
describe "when a fold spans a single soft-wrapped buffer row", ->
it "applies the 'folded' decoration only to its initial screen row", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(20)
editor.foldBufferRange([[0, 20], [0, 22]])
editor.foldBufferRange([[0, 10], [0, 14]])
presenter = buildPresenter(explicitHeight: 35, scrollTop: 0, tileSize: 2)
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain('folded')
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
describe "when a fold is at the end of a soft-wrapped buffer row", ->
it "applies the 'folded' decoration only to its initial screen row", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(25)
editor.foldBufferRow(1)
presenter = buildPresenter(explicitHeight: 35, scrollTop: 0, tileSize: 2)
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toContain('folded')
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
describe ".foldable", ->
it "marks line numbers at the start of a foldable region as foldable", ->
presenter = buildPresenter()
@@ -3460,9 +3397,9 @@ describe "TextEditorPresenter", ->
gutterName: 'test-gutter-2'
class: 'test-class'
marker4 = editor.markBufferRange([[0, 0], [1, 0]])
decoration4 = editor.decorateMarker(marker4, decorationParams)
decoration4 = null
waitsForStateToUpdate presenter
waitsForStateToUpdate presenter, -> decoration4 = editor.decorateMarker(marker4, decorationParams)
runs ->
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})

View File

@@ -0,0 +1,49 @@
TextEditorRegistry = require '../src/text-editor-registry'
describe "TextEditorRegistry", ->
[registry, editor] = []
beforeEach ->
registry = new TextEditorRegistry
describe "when a TextEditor is added", ->
it "gets added to the list of registered editors", ->
editor = {}
registry.add(editor)
expect(editor.registered).toBe true
expect(registry.editors.size).toBe 1
expect(registry.editors.has(editor)).toBe(true)
it "returns a Disposable that can unregister the editor", ->
editor = {}
disposable = registry.add(editor)
expect(registry.editors.size).toBe 1
disposable.dispose()
expect(registry.editors.size).toBe 0
expect(editor.registered).toBe false
it "can be removed", ->
editor = {}
registry.add(editor)
expect(registry.editors.size).toBe 1
success = registry.remove(editor)
expect(success).toBe true
expect(registry.editors.size).toBe 0
expect(editor.registered).toBe false
describe "when the registry is observed", ->
it "calls the callback for current and future editors until unsubscribed", ->
[editor1, editor2, editor3] = [{}, {}, {}]
registry.add(editor1)
subscription = registry.observe spy = jasmine.createSpy()
expect(spy.calls.length).toBe 1
registry.add(editor2)
expect(spy.calls.length).toBe 2
expect(spy.argsForCall[0][0]).toBe editor1
expect(spy.argsForCall[1][0]).toBe editor2
subscription.dispose()
registry.add(editor3)
expect(spy.calls.length).toBe 2

View File

@@ -39,31 +39,19 @@ describe "TextEditor", ->
it "preserves the invisibles setting", ->
atom.config.set('editor.showInvisibles', true)
previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles
previousLineText = editor.lineTextForScreenRow(0)
editor2 = TextEditor.deserialize(editor.serialize(), atom)
expect(previousInvisibles).toBeDefined()
expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles
expect(editor2.lineTextForScreenRow(0)).toBe(previousLineText)
it "updates invisibles if the settings have changed between serialization and deserialization", ->
atom.config.set('editor.showInvisibles', true)
previousLineText = editor.lineTextForScreenRow(0)
state = editor.serialize()
atom.config.set('editor.invisibles', eol: '?')
editor2 = TextEditor.deserialize(state, atom)
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
it "restores pending tabs in pending state", ->
expect(editor.isPending()).toBe false
editor2 = TextEditor.deserialize(editor.serialize(), atom)
expect(editor2.isPending()).toBe false
pendingEditor = atom.workspace.buildTextEditor(pending: true)
expect(pendingEditor.isPending()).toBe true
editor3 = TextEditor.deserialize(pendingEditor.serialize(), atom)
expect(editor3.isPending()).toBe true
expect(editor2.lineTextForScreenRow(0)).not.toBe(previousLineText)
expect(editor2.lineTextForScreenRow(0).endsWith('?')).toBe(true)
describe "when the editor is constructed with the largeFileMode option set to true", ->
it "loads the editor but doesn't tokenize", ->
@@ -74,15 +62,14 @@ describe "TextEditor", ->
runs ->
buffer = editor.getBuffer()
expect(editor.tokenizedLineForScreenRow(0).text).toBe buffer.lineForRow(0)
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
expect(editor.tokenizedLineForScreenRow(1).tokens.length).toBe 2 # soft tab
expect(editor.tokenizedLineForScreenRow(12).text).toBe buffer.lineForRow(12)
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
expect(editor.lineTextForScreenRow(0)).toBe buffer.lineForRow(0)
expect(editor.tokensForScreenRow(0).length).toBe 1
expect(editor.tokensForScreenRow(1).length).toBe 2 # soft tab
expect(editor.lineTextForScreenRow(12)).toBe buffer.lineForRow(12)
expect(editor.getCursorScreenPosition()).toEqual [0, 0]
editor.insertText('hey"')
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
expect(editor.tokenizedLineForScreenRow(1).tokens.length).toBe 2 # sof tab
expect(editor.tokensForScreenRow(0).length).toBe 1
expect(editor.tokensForScreenRow(1).length).toBe 2 # soft tab
describe ".copy()", ->
it "returns a different edit session with the same initial state", ->
@@ -192,17 +179,19 @@ describe "TextEditor", ->
expect(editor1.getLongTitle()).toBe "readme \u2014 sample-theme-1"
expect(editor2.getLongTitle()).toBe "readme \u2014 sample-theme-2"
it "returns '<filename> — <parent-directories>' when opened files have identical file and dir names", ->
it "returns '<filename> — <parent-directories>' when opened files have identical file names in subdirectories", ->
editor1 = null
editor2 = null
path1 = path.join('sample-theme-1', 'src', 'js')
path2 = path.join('sample-theme-2', 'src', 'js')
waitsForPromise ->
atom.workspace.open(path.join('sample-theme-1', 'src', 'js', 'main.js')).then (o) ->
atom.workspace.open(path.join(path1, 'main.js')).then (o) ->
editor1 = o
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) ->
atom.workspace.open(path.join(path2, 'main.js')).then (o) ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe "main.js \u2014 sample-theme-1/src/js"
expect(editor2.getLongTitle()).toBe "main.js \u2014 sample-theme-2/src/js"
expect(editor1.getLongTitle()).toBe "main.js \u2014 #{path1}"
expect(editor2.getLongTitle()).toBe "main.js \u2014 #{path2}"
it "returns '<filename> — <parent-directories>' when opened files have identical file and same parent dir name", ->
editor1 = null
@@ -214,7 +203,7 @@ describe "TextEditor", ->
editor2 = o
runs ->
expect(editor1.getLongTitle()).toBe "main.js \u2014 js"
expect(editor2.getLongTitle()).toBe "main.js \u2014 js/plugin"
expect(editor2.getLongTitle()).toBe "main.js \u2014 " + path.join('js', 'plugin')
it "notifies ::onDidChangeTitle observers when the underlying buffer path changes", ->
observed = []
@@ -322,7 +311,7 @@ describe "TextEditor", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
editor.createFold(2, 3)
editor.foldBufferRowRange(2, 3)
it "positions the cursor at the buffer position that corresponds to the given screen position", ->
editor.setCursorScreenPosition([9, 0])
@@ -503,7 +492,7 @@ describe "TextEditor", ->
it "wraps to the end of the previous line", ->
editor.setCursorScreenPosition([4, 4])
editor.moveLeft()
expect(editor.getCursorScreenPosition()).toEqual [3, 50]
expect(editor.getCursorScreenPosition()).toEqual [3, 46]
describe "when the cursor is on the first line", ->
it "remains in the same position (0,0)", ->
@@ -691,7 +680,7 @@ describe "TextEditor", ->
editor.setCursorScreenPosition([0, 2])
editor.moveToEndOfLine()
cursor = editor.getLastCursor()
expect(cursor.getScreenPosition()).toEqual [3, 4]
expect(cursor.getScreenPosition()).toEqual [4, 4]
describe ".moveToFirstCharacterOfLine()", ->
describe "when soft wrap is on", ->
@@ -1197,14 +1186,10 @@ describe "TextEditor", ->
cursor2 = editor.addCursorAtBufferPosition([1, 4])
expect(cursor2.marker).toBe cursor1.marker
describe '.logCursorScope()', ->
beforeEach ->
spyOn(atom.notifications, 'addInfo')
it 'opens a notification', ->
editor.logCursorScope()
expect(atom.notifications.addInfo).toHaveBeenCalled()
describe '.getCursorScope()', ->
it 'returns the current scope', ->
descriptor = editor.getCursorScope()
expect(descriptor.scopes).toContain('source.js')
describe "selection", ->
selection = null
@@ -1810,22 +1795,22 @@ describe "TextEditor", ->
describe "when the 'preserveFolds' option is false (the default)", ->
it "removes folds that contain the selections", ->
editor.setSelectedBufferRange([[0, 0], [0, 0]])
editor.createFold(1, 4)
editor.createFold(2, 3)
editor.createFold(6, 8)
editor.createFold(10, 11)
editor.foldBufferRowRange(1, 4)
editor.foldBufferRowRange(2, 3)
editor.foldBufferRowRange(6, 8)
editor.foldBufferRowRange(10, 11)
editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]])
expect(editor.tokenizedLineForScreenRow(1).fold).toBeUndefined()
expect(editor.tokenizedLineForScreenRow(2).fold).toBeUndefined()
expect(editor.tokenizedLineForScreenRow(6).fold).toBeUndefined()
expect(editor.tokenizedLineForScreenRow(10).fold).toBeDefined()
expect(editor.isFoldedAtScreenRow(1)).toBeFalsy()
expect(editor.isFoldedAtScreenRow(2)).toBeFalsy()
expect(editor.isFoldedAtScreenRow(6)).toBeFalsy()
expect(editor.isFoldedAtScreenRow(10)).toBeTruthy()
describe "when the 'preserveFolds' option is true", ->
it "does not remove folds that contain the selections", ->
editor.setSelectedBufferRange([[0, 0], [0, 0]])
editor.createFold(1, 4)
editor.createFold(6, 8)
editor.foldBufferRowRange(1, 4)
editor.foldBufferRowRange(6, 8)
editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 0], [6, 1]]], preserveFolds: true)
expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
@@ -2142,20 +2127,31 @@ describe "TextEditor", ->
editor.splitSelectionsIntoLines()
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
describe ".consolidateSelections()", ->
it "destroys all selections but the least recent, returning true if any selections were destroyed", ->
editor.setSelectedBufferRange([[3, 16], [3, 21]])
selection1 = editor.getLastSelection()
describe "::consolidateSelections()", ->
makeMultipleSelections = ->
selection.setBufferRange [[3, 16], [3, 21]]
selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]])
expect(editor.getSelections()).toEqual [selection, selection2, selection3, selection4]
[selection, selection2, selection3, selection4]
it "destroys all selections but the oldest selection and autoscrolls to it, returning true if any selections were destroyed", ->
[selection1] = makeMultipleSelections()
autoscrollEvents = []
editor.onDidRequestAutoscroll (event) -> autoscrollEvents.push(event)
expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
expect(editor.consolidateSelections()).toBeTruthy()
expect(editor.getSelections()).toEqual [selection1]
expect(selection1.isEmpty()).toBeFalsy()
expect(editor.consolidateSelections()).toBeFalsy()
expect(editor.getSelections()).toEqual [selection1]
expect(autoscrollEvents).toEqual([
{screenRange: selection1.getScreenRange(), options: {center: true, reversed: false}}
])
describe "when the cursor is moved while there is a selection", ->
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
@@ -2226,7 +2222,7 @@ describe "TextEditor", ->
it "moves the line to the previous row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRange([[4, 2], [4, 9]], preserveFolds: true)
expect(editor.getSelectedBufferRange()).toEqual [[4, 2], [4, 9]]
@@ -2254,7 +2250,7 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
expect(editor.lineTextForBufferRow(9)).toBe " };"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
@@ -2292,7 +2288,7 @@ describe "TextEditor", ->
it "moves the lines to the previous row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRange([[3, 2], [4, 9]], preserveFolds: true)
expect(editor.isFoldedAtBufferRow(3)).toBeFalsy()
@@ -2320,7 +2316,7 @@ describe "TextEditor", ->
it "moves the lines to the previous row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRange([[4, 2], [8, 9]], preserveFolds: true)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
@@ -2364,7 +2360,7 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
expect(editor.lineTextForBufferRow(9)).toBe " };"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
@@ -2404,7 +2400,7 @@ describe "TextEditor", ->
it "moves the lines to the previous row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRanges([
[[2, 2], [2, 9]],
[[4, 2], [4, 9]]
@@ -2442,7 +2438,7 @@ describe "TextEditor", ->
describe "when there is a fold", ->
it "moves all lines that spanned by a selection to preceding row, preserving all folds", ->
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
@@ -2469,8 +2465,8 @@ describe "TextEditor", ->
describe 'and many selections intersects folded rows', ->
it 'moves and preserves all the folds', ->
editor.createFold(2, 4)
editor.createFold(7, 9)
editor.foldBufferRowRange(2, 4)
editor.foldBufferRowRange(7, 9)
editor.setSelectedBufferRanges([
[[1, 0], [5, 4]],
@@ -2554,7 +2550,7 @@ describe "TextEditor", ->
it "moves the line to the following row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRange([[4, 2], [4, 9]], preserveFolds: true)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
@@ -2580,7 +2576,7 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
@@ -2634,7 +2630,7 @@ describe "TextEditor", ->
it "moves the lines to the following row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRange([[3, 2], [4, 9]], preserveFolds: true)
expect(editor.isFoldedAtBufferRow(3)).toBeFalsy()
@@ -2662,7 +2658,7 @@ describe "TextEditor", ->
it "moves the lines to the following row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRange([[4, 2], [8, 9]], preserveFolds: true)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
@@ -2692,7 +2688,7 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(2)).toBe " if (items.length <= 1) return items;"
expect(editor.lineTextForBufferRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
@@ -2734,8 +2730,8 @@ describe "TextEditor", ->
describe 'and many selections intersects folded rows', ->
it 'moves and preserves all the folds', ->
editor.createFold(2, 4)
editor.createFold(7, 9)
editor.foldBufferRowRange(2, 4)
editor.foldBufferRowRange(7, 9)
editor.setSelectedBufferRanges([
[[2, 0], [2, 4]],
@@ -2764,7 +2760,7 @@ describe "TextEditor", ->
describe "when there is a fold below one of the selected row", ->
it "moves all lines spanned by a selection to the following row, preserving the fold", ->
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
@@ -2787,7 +2783,7 @@ describe "TextEditor", ->
describe "when there is a fold below a group of multiple selections without any lines with no selection in-between", ->
it "moves all the lines below the fold, preserving the fold", ->
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
@@ -2812,7 +2808,7 @@ describe "TextEditor", ->
it "moves the lines to the previous row without breaking the fold", ->
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
editor.createFold(4, 7)
editor.foldBufferRowRange(4, 7)
editor.setSelectedBufferRanges([
[[2, 2], [2, 9]],
[[4, 2], [4, 9]]
@@ -2879,6 +2875,13 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(1)).toBe "1"
expect(editor.lineTextForBufferRow(2)).toBe "2"
describe "when the line is the last buffer row", ->
it "doesn't move it", ->
editor.setText("abc\ndef")
editor.setCursorBufferPosition([1, 0])
editor.moveLineDown()
expect(editor.getText()).toBe("abc\ndef")
describe ".insertText(text)", ->
describe "when there is a single selection", ->
beforeEach ->
@@ -2950,10 +2953,10 @@ describe "TextEditor", ->
describe "when there is a selection that ends on a folded line", ->
it "destroys the selection", ->
editor.createFold(2, 4)
editor.foldBufferRowRange(2, 4)
editor.setSelectedBufferRange([[1, 0], [2, 0]])
editor.insertText('holy cow')
expect(editor.tokenizedLineForScreenRow(2).fold).toBeUndefined()
expect(editor.isFoldedAtScreenRow(2)).toBeFalsy()
describe "when there are ::onWillInsertText and ::onDidInsertText observers", ->
beforeEach ->
@@ -3175,7 +3178,7 @@ describe "TextEditor", ->
expect(editor.indentationForBufferRow(0)).toBe 1
expect(editor.indentationForBufferRow(1)).toBe 1
it "indents the new line to the correct level when editor.autoIndent is true and using a off-side rule language", ->
it "indents the new line to the correct level when editor.autoIndent is true and using an off-side rule language", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
@@ -3247,15 +3250,14 @@ describe "TextEditor", ->
editor.setCursorScreenPosition(row: 0, column: 0)
editor.backspace()
describe "when the cursor is on the first column of a line below a fold", ->
it "deletes the folded lines", ->
editor.setCursorScreenPosition([4, 0])
editor.foldCurrentRow()
editor.setCursorScreenPosition([5, 0])
describe "when the cursor is after a fold", ->
it "deletes the folded range", ->
editor.foldBufferRange([[4, 7], [5, 8]])
editor.setCursorBufferPosition([5, 8])
editor.backspace()
expect(buffer.lineForRow(4)).toBe " return sort(left).concat(pivot).concat(sort(right));"
expect(buffer.lineForRow(4).fold).toBeUndefined()
expect(buffer.lineForRow(4)).toBe " whirrent = items.shift();"
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
describe "when the cursor is in the middle of a line below a fold", ->
it "backspaces as normal", ->
@@ -3268,14 +3270,13 @@ describe "TextEditor", ->
expect(buffer.lineForRow(8)).toBe " eturn sort(left).concat(pivot).concat(sort(right));"
describe "when the cursor is on a folded screen line", ->
it "deletes all of the folded lines along with the fold", ->
it "deletes the contents of the fold before the cursor", ->
editor.setCursorBufferPosition([3, 0])
editor.foldCurrentRow()
editor.backspace()
expect(buffer.lineForRow(1)).toBe ""
expect(buffer.lineForRow(2)).toBe " return sort(Array.apply(this, arguments));"
expect(editor.getCursorScreenPosition()).toEqual [1, 0]
expect(buffer.lineForRow(1)).toBe " var sort = function(items) var pivot = items.shift(), current, left = [], right = [];"
expect(editor.getCursorScreenPosition()).toEqual [1, 29]
describe "when there are multiple cursors", ->
describe "when cursors are on the same line", ->
@@ -3342,7 +3343,7 @@ describe "TextEditor", ->
editor.backspace()
expect(buffer.lineForRow(3)).toBe " while(items.length > 0) {"
expect(editor.tokenizedLineForScreenRow(3).fold).toBeDefined()
expect(editor.isFoldedAtScreenRow(3)).toBe(true)
describe "when there are multiple selections", ->
it "removes all selected text", ->
@@ -3515,16 +3516,16 @@ describe "TextEditor", ->
editor.delete()
expect(buffer.lineForRow(12)).toBe '};'
describe "when the cursor is on the end of a line above a fold", ->
describe "when the cursor is before a fold", ->
it "only deletes the lines inside the fold", ->
editor.foldBufferRow(4)
editor.setCursorScreenPosition([3, Infinity])
editor.foldBufferRange([[3, 6], [4, 8]])
editor.setCursorScreenPosition([3, 6])
cursorPositionBefore = editor.getCursorScreenPosition()
editor.delete()
expect(buffer.lineForRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
expect(buffer.lineForRow(4)).toBe " return sort(left).concat(pivot).concat(sort(right));"
expect(buffer.lineForRow(3)).toBe " vae(items.length > 0) {"
expect(buffer.lineForRow(4)).toBe " current = items.shift();"
expect(editor.getCursorScreenPosition()).toEqual cursorPositionBefore
describe "when the cursor is in the middle a line above a fold", ->
@@ -3536,20 +3537,21 @@ describe "TextEditor", ->
editor.delete()
expect(buffer.lineForRow(3)).toBe " ar pivot = items.shift(), current, left = [], right = [];"
expect(editor.tokenizedLineForScreenRow(4).fold).toBeDefined()
expect(editor.isFoldedAtScreenRow(4)).toBe(true)
expect(editor.getCursorScreenPosition()).toEqual [3, 4]
describe "when the cursor is on a folded line", ->
it "removes the lines contained by the fold", ->
editor.setSelectedBufferRange([[2, 0], [2, 0]])
editor.createFold(2, 4)
editor.createFold(2, 6)
oldLine7 = buffer.lineForRow(7)
oldLine8 = buffer.lineForRow(8)
describe "when the cursor is inside a fold", ->
it "removes the folded content after the cursor", ->
editor.foldBufferRange([[2, 6], [6, 21]])
editor.setCursorBufferPosition([4, 9])
editor.delete()
expect(editor.tokenizedLineForScreenRow(2).text).toBe oldLine7
expect(editor.tokenizedLineForScreenRow(3).text).toBe oldLine8
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(buffer.lineForRow(4)).toBe ' while ? left.push(current) : right.push(current);'
expect(buffer.lineForRow(5)).toBe ' }'
expect(editor.getCursorBufferPosition()).toEqual [4, 9]
describe "when there are multiple cursors", ->
describe "when cursors are on the same line", ->
@@ -3806,10 +3808,10 @@ describe "TextEditor", ->
it "cuts up to the end of the line", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(10)
editor.setCursorScreenPosition([2, 2])
editor.setEditorWidthInChars(25)
editor.setCursorScreenPosition([2, 6])
editor.cutToEndOfLine()
expect(editor.tokenizedLineForScreenRow(2).text).toBe '= () {'
expect(editor.lineTextForScreenRow(2)).toBe ' var function(items) {'
describe "when soft wrap is off", ->
describe "when nothing is selected", ->
@@ -4560,11 +4562,142 @@ describe "TextEditor", ->
expect(cursor1.getBufferPosition()).toEqual [0, 0]
expect(cursor3.getBufferPosition()).toEqual [1, 2]
describe ".moveSelectionLeft()", ->
it "moves one active selection on one line one column to the left", ->
editor.setSelectedBufferRange [[0, 4], [0, 13]]
expect(editor.getSelectedText()).toBe 'quicksort'
editor.moveSelectionLeft()
expect(editor.getSelectedText()).toBe 'quicksort'
expect(editor.getSelectedBufferRange()).toEqual [[0, 3], [0, 12]]
it "moves multiple active selections on one line one column to the left", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 3], [0, 12]], [[0, 15], [0, 23]]]
it "moves multiple active selections on multiple lines one column to the left", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 3], [0, 12]], [[1, 5], [1, 9]]]
describe "when a selection is at the first column of a line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[1, 0], [1, 3]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe ' v'
editor.moveSelectionLeft()
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe ' v'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]], [[1, 0], [1, 3]]]
describe "when multiple selections are active on one line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[0, 4], [0, 13]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe 'quicksort'
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe 'quicksort'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]], [[0, 4], [0, 13]]]
describe ".moveSelectionRight()", ->
it "moves one active selection on one line one column to the right", ->
editor.setSelectedBufferRange [[0, 4], [0, 13]]
expect(editor.getSelectedText()).toBe 'quicksort'
editor.moveSelectionRight()
expect(editor.getSelectedText()).toBe 'quicksort'
expect(editor.getSelectedBufferRange()).toEqual [[0, 5], [0, 14]]
it "moves multiple active selections on one line one column to the right", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 5], [0, 14]], [[0, 17], [0, 25]]]
it "moves multiple active selections on multiple lines one column to the right", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 5], [0, 14]], [[1, 7], [1, 11]]]
describe "when a selection is at the last column of a line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[2, 34], [2, 40]], [[5, 22], [5, 30]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'items;'
expect(selections[1].getText()).toBe 'shift();'
editor.moveSelectionRight()
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'items;'
expect(selections[1].getText()).toBe 'shift();'
expect(editor.getSelectedBufferRanges()).toEqual [[[2, 34], [2, 40]], [[5, 22], [5, 30]]]
describe "when multiple selections are active on one line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[2, 27], [2, 33]], [[2, 34], [2, 40]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'return'
expect(selections[1].getText()).toBe 'items;'
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'return'
expect(selections[1].getText()).toBe 'items;'
expect(editor.getSelectedBufferRanges()).toEqual [[[2, 27], [2, 33]], [[2, 34], [2, 40]]]
describe 'reading text', ->
it '.lineTextForScreenRow(row)', ->
editor.foldBufferRow(4)
expect(editor.lineTextForScreenRow(5)).toEqual ' return sort(left).concat(pivot).concat(sort(right));'
expect(editor.lineTextForScreenRow(100)).not.toBeDefined()
expect(editor.lineTextForScreenRow(9)).toEqual '};'
expect(editor.lineTextForScreenRow(10)).toBeUndefined()
describe ".deleteLine()", ->
it "deletes the first line when the cursor is there", ->
@@ -4921,11 +5054,13 @@ describe "TextEditor", ->
it 'retokenizes when the tab length is updated via .setTabLength()', ->
expect(editor.getTabLength()).toBe 2
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 2
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
expect(leadingWhitespaceTokens.length).toBe(3)
editor.setTabLength(6)
expect(editor.getTabLength()).toBe 6
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 6
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
expect(leadingWhitespaceTokens.length).toBe(1)
changeHandler = jasmine.createSpy('changeHandler')
editor.onDidChange(changeHandler)
@@ -4934,21 +5069,25 @@ describe "TextEditor", ->
it 'retokenizes when the editor.tabLength setting is updated', ->
expect(editor.getTabLength()).toBe 2
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 2
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
expect(leadingWhitespaceTokens.length).toBe(3)
atom.config.set 'editor.tabLength', 6, scopeSelector: '.source.js'
expect(editor.getTabLength()).toBe 6
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 6
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
expect(leadingWhitespaceTokens.length).toBe(1)
it 'updates the tab length when the grammar changes', ->
atom.config.set 'editor.tabLength', 6, scopeSelector: '.source.coffee'
expect(editor.getTabLength()).toBe 2
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 2
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
expect(leadingWhitespaceTokens.length).toBe(3)
editor.setGrammar(coffeeEditor.getGrammar())
expect(editor.getTabLength()).toBe 6
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 6
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
expect(leadingWhitespaceTokens.length).toBe(1)
describe ".indentLevelForLine(line)", ->
it "returns the indent level when the line has only leading whitespace", ->
@@ -4984,11 +5123,11 @@ describe "TextEditor", ->
runs ->
expect(editor.getGrammar()).toBe atom.grammars.nullGrammar
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
expect(editor.tokensForScreenRow(0).length).toBe(1)
atom.grammars.addGrammar(jsGrammar)
expect(editor.getGrammar()).toBe jsGrammar
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBeGreaterThan 1
expect(editor.tokensForScreenRow(0).length).toBeGreaterThan 1
describe "editor.autoIndent", ->
describe "when editor.autoIndent is false (default)", ->
@@ -5024,7 +5163,7 @@ describe "TextEditor", ->
expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
describe "when the line preceding the newline does't add a level of indentation", ->
it "indents the new line to the same level a as the preceding line", ->
it "indents the new line to the same level as the preceding line", ->
editor.setCursorBufferPosition([5, 14])
editor.insertText('\n')
expect(editor.indentationForBufferRow(6)).toBe editor.indentationForBufferRow(5)
@@ -5129,10 +5268,34 @@ describe "TextEditor", ->
coffeeEditor.insertText("\n")
expect(coffeeEditor.lineTextForBufferRow(2)).toBe ""
describe "editor.atomicSoftTabs", ->
it "skips tab-length runs of leading whitespace when moving the cursor", ->
atom.config.set('editor.tabLength', 4)
atom.config.set('editor.atomicSoftTabs', true)
editor.setCursorScreenPosition([2, 3])
expect(editor.getCursorScreenPosition()).toEqual [2, 4]
atom.config.set('editor.atomicSoftTabs', false)
editor.setCursorScreenPosition([2, 3])
expect(editor.getCursorScreenPosition()).toEqual [2, 3]
atom.config.set('editor.atomicSoftTabs', true)
editor.setCursorScreenPosition([2, 3])
expect(editor.getCursorScreenPosition()).toEqual [2, 4]
atom.config.set('editor.atomicSoftTabs', false, scopeSelector: '.source.foo')
editor.setCursorScreenPosition([2, 3])
expect(editor.getCursorScreenPosition()).toEqual [2, 4]
atom.config.set('editor.atomicSoftTabs', false, scopeSelector: '.source.js')
editor.setCursorScreenPosition([2, 3])
expect(editor.getCursorScreenPosition()).toEqual [2, 3]
describe ".destroy()", ->
it "destroys marker layers associated with the text editor", ->
selectionsMarkerLayerId = editor.selectionsMarkerLayer.id
foldsMarkerLayerId = editor.displayBuffer.foldsMarkerLayer.id
foldsMarkerLayerId = editor.displayLayer.foldsMarkerLayer.id
editor.destroy()
expect(buffer.getMarkerLayer(selectionsMarkerLayerId)).toBeUndefined()
expect(buffer.getMarkerLayer(foldsMarkerLayerId)).toBeUndefined()
@@ -5216,10 +5379,10 @@ describe "TextEditor", ->
expect(editor.getSelectedBufferRanges()).toEqual [[[3, 5], [3, 5]], [[9, 0], [14, 0]]]
# folds are also duplicated
expect(editor.tokenizedLineForScreenRow(5).fold).toBeDefined()
expect(editor.tokenizedLineForScreenRow(7).fold).toBeDefined()
expect(editor.tokenizedLineForScreenRow(7).text).toBe " while(items.length > 0) {"
expect(editor.tokenizedLineForScreenRow(8).text).toBe " return sort(left).concat(pivot).concat(sort(right));"
expect(editor.isFoldedAtScreenRow(5)).toBe(true)
expect(editor.isFoldedAtScreenRow(7)).toBe(true)
expect(editor.lineTextForScreenRow(7)).toBe " while(items.length > 0) {" + editor.displayLayer.foldCharacter
expect(editor.lineTextForScreenRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
it "duplicates all folded lines for empty selections on folded lines", ->
editor.foldBufferRow(4)
@@ -5415,17 +5578,15 @@ describe "TextEditor", ->
runs ->
editor.setText("// http://github.com")
{tokens} = editor.tokenizedLineForScreenRow(0)
expect(tokens[1].value).toBe " http://github.com"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']
waitsForPromise ->
atom.packages.activatePackage('language-hyperlink')
runs ->
{tokens} = editor.tokenizedLineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js', 'markup.underline.link.http.hyperlink']
describe "when the grammar is updated", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
@@ -5435,25 +5596,22 @@ describe "TextEditor", ->
runs ->
editor.setText("// SELECT * FROM OCTOCATS")
{tokens} = editor.tokenizedLineForScreenRow(0)
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']
waitsForPromise ->
atom.packages.activatePackage('package-with-injection-selector')
runs ->
{tokens} = editor.tokenizedLineForScreenRow(0)
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']
waitsForPromise ->
atom.packages.activatePackage('language-sql')
runs ->
{tokens} = editor.tokenizedLineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js', 'keyword.other.DML.sql', 'keyword.operator.star.sql', 'keyword.other.DML.sql']
describe ".normalizeTabsInBufferRange()", ->
it "normalizes tabs depending on the editor's soft tab/tab length settings", ->
@@ -5581,6 +5739,19 @@ describe "TextEditor", ->
expect(editor.getFirstVisibleScreenRow()).toEqual 89
expect(editor.getVisibleRowRange()).toEqual [89, 99]
describe "::scrollToScreenPosition(position, [options])", ->
it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", ->
scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll")
editor.onDidRequestAutoscroll(scrollSpy)
editor.scrollToScreenPosition([8, 20])
editor.scrollToScreenPosition([8, 20], center: true)
editor.scrollToScreenPosition([8, 20], center: false, reversed: true)
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {})
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true})
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true})
describe '.get/setPlaceholderText()', ->
it 'can be created with placeholderText', ->
newEditor = atom.workspace.buildTextEditor(
@@ -5598,28 +5769,6 @@ describe "TextEditor", ->
expect(handler).toHaveBeenCalledWith 'OK'
expect(editor.getPlaceholderText()).toBe 'OK'
describe ".checkoutHeadRevision()", ->
it "reverts to the version of its file checked into the project repository", ->
atom.config.set("editor.confirmCheckoutHeadRevision", false)
editor.setCursorBufferPosition([0, 0])
editor.insertText("---\n")
expect(editor.lineTextForBufferRow(0)).toBe "---"
waitsForPromise ->
editor.checkoutHeadRevision()
runs ->
expect(editor.lineTextForBufferRow(0)).toBe "var quicksort = function () {"
describe "when there's no repository for the editor's file", ->
it "doesn't do anything", ->
editor = atom.workspace.buildTextEditor()
editor.setText("stuff")
editor.checkoutHeadRevision()
waitsForPromise -> editor.checkoutHeadRevision()
describe 'gutters', ->
describe 'the TextEditor constructor', ->
it 'creates a line-number gutter', ->
@@ -5748,6 +5897,7 @@ describe "TextEditor", ->
expect(editor.decorationsStateForScreenRowRange(0, 5)[decoration.id]).toEqual {
properties: {type: 'highlight', class: 'foo'}
screenRange: marker.getScreenRange(),
bufferRange: marker.getBufferRange(),
rangeIsReversed: false
}
@@ -5768,26 +5918,31 @@ describe "TextEditor", ->
expect(decorationState["#{layer1Decoration1.id}-#{marker1.id}"]).toEqual {
properties: {type: 'highlight', class: 'foo'},
screenRange: marker1.getRange(),
bufferRange: marker1.getRange(),
rangeIsReversed: false
}
expect(decorationState["#{layer1Decoration1.id}-#{marker2.id}"]).toEqual {
properties: {type: 'highlight', class: 'foo'},
screenRange: marker2.getRange(),
bufferRange: marker2.getRange(),
rangeIsReversed: false
}
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
properties: {type: 'highlight', class: 'bar'},
screenRange: marker1.getRange(),
bufferRange: marker1.getRange(),
rangeIsReversed: false
}
expect(decorationState["#{layer1Decoration2.id}-#{marker2.id}"]).toEqual {
properties: {type: 'highlight', class: 'bar'},
screenRange: marker2.getRange(),
bufferRange: marker2.getRange(),
rangeIsReversed: false
}
expect(decorationState["#{layer2Decoration.id}-#{marker3.id}"]).toEqual {
properties: {type: 'highlight', class: 'baz'},
screenRange: marker3.getRange(),
bufferRange: marker3.getRange(),
rangeIsReversed: false
}
@@ -5799,16 +5954,19 @@ describe "TextEditor", ->
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
properties: {type: 'highlight', class: 'bar'},
screenRange: marker1.getRange(),
bufferRange: marker1.getRange(),
rangeIsReversed: false
}
expect(decorationState["#{layer1Decoration2.id}-#{marker2.id}"]).toEqual {
properties: {type: 'highlight', class: 'bar'},
screenRange: marker2.getRange(),
bufferRange: marker2.getRange(),
rangeIsReversed: false
}
expect(decorationState["#{layer2Decoration.id}-#{marker3.id}"]).toEqual {
properties: {type: 'highlight', class: 'baz'},
screenRange: marker3.getRange(),
bufferRange: marker3.getRange(),
rangeIsReversed: false
}
@@ -5817,6 +5975,7 @@ describe "TextEditor", ->
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
properties: {type: 'highlight', class: 'quux'},
screenRange: marker1.getRange(),
bufferRange: marker1.getRange(),
rangeIsReversed: false
}
@@ -5825,55 +5984,46 @@ describe "TextEditor", ->
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
properties: {type: 'highlight', class: 'bar'},
screenRange: marker1.getRange(),
bufferRange: marker1.getRange(),
rangeIsReversed: false
}
describe "pending state", ->
editor1 = null
eventCount = null
describe "when the editor is constructed with the showInvisibles option set to false", ->
beforeEach ->
atom.workspace.destroyActivePane()
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) -> editor1 = o
atom.workspace.open('sample.js', showInvisibles: false).then (o) -> editor = o
runs ->
eventCount = 0
editor1.onDidTerminatePendingState -> eventCount++
it "ignores invisibles even if editor.showInvisibles is true", ->
atom.config.set('editor.showInvisibles', true)
expect(editor.lineTextForScreenRow(0).indexOf(atom.config.get('editor.invisibles.eol'))).toBe(-1)
it "does not open file in pending state by default", ->
expect(editor.isPending()).toBe false
describe "indent guides", ->
it "shows indent guides when `editor.showIndentGuide` is set to true and the editor is not mini", ->
editor.setText(" foo")
atom.config.set('editor.tabLength', 2)
it "opens file in pending state if 'pending' option is true", ->
expect(editor1.isPending()).toBe true
atom.config.set('editor.showIndentGuide', false)
expect(editor.tokensForScreenRow(0)).toEqual ['source.js', 'leading-whitespace']
it "terminates pending state if ::terminatePendingState is invoked", ->
editor1.terminatePendingState()
atom.config.set('editor.showIndentGuide', true)
expect(editor.tokensForScreenRow(0)).toEqual ['source.js', 'leading-whitespace indent-guide']
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
editor.setMini(true)
expect(editor.tokensForScreenRow(0)).toEqual ['source.js', 'leading-whitespace']
it "terminates pending state when buffer is changed", ->
editor1.insertText('I\'ll be back!')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
describe "when the editor is constructed with the grammar option set", ->
beforeEach ->
atom.workspace.destroyActivePane()
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
waitsForPromise ->
atom.workspace.open('sample.js', grammar: atom.grammars.grammarForScopeName('source.coffee')).then (o) -> editor = o
it "only calls terminate handler once when text is modified twice", ->
editor1.insertText('Some text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
it "sets the grammar", ->
expect(editor.getGrammar().name).toBe 'CoffeeScript'
editor1.save()
editor1.insertText('More text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
it "only calls terminate handler once when terminatePendingState is called twice", ->
editor1.terminatePendingState()
editor1.terminatePendingState()
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
describe "::getElement", ->
it "returns an element", ->
expect(editor.getElement() instanceof HTMLElement).toBe(true)

View File

@@ -75,22 +75,23 @@ describe 'text utilities', ->
expect(textUtils.isKoreanCharacter("O")).toBe(false)
describe ".isCJKCharacter(character)", ->
it "returns true when the character is either a korean, half-width or double-width character", ->
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
expect(textUtils.isCJKCharacter("")).toBe(true)
describe ".isWrapBoundary(previousCharacter, character)", ->
it "returns true when the character is CJK or when the previous character is a space/tab", ->
anyCharacter = 'x'
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isWrapBoundary(anyCharacter, "")).toBe(true)
expect(textUtils.isDoubleWidthCharacter("a")).toBe(false)
expect(textUtils.isDoubleWidthCharacter("O")).toBe(false)
expect(textUtils.isDoubleWidthCharacter("z")).toBe(false)
expect(textUtils.isWrapBoundary(' ', 'h')).toBe(true)
expect(textUtils.isWrapBoundary('\t', 'h')).toBe(true)
expect(textUtils.isWrapBoundary('a', 'h')).toBe(false)

View File

@@ -175,7 +175,7 @@ describe "atom.themes", ->
expect(styleElementAddedHandler).toHaveBeenCalled()
element = document.querySelector('head style[source-path*="css.css"]')
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(cssPath)
expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(cssPath)
expect(element.textContent).toBe fs.readFileSync(cssPath, 'utf8')
# doesn't append twice
@@ -194,7 +194,7 @@ describe "atom.themes", ->
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
element = document.querySelector('head style[source-path*="sample.less"]')
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(lessPath)
expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(lessPath)
expect(element.textContent).toBe """
#header {
color: #4d926f;
@@ -213,9 +213,9 @@ describe "atom.themes", ->
it "supports requiring css and less stylesheets without an explicit extension", ->
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'css')
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
document.querySelector('head style[source-path*="css.css"]').remove()
document.querySelector('head style[source-path*="sample.less"]').remove()

View File

@@ -0,0 +1,103 @@
/** @babel */
import TokenizedBufferIterator from '../src/tokenized-buffer-iterator'
import {Point} from 'text-buffer'
describe('TokenizedBufferIterator', () => {
it('reports two boundaries at the same position when tags close, open, then close again without a non-negative integer separating them (regression)', () => {
const tokenizedBuffer = {
tokenizedLineForRow () {
return {
tags: [-1, -2, -1, -2],
text: '',
openScopes: []
}
}
}
const grammarRegistry = {
scopeForId () {
return 'foo'
}
}
const iterator = new TokenizedBufferIterator(tokenizedBuffer, grammarRegistry)
iterator.seek(Point(0, 0))
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['foo'])
iterator.moveToSuccessor()
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual([])
})
it("reports a boundary at line end if the next line's open scopes don't match the containing tags for the current line", () => {
const tokenizedBuffer = {
tokenizedLineForRow (row) {
if (row === 0) {
return {
tags: [-1, 3, -2, -3],
text: 'bar',
openScopes: []
}
} else if (row === 1) {
return {
tags: [3],
text: 'baz',
openScopes: [-1]
}
} else if (row === 2) {
return {
tags: [-2],
text: '',
openScopes: [-1]
}
}
}
}
const grammarRegistry = {
scopeForId (id) {
if (id === -2 || id === -1) {
return 'foo'
} else if (id === -3) {
return 'qux'
}
}
}
const iterator = new TokenizedBufferIterator(tokenizedBuffer, grammarRegistry)
iterator.seek(Point(0, 0))
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['qux'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual(['qux'])
expect(iterator.getOpenTags()).toEqual([])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(1, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(2, 0))
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual([])
})
})

View File

@@ -1,5 +1,5 @@
TokenizedBuffer = require '../src/tokenized-buffer'
TextBuffer = require 'text-buffer'
{Point} = TextBuffer = require 'text-buffer'
_ = require 'underscore-plus'
describe "TokenizedBuffer", ->
@@ -134,13 +134,10 @@ describe "TokenizedBuffer", ->
describe "on construction", ->
it "initially creates un-tokenized screen lines, then tokenizes lines chunk at a time in the background", ->
line0 = tokenizedBuffer.tokenizedLineForRow(0)
expect(line0.tokens.length).toBe 1
expect(line0.tokens[0]).toEqual(value: line0.text, scopes: ['source.js'])
expect(line0.tokens).toEqual([value: line0.text, scopes: ['source.js']])
line11 = tokenizedBuffer.tokenizedLineForRow(11)
expect(line11.tokens.length).toBe 2
expect(line11.tokens[0]).toEqual(value: " ", scopes: ['source.js'], isAtomic: true)
expect(line11.tokens[1]).toEqual(value: "return sort(Array.apply(this, arguments));", scopes: ['source.js'])
expect(line11.tokens).toEqual([value: " return sort(Array.apply(this, arguments));", scopes: ['source.js']])
# background tokenization has not begun
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack).toBeUndefined()
@@ -202,8 +199,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.firstInvalidRow()).toBe 3
advanceClock()
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0)
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0)
expect(tokenizedBuffer.firstInvalidRow()).toBe 8
describe "when there is a buffer change surrounding an invalid row", ->
@@ -237,7 +233,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js'])
# line 2 is unchanged
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -253,7 +249,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 2, delta: 0)
expect(event).toEqual(start: 2, end: 2, delta: 0)
changeHandler.reset()
advanceClock()
@@ -263,8 +259,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(event).toEqual(start: 2, end: 5, delta: 0)
expect(event).toEqual(start: 3, end: 5, delta: 0)
it "resumes highlighting with the state of the previous line", ->
buffer.insert([0, 0], '/*')
@@ -285,14 +280,14 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
# lines below deleted regions should be shifted upward
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false
expect(event).toEqual(start: 1, end: 3, delta: -2)
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
@@ -305,7 +300,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 3, delta: -1)
expect(event).toEqual(start: 2, end: 3, delta: -1)
changeHandler.reset()
advanceClock()
@@ -314,8 +309,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(event).toEqual(start: 2, end: 4, delta: 0)
expect(event).toEqual(start: 3, end: 4, delta: 0)
describe "when lines are both updated and inserted", ->
it "updates tokens to reflect the change", ->
@@ -334,12 +328,12 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
# previous line 3 is pushed down to become line 5
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0
expect(event).toEqual(start: 1, end: 2, delta: 2)
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
@@ -350,7 +344,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 2, delta: 2)
expect(event).toEqual(start: 2, end: 2, delta: 2)
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
@@ -380,32 +374,6 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeTruthy()
it "tokenizes leading whitespace based on the new tab length", ->
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].isAtomic).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].value).toBe " "
tokenizedBuffer.setTabLength(4)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].isAtomic).toBeFalsy()
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].value).toBe " current "
it "does not tokenize whitespaces followed by combining characters as leading whitespace", ->
buffer.setText(" \u030b")
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.tokenizedLineForRow(0)
expect(tokens[0].value).toBe " "
expect(tokens[0].hasLeadingWhitespace()).toBe true
expect(tokens[1].value).toBe " "
expect(tokens[1].hasLeadingWhitespace()).toBe true
expect(tokens[2].value).toBe " \u030b"
expect(tokens[2].hasLeadingWhitespace()).toBe false
it "does not break out soft tabs across a scope boundary", ->
waitsForPromise ->
atom.packages.activatePackage('language-gfm')
@@ -442,133 +410,6 @@ describe "TokenizedBuffer", ->
beforeEach ->
fullyTokenize(tokenizedBuffer)
it "renders each tab as its own atomic token with a value of size tabLength", ->
tabAsSpaces = _.multiplyString(' ', tokenizedBuffer.getTabLength())
screenLine0 = tokenizedBuffer.tokenizedLineForRow(0)
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
{tokens} = screenLine0
expect(tokens.length).toBe 4
expect(tokens[0].value).toBe "#"
expect(tokens[1].value).toBe " Econ 101"
expect(tokens[2].value).toBe tabAsSpaces
expect(tokens[2].scopes).toEqual tokens[1].scopes
expect(tokens[2].isAtomic).toBeTruthy()
expect(tokens[3].value).toBe ""
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
it "aligns the hard tabs to the correct tab stop column", ->
buffer.setText """
1\t2 \t3\t4
12\t3 \t4\t5
123\t4 \t5\t6
"""
tokenizedBuffer.setTabLength(4)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
tokenizedBuffer.setTabLength(3)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 3
tokenizedBuffer.setTabLength(2)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
tokenizedBuffer.setTabLength(1)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
describe "when the buffer contains UTF-8 surrogate pairs", ->
beforeEach ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
runs ->
buffer = atom.project.bufferForPathSync 'sample-with-pairs.js'
buffer.setText """
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
buffer.release()
it "renders each UTF-8 surrogate pair as its own atomic token", ->
screenLine0 = tokenizedBuffer.tokenizedLineForRow(0)
expect(screenLine0.text).toBe "'abc\uD835\uDF97def'"
{tokens} = screenLine0
expect(tokens.length).toBe 5
expect(tokens[0].value).toBe "'"
expect(tokens[1].value).toBe "abc"
expect(tokens[2].value).toBe "\uD835\uDF97"
expect(tokens[2].isAtomic).toBeTruthy()
expect(tokens[3].value).toBe "def"
expect(tokens[4].value).toBe "'"
screenLine1 = tokenizedBuffer.tokenizedLineForRow(1)
expect(screenLine1.text).toBe "//\uD835\uDF97xyz"
{tokens} = screenLine1
expect(tokens.length).toBe 4
expect(tokens[0].value).toBe '//'
expect(tokens[1].value).toBe '\uD835\uDF97'
expect(tokens[1].value).toBeTruthy()
expect(tokens[2].value).toBe 'xyz'
expect(tokens[3].value).toBe ''
describe "when the grammar is tokenized", ->
it "emits the `tokenized` event", ->
editor = null
@@ -578,7 +419,7 @@ describe "TokenizedBuffer", ->
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
tokenizedBuffer = editor.tokenizedBuffer
tokenizedBuffer.onDidTokenize tokenizedHandler
fullyTokenize(tokenizedBuffer)
expect(tokenizedHandler.callCount).toBe(1)
@@ -591,7 +432,7 @@ describe "TokenizedBuffer", ->
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
tokenizedBuffer = editor.tokenizedBuffer
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.onDidTokenize tokenizedHandler
@@ -609,7 +450,7 @@ describe "TokenizedBuffer", ->
atom.workspace.open('coffee.coffee').then (o) -> editor = o
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
tokenizedBuffer = editor.tokenizedBuffer
tokenizedBuffer.onDidTokenize tokenizedHandler
fullyTokenize(tokenizedBuffer)
tokenizedHandler.reset()
@@ -686,132 +527,7 @@ describe "TokenizedBuffer", ->
it "returns the range covered by all contigous tokens (within a single line)", ->
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.function', [1, 18])).toEqual [[1, 6], [1, 28]]
describe "when the editor.tabLength config value changes", ->
it "updates the tab length of the tokenized lines", ->
buffer = atom.project.bufferForPathSync('sample.js')
buffer.setText('\ttest')
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
atom.config.set('editor.tabLength', 6)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
it "does not allow the tab length to be less than 1", ->
buffer = atom.project.bufferForPathSync('sample.js')
buffer.setText('\ttest')
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
atom.config.set('editor.tabLength', 1)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
atom.config.set('editor.tabLength', 0)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
describe "when the invisibles value changes", ->
beforeEach ->
it "updates the tokens with the appropriate invisible characters", ->
buffer = new TextBuffer(text: " \t a line with tabs\tand \tspaces \t ")
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
atom.config.set("editor.showInvisibles", true)
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "SST Sa line with tabsTand T spacesSTS"
# Also needs to work for copies
expect(tokenizedBuffer.tokenizedLineForRow(0).copy().text).toBe "SST Sa line with tabsTand T spacesSTS"
it "assigns endOfLineInvisibles to tokenized lines", ->
buffer = new TextBuffer(text: "a line that ends in a carriage-return-line-feed \r\na line that ends in just a line-feed\na line with no ending")
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
atom.config.set('editor.showInvisibles', true)
atom.config.set("editor.invisibles", cr: 'R', eol: 'N')
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R', 'N']
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual ['N']
# Lines ending in soft wraps get no invisibles
[left, right] = tokenizedBuffer.tokenizedLineForRow(0).softWrapAt(20)
expect(left.endOfLineInvisibles).toBe null
expect(right.endOfLineInvisibles).toEqual ['R', 'N']
atom.config.set("editor.invisibles", cr: 'R', eol: false)
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R']
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual []
describe "leading and trailing whitespace", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
it "assigns ::firstNonWhitespaceIndex on tokens that have leading whitespace", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0].firstNonWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].firstNonWhitespaceIndex).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].firstNonWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].firstNonWhitespaceIndex).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].firstNonWhitespaceIndex).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2].firstNonWhitespaceIndex).toBe null
# The 4th token *has* leading whitespace, but isn't entirely whitespace
buffer.insert([5, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3].firstNonWhitespaceIndex).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4].firstNonWhitespaceIndex).toBe null
# Lines that are *only* whitespace are not considered to have leading whitespace
buffer.insert([10, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(10).tokens[0].firstNonWhitespaceIndex).toBe null
it "assigns ::firstTrailingWhitespaceIndex on tokens that have trailing whitespace", ->
buffer.insert([0, Infinity], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[11].firstTrailingWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[12].firstTrailingWhitespaceIndex).toBe 0
# The last token *has* trailing whitespace, but isn't entirely whitespace
buffer.setTextInRange([[2, 39], [2, 40]], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[14].firstTrailingWhitespaceIndex).toBe null
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[15].firstTrailingWhitespaceIndex).toBe 6
# Lines that are *only* whitespace are considered to have trailing whitespace
buffer.insert([10, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(10).tokens[0].firstTrailingWhitespaceIndex).toBe 0
it "only marks trailing whitespace on the last segment of a soft-wrapped line", ->
buffer.insert([0, Infinity], ' ')
tokenizedLine = tokenizedBuffer.tokenizedLineForRow(0)
[segment1, segment2] = tokenizedLine.softWrapAt(16)
expect(segment1.tokens[5].value).toBe ' '
expect(segment1.tokens[5].firstTrailingWhitespaceIndex).toBe null
expect(segment2.tokens[6].value).toBe ' '
expect(segment2.tokens[6].firstTrailingWhitespaceIndex).toBe 0
it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", ->
buffer.setText(" \t a line with tabs\tand \tspaces \t ")
atom.config.set("editor.showInvisibles", true)
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
fullyTokenize(tokenizedBuffer)
line = tokenizedBuffer.tokenizedLineForRow(0).copy()
expect(line.tokens[0].firstNonWhitespaceIndex).toBe 2
expect(line.tokens[line.tokens.length - 1].firstTrailingWhitespaceIndex).toBe 0
describe ".indentLevel on tokenized lines", ->
describe ".indentLevelForRow(row)", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
@@ -821,43 +537,43 @@ describe "TokenizedBuffer", ->
describe "when the line is non-empty", ->
it "has an indent level based on the leading whitespace on the line", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).indentLevel).toBe 0
expect(tokenizedBuffer.tokenizedLineForRow(1).indentLevel).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2
expect(tokenizedBuffer.indentLevelForRow(0)).toBe 0
expect(tokenizedBuffer.indentLevelForRow(1)).toBe 1
expect(tokenizedBuffer.indentLevelForRow(2)).toBe 2
buffer.insert([2, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2.5
expect(tokenizedBuffer.indentLevelForRow(2)).toBe 2.5
describe "when the line is empty", ->
it "assumes the indentation level of the first non-empty line below or above if one exists", ->
buffer.insert([12, 0], ' ')
buffer.insert([12, Infinity], '\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(14).indentLevel).toBe 2
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
expect(tokenizedBuffer.indentLevelForRow(14)).toBe 2
buffer.insert([1, Infinity], '\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(3).indentLevel).toBe 2
expect(tokenizedBuffer.indentLevelForRow(2)).toBe 2
expect(tokenizedBuffer.indentLevelForRow(3)).toBe 2
buffer.setText('\n\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(1).indentLevel).toBe 0
expect(tokenizedBuffer.indentLevelForRow(1)).toBe 0
describe "when the changed lines are surrounded by whitespace-only lines", ->
it "updates the indentLevel of empty lines that precede the change", ->
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 0
expect(tokenizedBuffer.indentLevelForRow(12)).toBe 0
buffer.insert([12, 0], '\n')
buffer.insert([13, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 1
expect(tokenizedBuffer.indentLevelForRow(12)).toBe 1
it "updates empty line indent guides when the empty line is the last line", ->
buffer.insert([12, 2], '\n')
# The newline and the tab need to be in two different operations to surface the bug
buffer.insert([12, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 1
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 1
buffer.insert([12, 0], ' ')
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(14)).not.toBeDefined()
it "updates the indentLevel of empty lines surrounding a change that inserts lines", ->
@@ -865,24 +581,24 @@ describe "TokenizedBuffer", ->
buffer.insert([7, 0], '\n\n')
buffer.insert([5, 0], '\n\n')
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 3
expect(tokenizedBuffer.tokenizedLineForRow(11).indentLevel).toBe 2
expect(tokenizedBuffer.indentLevelForRow(5)).toBe 3
expect(tokenizedBuffer.indentLevelForRow(6)).toBe 3
expect(tokenizedBuffer.indentLevelForRow(9)).toBe 3
expect(tokenizedBuffer.indentLevelForRow(10)).toBe 3
expect(tokenizedBuffer.indentLevelForRow(11)).toBe 2
tokenizedBuffer.onDidChange changeHandler = jasmine.createSpy('changeHandler')
buffer.setTextInRange([[7, 0], [8, 65]], ' one\n two\n three\n four')
delete changeHandler.argsForCall[0][0].bufferChange
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: 2)
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, delta: 2)
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(11).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 4
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
expect(tokenizedBuffer.indentLevelForRow(5)).toBe 4
expect(tokenizedBuffer.indentLevelForRow(6)).toBe 4
expect(tokenizedBuffer.indentLevelForRow(11)).toBe 4
expect(tokenizedBuffer.indentLevelForRow(12)).toBe 4
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
it "updates the indentLevel of empty lines surrounding a change that removes lines", ->
# create some new lines
@@ -894,16 +610,16 @@ describe "TokenizedBuffer", ->
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
delete changeHandler.argsForCall[0][0].bufferChange
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, delta: -1)
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(7).indentLevel).toBe 2 # new text
expect(tokenizedBuffer.tokenizedLineForRow(8).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # }
expect(tokenizedBuffer.indentLevelForRow(5)).toBe 2
expect(tokenizedBuffer.indentLevelForRow(6)).toBe 2
expect(tokenizedBuffer.indentLevelForRow(7)).toBe 2 # new text
expect(tokenizedBuffer.indentLevelForRow(8)).toBe 2
expect(tokenizedBuffer.indentLevelForRow(9)).toBe 2
expect(tokenizedBuffer.indentLevelForRow(10)).toBe 2 # }
describe ".foldable on tokenized lines", ->
describe "::isFoldableAtRow(row)", ->
changes = null
beforeEach ->
@@ -915,74 +631,66 @@ describe "TokenizedBuffer", ->
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.onDidChange (change) ->
delete change.bufferChange
changes.push(change)
it "sets .foldable to true on the first line of multi-line comments", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
expect(tokenizedBuffer.tokenizedLineForRow(13).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(14).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(15).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(16).foldable).toBe false
it "includes the first line of multi-line comments", ->
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent
expect(tokenizedBuffer.isFoldableAtRow(13)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(14)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(15)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(16)).toBe false
buffer.insert([0, Infinity], '\n')
expect(changes).toEqual [{start: 0, end: 1, delta: 1}]
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe false
changes = []
buffer.undo()
expect(changes).toEqual [{start: 0, end: 2, delta: -1}]
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
it "sets .foldable to true on non-comment lines that precede an increase in indentation", ->
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent
it "includes non-comment lines that precede an increase in indentation", ->
buffer.insert([2, 0], ' ') # commented lines preceding an indent aren't foldable
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(4).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(5).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(4)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(5)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.insert([7, 0], ' ')
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.undo()
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.insert([7, 0], " \n x\n")
expect(changes).toEqual [{start: 6, end: 7, delta: 2}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.insert([9, 0], " ")
expect(changes).toEqual [{start: 9, end: 9, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
describe "when the buffer is configured with the null grammar", ->
it "uses the placeholder tokens and does not actually tokenize using the grammar", ->
@@ -1061,3 +769,107 @@ describe "TokenizedBuffer", ->
runs ->
expect(coffeeCalled).toBe true
describe "text decoration layer API", ->
describe "iterator", ->
it "iterates over the syntactic scope boundaries", ->
buffer = new TextBuffer(text: "var foo = 1 /*\nhello*/var bar = 2\n")
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".js"))
fullyTokenize(tokenizedBuffer)
iterator = tokenizedBuffer.buildIterator()
iterator.seek(Point(0, 0))
expectedBoundaries = [
{position: Point(0, 0), closeTags: [], openTags: ["source.js", "storage.type.var.js"]}
{position: Point(0, 3), closeTags: ["storage.type.var.js"], openTags: []}
{position: Point(0, 8), closeTags: [], openTags: ["keyword.operator.assignment.js"]}
{position: Point(0, 9), closeTags: ["keyword.operator.assignment.js"], openTags: []}
{position: Point(0, 10), closeTags: [], openTags: ["constant.numeric.decimal.js"]}
{position: Point(0, 11), closeTags: ["constant.numeric.decimal.js"], openTags: []}
{position: Point(0, 12), closeTags: [], openTags: ["comment.block.js", "punctuation.definition.comment.js"]}
{position: Point(0, 14), closeTags: ["punctuation.definition.comment.js"], openTags: []}
{position: Point(1, 5), closeTags: [], openTags: ["punctuation.definition.comment.js"]}
{position: Point(1, 7), closeTags: ["punctuation.definition.comment.js", "comment.block.js"], openTags: ["storage.type.var.js"]}
{position: Point(1, 10), closeTags: ["storage.type.var.js"], openTags: []}
{position: Point(1, 15), closeTags: [], openTags: ["keyword.operator.assignment.js"]}
{position: Point(1, 16), closeTags: ["keyword.operator.assignment.js"], openTags: []}
{position: Point(1, 17), closeTags: [], openTags: ["constant.numeric.decimal.js"]}
{position: Point(1, 18), closeTags: ["constant.numeric.decimal.js"], openTags: []}
]
loop
boundary = {
position: iterator.getPosition(),
closeTags: iterator.getCloseTags(),
openTags: iterator.getOpenTags()
}
expect(boundary).toEqual(expectedBoundaries.shift())
break unless iterator.moveToSuccessor()
expect(iterator.seek(Point(0, 1))).toEqual(["source.js", "storage.type.var.js"])
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.seek(Point(0, 8))).toEqual(["source.js"])
expect(iterator.getPosition()).toEqual(Point(0, 8))
expect(iterator.seek(Point(1, 0))).toEqual(["source.js", "comment.block.js"])
expect(iterator.getPosition()).toEqual(Point(1, 5))
expect(iterator.seek(Point(1, 18))).toEqual(["source.js", "constant.numeric.decimal.js"])
expect(iterator.getPosition()).toEqual(Point(1, 18))
expect(iterator.seek(Point(2, 0))).toEqual(["source.js"])
iterator.moveToSuccessor() # ensure we don't infinitely loop (regression test)
it "does not report columns beyond the length of the line", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
runs ->
buffer = new TextBuffer(text: "# hello\n# world")
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".coffee"))
fullyTokenize(tokenizedBuffer)
iterator = tokenizedBuffer.buildIterator()
iterator.seek(Point(0, 0))
iterator.moveToSuccessor()
iterator.moveToSuccessor()
expect(iterator.getPosition().column).toBe(7)
iterator.moveToSuccessor()
expect(iterator.getPosition().column).toBe(0)
iterator.seek(Point(0, 7))
expect(iterator.getPosition().column).toBe(7)
iterator.seek(Point(0, 8))
expect(iterator.getPosition().column).toBe(7)
it "correctly terminates scopes at the beginning of the line (regression)", ->
grammar = atom.grammars.createGrammar('test', {
'scopeName': 'text.broken'
'name': 'Broken grammar'
'patterns': [
{'begin': 'start', 'end': '(?=end)', 'name': 'blue.broken'},
{'match': '.', 'name': 'yellow.broken'}
]
})
buffer = new TextBuffer(text: 'start x\nend x\nx')
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
tokenizedBuffer.setGrammar(grammar)
fullyTokenize(tokenizedBuffer)
iterator = tokenizedBuffer.buildIterator()
iterator.seek(Point(1, 0))
expect(iterator.getPosition()).toEqual([1, 0])
expect(iterator.getCloseTags()).toEqual ['blue.broken']
expect(iterator.getOpenTags()).toEqual ['yellow.broken']

View File

@@ -1,19 +0,0 @@
describe "TokenizedLine", ->
editor = null
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('language-coffee-script')
describe "::isOnlyWhitespace()", ->
beforeEach ->
waitsForPromise ->
atom.workspace.open('coffee.coffee').then (o) -> editor = o
it "returns true when the line is only whitespace", ->
expect(editor.tokenizedLineForScreenRow(3).isOnlyWhitespace()).toBe true
expect(editor.tokenizedLineForScreenRow(7).isOnlyWhitespace()).toBe true
expect(editor.tokenizedLineForScreenRow(23).isOnlyWhitespace()).toBe true
it "returns false when the line is not only whitespace", ->
expect(editor.tokenizedLineForScreenRow(0).isOnlyWhitespace()).toBe false
expect(editor.tokenizedLineForScreenRow(2).isOnlyWhitespace()).toBe false

View File

@@ -28,6 +28,12 @@ describe "TooltipManager", ->
hover element, ->
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
it "creates a tooltip immediately if the trigger type is manual", ->
disposable = manager.add element, title: "Title", trigger: "manual"
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
disposable.dispose()
expect(document.body.querySelector(".tooltip")).toBeNull()
it "allows jQuery elements to be passed as the target", ->
element2 = document.createElement('div')
jasmine.attachToDOM(element2)

View File

@@ -23,6 +23,15 @@ describe "ViewRegistry", ->
component = new TestComponent
expect(registry.getView(component)).toBe component.element
describe "when passed an object with a getElement function", ->
it "returns the return value of getElement if it's an instance of HTMLElement", ->
class TestComponent
getElement: ->
@myElement ?= document.createElement('div')
component = new TestComponent
expect(registry.getView(component)).toBe component.myElement
describe "when passed a model object", ->
describe "when a view provider is registered matching the object's constructor", ->
it "constructs a view element and assigns the model on it", ->

View File

@@ -4,7 +4,7 @@ fs = require 'fs-plus'
temp = require 'temp'
TextEditor = require '../src/text-editor'
WindowEventHandler = require '../src/window-event-handler'
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
describe "WindowEventHandler", ->
[projectPath, windowEventHandler] = []
@@ -53,7 +53,7 @@ describe "WindowEventHandler", ->
describe "beforeunload event", ->
beforeEach ->
jasmine.unspy(TextEditor.prototype, "shouldPromptToSave")
spyOn(ipc, 'send')
spyOn(ipcRenderer, 'send')
describe "when pane items are modified", ->
editor = null
@@ -65,17 +65,17 @@ describe "WindowEventHandler", ->
spyOn(atom.workspace, 'confirmClose').andReturn(true)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(ipc.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
expect(ipcRenderer.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
it "cancels the unload if the user selects cancel", ->
spyOn(atom.workspace, 'confirmClose').andReturn(false)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(ipc.send).toHaveBeenCalledWith('did-cancel-window-unload')
expect(ipcRenderer.send).toHaveBeenCalledWith('did-cancel-window-unload')
describe "when a link is clicked", ->
it "opens the http/https links in an external application", ->
shell = require 'shell'
{shell} = require 'electron'
spyOn(shell, 'openExternal')
link = document.createElement('a')

View File

@@ -1,4 +1,4 @@
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
path = require 'path'
temp = require('temp').track()
@@ -47,9 +47,14 @@ describe "WorkspaceElement", ->
it "updates the font-family based on the 'editor.fontFamily' config value", ->
initialCharWidth = editor.getDefaultCharWidth()
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
fontFamily = atom.config.get('editor.fontFamily')
fontFamily += ", 'Apple Color Emoji'" if process.platform is 'darwin'
expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily
atom.config.set('editor.fontFamily', 'sans-serif')
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
fontFamily = atom.config.get('editor.fontFamily')
fontFamily += ", 'Apple Color Emoji'" if process.platform is 'darwin'
expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
it "updates the line-height based on the 'editor.lineHeight' config value", ->
@@ -127,35 +132,35 @@ describe "WorkspaceElement", ->
describe "the 'window:run-package-specs' command", ->
it "runs the package specs for the active item's project path, or the first project path", ->
workspaceElement = atom.views.getView(atom.workspace)
spyOn(ipc, 'send')
spyOn(ipcRenderer, 'send')
# No project paths. Don't try to run specs.
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).not.toHaveBeenCalledWith("run-package-specs")
expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs")
projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
atom.project.setPaths(projectPaths)
# No active item. Use first project directory.
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipcRenderer.send.reset()
# Active item doesn't implement ::getPath(). Use first project directory.
item = document.createElement("div")
atom.workspace.getActivePane().activateItem(item)
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipcRenderer.send.reset()
# Active item has no path. Use first project directory.
item.getPath = -> null
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipcRenderer.send.reset()
# Active item has path. Use project path for item path.
item.getPath = -> path.join(projectPaths[1], "a-file.txt")
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
ipcRenderer.send.reset()

View File

@@ -22,11 +22,11 @@ describe "Workspace", ->
describe "serialization", ->
simulateReload = ->
workspaceState = atom.workspace.serialize()
projectState = atom.project.serialize()
projectState = atom.project.serialize({isUnloading: true})
atom.workspace.destroy()
atom.project.destroy()
atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom)})
atom.project.deserialize(projectState, atom.deserializers)
atom.project.deserialize(projectState)
atom.workspace = new Workspace({
config: atom.config, project: atom.project, packageManager: atom.packages,
grammarRegistry: atom.grammars, deserializerManager: atom.deserializers,
@@ -80,7 +80,8 @@ describe "Workspace", ->
expect(untitledEditor.getText()).toBe("An untitled editor.")
expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath()
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{pathEscaped}///
describe "where there are no open panes or editors", ->
it "constructs the view with no open editors", ->
@@ -428,7 +429,7 @@ describe "Workspace", ->
workspace.open('sample.js').then (e) -> editor = e
runs ->
expect(editor.displayBuffer.largeFileMode).toBe true
expect(editor.largeFileMode).toBe true
describe "when the file is over 20MB", ->
it "prompts the user to make sure they want to open a file this big", ->
@@ -453,7 +454,7 @@ describe "Workspace", ->
runs ->
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(editor.displayBuffer.largeFileMode).toBe true
expect(editor.largeFileMode).toBe true
describe "when passed a path that matches a custom opener", ->
it "returns the resource returned by the custom opener", ->
@@ -585,6 +586,72 @@ describe "Workspace", ->
open = -> workspace.open('file1', workspace.getActivePane())
expect(open).toThrow()
describe "when the file is already open in pending state", ->
it "should terminate the pending state", ->
editor = null
pane = null
waitsForPromise ->
atom.workspace.open('sample.js', pending: true).then (o) ->
editor = o
pane = atom.workspace.getActivePane()
runs ->
expect(pane.getPendingItem()).toEqual editor
waitsForPromise ->
atom.workspace.open('sample.js')
runs ->
expect(pane.getPendingItem()).toBeNull()
describe "when opening will switch from a pending tab to a permanent tab", ->
it "keeps the pending tab open", ->
editor1 = null
editor2 = null
waitsForPromise ->
atom.workspace.open('sample.txt').then (o) ->
editor1 = o
waitsForPromise ->
atom.workspace.open('sample2.txt', pending: true).then (o) ->
editor2 = o
runs ->
pane = atom.workspace.getActivePane()
pane.activateItem(editor1)
expect(pane.getItems().length).toBe 2
expect(pane.getItems()).toEqual [editor1, editor2]
describe "when replacing a pending item which is the last item in a second pane", ->
it "does not destroy the pane even if core.destroyEmptyPanes is on", ->
atom.config.set('core.destroyEmptyPanes', true)
editor1 = null
editor2 = null
leftPane = atom.workspace.getActivePane()
rightPane = null
waitsForPromise ->
atom.workspace.open('sample.js', pending: true, split: 'right').then (o) ->
editor1 = o
rightPane = atom.workspace.getActivePane()
spyOn rightPane, "destroyed"
runs ->
expect(leftPane).not.toBe rightPane
expect(atom.workspace.getActivePane()).toBe rightPane
expect(atom.workspace.getActivePane().getItems().length).toBe 1
expect(rightPane.getPendingItem()).toBe editor1
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) ->
editor2 = o
runs ->
expect(rightPane.getPendingItem()).toBe editor2
expect(rightPane.destroyed.callCount).toBe 0
describe "::reopenItem()", ->
it "opens the uri associated with the last closed pane that isn't currently open", ->
pane = workspace.getActivePane()
@@ -767,25 +834,29 @@ describe "Workspace", ->
describe "when there is an active pane item", ->
it "sets the title to the pane item's title plus the project path", ->
item = atom.workspace.getActivePaneItem()
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the title of the active pane item changes", ->
it "updates the window title based on the item's new title", ->
editor = atom.workspace.getActivePaneItem()
editor.buffer.setPath(path.join(temp.dir, 'hi'))
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the active pane's item changes", ->
it "updates the title to the new item's title plus the project path", ->
atom.workspace.getActivePane().activateNextItem()
item = atom.workspace.getActivePaneItem()
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the last pane item is removed", ->
it "updates the title to contain the project's path", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
expect(document.title).toMatch ///^#{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{pathEscaped}///
describe "when an inactive pane's item changes", ->
it "does not update the title", ->
@@ -809,7 +880,8 @@ describe "Workspace", ->
})
workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
item = workspace2.getActivePaneItem()
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{pathEscaped}///
workspace2.destroy()
describe "document edited status", ->
@@ -1532,3 +1604,60 @@ describe "Workspace", ->
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.close).toHaveBeenCalled()
describe "when the core.allowPendingPaneItems option is falsey", ->
it "does not open item with `pending: true` option as pending", ->
pane = null
atom.config.set('core.allowPendingPaneItems', false)
waitsForPromise ->
atom.workspace.open('sample.js', pending: true).then ->
pane = atom.workspace.getActivePane()
runs ->
expect(pane.getPendingItem()).toBeFalsy()
describe "grammar activation", ->
beforeEach ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
it "notifies the workspace of which grammar is used", ->
editor = null
grammarUsed = jasmine.createSpy()
atom.workspace.handleGrammarUsed = grammarUsed
waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
waitsFor -> grammarUsed.callCount is 1
runs ->
expect(grammarUsed.argsForCall[0][0].name).toBe 'JavaScript'
describe ".checkoutHeadRevision()", ->
editor = null
beforeEach ->
atom.config.set("editor.confirmCheckoutHeadRevision", false)
waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
it "reverts to the version of its file checked into the project repository", ->
editor.setCursorBufferPosition([0, 0])
editor.insertText("---\n")
expect(editor.lineTextForBufferRow(0)).toBe "---"
waitsForPromise ->
atom.workspace.checkoutHeadRevision(editor)
runs ->
expect(editor.lineTextForBufferRow(0)).toBe ""
describe "when there's no repository for the editor's file", ->
it "doesn't do anything", ->
editor = atom.workspace.buildTextEditor()
editor.setText("stuff")
atom.workspace.checkoutHeadRevision(editor)
waitsForPromise -> atom.workspace.checkoutHeadRevision(editor)
escapeStringRegex = (str) ->
str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')

View File

@@ -1,69 +1,73 @@
_ = require 'underscore-plus'
ipc = require 'ipc'
remote = require 'remote'
shell = require 'shell'
webFrame = require 'web-frame'
{screen, ipcRenderer, remote, shell, webFrame} = require 'electron'
ipcHelpers = require './ipc-helpers'
{Disposable} = require 'event-kit'
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
module.exports =
class ApplicationDelegate
open: (params) ->
ipc.send('open', params)
ipcRenderer.send('open', params)
pickFolder: (callback) ->
responseChannel = "atom-pick-folder-response"
ipc.on responseChannel, (path) ->
ipc.removeAllListeners(responseChannel)
ipcRenderer.on responseChannel, (event, path) ->
ipcRenderer.removeAllListeners(responseChannel)
callback(path)
ipc.send("pick-folder", responseChannel)
ipcRenderer.send("pick-folder", responseChannel)
getCurrentWindow: ->
remote.getCurrentWindow()
closeWindow: ->
ipc.send("call-window-method", "close")
ipcRenderer.send("call-window-method", "close")
getTemporaryWindowState: ->
ipcHelpers.call('get-temporary-window-state')
setTemporaryWindowState: (state) ->
ipcHelpers.call('set-temporary-window-state', state)
getWindowSize: ->
[width, height] = remote.getCurrentWindow().getSize()
{width, height}
setWindowSize: (width, height) ->
remote.getCurrentWindow().setSize(width, height)
ipcHelpers.call('set-window-size', width, height)
getWindowPosition: ->
[x, y] = remote.getCurrentWindow().getPosition()
{x, y}
setWindowPosition: (x, y) ->
ipc.send("call-window-method", "setPosition", x, y)
ipcHelpers.call('set-window-position', x, y)
centerWindow: ->
ipc.send("call-window-method", "center")
ipcHelpers.call('center-window')
focusWindow: ->
ipc.send("call-window-method", "focus")
ipcHelpers.call('focus-window')
showWindow: ->
ipc.send("call-window-method", "show")
ipcHelpers.call('show-window')
hideWindow: ->
ipc.send("call-window-method", "hide")
ipcHelpers.call('hide-window')
restartWindow: ->
ipc.send("call-window-method", "restart")
reloadWindow: ->
ipcRenderer.send("call-window-method", "reload")
isWindowMaximized: ->
remote.getCurrentWindow().isMaximized()
maximizeWindow: ->
ipc.send("call-window-method", "maximize")
ipcRenderer.send("call-window-method", "maximize")
isWindowFullScreen: ->
remote.getCurrentWindow().isFullScreen()
setWindowFullScreen: (fullScreen=false) ->
ipc.send("call-window-method", "setFullScreen", fullScreen)
ipcRenderer.send("call-window-method", "setFullScreen", fullScreen)
openWindowDevTools: ->
new Promise (resolve) ->
@@ -75,7 +79,7 @@ class ApplicationDelegate
resolve()
else
remote.getCurrentWindow().once("devtools-opened", -> resolve())
ipc.send("call-window-method", "openDevTools")
ipcRenderer.send("call-window-method", "openDevTools")
closeWindowDevTools: ->
new Promise (resolve) ->
@@ -87,7 +91,7 @@ class ApplicationDelegate
resolve()
else
remote.getCurrentWindow().once("devtools-closed", -> resolve())
ipc.send("call-window-method", "closeDevTools")
ipcRenderer.send("call-window-method", "closeDevTools")
toggleWindowDevTools: ->
new Promise (resolve) =>
@@ -101,16 +105,16 @@ class ApplicationDelegate
@openWindowDevTools().then(resolve)
executeJavaScriptInWindowDevTools: (code) ->
ipc.send("call-window-method", "executeJavaScriptInDevTools", code)
ipcRenderer.send("execute-javascript-in-dev-tools", code)
setWindowDocumentEdited: (edited) ->
ipc.send("call-window-method", "setDocumentEdited", edited)
ipcRenderer.send("call-window-method", "setDocumentEdited", edited)
setRepresentedFilename: (filename) ->
ipc.send("call-window-method", "setRepresentedFilename", filename)
ipcRenderer.send("call-window-method", "setRepresentedFilename", filename)
addRecentDocument: (filename) ->
ipc.send("add-recent-document", filename)
ipcRenderer.send("add-recent-document", filename)
setRepresentedDirectoryPaths: (paths) ->
loadSettings = getWindowLoadSettings()
@@ -118,14 +122,13 @@ class ApplicationDelegate
setWindowLoadSettings(loadSettings)
setAutoHideWindowMenuBar: (autoHide) ->
ipc.send("call-window-method", "setAutoHideMenuBar", autoHide)
ipcRenderer.send("call-window-method", "setAutoHideMenuBar", autoHide)
setWindowMenuBarVisibility: (visible) ->
remote.getCurrentWindow().setMenuBarVisibility(visible)
getPrimaryDisplayWorkAreaSize: ->
screen = remote.require 'screen'
screen.getPrimaryDisplay().workAreaSize
remote.screen.getPrimaryDisplay().workAreaSize
confirm: ({message, detailedMessage, buttons}) ->
buttons ?= {}
@@ -134,8 +137,7 @@ class ApplicationDelegate
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox(remote.getCurrentWindow(), {
chosen = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info'
message: message
detail: detailedMessage
@@ -157,45 +159,110 @@ class ApplicationDelegate
params = _.clone(params)
params.title ?= 'Save File'
params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
dialog = remote.require('dialog')
dialog.showSaveDialog remote.getCurrentWindow(), params
remote.dialog.showSaveDialog remote.getCurrentWindow(), params
playBeepSound: ->
shell.beep()
onDidOpenLocations: (callback) ->
outerCallback = (message, detail) ->
if message is 'open-locations'
callback(detail)
outerCallback = (event, message, detail) ->
callback(detail) if message is 'open-locations'
ipc.on('message', outerCallback)
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipc.removeListener('message', outerCallback)
ipcRenderer.removeListener('message', outerCallback)
onUpdateAvailable: (callback) ->
outerCallback = (message, detail) ->
if message is 'update-available'
callback(detail)
outerCallback = (event, message, detail) ->
# TODO: Yes, this is strange that `onUpdateAvailable` is listening for
# `did-begin-downloading-update`. We currently have no mechanism to know
# if there is an update, so begin of downloading is a good proxy.
callback(detail) if message is 'did-begin-downloading-update'
ipc.on('message', outerCallback)
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipc.removeListener('message', outerCallback)
ipcRenderer.removeListener('message', outerCallback)
onDidBeginDownloadingUpdate: (callback) ->
@onUpdateAvailable(callback)
onDidBeginCheckingForUpdate: (callback) ->
outerCallback = (event, message, detail) ->
callback(detail) if message is 'checking-for-update'
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipcRenderer.removeListener('message', outerCallback)
onDidCompleteDownloadingUpdate: (callback) ->
outerCallback = (event, message, detail) ->
# TODO: We could rename this event to `did-complete-downloading-update`
callback(detail) if message is 'update-available'
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipcRenderer.removeListener('message', outerCallback)
onUpdateNotAvailable: (callback) ->
outerCallback = (event, message, detail) ->
callback(detail) if message is 'update-not-available'
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipcRenderer.removeListener('message', outerCallback)
onUpdateError: (callback) ->
outerCallback = (event, message, detail) ->
callback(detail) if message is 'update-error'
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipcRenderer.removeListener('message', outerCallback)
onApplicationMenuCommand: (callback) ->
ipc.on('command', callback)
outerCallback = (event, args...) ->
callback(args...)
ipcRenderer.on('command', outerCallback)
new Disposable ->
ipc.removeListener('command', callback)
ipcRenderer.removeListener('command', outerCallback)
onContextMenuCommand: (callback) ->
ipc.on('context-command', callback)
outerCallback = (event, args...) ->
callback(args...)
ipcRenderer.on('context-command', outerCallback)
new Disposable ->
ipc.removeListener('context-command', callback)
ipcRenderer.removeListener('context-command', outerCallback)
didCancelWindowUnload: ->
ipc.send('did-cancel-window-unload')
ipcRenderer.send('did-cancel-window-unload')
openExternal: (url) ->
shell.openExternal(url)
disablePinchToZoom: ->
webFrame.setZoomLevelLimits(1, 1)
disableZoom: ->
outerCallback = ->
webFrame.setZoomLevelLimits(1, 1)
outerCallback()
# Set the limits every time a display is added or removed, otherwise the
# configuration gets reset to the default, which allows zooming the
# webframe.
screen.on('display-added', outerCallback)
screen.on('display-removed', outerCallback)
new Disposable ->
screen.removeListener('display-added', outerCallback)
screen.removeListener('display-removed', outerCallback)
checkForUpdate: ->
ipcRenderer.send('command', 'application:check-for-update')
restartAndInstallUpdate: ->
ipcRenderer.send('command', 'application:install-update')
getAutoUpdateManagerState: ->
ipcRenderer.sendSync('get-auto-update-manager-state')
getAutoUpdateManagerErrorMessage: ->
ipcRenderer.sendSync('get-auto-update-manager-error')

View File

@@ -1,15 +1,16 @@
crypto = require 'crypto'
path = require 'path'
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
_ = require 'underscore-plus'
{deprecate} = require 'grim'
{CompositeDisposable, Emitter} = require 'event-kit'
{CompositeDisposable, Disposable, Emitter} = require 'event-kit'
fs = require 'fs-plus'
{mapSourcePosition} = require 'source-map-support'
Model = require './model'
WindowEventHandler = require './window-event-handler'
StylesElement = require './styles-element'
StateStore = require './state-store'
StorageFolder = require './storage-folder'
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
registerDefaultCommands = require './register-default-commands'
@@ -40,6 +41,8 @@ Project = require './project'
TextEditor = require './text-editor'
TextBuffer = require 'text-buffer'
Gutter = require './gutter'
TextEditorRegistry = require './text-editor-registry'
AutoUpdateManager = require './auto-update-manager'
WorkspaceElement = require './workspace-element'
PanelContainerElement = require './panel-container-element'
@@ -111,22 +114,35 @@ class AtomEnvironment extends Model
# Public: A {Workspace} instance
workspace: null
# Public: A {TextEditorRegistry} instance
textEditors: null
# Private: An {AutoUpdateManager} instance
autoUpdater: null
saveStateDebounceInterval: 1000
###
Section: Construction and Destruction
###
# Call .loadOrCreate instead
constructor: (params={}) ->
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
@state = {version: @constructor.version}
{@blobStore, @applicationDelegate, @window, @document, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
@unloaded = false
@loadTime = null
{devMode, safeMode, resourcePath} = @getLoadSettings()
{devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings()
@emitter = new Emitter
@disposables = new CompositeDisposable
@stateStore = new StateStore('AtomEnvironments', 1)
if clearWindowState
@getStorageFolder().clear()
@stateStore.clear()
@deserializers = new DeserializerManager(this)
@deserializeTimings = {}
@@ -134,10 +150,10 @@ class AtomEnvironment extends Model
@notifications = new NotificationManager
@config = new Config({configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence})
@config = new Config({@configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence})
@setConfigSchema()
@keymaps = new KeymapManager({configDirPath, resourcePath, notificationManager: @notifications})
@keymaps = new KeymapManager({@configDirPath, resourcePath, notificationManager: @notifications})
@tooltips = new TooltipManager(keymapManager: @keymaps)
@@ -146,16 +162,16 @@ class AtomEnvironment extends Model
@grammars = new GrammarRegistry({@config})
@styles = new StyleManager({configDirPath})
@styles = new StyleManager({@configDirPath})
@packages = new PackageManager({
devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
devMode, @configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications,
grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views
})
@themes = new ThemeManager({
packageManager: @packages, configDirPath, resourcePath, safeMode, @config,
packageManager: @packages, @configDirPath, resourcePath, safeMode, @config,
styleManager: @styles, notificationManager: @notifications, viewRegistry: @views
})
@@ -179,6 +195,9 @@ class AtomEnvironment extends Model
})
@themes.workspace = @workspace
@textEditors = new TextEditorRegistry
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
@config.load()
@themes.loadBaseStylesheets()
@@ -189,7 +208,7 @@ class AtomEnvironment extends Model
@stylesElement = @styles.buildStylesElement()
@document.head.appendChild(@stylesElement)
@applicationDelegate.disablePinchToZoom()
@disposables.add(@applicationDelegate.disableZoom())
@keymaps.subscribeToFileReadFailure()
@keymaps.loadBundledKeymaps()
@@ -200,19 +219,30 @@ class AtomEnvironment extends Model
@registerDefaultViewProviders()
@installUncaughtErrorHandler()
@attachSaveStateListeners()
@installWindowEventHandler()
@observeAutoHideMenuBar()
checkPortableHomeWritable = ->
responseChannel = "check-portable-home-writable-response"
ipc.on responseChannel, (response) ->
ipc.removeAllListeners(responseChannel)
ipcRenderer.on responseChannel, (event, response) ->
ipcRenderer.removeAllListeners(responseChannel)
atom.notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
ipc.send('check-portable-home-writable', responseChannel)
ipcRenderer.send('check-portable-home-writable', responseChannel)
checkPortableHomeWritable()
attachSaveStateListeners: ->
saveState = _.debounce((=>
window.requestIdleCallback => @saveState({isUnloading: false}) unless @unloaded
), @saveStateDebounceInterval)
@document.addEventListener('mousedown', saveState, true)
@document.addEventListener('keydown', saveState, true)
@disposables.add new Disposable =>
@document.removeEventListener('mousedown', saveState, true)
@document.removeEventListener('keydown', saveState, true)
setConfigSchema: ->
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
@@ -226,7 +256,7 @@ class AtomEnvironment extends Model
@deserializers.add(TextBuffer)
registerDefaultCommands: ->
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller})
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller, notificationManager: @notifications, @project, @clipboard})
registerDefaultViewProviders: ->
@views.addViewProvider Workspace, (model, env) ->
@@ -241,8 +271,6 @@ class AtomEnvironment extends Model
new PaneAxisElement().initialize(model, env)
@views.addViewProvider Pane, (model, env) ->
new PaneElement().initialize(model, env)
@views.addViewProvider TextEditor, (model, env) ->
new TextEditorElement().initialize(model, env)
@views.addViewProvider(Gutter, createGutterView)
registerDefaultOpeners: ->
@@ -302,9 +330,6 @@ class AtomEnvironment extends Model
@views.clear()
@registerDefaultViewProviders()
@state.packageStates = {}
delete @state.workspace
destroy: ->
return if not @project
@@ -317,6 +342,7 @@ class AtomEnvironment extends Model
@commands.clear()
@stylesElement.remove()
@config.unobserveUserConfig()
@autoUpdater.destroy()
@uninstallWindowEventHandler()
@@ -395,6 +421,16 @@ class AtomEnvironment extends Model
getVersion: ->
@appVersion ?= @getLoadSettings().appVersion
# Returns the release channel as a {String}. Will return one of `'dev', 'beta', 'stable'`
getReleaseChannel: ->
version = @getVersion()
if version.indexOf('beta') > -1
'beta'
else if version.indexOf('dev') > -1
'dev'
else
'stable'
# Public: Returns a {Boolean} that is `true` if the current version is an official release.
isReleasedVersion: ->
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
@@ -497,7 +533,7 @@ class AtomEnvironment extends Model
# Extended: Reload the current window.
reload: ->
@applicationDelegate.restartWindow()
@applicationDelegate.reloadWindow()
# Extended: Returns a {Boolean} that is `true` if the current window is maximized.
isMaximized: ->
@@ -524,21 +560,18 @@ class AtomEnvironment extends Model
# Restore the window to its previous dimensions and show it.
#
# Also restores the full screen and maximized state on the next tick to
# Restores the full screen and maximized state after the window has resized to
# prevent resize glitches.
displayWindow: ->
dimensions = @restoreWindowDimensions()
@show()
@focus()
setImmediate =>
@setFullScreen(true) if @workspace?.fullScreen
@maximize() if dimensions?.maximized and process.platform isnt 'darwin'
if @isFirstLoad()
loadSettings = getWindowLoadSettings()
loadSettings.firstLoad = false
setWindowLoadSettings(loadSettings)
@restoreWindowDimensions().then =>
steps = [
@restoreWindowBackground(),
@show(),
@focus()
]
steps.push(@setFullScreen(true)) if @windowDimensions?.fullScreen
steps.push(@maximize()) if @windowDimensions?.maximized and process.platform isnt 'darwin'
Promise.all(steps)
# Get the dimensions of this window.
#
@@ -566,22 +599,24 @@ class AtomEnvironment extends Model
# * `width` The new width.
# * `height` The new height.
setWindowDimensions: ({x, y, width, height}) ->
steps = []
if width? and height?
@setSize(width, height)
steps.push(@setSize(width, height))
if x? and y?
@setPosition(x, y)
steps.push(@setPosition(x, y))
else
@center()
steps.push(@center())
Promise.all(steps)
# Returns true if the dimensions are useable, false if they should be ignored.
# Work around for https://github.com/atom/atom-shell/issues/473
isValidDimensions: ({x, y, width, height}={}) ->
width > 0 and height > 0 and x + width > 0 and y + height > 0
storeDefaultWindowDimensions: ->
dimensions = @getWindowDimensions()
if @isValidDimensions(dimensions)
localStorage.setItem("defaultWindowDimensions", JSON.stringify(dimensions))
storeWindowDimensions: ->
@windowDimensions = @getWindowDimensions()
if @isValidDimensions(@windowDimensions)
localStorage.setItem("defaultWindowDimensions", JSON.stringify(@windowDimensions))
getDefaultWindowDimensions: ->
{windowDimensions} = @getLoadSettings()
@@ -601,22 +636,16 @@ class AtomEnvironment extends Model
{x: 0, y: 0, width: Math.min(1024, width), height}
restoreWindowDimensions: ->
dimensions = null
unless @windowDimensions? and @isValidDimensions(@windowDimensions)
@windowDimensions = @getDefaultWindowDimensions()
@setWindowDimensions(@windowDimensions).then -> @windowDimensions
# The first time the window's loaded we want to use the default dimensions.
# But after that, e.g., when the window's been reloaded, we want to use the
# dimensions we've saved for it.
if not @isFirstLoad()
dimensions = @state.windowDimensions
unless @isValidDimensions(dimensions)
dimensions = @getDefaultWindowDimensions()
@setWindowDimensions(dimensions)
dimensions
storeWindowDimensions: ->
dimensions = @getWindowDimensions()
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
restoreWindowBackground: ->
if backgroundColor = window.localStorage.getItem('atom:window-background-color')
@backgroundStylesheet = document.createElement('style')
@backgroundStylesheet.type = 'text/css'
@backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + ' !important; }'
document.head.appendChild(@backgroundStylesheet)
storeWindowBackground: ->
return if @inSpecMode()
@@ -627,44 +656,58 @@ class AtomEnvironment extends Model
# Call this method when establishing a real application window.
startEditorWindow: ->
@commandInstaller.installAtomCommand false, (error) ->
console.warn error.message if error?
@commandInstaller.installApmCommand false, (error) ->
console.warn error.message if error?
@unloaded = false
@loadState().then (state) =>
@windowDimensions = state?.windowDimensions
@displayWindow().then =>
@commandInstaller.installAtomCommand false, (error) ->
console.warn error.message if error?
@commandInstaller.installApmCommand false, (error) ->
console.warn error.message if error?
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
@listenForUpdates()
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
@listenForUpdates()
@registerDefaultTargetForKeymaps()
@registerDefaultTargetForKeymaps()
@packages.loadPackages()
@packages.loadPackages()
@document.body.appendChild(@views.getView(@workspace))
startTime = Date.now()
@deserialize(state) if state?
@deserializeTimings.atom = Date.now() - startTime
@watchProjectPath()
@document.body.appendChild(@views.getView(@workspace))
@backgroundStylesheet?.remove()
@packages.activate()
@keymaps.loadUserKeymap()
@requireUserInitScript() unless @getLoadSettings().safeMode
@watchProjectPaths()
@menu.update()
@packages.activate()
@keymaps.loadUserKeymap()
@requireUserInitScript() unless @getLoadSettings().safeMode
@openInitialEmptyEditorIfNecessary()
@menu.update()
@openInitialEmptyEditorIfNecessary()
serialize: (options) ->
version: @constructor.version
project: @project.serialize(options)
workspace: @workspace.serialize()
packageStates: @packages.serialize()
grammars: {grammarOverridesByPath: @grammars.grammarOverridesByPath}
fullScreen: @isFullScreen()
windowDimensions: @windowDimensions
unloadEditorWindow: ->
return if not @project
@saveState({isUnloading: true})
@storeWindowBackground()
@state.grammars = {grammarOverridesByPath: @grammars.grammarOverridesByPath}
@state.project = @project.serialize()
@state.workspace = @workspace.serialize()
@packages.deactivatePackages()
@state.packageStates = @packages.packageStates
@state.fullScreen = @isFullScreen()
@saveStateSync()
@saveBlobStoreSync()
@unloaded = true
openInitialEmptyEditorIfNecessary: ->
return unless @config.get('core.openEmptyEditorOnStart')
@@ -747,6 +790,7 @@ class AtomEnvironment extends Model
# Returns a {Promise} that resolves when the DevTools have been opened or
# closed.
toggleDevTools: ->
require("devtron").install()
@applicationDelegate.toggleWindowDevTools()
# Extended: Execute code in dev tools.
@@ -772,7 +816,7 @@ class AtomEnvironment extends Model
@themes.load()
# Notify the browser project of the window's current project path
watchProjectPath: ->
watchProjectPaths: ->
@disposables.add @project.onDidChangePaths =>
@applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths())
@@ -797,45 +841,48 @@ class AtomEnvironment extends Model
@blobStore.save()
saveStateSync: ->
return unless @enablePersistence
saveState: (options) ->
return Promise.resolve() unless @enablePersistence
if storageKey = @getStateKey(@project?.getPaths())
@getStorageFolder().store(storageKey, @state)
new Promise (resolve, reject) =>
return if not @project
state = @serialize(options)
savePromise =
if storageKey = @getStateKey(@project?.getPaths())
@stateStore.save(storageKey, state)
else
@applicationDelegate.setTemporaryWindowState(state)
savePromise.catch(reject).then(resolve)
loadState: ->
if @enablePersistence
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
@stateStore.load(stateKey).then (state) =>
if state
state
else
# TODO: remove this when every user has migrated to the IndexedDb state store.
@getStorageFolder().load(stateKey)
else
@applicationDelegate.getTemporaryWindowState()
else
@getCurrentWindow().loadSettings.windowState = JSON.stringify(@state)
Promise.resolve(null)
loadStateSync: ->
return unless @enablePersistence
startTime = Date.now()
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
if state = @getStorageFolder().load(stateKey)
@state = state
if not @state? and windowState = @getLoadSettings().windowState
try
if state = JSON.parse(@getLoadSettings().windowState)
@state = state
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
@deserializeTimings.atom = Date.now() - startTime
if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath
deserialize: (state) ->
if grammarOverridesByPath = state.grammars?.grammarOverridesByPath
@grammars.grammarOverridesByPath = grammarOverridesByPath
@setFullScreen(@state.fullScreen)
@setFullScreen(state.fullScreen)
@packages.packageStates = @state.packageStates ? {}
@packages.packageStates = state.packageStates ? {}
startTime = Date.now()
@project.deserialize(@state.project, @deserializers) if @state.project?
@project.deserialize(state.project, @deserializers) if state.project?
@deserializeTimings.project = Date.now() - startTime
startTime = Date.now()
@workspace.deserialize(@state.workspace, @deserializers) if @state.workspace?
@workspace.deserialize(state.workspace, @deserializers) if state.workspace?
@deserializeTimings.workspace = Date.now() - startTime
getStateKey: (paths) ->
@@ -845,12 +892,12 @@ class AtomEnvironment extends Model
else
null
getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
getStorageFolder: ->
@storageFolder ?= new StorageFolder(@getConfigDirPath())
getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
getUserInitScriptPath: ->
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee')
@@ -864,6 +911,7 @@ class AtomEnvironment extends Model
detail: error.message
dismissable: true
# TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead
onUpdateAvailable: (callback) ->
@emitter.on 'update-available', callback
@@ -871,7 +919,8 @@ class AtomEnvironment extends Model
@emitter.emit 'update-available', details
listenForUpdates: ->
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
# listen for updates available locally (that have been successfully downloaded)
@disposables.add(@autoUpdater.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this)))
setBodyPlatformClass: ->
@document.body.classList.add("platform-#{process.platform}")
@@ -893,8 +942,8 @@ class AtomEnvironment extends Model
openLocations: (locations) ->
needsProjectPaths = @project?.getPaths().length is 0
for {pathToOpen, initialLine, initialColumn} in locations
if pathToOpen? and needsProjectPaths
for {pathToOpen, initialLine, initialColumn, forceAddToWindow} in locations
if pathToOpen? and (needsProjectPaths or forceAddToWindow)
if fs.existsSync(pathToOpen)
@project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))

Some files were not shown because too many files have changed in this diff Show More