Merge branch 'master' into mb-use-language-mode-api

This commit is contained in:
Max Brunsfeld
2017-11-15 11:53:54 -08:00
27 changed files with 2609 additions and 2521 deletions

View File

@@ -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/

View File

@@ -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

View File

@@ -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.

View File

@@ -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).

View File

@@ -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).

View File

@@ -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).

View File

@@ -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"
},

View File

@@ -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) {

View File

@@ -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'

View 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')
})
})
})

View File

@@ -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 () => {

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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]],

View File

@@ -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("")).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, "")).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
View 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('')).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, '')).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)
})
)
})

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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()) {

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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
View 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
}

View File

@@ -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)
}

View File

@@ -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
}