mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge branch 'master' into mb-use-language-mode-api
This commit is contained in:
@@ -36,7 +36,7 @@ This project and everyone participating in it is governed by the [Atom Code of C
|
||||
|
||||
## I don't want to read this whole thing I just have a question!!!
|
||||
|
||||
> **Note:** [Please don't file an issue to ask a question.](http://blog.atom.io/2016/04/19/managing-the-deluge-of-atom-issues.html) You'll get faster results by using the resources below.
|
||||
> **Note:** [Please don't file an issue to ask a question.](https://blog.atom.io/2016/04/19/managing-the-deluge-of-atom-issues.html) You'll get faster results by using the resources below.
|
||||
|
||||
We have an official message board with a detailed FAQ and where the community chimes in with helpful advice if you have questions.
|
||||
|
||||
@@ -45,7 +45,7 @@ We have an official message board with a detailed FAQ and where the community ch
|
||||
|
||||
If chat is more your speed, you can join the Atom and Electron Slack team:
|
||||
|
||||
* [Join the Atom and Electron Slack Team](http://atom-slack.herokuapp.com/)
|
||||
* [Join the Atom and Electron Slack Team](https://atom-slack.herokuapp.com/)
|
||||
* Even though Slack is a chat service, sometimes it takes several hours for community members to respond — please be patient!
|
||||
* Use the `#atom` channel for general questions or discussion about Atom
|
||||
* Use the `#electron` channel for questions about Electron
|
||||
@@ -119,7 +119,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
|
||||
|
||||
#### Before Submitting A Bug Report
|
||||
|
||||
* **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 [debugging guide](https://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](https://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](https://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](https://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 **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
|
||||
@@ -135,15 +135,15 @@ Explain the problem and include additional details to help maintainers reproduce
|
||||
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
|
||||
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
|
||||
* **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 macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
* **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](https://www.cockos.com/licecap/) to record GIFs on macOS 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 macOS, 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 or memory**, include a [CPU profile capture](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance) with your report.
|
||||
* **If Chrome's developer tools pane is shown without you triggering it**, that normally means that you have a syntax error in one of your themes or in your `styles.less`. Try running in [Safe Mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode) and using a different theme or comment out the contents of your `styles.less` to see if that fixes the problem.
|
||||
* **If the problem is related to performance or memory**, include a [CPU profile capture](https://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance) with your report.
|
||||
* **If Chrome's developer tools pane is shown without you triggering it**, that normally means that you have a syntax error in one of your themes or in your `styles.less`. Try running in [Safe Mode](https://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode) and using a different theme or comment out the contents of your `styles.less` to see if that fixes the problem.
|
||||
* **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](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance-problems-with-the-dev-tools-cpu-profiler)?**
|
||||
* **Can you reproduce the problem in [safe mode](https://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.
|
||||
@@ -155,7 +155,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](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 [local configuration files](https://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?
|
||||
|
||||
@@ -167,7 +167,7 @@ Before creating enhancement suggestions, please check [this list](#before-submit
|
||||
|
||||
#### Before Submitting An Enhancement Suggestion
|
||||
|
||||
* **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 the [debugging guide](https://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](https://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](https://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.
|
||||
@@ -180,7 +180,7 @@ Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com
|
||||
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
|
||||
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
|
||||
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
|
||||
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Atom which the suggestion is related to. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Atom which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
* **Explain why this enhancement would be useful** to most Atom users and isn't something that can or should be implemented as a [community package](#atom-and-packages).
|
||||
* **List some other text editors or applications where this enhancement exists.**
|
||||
* **Specify which version of Atom you're using.** You can get the exact version by running `atom -v` in your terminal, or by starting Atom and running the `Application: About` command from the [Command Palette](https://github.com/atom/command-palette).
|
||||
@@ -195,11 +195,14 @@ Unsure where to begin contributing to Atom? You can start by looking through the
|
||||
|
||||
Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have.
|
||||
|
||||
If you want to read about using Atom or developing packages in Atom, the [Atom Flight Manual](http://flight-manual.atom.io) is free and available online. You can find the source to the manual in [atom/flight-manual.atom.io](https://github.com/atom/flight-manual.atom.io).
|
||||
If you want to read about using Atom or developing packages in Atom, the [Atom Flight Manual](https://flight-manual.atom.io) is free and available online. You can find the source to the manual in [atom/flight-manual.atom.io](https://github.com/atom/flight-manual.atom.io).
|
||||
|
||||
#### Local development
|
||||
|
||||
All packages can be developed locally. For instructions on how to do this, see [Contributing to Official Atom Packages][contributing-to-official-atom-packages] in the [Atom Flight Manual](http://flight-manual.atom.io).
|
||||
Atom Core and all packages can be developed locally. For instructions on how to do this, see the following sections in the [Atom Flight Manual](https://flight-manual.atom.io):
|
||||
|
||||
* [Hacking on Atom Core][hacking-on-atom-core]
|
||||
* [Contributing to Official Atom Packages][contributing-to-official-atom-packages]
|
||||
|
||||
### Pull Requests
|
||||
|
||||
@@ -207,10 +210,10 @@ All packages can be developed locally. For instructions on how to do this, see [
|
||||
* Do not include issue numbers in the PR title
|
||||
* Include screenshots and animated GIFs in your pull request whenever possible.
|
||||
* Follow the [JavaScript](#javascript-styleguide) and [CoffeeScript](#coffeescript-styleguide) styleguides.
|
||||
* Include thoughtfully-worded, well-structured [Jasmine](http://jasmine.github.io/) specs in the `./spec` folder. Run them using `atom --test spec`. See the [Specs Styleguide](#specs-styleguide) below.
|
||||
* Include thoughtfully-worded, well-structured [Jasmine](https://jasmine.github.io/) specs in the `./spec` folder. Run them using `atom --test spec`. See the [Specs Styleguide](#specs-styleguide) below.
|
||||
* Document new code based on the [Documentation Styleguide](#documentation-styleguide)
|
||||
* End all files with a newline
|
||||
* [Avoid platform-dependent code](http://flight-manual.atom.io/hacking-atom/sections/cross-platform-compatibility/)
|
||||
* [Avoid platform-dependent code](https://flight-manual.atom.io/hacking-atom/sections/cross-platform-compatibility/)
|
||||
* Place requires in the following order:
|
||||
* Built in Node Modules (such as `path`)
|
||||
* Built in Atom and Electron Modules (such as `atom`, `remote`)
|
||||
@@ -247,7 +250,7 @@ All packages can be developed locally. For instructions on how to do this, see [
|
||||
|
||||
### JavaScript Styleguide
|
||||
|
||||
All JavaScript must adhere to [JavaScript Standard Style](http://standardjs.com/).
|
||||
All JavaScript must adhere to [JavaScript Standard Style](https://standardjs.com/).
|
||||
|
||||
* Prefer the object spread operator (`{...anotherObj}`) to `Object.assign()`
|
||||
* Inline `export`s with expressions whenever possible
|
||||
@@ -289,7 +292,7 @@ All JavaScript must adhere to [JavaScript Standard Style](http://standardjs.com/
|
||||
|
||||
### Specs Styleguide
|
||||
|
||||
- Include thoughtfully-worded, well-structured [Jasmine](http://jasmine.github.io/) specs in the `./spec` folder.
|
||||
- Include thoughtfully-worded, well-structured [Jasmine](https://jasmine.github.io/) specs in the `./spec` folder.
|
||||
- Treat `describe` as a noun or situation.
|
||||
- Treat `it` as a statement about state or how an operation changes state.
|
||||
|
||||
@@ -366,7 +369,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 macOS. |
|
||||
| `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/)). |
|
||||
| `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](https://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. |
|
||||
@@ -491,4 +494,5 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
|
||||
|
||||
[beginner]:https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc
|
||||
[help-wanted]:https://github.com/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc+-label%3Abeginner
|
||||
[contributing-to-official-atom-packages]:http://flight-manual.atom.io/hacking-atom/sections/contributing-to-official-atom-packages/
|
||||
[contributing-to-official-atom-packages]:https://flight-manual.atom.io/hacking-atom/sections/contributing-to-official-atom-packages/
|
||||
[hacking-on-atom-core]: https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/
|
||||
|
||||
@@ -81,10 +81,10 @@ repeat these steps to upgrade to future releases.
|
||||
|
||||
## Building
|
||||
|
||||
* [Linux](./docs/build-instructions/linux.md)
|
||||
* [macOS](./docs/build-instructions/macOS.md)
|
||||
* [FreeBSD](./docs/build-instructions/freebsd.md)
|
||||
* [Windows](./docs/build-instructions/windows.md)
|
||||
* [Linux](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-linux)
|
||||
* [macOS](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac)
|
||||
* [Windows](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
If you're looking for support for Atom there are a lot of options, check out:
|
||||
|
||||
* User Documentation — [The Atom Flight Manual](http://flight-manual.atom.io)
|
||||
* User Documentation — [The Atom Flight Manual](https://flight-manual.atom.io)
|
||||
* Developer Documentation — [Atom API Documentation](https://atom.io/docs/api/latest)
|
||||
* FAQ — [The Atom FAQ on Discuss](https://discuss.atom.io/c/faq)
|
||||
* Message Board — [Discuss, the official Atom and Electron message board](https://discuss.atom.io)
|
||||
* Chat — [Join the Atom Slack team](http://atom-slack.herokuapp.com/)
|
||||
* Chat — [Join the Atom Slack team](https://atom-slack.herokuapp.com/)
|
||||
|
||||
On Discuss and in the Atom Slack team, there are a bunch of helpful community members that should be willing to point you in the right direction.
|
||||
|
||||
@@ -1,130 +1 @@
|
||||
# Linux
|
||||
|
||||
Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
|
||||
## Requirements
|
||||
|
||||
* OS with 64-bit or 32-bit architecture
|
||||
* C++11 toolchain
|
||||
* Git
|
||||
* Node.js 6.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
|
||||
* npm 3.10.x or later (run `npm install -g npm`)
|
||||
* Ensure node-gyp uses python2 (run `npm config set python /usr/bin/python2 -g`, use `sudo` if you didn't install node via nvm)
|
||||
* Development headers for [libsecret](https://wiki.gnome.org/Projects/Libsecret).
|
||||
|
||||
For more details, scroll down to find how to setup a specific Linux distro.
|
||||
|
||||
## Instructions
|
||||
|
||||
```sh
|
||||
git clone https://github.com/atom/atom.git
|
||||
cd atom
|
||||
script/build
|
||||
```
|
||||
|
||||
To also install the newly built application, use `--create-debian-package` or `--create-rpm-package` and then install the generated package via the system package manager.
|
||||
|
||||
### `script/build` Options
|
||||
|
||||
* `--compress-artifacts`: zips the generated application as `out/atom-{arch}.tar.gz`.
|
||||
* `--create-debian-package`: creates a .deb package as `out/atom-{arch}.deb`
|
||||
* `--create-rpm-package`: creates a .rpm package as `out/atom-{arch}.rpm`
|
||||
* `--install[=dir]`: installs the application in `${dir}`; `${dir}` defaults to `/usr/local`.
|
||||
|
||||
### Ubuntu / Debian
|
||||
|
||||
* Install GNOME headers and other basic prerequisites:
|
||||
|
||||
```sh
|
||||
sudo apt-get install build-essential git libsecret-1-dev fakeroot rpm libx11-dev libxkbfile-dev
|
||||
```
|
||||
|
||||
* If `script/build` exits with an error, you may need to install a newer C++ compiler with C++11:
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-5 g++-5
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5
|
||||
sudo update-alternatives --config gcc # choose gcc-5 from the list
|
||||
```
|
||||
|
||||
### Fedora 22+
|
||||
|
||||
* `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel`
|
||||
|
||||
### Fedora 21 / CentOS / RHEL
|
||||
|
||||
* `sudo yum install -y make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools`
|
||||
|
||||
### Arch
|
||||
|
||||
* `sudo pacman -S --needed gconf base-devel git nodejs npm libsecret python2 libx11 libxkbfile`
|
||||
* `export PYTHON=/usr/bin/python2` before building Atom.
|
||||
|
||||
### Slackware
|
||||
|
||||
* `sbopkg -k -i node -i atom`
|
||||
|
||||
### openSUSE
|
||||
|
||||
* `sudo zypper install nodejs nodejs-devel make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel`
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### TypeError: Unable to watch path
|
||||
|
||||
If you get following error with a big traceback right after Atom starts:
|
||||
|
||||
```
|
||||
TypeError: Unable to watch path
|
||||
```
|
||||
|
||||
you have to increase number of watched files by inotify. For testing if
|
||||
this is the reason for this error you can issue
|
||||
|
||||
```sh
|
||||
sudo sysctl fs.inotify.max_user_watches=32768
|
||||
```
|
||||
|
||||
and restart Atom. If Atom now works fine, you can make this setting permanent:
|
||||
|
||||
```sh
|
||||
echo 32768 | sudo tee -a /proc/sys/fs/inotify/max_user_watches
|
||||
```
|
||||
|
||||
See also [#2082](https://github.com/atom/atom/issues/2082).
|
||||
|
||||
### /usr/bin/env: node: No such file or directory
|
||||
|
||||
If you get this notice when attempting to run any script, you either do not have
|
||||
Node.js installed, or node isn't identified as Node.js on your machine. If it's
|
||||
the latter, this might be caused by installing Node.js via the distro package
|
||||
manager and not nvm, so entering `sudo ln -s /usr/bin/nodejs /usr/bin/node` into
|
||||
your terminal may fix the issue. On some variants (mostly Debian based distros)
|
||||
you can use `update-alternatives` too:
|
||||
|
||||
```sh
|
||||
sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 1 --slave /usr/bin/js js /usr/bin/nodejs
|
||||
```
|
||||
|
||||
### AttributeError: 'module' object has no attribute 'script_main'
|
||||
|
||||
If you get following error with a big traceback while building Atom:
|
||||
|
||||
```
|
||||
sys.exit(gyp.script_main()) AttributeError: 'module' object has no attribute 'script_main' gyp ERR!
|
||||
```
|
||||
|
||||
you need to uninstall the system version of gyp.
|
||||
|
||||
On Fedora you would do the following:
|
||||
|
||||
```sh
|
||||
sudo yum remove gyp
|
||||
```
|
||||
|
||||
### Linux build error reports in atom/atom
|
||||
* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues)
|
||||
to get a list of reports about build errors on Linux.
|
||||
See the [Hacking on Atom Core](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-linux) section in the [Atom Flight Manual](http://flight-manual.atom.io).
|
||||
|
||||
@@ -1,29 +1 @@
|
||||
# macOS
|
||||
|
||||
## Requirements
|
||||
|
||||
* macOS 10.8 or later
|
||||
* Node.js 6.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
|
||||
* npm 3.10.x or later (run `npm install -g npm`)
|
||||
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
|
||||
|
||||
## Instructions
|
||||
|
||||
```sh
|
||||
git clone https://github.com/atom/atom.git
|
||||
cd atom
|
||||
script/build
|
||||
```
|
||||
|
||||
To also install the newly built application, use `script/build --install`.
|
||||
|
||||
### `script/build` Options
|
||||
|
||||
* `--code-sign`: signs the application with the GitHub certificate specified in `$ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL`.
|
||||
* `--compress-artifacts`: zips the generated application as `out/atom-mac.zip`.
|
||||
* `--install[=dir]`: installs the application at `${dir}/Atom.app` for dev and stable versions or at `${dir}/Atom-Beta.app` for beta versions; `${dir}` defaults to `/Applications`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### macOS build error reports in atom/atom
|
||||
* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Amac&type=Issues) to get a list of reports about build errors on macOS.
|
||||
See the [Hacking on Atom Core](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-mac) section in the [Atom Flight Manual](http://flight-manual.atom.io).
|
||||
|
||||
@@ -1,90 +1 @@
|
||||
# Windows
|
||||
|
||||
## Requirements
|
||||
|
||||
* Node.js 6.9.4 or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)
|
||||
* Python v2.7.x
|
||||
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`. If it is installed elsewhere create a symbolic link to the directory containing the python.exe using: `mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27`
|
||||
* 7zip (7z.exe available from the command line) - for creating distribution zip files
|
||||
* Visual Studio, either:
|
||||
* [Visual C++ Build Tools 2015](http://landinghub.visualstudio.com/visual-cpp-build-tools)
|
||||
* [Visual Studio 2013 Update 5](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express Edition or better)
|
||||
* [Visual Studio 2015](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community Edition or better)
|
||||
|
||||
Also ensure that:
|
||||
* The default installation folder is chosen so the build tools can find it
|
||||
* If using Visual Studio make sure Visual C++ support is selected/installed
|
||||
* If using Visual C++ Build Tools make sure Windows 8 SDK is selected/installed
|
||||
* A `git` command is in your path
|
||||
* Set the `GYP_MSVS_VERSION` environment variable to the Visual Studio/Build Tools version (`2013` or `2015`) e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell (or set it in Windows advanced system settings).
|
||||
|
||||
## Instructions
|
||||
|
||||
You can run these commands using Command Prompt, PowerShell, Git Shell, or any other terminal. These instructions will assume the use of Command Prompt.
|
||||
|
||||
```
|
||||
cd C:\
|
||||
git clone https://github.com/atom/atom.git
|
||||
cd atom
|
||||
script\build
|
||||
```
|
||||
|
||||
To also install the newly built application, use `script\build --create-windows-installer` and launch the generated installers.
|
||||
|
||||
### `script\build` Options
|
||||
* `--code-sign`: signs the application with the GitHub certificate specified in `$WIN_P12KEY_URL`.
|
||||
* `--compress-artifacts`: zips the generated application as `out\atom-windows.zip` (requires [7-Zip](http://www.7-zip.org)).
|
||||
* `--create-windows-installer`: creates an `.msi`, an `.exe` and two `.nupkg` packages in the `out` directory.
|
||||
* `--install[=dir]`: installs the application in `${dir}\Atom\app-dev`; `${dir}` defaults to `%LOCALAPPDATA%`.
|
||||
|
||||
### Running tests
|
||||
|
||||
In order to run tests from command line you need `apm`, available after you install Atom or after you build from source. If you installed it, run the following commands (assuming `C:\atom` is the root of your Atom repository):
|
||||
|
||||
```bash
|
||||
cd C:\atom
|
||||
apm test
|
||||
```
|
||||
|
||||
When building Atom from source, the `apm` command is not added to the system path by default. In this case, you can either add it yourself or explicitly list the complete path in previous commands. The default install location is `%LOCALAPPDATA%\Atom\app-dev\resources\cli\`.
|
||||
|
||||
**NOTE**: Please keep in mind that there are still some tests that don't pass on Windows.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Errors
|
||||
* `node is not recognized`
|
||||
* If you just installed Node.js, you'll need to restart Command Prompt before the `node` command is available on your path.
|
||||
|
||||
* `msbuild.exe failed with exit code: 1`
|
||||
* If using **Visual Studio**, ensure you have the **Visual C++** component installed. Go into Add/Remove Programs, select Visual Studio, press Modify, and then check the Visual C++ box.
|
||||
* If using **Visual C++ Build Tools**, ensure you have the **Windows 8 SDK** component installed. Go into Add/Remove Programs, select Visual C++ Build Tools, press Modify and then check the Windows 8 SDK 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. For example, use `C:\atom` instead of `C:\my stuff\atom`.
|
||||
* 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
|
||||
|
||||
* `'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.`
|
||||
* Try setting the `GYP_MSVS_VERSION` environment variable to **2013** or **2015** depending on what version of Visual Studio/Build Tools is installed and then `script\clean` followed by `script\build` (re-open the Command Prompt if you set the variable using the GUI).
|
||||
|
||||
* `'node-gyp' is not recognized as an internal or external command, operable program or batch file.`
|
||||
* Try running `npm install -g node-gyp`, and run `script\build` again.
|
||||
|
||||
* 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 cases.
|
||||
|
||||
### Windows build error reports in atom/atom
|
||||
* If all fails, use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues) to get a list of reports about build errors on Windows, and see if yours has already been reported.
|
||||
* If it hasn't, please open a new issue with your Windows version, architecture (x86 or x64), and a screenshot of your build output, including the Node.js and Python versions.
|
||||
See the [Hacking on Atom Core](http://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/#platform-windows) section in the [Atom Flight Manual](http://flight-manual.atom.io).
|
||||
|
||||
20
package.json
20
package.json
@@ -70,7 +70,7 @@
|
||||
"service-hub": "^0.7.4",
|
||||
"sinon": "1.17.4",
|
||||
"temp": "^0.8.3",
|
||||
"text-buffer": "13.9.0-language-modes-2",
|
||||
"text-buffer": "13.9.0-language-modes-3",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"winreg": "^1.2.1",
|
||||
@@ -94,7 +94,7 @@
|
||||
"autocomplete-atom-api": "0.10.5",
|
||||
"autocomplete-css": "0.17.4",
|
||||
"autocomplete-html": "0.8.3",
|
||||
"autocomplete-plus": "2.37.2",
|
||||
"autocomplete-plus": "2.37.5",
|
||||
"autocomplete-snippets": "1.11.2",
|
||||
"autoflow": "0.29.0",
|
||||
"autosave": "0.24.6",
|
||||
@@ -104,12 +104,12 @@
|
||||
"command-palette": "0.42.0",
|
||||
"dalek": "0.2.1",
|
||||
"deprecation-cop": "0.56.9",
|
||||
"dev-live-reload": "0.47.1",
|
||||
"dev-live-reload": "0.48.1",
|
||||
"encoding-selector": "0.23.7",
|
||||
"exception-reporting": "0.41.5",
|
||||
"find-and-replace": "0.213.0",
|
||||
"find-and-replace": "0.214.0",
|
||||
"fuzzy-finder": "1.7.3",
|
||||
"github": "0.8.1",
|
||||
"github": "0.8.2",
|
||||
"git-diff": "1.3.6",
|
||||
"go-to-line": "0.32.1",
|
||||
"grammar-selector": "0.49.8",
|
||||
@@ -117,12 +117,12 @@
|
||||
"incompatible-packages": "0.27.3",
|
||||
"keybinding-resolver": "0.38.1",
|
||||
"line-ending-selector": "0.7.4",
|
||||
"link": "0.31.3",
|
||||
"link": "0.31.4",
|
||||
"markdown-preview": "0.159.18",
|
||||
"metrics": "1.2.6",
|
||||
"notifications": "0.69.2",
|
||||
"open-on-github": "1.3.0",
|
||||
"package-generator": "1.1.1",
|
||||
"package-generator": "1.2.0",
|
||||
"settings-view": "0.253.0",
|
||||
"snippets": "1.1.9",
|
||||
"spell-check": "0.72.3",
|
||||
@@ -131,8 +131,8 @@
|
||||
"symbols-view": "0.118.1",
|
||||
"tabs": "0.109.1",
|
||||
"timecop": "0.36.2",
|
||||
"tree-view": "0.221.2",
|
||||
"update-package-dependencies": "0.12.0",
|
||||
"tree-view": "0.222.0",
|
||||
"update-package-dependencies": "0.13.0",
|
||||
"welcome": "0.36.5",
|
||||
"whitespace": "0.37.5",
|
||||
"wrap-guide": "0.40.2",
|
||||
@@ -166,7 +166,7 @@
|
||||
"language-text": "0.7.3",
|
||||
"language-todo": "0.29.3",
|
||||
"language-toml": "0.18.1",
|
||||
"language-typescript": "0.2.2",
|
||||
"language-typescript": "0.2.3",
|
||||
"language-xml": "0.35.2",
|
||||
"language-yaml": "0.31.1"
|
||||
},
|
||||
|
||||
18
script/test
18
script/test
@@ -3,6 +3,7 @@
|
||||
'use strict'
|
||||
|
||||
require('colors')
|
||||
const argv = require('yargs').argv
|
||||
const assert = require('assert')
|
||||
const async = require('async')
|
||||
const childProcess = require('child_process')
|
||||
@@ -150,17 +151,26 @@ function runBenchmarkTests (callback) {
|
||||
let testSuitesToRun = testSuitesForPlatform(process.platform)
|
||||
|
||||
function testSuitesForPlatform (platform) {
|
||||
let suites = [];
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
|
||||
suites = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
|
||||
break
|
||||
case 'win32':
|
||||
return (process.arch === 'x64') ? [runCoreMainProcessTests, runCoreRenderProcessTests] : [runCoreMainProcessTests]
|
||||
suites = (process.arch === 'x64') ? [runCoreMainProcessTests, runCoreRenderProcessTests] : [runCoreMainProcessTests]
|
||||
break
|
||||
case 'linux':
|
||||
return [runCoreMainProcessTests]
|
||||
suites = [runCoreMainProcessTests]
|
||||
break
|
||||
default:
|
||||
console.log(`Unrecognized platform: ${platform}`)
|
||||
return []
|
||||
}
|
||||
|
||||
if (argv.skipMainProcessTests) {
|
||||
suites = suites.filter(suite => suite !== runCoreMainProcessTests);
|
||||
}
|
||||
|
||||
return suites;
|
||||
}
|
||||
|
||||
async.series(testSuitesToRun, function (err, exitCodes) {
|
||||
|
||||
@@ -1,711 +0,0 @@
|
||||
_ = require 'underscore-plus'
|
||||
path = require 'path'
|
||||
temp = require('temp').track()
|
||||
AtomEnvironment = require '../src/atom-environment'
|
||||
StorageFolder = require '../src/storage-folder'
|
||||
|
||||
describe "AtomEnvironment", ->
|
||||
afterEach ->
|
||||
try
|
||||
temp.cleanupSync()
|
||||
|
||||
describe 'window sizing methods', ->
|
||||
describe '::getPosition and ::setPosition', ->
|
||||
originalPosition = null
|
||||
beforeEach ->
|
||||
originalPosition = atom.getPosition()
|
||||
|
||||
afterEach ->
|
||||
atom.setPosition(originalPosition.x, originalPosition.y)
|
||||
|
||||
it 'sets the position of the window, and can retrieve the position just set', ->
|
||||
atom.setPosition(22, 45)
|
||||
expect(atom.getPosition()).toEqual x: 22, y: 45
|
||||
|
||||
describe '::getSize and ::setSize', ->
|
||||
originalSize = null
|
||||
beforeEach ->
|
||||
originalSize = atom.getSize()
|
||||
afterEach ->
|
||||
atom.setSize(originalSize.width, originalSize.height)
|
||||
|
||||
it 'sets the size of the window, and can retrieve the size just set', ->
|
||||
newWidth = originalSize.width - 12
|
||||
newHeight = originalSize.height - 23
|
||||
waitsForPromise ->
|
||||
atom.setSize(newWidth, newHeight)
|
||||
runs ->
|
||||
expect(atom.getSize()).toEqual width: newWidth, height: newHeight
|
||||
|
||||
describe ".isReleasedVersion()", ->
|
||||
it "returns false if the version is a SHA and true otherwise", ->
|
||||
version = '0.1.0'
|
||||
spyOn(atom, 'getVersion').andCallFake -> version
|
||||
expect(atom.isReleasedVersion()).toBe true
|
||||
version = '36b5518'
|
||||
expect(atom.isReleasedVersion()).toBe false
|
||||
|
||||
describe "loading default config", ->
|
||||
it 'loads the default core config schema', ->
|
||||
expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe true
|
||||
expect(atom.config.get('core.followSymlinks')).toBe true
|
||||
expect(atom.config.get('editor.showInvisibles')).toBe false
|
||||
|
||||
describe "window onerror handler", ->
|
||||
devToolsPromise = null
|
||||
beforeEach ->
|
||||
devToolsPromise = Promise.resolve()
|
||||
spyOn(atom, 'openDevTools').andReturn(devToolsPromise)
|
||||
spyOn(atom, 'executeJavaScriptInDevTools')
|
||||
|
||||
it "will open the dev tools when an error is triggered", ->
|
||||
try
|
||||
a + 1
|
||||
catch e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
|
||||
waitsForPromise -> devToolsPromise
|
||||
runs ->
|
||||
expect(atom.openDevTools).toHaveBeenCalled()
|
||||
expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled()
|
||||
|
||||
describe "::onWillThrowError", ->
|
||||
willThrowSpy = null
|
||||
beforeEach ->
|
||||
willThrowSpy = jasmine.createSpy()
|
||||
|
||||
it "is called when there is an error", ->
|
||||
error = null
|
||||
atom.onWillThrowError(willThrowSpy)
|
||||
try
|
||||
a + 1
|
||||
catch e
|
||||
error = e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
|
||||
delete willThrowSpy.mostRecentCall.args[0].preventDefault
|
||||
expect(willThrowSpy).toHaveBeenCalledWith
|
||||
message: error.toString()
|
||||
url: 'abc'
|
||||
line: 2
|
||||
column: 3
|
||||
originalError: error
|
||||
|
||||
it "will not show the devtools when preventDefault() is called", ->
|
||||
willThrowSpy.andCallFake (errorObject) -> errorObject.preventDefault()
|
||||
atom.onWillThrowError(willThrowSpy)
|
||||
|
||||
try
|
||||
a + 1
|
||||
catch e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
|
||||
expect(willThrowSpy).toHaveBeenCalled()
|
||||
expect(atom.openDevTools).not.toHaveBeenCalled()
|
||||
expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled()
|
||||
|
||||
describe "::onDidThrowError", ->
|
||||
didThrowSpy = null
|
||||
beforeEach ->
|
||||
didThrowSpy = jasmine.createSpy()
|
||||
|
||||
it "is called when there is an error", ->
|
||||
error = null
|
||||
atom.onDidThrowError(didThrowSpy)
|
||||
try
|
||||
a + 1
|
||||
catch e
|
||||
error = e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
expect(didThrowSpy).toHaveBeenCalledWith
|
||||
message: error.toString()
|
||||
url: 'abc'
|
||||
line: 2
|
||||
column: 3
|
||||
originalError: error
|
||||
|
||||
describe ".assert(condition, message, callback)", ->
|
||||
errors = null
|
||||
|
||||
beforeEach ->
|
||||
errors = []
|
||||
spyOn(atom, 'isReleasedVersion').andReturn(true)
|
||||
atom.onDidFailAssertion (error) -> errors.push(error)
|
||||
|
||||
describe "if the condition is false", ->
|
||||
it "notifies onDidFailAssertion handlers with an error object based on the call site of the assertion", ->
|
||||
result = atom.assert(false, "a == b")
|
||||
expect(result).toBe false
|
||||
expect(errors.length).toBe 1
|
||||
expect(errors[0].message).toBe "Assertion failed: a == b"
|
||||
expect(errors[0].stack).toContain('atom-environment-spec')
|
||||
|
||||
describe "if passed a callback function", ->
|
||||
it "calls the callback with the assertion failure's error object", ->
|
||||
error = null
|
||||
atom.assert(false, "a == b", (e) -> error = e)
|
||||
expect(error).toBe errors[0]
|
||||
|
||||
describe "if passed metadata", ->
|
||||
it "assigns the metadata on the assertion failure's error object", ->
|
||||
atom.assert(false, "a == b", {foo: 'bar'})
|
||||
expect(errors[0].metadata).toEqual {foo: 'bar'}
|
||||
|
||||
describe "when Atom has been built from source", ->
|
||||
it "throws an error", ->
|
||||
atom.isReleasedVersion.andReturn(false)
|
||||
expect(-> atom.assert(false, 'testing')).toThrow('Assertion failed: testing')
|
||||
|
||||
describe "if the condition is true", ->
|
||||
it "does nothing", ->
|
||||
result = atom.assert(true, "a == b")
|
||||
expect(result).toBe true
|
||||
expect(errors).toEqual []
|
||||
|
||||
describe "saving and loading", ->
|
||||
beforeEach ->
|
||||
atom.enablePersistence = true
|
||||
|
||||
afterEach ->
|
||||
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(),
|
||||
initialPaths: [dir1]
|
||||
windowState: null
|
||||
|
||||
spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings
|
||||
spyOn(atom, 'serialize').andReturn({stuff: 'cool'})
|
||||
|
||||
atom.project.setPaths([dir1, dir2])
|
||||
# State persistence will fail if other Atom instances are running
|
||||
waitsForPromise ->
|
||||
atom.stateStore.connect().then (isConnected) ->
|
||||
expect(isConnected).toBe true
|
||||
|
||||
waitsForPromise ->
|
||||
atom.saveState().then ->
|
||||
atom.loadState().then (state) ->
|
||||
expect(state).toBeFalsy()
|
||||
|
||||
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", ->
|
||||
atomEnv = new AtomEnvironment({
|
||||
applicationDelegate: global.atom.applicationDelegate,
|
||||
})
|
||||
idleCallbacks = []
|
||||
atomEnv.initialize({
|
||||
window: {
|
||||
requestIdleCallback: (callback) -> idleCallbacks.push(callback),
|
||||
addEventListener: ->
|
||||
removeEventListener: ->
|
||||
},
|
||||
document: document.implementation.createHTMLDocument()
|
||||
})
|
||||
|
||||
spyOn(atomEnv, 'saveState')
|
||||
|
||||
keydown = new KeyboardEvent('keydown')
|
||||
atomEnv.document.dispatchEvent(keydown)
|
||||
advanceClock atomEnv.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atomEnv.saveState.reset()
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
advanceClock atomEnv.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atomEnv.destroy()
|
||||
|
||||
it "ignores mousedown/keydown events happening after calling unloadEditorWindow", ->
|
||||
atomEnv = new AtomEnvironment({
|
||||
applicationDelegate: global.atom.applicationDelegate,
|
||||
})
|
||||
idleCallbacks = []
|
||||
atomEnv.initialize({
|
||||
window: {
|
||||
requestIdleCallback: (callback) -> idleCallbacks.push(callback),
|
||||
addEventListener: ->
|
||||
removeEventListener: ->
|
||||
},
|
||||
document: document.implementation.createHTMLDocument()
|
||||
})
|
||||
|
||||
spyOn(atomEnv, 'saveState')
|
||||
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
atomEnv.unloadEditorWindow()
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock atomEnv.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
advanceClock atomEnv.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
|
||||
atomEnv.destroy()
|
||||
|
||||
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'})
|
||||
|
||||
it "serializes the text editor registry", ->
|
||||
editor = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js').then (e) -> editor = e
|
||||
|
||||
waitsForPromise ->
|
||||
atom.textEditors.setGrammarOverride(editor, 'text.plain')
|
||||
|
||||
atom2 = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
window: document.createElement('div'),
|
||||
document: Object.assign(
|
||||
document.createElement('div'),
|
||||
{
|
||||
body: document.createElement('div'),
|
||||
head: document.createElement('div'),
|
||||
}
|
||||
)
|
||||
})
|
||||
atom2.initialize({document, window})
|
||||
atom2.deserialize(atom.serialize()).then ->
|
||||
expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain')
|
||||
atom2.destroy()
|
||||
|
||||
describe "deserialization failures", ->
|
||||
|
||||
it "propagates project state restoration failures", ->
|
||||
spyOn(atom.project, 'deserialize').andCallFake ->
|
||||
err = new Error('deserialization failure')
|
||||
err.missingProjectPaths = ['/foo']
|
||||
Promise.reject(err)
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
waitsForPromise -> atom.deserialize({project: 'should work'})
|
||||
runs ->
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith 'Unable to open project directory',
|
||||
{description: 'Project directory `/foo` is no longer on disk.'}
|
||||
|
||||
it "accumulates and reports two errors with one notification", ->
|
||||
spyOn(atom.project, 'deserialize').andCallFake ->
|
||||
err = new Error('deserialization failure')
|
||||
err.missingProjectPaths = ['/foo', '/wat']
|
||||
Promise.reject(err)
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
waitsForPromise -> atom.deserialize({project: 'should work'})
|
||||
runs ->
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith 'Unable to open 2 project directories',
|
||||
{description: 'Project directories `/foo` and `/wat` are no longer on disk.'}
|
||||
|
||||
it "accumulates and reports three+ errors with one notification", ->
|
||||
spyOn(atom.project, 'deserialize').andCallFake ->
|
||||
err = new Error('deserialization failure')
|
||||
err.missingProjectPaths = ['/foo', '/wat', '/stuff', '/things']
|
||||
Promise.reject(err)
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
waitsForPromise -> atom.deserialize({project: 'should work'})
|
||||
runs ->
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith 'Unable to open 4 project directories',
|
||||
{description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'}
|
||||
|
||||
describe "openInitialEmptyEditorIfNecessary", ->
|
||||
describe "when there are no paths set", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'getLoadSettings').andReturn(initialPaths: [])
|
||||
|
||||
it "opens an empty buffer", ->
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(null)
|
||||
|
||||
describe "when there is already a buffer open", ->
|
||||
beforeEach ->
|
||||
waitsForPromise -> atom.workspace.open()
|
||||
|
||||
it "does not open an empty buffer", ->
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
|
||||
describe "when the project has a path", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'getLoadSettings').andReturn(initialPaths: ['something'])
|
||||
spyOn(atom.workspace, 'open')
|
||||
|
||||
it "does not open an empty buffer", ->
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
|
||||
describe "adding a project folder", ->
|
||||
it "does nothing if the user dismisses the file picker", ->
|
||||
initialPaths = atom.project.getPaths()
|
||||
tempDirectory = temp.mkdirSync("a-new-directory")
|
||||
spyOn(atom, "pickFolder").andCallFake (callback) -> callback(null)
|
||||
atom.addProjectFolder()
|
||||
expect(atom.project.getPaths()).toEqual(initialPaths)
|
||||
|
||||
describe "when there is no saved state for the added folders", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'loadState').andReturn(Promise.resolve(null))
|
||||
spyOn(atom, 'attemptRestoreProjectStateForPaths')
|
||||
|
||||
it "adds the selected folder to the project", ->
|
||||
initialPaths = atom.project.setPaths([])
|
||||
tempDirectory = temp.mkdirSync("a-new-directory")
|
||||
spyOn(atom, "pickFolder").andCallFake (callback) ->
|
||||
callback([tempDirectory])
|
||||
waitsForPromise ->
|
||||
atom.addProjectFolder()
|
||||
runs ->
|
||||
expect(atom.project.getPaths()).toEqual([tempDirectory])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
|
||||
describe "when there is saved state for the relevant directories", ->
|
||||
state = Symbol('savedState')
|
||||
|
||||
beforeEach ->
|
||||
spyOn(atom, "getStateKey").andCallFake (dirs) -> dirs.join(':')
|
||||
spyOn(atom, "loadState").andCallFake (key) ->
|
||||
if key is __dirname then Promise.resolve(state) else Promise.resolve(null)
|
||||
spyOn(atom, "attemptRestoreProjectStateForPaths")
|
||||
spyOn(atom, "pickFolder").andCallFake (callback) ->
|
||||
callback([__dirname])
|
||||
atom.project.setPaths([])
|
||||
|
||||
describe "when there are no project folders", ->
|
||||
it "attempts to restore the project state", ->
|
||||
waitsForPromise ->
|
||||
atom.addProjectFolder()
|
||||
runs ->
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
|
||||
describe "when there are already project folders", ->
|
||||
openedPath = path.join(__dirname, 'fixtures')
|
||||
beforeEach ->
|
||||
atom.project.setPaths([openedPath])
|
||||
|
||||
it "does not attempt to restore the project state, instead adding the project paths", ->
|
||||
waitsForPromise ->
|
||||
atom.addProjectFolder()
|
||||
runs ->
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
expect(atom.project.getPaths()).toEqual([openedPath, __dirname])
|
||||
|
||||
describe "attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)", ->
|
||||
describe "when the window is clean (empty or has only unnamed, unmodified buffers)", ->
|
||||
beforeEach ->
|
||||
# Unnamed, unmodified buffer doesn't count toward "clean"-ness
|
||||
waitsForPromise -> atom.workspace.open()
|
||||
|
||||
it "automatically restores the saved state into the current environment", ->
|
||||
state = Symbol()
|
||||
spyOn(atom.workspace, 'open')
|
||||
spyOn(atom, 'restoreStateIntoThisEnvironment')
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.restoreStateIntoThisEnvironment).toHaveBeenCalledWith(state)
|
||||
expect(atom.workspace.open.callCount).toBe(1)
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(__filename)
|
||||
|
||||
describe "when a dock has a non-text editor", ->
|
||||
it "doesn't prompt the user to restore state", ->
|
||||
dock = atom.workspace.getLeftDock()
|
||||
dock.getActivePane().addItem
|
||||
getTitle: -> 'title'
|
||||
element: document.createElement 'div'
|
||||
state = Symbol()
|
||||
spyOn(atom, 'confirm')
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).not.toHaveBeenCalled()
|
||||
|
||||
describe "when the window is dirty", ->
|
||||
editor = null
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise -> atom.workspace.open().then (e) ->
|
||||
editor = e
|
||||
editor.setText('new editor')
|
||||
|
||||
describe "when a dock has a modified editor", ->
|
||||
it "prompts the user to restore the state", ->
|
||||
dock = atom.workspace.getLeftDock()
|
||||
dock.getActivePane().addItem editor
|
||||
spyOn(atom, "confirm").andReturn(1)
|
||||
spyOn(atom.project, 'addPath')
|
||||
spyOn(atom.workspace, 'open')
|
||||
state = Symbol()
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "prompts the user to restore the state in a new window, discarding it and adding folder to current window", ->
|
||||
spyOn(atom, "confirm").andReturn(1)
|
||||
spyOn(atom.project, 'addPath')
|
||||
spyOn(atom.workspace, 'open')
|
||||
state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(atom.project.addPath.callCount).toBe(1)
|
||||
expect(atom.project.addPath).toHaveBeenCalledWith(__dirname)
|
||||
expect(atom.workspace.open.callCount).toBe(1)
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(__filename)
|
||||
|
||||
it "prompts the user to restore the state in a new window, opening a new window", ->
|
||||
spyOn(atom, "confirm").andReturn(0)
|
||||
spyOn(atom, "open")
|
||||
state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(atom.open).toHaveBeenCalledWith
|
||||
pathsToOpen: [__dirname, __filename]
|
||||
newWindow: true
|
||||
devMode: atom.inDevMode()
|
||||
safeMode: atom.inSafeMode()
|
||||
|
||||
describe "::unloadEditorWindow()", ->
|
||||
it "saves the BlobStore so it can be loaded after reload", ->
|
||||
configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||
fakeBlobStore = jasmine.createSpyObj("blob store", ["save"])
|
||||
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true})
|
||||
atomEnvironment.initialize({configDirPath, blobStore: fakeBlobStore, window, document})
|
||||
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
|
||||
expect(fakeBlobStore.save).toHaveBeenCalled()
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
describe "::destroy()", ->
|
||||
it "does not throw exceptions when unsubscribing from ipc events (regression)", ->
|
||||
configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||
fakeDocument = {
|
||||
addEventListener: ->
|
||||
removeEventListener: ->
|
||||
head: document.createElement('head')
|
||||
body: document.createElement('body')
|
||||
}
|
||||
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate})
|
||||
atomEnvironment.initialize({window, document: fakeDocument})
|
||||
spyOn(atomEnvironment.packages, 'loadPackages').andReturn(Promise.resolve())
|
||||
spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve())
|
||||
spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve())
|
||||
waitsForPromise ->
|
||||
atomEnvironment.startEditorWindow()
|
||||
runs ->
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
atomEnvironment.destroy()
|
||||
|
||||
describe "::whenShellEnvironmentLoaded()", ->
|
||||
[atomEnvironment, envLoaded, spy] = []
|
||||
|
||||
beforeEach ->
|
||||
resolve = null
|
||||
promise = new Promise (r) -> resolve = r
|
||||
envLoaded = ->
|
||||
resolve()
|
||||
waitsForPromise -> promise
|
||||
atomEnvironment = new AtomEnvironment
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
updateProcessEnv: -> promise
|
||||
atomEnvironment.initialize({window, document})
|
||||
spy = jasmine.createSpy()
|
||||
|
||||
afterEach ->
|
||||
atomEnvironment.destroy()
|
||||
|
||||
it "is triggered once the shell environment is loaded", ->
|
||||
atomEnvironment.whenShellEnvironmentLoaded spy
|
||||
atomEnvironment.updateProcessEnvAndTriggerHooks()
|
||||
envLoaded()
|
||||
runs -> expect(spy).toHaveBeenCalled()
|
||||
|
||||
it "triggers the callback immediately if the shell environment is already loaded", ->
|
||||
atomEnvironment.updateProcessEnvAndTriggerHooks()
|
||||
envLoaded()
|
||||
runs ->
|
||||
atomEnvironment.whenShellEnvironmentLoaded spy
|
||||
expect(spy).toHaveBeenCalled()
|
||||
|
||||
describe "::openLocations(locations) (called via IPC from browser process)", ->
|
||||
beforeEach ->
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.project.setPaths([])
|
||||
|
||||
describe "when there is no saved state", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, "loadState").andReturn(Promise.resolve(null))
|
||||
|
||||
describe "when the opened path exists", ->
|
||||
it "adds it to the project's paths", ->
|
||||
pathToOpen = __filename
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}])
|
||||
runs -> 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')
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen: firstPathToOpen}])
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
|
||||
runs -> 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')
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}])
|
||||
runs -> expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
|
||||
describe "when the opened path is a file", ->
|
||||
it "opens it in the workspace", ->
|
||||
pathToOpen = __filename
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}])
|
||||
runs -> expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
|
||||
|
||||
describe "when the opened path is a directory", ->
|
||||
it "does not open it in the workspace", ->
|
||||
pathToOpen = __dirname
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}])
|
||||
runs -> expect(atom.workspace.open.callCount).toBe 0
|
||||
|
||||
describe "when the opened path is a uri", ->
|
||||
it "adds it to the project's paths as is", ->
|
||||
pathToOpen = 'remote://server:7644/some/dir/path'
|
||||
spyOn(atom.project, 'addPath')
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}])
|
||||
runs -> expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)
|
||||
|
||||
describe "when there is saved state for the relevant directories", ->
|
||||
state = Symbol('savedState')
|
||||
|
||||
beforeEach ->
|
||||
spyOn(atom, "getStateKey").andCallFake (dirs) -> dirs.join(':')
|
||||
spyOn(atom, "loadState").andCallFake (key) ->
|
||||
if key is __dirname then Promise.resolve(state) else Promise.resolve(null)
|
||||
spyOn(atom, "attemptRestoreProjectStateForPaths")
|
||||
|
||||
describe "when there are no project folders", ->
|
||||
it "attempts to restore the project state", ->
|
||||
pathToOpen = __dirname
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}])
|
||||
runs ->
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], [])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
|
||||
it "opens the specified files", ->
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}])
|
||||
runs ->
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
|
||||
|
||||
describe "when there are already project folders", ->
|
||||
beforeEach ->
|
||||
atom.project.setPaths([__dirname])
|
||||
|
||||
it "does not attempt to restore the project state, instead adding the project paths", ->
|
||||
pathToOpen = path.join(__dirname, 'fixtures')
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen, forceAddToWindow: true}])
|
||||
runs ->
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen])
|
||||
|
||||
it "opens the specified files", ->
|
||||
pathToOpen = path.join(__dirname, 'fixtures')
|
||||
fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt')
|
||||
waitsForPromise -> atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}])
|
||||
runs ->
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen])
|
||||
expect(atom.project.getPaths()).toEqual([__dirname])
|
||||
|
||||
describe "::updateAvailable(info) (called via IPC from browser process)", ->
|
||||
subscription = null
|
||||
|
||||
afterEach ->
|
||||
subscription?.dispose()
|
||||
|
||||
it "invokes onUpdateAvailable listeners", ->
|
||||
return unless process.platform is 'darwin' # Test tied to electron autoUpdater, we use something else on Linux and Win32
|
||||
|
||||
atom.listenForUpdates()
|
||||
|
||||
updateAvailableHandler = jasmine.createSpy("update-available-handler")
|
||||
subscription = atom.onUpdateAvailable updateAvailableHandler
|
||||
|
||||
autoUpdater = require('electron').remote.autoUpdater
|
||||
autoUpdater.emit 'update-downloaded', null, "notes", "version"
|
||||
|
||||
waitsFor ->
|
||||
updateAvailableHandler.callCount > 0
|
||||
|
||||
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'
|
||||
770
spec/atom-environment-spec.js
Normal file
770
spec/atom-environment-spec.js
Normal file
@@ -0,0 +1,770 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
const _ = require('underscore-plus')
|
||||
const path = require('path')
|
||||
const temp = require('temp').track()
|
||||
const AtomEnvironment = require('../src/atom-environment')
|
||||
const StorageFolder = require('../src/storage-folder')
|
||||
|
||||
describe('AtomEnvironment', () => {
|
||||
afterEach(() => {
|
||||
try {
|
||||
temp.cleanupSync()
|
||||
} catch (error) {}
|
||||
})
|
||||
|
||||
describe('window sizing methods', () => {
|
||||
describe('::getPosition and ::setPosition', () => {
|
||||
let originalPosition = null
|
||||
beforeEach(() => originalPosition = atom.getPosition())
|
||||
|
||||
afterEach(() => atom.setPosition(originalPosition.x, originalPosition.y))
|
||||
|
||||
it('sets the position of the window, and can retrieve the position just set', () => {
|
||||
atom.setPosition(22, 45)
|
||||
expect(atom.getPosition()).toEqual({x: 22, y: 45})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::getSize and ::setSize', () => {
|
||||
let originalSize = null
|
||||
beforeEach(() => originalSize = atom.getSize())
|
||||
afterEach(() => atom.setSize(originalSize.width, originalSize.height))
|
||||
|
||||
it('sets the size of the window, and can retrieve the size just set', async () => {
|
||||
const newWidth = originalSize.width - 12
|
||||
const newHeight = originalSize.height - 23
|
||||
await 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', () => {
|
||||
let version = '0.1.0'
|
||||
spyOn(atom, 'getVersion').andCallFake(() => version)
|
||||
expect(atom.isReleasedVersion()).toBe(true)
|
||||
version = '36b5518'
|
||||
expect(atom.isReleasedVersion()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loading default config', () => {
|
||||
it('loads the default core config schema', () => {
|
||||
expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe(true)
|
||||
expect(atom.config.get('core.followSymlinks')).toBe(true)
|
||||
expect(atom.config.get('editor.showInvisibles')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('window onerror handler', () => {
|
||||
let devToolsPromise = null
|
||||
beforeEach(() => {
|
||||
devToolsPromise = Promise.resolve()
|
||||
spyOn(atom, 'openDevTools').andReturn(devToolsPromise)
|
||||
spyOn(atom, 'executeJavaScriptInDevTools')
|
||||
})
|
||||
|
||||
it('will open the dev tools when an error is triggered', async () => {
|
||||
try {
|
||||
a + 1
|
||||
} catch (e) {
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
|
||||
await devToolsPromise
|
||||
expect(atom.openDevTools).toHaveBeenCalled()
|
||||
expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('::onWillThrowError', () => {
|
||||
let willThrowSpy = null
|
||||
|
||||
beforeEach(() => {
|
||||
willThrowSpy = jasmine.createSpy()
|
||||
})
|
||||
|
||||
it('is called when there is an error', () => {
|
||||
let error = null
|
||||
atom.onWillThrowError(willThrowSpy)
|
||||
try {
|
||||
a + 1
|
||||
} catch (e) {
|
||||
error = e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
|
||||
delete willThrowSpy.mostRecentCall.args[0].preventDefault
|
||||
expect(willThrowSpy).toHaveBeenCalledWith({
|
||||
message: error.toString(),
|
||||
url: 'abc',
|
||||
line: 2,
|
||||
column: 3,
|
||||
originalError: error
|
||||
})
|
||||
})
|
||||
|
||||
it('will not show the devtools when preventDefault() is called', () => {
|
||||
willThrowSpy.andCallFake(errorObject => errorObject.preventDefault())
|
||||
atom.onWillThrowError(willThrowSpy)
|
||||
|
||||
try {
|
||||
a + 1
|
||||
} catch (e) {
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
|
||||
expect(willThrowSpy).toHaveBeenCalled()
|
||||
expect(atom.openDevTools).not.toHaveBeenCalled()
|
||||
expect(atom.executeJavaScriptInDevTools).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidThrowError', () => {
|
||||
let didThrowSpy = null
|
||||
beforeEach(() => didThrowSpy = jasmine.createSpy())
|
||||
|
||||
it('is called when there is an error', () => {
|
||||
let error = null
|
||||
atom.onDidThrowError(didThrowSpy)
|
||||
try {
|
||||
a + 1
|
||||
} catch (e) {
|
||||
error = e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
expect(didThrowSpy).toHaveBeenCalledWith({
|
||||
message: error.toString(),
|
||||
url: 'abc',
|
||||
line: 2,
|
||||
column: 3,
|
||||
originalError: error
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.assert(condition, message, callback)', () => {
|
||||
let errors = null
|
||||
|
||||
beforeEach(() => {
|
||||
errors = []
|
||||
spyOn(atom, 'isReleasedVersion').andReturn(true)
|
||||
atom.onDidFailAssertion(error => errors.push(error))
|
||||
})
|
||||
|
||||
describe('if the condition is false', () => {
|
||||
it('notifies onDidFailAssertion handlers with an error object based on the call site of the assertion', () => {
|
||||
const result = atom.assert(false, 'a == b')
|
||||
expect(result).toBe(false)
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0].message).toBe('Assertion failed: a == b')
|
||||
expect(errors[0].stack).toContain('atom-environment-spec')
|
||||
})
|
||||
|
||||
describe('if passed a callback function', () => {
|
||||
it("calls the callback with the assertion failure's error object", () => {
|
||||
let error = null
|
||||
atom.assert(false, 'a == b', e => error = e)
|
||||
expect(error).toBe(errors[0])
|
||||
})
|
||||
})
|
||||
|
||||
describe('if passed metadata', () => {
|
||||
it("assigns the metadata on the assertion failure's error object", () => {
|
||||
atom.assert(false, 'a == b', {foo: 'bar'})
|
||||
expect(errors[0].metadata).toEqual({foo: 'bar'})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when Atom has been built from source', () => {
|
||||
it('throws an error', () => {
|
||||
atom.isReleasedVersion.andReturn(false)
|
||||
expect(() => atom.assert(false, 'testing')).toThrow('Assertion failed: testing')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('if the condition is true', () => {
|
||||
it('does nothing', () => {
|
||||
const result = atom.assert(true, 'a == b')
|
||||
expect(result).toBe(true)
|
||||
expect(errors).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('saving and loading', () => {
|
||||
beforeEach(() => atom.enablePersistence = true)
|
||||
|
||||
afterEach(() => atom.enablePersistence = false)
|
||||
|
||||
it('selects the state based on the current project paths', async () => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')]
|
||||
|
||||
const loadSettings = Object.assign(atom.getLoadSettings(), {
|
||||
initialPaths: [dir1],
|
||||
windowState: null
|
||||
})
|
||||
|
||||
spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings)
|
||||
spyOn(atom, 'serialize').andReturn({stuff: 'cool'})
|
||||
|
||||
atom.project.setPaths([dir1, dir2])
|
||||
|
||||
// State persistence will fail if other Atom instances are running
|
||||
expect(await atom.stateStore.connect()).toBe(true)
|
||||
|
||||
await atom.saveState()
|
||||
expect(await atom.loadState()).toBeFalsy()
|
||||
|
||||
loadSettings.initialPaths = [dir2, dir1]
|
||||
expect(await atom.loadState()).toEqual({stuff: 'cool'})
|
||||
})
|
||||
|
||||
it('saves state when the CPU is idle after a keydown or mousedown event', () => {
|
||||
const atomEnv = new AtomEnvironment({
|
||||
applicationDelegate: global.atom.applicationDelegate
|
||||
})
|
||||
const idleCallbacks = []
|
||||
atomEnv.initialize({
|
||||
window: {
|
||||
requestIdleCallback (callback) { idleCallbacks.push(callback) },
|
||||
addEventListener () {},
|
||||
removeEventListener () {}
|
||||
},
|
||||
document: document.implementation.createHTMLDocument()
|
||||
})
|
||||
|
||||
spyOn(atomEnv, 'saveState')
|
||||
|
||||
const keydown = new KeyboardEvent('keydown')
|
||||
atomEnv.document.dispatchEvent(keydown)
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atomEnv.saveState.reset()
|
||||
const mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atomEnv.destroy()
|
||||
})
|
||||
|
||||
it('ignores mousedown/keydown events happening after calling unloadEditorWindow', () => {
|
||||
const atomEnv = new AtomEnvironment({
|
||||
applicationDelegate: global.atom.applicationDelegate
|
||||
})
|
||||
const idleCallbacks = []
|
||||
atomEnv.initialize({
|
||||
window: {
|
||||
requestIdleCallback (callback) { idleCallbacks.push(callback) },
|
||||
addEventListener () {},
|
||||
removeEventListener () {}
|
||||
},
|
||||
document: document.implementation.createHTMLDocument()
|
||||
})
|
||||
|
||||
spyOn(atomEnv, 'saveState')
|
||||
|
||||
let mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
atomEnv.unloadEditorWindow()
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
|
||||
atomEnv.destroy()
|
||||
})
|
||||
|
||||
it('serializes the project state with all the options supplied in saveState', async () => {
|
||||
spyOn(atom.project, 'serialize').andReturn({foo: 42})
|
||||
|
||||
await atom.saveState({anyOption: 'any option'})
|
||||
expect(atom.project.serialize.calls.length).toBe(1)
|
||||
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'})
|
||||
})
|
||||
|
||||
it('serializes the text editor registry', async () => {
|
||||
const editor = await atom.workspace.open('sample.js')
|
||||
atom.textEditors.setGrammarOverride(editor, 'text.plain')
|
||||
|
||||
const atom2 = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
window: document.createElement('div'),
|
||||
document: Object.assign(
|
||||
document.createElement('div'),
|
||||
{
|
||||
body: document.createElement('div'),
|
||||
head: document.createElement('div')
|
||||
}
|
||||
)
|
||||
})
|
||||
atom2.initialize({document, window})
|
||||
|
||||
await atom2.deserialize(atom.serialize())
|
||||
expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain')
|
||||
atom2.destroy()
|
||||
})
|
||||
|
||||
describe('deserialization failures', () => {
|
||||
it('propagates project state restoration failures', async () => {
|
||||
spyOn(atom.project, 'deserialize').andCallFake(() => {
|
||||
const err = new Error('deserialization failure')
|
||||
err.missingProjectPaths = ['/foo']
|
||||
return Promise.reject(err)
|
||||
})
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
await atom.deserialize({project: 'should work'})
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open project directory', {
|
||||
description: 'Project directory `/foo` is no longer on disk.'
|
||||
})
|
||||
})
|
||||
|
||||
it('accumulates and reports two errors with one notification', async () => {
|
||||
spyOn(atom.project, 'deserialize').andCallFake(() => {
|
||||
const err = new Error('deserialization failure')
|
||||
err.missingProjectPaths = ['/foo', '/wat']
|
||||
return Promise.reject(err)
|
||||
})
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
await atom.deserialize({project: 'should work'})
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 2 project directories', {
|
||||
description: 'Project directories `/foo` and `/wat` are no longer on disk.'
|
||||
})
|
||||
})
|
||||
|
||||
it('accumulates and reports three+ errors with one notification', async () => {
|
||||
spyOn(atom.project, 'deserialize').andCallFake(() => {
|
||||
const err = new Error('deserialization failure')
|
||||
err.missingProjectPaths = ['/foo', '/wat', '/stuff', '/things']
|
||||
return Promise.reject(err)
|
||||
})
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
await atom.deserialize({project: 'should work'})
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 4 project directories', {
|
||||
description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('openInitialEmptyEditorIfNecessary', () => {
|
||||
describe('when there are no paths set', () => {
|
||||
beforeEach(() => spyOn(atom, 'getLoadSettings').andReturn({initialPaths: []}))
|
||||
|
||||
it('opens an empty buffer', () => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(null)
|
||||
})
|
||||
|
||||
describe('when there is already a buffer open', () => {
|
||||
beforeEach(async () => {
|
||||
await atom.workspace.open()
|
||||
})
|
||||
|
||||
it('does not open an empty buffer', () => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has a path', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getLoadSettings').andReturn({initialPaths: ['something']})
|
||||
spyOn(atom.workspace, 'open')
|
||||
})
|
||||
|
||||
it('does not open an empty buffer', () => {
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('adding a project folder', () => {
|
||||
it('does nothing if the user dismisses the file picker', () => {
|
||||
const initialPaths = atom.project.getPaths()
|
||||
const tempDirectory = temp.mkdirSync('a-new-directory')
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback(null))
|
||||
atom.addProjectFolder()
|
||||
expect(atom.project.getPaths()).toEqual(initialPaths)
|
||||
})
|
||||
|
||||
describe('when there is no saved state for the added folders', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'loadState').andReturn(Promise.resolve(null))
|
||||
spyOn(atom, 'attemptRestoreProjectStateForPaths')
|
||||
})
|
||||
|
||||
it('adds the selected folder to the project', async () => {
|
||||
const initialPaths = atom.project.setPaths([])
|
||||
const tempDirectory = temp.mkdirSync('a-new-directory')
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback([tempDirectory]))
|
||||
await atom.addProjectFolder()
|
||||
expect(atom.project.getPaths()).toEqual([tempDirectory])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is saved state for the relevant directories', () => {
|
||||
const state = Symbol('savedState')
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':'))
|
||||
spyOn(atom, 'loadState').andCallFake(async (key) => key === __dirname ? state : null)
|
||||
spyOn(atom, 'attemptRestoreProjectStateForPaths')
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback([__dirname]))
|
||||
atom.project.setPaths([])
|
||||
})
|
||||
|
||||
describe('when there are no project folders', () => {
|
||||
it('attempts to restore the project state', async () => {
|
||||
await atom.addProjectFolder()
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there are already project folders', () => {
|
||||
const openedPath = path.join(__dirname, 'fixtures')
|
||||
|
||||
beforeEach(() => atom.project.setPaths([openedPath]))
|
||||
|
||||
it('does not attempt to restore the project state, instead adding the project paths', async () => {
|
||||
await atom.addProjectFolder()
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
expect(atom.project.getPaths()).toEqual([openedPath, __dirname])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)', () => {
|
||||
describe('when the window is clean (empty or has only unnamed, unmodified buffers)', () => {
|
||||
beforeEach(async () => {
|
||||
// Unnamed, unmodified buffer doesn't count toward "clean"-ness
|
||||
await atom.workspace.open()
|
||||
})
|
||||
|
||||
it('automatically restores the saved state into the current environment', () => {
|
||||
const state = {}
|
||||
spyOn(atom.workspace, 'open')
|
||||
spyOn(atom, 'restoreStateIntoThisEnvironment')
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.restoreStateIntoThisEnvironment).toHaveBeenCalledWith(state)
|
||||
expect(atom.workspace.open.callCount).toBe(1)
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(__filename)
|
||||
})
|
||||
|
||||
describe('when a dock has a non-text editor', () => {
|
||||
it("doesn't prompt the user to restore state", () => {
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
dock.getActivePane().addItem({
|
||||
getTitle () { return 'title' },
|
||||
element: document.createElement('div')
|
||||
})
|
||||
const state = {}
|
||||
spyOn(atom, 'confirm')
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the window is dirty', () => {
|
||||
let editor
|
||||
|
||||
beforeEach(async () => {
|
||||
editor = await atom.workspace.open()
|
||||
editor.setText('new editor')
|
||||
})
|
||||
|
||||
describe('when a dock has a modified editor', () => {
|
||||
it('prompts the user to restore the state', () => {
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
dock.getActivePane().addItem(editor)
|
||||
spyOn(atom, 'confirm').andReturn(1)
|
||||
spyOn(atom.project, 'addPath')
|
||||
spyOn(atom.workspace, 'open')
|
||||
const state = Symbol()
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', () => {
|
||||
spyOn(atom, 'confirm').andReturn(1)
|
||||
spyOn(atom.project, 'addPath')
|
||||
spyOn(atom.workspace, 'open')
|
||||
const state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(atom.project.addPath.callCount).toBe(1)
|
||||
expect(atom.project.addPath).toHaveBeenCalledWith(__dirname)
|
||||
expect(atom.workspace.open.callCount).toBe(1)
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(__filename)
|
||||
})
|
||||
|
||||
it('prompts the user to restore the state in a new window, opening a new window', () => {
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
spyOn(atom, 'open')
|
||||
const state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(atom.open).toHaveBeenCalledWith({
|
||||
pathsToOpen: [__dirname, __filename],
|
||||
newWindow: true,
|
||||
devMode: atom.inDevMode(),
|
||||
safeMode: atom.inSafeMode()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::unloadEditorWindow()', () => {
|
||||
it('saves the BlobStore so it can be loaded after reload', () => {
|
||||
const configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||
const fakeBlobStore = jasmine.createSpyObj('blob store', ['save'])
|
||||
const atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true})
|
||||
atomEnvironment.initialize({configDirPath, blobStore: fakeBlobStore, window, document})
|
||||
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
|
||||
expect(fakeBlobStore.save).toHaveBeenCalled()
|
||||
|
||||
atomEnvironment.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('::destroy()', () => {
|
||||
it('does not throw exceptions when unsubscribing from ipc events (regression)', async () => {
|
||||
const configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||
const fakeDocument = {
|
||||
addEventListener () {},
|
||||
removeEventListener () {},
|
||||
head: document.createElement('head'),
|
||||
body: document.createElement('body')
|
||||
}
|
||||
const atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate})
|
||||
atomEnvironment.initialize({window, document: fakeDocument})
|
||||
spyOn(atomEnvironment.packages, 'loadPackages').andReturn(Promise.resolve())
|
||||
spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve())
|
||||
spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve())
|
||||
await atomEnvironment.startEditorWindow()
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
atomEnvironment.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('::whenShellEnvironmentLoaded()', () => {
|
||||
let atomEnvironment, envLoaded, spy
|
||||
|
||||
beforeEach(() => {
|
||||
let resolve = null
|
||||
const promise = new Promise((r) => { resolve = r })
|
||||
envLoaded = () => {
|
||||
resolve()
|
||||
promise
|
||||
}
|
||||
atomEnvironment = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
updateProcessEnv () { return promise }
|
||||
})
|
||||
atomEnvironment.initialize({window, document})
|
||||
spy = jasmine.createSpy()
|
||||
})
|
||||
|
||||
afterEach(() => atomEnvironment.destroy())
|
||||
|
||||
it('is triggered once the shell environment is loaded', async () => {
|
||||
atomEnvironment.whenShellEnvironmentLoaded(spy)
|
||||
atomEnvironment.updateProcessEnvAndTriggerHooks()
|
||||
await envLoaded()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('triggers the callback immediately if the shell environment is already loaded', async () => {
|
||||
atomEnvironment.updateProcessEnvAndTriggerHooks()
|
||||
await envLoaded()
|
||||
atomEnvironment.whenShellEnvironmentLoaded(spy)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('::openLocations(locations) (called via IPC from browser process)', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.project.setPaths([])
|
||||
})
|
||||
|
||||
describe('when there is no saved state', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'loadState').andReturn(Promise.resolve(null))
|
||||
})
|
||||
|
||||
describe('when the opened path exists', () => {
|
||||
it("adds it to the project's paths", async () => {
|
||||
const pathToOpen = __filename
|
||||
await 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", async () => {
|
||||
const firstPathToOpen = __dirname
|
||||
const secondPathToOpen = path.resolve(__dirname, './fixtures')
|
||||
await atom.openLocations([{pathToOpen: firstPathToOpen}])
|
||||
await 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', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.project.getPaths()[0]).toBe(__dirname)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the opened path is a file', () => {
|
||||
it('opens it in the workspace', async () => {
|
||||
const pathToOpen = __filename
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.workspace.open.mostRecentCall.args[0]).toBe(__filename)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the opened path is a directory', () => {
|
||||
it('does not open it in the workspace', async () => {
|
||||
const pathToOpen = __dirname
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.workspace.open.callCount).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the opened path is a uri', () => {
|
||||
it("adds it to the project's paths as is", async () => {
|
||||
const pathToOpen = 'remote://server:7644/some/dir/path'
|
||||
spyOn(atom.project, 'addPath')
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is saved state for the relevant directories', () => {
|
||||
const state = Symbol('savedState')
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':'))
|
||||
spyOn(atom, 'loadState').andCallFake(function (key) {
|
||||
if (key === __dirname) { return Promise.resolve(state) } else { return Promise.resolve(null) }
|
||||
})
|
||||
spyOn(atom, 'attemptRestoreProjectStateForPaths')
|
||||
})
|
||||
|
||||
describe('when there are no project folders', () => {
|
||||
it('attempts to restore the project state', async () => {
|
||||
const pathToOpen = __dirname
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], [])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
|
||||
it('opens the specified files', async () => {
|
||||
await atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there are already project folders', () => {
|
||||
beforeEach(() => atom.project.setPaths([__dirname]))
|
||||
|
||||
it('does not attempt to restore the project state, instead adding the project paths', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'fixtures')
|
||||
await atom.openLocations([{pathToOpen, forceAddToWindow: true}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen])
|
||||
})
|
||||
|
||||
it('opens the specified files', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'fixtures')
|
||||
const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt')
|
||||
await atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen])
|
||||
expect(atom.project.getPaths()).toEqual([__dirname])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::updateAvailable(info) (called via IPC from browser process)', () => {
|
||||
let subscription
|
||||
|
||||
afterEach(() => {
|
||||
if (subscription) subscription.dispose()
|
||||
})
|
||||
|
||||
it('invokes onUpdateAvailable listeners', async () => {
|
||||
if (process.platform !== 'darwin') return // Test tied to electron autoUpdater, we use something else on Linux and Win32
|
||||
|
||||
const updateAvailablePromise = new Promise(resolve => {
|
||||
subscription = atom.onUpdateAvailable(resolve)
|
||||
})
|
||||
|
||||
atom.listenForUpdates()
|
||||
const {autoUpdater} = require('electron').remote
|
||||
autoUpdater.emit('update-downloaded', null, 'notes', 'version')
|
||||
|
||||
const {releaseVersion} = await updateAvailablePromise
|
||||
expect(releaseVersion).toBe('version')
|
||||
})
|
||||
})
|
||||
|
||||
describe('::getReleaseChannel()', () => {
|
||||
let 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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,14 @@ describe('GitRepositoryProvider', () => {
|
||||
provider = new GitRepositoryProvider(atom.project, atom.config, atom.confirm)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (provider) {
|
||||
Object.keys(provider.pathToRepository).forEach(key => {
|
||||
provider.pathToRepository[key].destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('.repositoryForDirectory(directory)', () => {
|
||||
describe('when specified a Directory with a Git repository', () => {
|
||||
it('resolves with a GitRepository', async () => {
|
||||
|
||||
@@ -352,11 +352,9 @@ describe('AtomApplication', function () {
|
||||
|
||||
const atomApplication1 = buildAtomApplication()
|
||||
const app1Window1 = atomApplication1.launch(parseCommandLine([tempDirPath1]))
|
||||
await emitterEventPromise(app1Window1, 'window:locations-opened')
|
||||
const app1Window2 = atomApplication1.launch(parseCommandLine([tempDirPath2]))
|
||||
await Promise.all([
|
||||
emitterEventPromise(app1Window1, 'window:locations-opened'),
|
||||
emitterEventPromise(app1Window2, 'window:locations-opened')
|
||||
])
|
||||
await emitterEventPromise(app1Window2, 'window:locations-opened')
|
||||
|
||||
await Promise.all([
|
||||
app1Window1.prepareToUnload(),
|
||||
|
||||
@@ -1325,7 +1325,7 @@ describe('TextEditorComponent', () => {
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
const expectedScrollLeft = component.getScrollLeft()
|
||||
component.didMouseWheel({deltaX: 5, deltaY: 20})
|
||||
component.didMouseWheel({wheelDeltaX: -5, wheelDeltaY: -20})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`)
|
||||
@@ -1334,7 +1334,7 @@ describe('TextEditorComponent', () => {
|
||||
{
|
||||
const expectedScrollTop = component.getScrollTop() - (10 * (scrollSensitivity / 100))
|
||||
const expectedScrollLeft = component.getScrollLeft()
|
||||
component.didMouseWheel({deltaX: 5, deltaY: -10})
|
||||
component.didMouseWheel({wheelDeltaX: -5, wheelDeltaY: 10})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`)
|
||||
@@ -1343,7 +1343,7 @@ describe('TextEditorComponent', () => {
|
||||
{
|
||||
const expectedScrollTop = component.getScrollTop()
|
||||
const expectedScrollLeft = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 20, deltaY: -10})
|
||||
component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 10})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`)
|
||||
@@ -1352,7 +1352,7 @@ describe('TextEditorComponent', () => {
|
||||
{
|
||||
const expectedScrollTop = component.getScrollTop()
|
||||
const expectedScrollLeft = component.getScrollLeft() - (10 * (scrollSensitivity / 100))
|
||||
component.didMouseWheel({deltaX: -10, deltaY: 8})
|
||||
component.didMouseWheel({wheelDeltaX: 10, wheelDeltaY: -8})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(${-expectedScrollLeft}px, ${-expectedScrollTop}px)`)
|
||||
@@ -1364,14 +1364,14 @@ describe('TextEditorComponent', () => {
|
||||
const {component, editor} = buildComponent({height: 50, width: 50, scrollSensitivity})
|
||||
|
||||
{
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 3})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -3})
|
||||
expect(component.getScrollTop()).toBe(1)
|
||||
expect(component.getScrollLeft()).toBe(0)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -1px)`)
|
||||
}
|
||||
|
||||
{
|
||||
component.didMouseWheel({deltaX: 4, deltaY: 0})
|
||||
component.didMouseWheel({wheelDeltaX: -4, wheelDeltaY: 0})
|
||||
expect(component.getScrollTop()).toBe(1)
|
||||
expect(component.getScrollLeft()).toBe(1)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-1px, -1px)`)
|
||||
@@ -1379,14 +1379,14 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
editor.update({scrollSensitivity: 100})
|
||||
{
|
||||
component.didMouseWheel({deltaX: 0, deltaY: -0.3})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: 0.3})
|
||||
expect(component.getScrollTop()).toBe(0)
|
||||
expect(component.getScrollLeft()).toBe(1)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-1px, 0px)`)
|
||||
}
|
||||
|
||||
{
|
||||
component.didMouseWheel({deltaX: -0.1, deltaY: 0})
|
||||
component.didMouseWheel({wheelDeltaX: 0.1, wheelDeltaY: 0})
|
||||
expect(component.getScrollTop()).toBe(0)
|
||||
expect(component.getScrollLeft()).toBe(0)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, 0px)`)
|
||||
@@ -1400,7 +1400,7 @@ describe('TextEditorComponent', () => {
|
||||
component.props.platform = 'linux'
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 20})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`)
|
||||
await setScrollTop(component, 0)
|
||||
@@ -1408,7 +1408,7 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
{
|
||||
const expectedScrollLeft = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 20, shiftKey: true})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true})
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-${expectedScrollLeft}px, 0px)`)
|
||||
await setScrollLeft(component, 0)
|
||||
@@ -1416,7 +1416,7 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 20, deltaY: 0, shiftKey: true})
|
||||
component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`)
|
||||
await setScrollTop(component, 0)
|
||||
@@ -1425,7 +1425,7 @@ describe('TextEditorComponent', () => {
|
||||
component.props.platform = 'win32'
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 20})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`)
|
||||
await setScrollTop(component, 0)
|
||||
@@ -1433,7 +1433,7 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
{
|
||||
const expectedScrollLeft = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 20, shiftKey: true})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true})
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-${expectedScrollLeft}px, 0px)`)
|
||||
await setScrollLeft(component, 0)
|
||||
@@ -1441,7 +1441,7 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 20, deltaY: 0, shiftKey: true})
|
||||
component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`)
|
||||
await setScrollTop(component, 0)
|
||||
@@ -1450,7 +1450,7 @@ describe('TextEditorComponent', () => {
|
||||
component.props.platform = 'darwin'
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 20})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`)
|
||||
await setScrollTop(component, 0)
|
||||
@@ -1458,7 +1458,7 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
{
|
||||
const expectedScrollTop = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 0, deltaY: 20, shiftKey: true})
|
||||
component.didMouseWheel({wheelDeltaX: 0, wheelDeltaY: -20, shiftKey: true})
|
||||
expect(component.getScrollTop()).toBe(expectedScrollTop)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(0px, -${expectedScrollTop}px)`)
|
||||
await setScrollTop(component, 0)
|
||||
@@ -1466,7 +1466,7 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
{
|
||||
const expectedScrollLeft = 20 * (scrollSensitivity / 100)
|
||||
component.didMouseWheel({deltaX: 20, deltaY: 0, shiftKey: true})
|
||||
component.didMouseWheel({wheelDeltaX: -20, wheelDeltaY: 0, shiftKey: true})
|
||||
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
|
||||
expect(component.refs.content.style.transform).toBe(`translate(-${expectedScrollLeft}px, 0px)`)
|
||||
await setScrollLeft(component, 0)
|
||||
|
||||
@@ -1061,6 +1061,20 @@ describe('TextEditor', () => {
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 1])
|
||||
})
|
||||
|
||||
it('stops at camelCase boundaries with non-ascii characters', () => {
|
||||
editor.setText(' gétÁrevìôüsWord\n')
|
||||
editor.setCursorBufferPosition([0, 16])
|
||||
|
||||
editor.moveToPreviousSubwordBoundary()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 12])
|
||||
|
||||
editor.moveToPreviousSubwordBoundary()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 4])
|
||||
|
||||
editor.moveToPreviousSubwordBoundary()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 1])
|
||||
})
|
||||
|
||||
it('skips consecutive non-word characters', () => {
|
||||
editor.setText('e, => \n')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
@@ -1086,6 +1100,21 @@ describe('TextEditor', () => {
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 2])
|
||||
})
|
||||
|
||||
it('skips consecutive uppercase non-ascii letters', () => {
|
||||
editor.setText(' ÀÁÅDF \n')
|
||||
editor.setCursorBufferPosition([0, 7])
|
||||
editor.moveToPreviousSubwordBoundary()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 6])
|
||||
|
||||
editor.moveToPreviousSubwordBoundary()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 1])
|
||||
|
||||
editor.setText('ALPhA\n')
|
||||
editor.setCursorBufferPosition([0, 4])
|
||||
editor.moveToPreviousSubwordBoundary()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([0, 2])
|
||||
})
|
||||
|
||||
it('skips consecutive numbers', () => {
|
||||
editor.setText(' 88 \n')
|
||||
editor.setCursorBufferPosition([0, 4])
|
||||
@@ -3304,13 +3333,13 @@ describe('TextEditor', () => {
|
||||
beforeEach(() => {
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setEditorWidthInChars(80)
|
||||
editor.setText(`\
|
||||
1
|
||||
2
|
||||
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
||||
3
|
||||
4\
|
||||
`)
|
||||
editor.setText(dedent `
|
||||
1
|
||||
2
|
||||
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
||||
3
|
||||
4
|
||||
`)
|
||||
})
|
||||
|
||||
it('moves the lines past the soft wrapped line', () => {
|
||||
@@ -5864,21 +5893,20 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh
|
||||
|
||||
editor.duplicateLines()
|
||||
|
||||
expect(editor.getTextInBufferRange([[2, 0], [13, 5]])).toBe(`\
|
||||
\ if (items.length <= 1) return items;
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}\
|
||||
`
|
||||
)
|
||||
expect(editor.getTextInBufferRange([[2, 0], [13, 5]])).toBe(dedent `
|
||||
if (items.length <= 1) return items;
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}\
|
||||
`.split('\n').map(l => ` ${l}`).join('\n'))
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([[[3, 5], [3, 5]], [[9, 0], [14, 0]]])
|
||||
|
||||
// folds are also duplicated
|
||||
@@ -5894,42 +5922,40 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh
|
||||
|
||||
editor.duplicateLines()
|
||||
|
||||
expect(editor.getTextInBufferRange([[2, 0], [11, 5]])).toBe(`\
|
||||
\ if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}\
|
||||
`
|
||||
)
|
||||
expect(editor.getTextInBufferRange([[2, 0], [11, 5]])).toBe(dedent`
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
`.split('\n').map(l => ` ${l}`).join('\n'))
|
||||
expect(editor.getSelectedBufferRange()).toEqual([[8, 0], [8, 0]])
|
||||
})
|
||||
|
||||
it('can duplicate the last line of the buffer', () => {
|
||||
editor.setSelectedBufferRange([[11, 0], [12, 2]])
|
||||
editor.duplicateLines()
|
||||
expect(editor.getTextInBufferRange([[11, 0], [14, 2]])).toBe(`\
|
||||
\ return sort(Array.apply(this, arguments));
|
||||
};
|
||||
return sort(Array.apply(this, arguments));
|
||||
};\
|
||||
`
|
||||
)
|
||||
expect(editor.getTextInBufferRange([[11, 0], [14, 2]])).toBe(' ' + dedent `
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
`.trim())
|
||||
expect(editor.getSelectedBufferRange()).toEqual([[13, 0], [14, 2]])
|
||||
})
|
||||
|
||||
it('only duplicates lines containing multiple selections once', () => {
|
||||
editor.setText(`\
|
||||
aaaaaa
|
||||
bbbbbb
|
||||
cccccc
|
||||
dddddd\
|
||||
`)
|
||||
editor.setText(dedent `
|
||||
aaaaaa
|
||||
bbbbbb
|
||||
cccccc
|
||||
dddddd
|
||||
`)
|
||||
editor.setSelectedBufferRanges([
|
||||
[[0, 1], [0, 2]],
|
||||
[[0, 3], [0, 4]],
|
||||
@@ -5938,15 +5964,15 @@ dddddd\
|
||||
[[3, 3], [3, 4]]
|
||||
])
|
||||
editor.duplicateLines()
|
||||
expect(editor.getText()).toBe(`\
|
||||
aaaaaa
|
||||
aaaaaa
|
||||
bbbbbb
|
||||
cccccc
|
||||
dddddd
|
||||
cccccc
|
||||
dddddd\
|
||||
`)
|
||||
expect(editor.getText()).toBe(dedent `
|
||||
aaaaaa
|
||||
aaaaaa
|
||||
bbbbbb
|
||||
cccccc
|
||||
dddddd
|
||||
cccccc
|
||||
dddddd
|
||||
`)
|
||||
expect(editor.getSelectedBufferRanges()).toEqual([
|
||||
[[1, 1], [1, 2]],
|
||||
[[1, 3], [1, 4]],
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
textUtils = require '../src/text-utils'
|
||||
|
||||
describe 'text utilities', ->
|
||||
describe '.hasPairedCharacter(string)', ->
|
||||
it 'returns true when the string contains a surrogate pair, variation sequence, or combined character', ->
|
||||
expect(textUtils.hasPairedCharacter('abc')).toBe false
|
||||
expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe true
|
||||
expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe true
|
||||
expect(textUtils.hasPairedCharacter('\u2714\uFE0E')).toBe true
|
||||
expect(textUtils.hasPairedCharacter('e\u0301')).toBe true
|
||||
|
||||
expect(textUtils.hasPairedCharacter('\uD835')).toBe false
|
||||
expect(textUtils.hasPairedCharacter('\uDF97')).toBe false
|
||||
expect(textUtils.hasPairedCharacter('\uFE0E')).toBe false
|
||||
expect(textUtils.hasPairedCharacter('\u0301')).toBe false
|
||||
|
||||
expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe false
|
||||
expect(textUtils.hasPairedCharacter('\u0301\u0301')).toBe false
|
||||
|
||||
describe '.isPairedCharacter(string, index)', ->
|
||||
it 'returns true when the index is the start of a high/low surrogate pair, variation sequence, or combined character', ->
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe false
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe true
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe false
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe false
|
||||
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 0)).toBe false
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 1)).toBe true
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 2)).toBe false
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe false
|
||||
|
||||
expect(textUtils.isPairedCharacter('\uD835')).toBe false
|
||||
expect(textUtils.isPairedCharacter('\uDF97')).toBe false
|
||||
expect(textUtils.isPairedCharacter('\uFE0E')).toBe false
|
||||
expect(textUtils.isPairedCharacter('\uFE0E')).toBe false
|
||||
|
||||
expect(textUtils.isPairedCharacter('\uFE0E\uFE0E')).toBe false
|
||||
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 0)).toBe false
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 1)).toBe true
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 2)).toBe false
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 3)).toBe false
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 4)).toBe false
|
||||
|
||||
describe ".isDoubleWidthCharacter(character)", ->
|
||||
it "returns true when the character is either japanese, chinese or a full width form", ->
|
||||
expect(textUtils.isDoubleWidthCharacter("我")).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter("私")).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter("B")).toBe(true)
|
||||
expect(textUtils.isDoubleWidthCharacter(",")).toBe(true)
|
||||
expect(textUtils.isDoubleWidthCharacter("¢")).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter("a")).toBe(false)
|
||||
|
||||
describe ".isHalfWidthCharacter(character)", ->
|
||||
it "returns true when the character is an half width form", ->
|
||||
expect(textUtils.isHalfWidthCharacter("ハ")).toBe(true)
|
||||
expect(textUtils.isHalfWidthCharacter("ヒ")).toBe(true)
|
||||
expect(textUtils.isHalfWidthCharacter("ᆲ")).toBe(true)
|
||||
expect(textUtils.isHalfWidthCharacter("■")).toBe(true)
|
||||
|
||||
expect(textUtils.isHalfWidthCharacter("B")).toBe(false)
|
||||
|
||||
describe ".isKoreanCharacter(character)", ->
|
||||
it "returns true when the character is a korean character", ->
|
||||
expect(textUtils.isKoreanCharacter("우")).toBe(true)
|
||||
expect(textUtils.isKoreanCharacter("가")).toBe(true)
|
||||
expect(textUtils.isKoreanCharacter("ㅢ")).toBe(true)
|
||||
expect(textUtils.isKoreanCharacter("ㄼ")).toBe(true)
|
||||
|
||||
expect(textUtils.isKoreanCharacter("O")).toBe(false)
|
||||
|
||||
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.isWrapBoundary(' ', 'h')).toBe(true)
|
||||
expect(textUtils.isWrapBoundary('\t', 'h')).toBe(true)
|
||||
expect(textUtils.isWrapBoundary('a', 'h')).toBe(false)
|
||||
110
spec/text-utils-spec.js
Normal file
110
spec/text-utils-spec.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const textUtils = require('../src/text-utils')
|
||||
|
||||
describe('text utilities', () => {
|
||||
describe('.hasPairedCharacter(string)', () =>
|
||||
it('returns true when the string contains a surrogate pair, variation sequence, or combined character', () => {
|
||||
expect(textUtils.hasPairedCharacter('abc')).toBe(false)
|
||||
expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe(true)
|
||||
expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe(true)
|
||||
expect(textUtils.hasPairedCharacter('\u2714\uFE0E')).toBe(true)
|
||||
expect(textUtils.hasPairedCharacter('e\u0301')).toBe(true)
|
||||
|
||||
expect(textUtils.hasPairedCharacter('\uD835')).toBe(false)
|
||||
expect(textUtils.hasPairedCharacter('\uDF97')).toBe(false)
|
||||
expect(textUtils.hasPairedCharacter('\uFE0E')).toBe(false)
|
||||
expect(textUtils.hasPairedCharacter('\u0301')).toBe(false)
|
||||
|
||||
expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe(false)
|
||||
expect(textUtils.hasPairedCharacter('\u0301\u0301')).toBe(false)
|
||||
})
|
||||
)
|
||||
|
||||
describe('.isPairedCharacter(string, index)', () =>
|
||||
it('returns true when the index is the start of a high/low surrogate pair, variation sequence, or combined character', () => {
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe(true)
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe(true)
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe(false)
|
||||
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 0)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 1)).toBe(true)
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 2)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe(false)
|
||||
|
||||
expect(textUtils.isPairedCharacter('\uD835')).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('\uDF97')).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('\uFE0E')).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('\uFE0E')).toBe(false)
|
||||
|
||||
expect(textUtils.isPairedCharacter('\uFE0E\uFE0E')).toBe(false)
|
||||
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 0)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 1)).toBe(true)
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 2)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 3)).toBe(false)
|
||||
expect(textUtils.isPairedCharacter('ae\u0301c', 4)).toBe(false)
|
||||
})
|
||||
)
|
||||
|
||||
describe('.isDoubleWidthCharacter(character)', () =>
|
||||
it('returns true when the character is either japanese, chinese or a full width form', () => {
|
||||
expect(textUtils.isDoubleWidthCharacter('我')).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter('私')).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter('B')).toBe(true)
|
||||
expect(textUtils.isDoubleWidthCharacter(',')).toBe(true)
|
||||
expect(textUtils.isDoubleWidthCharacter('¢')).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter('a')).toBe(false)
|
||||
})
|
||||
)
|
||||
|
||||
describe('.isHalfWidthCharacter(character)', () =>
|
||||
it('returns true when the character is an half width form', () => {
|
||||
expect(textUtils.isHalfWidthCharacter('ハ')).toBe(true)
|
||||
expect(textUtils.isHalfWidthCharacter('ヒ')).toBe(true)
|
||||
expect(textUtils.isHalfWidthCharacter('ᆲ')).toBe(true)
|
||||
expect(textUtils.isHalfWidthCharacter('■')).toBe(true)
|
||||
|
||||
expect(textUtils.isHalfWidthCharacter('B')).toBe(false)
|
||||
})
|
||||
)
|
||||
|
||||
describe('.isKoreanCharacter(character)', () =>
|
||||
it('returns true when the character is a korean character', () => {
|
||||
expect(textUtils.isKoreanCharacter('우')).toBe(true)
|
||||
expect(textUtils.isKoreanCharacter('가')).toBe(true)
|
||||
expect(textUtils.isKoreanCharacter('ㅢ')).toBe(true)
|
||||
expect(textUtils.isKoreanCharacter('ㄼ')).toBe(true)
|
||||
|
||||
expect(textUtils.isKoreanCharacter('O')).toBe(false)
|
||||
})
|
||||
)
|
||||
|
||||
describe('.isWrapBoundary(previousCharacter, character)', () =>
|
||||
it('returns true when the character is CJK or when the previous character is a space/tab', () => {
|
||||
const 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.isWrapBoundary(' ', 'h')).toBe(true)
|
||||
expect(textUtils.isWrapBoundary('\t', 'h')).toBe(true)
|
||||
expect(textUtils.isWrapBoundary('a', 'h')).toBe(false)
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -108,8 +108,12 @@ describe('TooltipManager', () => {
|
||||
const element2 = document.createElement('div')
|
||||
jasmine.attachToDOM(element2)
|
||||
|
||||
const fakeJqueryWrapper = [element, element2]
|
||||
fakeJqueryWrapper.jquery = 'any-version'
|
||||
const fakeJqueryWrapper = {
|
||||
0: element,
|
||||
1: element2,
|
||||
length: 2,
|
||||
jquery: 'any-version'
|
||||
}
|
||||
const disposable = manager.add(fakeJqueryWrapper, {title: 'Title'})
|
||||
|
||||
hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1345
src/atom-environment.js
Normal file
1345
src/atom-environment.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -454,23 +454,25 @@ class Cursor extends Model {
|
||||
getPreviousWordBoundaryBufferPosition (options = {}) {
|
||||
const currentBufferPosition = this.getBufferPosition()
|
||||
const previousNonBlankRow = this.editor.buffer.previousNonBlankRow(currentBufferPosition.row)
|
||||
const scanRange = [[previousNonBlankRow || 0, 0], currentBufferPosition]
|
||||
const scanRange = Range(Point(previousNonBlankRow || 0, 0), currentBufferPosition)
|
||||
|
||||
let beginningOfWordPosition
|
||||
this.editor.backwardsScanInBufferRange(options.wordRegex || this.wordRegExp(), scanRange, ({range, stop}) => {
|
||||
const ranges = this.editor.buffer.findAllInRangeSync(
|
||||
options.wordRegex || this.wordRegExp(),
|
||||
scanRange
|
||||
)
|
||||
|
||||
const range = ranges[ranges.length - 1]
|
||||
if (range) {
|
||||
if (range.start.row < currentBufferPosition.row && currentBufferPosition.column > 0) {
|
||||
// force it to stop at the beginning of each line
|
||||
beginningOfWordPosition = new Point(currentBufferPosition.row, 0)
|
||||
} else if (range.end.isLessThan(currentBufferPosition)) {
|
||||
beginningOfWordPosition = range.end
|
||||
return Point(currentBufferPosition.row, 0)
|
||||
} else if (currentBufferPosition.isGreaterThan(range.end)) {
|
||||
return Point.fromObject(range.end)
|
||||
} else {
|
||||
beginningOfWordPosition = range.start
|
||||
return Point.fromObject(range.start)
|
||||
}
|
||||
|
||||
if (!beginningOfWordPosition.isEqual(currentBufferPosition)) stop()
|
||||
})
|
||||
|
||||
return beginningOfWordPosition || currentBufferPosition
|
||||
} else {
|
||||
return currentBufferPosition
|
||||
}
|
||||
}
|
||||
|
||||
// Public: Returns buffer position of the next word boundary. It might be on
|
||||
@@ -481,23 +483,24 @@ class Cursor extends Model {
|
||||
// (default: {::wordRegExp})
|
||||
getNextWordBoundaryBufferPosition (options = {}) {
|
||||
const currentBufferPosition = this.getBufferPosition()
|
||||
const scanRange = [currentBufferPosition, this.editor.getEofBufferPosition()]
|
||||
const scanRange = Range(currentBufferPosition, this.editor.getEofBufferPosition())
|
||||
|
||||
let endOfWordPosition
|
||||
this.editor.scanInBufferRange((options.wordRegex != null ? options.wordRegex : this.wordRegExp()), scanRange, function ({range, stop}) {
|
||||
const range = this.editor.buffer.findInRangeSync(
|
||||
options.wordRegex || this.wordRegExp(),
|
||||
scanRange
|
||||
)
|
||||
|
||||
if (range) {
|
||||
if (range.start.row > currentBufferPosition.row) {
|
||||
// force it to stop at the beginning of each line
|
||||
endOfWordPosition = new Point(range.start.row, 0)
|
||||
} else if (range.start.isGreaterThan(currentBufferPosition)) {
|
||||
endOfWordPosition = range.start
|
||||
return Point(range.start.row, 0)
|
||||
} else if (currentBufferPosition.isLessThan(range.start)) {
|
||||
return Point.fromObject(range.start)
|
||||
} else {
|
||||
endOfWordPosition = range.end
|
||||
return Point.fromObject(range.end)
|
||||
}
|
||||
|
||||
if (!endOfWordPosition.isEqual(currentBufferPosition)) stop()
|
||||
})
|
||||
|
||||
return endOfWordPosition || currentBufferPosition
|
||||
} else {
|
||||
return currentBufferPosition
|
||||
}
|
||||
}
|
||||
|
||||
// Public: Retrieves the buffer position of where the current word starts.
|
||||
|
||||
@@ -422,7 +422,7 @@ class PathWatcher {
|
||||
// Extended: Return a {Promise} that will resolve when the underlying native watcher is ready to begin sending events.
|
||||
// When testing filesystem watchers, it's important to await this promise before making filesystem changes that you
|
||||
// intend to assert about because there will be a delay between the instantiation of the watcher and the activation
|
||||
// of the underlying OS resources that feed it events.
|
||||
// of the underlying OS resources that feed its events.
|
||||
//
|
||||
// PathWatchers acquired through `watchPath` are already started.
|
||||
//
|
||||
@@ -533,7 +533,7 @@ class PathWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// Extended: Unsubscribe all subscribers from filesystem events. Native resources will be release asynchronously,
|
||||
// Extended: Unsubscribe all subscribers from filesystem events. Native resources will be released asynchronously,
|
||||
// but this watcher will stop broadcasting events immediately.
|
||||
dispose () {
|
||||
for (const sub of this.changeCallbacks.values()) {
|
||||
|
||||
@@ -219,18 +219,40 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage
|
||||
'editor:toggle-soft-wrap': -> @toggleSoftWrapped()
|
||||
'editor:fold-all': -> @foldAll()
|
||||
'editor:unfold-all': -> @unfoldAll()
|
||||
'editor:fold-current-row': -> @foldCurrentRow()
|
||||
'editor:unfold-current-row': -> @unfoldCurrentRow()
|
||||
'editor:fold-current-row': ->
|
||||
@foldCurrentRow()
|
||||
@scrollToCursorPosition()
|
||||
'editor:unfold-current-row': ->
|
||||
@unfoldCurrentRow()
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-selection': -> @foldSelectedLines()
|
||||
'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0)
|
||||
'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1)
|
||||
'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2)
|
||||
'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3)
|
||||
'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4)
|
||||
'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5)
|
||||
'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6)
|
||||
'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
|
||||
'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
|
||||
'editor:fold-at-indent-level-1': ->
|
||||
@foldAllAtIndentLevel(0)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-2': ->
|
||||
@foldAllAtIndentLevel(1)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-3': ->
|
||||
@foldAllAtIndentLevel(2)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-4': ->
|
||||
@foldAllAtIndentLevel(3)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-5': ->
|
||||
@foldAllAtIndentLevel(4)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-6': ->
|
||||
@foldAllAtIndentLevel(5)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-7': ->
|
||||
@foldAllAtIndentLevel(6)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-8': ->
|
||||
@foldAllAtIndentLevel(7)
|
||||
@scrollToCursorPosition()
|
||||
'editor:fold-at-indent-level-9': ->
|
||||
@foldAllAtIndentLevel(8)
|
||||
@scrollToCursorPosition()
|
||||
'editor:log-cursor-scope': -> showCursorScope(@getCursorScope(), notificationManager)
|
||||
'editor:copy-path': -> copyPathToClipboard(this, project, clipboard, false)
|
||||
'editor:copy-project-path': -> copyPathToClipboard(this, project, clipboard, true)
|
||||
|
||||
@@ -1510,28 +1510,28 @@ class TextEditorComponent {
|
||||
didMouseWheel (event) {
|
||||
const scrollSensitivity = this.props.model.getScrollSensitivity() / 100
|
||||
|
||||
let {deltaX, deltaY} = event
|
||||
let {wheelDeltaX, wheelDeltaY} = event
|
||||
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||
deltaX = (Math.sign(deltaX) === 1)
|
||||
? Math.max(1, deltaX * scrollSensitivity)
|
||||
: Math.min(-1, deltaX * scrollSensitivity)
|
||||
deltaY = 0
|
||||
if (Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)) {
|
||||
wheelDeltaX = (Math.sign(wheelDeltaX) === 1)
|
||||
? Math.max(1, wheelDeltaX * scrollSensitivity)
|
||||
: Math.min(-1, wheelDeltaX * scrollSensitivity)
|
||||
wheelDeltaY = 0
|
||||
} else {
|
||||
deltaX = 0
|
||||
deltaY = (Math.sign(deltaY) === 1)
|
||||
? Math.max(1, deltaY * scrollSensitivity)
|
||||
: Math.min(-1, deltaY * scrollSensitivity)
|
||||
wheelDeltaX = 0
|
||||
wheelDeltaY = (Math.sign(wheelDeltaY) === 1)
|
||||
? Math.max(1, wheelDeltaY * scrollSensitivity)
|
||||
: Math.min(-1, wheelDeltaY * scrollSensitivity)
|
||||
}
|
||||
|
||||
if (this.getPlatform() !== 'darwin' && event.shiftKey) {
|
||||
let temp = deltaX
|
||||
deltaX = deltaY
|
||||
deltaY = temp
|
||||
let temp = wheelDeltaX
|
||||
wheelDeltaX = wheelDeltaY
|
||||
wheelDeltaY = temp
|
||||
}
|
||||
|
||||
const scrollLeftChanged = deltaX !== 0 && this.setScrollLeft(this.getScrollLeft() + deltaX)
|
||||
const scrollTopChanged = deltaY !== 0 && this.setScrollTop(this.getScrollTop() + deltaY)
|
||||
const scrollLeftChanged = wheelDeltaX !== 0 && this.setScrollLeft(this.getScrollLeft() - wheelDeltaX)
|
||||
const scrollTopChanged = wheelDeltaY !== 0 && this.setScrollTop(this.getScrollTop() - wheelDeltaY)
|
||||
|
||||
if (scrollLeftChanged || scrollTopChanged) this.updateSync()
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
isHighSurrogate = (charCode) ->
|
||||
0xD800 <= charCode <= 0xDBFF
|
||||
|
||||
isLowSurrogate = (charCode) ->
|
||||
0xDC00 <= charCode <= 0xDFFF
|
||||
|
||||
isVariationSelector = (charCode) ->
|
||||
0xFE00 <= charCode <= 0xFE0F
|
||||
|
||||
isCombiningCharacter = (charCode) ->
|
||||
0x0300 <= charCode <= 0x036F or
|
||||
0x1AB0 <= charCode <= 0x1AFF or
|
||||
0x1DC0 <= charCode <= 0x1DFF or
|
||||
0x20D0 <= charCode <= 0x20FF or
|
||||
0xFE20 <= charCode <= 0xFE2F
|
||||
|
||||
# Are the given character codes a high/low surrogate pair?
|
||||
#
|
||||
# * `charCodeA` The first character code {Number}.
|
||||
# * `charCode2` The second character code {Number}.
|
||||
#
|
||||
# Return a {Boolean}.
|
||||
isSurrogatePair = (charCodeA, charCodeB) ->
|
||||
isHighSurrogate(charCodeA) and isLowSurrogate(charCodeB)
|
||||
|
||||
# Are the given character codes a variation sequence?
|
||||
#
|
||||
# * `charCodeA` The first character code {Number}.
|
||||
# * `charCode2` The second character code {Number}.
|
||||
#
|
||||
# Return a {Boolean}.
|
||||
isVariationSequence = (charCodeA, charCodeB) ->
|
||||
not isVariationSelector(charCodeA) and isVariationSelector(charCodeB)
|
||||
|
||||
# Are the given character codes a combined character pair?
|
||||
#
|
||||
# * `charCodeA` The first character code {Number}.
|
||||
# * `charCode2` The second character code {Number}.
|
||||
#
|
||||
# Return a {Boolean}.
|
||||
isCombinedCharacter = (charCodeA, charCodeB) ->
|
||||
not isCombiningCharacter(charCodeA) and isCombiningCharacter(charCodeB)
|
||||
|
||||
# Is the character at the given index the start of high/low surrogate pair
|
||||
# a variation sequence, or a combined character?
|
||||
#
|
||||
# * `string` The {String} to check for a surrogate pair, variation sequence,
|
||||
# or combined character.
|
||||
# * `index` The {Number} index to look for a surrogate pair, variation
|
||||
# sequence, or combined character.
|
||||
#
|
||||
# Return a {Boolean}.
|
||||
isPairedCharacter = (string, index=0) ->
|
||||
charCodeA = string.charCodeAt(index)
|
||||
charCodeB = string.charCodeAt(index + 1)
|
||||
isSurrogatePair(charCodeA, charCodeB) or
|
||||
isVariationSequence(charCodeA, charCodeB) or
|
||||
isCombinedCharacter(charCodeA, charCodeB)
|
||||
|
||||
IsJapaneseKanaCharacter = (charCode) ->
|
||||
0x3000 <= charCode <= 0x30FF
|
||||
|
||||
isCJKUnifiedIdeograph = (charCode) ->
|
||||
0x4E00 <= charCode <= 0x9FFF
|
||||
|
||||
isFullWidthForm = (charCode) ->
|
||||
0xFF01 <= charCode <= 0xFF5E or
|
||||
0xFFE0 <= charCode <= 0xFFE6
|
||||
|
||||
isDoubleWidthCharacter = (character) ->
|
||||
charCode = character.charCodeAt(0)
|
||||
|
||||
IsJapaneseKanaCharacter(charCode) or
|
||||
isCJKUnifiedIdeograph(charCode) or
|
||||
isFullWidthForm(charCode)
|
||||
|
||||
isHalfWidthCharacter = (character) ->
|
||||
charCode = character.charCodeAt(0)
|
||||
|
||||
0xFF65 <= charCode <= 0xFFDC or
|
||||
0xFFE8 <= charCode <= 0xFFEE
|
||||
|
||||
isKoreanCharacter = (character) ->
|
||||
charCode = character.charCodeAt(0)
|
||||
|
||||
0xAC00 <= charCode <= 0xD7A3 or
|
||||
0x1100 <= charCode <= 0x11FF or
|
||||
0x3130 <= charCode <= 0x318F or
|
||||
0xA960 <= charCode <= 0xA97F or
|
||||
0xD7B0 <= charCode <= 0xD7FF
|
||||
|
||||
isCJKCharacter = (character) ->
|
||||
isDoubleWidthCharacter(character) or
|
||||
isHalfWidthCharacter(character) or
|
||||
isKoreanCharacter(character)
|
||||
|
||||
isWordStart = (previousCharacter, character) ->
|
||||
(previousCharacter is ' ' or previousCharacter is '\t') and
|
||||
(character isnt ' ' and character isnt '\t')
|
||||
|
||||
isWrapBoundary = (previousCharacter, character) ->
|
||||
isWordStart(previousCharacter, character) or isCJKCharacter(character)
|
||||
|
||||
# Does the given string contain at least surrogate pair, variation sequence,
|
||||
# or combined character?
|
||||
#
|
||||
# * `string` The {String} to check for the presence of paired characters.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
hasPairedCharacter = (string) ->
|
||||
index = 0
|
||||
while index < string.length
|
||||
return true if isPairedCharacter(string, index)
|
||||
index++
|
||||
false
|
||||
|
||||
module.exports = {
|
||||
isPairedCharacter, hasPairedCharacter,
|
||||
isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter,
|
||||
isWrapBoundary
|
||||
}
|
||||
130
src/text-utils.js
Normal file
130
src/text-utils.js
Normal file
@@ -0,0 +1,130 @@
|
||||
const isHighSurrogate = (charCode) =>
|
||||
charCode >= 0xD800 && charCode <= 0xDBFF
|
||||
|
||||
const isLowSurrogate = (charCode) =>
|
||||
charCode >= 0xDC00 && charCode <= 0xDFFF
|
||||
|
||||
const isVariationSelector = (charCode) =>
|
||||
charCode >= 0xFE00 && charCode <= 0xFE0F
|
||||
|
||||
const isCombiningCharacter = charCode =>
|
||||
(charCode >= 0x0300 && charCode <= 0x036F) ||
|
||||
(charCode >= 0x1AB0 && charCode <= 0x1AFF) ||
|
||||
(charCode >= 0x1DC0 && charCode <= 0x1DFF) ||
|
||||
(charCode >= 0x20D0 && charCode <= 0x20FF) ||
|
||||
(charCode >= 0xFE20 && charCode <= 0xFE2F)
|
||||
|
||||
// Are the given character codes a high/low surrogate pair?
|
||||
//
|
||||
// * `charCodeA` The first character code {Number}.
|
||||
// * `charCode2` The second character code {Number}.
|
||||
//
|
||||
// Return a {Boolean}.
|
||||
const isSurrogatePair = (charCodeA, charCodeB) =>
|
||||
isHighSurrogate(charCodeA) && isLowSurrogate(charCodeB)
|
||||
|
||||
// Are the given character codes a variation sequence?
|
||||
//
|
||||
// * `charCodeA` The first character code {Number}.
|
||||
// * `charCode2` The second character code {Number}.
|
||||
//
|
||||
// Return a {Boolean}.
|
||||
const isVariationSequence = (charCodeA, charCodeB) =>
|
||||
!isVariationSelector(charCodeA) && isVariationSelector(charCodeB)
|
||||
|
||||
// Are the given character codes a combined character pair?
|
||||
//
|
||||
// * `charCodeA` The first character code {Number}.
|
||||
// * `charCode2` The second character code {Number}.
|
||||
//
|
||||
// Return a {Boolean}.
|
||||
const isCombinedCharacter = (charCodeA, charCodeB) =>
|
||||
!isCombiningCharacter(charCodeA) && isCombiningCharacter(charCodeB)
|
||||
|
||||
// Is the character at the given index the start of high/low surrogate pair
|
||||
// a variation sequence, or a combined character?
|
||||
//
|
||||
// * `string` The {String} to check for a surrogate pair, variation sequence,
|
||||
// or combined character.
|
||||
// * `index` The {Number} index to look for a surrogate pair, variation
|
||||
// sequence, or combined character.
|
||||
//
|
||||
// Return a {Boolean}.
|
||||
const isPairedCharacter = (string, index = 0) => {
|
||||
const charCodeA = string.charCodeAt(index)
|
||||
const charCodeB = string.charCodeAt(index + 1)
|
||||
return isSurrogatePair(charCodeA, charCodeB) ||
|
||||
isVariationSequence(charCodeA, charCodeB) ||
|
||||
isCombinedCharacter(charCodeA, charCodeB)
|
||||
}
|
||||
|
||||
const IsJapaneseKanaCharacter = charCode =>
|
||||
charCode >= 0x3000 && charCode <= 0x30FF
|
||||
|
||||
const isCJKUnifiedIdeograph = charCode =>
|
||||
charCode >= 0x4E00 && charCode <= 0x9FFF
|
||||
|
||||
const isFullWidthForm = charCode =>
|
||||
(charCode >= 0xFF01 && charCode <= 0xFF5E) ||
|
||||
(charCode >= 0xFFE0 && charCode <= 0xFFE6)
|
||||
|
||||
const isDoubleWidthCharacter = (character) => {
|
||||
const charCode = character.charCodeAt(0)
|
||||
|
||||
return IsJapaneseKanaCharacter(charCode) ||
|
||||
isCJKUnifiedIdeograph(charCode) ||
|
||||
isFullWidthForm(charCode)
|
||||
}
|
||||
|
||||
const isHalfWidthCharacter = (character) => {
|
||||
const charCode = character.charCodeAt(0)
|
||||
|
||||
return (charCode >= 0xFF65 && charCode <= 0xFFDC) ||
|
||||
(charCode >= 0xFFE8 && charCode <= 0xFFEE)
|
||||
}
|
||||
|
||||
const isKoreanCharacter = (character) => {
|
||||
const charCode = character.charCodeAt(0)
|
||||
|
||||
return (charCode >= 0xAC00 && charCode <= 0xD7A3) ||
|
||||
(charCode >= 0x1100 && charCode <= 0x11FF) ||
|
||||
(charCode >= 0x3130 && charCode <= 0x318F) ||
|
||||
(charCode >= 0xA960 && charCode <= 0xA97F) ||
|
||||
(charCode >= 0xD7B0 && charCode <= 0xD7FF)
|
||||
}
|
||||
|
||||
const isCJKCharacter = (character) =>
|
||||
isDoubleWidthCharacter(character) ||
|
||||
isHalfWidthCharacter(character) ||
|
||||
isKoreanCharacter(character)
|
||||
|
||||
const isWordStart = (previousCharacter, character) =>
|
||||
((previousCharacter === ' ') || (previousCharacter === '\t')) &&
|
||||
((character !== ' ') && (character !== '\t'))
|
||||
|
||||
const isWrapBoundary = (previousCharacter, character) =>
|
||||
isWordStart(previousCharacter, character) || isCJKCharacter(character)
|
||||
|
||||
// Does the given string contain at least surrogate pair, variation sequence,
|
||||
// or combined character?
|
||||
//
|
||||
// * `string` The {String} to check for the presence of paired characters.
|
||||
//
|
||||
// Returns a {Boolean}.
|
||||
const hasPairedCharacter = (string) => {
|
||||
let index = 0
|
||||
while (index < string.length) {
|
||||
if (isPairedCharacter(string, index)) { return true }
|
||||
index++
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isPairedCharacter,
|
||||
hasPairedCharacter,
|
||||
isDoubleWidthCharacter,
|
||||
isHalfWidthCharacter,
|
||||
isKoreanCharacter,
|
||||
isWrapBoundary
|
||||
}
|
||||
@@ -50,6 +50,8 @@ class ThemeManager {
|
||||
// updating the list of active themes have completed.
|
||||
//
|
||||
// * `callback` {Function}
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeActiveThemes (callback) {
|
||||
return this.emitter.on('did-change-active-themes', callback)
|
||||
}
|
||||
|
||||
@@ -114,7 +114,9 @@ class TooltipManager {
|
||||
add (target, options) {
|
||||
if (target.jquery) {
|
||||
const disposable = new CompositeDisposable()
|
||||
for (const element of target) { disposable.add(this.add(element, options)) }
|
||||
for (let i = 0; i < target.length; i++) {
|
||||
disposable.add(this.add(target[i], options))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user