mirror of
https://github.com/atom/atom.git
synced 2026-01-23 22:08:08 -05:00
Merge branch 'master' into sm-flexible-modals
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ debug.log
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
out/
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
28
ISSUE_TEMPLATE.md
Normal 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.
|
||||
16
README.md
16
README.md
@@ -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)
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "1.6.0"
|
||||
"atom-package-manager": "1.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
2
atom.sh
2
atom.sh
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
BIN
build/certs/AtomDevTestSignKey.p12
Normal file
BIN
build/certs/AtomDevTestSignKey.p12
Normal file
Binary file not shown.
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
30
build/tasks/mktar-task.coffee
Normal file
30
build/tasks/mktar-task.coffee
Normal 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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
4
circle.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
general:
|
||||
branches:
|
||||
only:
|
||||
- io-circle-ci
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
18
docs/native-profiling.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Profiling the Atom Render Process on OS X with Instruments
|
||||
|
||||

|
||||
|
||||
* 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'}
|
||||
]
|
||||
|
||||
@@ -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'}
|
||||
]
|
||||
|
||||
@@ -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'}
|
||||
]
|
||||
|
||||
138
package.json
138
package.json
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
15
script/build
15
script/build
@@ -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);
|
||||
|
||||
51
script/clean
51
script/clean
@@ -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
39
script/mktar
Executable 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"
|
||||
@@ -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);
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
125
spec/auto-update-manager-spec.js
Normal file
125
spec/auto-update-manager-spec.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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"'
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
85
spec/decoration-manager-spec.coffee
Normal file
85
spec/decoration-manager-spec.coffee
Normal 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
160
spec/environment-helpers-spec.js
Normal file
160
spec/environment-helpers-spec.js
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
1
spec/fixtures/git/repo-with-submodules/git.git/refs/remotes/origin/master
vendored
Normal file
1
spec/fixtures/git/repo-with-submodules/git.git/refs/remotes/origin/master
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5
|
||||
3
spec/fixtures/lorem.txt
vendored
Normal file
3
spec/fixtures/lorem.txt
vendored
Normal 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.
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = function (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'Deserializer1',
|
||||
state: state
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = function (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'Deserializer2',
|
||||
state: state
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
module.exports = {
|
||||
activate: function() {}
|
||||
activate () {},
|
||||
|
||||
deserializeMethod1 (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'deserializeMethod1',
|
||||
state: state
|
||||
}
|
||||
},
|
||||
|
||||
deserializeMethod2 (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'deserializeMethod2',
|
||||
state: state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"main": "./index",
|
||||
"deserializers": {
|
||||
"Deserializer1": "./deserializer-1.js",
|
||||
"Deserializer2": "./deserializer-2.js"
|
||||
"Deserializer1": "deserializeMethod1",
|
||||
"Deserializer2": "deserializeMethod2"
|
||||
}
|
||||
}
|
||||
|
||||
8
spec/fixtures/packages/package-with-prefixed-and-suffixed-repo-url/package.json
vendored
Normal file
8
spec/fixtures/packages/package-with-prefixed-and-suffixed-repo-url/package.json
vendored
Normal 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."
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function (state) {
|
||||
return {state: state}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"main": "./index",
|
||||
"version": "1.0.0",
|
||||
"deserializers": {
|
||||
"DeserializerFromPackageWithViewProviders": "./deserializer"
|
||||
"DeserializerFromPackageWithViewProviders": "theDeserializerMethod"
|
||||
},
|
||||
"viewProviders": [
|
||||
"./view-provider-1",
|
||||
"./view-provider-2"
|
||||
"viewProviderMethod1",
|
||||
"viewProviderMethod2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
13
spec/fixtures/sample-with-comments.js
vendored
13
spec/fixtures/sample-with-comments.js
vendored
@@ -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));
|
||||
};
|
||||
};
|
||||
|
||||
2
spec/fixtures/shebang
vendored
2
spec/fixtures/shebang
vendored
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
puts "America – fuck yeah!"
|
||||
puts "Atom fixture test"
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
34
spec/pane-axis-element-spec.coffee
Normal file
34
spec/pane-axis-element-spec.coffee
Normal 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]))
|
||||
@@ -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]
|
||||
|
||||
@@ -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)", ->
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
57
spec/spawner-spec.coffee
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
61
spec/state-store-spec.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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(' ')
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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'})
|
||||
|
||||
49
spec/text-editor-registry-spec.coffee
Normal file
49
spec/text-editor-registry-spec.coffee
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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("B")).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, "B")).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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
103
spec/tokenized-buffer-iterator-spec.js
Normal file
103
spec/tokenized-buffer-iterator-spec.js
Normal 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([])
|
||||
})
|
||||
})
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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, '\\$&')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user