diff --git a/.travis.yml b/.travis.yml
index d5918dc8d..06416e8e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ git:
matrix:
include:
- os: linux
- env: NODE_VERSION=4.4.7 DISPLAY=:99.0 CC=clang CXX=clang++ npm_config_clang=1
+ env: NODE_VERSION=6.9.4 DISPLAY=:99.0 CC=clang CXX=clang++ npm_config_clang=1
sudo: false
@@ -19,7 +19,9 @@ install:
- npm install -g npm
- script/build --create-debian-package --create-rpm-package --compress-artifacts
-script: script/test
+script:
+ - script/lint
+ - script/test
cache:
directories:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ceb4186d9..49606b50c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -84,7 +84,7 @@ When we make a significant decision in how we maintain the project and what we c
This section guides you through submitting a bug report for Atom. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
-Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). If you'd like, you can use [this template](#template-for-submitting-bug-reports) to structure the information.
+Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster.
#### Before Submitting A Bug Report
@@ -95,7 +95,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
#### How Do I Submit A (Good) Bug Report?
-Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#atom-and-packages) your bug is related to, create an issue on that repository and provide the following information.
+Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#atom-and-packages) your bug is related to, create an issue on that repository and provide the following information by filling in [the template](ISSUE_TEMPLATE.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
@@ -128,47 +128,11 @@ Include details about your configuration and environment:
* **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?
-#### Template For Submitting Bug Reports
-
- [Short description of problem here]
-
- **Reproduction Steps:**
-
- 1. [First Step]
- 2. [Second Step]
- 3. [Other Steps...]
-
- **Expected behavior:**
-
- [Describe expected behavior here]
-
- **Observed behavior:**
-
- [Describe observed behavior here]
-
- **Screenshots and GIFs**
-
- 
-
- **Atom version:** [Enter Atom version here]
- **OS and version:** [Enter OS name and version here]
-
- **Installed packages:**
-
- [List of installed packages here]
-
- **Additional information:**
-
- * Problem can be reproduced in safe mode: [Yes/No]
- * Problem started happening recently, didn't happen in an older version of Atom: [Yes/No]
- * Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
- * Problem happens with all files and projects, not only some files or projects: [Yes/No]
-
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Atom, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
-Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). If you'd like, you can use [this template](#template-for-submitting-enhancement-suggestions) to structure the information.
+Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](ISSUE_TEMPLATE.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before Submitting An Enhancement Suggestion
@@ -191,33 +155,6 @@ Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com
* **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).
* **Specify the name and version of the OS you're using.**
-#### Template For Submitting Enhancement Suggestions
-
- [Short description of suggestion]
-
- **Steps which explain the enhancement**
-
- 1. [First Step]
- 2. [Second Step]
- 3. [Other Steps...]
-
- **Current and suggested behavior**
-
- [Describe current and suggested behavior here]
-
- **Why would the enhancement be useful to most users**
-
- [Explain why the enhancement would be useful to most users]
-
- [List some other text editors or applications where this enhancement exists]
-
- **Screenshots and GIFs**
-
- 
-
- **Atom Version:** [Enter Atom version here]
- **OS and Version:** [Enter OS name and version here]
-
### Your First Code Contribution
Unsure where to begin contributing to Atom? You can start by looking through these `beginner` and `help-wanted` issues:
@@ -231,6 +168,7 @@ If you want to read about using Atom or developing packages in Atom, the [Atom F
### Pull Requests
+* Fill in [the required template](PULL_REQUEST_TEMPLATE.md)
* 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
@@ -240,16 +178,12 @@ If you want to read about using Atom or developing packages in Atom, the [Atom F
* End files with a newline.
* Place requires in the following order:
* Built in Node Modules (such as `path`)
- * Built in Atom and Atom Shell Modules (such as `atom`, `shell`)
+ * Built in Atom and Electron Modules (such as `atom`, `remote`)
* Local Modules (using relative paths)
* Place class properties in the following order:
* Class methods and properties (methods starting with a `@`)
* Instance methods and properties
-* Avoid platform-dependent code:
- * Use `require('fs-plus').getHomeDirectory()` to get the home directory.
- * Use `path.join()` to concatenate filenames.
- * Use `os.tmpdir()` rather than `/tmp` when you need to reference the
- temporary directory.
+* [Avoid platform-dependent code](http://flight-manual.atom.io/hacking-atom/sections/cross-platform-compatibility/)
* Using a plain `return` when returning explicitly at the end of a function.
* Not `return null`, `return undefined`, `null`, or `undefined`
@@ -427,12 +361,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
| `deprecation-help` | [search][search-atom-repo-label-deprecation-help] | [search][search-atom-org-label-deprecation-help] | Issues for helping package authors remove usage of deprecated APIs in packages. |
| `electron` | [search][search-atom-repo-label-electron] | [search][search-atom-org-label-electron] | Issues that require changes to [Electron](https://electron.atom.io) to fix or implement. |
-#### Core Team Project Management
-
-| Label name | `atom/atom` :mag_right: | `atom`‑org :mag_right: | Description |
-| --- | --- | --- | --- |
-| `atom` | [search][search-atom-repo-label-atom] | [search][search-atom-org-label-atom] | Topics discussed for prioritization at the next meeting of Atom core team members. |
-
#### Pull Request Labels
| Label name | `atom/atom` :mag_right: | `atom`‑org :mag_right: | Description
@@ -519,8 +447,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
[search-atom-org-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Adeprecation-help
[search-atom-repo-label-electron]: https://github.com/issues?q=is%3Aissue+repo%3Aatom%2Fatom+is%3Aopen+label%3Aelectron
[search-atom-org-label-electron]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aelectron
-[search-atom-repo-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aatom
-[search-atom-org-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aatom
[search-atom-repo-label-work-in-progress]: https://github.com/pulls?q=is%3Aopen+is%3Apr+repo%3Aatom%2Fatom+label%3Awork-in-progress
[search-atom-org-label-work-in-progress]: https://github.com/pulls?q=is%3Aopen+is%3Apr+user%3Aatom+label%3Awork-in-progress
[search-atom-repo-label-needs-review]: https://github.com/pulls?q=is%3Aopen+is%3Apr+repo%3Aatom%2Fatom+label%3Aneeds-review
@@ -533,4 +459,4 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
[search-atom-org-label-needs-testing]: https://github.com/pulls?q=is%3Aopen+is%3Apr+user%3Aatom+label%3Aneeds-testing
[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
+[help-wanted]:https://github.com/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc+-label%3Abeginner
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index d2ac45f05..b60bb86c9 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -1,17 +1,23 @@
+
+
### Prerequisites
-* [ ] Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode)?
-* [ ] Are you running the [latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version)?
-* [ ] Did you check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)?
-* [ ] Did you check the [FAQs on Discuss](https://discuss.atom.io/c/faq)?
-* [ ] Are you reporting to the [correct repository](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#atom-and-packages)?
-* [ ] Did you [perform a cursory search](https://github.com/issues?q=is%3Aissue+user%3Aatom+-repo%3Aatom%2Felectron) to see if your bug or enhancement is already reported?
-
-For more information on how to write a good [bug report](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#how-do-i-submit-a-good-bug-report) or [enhancement request](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#how-do-i-submit-a-good-enhancement-suggestion), see the `CONTRIBUTING` guide.
+* [ ] Put an X between the brackets on this line if you have done all of the following:
+ * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode
+ * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/
+ * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq
+ * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom
+ * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages
### Description
-[Description of the bug or feature]
+[Description of the issue]
### Steps to Reproduce
@@ -19,10 +25,16 @@ For more information on how to write a good [bug report](https://github.com/atom
2. [Second Step]
3. [and so on...]
-**Expected behavior:** [What you expected to happen]
+**Expected behavior:** [What you expect to happen]
-**Actual behavior:** [What actually happened]
+**Actual behavior:** [What actually happens]
+
+**Reproduces how often:** [What percentage of the time does it reproduce?]
### Versions
-You can get this information from executing `atom --version` and `apm --version` at the command line. Also, please include the OS and what version of the OS you're running.
+You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running.
+
+### Additional Information
+
+Any additional information, configuration or data that might be necessary to reproduce the issue.
diff --git a/LICENSE.md b/LICENSE.md
index e4a68c7dc..5bdf03cde 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2016 GitHub Inc.
+Copyright (c) 2011-2017 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000..a578c38ce
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,32 @@
+### Requirements
+
+* Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
+* All new code requires tests to ensure against regressions
+
+### Description of the Change
+
+
+
+### Alternate Designs
+
+
+
+### Why Should This Be In Core?
+
+
+
+### Benefits
+
+
+
+### Possible Drawbacks
+
+
+
+### Applicable Issues
+
+
diff --git a/README.md b/README.md
index 8c09d3d40..74a5b4cee 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@

-[](https://circleci.com/gh/atom/atom) [](https://travis-ci.org/atom/atom) [](https://ci.appveyor.com/project/Atom/atom)
+[](https://circleci.com/gh/atom/atom) [](https://travis-ci.org/atom/atom) [](https://ci.appveyor.com/project/Atom/atom)
[](https://david-dm.org/atom/atom)
[](http://atom-slack.herokuapp.com/)
@@ -93,7 +93,7 @@ repeat these steps to upgrade to future releases.
## Building
* [Linux](./docs/build-instructions/linux.md)
-* [macOS](./docs/build-instructions/macos.md)
+* [macOS](./docs/build-instructions/macOS.md)
* [FreeBSD](./docs/build-instructions/freebsd.md)
* [Windows](./docs/build-instructions/windows.md)
diff --git a/apm/package.json b/apm/package.json
index 732ab208a..ede2d1bd7 100644
--- a/apm/package.json
+++ b/apm/package.json
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
- "atom-package-manager": "1.15.1"
+ "atom-package-manager": "1.15.3"
}
}
diff --git a/appveyor.yml b/appveyor.yml
index af8260618..3d8c0b274 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -15,7 +15,7 @@ environment:
ATOM_DEV_RESOURCE_PATH: c:\projects\atom
matrix:
- - NODE_VERSION: 4.4.5
+ - NODE_VERSION: 6.8.0
install:
- SET PATH=C:\Program Files\Atom\resources\cli;%PATH%
@@ -27,6 +27,7 @@ build_script:
- script\build.cmd --code-sign --create-windows-installer --compress-artifacts
test_script:
+ - script\lint.cmd
- script\test.cmd
deploy: off
@@ -50,6 +51,4 @@ cache:
- '%APPVEYOR_BUILD_FOLDER%\node_modules'
- '%APPVEYOR_BUILD_FOLDER%\electron'
- '%USERPROFILE%\.atom\.apm'
- - '%USERPROFILE%\.atom\.node-gyp\.atom'
- - '%USERPROFILE%\.atom\.npm'
- '%USERPROFILE%\.atom\compile-cache'
diff --git a/atom.sh b/atom.sh
index a8c30fa19..6b0e94430 100755
--- a/atom.sh
+++ b/atom.sh
@@ -55,27 +55,38 @@ if [ $EXPECT_OUTPUT ]; then
fi
if [ $OS == 'Mac' ]; then
+ if [ -L "$0" ]; then
+ SCRIPT="$(readlink "$0")"
+ else
+ SCRIPT="$0"
+ fi
+ ATOM_APP="$(dirname "$(dirname "$(dirname "$(dirname "$SCRIPT")")")")"
+ if [ "$ATOM_APP" == . ]; then
+ unset ATOM_APP
+ else
+ ATOM_PATH="$(dirname "$ATOM_APP")"
+ ATOM_APP_NAME="$(basename "$ATOM_APP")"
+ fi
+
if [ -n "$BETA_VERSION" ]; then
- ATOM_APP_NAME="Atom Beta.app"
ATOM_EXECUTABLE_NAME="Atom Beta"
else
- ATOM_APP_NAME="Atom.app"
ATOM_EXECUTABLE_NAME="Atom"
fi
if [ -z "${ATOM_PATH}" ]; then
- # If ATOM_PATH isnt set, check /Applications and then ~/Applications for Atom.app
+ # If ATOM_PATH isn't set, check /Applications and then ~/Applications for Atom.app
if [ -x "/Applications/$ATOM_APP_NAME" ]; then
ATOM_PATH="/Applications"
elif [ -x "$HOME/Applications/$ATOM_APP_NAME" ]; then
ATOM_PATH="$HOME/Applications"
else
- # We havent found an Atom.app, use spotlight to search for Atom
+ # We haven't found an Atom.app, use spotlight to search for Atom
ATOM_PATH="$(mdfind "kMDItemCFBundleIdentifier == 'com.github.atom'" | grep -v ShipIt | head -1 | xargs -0 dirname)"
# Exit if Atom can't be found
if [ ! -x "$ATOM_PATH/$ATOM_APP_NAME" ]; then
- echo "Cannot locate Atom.app, it is usually located in /Applications. Set the ATOM_PATH environment variable to the directory containing Atom.app."
+ echo "Cannot locate ${ATOM_APP_NAME}, it is usually located in /Applications. Set the ATOM_PATH environment variable to the directory containing ${ATOM_APP_NAME}."
exit 1
fi
fi
diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js
index 694729724..64c6f4d94 100644
--- a/benchmarks/text-editor-large-file-construction.bench.js
+++ b/benchmarks/text-editor-large-file-construction.bench.js
@@ -26,7 +26,7 @@ export default async function ({test}) {
console.log(text.length / 1024)
let t0 = window.performance.now()
- const buffer = new TextBuffer(text)
+ const buffer = new TextBuffer({text})
const editor = new TextEditor({buffer, largeFileMode: true})
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()
diff --git a/benchmarks/text-editor-long-lines.bench.js b/benchmarks/text-editor-long-lines.bench.js
new file mode 100644
index 000000000..a99220d4e
--- /dev/null
+++ b/benchmarks/text-editor-long-lines.bench.js
@@ -0,0 +1,97 @@
+/** @babel */
+
+import path from 'path'
+import fs from 'fs'
+import {TextEditor, TextBuffer} from 'atom'
+
+const SIZES_IN_KB = [
+ 512,
+ 1024,
+ 2048
+]
+const REPEATED_TEXT = fs.readFileSync(path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'), 'utf8').replace(/\n/g, '')
+const TEXT = REPEATED_TEXT.repeat(Math.ceil(SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024 / REPEATED_TEXT.length))
+
+export default async function ({test}) {
+ const data = []
+
+ const workspaceElement = atom.views.getView(atom.workspace)
+ document.body.appendChild(workspaceElement)
+
+ atom.packages.loadPackages()
+ await atom.packages.activate()
+
+ console.log(atom.getLoadSettings().resourcePath);
+
+ for (let pane of atom.workspace.getPanes()) {
+ pane.destroy()
+ }
+
+ for (const sizeInKB of SIZES_IN_KB) {
+ const text = TEXT.slice(0, sizeInKB * 1024)
+ console.log(text.length / 1024)
+
+ let t0 = window.performance.now()
+ const buffer = new TextBuffer({text})
+ const editor = new TextEditor({buffer, largeFileMode: true})
+ editor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
+ atom.workspace.getActivePane().activateItem(editor)
+ let t1 = window.performance.now()
+
+ data.push({
+ name: 'Opening a large single-line file',
+ x: sizeInKB,
+ duration: t1 - t0
+ })
+
+ const tickDurations = []
+ for (let i = 0; i < 20; i++) {
+ await timeout(50)
+ t0 = window.performance.now()
+ await timeout(0)
+ t1 = window.performance.now()
+ tickDurations[i] = t1 - t0
+ }
+
+ data.push({
+ name: 'Max time event loop was blocked after opening a large single-line file',
+ x: sizeInKB,
+ duration: Math.max(...tickDurations)
+ })
+
+ t0 = window.performance.now()
+ editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({
+ top: 100,
+ left: 30
+ }))
+ t1 = window.performance.now()
+
+ data.push({
+ name: 'Clicking the editor after opening a large single-line file',
+ x: sizeInKB,
+ duration: t1 - t0
+ })
+
+ t0 = window.performance.now()
+ editor.element.setScrollTop(editor.element.getScrollTop() + 100)
+ t1 = window.performance.now()
+
+ data.push({
+ name: 'Scrolling down after opening a large single-line file',
+ x: sizeInKB,
+ duration: t1 - t0
+ })
+
+ editor.destroy()
+ buffer.destroy()
+ await timeout(10000)
+ }
+
+ workspaceElement.remove()
+
+ return data
+}
+
+function timeout (duration) {
+ return new Promise((resolve) => setTimeout(resolve, duration))
+}
diff --git a/circle.yml b/circle.yml
index ee4eafc1f..c264754d4 100644
--- a/circle.yml
+++ b/circle.yml
@@ -16,8 +16,8 @@ general:
dependencies:
pre:
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.3/install.sh | bash
- - nvm install 4.4.7
- - nvm use 4.4.7
+ - nvm install 6.9.4
+ - nvm use 6.9.4
- npm install -g npm
override:
diff --git a/docs/build-instructions/build-status.md b/docs/build-instructions/build-status.md
index a17923224..6a366b430 100644
--- a/docs/build-instructions/build-status.md
+++ b/docs/build-instructions/build-status.md
@@ -1,112 +1,115 @@
# Atom build status
-| System | macOS | Windows | Dependencies |
-|--------|------|---------|--------------|
-| Atom | [](https://travis-ci.org/atom/atom) | [](https://ci.appveyor.com/project/Atom/atom) | [](https://david-dm.org/atom/atom) |
-| APM | [](https://travis-ci.org/atom/apm) | [](https://ci.appveyor.com/project/Atom/apm/branch/master) | [](https://david-dm.org/atom/apm) |
-| Electron | [](https://travis-ci.org/electron/electron) | [](https://ci.appveyor.com/project/Atom/electron) | [](https://david-dm.org/electron/electron)
+| System | Travis | AppVeyor/Win | Circle/Mac | Dependencies |
+|--------|--------|--------------|------------|--------------|
+| [Atom](https://github.com/atom/atom) | [](https://travis-ci.org/atom/atom) | [](https://ci.appveyor.com/project/Atom/atom) | [](https://circleci.com/gh/atom/atom) | [](https://david-dm.org/atom/atom) |
+| [APM](https://github.com/atom/apm) | [](https://travis-ci.org/atom/apm) | [](https://ci.appveyor.com/project/Atom/apm/branch/master) | | [](https://david-dm.org/atom/apm) |
+| [Electron](https://github.com/electron/electron) | [](https://travis-ci.org/electron/electron) | [](https://ci.appveyor.com/project/Atom/electron) | | [](https://david-dm.org/electron/electron)
## Packages
-| Package | macOS | Windows | Dependencies |
-|---------|------|---------|--------------|
-| About | [](https://travis-ci.org/atom/about) | [](https://ci.appveyor.com/project/atom/about/branch/master) | [](https://david-dm.org/atom/about) |
-| Archive View | [](https://travis-ci.org/atom/archive-view) | [](https://ci.appveyor.com/project/Atom/archive-view/branch/master) | [](https://david-dm.org/atom/archive-view) |
-| AutoComplete Atom API | [](https://travis-ci.org/atom/autocomplete-atom-api) | [](https://ci.appveyor.com/project/Atom/autocomplete-atom-api/branch/master) | [](https://david-dm.org/atom/autocomplete-atom-api) |
-| Atom Space Pen Views | [](https://travis-ci.org/atom/atom-space-pen-views) | [](https://ci.appveyor.com/project/Atom/atom-space-pen-views/branch/master) | [](https://david-dm.org/atom/atom-space-pen-views) |
-| AutoComplete CSS | [](https://travis-ci.org/atom/autocomplete-css) | [](https://ci.appveyor.com/project/Atom/autocomplete-css/branch/master) | [](https://david-dm.org/atom/autocomplete-css) |
-| AutoComplete HTML | [](https://travis-ci.org/atom/autocomplete-html) | [](https://ci.appveyor.com/project/Atom/autocomplete-html/branch/master) | [](https://david-dm.org/atom/autocomplete-html) |
-| AutoComplete+ | [](https://travis-ci.org/atom/autocomplete-plus) | [](https://ci.appveyor.com/project/Atom/autocomplete-plus/branch/master) | [](https://david-dm.org/atom/autocomplete-plus) |
-| AutoComplete Snippets | [](https://travis-ci.org/atom/autocomplete-snippets) | [](https://ci.appveyor.com/project/Atom/autocomplete-snippets/branch/master) | [](https://david-dm.org/atom/autocomplete-snippets) |
-| AutoFlow | [](https://travis-ci.org/atom/autoflow) | [](https://ci.appveyor.com/project/Atom/autoflow/branch/master) | [](https://david-dm.org/atom/autoflow) |
-| AutoSave | [](https://travis-ci.org/atom/autosave) | [](https://ci.appveyor.com/project/Atom/autosave/branch/master) | [](https://david-dm.org/atom/autosave) |
-| Background Tips | [](https://travis-ci.org/atom/background-tips) | [](https://ci.appveyor.com/project/Atom/background-tips/branch/master) | [](https://david-dm.org/atom/background-tips) |
-| Bookmarks | [](https://travis-ci.org/atom/bookmarks) | [](https://ci.appveyor.com/project/Atom/bookmarks/branch/master) | [](https://david-dm.org/atom/bookmarks) |
-| Bracket Matcher | [](https://travis-ci.org/atom/bracket-matcher) | [](https://ci.appveyor.com/project/Atom/bracket-matcher/branch/master) | [](https://david-dm.org/atom/bracket-matcher) |
-| Command Palette | [](https://travis-ci.org/atom/command-palette) | [](https://ci.appveyor.com/project/Atom/command-palette/branch/master) | [](https://david-dm.org/atom/command-palette) |
-| Deprecation Cop | [](https://travis-ci.org/atom/deprecation-cop) | [](https://ci.appveyor.com/project/Atom/deprecation-cop/branch/master) | [](https://david-dm.org/atom/deprecation-cop) |
-| Dev Live Reload | [](https://travis-ci.org/atom/dev-live-reload) | [](https://ci.appveyor.com/project/Atom/dev-live-reload/branch/master) | [](https://david-dm.org/atom/dev-live-reload) |
-| Encoding Selector | [](https://travis-ci.org/atom/encoding-selector) | [](https://ci.appveyor.com/project/Atom/encoding-selector/branch/master) | [](https://david-dm.org/atom/encoding-selector) |
-| Exception Reporting | [](https://travis-ci.org/atom/exception-reporting) | [](https://ci.appveyor.com/project/Atom/exception-reporting/branch/master) | [](https://david-dm.org/atom/exception-reporting) |
-| Find and Replace | [](https://travis-ci.org/atom/find-and-replace) | [](https://ci.appveyor.com/project/Atom/find-and-replace/branch/master) | [](https://david-dm.org/atom/find-and-replace) |
-| Fuzzy Finder | [](https://travis-ci.org/atom/fuzzy-finder) | [](https://ci.appveyor.com/project/Atom/fuzzy-finder/branch/master) | [](https://david-dm.org/atom/fuzzy-finder) |
-| Git Diff | [](https://travis-ci.org/atom/git-diff) | [](https://ci.appveyor.com/project/Atom/git-diff/branch/master) | [](https://david-dm.org/atom/git-diff) |
-| Go to Line | [](https://travis-ci.org/atom/go-to-line) | [](https://ci.appveyor.com/project/Atom/go-to-line/branch/master) | [](https://david-dm.org/atom/go-to-line) |
-| Grammar Selector | [](https://travis-ci.org/atom/grammar-selector) | [](https://ci.appveyor.com/project/Atom/grammar-selector/branch/master) | [](https://david-dm.org/atom/grammar-selector) |
-| Image View | [](https://travis-ci.org/atom/image-view) | [](https://ci.appveyor.com/project/Atom/image-view/branch/master) | [](https://david-dm.org/atom/image-view) |
-| Incompatible Packages | [](https://travis-ci.org/atom/incompatible-packages) | [](https://ci.appveyor.com/project/Atom/incompatible-packages/branch/master) | [](https://david-dm.org/atom/incompatible-packages) |
-| Keybinding Resolver | [](https://travis-ci.org/atom/keybinding-resolver) | [](https://ci.appveyor.com/project/Atom/keybinding-resolver/branch/master) | [](https://david-dm.org/atom/keybinding-resolver) |
-| Line Ending Selector | [](https://travis-ci.org/atom/line-ending-selector) | [](https://ci.appveyor.com/project/Atom/line-ending-selector/branch/master) | [](https://david-dm.org/atom/line-ending-selector) |
-| Link | [](https://travis-ci.org/atom/link) | [](https://ci.appveyor.com/project/Atom/link/branch/master) | [](https://david-dm.org/atom/link) |
-| Markdown Preview | [](https://travis-ci.org/atom/markdown-preview) | [](https://ci.appveyor.com/project/Atom/markdown-preview/branch/master) | [](https://david-dm.org/atom/markdown-preview) |
-| Metrics | [](https://travis-ci.org/atom/metrics) | [](https://ci.appveyor.com/project/Atom/metrics/branch/master) | [](https://david-dm.org/atom/metrics) |
-| Notifications | [](https://travis-ci.org/atom/notifications) | [](https://ci.appveyor.com/project/Atom/notifications/branch/master) | [](https://david-dm.org/atom/notifications) |
-| Open on Github | [](https://travis-ci.org/atom/open-on-github) | [](https://ci.appveyor.com/project/Atom/open-on-github/branch/master) | [](https://david-dm.org/atom/open-on-github) |
-| Package Generator | [](https://travis-ci.org/atom/package-generator)| [](https://ci.appveyor.com/project/Atom/package-generator/branch/master) | [](https://david-dm.org/atom/package-generator) |
-| Settings View | [](https://travis-ci.org/atom/settings-view) | [](https://ci.appveyor.com/project/Atom/settings-view/branch/master) | [](https://david-dm.org/atom/settings-view) |
-| Snippets | [](https://travis-ci.org/atom/snippets) | [](https://ci.appveyor.com/project/Atom/snippets/branch/master) | [](https://david-dm.org/atom/snippets) |
-| Spell Check | [](https://travis-ci.org/atom/spell-check) | [](https://ci.appveyor.com/project/Atom/spell-check/branch/master) | [](https://david-dm.org/atom/spell-check) |
-| Status Bar | [](https://travis-ci.org/atom/status-bar) | [](https://ci.appveyor.com/project/Atom/status-bar/branch/master) | [](https://david-dm.org/atom/status-bar) |
-| Styleguide | [](https://travis-ci.org/atom/styleguide) | [](https://ci.appveyor.com/project/Atom/styleguide/branch/master) | [](https://david-dm.org/atom/styleguide) |
-| Symbols View | [](https://travis-ci.org/atom/symbols-view) | [](https://ci.appveyor.com/project/Atom/symbols-view/branch/master) | [](https://david-dm.org/atom/symbols-view) |
-| Tabs | [](https://travis-ci.org/atom/tabs) | [](https://ci.appveyor.com/project/Atom/tabs/branch/master) | [](https://david-dm.org/atom/tabs) |
-| Timecop | [](https://travis-ci.org/atom/timecop) | [](https://ci.appveyor.com/project/Atom/timecop/branch/master) | [](https://david-dm.org/atom/timecop) |
-| Tree View | [](https://travis-ci.org/atom/tree-view) | [](https://ci.appveyor.com/project/Atom/tree-view/branch/master) | [](https://david-dm.org/atom/tree-view) |
-| Update Package Dependencies | [](https://travis-ci.org/atom/update-package-dependencies) | [](https://ci.appveyor.com/project/Atom/update-package-dependencies/branch/master) | [](https://david-dm.org/atom/update-package-dependencies) |
-| Welcome | [](https://travis-ci.org/atom/welcome) | [](https://ci.appveyor.com/project/Atom/welcome/branch/master) | [](https://david-dm.org/atom/welcome) |
-| Whitespace | [](https://travis-ci.org/atom/whitespace) | [](https://ci.appveyor.com/project/Atom/whitespace/branch/master) | [](https://david-dm.org/atom/whitespace) |
-| Wrap Guide | [](https://travis-ci.org/atom/wrap-guide) | [](https://ci.appveyor.com/project/Atom/wrap-guide/branch/master) | [](https://david-dm.org/atom/wrap-guide) |
-
+| Package | Travis | AppVeyor/Win | Dependencies |
+|---------|--------|--------------|--------------|
+| [About](https://github.com/atom/about) | [](https://travis-ci.org/atom/about) | [](https://ci.appveyor.com/project/atom/about/branch/master) | [](https://david-dm.org/atom/about) |
+| [Archive View](https://github.com/atom/archive-view) | [](https://travis-ci.org/atom/archive-view) | [](https://ci.appveyor.com/project/Atom/archive-view/branch/master) | [](https://david-dm.org/atom/archive-view) |
+| [AutoComplete Atom API](https://github.com/atom/autocomplete-atom-api) | [](https://travis-ci.org/atom/autocomplete-atom-api) | [](https://ci.appveyor.com/project/Atom/autocomplete-atom-api/branch/master) | [](https://david-dm.org/atom/autocomplete-atom-api) |
+| [AutoComplete CSS](https://github.com/atom/autocomplete-css) | [](https://travis-ci.org/atom/autocomplete-css) | [](https://ci.appveyor.com/project/Atom/autocomplete-css/branch/master) | [](https://david-dm.org/atom/autocomplete-css) |
+| [AutoComplete HTML](https://github.com/atom/autocomplete-html) | [](https://travis-ci.org/atom/autocomplete-html) | [](https://ci.appveyor.com/project/Atom/autocomplete-html/branch/master) | [](https://david-dm.org/atom/autocomplete-html) |
+| [AutoComplete+](https://github.com/atom/autocomplete-plus) | [](https://travis-ci.org/atom/autocomplete-plus) | [](https://ci.appveyor.com/project/Atom/autocomplete-plus/branch/master) | [](https://david-dm.org/atom/autocomplete-plus) |
+| [AutoComplete Snippets](https://github.com/atom/autocomplete-snippets) | [](https://travis-ci.org/atom/autocomplete-snippets) | [](https://ci.appveyor.com/project/Atom/autocomplete-snippets/branch/master) | [](https://david-dm.org/atom/autocomplete-snippets) |
+| [AutoFlow](https://github.com/atom/autoflow) | [](https://travis-ci.org/atom/autoflow) | [](https://ci.appveyor.com/project/Atom/autoflow/branch/master) | [](https://david-dm.org/atom/autoflow) |
+| [AutoSave](https://github.com/atom/autosave) | [](https://travis-ci.org/atom/autosave) | [](https://ci.appveyor.com/project/Atom/autosave/branch/master) | [](https://david-dm.org/atom/autosave) |
+| [Background Tips](https://github.com/atom/background-tips) | [](https://travis-ci.org/atom/background-tips) | [](https://ci.appveyor.com/project/Atom/background-tips/branch/master) | [](https://david-dm.org/atom/background-tips) |
+| [Bookmarks](https://github.com/atom/bookmarks) | [](https://travis-ci.org/atom/bookmarks) | [](https://ci.appveyor.com/project/Atom/bookmarks/branch/master) | [](https://david-dm.org/atom/bookmarks) |
+| [Bracket Matcher](https://github.com/atom/bracket-matcher) | [](https://travis-ci.org/atom/bracket-matcher) | [](https://ci.appveyor.com/project/Atom/bracket-matcher/branch/master) | [](https://david-dm.org/atom/bracket-matcher) |
+| [Command Palette](https://github.com/atom/command-palette) | [](https://travis-ci.org/atom/command-palette) | [](https://ci.appveyor.com/project/Atom/command-palette/branch/master) | [](https://david-dm.org/atom/command-palette) |
+| [Deprecation Cop](https://github.com/atom/deprecation-cop) | [](https://travis-ci.org/atom/deprecation-cop) | [](https://ci.appveyor.com/project/Atom/deprecation-cop/branch/master) | [](https://david-dm.org/atom/deprecation-cop) |
+| [Dev Live Reload](https://github.com/atom/dev-live-reload) | [](https://travis-ci.org/atom/dev-live-reload) | [](https://ci.appveyor.com/project/Atom/dev-live-reload/branch/master) | [](https://david-dm.org/atom/dev-live-reload) |
+| [Encoding Selector](https://github.com/atom/encoding-selector) | [](https://travis-ci.org/atom/encoding-selector) | [](https://ci.appveyor.com/project/Atom/encoding-selector/branch/master) | [](https://david-dm.org/atom/encoding-selector) |
+| [Exception Reporting](https://github.com/atom/exception-reporting) | [](https://travis-ci.org/atom/exception-reporting) | [](https://ci.appveyor.com/project/Atom/exception-reporting/branch/master) | [](https://david-dm.org/atom/exception-reporting) |
+| [Find and Replace](https://github.com/atom/find-and-replace) | [](https://travis-ci.org/atom/find-and-replace) | [](https://ci.appveyor.com/project/Atom/find-and-replace/branch/master) | [](https://david-dm.org/atom/find-and-replace) |
+| [Fuzzy Finder](https://github.com/atom/fuzzy-finder) | [](https://travis-ci.org/atom/fuzzy-finder) | [](https://ci.appveyor.com/project/Atom/fuzzy-finder/branch/master) | [](https://david-dm.org/atom/fuzzy-finder) |
+| [Git Diff](https://github.com/atom/git-diff) | [](https://travis-ci.org/atom/git-diff) | [](https://ci.appveyor.com/project/Atom/git-diff/branch/master) | [](https://david-dm.org/atom/git-diff) |
+| [Go to Line](https://github.com/atom/go-to-line) | [](https://travis-ci.org/atom/go-to-line) | [](https://ci.appveyor.com/project/Atom/go-to-line/branch/master) | [](https://david-dm.org/atom/go-to-line) |
+| [Grammar Selector](https://github.com/atom/grammar-selector) | [](https://travis-ci.org/atom/grammar-selector) | [](https://ci.appveyor.com/project/Atom/grammar-selector/branch/master) | [](https://david-dm.org/atom/grammar-selector) |
+| [Image View](https://github.com/atom/image-view) | [](https://travis-ci.org/atom/image-view) | [](https://ci.appveyor.com/project/Atom/image-view/branch/master) | [](https://david-dm.org/atom/image-view) |
+| [Incompatible Packages](https://github.com/atom/incompatible-packages) | [](https://travis-ci.org/atom/incompatible-packages) | [](https://ci.appveyor.com/project/Atom/incompatible-packages/branch/master) | [](https://david-dm.org/atom/incompatible-packages) |
+| [Keybinding Resolver](https://github.com/atom/keybinding-resolver) | [](https://travis-ci.org/atom/keybinding-resolver) | [](https://ci.appveyor.com/project/Atom/keybinding-resolver/branch/master) | [](https://david-dm.org/atom/keybinding-resolver) |
+| [Line Ending Selector](https://github.com/atom/line-ending-selector) | [](https://travis-ci.org/atom/line-ending-selector) | [](https://ci.appveyor.com/project/Atom/line-ending-selector/branch/master) | [](https://david-dm.org/atom/line-ending-selector) |
+| [Link](https://github.com/atom/link) | [](https://travis-ci.org/atom/link) | [](https://ci.appveyor.com/project/Atom/link/branch/master) | [](https://david-dm.org/atom/link) |
+| [Markdown Preview](https://github.com/atom/markdown-preview) | [](https://travis-ci.org/atom/markdown-preview) | [](https://ci.appveyor.com/project/Atom/markdown-preview/branch/master) | [](https://david-dm.org/atom/markdown-preview) |
+| [Metrics](https://github.com/atom/metrics) | [](https://travis-ci.org/atom/metrics) | [](https://ci.appveyor.com/project/Atom/metrics/branch/master) | [](https://david-dm.org/atom/metrics) |
+| [Notifications](https://github.com/atom/notifications) | [](https://travis-ci.org/atom/notifications) | [](https://ci.appveyor.com/project/Atom/notifications/branch/master) | [](https://david-dm.org/atom/notifications) |
+| [Open on Github](https://github.com/atom/open-on-github) | [](https://travis-ci.org/atom/open-on-github) | [](https://ci.appveyor.com/project/Atom/open-on-github/branch/master) | [](https://david-dm.org/atom/open-on-github) |
+| [Package Generator](https://github.com/atom/package-generator) | [](https://travis-ci.org/atom/package-generator)| [](https://ci.appveyor.com/project/Atom/package-generator/branch/master) | [](https://david-dm.org/atom/package-generator) |
+| [Settings View](https://github.com/atom/settings-view) | [](https://travis-ci.org/atom/settings-view) | [](https://ci.appveyor.com/project/Atom/settings-view/branch/master) | [](https://david-dm.org/atom/settings-view) |
+| [Snippets](https://github.com/atom/snippets) | [](https://travis-ci.org/atom/snippets) | [](https://ci.appveyor.com/project/Atom/snippets/branch/master) | [](https://david-dm.org/atom/snippets) |
+| [Spell Check](https://github.com/atom/spell-check) | [](https://travis-ci.org/atom/spell-check) | [](https://ci.appveyor.com/project/Atom/spell-check/branch/master) | [](https://david-dm.org/atom/spell-check) |
+| [Status Bar](https://github.com/atom/status-bar) | [](https://travis-ci.org/atom/status-bar) | [](https://ci.appveyor.com/project/Atom/status-bar/branch/master) | [](https://david-dm.org/atom/status-bar) |
+| [Styleguide](https://github.com/atom/styleguide) | [](https://travis-ci.org/atom/styleguide) | [](https://ci.appveyor.com/project/Atom/styleguide/branch/master) | [](https://david-dm.org/atom/styleguide) |
+| [Symbols View](https://github.com/atom/symbols-view) | [](https://travis-ci.org/atom/symbols-view) | [](https://ci.appveyor.com/project/Atom/symbols-view/branch/master) | [](https://david-dm.org/atom/symbols-view) |
+| [Tabs](https://github.com/atom/tabs) | [](https://travis-ci.org/atom/tabs) | [](https://ci.appveyor.com/project/Atom/tabs/branch/master) | [](https://david-dm.org/atom/tabs) |
+| [Timecop](https://github.com/atom/timecop) | [](https://travis-ci.org/atom/timecop) | [](https://ci.appveyor.com/project/Atom/timecop/branch/master) | [](https://david-dm.org/atom/timecop) |
+| [Tree View](https://github.com/atom/tree-view) | [](https://travis-ci.org/atom/tree-view) | [](https://ci.appveyor.com/project/Atom/tree-view/branch/master) | [](https://david-dm.org/atom/tree-view) |
+| [Update Package Dependencies](https://github.com/atom/update-package-dependencies) | [](https://travis-ci.org/atom/update-package-dependencies) | [](https://ci.appveyor.com/project/Atom/update-package-dependencies/branch/master) | [](https://david-dm.org/atom/update-package-dependencies) |
+| [Welcome](https://github.com/atom/welcome) | [](https://travis-ci.org/atom/welcome) | [](https://ci.appveyor.com/project/Atom/welcome/branch/master) | [](https://david-dm.org/atom/welcome) |
+| [Whitespace](https://github.com/atom/whitespace) | [](https://travis-ci.org/atom/whitespace) | [](https://ci.appveyor.com/project/Atom/whitespace/branch/master) | [](https://david-dm.org/atom/whitespace) |
+| [Wrap Guide](https://github.com/atom/wrap-guide) | [](https://travis-ci.org/atom/wrap-guide) | [](https://ci.appveyor.com/project/Atom/wrap-guide/branch/master) | [](https://david-dm.org/atom/wrap-guide) |
## Libraries
-| Library | macOS | Windows | Dependencies |
-|---------|------|---------|--------------|
-| Clear Cut | [](https://travis-ci.org/atom/clear-cut) | [](https://ci.appveyor.com/project/Atom/clear-cut/branch/master) | [](https://david-dm.org/atom/clear-cut) |
-| Event Kit | [](https://travis-ci.org/atom/event-kit) | [](https://ci.appveyor.com/project/Atom/event-kit/branch/master) | [](https://david-dm.org/atom/event-kit) |
-| Fs Plus | [](https://travis-ci.org/atom/fs-plus) | [](https://ci.appveyor.com/project/Atom/fs-plus/branch/master) | [](https://david-dm.org/atom/fs-plus) |
-| Grim | [](https://travis-ci.org/atom/grim) | [](https://ci.appveyor.com/project/Atom/grim/branch/master) | [](https://david-dm.org/atom/grim) |
-| Jasmine Focused | [](https://travis-ci.org/atom/grim) | [](https://ci.appveyor.com/project/Atom/jasmine-focused/branch/master) | [](https://david-dm.org/atom/jasmine-focused) |
-| Property Accessors | [](https://travis-ci.org/atom/property-accessors) | [](https://ci.appveyor.com/project/Atom/property-accessors/branch/master) | [](https://david-dm.org/atom/property-accessors) |
-| TextBuffer | [](https://travis-ci.org/atom/text-buffer) | [](https://ci.appveyor.com/project/Atom/text-buffer/branch/master) | [](https://david-dm.org/atom/text-buffer) |
-| Underscore-Plus | [](https://travis-ci.org/atom/underscore-plus) | [](https://ci.appveyor.com/project/Atom/underscore-plus/branch/master) | [](https://david-dm.org/atom/underscore-plus) |
-
+| Library | Travis | AppVeyor/Win | Dependencies |
+|---------|--------|--------------|--------------|
+| [Clear Cut](https://github.com/atom/clear-cut) | [](https://travis-ci.org/atom/clear-cut) | [](https://ci.appveyor.com/project/Atom/clear-cut/branch/master) | [](https://david-dm.org/atom/clear-cut) |
+| [Event Kit](https://github.com/atom/event-kit) | [](https://travis-ci.org/atom/event-kit) | [](https://ci.appveyor.com/project/Atom/event-kit/branch/master) | [](https://david-dm.org/atom/event-kit) |
+| [First Mate](https://github.com/atom/first-mate) | [](https://travis-ci.org/atom/first-mate) | [](https://ci.appveyor.com/project/Atom/first-mate) | [](https://david-dm.org/atom/first-mate) |
+| [Fs Plus](https://github.com/atom/fs-plus) | [](https://travis-ci.org/atom/fs-plus) | [](https://ci.appveyor.com/project/Atom/fs-plus/branch/master) | [](https://david-dm.org/atom/fs-plus) |
+| [Grim](https://github.com/atom/grim) | [](https://travis-ci.org/atom/grim) | [](https://ci.appveyor.com/project/Atom/grim/branch/master) | [](https://david-dm.org/atom/grim) |
+| [Jasmine Focused](https://github.com/atom/jasmine-focused) | [](https://travis-ci.org/atom/grim) | [](https://ci.appveyor.com/project/Atom/jasmine-focused/branch/master) | [](https://david-dm.org/atom/jasmine-focused) |
+| [Keyboard Layout](https://github.com/atom/keyboard-layout) | [](https://travis-ci.org/atom/keyboard-layout) | [](https://ci.appveyor.com/project/Atom/keyboard-layout) | [](https://david-dm.org/atom/keyboard-layout) |
+| [Oniguruma](https://github.com/atom/node-oniguruma) | [](https://travis-ci.org/atom/node-oniguruma) | [](https://ci.appveyor.com/project/Atom/node-oniguruma/branch/master) | [](https://david-dm.org/atom/node-oniguruma) |
+| [PathWatcher](https://github.com/atom/node-pathwatcher) | [](https://travis-ci.org/atom/node-pathwatcher) | [](https://ci.appveyor.com/project/Atom/node-pathwatcher) | [](https://david-dm.org/atom/node-pathwatcher) |
+| [Property Accessors](https://github.com/atom/property-accessors) | [](https://travis-ci.org/atom/property-accessors) | [](https://ci.appveyor.com/project/Atom/property-accessors/branch/master) | [](https://david-dm.org/atom/property-accessors) |
+| [Season](https://github.com/atom/season) | [](https://travis-ci.org/atom/season) | [](https://ci.appveyor.com/project/Atom/season) | [](https://david-dm.org/atom/season) |
+| [Superstring](https://github.com/atom/superstring) | [](https://travis-ci.org/atom/superstring) | [](https://ci.appveyor.com/project/Atom/superstring/branch/master) | | [](https://david-dm.org/atom/superstring) |
+| [TextBuffer](https://github.com/atom/text-buffer) | [](https://travis-ci.org/atom/text-buffer) | [](https://ci.appveyor.com/project/Atom/text-buffer/branch/master) | [](https://david-dm.org/atom/text-buffer) |
+| [Underscore-Plus](https://github.com/atom/underscore-plus) | [](https://travis-ci.org/atom/underscore-plus) | [](https://ci.appveyor.com/project/Atom/underscore-plus/branch/master) | [](https://david-dm.org/atom/underscore-plus) |
## Tools
-| Language | macOS | Windows | Dependencies |
-|----------|------|---------|--------------|
-| AtomDoc | [](https://travis-ci.org/atom/atomdoc) | [](https://ci.appveyor.com/project/Atom/atomdoc/branch/master) | [](https://david-dm.org/atom/atomdoc)
+| Language | Travis | AppVeyor/Win | Dependencies |
+|----------|--------|--------------|--------------|
+| [AtomDoc](https://github.com/atom/atomdoc) | [](https://travis-ci.org/atom/atomdoc) | [](https://ci.appveyor.com/project/Atom/atomdoc/branch/master) | [](https://david-dm.org/atom/atomdoc)
## Languages
-| Language | macOS | Windows |
-|----------|------|---------|
-| C/C++ | [](https://travis-ci.org/atom/language-c) | [](https://ci.appveyor.com/project/Atom/language-c/branch/master) |
-| C# | [](https://travis-ci.org/atom/language-csharp) | [](https://ci.appveyor.com/project/Atom/language-csharp/branch/master) |
-| Clojure | [](https://travis-ci.org/atom/language-clojure) | [](https://ci.appveyor.com/project/Atom/language-clojure/branch/master) |
-| CoffeeScript | [](https://travis-ci.org/atom/language-coffee-script) | [](https://ci.appveyor.com/project/Atom/language-coffee-script/branch/master) |
-| CSS | [](https://travis-ci.org/atom/language-css) | [](https://ci.appveyor.com/project/Atom/language-css/branch/master) |
-| Git | [](https://travis-ci.org/atom/language-git) | [](https://ci.appveyor.com/project/Atom/language-git/branch/master) |
-| GitHub Flavored Markdown | [](https://travis-ci.org/atom/language-gfm) | [](https://ci.appveyor.com/project/Atom/language-gfm/branch/master) |
-| Go | [](https://travis-ci.org/atom/language-go) | [](https://ci.appveyor.com/project/Atom/language-go/branch/master) |
-| HTML | [](https://travis-ci.org/atom/language-html) | [](https://ci.appveyor.com/project/Atom/language-html/branch/master) |
-| Hyperlink | [](https://travis-ci.org/atom/language-hyperlink) | [](https://ci.appveyor.com/project/Atom/language-hyperlink/branch/master) |
-| Java | [](https://travis-ci.org/atom/language-java) | [](https://ci.appveyor.com/project/Atom/language-java/branch/master) |
-| JavaScript | [](https://travis-ci.org/atom/language-javascript) | [](https://ci.appveyor.com/project/Atom/language-javascript-dijf8/branch/master) |
-| JSON | [](https://travis-ci.org/atom/language-json) | [](https://ci.appveyor.com/project/Atom/language-json/branch/master) |
-| Less | [](https://travis-ci.org/atom/language-less) | [](https://ci.appveyor.com/project/Atom/language-less/branch/master) |
-| Make | [](https://travis-ci.org/atom/language-make) | [](https://ci.appveyor.com/project/Atom/language-make/branch/master) |
-| Mustache | [](https://travis-ci.org/atom/language-mustache) | [](https://ci.appveyor.com/project/Atom/language-mustache/branch/master) |
-| Objective-C | [](https://travis-ci.org/atom/language-objective-c) | [](https://ci.appveyor.com/project/Atom/language-objective-c/branch/master) |
-| Perl | [](https://travis-ci.org/atom/language-perl) | [](https://ci.appveyor.com/project/Atom/language-perl/branch/master) |
-| PHP | [](https://travis-ci.org/atom/language-php) | [](https://ci.appveyor.com/project/Atom/language-php/branch/master) |
-| Python | [](https://travis-ci.org/atom/language-python) | [](https://ci.appveyor.com/project/Atom/language-python/branch/master) |
-| Ruby | [](https://travis-ci.org/atom/language-ruby) | [](https://ci.appveyor.com/project/Atom/language-ruby/branch/master) |
-| Ruby on Rails | [](https://travis-ci.org/atom/language-ruby-on-rails) | [](https://ci.appveyor.com/project/Atom/language-ruby-on-rails/branch/master) |
-| Sass | [](https://travis-ci.org/atom/language-sass) | [](https://ci.appveyor.com/project/Atom/language-sass/branch/master) |
-| ShellScript | [](https://travis-ci.org/atom/language-shellscript) | [](https://ci.appveyor.com/project/Atom/language-shellscript/branch/master) |
-| SQL | [](https://travis-ci.org/atom/language-sql) | [](https://ci.appveyor.com/project/Atom/language-sql/branch/master) |
-| TODO | [](https://travis-ci.org/atom/language-todo) | [](https://ci.appveyor.com/project/Atom/language-todo/branch/master) |
-| TOML | [](https://travis-ci.org/atom/language-toml) | [](https://ci.appveyor.com/project/Atom/language-toml/branch/master) |
-| XML | [](https://travis-ci.org/atom/language-xml) | [](https://ci.appveyor.com/project/Atom/language-xml/branch/master) |
-| YAML | [](https://travis-ci.org/atom/language-yaml) | [](https://ci.appveyor.com/project/Atom/language-yaml/branch/master) |
+| Language | Travis | AppVeyor/Win |
+|----------|--------|--------------|
+| [C/C++](https://github.com/atom/language-c) | [](https://travis-ci.org/atom/language-c) | [](https://ci.appveyor.com/project/Atom/language-c/branch/master) |
+| [C#](https://github.com/atom/language-csharp) | [](https://travis-ci.org/atom/language-csharp) | [](https://ci.appveyor.com/project/Atom/language-csharp/branch/master) |
+| [Clojure](https://github.com/atom/language-clojure) | [](https://travis-ci.org/atom/language-clojure) | [](https://ci.appveyor.com/project/Atom/language-clojure/branch/master) |
+| [CoffeeScript](https://github.com/atom/language-coffee-script) | [](https://travis-ci.org/atom/language-coffee-script) | [](https://ci.appveyor.com/project/Atom/language-coffee-script/branch/master) |
+| [CSS](https://github.com/atom/language-css) | [](https://travis-ci.org/atom/language-css) | [](https://ci.appveyor.com/project/Atom/language-css/branch/master) |
+| [Git](https://github.com/atom/language-git) | [](https://travis-ci.org/atom/language-git) | [](https://ci.appveyor.com/project/Atom/language-git/branch/master) |
+| [GitHub Flavored Markdown](https://github.com/atom/language-gfm) | [](https://travis-ci.org/atom/language-gfm) | [](https://ci.appveyor.com/project/Atom/language-gfm/branch/master) |
+| [Go](https://github.com/atom/language-go) | [](https://travis-ci.org/atom/language-go) | [](https://ci.appveyor.com/project/Atom/language-go/branch/master) |
+| [HTML](https://github.com/atom/language-html) | [](https://travis-ci.org/atom/language-html) | [](https://ci.appveyor.com/project/Atom/language-html/branch/master) |
+| [Hyperlink](https://github.com/atom/language-hyperlink) | [](https://travis-ci.org/atom/language-hyperlink) | [](https://ci.appveyor.com/project/Atom/language-hyperlink/branch/master) |
+| [Java](https://github.com/atom/language-java) | [](https://travis-ci.org/atom/language-java) | [](https://ci.appveyor.com/project/Atom/language-java/branch/master) |
+| [JavaScript](https://github.com/atom/language-javascript) | [](https://travis-ci.org/atom/language-javascript) | [](https://ci.appveyor.com/project/Atom/language-javascript-dijf8/branch/master) |
+| [JSON](https://github.com/atom/language-json) | [](https://travis-ci.org/atom/language-json) | [](https://ci.appveyor.com/project/Atom/language-json/branch/master) |
+| [Less](https://github.com/atom/language-less) | [](https://travis-ci.org/atom/language-less) | [](https://ci.appveyor.com/project/Atom/language-less/branch/master) |
+| [Make](https://github.com/atom/language-make) | [](https://travis-ci.org/atom/language-make) | [](https://ci.appveyor.com/project/Atom/language-make/branch/master) |
+| [Mustache](https://github.com/atom/language-mustache) | [](https://travis-ci.org/atom/language-mustache) | [](https://ci.appveyor.com/project/Atom/language-mustache/branch/master) |
+| [Objective-C](https://github.com/atom/language-objective-c) | [](https://travis-ci.org/atom/language-objective-c) | [](https://ci.appveyor.com/project/Atom/language-objective-c/branch/master) |
+| [Perl](https://github.com/atom/language-perl) | [](https://travis-ci.org/atom/language-perl) | [](https://ci.appveyor.com/project/Atom/language-perl/branch/master) |
+| [PHP](https://github.com/atom/language-php) | [](https://travis-ci.org/atom/language-php) | [](https://ci.appveyor.com/project/Atom/language-php/branch/master) |
+| [Python](https://github.com/atom/language-python) | [](https://travis-ci.org/atom/language-python) | [](https://ci.appveyor.com/project/Atom/language-python/branch/master) |
+| [Ruby](https://github.com/atom/language-ruby) | [](https://travis-ci.org/atom/language-ruby) | [](https://ci.appveyor.com/project/Atom/language-ruby/branch/master) |
+| [Ruby on Rails](https://github.com/atom/language-ruby-on-rails) | [](https://travis-ci.org/atom/language-ruby-on-rails) | [](https://ci.appveyor.com/project/Atom/language-ruby-on-rails/branch/master) |
+| [Sass](https://github.com/atom/language-sass) | [](https://travis-ci.org/atom/language-sass) | [](https://ci.appveyor.com/project/Atom/language-sass/branch/master) |
+| [ShellScript](https://github.com/atom/language-shellscript) | [](https://travis-ci.org/atom/language-shellscript) | [](https://ci.appveyor.com/project/Atom/language-shellscript/branch/master) |
+| [SQL](https://github.com/atom/language-sql) | [](https://travis-ci.org/atom/language-sql) | [](https://ci.appveyor.com/project/Atom/language-sql/branch/master) |
+| [TODO](https://github.com/atom/language-todo) | [](https://travis-ci.org/atom/language-todo) | [](https://ci.appveyor.com/project/Atom/language-todo/branch/master) |
+| [TOML](https://github.com/atom/language-toml) | [](https://travis-ci.org/atom/language-toml) | [](https://ci.appveyor.com/project/Atom/language-toml/branch/master) |
+| [XML](https://github.com/atom/language-xml) | [](https://travis-ci.org/atom/language-xml) | [](https://ci.appveyor.com/project/Atom/language-xml/branch/master) |
+| [YAML](https://github/atom/language-yaml) | [](https://travis-ci.org/atom/language-yaml) | [](https://ci.appveyor.com/project/Atom/language-yaml/branch/master) |
diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md
index a05bbff16..1caf740ba 100644
--- a/docs/build-instructions/linux.md
+++ b/docs/build-instructions/linux.md
@@ -7,7 +7,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++11 toolchain
* Git
-* Node.js 4.4.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
+* Node.js 6.x (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 [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring).
@@ -49,10 +49,14 @@ To also install the newly built application, use `--create-debian-package` or `-
sudo update-alternatives --config gcc # choose gcc-5 from the list
```
-### Fedora / CentOS / RHEL
+### Fedora 22+
* `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools libX11-devel libxkbfile-devel`
+### Fedora 21 / CentOS / RHEL
+
+* `sudo yum install -y make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools`
+
### Arch
* `sudo pacman -S --needed gconf base-devel git nodejs npm libgnome-keyring python2 libX11-devel libxkbfile-devel`
diff --git a/docs/build-instructions/macos.md b/docs/build-instructions/macOS.md
similarity index 82%
rename from docs/build-instructions/macos.md
rename to docs/build-instructions/macOS.md
index f03d0e385..0d9335eea 100644
--- a/docs/build-instructions/macos.md
+++ b/docs/build-instructions/macOS.md
@@ -3,7 +3,7 @@
## Requirements
* macOS 10.8 or later
- * Node.js 4.4.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
+ * Node.js 6.x (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)
@@ -26,4 +26,4 @@ To also install the newly built application, use `script/build --install`.
## Troubleshooting
### macOS build error reports in atom/atom
-* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Aos-x&type=Issues) to get a list of reports about build errors on macOS.
+* 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.
diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md
index 68625b217..2c231b2dc 100644
--- a/docs/build-instructions/windows.md
+++ b/docs/build-instructions/windows.md
@@ -2,9 +2,10 @@
## Requirements
-* Node.js 4.4.x or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)
+* Node.js 6.x (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)
diff --git a/keymaps/base.cson b/keymaps/base.cson
index d9941643c..b421392ab 100644
--- a/keymaps/base.cson
+++ b/keymaps/base.cson
@@ -7,13 +7,13 @@
'atom-text-editor:not([mini])':
# Atom Specific
- 'ctrl-C': 'editor:copy-path'
+ 'ctrl-shift-c': 'editor:copy-path'
# Sublime Parity
'tab': 'editor:indent'
'enter': 'editor:newline'
'shift-tab': 'editor:outdent-selected-rows'
- 'ctrl-K': 'editor:delete-line'
+ 'ctrl-shift-k': 'editor:delete-line'
'.select-list atom-text-editor[mini]':
'enter': 'core:confirm'
@@ -24,7 +24,7 @@
'atom-text-editor !important, atom-text-editor[mini] !important':
'escape': 'editor:consolidate-selections'
-# allow standard input fields to work correctly
+# Allow standard input fields to work correctly
'body .native-key-bindings':
'tab': 'core:focus-next'
'shift-tab': 'core:focus-previous'
@@ -66,7 +66,7 @@
'ctrl-shift-right': 'native!'
'ctrl-b': 'native!'
'ctrl-f': 'native!'
- 'ctrl-F': 'native!'
- 'ctrl-B': 'native!'
+ 'ctrl-shift-f': 'native!'
+ 'ctrl-shift-b': 'native!'
'ctrl-h': 'native!'
'ctrl-d': 'native!'
diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson
index 4ae6d88db..fa942d97c 100644
--- a/keymaps/darwin.cson
+++ b/keymaps/darwin.cson
@@ -10,10 +10,10 @@
'ctrl-n': 'core:move-down'
'ctrl-b': 'core:move-left'
'ctrl-f': 'core:move-right'
- 'ctrl-P': 'core:select-up'
- 'ctrl-N': 'core:select-down'
- 'ctrl-F': 'core:select-right'
- 'ctrl-B': 'core:select-left'
+ 'ctrl-shift-p': 'core:select-up'
+ 'ctrl-shift-n': 'core:select-down'
+ 'ctrl-shift-f': 'core:select-right'
+ 'ctrl-shift-b': 'core:select-left'
'ctrl-h': 'core:backspace'
'ctrl-d': 'core:delete'
@@ -34,19 +34,19 @@
# Sublime Parity
'cmd-,': 'application:show-settings'
- 'cmd-N': 'application:new-window'
- 'cmd-W': 'window:close'
+ 'cmd-shift-n': 'application:new-window'
+ 'cmd-shift-w': 'window:close'
'cmd-o': 'application:open'
- 'cmd-O': 'application:add-project-folder'
- 'cmd-T': 'pane:reopen-closed-item'
+ 'cmd-shift-o': 'application:add-project-folder'
+ 'cmd-shift-t': 'pane:reopen-closed-item'
'cmd-n': 'application:new-file'
'cmd-s': 'core:save'
- 'cmd-S': 'core:save-as'
+ 'cmd-shift-s': 'core:save-as'
'cmd-alt-s': 'window:save-all'
'cmd-w': 'core:close'
'cmd-ctrl-f': 'window:toggle-full-screen'
'cmd-z': 'core:undo'
- 'cmd-Z': 'core:redo'
+ 'cmd-shift-z': 'core:redo'
'cmd-y': 'core:redo'
'cmd-x': 'core:cut'
'cmd-c': 'core:copy'
@@ -116,8 +116,8 @@
'cmd-backspace': 'editor:delete-to-beginning-of-line'
'cmd-shift-backspace': 'editor:delete-to-beginning-of-line'
'cmd-delete': 'editor:delete-to-end-of-line'
- 'ctrl-A': 'editor:select-to-first-character-of-line'
- 'ctrl-E': 'editor:select-to-end-of-line'
+ 'ctrl-shift-a': 'editor:select-to-first-character-of-line'
+ 'ctrl-shift-e': 'editor:select-to-end-of-line'
'cmd-left': 'editor:move-to-first-character-of-line'
'cmd-right': 'editor:move-to-end-of-screen-line'
'cmd-shift-left': 'editor:select-to-first-character-of-line'
@@ -129,19 +129,19 @@
'ctrl-k': 'editor:cut-to-end-of-line'
# Atom Specific
- 'ctrl-W': 'editor:select-word'
+ 'ctrl-shift-w': 'editor:select-word'
'cmd-ctrl-left': 'editor:move-selection-left'
'cmd-ctrl-right': 'editor:move-selection-right'
# Emacs
'alt-f': 'editor:move-to-end-of-word'
'alt-ctrl-f': 'editor:move-to-next-subword-boundary'
- 'alt-F': 'editor:select-to-end-of-word'
- 'alt-ctrl-F': 'editor:select-to-next-subword-boundary'
+ 'alt-shift-f': 'editor:select-to-end-of-word'
+ 'alt-ctrl-shift-f': 'editor:select-to-next-subword-boundary'
'alt-b': 'editor:move-to-beginning-of-word'
'alt-ctrl-b': 'editor:move-to-previous-subword-boundary'
- 'alt-B': 'editor:select-to-beginning-of-word'
- 'alt-ctrl-B': 'editor:select-to-previous-subword-boundary'
+ 'alt-shift-b': 'editor:select-to-beginning-of-word'
+ 'alt-ctrl-shift-b': 'editor:select-to-previous-subword-boundary'
'alt-h': 'editor:delete-to-beginning-of-word'
'alt-ctrl-h': 'editor:delete-to-beginning-of-subword'
'alt-d': 'editor:delete-to-end-of-word'
@@ -178,8 +178,8 @@
'ctrl-cmd-down': 'editor:move-line-down'
'cmd-/': 'editor:toggle-line-comments'
'cmd-j': 'editor:join-lines'
- 'cmd-D': 'editor:duplicate-lines'
- 'cmd-L': 'editor:split-selections-into-lines'
+ 'cmd-shift-d': 'editor:duplicate-lines'
+ 'cmd-shift-l': 'editor:split-selections-into-lines'
'ctrl-shift-up': 'editor:add-selection-above'
'ctrl-shift-down': 'editor:add-selection-below'
@@ -202,10 +202,10 @@
'cmd-alt-=': 'pane:increase-size'
'cmd-alt--': 'pane:decrease-size'
-# allow standard input fields to work correctly
+# Allow standard input fields to work correctly
'body .native-key-bindings':
'cmd-z': 'native!'
- 'cmd-Z': 'native!'
+ 'cmd-shift-z': 'native!'
'cmd-x': 'native!'
'cmd-c': 'native!'
'cmd-v': 'native!'
diff --git a/keymaps/linux.cson b/keymaps/linux.cson
index 1f78739a9..d6ded1f90 100644
--- a/keymaps/linux.cson
+++ b/keymaps/linux.cson
@@ -6,11 +6,11 @@
'down': 'core:move-down'
'left': 'core:move-left'
'right': 'core:move-right'
- 'ctrl-alt-r': 'window:reload'
+ 'ctrl-shift-f5': 'window:reload'
'ctrl-shift-i': 'window:toggle-dev-tools'
- 'ctrl-alt-p': 'window:run-package-specs'
+ 'ctrl-shift-y': 'window:run-package-specs'
'ctrl-shift-o': 'application:open-folder'
- 'ctrl-alt-o': 'application:add-project-folder'
+ 'ctrl-shift-a': 'application:add-project-folder'
'ctrl-shift-pageup': 'pane:move-item-left'
'ctrl-shift-pagedown': 'pane:move-item-right'
'f11': 'window:toggle-full-screen'
@@ -19,14 +19,14 @@
# Sublime Parity
'ctrl-,': 'application:show-settings'
- 'ctrl-N': 'application:new-window'
- 'ctrl-W': 'window:close'
+ 'ctrl-shift-n': 'application:new-window'
+ 'ctrl-shift-w': 'window:close'
'ctrl-o': 'application:open-file'
'ctrl-q': 'application:quit'
- 'ctrl-T': 'pane:reopen-closed-item'
+ 'ctrl-shift-t': 'pane:reopen-closed-item'
'ctrl-n': 'application:new-file'
'ctrl-s': 'core:save'
- 'ctrl-S': 'core:save-as'
+ 'ctrl-shift-s': 'core:save-as'
'ctrl-f4': 'core:close'
'ctrl-w': 'core:close'
'ctrl-z': 'core:undo'
@@ -70,12 +70,12 @@
'ctrl-k left': 'pane:split-left-and-copy-active-item' # Atom Specific
'ctrl-k right': 'pane:split-right-and-copy-active-item' # Atom Specific
'ctrl-k ctrl-w': 'pane:close' # Atom Specific
- 'ctrl-k alt-ctrl-w': 'pane:close-other-items' # Atom Specific
+ 'ctrl-k ctrl-alt-w': 'pane:close-other-items' # Atom Specific
'ctrl-k ctrl-p': 'window:focus-previous-pane'
'ctrl-k ctrl-n': 'window:focus-next-pane'
- 'ctrl-k ctrl-up': 'window:focus-pane-above'
- 'ctrl-k ctrl-down': 'window:focus-pane-below'
- 'ctrl-k ctrl-left': 'window:focus-pane-on-left'
+ 'ctrl-k ctrl-up': 'window:focus-pane-above'
+ 'ctrl-k ctrl-down': 'window:focus-pane-below'
+ 'ctrl-k ctrl-left': 'window:focus-pane-on-left'
'ctrl-k ctrl-right': 'window:focus-pane-on-right'
'alt-1': 'pane:show-item-1'
'alt-2': 'pane:show-item-2'
@@ -108,16 +108,14 @@
# Sublime Parity
'ctrl-a': 'core:select-all'
- 'ctrl-alt-shift-p': 'editor:log-cursor-scope'
'ctrl-k ctrl-u': 'editor:upper-case'
'ctrl-k ctrl-l': 'editor:lower-case'
'ctrl-l': 'editor:select-line'
'atom-workspace atom-text-editor:not([mini])':
# Atom specific
- 'alt-ctrl-z': 'editor:checkout-head-revision'
'ctrl-<': 'editor:scroll-to-cursor'
- 'alt-ctrl-f': 'editor:fold-selection'
+ 'ctrl-alt-shift-[': 'editor:fold-selection'
# Sublime Parity
'ctrl-enter': 'editor:newline-below'
@@ -128,7 +126,7 @@
'ctrl-down': 'editor:move-line-down'
'ctrl-/': 'editor:toggle-line-comments'
'ctrl-j': 'editor:join-lines'
- 'ctrl-D': 'editor:duplicate-lines'
+ 'ctrl-shift-d': 'editor:duplicate-lines'
'alt-shift-up': 'editor:add-selection-above'
'alt-shift-down': 'editor:add-selection-below'
@@ -151,10 +149,10 @@
'ctrl-alt-=': 'pane:increase-size'
'ctrl-alt--': 'pane:decrease-size'
-# allow standard input fields to work correctly
+# Allow standard input fields to work correctly
'body .native-key-bindings':
'ctrl-z': 'native!'
- 'ctrl-Z': 'native!'
+ 'ctrl-shift-z': 'native!'
'ctrl-x': 'native!'
'ctrl-c': 'native!'
'ctrl-v': 'native!'
diff --git a/keymaps/win32.cson b/keymaps/win32.cson
index d43c124d4..14f5a4283 100644
--- a/keymaps/win32.cson
+++ b/keymaps/win32.cson
@@ -12,11 +12,11 @@
'ctrl-down': 'core:move-down'
'left': 'core:move-left'
'right': 'core:move-right'
- 'ctrl-alt-r': 'window:reload'
+ 'ctrl-shift-f5': 'window:reload'
'ctrl-shift-i': 'window:toggle-dev-tools'
- 'ctrl-alt-p': 'window:run-package-specs'
+ 'ctrl-shift-y': 'window:run-package-specs'
'ctrl-shift-o': 'application:open-folder'
- 'ctrl-alt-o': 'application:add-project-folder'
+ 'ctrl-shift-a': 'application:add-project-folder'
'ctrl-shift-left': 'pane:move-item-left'
'ctrl-shift-right': 'pane:move-item-right'
'f11': 'window:toggle-full-screen'
@@ -25,13 +25,13 @@
# Sublime Parity
'ctrl-,': 'application:show-settings'
- 'ctrl-N': 'application:new-window'
- 'ctrl-W': 'window:close'
+ 'ctrl-shift-n': 'application:new-window'
+ 'ctrl-shift-w': 'window:close'
'ctrl-o': 'application:open-file'
- 'ctrl-T': 'pane:reopen-closed-item'
+ 'ctrl-shift-t': 'pane:reopen-closed-item'
'ctrl-n': 'application:new-file'
'ctrl-s': 'core:save'
- 'ctrl-S': 'core:save-as'
+ 'ctrl-shift-s': 'core:save-as'
'ctrl-f4': 'core:close'
'ctrl-w': 'core:close'
'ctrl-z': 'core:undo'
@@ -75,12 +75,12 @@
'ctrl-k left': 'pane:split-left-and-copy-active-item' # Atom Specific
'ctrl-k right': 'pane:split-right-and-copy-active-item' # Atom Specific
'ctrl-k ctrl-w': 'pane:close' # Atom Specific
- 'ctrl-k alt-ctrl-w': 'pane:close-other-items' # Atom Specific
+ 'ctrl-k ctrl-alt-w': 'pane:close-other-items' # Atom Specific
'ctrl-k ctrl-p': 'window:focus-previous-pane'
'ctrl-k ctrl-n': 'window:focus-next-pane'
- 'ctrl-k ctrl-up': 'window:focus-pane-above'
- 'ctrl-k ctrl-down': 'window:focus-pane-below'
- 'ctrl-k ctrl-left': 'window:focus-pane-on-left'
+ 'ctrl-k ctrl-up': 'window:focus-pane-above'
+ 'ctrl-k ctrl-down': 'window:focus-pane-below'
+ 'ctrl-k ctrl-left': 'window:focus-pane-on-left'
'ctrl-k ctrl-right': 'window:focus-pane-on-right'
'alt-1': 'pane:show-item-1'
'alt-2': 'pane:show-item-2'
@@ -113,16 +113,14 @@
# Sublime Parity
'ctrl-a': 'core:select-all'
- 'ctrl-alt-shift-p': 'editor:log-cursor-scope'
'ctrl-k ctrl-u': 'editor:upper-case'
'ctrl-k ctrl-l': 'editor:lower-case'
'ctrl-l': 'editor:select-line'
'atom-workspace atom-text-editor:not([mini])':
# Atom specific
- 'alt-ctrl-z': 'editor:checkout-head-revision'
'ctrl-<': 'editor:scroll-to-cursor'
- 'alt-ctrl-f': 'editor:fold-selection'
+ 'ctrl-alt-shift-[': 'editor:fold-selection'
# Sublime Parity
'ctrl-enter': 'editor:newline-below'
@@ -133,7 +131,7 @@
'ctrl-down': 'editor:move-line-down'
'ctrl-/': 'editor:toggle-line-comments'
'ctrl-j': 'editor:join-lines'
- 'ctrl-D': 'editor:duplicate-lines'
+ 'ctrl-shift-d': 'editor:duplicate-lines'
'ctrl-alt-[': 'editor:fold-current-row'
'ctrl-alt-]': 'editor:unfold-current-row'
@@ -154,10 +152,10 @@
'ctrl-alt-=': 'pane:increase-size'
'ctrl-alt--': 'pane:decrease-size'
-# allow standard input fields to work correctly
+# Allow standard input fields to work correctly
'body .native-key-bindings':
'ctrl-z': 'native!'
- 'ctrl-Z': 'native!'
+ 'ctrl-shift-z': 'native!'
'ctrl-x': 'native!'
'ctrl-c': 'native!'
'ctrl-v': 'native!'
diff --git a/menus/darwin.cson b/menus/darwin.cson
index f16bfa981..055cd2405 100644
--- a/menus/darwin.cson
+++ b/menus/darwin.cson
@@ -108,9 +108,9 @@
submenu: [
{ label: 'Fold', command: 'editor:fold-current-row' }
{ label: 'Unfold', command: 'editor:unfold-current-row' }
+ { label: 'Fold All', command: 'editor:fold-all' }
{ label: 'Unfold All', command: 'editor:unfold-all' }
{ type: 'separator' }
- { label: 'Fold All', command: 'editor:fold-all' }
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
diff --git a/menus/linux.cson b/menus/linux.cson
index c900d3d29..94fb90a30 100644
--- a/menus/linux.cson
+++ b/menus/linux.cson
@@ -81,9 +81,9 @@
submenu: [
{ label: '&Fold', command: 'editor:fold-current-row' }
{ label: '&Unfold', command: 'editor:unfold-current-row' }
+ { label: 'Fol&d All', command: 'editor:fold-all' }
{ label: 'Unfold &All', command: 'editor:unfold-all' }
{ type: 'separator' }
- { label: 'Fol&d All', command: 'editor:fold-all' }
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
diff --git a/menus/win32.cson b/menus/win32.cson
index 7897709b7..70bb1487d 100644
--- a/menus/win32.cson
+++ b/menus/win32.cson
@@ -89,9 +89,9 @@
submenu: [
{ label: '&Fold', command: 'editor:fold-current-row' }
{ label: '&Unfold', command: 'editor:unfold-current-row' }
+ { label: 'Fol&d All', command: 'editor:fold-all' }
{ label: 'Unfold &All', command: 'editor:unfold-all' }
{ type: 'separator' }
- { label: 'Fol&d All', command: 'editor:fold-all' }
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
diff --git a/package.json b/package.json
index 2b83d2fb8..92a9920e3 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
- "version": "1.14.0-dev",
+ "version": "1.16.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/main-process/main.js",
"repository": {
@@ -12,13 +12,24 @@
"url": "https://github.com/atom/atom/issues"
},
"license": "MIT",
- "electronVersion": "1.3.6",
+ "electronVersion": "1.3.13",
"dependencies": {
"async": "0.2.6",
- "atom-keymap": "7.1.3",
- "atom-space-pen-views": "^2.0.0",
+ "atom-keymap": "7.1.20",
+ "atom-select-list": "0.0.12",
"atom-ui": "0.4.1",
- "babel-core": "5.8.38",
+ "babel-core": "6.22.1",
+ "babel-plugin-add-module-exports": "0.2.1",
+ "babel-plugin-transform-async-to-generator": "6.22.0",
+ "babel-plugin-transform-class-properties": "6.23.0",
+ "babel-plugin-transform-decorators-legacy": "1.3.4",
+ "babel-plugin-transform-do-expressions": "6.22.0",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.23.0",
+ "babel-plugin-transform-export-extensions": "6.22.0",
+ "babel-plugin-transform-flow-strip-types": "6.22.0",
+ "babel-plugin-transform-function-bind": "6.22.0",
+ "babel-plugin-transform-object-rest-spread": "6.23.0",
+ "babel-plugin-transform-react-jsx": "6.23.0",
"cached-run-in-this-context": "0.4.1",
"chai": "3.5.0",
"chart.js": "^2.3.0",
@@ -33,7 +44,7 @@
"fs-plus": "2.9.2",
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
- "git-utils": "^4.1.2",
+ "git-utils": "4.1.2",
"glob": "^7.1.1",
"grim": "1.5.0",
"jasmine-json": "~0.0",
@@ -49,14 +60,14 @@
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"oniguruma": "6.1.0",
- "pathwatcher": "~6.5",
+ "pathwatcher": "6.8.0",
"postcss": "5.2.4",
"postcss-selector-parser": "2.2.1",
"property-accessors": "^1.1.3",
"random-words": "0.0.1",
"resolve": "^1.1.6",
"runas": "^3.1",
- "scandal": "^2.2.1",
+ "scandal": "2.2.2",
"scoped-property-store": "^0.17.0",
"scrollbar-style": "^3.2",
"season": "^5.4.1",
@@ -65,7 +76,7 @@
"sinon": "1.17.4",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
- "text-buffer": "9.4.3",
+ "text-buffer": "10.3.11",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
@@ -76,91 +87,91 @@
"atom-dark-ui": "0.53.0",
"atom-light-syntax": "0.29.0",
"atom-light-ui": "0.46.0",
- "base16-tomorrow-dark-theme": "1.4.0",
- "base16-tomorrow-light-theme": "1.4.0",
- "one-dark-ui": "1.8.2",
- "one-light-ui": "1.8.2",
- "one-dark-syntax": "1.6.0",
- "one-light-syntax": "1.6.0",
- "solarized-dark-syntax": "1.1.1",
- "solarized-light-syntax": "1.1.1",
+ "base16-tomorrow-dark-theme": "1.5.0",
+ "base16-tomorrow-light-theme": "1.5.0",
+ "one-dark-ui": "1.9.1",
+ "one-light-ui": "1.9.1",
+ "one-dark-syntax": "1.7.1",
+ "one-light-syntax": "1.7.1",
+ "solarized-dark-syntax": "1.1.2",
+ "solarized-light-syntax": "1.1.2",
"about": "1.7.2",
- "archive-view": "0.62.0",
+ "archive-view": "0.62.2",
"autocomplete-atom-api": "0.10.0",
- "autocomplete-css": "0.14.1",
+ "autocomplete-css": "0.15.0",
"autocomplete-html": "0.7.2",
- "autocomplete-plus": "2.33.1",
+ "autocomplete-plus": "2.34.2",
"autocomplete-snippets": "1.11.0",
- "autoflow": "0.27.0",
- "autosave": "0.23.2",
+ "autoflow": "0.29.0",
+ "autosave": "0.24.0",
"background-tips": "0.26.1",
- "bookmarks": "0.43.2",
- "bracket-matcher": "0.82.2",
- "command-palette": "0.39.1",
- "deprecation-cop": "0.55.1",
+ "bookmarks": "0.44.1",
+ "bracket-matcher": "0.85.2",
+ "command-palette": "0.40.2",
+ "deprecation-cop": "0.56.2",
"dev-live-reload": "0.47.0",
- "encoding-selector": "0.22.0",
- "exception-reporting": "0.40.0",
- "find-and-replace": "0.204.2",
- "fuzzy-finder": "1.4.0",
- "git-diff": "1.2.0",
- "go-to-line": "0.31.2",
- "grammar-selector": "0.48.2",
- "image-view": "0.60.0",
+ "encoding-selector": "0.23.1",
+ "exception-reporting": "0.41.1",
+ "find-and-replace": "0.206.3",
+ "fuzzy-finder": "1.4.1",
+ "git-diff": "1.3.1",
+ "go-to-line": "0.32.0",
+ "grammar-selector": "0.49.2",
+ "image-view": "0.61.0",
"incompatible-packages": "0.26.1",
- "keybinding-resolver": "0.35.0",
- "line-ending-selector": "0.5.1",
+ "keybinding-resolver": "0.36.1",
+ "line-ending-selector": "0.6.1",
"link": "0.31.2",
- "markdown-preview": "0.159.1",
- "metrics": "1.0.0",
- "notifications": "0.65.1",
+ "markdown-preview": "0.159.7",
+ "metrics": "1.2.0",
+ "notifications": "0.66.2",
"open-on-github": "1.2.1",
- "package-generator": "1.0.2",
- "settings-view": "0.244.0",
- "snippets": "1.0.4",
- "spell-check": "0.68.5",
- "status-bar": "1.6.0",
- "styleguide": "0.48.0",
- "symbols-view": "0.113.1",
- "tabs": "0.103.1",
- "timecop": "0.33.2",
- "tree-view": "0.211.1",
+ "package-generator": "1.1.0",
+ "settings-view": "0.247.0",
+ "snippets": "1.0.5",
+ "spell-check": "0.71.0",
+ "status-bar": "1.8.1",
+ "styleguide": "0.49.2",
+ "symbols-view": "0.114.0",
+ "tabs": "0.104.1",
+ "timecop": "0.35.0",
+ "tree-view": "0.214.1",
"update-package-dependencies": "0.10.0",
- "welcome": "0.35.1",
- "whitespace": "0.35.0",
- "wrap-guide": "0.39.0",
- "language-c": "0.54.0",
- "language-clojure": "0.22.1",
- "language-coffee-script": "0.48.1",
- "language-csharp": "0.13.0",
- "language-css": "0.40.1",
+ "welcome": "0.36.1",
+ "whitespace": "0.36.2",
+ "wrap-guide": "0.39.1",
+ "language-c": "0.56.0",
+ "language-clojure": "0.22.2",
+ "language-coffee-script": "0.48.4",
+ "language-csharp": "0.14.1",
+ "language-css": "0.42.0",
"language-gfm": "0.88.0",
- "language-git": "0.15.0",
- "language-go": "0.43.0",
- "language-html": "0.46.1",
+ "language-git": "0.19.0",
+ "language-go": "0.43.1",
+ "language-html": "0.47.2",
"language-hyperlink": "0.16.1",
- "language-java": "0.24.0",
- "language-javascript": "0.122.0",
+ "language-java": "0.26.0",
+ "language-javascript": "0.126.0",
"language-json": "0.18.3",
- "language-less": "0.29.6",
- "language-make": "0.22.2",
- "language-mustache": "0.13.0",
+ "language-less": "0.30.1",
+ "language-make": "0.22.3",
+ "language-mustache": "0.13.1",
"language-objective-c": "0.15.1",
"language-perl": "0.37.0",
- "language-php": "0.37.3",
- "language-property-list": "0.8.0",
- "language-python": "0.45.1",
- "language-ruby": "0.70.2",
- "language-ruby-on-rails": "0.25.1",
- "language-sass": "0.57.0",
- "language-shellscript": "0.23.0",
+ "language-php": "0.37.4",
+ "language-property-list": "0.9.0",
+ "language-python": "0.45.2",
+ "language-ruby": "0.70.5",
+ "language-ruby-on-rails": "0.25.2",
+ "language-sass": "0.57.1",
+ "language-shellscript": "0.25.0",
"language-source": "0.9.0",
- "language-sql": "0.25.0",
+ "language-sql": "0.25.3",
"language-text": "0.7.1",
"language-todo": "0.29.1",
"language-toml": "0.18.1",
- "language-xml": "0.34.12",
- "language-yaml": "0.27.1"
+ "language-xml": "0.34.16",
+ "language-yaml": "0.28.0"
},
"private": true,
"scripts": {
diff --git a/resources/linux/redhat/atom.spec.in b/resources/linux/redhat/atom.spec.in
index 0ee120b35..82a5fbf9a 100644
--- a/resources/linux/redhat/atom.spec.in
+++ b/resources/linux/redhat/atom.spec.in
@@ -7,7 +7,7 @@ URL: https://atom.io/
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
Prefix: <%= installDir %>
-Requires: lsb-core-noarch
+Requires: lsb-core-noarch, libXss.so.1()(64bit)
%description
<%= description %>
diff --git a/resources/win/apm.cmd b/resources/win/apm.cmd
index 510168983..371172c43 100644
--- a/resources/win/apm.cmd
+++ b/resources/win/apm.cmd
@@ -1,3 +1,3 @@
@echo off
-"%~dp0\..\app\apm\bin\node.exe" "%~dp0\..\app\apm\lib\cli.js" %*
+"%~dp0\..\app\apm\bin\apm.cmd" %*
diff --git a/resources/win/apm.sh b/resources/win/apm.sh
index b50a70a82..99ccfec69 100644
--- a/resources/win/apm.sh
+++ b/resources/win/apm.sh
@@ -1,4 +1,3 @@
#!/bin/sh
-directory=$(dirname "$0")
-"$directory/../app/apm/bin/node.exe" "$directory/../app/apm/lib/cli.js" "$@"
+"$(dirname "$0")/../app/apm/apm.sh" "$@"
diff --git a/resources/win/folder.ico b/resources/win/folder.ico
new file mode 100644
index 000000000..ad1d9fc95
Binary files /dev/null and b/resources/win/folder.ico differ
diff --git a/script/build b/script/build
index b4bfee6f9..cfcbc4a93 100755
--- a/script/build
+++ b/script/build
@@ -23,6 +23,7 @@ const argv = yargs
.wrap(yargs.terminalWidth())
.argv
+const checkChromedriverVersion = require('./lib/check-chromedriver-version')
const cleanOutputDirectory = require('./lib/clean-output-directory')
const codeSignOnMac = require('./lib/code-sign-on-mac')
const compressArtifacts = require('./lib/compress-artifacts')
@@ -30,7 +31,6 @@ const copyAssets = require('./lib/copy-assets')
const createDebianPackage = require('./lib/create-debian-package')
const createRpmPackage = require('./lib/create-rpm-package')
const createWindowsInstaller = require('./lib/create-windows-installer')
-const downloadChromedriver = require('./lib/download-chromedriver')
const dumpSymbols = require('./lib/dump-symbols')
const generateAPIDocs = require('./lib/generate-api-docs')
const generateMetadata = require('./lib/generate-metadata')
@@ -49,6 +49,7 @@ process.on('unhandledRejection', function (e) {
process.exit(1)
})
+checkChromedriverVersion()
cleanOutputDirectory()
downloadChromedriver()
copyAssets()
diff --git a/script/deprecated-packages.json b/script/deprecated-packages.json
index 08f4d1186..12638967e 100644
--- a/script/deprecated-packages.json
+++ b/script/deprecated-packages.json
@@ -866,6 +866,10 @@
"hasDeprecations": true,
"latestHasDeprecations": true
},
+ "language-nlf": {
+ "hasAlternative": true,
+ "alternative": "language-nsis"
+ },
"language-rspec": {
"version": "<=0.2.1",
"hasDeprecations": true,
diff --git a/script/lib/check-chromedriver-version.js b/script/lib/check-chromedriver-version.js
new file mode 100644
index 000000000..90bc220e5
--- /dev/null
+++ b/script/lib/check-chromedriver-version.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const buildMetadata = require('../package.json')
+const CONFIG = require('../config')
+const semver = require('semver')
+
+module.exports = function () {
+ // Chromedriver should be specified as ~x.y where x and y match Electron major/minor
+ const chromedriverVer = buildMetadata.dependencies['electron-chromedriver']
+
+ // Always use tilde on electron-chromedriver so that it can pick up the best patch vesion
+ if (!chromedriverVer.startsWith('~')) {
+ throw new Error(`electron-chromedriver version in script/package.json should start with a tilde to match latest patch version.`)
+ }
+
+ const electronVer = CONFIG.appMetadata.electronVersion
+ if (!semver.satisfies(electronVer, chromedriverVer)) {
+ throw new Error(`electron-chromedriver ${chromedriverVer} incompatible with electron ${electronVer}.\n` +
+ 'Did you upgrade electron in package.json and forget to upgrade electron-chromedriver in ' +
+ `script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`)
+ }
+}
diff --git a/script/lib/code-sign-on-mac.js b/script/lib/code-sign-on-mac.js
index 80d316566..5e4c67707 100644
--- a/script/lib/code-sign-on-mac.js
+++ b/script/lib/code-sign-on-mac.js
@@ -5,15 +5,17 @@ const path = require('path')
const spawnSync = require('./spawn-sync')
module.exports = function (packagedAppPath) {
- if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL) {
+ if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
console.log('Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray)
return
}
- try {
- const certPath = path.join(os.tmpdir(), 'mac.p12')
+ let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH;
+ if (!certPath) {
+ certPath = path.join(os.tmpdir(), 'mac.p12')
downloadFileFromGithub(process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
-
+ }
+ try {
console.log(`Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`)
const unlockArgs = ['unlock-keychain']
// For signing on local workstations, password could be entered interactively
@@ -31,6 +33,18 @@ module.exports = function (packagedAppPath) {
'-T', '/usr/bin/codesign'
])
+
+ console.log('Running incantation to suppress dialog when signing on macOS Sierra')
+ try {
+ spawnSync('security', [
+ 'set-key-partition-list', '-S', 'apple-tool:,apple:', '-s',
+ '-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
+ process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
+ ])
+ } catch (e) {
+ console.log('Incantation failed... maybe this isn\'t Sierra?');
+ }
+
console.log(`Code-signing application at ${packagedAppPath}`)
spawnSync('codesign', [
'--deep', '--force', '--verbose',
@@ -38,7 +52,9 @@ module.exports = function (packagedAppPath) {
'--sign', 'Developer ID Application: GitHub', packagedAppPath
], {stdio: 'inherit'})
} finally {
- console.log(`Deleting certificate at ${certPath}`)
- fs.removeSync(certPath)
+ if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
+ console.log(`Deleting certificate at ${certPath}`)
+ fs.removeSync(certPath)
+ }
}
}
diff --git a/script/lib/create-windows-installer.js b/script/lib/create-windows-installer.js
index 7de22833b..8a0dc0f61 100644
--- a/script/lib/create-windows-installer.js
+++ b/script/lib/create-windows-installer.js
@@ -18,33 +18,32 @@ module.exports = function (packagedAppPath, codeSign) {
iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${CONFIG.channel}/atom.ico`,
loadingGif: path.join(CONFIG.repositoryRootPath, 'resources', 'win', 'loading.gif'),
outputDirectory: CONFIG.buildOutputPath,
- remoteReleases: `https://atom.io/api/updates${archSuffix}`,
+ remoteReleases: `https://atom.io/api/updates${archSuffix}?version=${CONFIG.appMetadata.version}`,
setupIcon: path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom.ico')
}
- // Remove this once an x64 version is published or atom.io is returning blank instead of 404 for RELEASES-X64
- if (process.arch === 'x64') {
- options.remoteReleases = null
- }
-
- const certPath = path.join(os.tmpdir(), 'win.p12')
- const signing = codeSign && process.env.WIN_P12KEY_URL
+ const signing = codeSign && (process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL || process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH)
+ let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH;
if (signing) {
- downloadFileFromGithub(process.env.WIN_P12KEY_URL, certPath)
+ if (!certPath) {
+ certPath = path.join(os.tmpdir(), 'win.p12')
+ downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
+ }
+
var signParams = []
signParams.push(`/f ${certPath}`) // Signing cert file
- signParams.push(`/p ${process.env.WIN_P12KEY_PASSWORD}`) // Signing cert password
+ signParams.push(`/p ${process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD}`) // Signing cert password
signParams.push('/fd sha256') // File digest algorithm
signParams.push('/tr http://timestamp.digicert.com') // Time stamp server
signParams.push('/td sha256') // Times stamp algorithm
options.signWithParams = signParams.join(' ')
} else {
- console.log('Skipping code-signing. Specify the --code-sign option and provide a WIN_P12KEY_URL environment variable to perform code-signing'.gray)
+ console.log('Skipping code-signing. Specify the --code-sign option and provide a ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable to perform code-signing'.gray)
}
const cleanUp = function () {
- if (fs.existsSync(certPath)) {
+ if (fs.existsSync(certPath) && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) {
console.log(`Deleting certificate at ${certPath}`)
fs.removeSync(certPath)
}
diff --git a/script/lib/download-chromedriver.js b/script/lib/download-chromedriver.js
deleted file mode 100644
index ec52823f9..000000000
--- a/script/lib/download-chromedriver.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict'
-
-const assert = require('assert')
-const downloadFileFromGithub = require('./download-file-from-github')
-const fs = require('fs-extra')
-const path = require('path')
-const semver = require('semver')
-const spawnSync = require('./spawn-sync')
-const syncRequest = require('sync-request')
-
-const CONFIG = require('../config')
-
-module.exports = function () {
- if (process.platform === 'darwin') {
- // Chromedriver is only distributed with the first patch release for any given
- // major and minor version of electron.
- const electronVersion = semver.parse(CONFIG.appMetadata.electronVersion)
- const electronVersionWithChromedriver = `${electronVersion.major}.${electronVersion.minor}.0`
- const electronAssets = getElectronAssetsForVersion(electronVersionWithChromedriver)
- const chromedriverAssets = electronAssets.filter(e => /chromedriver.*darwin-x64/.test(e.name))
- assert(chromedriverAssets.length === 1, 'Found more than one chrome driver asset to download!')
- const chromedriverAsset = chromedriverAssets[0]
-
- const chromedriverZipPath = path.join(CONFIG.electronDownloadPath, `electron-${electronVersionWithChromedriver}-${chromedriverAsset.name}`)
- if (!fs.existsSync(chromedriverZipPath)) {
- downloadFileFromGithub(chromedriverAsset.url, chromedriverZipPath)
- }
-
- const chromedriverDirPath = path.join(CONFIG.electronDownloadPath, 'chromedriver')
- unzipPath(chromedriverZipPath, chromedriverDirPath)
- } else {
- console.log('Skipping Chromedriver download because it is used only on macOS'.gray)
- }
-}
-
-function getElectronAssetsForVersion (version) {
- const releaseURL = `https://api.github.com/repos/electron/electron/releases/tags/v${version}`
- const response = syncRequest('GET', releaseURL, {'headers': {'User-Agent': 'Atom Build'}})
-
- if (response.statusCode === 200) {
- const release = JSON.parse(response.body)
- return release.assets.map(a => { return {name: a.name, url: a.browser_download_url} })
- } else {
- throw new Error(`Error getting assets for ${releaseURL}. HTTP Status ${response.statusCode}.`)
- }
-}
-
-function unzipPath (inputPath, outputPath) {
- if (fs.existsSync(outputPath)) {
- console.log(`Removing "${outputPath}"`)
- fs.removeSync(outputPath)
- }
-
- console.log(`Unzipping "${inputPath}" to "${outputPath}"`)
- spawnSync('unzip', [inputPath, '-d', outputPath])
-}
diff --git a/script/lib/package-application.js b/script/lib/package-application.js
index a9cd4372a..1e63b8dc0 100644
--- a/script/lib/package-application.js
+++ b/script/lib/package-application.js
@@ -73,7 +73,7 @@ function copyNonASARResources (packagedAppPath, bundledResourcesPath) {
} else if (process.platform === 'linux') {
fs.copySync(path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png', '1024.png'), path.join(packagedAppPath, 'atom.png'))
} else if (process.platform === 'win32') {
- [ 'atom.cmd', 'atom.sh', 'atom.js', 'apm.cmd', 'apm.sh', 'file.ico' ]
+ [ 'atom.cmd', 'atom.sh', 'atom.js', 'apm.cmd', 'apm.sh', 'file.ico', 'folder.ico' ]
.forEach(file => fs.copySync(path.join('resources', 'win', file), path.join(bundledResourcesPath, 'cli', file)))
}
diff --git a/script/lib/verify-machine-requirements.js b/script/lib/verify-machine-requirements.js
index 6ba0044ef..5cf594e6e 100644
--- a/script/lib/verify-machine-requirements.js
+++ b/script/lib/verify-machine-requirements.js
@@ -17,8 +17,10 @@ module.exports = function () {
function verifyNode () {
const fullVersion = process.versions.node
const majorVersion = fullVersion.split('.')[0]
- if (majorVersion >= 4) {
+ if (majorVersion >= 4 && majorVersion < 7) {
console.log(`Node:\tv${fullVersion}`)
+ } else if (majorVersion >= 7) {
+ throw new Error(`Atom does not build properly on node v7+. node v${fullVersion} is installed.`)
} else {
throw new Error(`node v4+ is required to build Atom. node v${fullVersion} is installed.`)
}
diff --git a/script/package.json b/script/package.json
index 2ba28ba41..87c43f261 100644
--- a/script/package.json
+++ b/script/package.json
@@ -3,16 +3,16 @@
"description": "Atom build scripts",
"dependencies": {
"async": "2.0.1",
- "babel-core": "5.8.38",
"coffeelint": "1.15.7",
"colors": "1.1.2",
"csslint": "1.0.2",
"donna": "1.0.13",
+ "electron-chromedriver": "~1.3",
"electron-packager": "7.3.0",
- "electron-winstaller": "2.4.0",
+ "electron-winstaller": "2.5.1",
"fs-extra": "0.30.0",
"glob": "7.0.3",
- "joanna": "0.0.6",
+ "joanna": "0.0.8",
"legal-eagle": "0.13.0",
"lodash.template": "4.4.0",
"minidump": "0.9.0",
diff --git a/script/test b/script/test
index 38568207f..2e159d09b 100755
--- a/script/test
+++ b/script/test
@@ -93,11 +93,15 @@ function runBenchmarkTests (callback) {
cp.on('close', exitCode => { callback(null, exitCode) })
}
-let testSuitesToRun
-if (process.platform === 'darwin') {
- testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
-} else {
- testSuitesToRun = [runCoreMainProcessTests]
+let testSuitesToRun = testSuitesForPlatform(process.platform)
+
+function testSuitesForPlatform(platform) {
+ switch(platform) {
+ case 'darwin': return [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
+ case 'win32': return (process.arch === 'x64') ? [runCoreMainProcessTests, runCoreRenderProcessTests] : [runCoreMainProcessTests]
+ case 'linux': return [runCoreMainProcessTests]
+ default: return []
+ }
}
async.series(testSuitesToRun, function (err, exitCodes) {
diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee
index 6715d04e2..d967fb97b 100644
--- a/spec/atom-environment-spec.coffee
+++ b/spec/atom-environment-spec.coffee
@@ -1,10 +1,13 @@
_ = require 'underscore-plus'
path = require 'path'
-temp = require 'temp'
+temp = require('temp').track()
AtomEnvironment = require '../src/atom-environment'
StorageFolder = require '../src/storage-folder'
describe "AtomEnvironment", ->
+ afterEach ->
+ temp.cleanupSync()
+
describe 'window sizing methods', ->
describe '::getPosition and ::setPosition', ->
originalPosition = null
@@ -139,6 +142,11 @@ describe "AtomEnvironment", ->
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 "if the condition is true", ->
it "does nothing", ->
result = atom.assert(true, "a == b")
@@ -324,7 +332,7 @@ describe "AtomEnvironment", ->
describe "::unloadEditorWindow()", ->
it "saves the BlobStore so it can be loaded after reload", ->
- configDirPath = temp.mkdirSync()
+ configDirPath = temp.mkdirSync('atom-spec-environment')
fakeBlobStore = jasmine.createSpyObj("blob store", ["save"])
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true, configDirPath, blobStore: fakeBlobStore, window, document})
@@ -336,7 +344,7 @@ describe "AtomEnvironment", ->
describe "::destroy()", ->
it "does not throw exceptions when unsubscribing from ipc events (regression)", ->
- configDirPath = temp.mkdirSync()
+ configDirPath = temp.mkdirSync('atom-spec-environment')
fakeDocument = {
addEventListener: ->
removeEventListener: ->
@@ -401,6 +409,8 @@ describe "AtomEnvironment", ->
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")
diff --git a/spec/atom-paths-spec.js b/spec/atom-paths-spec.js
new file mode 100644
index 000000000..3e2da4760
--- /dev/null
+++ b/spec/atom-paths-spec.js
@@ -0,0 +1,118 @@
+/** @babel */
+
+import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
+import {app} from 'remote'
+import atomPaths from '../src/atom-paths'
+import fs from 'fs-plus'
+import path from 'path'
+const temp = require('temp').track()
+
+describe("AtomPaths", () => {
+ const portableAtomHomePath = path.join(atomPaths.getAppDirectory(), '..', '.atom')
+
+ afterEach(() => {
+ atomPaths.setAtomHome(app.getPath('home'))
+ })
+
+ describe('SetAtomHomePath', () => {
+ describe('when a portable .atom folder exists', () => {
+ beforeEach(() => {
+ delete process.env.ATOM_HOME
+ if (!fs.existsSync(portableAtomHomePath))
+ fs.mkdirSync(portableAtomHomePath)
+ })
+
+ afterEach(() => {
+ delete process.env.ATOM_HOME
+ fs.removeSync(portableAtomHomePath)
+ })
+
+ it('sets ATOM_HOME to the portable .atom folder if it has permission', () => {
+ atomPaths.setAtomHome(app.getPath('home'))
+ expect(process.env.ATOM_HOME).toEqual(portableAtomHomePath)
+ })
+
+ it('uses ATOM_HOME if no write access to portable .atom folder', () => {
+ if (process.platform === 'win32') return
+
+ const readOnlyPath = temp.mkdirSync('atom-path-spec-no-write-access')
+ process.env.ATOM_HOME = readOnlyPath
+ fs.chmodSync(portableAtomHomePath, 444)
+ atomPaths.setAtomHome(app.getPath('home'))
+ expect(process.env.ATOM_HOME).toEqual(readOnlyPath)
+ })
+ })
+
+ describe('when a portable folder does not exist', () => {
+ beforeEach(() => {
+ delete process.env.ATOM_HOME
+ fs.removeSync(portableAtomHomePath)
+ })
+
+ afterEach(() => {
+ delete process.env.ATOM_HOME
+ })
+
+ it('leaves ATOM_HOME unmodified if it was already set', () => {
+ const temporaryHome = temp.mkdirSync('atom-spec-setatomhomepath')
+ process.env.ATOM_HOME = temporaryHome
+ atomPaths.setAtomHome(app.getPath('home'))
+ expect(process.env.ATOM_HOME).toEqual(temporaryHome)
+ })
+
+ it('sets ATOM_HOME to a default location if not yet set', () => {
+ const expectedPath = path.join(app.getPath('home'), '.atom')
+ atomPaths.setAtomHome(app.getPath('home'))
+ expect(process.env.ATOM_HOME).toEqual(expectedPath)
+ })
+ })
+ })
+
+ describe('setUserData', () => {
+ let tempAtomHomePath = null
+ let electronUserDataPath = null
+ let defaultElectronUserDataPath = null
+
+ beforeEach(() => {
+ defaultElectronUserDataPath = app.getPath('userData')
+ delete process.env.ATOM_HOME
+ tempAtomHomePath = temp.mkdirSync('atom-paths-specs-userdata-home')
+ tempAtomConfigPath = path.join(tempAtomHomePath, '.atom')
+ fs.mkdirSync(tempAtomConfigPath)
+ electronUserDataPath = path.join(tempAtomConfigPath, 'electronUserData')
+ atomPaths.setAtomHome(tempAtomHomePath)
+ })
+
+ afterEach(() => {
+ delete process.env.ATOM_HOME
+ fs.removeSync(electronUserDataPath)
+ temp.cleanupSync()
+ app.setPath('userData', defaultElectronUserDataPath)
+ })
+
+ describe('when an electronUserData folder exists', () => {
+ it('sets userData path to the folder if it has permission', () => {
+ fs.mkdirSync(electronUserDataPath)
+ atomPaths.setUserData(app)
+ expect(app.getPath('userData')).toEqual(electronUserDataPath)
+ })
+
+ it('leaves userData unchanged if no write access to electronUserData folder', () => {
+ if (process.platform === 'win32') return
+
+ fs.mkdirSync(electronUserDataPath)
+ fs.chmodSync(electronUserDataPath, 444)
+ atomPaths.setUserData(app)
+ fs.chmodSync(electronUserDataPath, 666)
+ expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath)
+ })
+ })
+
+ describe('when an electronUserDataPath folder does not exist', () => {
+ it('leaves userData app path unchanged', () => {
+ atomPaths.setUserData(app)
+ expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath)
+ })
+ })
+ })
+})
diff --git a/spec/atom-portable-spec.coffee b/spec/atom-portable-spec.coffee
deleted file mode 100644
index aeb71b7c1..000000000
--- a/spec/atom-portable-spec.coffee
+++ /dev/null
@@ -1,67 +0,0 @@
-path = require 'path'
-fs = require 'fs-plus'
-AtomPortable = require '../src/main-process/atom-portable'
-
-portableModeCommonPlatformBehavior = (platform) ->
- describe "with ATOM_HOME environment variable", ->
- it "returns false", ->
- expect(AtomPortable.isPortableInstall(platform, "C:\\some\\path")).toBe false
-
- describe "without ATOM_HOME environment variable", ->
- environmentAtomHome = undefined
- portableAtomHomePath = path.join(path.dirname(process.execPath), "..", ".atom")
- portableAtomHomeNaturallyExists = fs.existsSync(portableAtomHomePath)
- portableAtomHomeBackupPath = "#{portableAtomHomePath}.temp"
-
- beforeEach ->
- fs.renameSync(portableAtomHomePath, portableAtomHomeBackupPath) if fs.existsSync(portableAtomHomePath)
-
- afterEach ->
- if portableAtomHomeNaturallyExists
- fs.renameSync(portableAtomHomeBackupPath, portableAtomHomePath) if not fs.existsSync(portableAtomHomePath)
- else
- fs.removeSync(portableAtomHomePath) if fs.existsSync(portableAtomHomePath)
- fs.removeSync(portableAtomHomeBackupPath) if fs.existsSync(portableAtomHomeBackupPath)
-
- describe "with .atom directory sibling to exec", ->
- beforeEach ->
- fs.mkdirSync(portableAtomHomePath) if not fs.existsSync(portableAtomHomePath)
-
- describe "without .atom directory sibling to exec", ->
- beforeEach ->
- fs.removeSync(portableAtomHomePath) if fs.existsSync(portableAtomHomePath)
-
- it "returns false", ->
- expect(AtomPortable.isPortableInstall(platform, environmentAtomHome)).toBe false
-
-describe "Set Portable Mode on #win32", ->
- portableAtomHomePath = path.join(path.dirname(process.execPath), "..", ".atom")
- portableAtomHomeNaturallyExists = fs.existsSync(portableAtomHomePath)
- portableAtomHomeBackupPath = "#{portableAtomHomePath}.temp"
-
- beforeEach ->
- fs.renameSync(portableAtomHomePath, portableAtomHomeBackupPath) if fs.existsSync(portableAtomHomePath)
-
- afterEach ->
- if portableAtomHomeNaturallyExists
- fs.renameSync(portableAtomHomeBackupPath, portableAtomHomePath) if not fs.existsSync(portableAtomHomePath)
- else
- fs.removeSync(portableAtomHomePath) if fs.existsSync(portableAtomHomePath)
- fs.removeSync(portableAtomHomeBackupPath) if fs.existsSync(portableAtomHomeBackupPath)
-
- it "creates a portable home directory", ->
- expect(fs.existsSync(portableAtomHomePath)).toBe false
-
- AtomPortable.setPortable(process.env.ATOM_HOME)
- expect(fs.existsSync(portableAtomHomePath)).toBe true
-
-describe "Check for Portable Mode", ->
- describe "Windows", ->
- portableModeCommonPlatformBehavior "win32"
-
- describe "Mac", ->
- it "returns false", ->
- expect(AtomPortable.isPortableInstall("darwin", "darwin")).toBe false
-
- describe "Linux", ->
- portableModeCommonPlatformBehavior "linux"
diff --git a/spec/auto-update-manager-spec.js b/spec/auto-update-manager-spec.js
index be3a67c84..b38e7827c 100644
--- a/spec/auto-update-manager-spec.js
+++ b/spec/auto-update-manager-spec.js
@@ -5,6 +5,9 @@ import {remote} from 'electron'
const electronAutoUpdater = remote.require('electron').autoUpdater
describe('AutoUpdateManager (renderer)', () => {
+
+ if (process.platform !== 'darwin') return // Tests are tied to electron autoUpdater, we use something else on Linux and Win32
+
let autoUpdateManager
beforeEach(() => {
diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee
index e95b000cb..4e7b2b395 100644
--- a/spec/babel-spec.coffee
+++ b/spec/babel-spec.coffee
@@ -19,6 +19,7 @@ describe "Babel transpiler support", ->
afterEach ->
CompileCache.setCacheDirectory(originalCacheDir)
+ temp.cleanupSync()
describe 'when a .js file starts with /** @babel */;', ->
it "transpiles it using babel", ->
diff --git a/spec/buffered-process-spec.coffee b/spec/buffered-process-spec.coffee
index 84d8b0440..6f9b2a28d 100644
--- a/spec/buffered-process-spec.coffee
+++ b/spec/buffered-process-spec.coffee
@@ -67,6 +67,29 @@ describe "BufferedProcess", ->
expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope2`'
expect(window.onerror.mostRecentCall.args[4].name).toBe 'BufferedProcessError'
+ describe "when autoStart is false", ->
+ it "doesnt start unless start method is called", ->
+ stdout = ''
+ stderr = ''
+ exitCallback = jasmine.createSpy('exit callback')
+ apmProcess = new BufferedProcess
+ autoStart: false
+ command: atom.packages.getApmPath()
+ args: ['-h']
+ options: {}
+ stdout: (lines) -> stdout += lines
+ stderr: (lines) -> stderr += lines
+ exit: exitCallback
+
+ expect(apmProcess.started).not.toBe(true)
+ apmProcess.start()
+ expect(apmProcess.started).toBe(true)
+
+ waitsFor -> exitCallback.callCount is 1
+ runs ->
+ expect(stderr).toContain 'apm - Atom Package Manager'
+ expect(stdout).toEqual ''
+
it "calls the specified stdout, stderr, and exit callbacks", ->
stdout = ''
stderr = ''
diff --git a/spec/command-installer-spec.coffee b/spec/command-installer-spec.coffee
index 84fd77a34..a1cf194a8 100644
--- a/spec/command-installer-spec.coffee
+++ b/spec/command-installer-spec.coffee
@@ -1,6 +1,6 @@
path = require 'path'
fs = require 'fs-plus'
-temp = require 'temp'
+temp = require('temp').track()
CommandInstaller = require '../src/command-installer'
describe "CommandInstaller on #darwin", ->
@@ -20,6 +20,9 @@ describe "CommandInstaller on #darwin", ->
spyOn(CommandInstaller::, 'getResourcesDirectory').andReturn(resourcesPath)
spyOn(CommandInstaller::, 'getInstallDirectory').andReturn(installationPath)
+ afterEach ->
+ temp.cleanupSync()
+
it "shows an error dialog when installing commands interactively fails", ->
appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"])
installer = new CommandInstaller("2.0.2", appDelegate)
diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee
index bec689c7d..13db6a055 100644
--- a/spec/compile-cache-spec.coffee
+++ b/spec/compile-cache-spec.coffee
@@ -21,8 +21,9 @@ describe 'CompileCache', ->
spyOn(TypeScriptSimple::, 'compile').andReturn 'the-typescript-code'
afterEach ->
- CSON.setCacheDir(CompileCache.getCacheDirectory())
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
+ CSON.setCacheDir(CompileCache.getCacheDirectory())
+ temp.cleanupSync()
describe 'addPathToCache(filePath, atomHome)', ->
describe 'when the given file is plain javascript', ->
@@ -77,6 +78,8 @@ describe 'CompileCache', ->
describe 'overriding Error.prepareStackTrace', ->
it 'removes the override on the next tick, and always assigns the raw stack', ->
+ return if process.platform is 'win32' # Flakey Error.stack contents on Win32
+
Error.prepareStackTrace = -> 'a-stack-trace'
error = new Error("Oops")
diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee
index acd9b112b..3134a428f 100644
--- a/spec/config-spec.coffee
+++ b/spec/config-spec.coffee
@@ -1,5 +1,5 @@
path = require 'path'
-temp = require 'temp'
+temp = require('temp').track()
CSON = require 'season'
fs = require 'fs-plus'
@@ -9,13 +9,14 @@ describe "Config", ->
beforeEach ->
spyOn(atom.config, "load")
spyOn(atom.config, "save")
- dotAtomPath = temp.path('dot-atom-dir')
+ dotAtomPath = temp.path('atom-spec-config')
atom.config.configDirPath = dotAtomPath
atom.config.enablePersistence = true
atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
afterEach ->
atom.config.enablePersistence = false
+ fs.removeSync(dotAtomPath)
describe ".get(keyPath, {scope, sources, excludeSources})", ->
it "allows a key path's value to be read", ->
@@ -486,8 +487,8 @@ describe "Config", ->
observeHandler.reset() # clear the initial call
atom.config.set('foo.bar.baz', "value 2")
expect(observeHandler).toHaveBeenCalledWith("value 2")
- observeHandler.reset()
+ observeHandler.reset()
atom.config.set('foo.bar.baz', "value 1")
expect(observeHandler).toHaveBeenCalledWith("value 1")
advanceClock(100) # complete pending save that was requested in ::set
@@ -1079,6 +1080,7 @@ describe "Config", ->
describe "when the configDirPath doesn't exist", ->
it "copies the contents of dot-atom to ~/.atom", ->
+ return if process.platform is 'win32' # Flakey test on Win32
initializationDone = false
jasmine.unspy(window, "setTimeout")
atom.config.initializeConfigDirectory ->
diff --git a/spec/decoration-manager-spec.coffee b/spec/decoration-manager-spec.coffee
index 44da440d9..e57660a57 100644
--- a/spec/decoration-manager-spec.coffee
+++ b/spec/decoration-manager-spec.coffee
@@ -1,13 +1,14 @@
DecorationManager = require '../src/decoration-manager'
describe "DecorationManager", ->
- [decorationManager, buffer, defaultMarkerLayer] = []
+ [decorationManager, buffer, displayLayer, markerLayer1, markerLayer2] = []
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
displayLayer = buffer.addDisplayLayer()
- defaultMarkerLayer = displayLayer.addMarkerLayer()
- decorationManager = new DecorationManager(displayLayer, defaultMarkerLayer)
+ markerLayer1 = displayLayer.addMarkerLayer()
+ markerLayer2 = displayLayer.addMarkerLayer()
+ decorationManager = new DecorationManager(displayLayer)
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@@ -17,38 +18,53 @@ describe "DecorationManager", ->
buffer.release()
describe "decorations", ->
- [marker, decoration, decorationProperties] = []
+ [layer1Marker, layer2Marker, layer1MarkerDecoration, layer2MarkerDecoration, decorationProperties] = []
beforeEach ->
- marker = defaultMarkerLayer.markBufferRange([[2, 13], [3, 15]])
+ layer1Marker = markerLayer1.markBufferRange([[2, 13], [3, 15]])
decorationProperties = {type: 'line-number', class: 'one'}
- decoration = decorationManager.decorateMarker(marker, decorationProperties)
+ layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
+ layer2Marker = markerLayer2.markBufferRange([[2, 13], [3, 15]])
+ layer2MarkerDecoration = decorationManager.decorateMarker(layer2Marker, decorationProperties)
it "can add decorations associated with markers and remove them", ->
- expect(decoration).toBeDefined()
- expect(decoration.getProperties()).toBe decorationProperties
- expect(decorationManager.decorationForId(decoration.id)).toBe decoration
- expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
+ expect(layer1MarkerDecoration).toBeDefined()
+ expect(layer1MarkerDecoration.getProperties()).toBe decorationProperties
+ expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).toBe layer1MarkerDecoration
+ expect(decorationManager.decorationsForScreenRowRange(2, 3)).toEqual {
+ "#{layer1Marker.id}": [layer1MarkerDecoration],
+ "#{layer2Marker.id}": [layer2MarkerDecoration]
+ }
- decoration.destroy()
- expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
- expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
+ layer1MarkerDecoration.destroy()
+ expect(decorationManager.decorationsForScreenRowRange(2, 3)[layer1Marker.id]).not.toBeDefined()
+ expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).not.toBeDefined()
+ layer2MarkerDecoration.destroy()
+ expect(decorationManager.decorationsForScreenRowRange(2, 3)[layer2Marker.id]).not.toBeDefined()
+ expect(decorationManager.decorationForId(layer2MarkerDecoration.id)).not.toBeDefined()
it "will not fail if the decoration is removed twice", ->
- decoration.destroy()
- decoration.destroy()
- expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
+ layer1MarkerDecoration.destroy()
+ layer1MarkerDecoration.destroy()
+ expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).not.toBeDefined()
it "does not allow destroyed markers to be decorated", ->
- marker.destroy()
+ layer1Marker.destroy()
expect(->
- decorationManager.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
+ decorationManager.decorateMarker(layer1Marker, {type: 'overlay', item: document.createElement('div')})
).toThrow("Cannot decorate a destroyed marker")
expect(decorationManager.getOverlayDecorations()).toEqual []
+ it "does not allow destroyed marker layers to be decorated", ->
+ layer = displayLayer.addMarkerLayer()
+ layer.destroy()
+ expect(->
+ decorationManager.decorateMarkerLayer(layer, {type: 'highlight'})
+ ).toThrow("Cannot decorate a destroyed marker layer")
+
describe "when a decoration is updated via Decoration::update()", ->
it "emits an 'updated' event containing the new and old params", ->
- decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
- decoration.setProperties type: 'line-number', class: 'two'
+ layer1MarkerDecoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
+ layer1MarkerDecoration.setProperties type: 'line-number', class: 'two'
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
expect(oldProperties).toEqual decorationProperties
@@ -56,29 +72,29 @@ describe "DecorationManager", ->
describe "::getDecorations(properties)", ->
it "returns decorations matching the given optional properties", ->
- expect(decorationManager.getDecorations()).toEqual [decoration]
+ expect(decorationManager.getDecorations()).toEqual [layer1MarkerDecoration, layer2MarkerDecoration]
expect(decorationManager.getDecorations(class: 'two').length).toEqual 0
- expect(decorationManager.getDecorations(class: 'one').length).toEqual 1
+ expect(decorationManager.getDecorations(class: 'one').length).toEqual 2
describe "::decorateMarker", ->
describe "when decorating gutters", ->
- [marker] = []
+ [layer1Marker] = []
beforeEach ->
- marker = defaultMarkerLayer.markBufferRange([[1, 0], [1, 0]])
+ layer1Marker = markerLayer1.markBufferRange([[1, 0], [1, 0]])
it "creates a decoration that is both of 'line-number' and 'gutter' type when called with the 'line-number' type", ->
decorationProperties = {type: 'line-number', class: 'one'}
- decoration = decorationManager.decorateMarker(marker, decorationProperties)
- expect(decoration.isType('line-number')).toBe true
- expect(decoration.isType('gutter')).toBe true
- expect(decoration.getProperties().gutterName).toBe 'line-number'
- expect(decoration.getProperties().class).toBe 'one'
+ layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
+ expect(layer1MarkerDecoration.isType('line-number')).toBe true
+ expect(layer1MarkerDecoration.isType('gutter')).toBe true
+ expect(layer1MarkerDecoration.getProperties().gutterName).toBe 'line-number'
+ expect(layer1MarkerDecoration.getProperties().class).toBe 'one'
it "creates a decoration that is only of 'gutter' type if called with the 'gutter' type and a 'gutterName'", ->
decorationProperties = {type: 'gutter', gutterName: 'test-gutter', class: 'one'}
- decoration = decorationManager.decorateMarker(marker, decorationProperties)
- expect(decoration.isType('gutter')).toBe true
- expect(decoration.isType('line-number')).toBe false
- expect(decoration.getProperties().gutterName).toBe 'test-gutter'
- expect(decoration.getProperties().class).toBe 'one'
+ layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
+ expect(layer1MarkerDecoration.isType('gutter')).toBe true
+ expect(layer1MarkerDecoration.isType('line-number')).toBe false
+ expect(layer1MarkerDecoration.getProperties().gutterName).toBe 'test-gutter'
+ expect(layer1MarkerDecoration.getProperties().class).toBe 'one'
diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee
index df4f589b5..bf23195cf 100644
--- a/spec/default-directory-provider-spec.coffee
+++ b/spec/default-directory-provider-spec.coffee
@@ -1,20 +1,26 @@
DefaultDirectoryProvider = require '../src/default-directory-provider'
path = require 'path'
fs = require 'fs-plus'
-temp = require 'temp'
+temp = require('temp').track()
describe "DefaultDirectoryProvider", ->
+ tmp = null
+
+ beforeEach ->
+ tmp = temp.mkdirSync('atom-spec-default-dir-provider')
+
+ afterEach ->
+ temp.cleanupSync()
+
describe ".directoryForURISync(uri)", ->
it "returns a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()
- tmp = temp.mkdirSync()
directory = provider.directoryForURISync(tmp)
expect(directory.getPath()).toEqual tmp
it "normalizes its input before creating a Directory for it", ->
provider = new DefaultDirectoryProvider()
- tmp = temp.mkdirSync()
nonNormalizedPath = tmp + path.sep + ".." + path.sep + path.basename(tmp)
expect(tmp.includes("..")).toBe false
expect(nonNormalizedPath.includes("..")).toBe true
@@ -22,9 +28,17 @@ describe "DefaultDirectoryProvider", ->
directory = provider.directoryForURISync(nonNormalizedPath)
expect(directory.getPath()).toEqual tmp
+ it "normalizes disk drive letter in path on #win32", ->
+ provider = new DefaultDirectoryProvider()
+ nonNormalizedPath = tmp[0].toLowerCase()+tmp.slice(1)
+ expect(tmp).not.toMatch /^[a-z]:/
+ expect(nonNormalizedPath).toMatch /^[a-z]:/
+
+ directory = provider.directoryForURISync(nonNormalizedPath)
+ expect(directory.getPath()).toEqual tmp
+
it "creates a Directory for its parent dir when passed a file", ->
provider = new DefaultDirectoryProvider()
- tmp = temp.mkdirSync()
file = path.join(tmp, "example.txt")
fs.writeFileSync(file, "data")
@@ -40,7 +54,6 @@ describe "DefaultDirectoryProvider", ->
describe ".directoryForURI(uri)", ->
it "returns a Promise that resolves to a Directory with a path that matches the uri", ->
provider = new DefaultDirectoryProvider()
- tmp = temp.mkdirSync()
waitsForPromise ->
provider.directoryForURI(tmp).then (directory) ->
diff --git a/spec/dom-element-pool-spec.coffee b/spec/dom-element-pool-spec.coffee
deleted file mode 100644
index 2efe80beb..000000000
--- a/spec/dom-element-pool-spec.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-DOMElementPool = require '../src/dom-element-pool'
-{contains} = require 'underscore-plus'
-
-describe "DOMElementPool", ->
- domElementPool = null
-
- beforeEach ->
- domElementPool = new DOMElementPool
-
- it "builds DOM nodes, recycling them when they are freed", ->
- [div, span1, span2, span3, span4, span5, textNode] = elements = [
- domElementPool.buildElement("div")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildText("Hello world!")
- ]
-
- div.appendChild(span1)
- span1.appendChild(span2)
- div.appendChild(span3)
- span3.appendChild(span4)
- span4.appendChild(textNode)
-
- domElementPool.freeElementAndDescendants(div)
- domElementPool.freeElementAndDescendants(span5)
-
- expect(contains(elements, domElementPool.buildElement("div"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildText("another text"))).toBe(true)
-
- expect(contains(elements, domElementPool.buildElement("div"))).toBe(false)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(false)
- expect(contains(elements, domElementPool.buildText("unexisting"))).toBe(false)
-
- it "forgets free nodes after being cleared", ->
- span = domElementPool.buildElement("span")
- div = domElementPool.buildElement("div")
- domElementPool.freeElementAndDescendants(span)
- domElementPool.freeElementAndDescendants(div)
-
- domElementPool.clear()
-
- expect(domElementPool.buildElement("span")).not.toBe(span)
- expect(domElementPool.buildElement("div")).not.toBe(div)
-
- it "throws an error when trying to free the same node twice", ->
- div = domElementPool.buildElement("div")
- domElementPool.freeElementAndDescendants(div)
- expect(-> domElementPool.freeElementAndDescendants(div)).toThrow()
-
- it "throws an error when trying to free an invalid element", ->
- expect(-> domElementPool.freeElementAndDescendants(null)).toThrow()
- expect(-> domElementPool.freeElementAndDescendants(undefined)).toThrow()
diff --git a/spec/dom-element-pool-spec.js b/spec/dom-element-pool-spec.js
new file mode 100644
index 000000000..9de932e27
--- /dev/null
+++ b/spec/dom-element-pool-spec.js
@@ -0,0 +1,112 @@
+const DOMElementPool = require ('../src/dom-element-pool')
+
+describe('DOMElementPool', function () {
+ let domElementPool
+
+ beforeEach(() => { domElementPool = new DOMElementPool() })
+
+ it('builds DOM nodes, recycling them when they are freed', function () {
+ let elements
+ const [div, span1, span2, span3, span4, span5, textNode] = Array.from(elements = [
+ domElementPool.buildElement('div', 'foo'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildText('Hello world!')
+ ])
+
+ expect(div.className).toBe('foo')
+ div.textContent = 'testing'
+ div.style.backgroundColor = 'red'
+ div.dataset.foo = 'bar'
+
+ expect(textNode.textContent).toBe('Hello world!')
+
+ div.appendChild(span1)
+ span1.appendChild(span2)
+ div.appendChild(span3)
+ span3.appendChild(span4)
+ span4.appendChild(textNode)
+
+ domElementPool.freeElementAndDescendants(div)
+ domElementPool.freeElementAndDescendants(span5)
+
+ expect(elements.includes(domElementPool.buildElement('div'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildText('another text'))).toBe(true)
+
+ expect(elements.includes(domElementPool.buildElement('div'))).toBe(false)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(false)
+ expect(elements.includes(domElementPool.buildText('unexisting'))).toBe(false)
+
+ expect(div.className).toBe('')
+ expect(div.textContent).toBe('')
+ expect(div.style.backgroundColor).toBe('')
+ expect(div.dataset.foo).toBeUndefined()
+
+ expect(textNode.textContent).toBe('another text')
+ })
+
+ it('forgets free nodes after being cleared', function () {
+ const span = domElementPool.buildElement('span')
+ const div = domElementPool.buildElement('div')
+ domElementPool.freeElementAndDescendants(span)
+ domElementPool.freeElementAndDescendants(div)
+
+ domElementPool.clear()
+
+ expect(domElementPool.buildElement('span')).not.toBe(span)
+ expect(domElementPool.buildElement('div')).not.toBe(div)
+ })
+
+ it('does not attempt to free nodes that were not created by the pool', () => {
+ let assertionFailure
+ atom.onDidFailAssertion((error) => assertionFailure = error)
+
+ const foreignDiv = document.createElement('div')
+ const div = domElementPool.buildElement('div')
+ div.appendChild(foreignDiv)
+ domElementPool.freeElementAndDescendants(div)
+ const span = domElementPool.buildElement('span')
+ span.appendChild(foreignDiv)
+ domElementPool.freeElementAndDescendants(span)
+
+ expect(assertionFailure).toBeUndefined()
+ })
+
+ it('fails an assertion when freeing the same element twice', function () {
+ let assertionFailure
+ atom.onDidFailAssertion((error) => assertionFailure = error)
+
+ const div = domElementPool.buildElement('div')
+ div.textContent = 'testing'
+ domElementPool.freeElementAndDescendants(div)
+ expect(assertionFailure).toBeUndefined()
+ domElementPool.freeElementAndDescendants(div)
+ expect(assertionFailure.message).toBe('Assertion failed: The element has already been freed!')
+ expect(assertionFailure.metadata.content).toBe('
testing
')
+ })
+
+ it('fails an assertion when freeing the same text node twice', function () {
+ let assertionFailure
+ atom.onDidFailAssertion((error) => assertionFailure = error)
+
+ const node = domElementPool.buildText('testing')
+ domElementPool.freeElementAndDescendants(node)
+ expect(assertionFailure).toBeUndefined()
+ domElementPool.freeElementAndDescendants(node)
+ expect(assertionFailure.message).toBe('Assertion failed: The element has already been freed!')
+ expect(assertionFailure.metadata.content).toBe('testing')
+ })
+
+ it('throws an error when trying to free an invalid element', function () {
+ expect(() => domElementPool.freeElementAndDescendants(null)).toThrow()
+ expect(() => domElementPool.freeElementAndDescendants(undefined)).toThrow()
+ })
+})
diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee
index 5147e59ee..a2ed39014 100644
--- a/spec/file-system-blob-store-spec.coffee
+++ b/spec/file-system-blob-store-spec.coffee
@@ -1,4 +1,4 @@
-temp = require 'temp'
+temp = require('temp').track()
path = require 'path'
fs = require 'fs-plus'
FileSystemBlobStore = require '../src/file-system-blob-store'
@@ -7,9 +7,12 @@ describe "FileSystemBlobStore", ->
[storageDirectory, blobStore] = []
beforeEach ->
- storageDirectory = temp.path()
+ storageDirectory = temp.path('atom-spec-filesystemblobstore')
blobStore = FileSystemBlobStore.load(storageDirectory)
+ afterEach ->
+ fs.removeSync(storageDirectory)
+
it "is empty when the file doesn't exist", ->
expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined()
expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined()
diff --git a/spec/fixtures/packages/package-with-deserializers/index.js b/spec/fixtures/packages/package-with-deserializers/index.js
index b9be23854..e653c0e67 100644
--- a/spec/fixtures/packages/package-with-deserializers/index.js
+++ b/spec/fixtures/packages/package-with-deserializers/index.js
@@ -1,4 +1,5 @@
module.exports = {
+ initialize() {},
activate () {},
deserializeMethod1 (state) {
diff --git a/spec/fixtures/sample.txt b/spec/fixtures/sample.txt
index 3e715502b..27d91067e 100644
--- a/spec/fixtures/sample.txt
+++ b/spec/fixtures/sample.txt
@@ -1 +1 @@
-Some text.
+Some textSome text.
diff --git a/spec/git-repository-provider-spec.coffee b/spec/git-repository-provider-spec.coffee
index bbbfb4b03..6c6a7b4b9 100644
--- a/spec/git-repository-provider-spec.coffee
+++ b/spec/git-repository-provider-spec.coffee
@@ -1,6 +1,6 @@
path = require 'path'
fs = require 'fs-plus'
-temp = require 'temp'
+temp = require('temp').track()
{Directory} = require 'pathwatcher'
GitRepository = require '../src/git-repository'
GitRepositoryProvider = require '../src/git-repository-provider'
@@ -11,6 +11,9 @@ describe "GitRepositoryProvider", ->
beforeEach ->
provider = new GitRepositoryProvider(atom.project, atom.config, atom.confirm)
+ afterEach ->
+ temp.cleanupSync()
+
describe ".repositoryForDirectory(directory)", ->
describe "when specified a Directory with a Git repository", ->
it "returns a Promise that resolves to a GitRepository", ->
diff --git a/spec/git-repository-spec.coffee b/spec/git-repository-spec.coffee
index c9a3badb5..59e8c4c68 100644
--- a/spec/git-repository-spec.coffee
+++ b/spec/git-repository-spec.coffee
@@ -1,11 +1,11 @@
-temp = require 'temp'
+temp = require('temp').track()
GitRepository = require '../src/git-repository'
fs = require 'fs-plus'
path = require 'path'
Project = require '../src/project'
copyRepository = ->
- workingDirPath = temp.mkdirSync('atom-working-dir')
+ workingDirPath = temp.mkdirSync('atom-spec-git')
fs.copySync(path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath)
fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
workingDirPath
@@ -19,6 +19,8 @@ describe "GitRepository", ->
afterEach ->
repo.destroy() if repo?.repo?
+ try
+ temp.cleanupSync() # These tests sometimes lag at shutting down resources
describe "@open(path)", ->
it "returns null when no repository is found", ->
@@ -29,10 +31,15 @@ describe "GitRepository", ->
expect(-> new GitRepository(path.join(temp.dir, 'nogit.txt'))).toThrow()
describe ".getPath()", ->
- it "returns the repository path for a .git directory path", ->
+ it "returns the repository path for a .git directory path with a file", ->
+ return if process.platform is 'win32' #Win32TestFailures - libgit2 does not detect files in .git folders
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git', 'HEAD'))
expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git')
+ it "returns the repository path for a .git directory path with a directory", ->
+ repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects'))
+ expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git')
+
it "returns the repository path for a repository path", ->
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git'))
expect(repo.getPath()).toBe path.join(__dirname, 'fixtures', 'git', 'master.git')
@@ -137,6 +144,8 @@ describe "GitRepository", ->
editor = atom.workspace.getActiveTextEditor()
it "displays a confirmation dialog by default", ->
+ return if process.platform is 'win32' # Permissions issues with this test on Windows
+
atom.confirm.andCallFake ({buttons}) -> buttons.OK()
atom.config.set('editor.confirmCheckoutHeadRevision', true)
@@ -145,6 +154,7 @@ describe "GitRepository", ->
expect(fs.readFileSync(filePath, 'utf8')).toBe ''
it "does not display a dialog when confirmation is disabled", ->
+ return if process.platform is 'win32' # Flakey EPERM opening a.txt on Win32
atom.config.set('editor.confirmCheckoutHeadRevision', false)
repo.checkoutHeadForEditor(editor)
@@ -154,7 +164,7 @@ describe "GitRepository", ->
describe ".destroy()", ->
it "throws an exception when any method is called after it is called", ->
- repo = new GitRepository(require.resolve('./fixtures/git/master.git/HEAD'))
+ repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git'))
repo.destroy()
expect(-> repo.getShortHead()).toThrow()
diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee
index 7dcff8bcd..47198a124 100644
--- a/spec/grammars-spec.coffee
+++ b/spec/grammars-spec.coffee
@@ -1,6 +1,6 @@
path = require 'path'
fs = require 'fs-plus'
-temp = require 'temp'
+temp = require('temp').track()
GrammarRegistry = require '../src/grammar-registry'
Grim = require 'grim'
@@ -24,6 +24,7 @@ describe "the `grammars` global", ->
afterEach ->
atom.packages.deactivatePackages()
atom.packages.unloadPackages()
+ temp.cleanupSync()
describe ".selectGrammar(filePath)", ->
it "always returns a grammar", ->
@@ -96,6 +97,7 @@ describe "the `grammars` global", ->
)
grammar1 = atom.grammars.loadGrammarSync(grammarPath1)
expect(atom.grammars.selectGrammar('more.test', '')).toBe grammar1
+ fs.removeSync(grammarPath1)
grammarPath2 = temp.path(suffix: '.json')
fs.writeFileSync grammarPath2, JSON.stringify(
@@ -105,6 +107,7 @@ describe "the `grammars` global", ->
)
grammar2 = atom.grammars.loadGrammarSync(grammarPath2)
expect(atom.grammars.selectGrammar('more.test', '')).toBe grammar2
+ fs.removeSync(grammarPath2)
it "favors non-bundled packages when breaking scoring ties", ->
waitsForPromise ->
diff --git a/spec/gutter-container-component-spec.coffee b/spec/gutter-container-component-spec.coffee
index 73a9d0f6c..c5efbaa8e 100644
--- a/spec/gutter-container-component-spec.coffee
+++ b/spec/gutter-container-component-spec.coffee
@@ -139,3 +139,22 @@ describe "GutterContainerComponent", ->
expect(expectedCustomGutterNode1).toBe atom.views.getView(customGutter1)
expectedCustomGutterNode3 = gutterContainerComponent.getDomNode().children.item(2)
expect(expectedCustomGutterNode3).toBe atom.views.getView(customGutter3)
+
+ it "reorders correctly when prepending multiple gutters at once", ->
+ lineNumberGutter = new Gutter(mockGutterContainer, {name: 'line-number'})
+ testState = buildTestState([lineNumberGutter])
+ gutterContainerComponent.updateSync(testState)
+ expect(gutterContainerComponent.getDomNode().children.length).toBe 1
+ expectedCustomGutterNode = gutterContainerComponent.getDomNode().children.item(0)
+ expect(expectedCustomGutterNode).toBe atom.views.getView(lineNumberGutter)
+
+ # Prepend two gutters at once
+ customGutter1 = new Gutter(mockGutterContainer, {name: 'first', priority: -200})
+ customGutter2 = new Gutter(mockGutterContainer, {name: 'second', priority: -100})
+ testState = buildTestState([customGutter1, customGutter2, lineNumberGutter])
+ gutterContainerComponent.updateSync(testState)
+ expect(gutterContainerComponent.getDomNode().children.length).toBe 3
+ expectedCustomGutterNode1 = gutterContainerComponent.getDomNode().children.item(0)
+ expect(expectedCustomGutterNode1).toBe atom.views.getView(customGutter1)
+ expectedCustomGutterNode2 = gutterContainerComponent.getDomNode().children.item(1)
+ expect(expectedCustomGutterNode2).toBe atom.views.getView(customGutter2)
diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee
index 1eb610a2f..856885f60 100644
--- a/spec/integration/helpers/start-atom.coffee
+++ b/spec/integration/helpers/start-atom.coffee
@@ -10,13 +10,13 @@ webdriverio = require '../../../script/node_modules/webdriverio'
AtomPath = remote.process.argv[0]
AtomLauncherPath = path.join(__dirname, "..", "helpers", "atom-launcher.sh")
-ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'electron', 'chromedriver', 'chromedriver')
+ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'script', 'node_modules', 'electron-chromedriver', 'bin', 'chromedriver')
SocketPath = path.join(os.tmpdir(), "atom-integration-test-#{Date.now()}.sock")
ChromedriverPort = 9515
ChromedriverURLBase = "/wd/hub"
ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status"
-userDataDir = temp.mkdirSync('atom-user-data-dir')
+userDataDir = null
chromeDriverUp = (done) ->
checkStatus = ->
@@ -38,6 +38,7 @@ chromeDriverDown = (done) ->
setTimeout(checkStatus, 100)
buildAtomClient = (args, env) ->
+ userDataDir = temp.mkdirSync('atom-user-data-dir')
client = webdriverio.remote(
host: 'localhost'
port: ChromedriverPort
diff --git a/spec/integration/smoke-spec.coffee b/spec/integration/smoke-spec.coffee
index 3f921c4fe..527ed1f8f 100644
--- a/spec/integration/smoke-spec.coffee
+++ b/spec/integration/smoke-spec.coffee
@@ -5,6 +5,8 @@ temp = require('temp').track()
runAtom = require './helpers/start-atom'
describe "Smoke Test", ->
+ return unless process.platform is 'darwin' # Fails on win32
+
atomHome = temp.mkdirSync('atom-home')
beforeEach ->
diff --git a/spec/keymap-extensions-spec.coffee b/spec/keymap-extensions-spec.coffee
new file mode 100644
index 000000000..784764036
--- /dev/null
+++ b/spec/keymap-extensions-spec.coffee
@@ -0,0 +1,23 @@
+path = require 'path'
+temp = require('temp').track()
+CSON = require 'season'
+fs = require 'fs-plus'
+
+describe "keymap-extensions", ->
+
+ beforeEach ->
+ atom.keymaps.configDirPath = temp.path('atom-spec-keymap-ext')
+ fs.writeFileSync(atom.keymaps.getUserKeymapPath(), '#')
+ @userKeymapLoaded = ->
+ atom.keymaps.onDidLoadUserKeymap => @userKeymapLoaded()
+
+ afterEach ->
+ fs.removeSync(atom.keymaps.configDirPath)
+ atom.keymaps.destroy()
+
+ describe "did-load-user-keymap", ->
+
+ it "fires when user keymap is loaded", ->
+ spyOn(this, 'userKeymapLoaded')
+ atom.keymaps.loadUserKeymap()
+ expect(@userKeymapLoaded).toHaveBeenCalled()
diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee
index 2172267db..68fd74804 100644
--- a/spec/lines-yardstick-spec.coffee
+++ b/spec/lines-yardstick-spec.coffee
@@ -78,9 +78,16 @@ describe "LinesYardstick", ->
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 38, top: 0})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})
+
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.875, top: 28})
+ when 'win32'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 42, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 71, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 280, top: 28})
it "reuses already computed pixel positions unless it is invalidated", ->
atom.styles.addStyleSheet """
@@ -133,72 +140,109 @@ describe "LinesYardstick", ->
editor.setText(text)
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
+ when 'win32'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 245
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 252
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 259
- describe "::screenPositionForPixelPosition(pixelPosition)", ->
- it "converts pixel positions to screen positions", ->
- atom.styles.addStyleSheet """
- * {
- font-size: 12px;
- font-family: monospace;
- }
- .syntax--function {
- font-size: 16px
- }
- """
+ it "handles lines containing a mix of left-to-right and right-to-left characters", ->
+ editor.setText('Persian, locally known as Parsi or Farsi (زبان فارسی), the predominant modern descendant of Old Persian.\n')
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 12.5})).toEqual([0, 2])
- expect(linesYardstick.screenPositionForPixelPosition({top: 14, left: 18.8})).toEqual([1, 3])
- expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100})).toEqual([2, 14])
- expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
- expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
- expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
- expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
- expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
- expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
+ atom.styles.addStyleSheet """
+ * {
+ font-size: 14px;
+ font-family: monospace;
+ }
+ """
- it "overshoots to the nearest character when text nodes are not spatially contiguous", ->
- atom.styles.addStyleSheet """
- * {
- font-size: 12px;
- font-family: monospace;
- }
- """
+ lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
+ linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
- buildLineNode = (screenRow) ->
- lineNode = document.createElement("div")
- lineNode.style.whiteSpace = "pre"
- lineNode.innerHTML = 'foobar'
- jasmine.attachToDOM(lineNode)
- createdLineNodes.push(lineNode)
- lineNode
- editor.setText("foobar")
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 126, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 521, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 487, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 873.625, top: 0})
+ when 'win32'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 120, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 496, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 464, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 832, top: 0})
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 7})).toEqual([0, 1])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 14})).toEqual([0, 2])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 21})).toEqual([0, 3])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 30})).toEqual([0, 3])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 50})).toEqual([0, 3])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 62})).toEqual([0, 3])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 69})).toEqual([0, 4])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 76})).toEqual([0, 5])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 100})).toEqual([0, 6])
- expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 200})).toEqual([0, 6])
+ describe "::screenPositionForPixelPosition(pixelPosition)", ->
+ it "converts pixel positions to screen positions", ->
+ atom.styles.addStyleSheet """
+ * {
+ font-size: 12px;
+ font-family: monospace;
+ }
+ .syntax--function {
+ font-size: 16px
+ }
+ """
- it "clips pixel positions above buffer start", ->
- expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
- expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0]
- expect(linesYardstick.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0]
- expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29]
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 12.5})).toEqual([0, 2])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 14, left: 18.8})).toEqual([1, 3])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100})).toEqual([2, 14])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
- it "clips pixel positions below buffer end", ->
- expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2]
- expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
- expect(linesYardstick.screenPositionForPixelPosition(top: (editor.getLastScreenRow() + 1) * 14, left: 0)).toEqual [12, 2]
- expect(linesYardstick.screenPositionForPixelPosition(top: editor.getLastScreenRow() * 14, left: 0)).toEqual [12, 0]
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
+ when 'win32'
+ expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 30])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 34])
- it "clips negative horizontal pixel positions", ->
- expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: -10)).toEqual [0, 0]
- expect(linesYardstick.screenPositionForPixelPosition(top: 1 * 14, left: -10)).toEqual [1, 0]
+ it "overshoots to the nearest character when text nodes are not spatially contiguous", ->
+ atom.styles.addStyleSheet """
+ * {
+ font-size: 12px;
+ font-family: monospace;
+ }
+ """
+
+ buildLineNode = (screenRow) ->
+ lineNode = document.createElement("div")
+ lineNode.style.whiteSpace = "pre"
+ lineNode.innerHTML = 'foobar'
+ jasmine.attachToDOM(lineNode)
+ createdLineNodes.push(lineNode)
+ lineNode
+ editor.setText("foobar")
+
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 7})).toEqual([0, 1])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 14})).toEqual([0, 2])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 21})).toEqual([0, 3])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 30})).toEqual([0, 3])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 50})).toEqual([0, 3])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 62})).toEqual([0, 3])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 69})).toEqual([0, 4])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 76})).toEqual([0, 5])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 100})).toEqual([0, 6])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 200})).toEqual([0, 6])
+
+ it "clips pixel positions above buffer start", ->
+ expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
+ expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0]
+ expect(linesYardstick.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0]
+ expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29]
+
+ it "clips pixel positions below buffer end", ->
+ expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2]
+ expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
+ expect(linesYardstick.screenPositionForPixelPosition(top: (editor.getLastScreenRow() + 1) * 14, left: 0)).toEqual [12, 2]
+ expect(linesYardstick.screenPositionForPixelPosition(top: editor.getLastScreenRow() * 14, left: 0)).toEqual [12, 0]
+
+ it "clips negative horizontal pixel positions", ->
+ expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: -10)).toEqual [0, 0]
+ expect(linesYardstick.screenPositionForPixelPosition(top: 1 * 14, left: -10)).toEqual [1, 0]
diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js
index 22902d3d8..d30900b99 100644
--- a/spec/main-process/atom-application.test.js
+++ b/spec/main-process/atom-application.test.js
@@ -22,7 +22,7 @@ describe('AtomApplication', function () {
originalAtomHome = process.env.ATOM_HOME
process.env.ATOM_HOME = makeTempDir('atom-home')
// Symlinking the compile cache into the temporary home dir makes the windows load much faster
- fs.symlinkSync(path.join(originalAtomHome, 'compile-cache'), path.join(process.env.ATOM_HOME, 'compile-cache'))
+ fs.symlinkSync(path.join(originalAtomHome, 'compile-cache'), path.join(process.env.ATOM_HOME, 'compile-cache'), 'junction')
season.writeFileSync(path.join(process.env.ATOM_HOME, 'config.cson'), {
'*': {
welcome: {showOnStartup: false},
@@ -196,7 +196,9 @@ describe('AtomApplication', function () {
it('persists window state based on the project directories', async function () {
const tempDirPath = makeTempDir()
const atomApplication = buildAtomApplication()
- const window1 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'new-file')]))
+ const nonExistentFilePath = path.join(tempDirPath, 'new-file')
+
+ const window1 = atomApplication.launch(parseCommandLine([nonExistentFilePath]))
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) {
@@ -205,17 +207,31 @@ describe('AtomApplication', function () {
}
})
})
+ await window1.saveState()
window1.close()
await window1.closedPromise
- const window2 = atomApplication.launch(parseCommandLine([path.join(tempDirPath)]))
+ // Restore unsaved state when opening the directory itself
+ const window2 = atomApplication.launch(parseCommandLine([tempDirPath]))
+ await window2.loadedPromise
const window2Text = await evalInWebContents(window2.browserWindow.webContents, function (sendBackToMainProcess) {
- atom.workspace.observeActivePaneItem(function (textEditor) {
- if (textEditor) sendBackToMainProcess(textEditor.getText())
- })
+ const textEditor = atom.workspace.getActiveTextEditor()
+ textEditor.moveToBottom()
+ textEditor.insertText(' How are you?')
+ sendBackToMainProcess(textEditor.getText())
})
+ assert.equal(window2Text, 'Hello World! How are you?')
+ await window2.saveState()
+ window2.close()
+ await window2.closedPromise
- assert.equal(window2Text, 'Hello World!')
+ // Restore unsaved state when opening a path to a non-existent file in the directory
+ const window3 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')]))
+ await window3.loadedPromise
+ const window3Texts = await evalInWebContents(window3.browserWindow.webContents, function (sendBackToMainProcess, nonExistentFilePath) {
+ sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText()))
+ })
+ assert.include(window3Texts, 'Hello World! How are you?')
})
it('shows all directories in the tree view when multiple directory paths are passed to Atom', async function () {
@@ -260,7 +276,7 @@ describe('AtomApplication', function () {
})
assert.equal(window1EditorTitle, 'untitled')
- const window2 = atomApplication.launch(parseCommandLine([]))
+ const window2 = atomApplication.openWithOptions(parseCommandLine([]))
await focusWindow(window2)
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
@@ -309,7 +325,7 @@ describe('AtomApplication', function () {
const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-directory-provider')
const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages')
fs.mkdirSync(packagesDirPath)
- fs.symlinkSync(packagePath, path.join(packagesDirPath, 'package-with-directory-provider'))
+ fs.symlinkSync(packagePath, path.join(packagesDirPath, 'package-with-directory-provider'), 'junction')
const atomApplication = buildAtomApplication()
atomApplication.config.set('core.disabledPackages', ['fuzzy-finder'])
@@ -396,6 +412,30 @@ describe('AtomApplication', function () {
})
}
})
+
+ describe('when adding or removing project folders', function () {
+ it('stores the window state immediately', async function () {
+ const dirA = makeTempDir()
+ const dirB = makeTempDir()
+
+ const atomApplication = buildAtomApplication()
+ const window = atomApplication.launch(parseCommandLine([dirA, dirB]))
+ await focusWindow(window)
+ assert.deepEqual(await getTreeViewRootDirectories(window), [dirA, dirB])
+
+ await evalInWebContents(window.browserWindow.webContents, (sendBackToMainProcess) => {
+ atom.project.removePath(atom.project.getPaths()[0])
+ sendBackToMainProcess(null)
+ })
+ assert.deepEqual(await getTreeViewRootDirectories(window), [dirB])
+
+ // Window state should be saved when the project folder is removed
+ const atomApplication2 = buildAtomApplication()
+ const [window2] = atomApplication2.launch(parseCommandLine([]))
+ await focusWindow(window2)
+ assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB])
+ })
+ })
})
describe('before quitting', function () {
@@ -448,7 +488,7 @@ describe('AtomApplication', function () {
}
let channelIdCounter = 0
- function evalInWebContents (webContents, source) {
+ function evalInWebContents (webContents, source, ...args) {
const channelId = 'eval-result-' + channelIdCounter++
return new Promise(function (resolve) {
electron.ipcMain.on(channelId, receiveResult)
diff --git a/spec/main-process/file-recovery-service.test.js b/spec/main-process/file-recovery-service.test.js
index 19c964be7..4821dbc9b 100644
--- a/spec/main-process/file-recovery-service.test.js
+++ b/spec/main-process/file-recovery-service.test.js
@@ -2,19 +2,23 @@
import {dialog} from 'electron'
import FileRecoveryService from '../../src/main-process/file-recovery-service'
-import temp from 'temp'
import fs from 'fs-plus'
import sinon from 'sinon'
import {escapeRegExp} from 'underscore-plus'
+const temp = require('temp').track()
describe("FileRecoveryService", () => {
let recoveryService, recoveryDirectory
beforeEach(() => {
- recoveryDirectory = temp.mkdirSync()
+ recoveryDirectory = temp.mkdirSync('atom-spec-file-recovery')
recoveryService = new FileRecoveryService(recoveryDirectory)
})
+ afterEach(() => {
+ temp.cleanupSync()
+ })
+
describe("when no crash happens during a save", () => {
it("creates a recovery file and deletes it after saving", () => {
const mockWindow = {}
@@ -28,6 +32,8 @@ describe("FileRecoveryService", () => {
recoveryService.didSavePath(mockWindow, filePath)
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
assert.equal(fs.readFileSync(filePath, 'utf8'), "changed")
+
+ fs.removeSync(filePath)
})
it("creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it", () => {
@@ -48,6 +54,8 @@ describe("FileRecoveryService", () => {
recoveryService.didSavePath(anotherMockWindow, filePath)
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
assert.equal(fs.readFileSync(filePath, 'utf8'), "changed")
+
+ fs.removeSync(filePath)
})
})
@@ -64,6 +72,8 @@ describe("FileRecoveryService", () => {
recoveryService.didCrashWindow(mockWindow)
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
assert.equal(fs.readFileSync(filePath, 'utf8'), "some content")
+
+ fs.removeSync(filePath)
})
it("restores the created recovery file when many windows attempt to save the same file and one of them crashes", () => {
@@ -94,13 +104,15 @@ describe("FileRecoveryService", () => {
recoveryService.didCrashWindow(anotherMockWindow)
assert.equal(fs.readFileSync(filePath, 'utf8'), "D")
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
+
+ fs.removeSync(filePath)
})
it("emits a warning when a file can't be recovered", sinon.test(function () {
const mockWindow = {}
const filePath = temp.path()
fs.writeFileSync(filePath, "content")
- fs.chmodSync(filePath, 0444)
+ fs.chmodSync(filePath, 0o444)
let logs = []
this.stub(console, 'log', (message) => logs.push(message))
@@ -113,6 +125,8 @@ describe("FileRecoveryService", () => {
assert.equal(logs.length, 1)
assert.match(logs[0], new RegExp(escapeRegExp(filePath)))
assert.match(logs[0], new RegExp(escapeRegExp(recoveryFiles[0])))
+
+ fs.removeSync(filePath)
}))
})
diff --git a/spec/menu-manager-spec.coffee b/spec/menu-manager-spec.coffee
index 5de5ecf92..2db6f35a0 100644
--- a/spec/menu-manager-spec.coffee
+++ b/spec/menu-manager-spec.coffee
@@ -79,6 +79,7 @@ describe "MenuManager", ->
runs -> expect(menu.sendToBrowserProcess.argsForCall[0][1]['b']).toBeUndefined()
it "omits key bindings that could conflict with AltGraph characters on macOS", ->
+ Object.defineProperty process, 'platform', value: 'darwin'
spyOn(menu, 'sendToBrowserProcess')
menu.add [{label: "A", submenu: [
{label: "B", command: "b"},
diff --git a/spec/module-cache-spec.coffee b/spec/module-cache-spec.coffee
index 4c0a549aa..1627ec776 100644
--- a/spec/module-cache-spec.coffee
+++ b/spec/module-cache-spec.coffee
@@ -1,13 +1,16 @@
path = require 'path'
Module = require 'module'
fs = require 'fs-plus'
-temp = require 'temp'
+temp = require('temp').track()
ModuleCache = require '../src/module-cache'
describe 'ModuleCache', ->
beforeEach ->
spyOn(Module, '_findPath').andCallThrough()
+ afterEach ->
+ temp.cleanupSync()
+
it 'resolves Electron module paths without hitting the filesystem', ->
builtins = ModuleCache.cache.builtins
expect(Object.keys(builtins).length).toBeGreaterThan 0
diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee
index 62e96f81c..6b4429cb1 100644
--- a/spec/package-manager-spec.coffee
+++ b/spec/package-manager-spec.coffee
@@ -1,6 +1,6 @@
path = require 'path'
Package = require '../src/package'
-temp = require 'temp'
+temp = require('temp').track()
fs = require 'fs-plus'
{Disposable} = require 'atom'
{buildKeydownEvent} = require '../src/keymap-extensions'
@@ -17,6 +17,9 @@ describe "PackageManager", ->
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
+ afterEach ->
+ temp.cleanupSync()
+
describe "::getApmPath()", ->
it "returns the path to the apm command", ->
apmPath = path.join(process.resourcesPath, "app", "apm", "bin", "apm")
@@ -24,12 +27,12 @@ describe "PackageManager", ->
apmPath += ".cmd"
expect(atom.packages.getApmPath()).toBe apmPath
- describe "when the core.apmPath setting is set", ->
- beforeEach ->
- atom.config.set("core.apmPath", "/path/to/apm")
+ describe "when the core.apmPath setting is set", ->
+ beforeEach ->
+ atom.config.set("core.apmPath", "/path/to/apm")
- it "returns the value of the core.apmPath config setting", ->
- expect(atom.packages.getApmPath()).toBe "/path/to/apm"
+ it "returns the value of the core.apmPath config setting", ->
+ expect(atom.packages.getApmPath()).toBe "/path/to/apm"
describe "::loadPackages()", ->
beforeEach ->
@@ -54,11 +57,13 @@ describe "PackageManager", ->
expect(pack.metadata.name).toBe "package-with-index"
it "returns the package if it has an invalid keymap", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
pack = atom.packages.loadPackage("package-with-broken-keymap")
expect(pack instanceof Package).toBe true
expect(pack.metadata.name).toBe "package-with-broken-keymap"
it "returns the package if it has an invalid stylesheet", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
pack = atom.packages.loadPackage("package-with-invalid-styles")
expect(pack instanceof Package).toBe true
expect(pack.metadata.name).toBe "package-with-invalid-styles"
@@ -72,6 +77,7 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[1][0].options.packageName).toEqual "package-with-invalid-styles"
it "returns null if the package has an invalid package.json", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(atom.packages.loadPackage("package-with-broken-package-json")).toBeNull()
@@ -104,6 +110,7 @@ describe "PackageManager", ->
describe "when the package is deprecated", ->
it "returns null", ->
+ spyOn(console, 'warn')
expect(atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'packages', 'wordcount'))).toBeNull()
expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.9')).toBe true
expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.0')).toBe true
@@ -389,6 +396,7 @@ describe "PackageManager", ->
expect(mainModule.activate.callCount).toBe 1
it "adds a notification when the activation commands are invalid", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> atom.packages.activatePackage('package-with-invalid-activation-commands')).not.toThrow()
@@ -397,6 +405,7 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-with-invalid-activation-commands"
it "adds a notification when the context menu is invalid", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> atom.packages.activatePackage('package-with-invalid-context-menu')).not.toThrow()
@@ -440,11 +449,9 @@ describe "PackageManager", ->
spyOn(mainModule, 'activate').andCallThrough()
spyOn(Package.prototype, 'requireMainModule').andCallThrough()
- promise = atom.packages.activatePackage('package-with-activation-hooks')
-
it "defers requiring/activating the main module until an triggering of an activation hook occurs", ->
+ promise = atom.packages.activatePackage('package-with-activation-hooks')
expect(Package.prototype.requireMainModule.callCount).toBe 0
-
atom.packages.triggerActivationHook('language-fictitious:grammar-used')
atom.packages.triggerDeferredActivationHooks()
@@ -455,6 +462,7 @@ describe "PackageManager", ->
expect(Package.prototype.requireMainModule.callCount).toBe 1
it "does not double register activation hooks when deactivating and reactivating", ->
+ promise = atom.packages.activatePackage('package-with-activation-hooks')
expect(mainModule.activate.callCount).toBe 0
atom.packages.triggerActivationHook('language-fictitious:grammar-used')
atom.packages.triggerDeferredActivationHooks()
@@ -489,6 +497,17 @@ describe "PackageManager", ->
expect(mainModule.activate.callCount).toBe 1
expect(Package.prototype.requireMainModule.callCount).toBe 1
+ it "activates the package immediately if the activation hook had already been triggered", ->
+ atom.packages.triggerActivationHook('language-fictitious:grammar-used')
+ atom.packages.triggerDeferredActivationHooks()
+ expect(Package.prototype.requireMainModule.callCount).toBe 0
+
+ waitsForPromise ->
+ atom.packages.activatePackage('package-with-activation-hooks')
+
+ runs ->
+ expect(Package.prototype.requireMainModule.callCount).toBe 1
+
describe "when the package has no main module", ->
it "does not throw an exception", ->
spyOn(console, "error")
@@ -533,8 +552,9 @@ describe "PackageManager", ->
waitsFor -> activatedPackage?
runs -> expect(activatedPackage.name).toBe 'package-with-main'
- describe "when the package throws an error while loading", ->
+ describe "when the package's main module throws an error on load", ->
it "adds a notification instead of throwing an exception", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
atom.config.set("core.disabledPackages", [])
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
@@ -543,6 +563,11 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-that-throws-an-exception package")
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-that-throws-an-exception"
+ it "re-throws the exception in test mode", ->
+ atom.config.set("core.disabledPackages", [])
+ addErrorHandler = jasmine.createSpy()
+ expect(-> atom.packages.activatePackage("package-that-throws-an-exception")).toThrow("This package throws an exception")
+
describe "when the package is not found", ->
it "rejects the promise", ->
atom.config.set("core.disabledPackages", [])
@@ -643,7 +668,7 @@ describe "PackageManager", ->
[element, events, userKeymapPath] = []
beforeEach ->
- userKeymapPath = path.join(temp.path(), "user-keymaps.cson")
+ userKeymapPath = path.join(temp.mkdirSync(), "user-keymaps.cson")
spyOn(atom.keymaps, "getUserKeymapPath").andReturn(userKeymapPath)
element = createTestElement('test-1')
@@ -660,6 +685,8 @@ describe "PackageManager", ->
atom.keymaps.watchSubscriptions[userKeymapPath].dispose()
delete atom.keymaps.watchSubscriptions[userKeymapPath]
+ temp.cleanupSync()
+
it "doesn't override user-defined keymaps", ->
fs.writeFileSync userKeymapPath, """
".test-1":
@@ -740,10 +767,6 @@ describe "PackageManager", ->
two = require.resolve("./fixtures/packages/package-with-style-sheets-manifest/styles/2.less")
three = require.resolve("./fixtures/packages/package-with-style-sheets-manifest/styles/3.css")
- one = atom.themes.stringToId(one)
- two = atom.themes.stringToId(two)
- three = atom.themes.stringToId(three)
-
expect(atom.themes.stylesheetElementForId(one)).toBeNull()
expect(atom.themes.stylesheetElementForId(two)).toBeNull()
expect(atom.themes.stylesheetElementForId(three)).toBeNull()
@@ -765,11 +788,6 @@ describe "PackageManager", ->
three = require.resolve("./fixtures/packages/package-with-styles/styles/3.test-context.css")
four = require.resolve("./fixtures/packages/package-with-styles/styles/4.css")
- one = atom.themes.stringToId(one)
- two = atom.themes.stringToId(two)
- three = atom.themes.stringToId(three)
- four = atom.themes.stringToId(four)
-
expect(atom.themes.stylesheetElementForId(one)).toBeNull()
expect(atom.themes.stylesheetElementForId(two)).toBeNull()
expect(atom.themes.stylesheetElementForId(three)).toBeNull()
@@ -887,6 +905,7 @@ describe "PackageManager", ->
describe "::serialize", ->
it "does not serialize packages that threw an error during activation", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
spyOn(console, 'warn')
badPack = null
waitsForPromise ->
@@ -934,6 +953,7 @@ describe "PackageManager", ->
atom.packages.unloadPackages()
it "calls `deactivate` on the package's main module if activate was successful", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
pack = null
waitsForPromise ->
atom.packages.activatePackage("package-with-deactivate").then (p) -> pack = p
@@ -1022,6 +1042,7 @@ describe "PackageManager", ->
describe "::activate()", ->
beforeEach ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
jasmine.snapshotDeprecations()
spyOn(console, 'warn')
atom.packages.loadPackages()
@@ -1036,6 +1057,7 @@ describe "PackageManager", ->
jasmine.restoreDeprecationsSnapshot()
it "sets hasActivatedInitialPackages", ->
+ spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null)
spyOn(atom.packages, 'activatePackages')
expect(atom.packages.hasActivatedInitialPackages()).toBe false
waitsForPromise -> atom.packages.activate()
diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee
index a0e7ffa4d..8119136be 100644
--- a/spec/package-spec.coffee
+++ b/spec/package-spec.coffee
@@ -205,3 +205,26 @@ describe "Package", ->
it "uses the package name defined in package.json", ->
expect(metadata.name).toBe 'package-with-a-totally-different-name'
+
+ describe "the initialize() hook", ->
+ it "gets called when the package is activated", ->
+ packagePath = atom.project.getDirectories()[0].resolve('packages/package-with-deserializers')
+ pack = buildPackage(packagePath)
+ pack.requireMainModule()
+ mainModule = pack.mainModule
+ spyOn(mainModule, 'initialize')
+ expect(mainModule.initialize).not.toHaveBeenCalled()
+ pack.activate()
+ expect(mainModule.initialize).toHaveBeenCalled()
+ expect(mainModule.initialize.callCount).toBe(1)
+
+ it "gets called when a deserializer is used", ->
+ packagePath = atom.project.getDirectories()[0].resolve('packages/package-with-deserializers')
+ pack = buildPackage(packagePath)
+ pack.requireMainModule()
+ mainModule = pack.mainModule
+ spyOn(mainModule, 'initialize')
+ pack.load()
+ expect(mainModule.initialize).not.toHaveBeenCalled()
+ atom.deserializers.deserialize({deserializer: 'Deserializer1', a: 'b'})
+ expect(mainModule.initialize).toHaveBeenCalled()
diff --git a/spec/package-transpilation-registry-spec.js b/spec/package-transpilation-registry-spec.js
index 310570c35..bf8f12475 100644
--- a/spec/package-transpilation-registry-spec.js
+++ b/spec/package-transpilation-registry-spec.js
@@ -44,19 +44,19 @@ describe("PackageTranspilationRegistry", () => {
})
describe('when a file is contained in a path that has custom transpilation', () => {
- const hitPath = '/path/to/lib/file.js'
- const hitPathCoffee = '/path/to/file2.coffee'
- const missPath = '/path/other/file3.js'
- const hitPathMissSubdir = '/path/to/file4.js'
- const hitPathMissExt = '/path/to/file5.ts'
- const nodeModulesFolder = '/path/to/lib/node_modules/file6.js'
- const hitNonStandardExt = '/path/to/file7.omgwhatisthis'
+ const hitPath = path.join('/path/to/lib/file.js')
+ const hitPathCoffee = path.join('/path/to/file2.coffee')
+ const missPath = path.join('/path/other/file3.js')
+ const hitPathMissSubdir =path.join('/path/to/file4.js')
+ const hitPathMissExt = path.join('/path/to/file5.ts')
+ const nodeModulesFolder = path.join('/path/to/lib/node_modules/file6.js')
+ const hitNonStandardExt = path.join('/path/to/file7.omgwhatisthis')
const jsSpec = { glob: "lib/**/*.js", transpiler: './transpiler-js', options: { type: 'js' } }
const coffeeSpec = { glob: "*.coffee", transpiler: './transpiler-coffee', options: { type: 'coffee' } }
const omgSpec = { glob: "*.omgwhatisthis", transpiler: './transpiler-omg', options: { type: 'omg' } }
- const expectedMeta = { name: 'my-package', path: '/path/to', meta: { some: 'metadata' } }
+ const expectedMeta = { name: 'my-package', path: path.join('/path/to'), meta: { some: 'metadata' } }
const jsTranspiler = {
transpile: (sourceCode, filePath, options) => {
@@ -100,7 +100,7 @@ describe("PackageTranspilationRegistry", () => {
throw new Error('bad transpiler path ' + spec.transpiler)
})
- registry.addTranspilerConfigForPath('/path/to', 'my-package', { some: 'metadata' }, [
+ registry.addTranspilerConfigForPath(path.join('/path/to'), 'my-package', { some: 'metadata' }, [
jsSpec, coffeeSpec, omgSpec
])
})
diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee
index d8f74db53..596b1ecea 100644
--- a/spec/pane-spec.coffee
+++ b/spec/pane-spec.coffee
@@ -1080,6 +1080,7 @@ describe "Pane", ->
expect(eventCount).toBe 1
it "only calls terminate handler once when text is modified twice", ->
+ originalText = editor1.getText()
editor1.insertText('Some text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
@@ -1091,6 +1092,10 @@ describe "Pane", ->
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe 1
+ # Reset fixture back to original state
+ editor1.setText(originalText)
+ editor1.save()
+
it "only calls clearPendingItem if there is a pending item to clear", ->
spyOn(pane, "clearPendingItem").andCallThrough()
diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee
index 30415a059..997f6054e 100644
--- a/spec/project-spec.coffee
+++ b/spec/project-spec.coffee
@@ -1,4 +1,4 @@
-temp = require 'temp'
+temp = require('temp').track()
Project = require '../src/project'
fs = require 'fs-plus'
path = require 'path'
@@ -12,6 +12,9 @@ describe "Project", ->
# Wait for project's service consumers to be asynchronously added
waits(1)
+ afterEach ->
+ temp.cleanupSync()
+
describe "serialization", ->
deserializedProject = null
@@ -51,7 +54,7 @@ describe "Project", ->
it "does not deserialize buffers when their path is a directory that exists", ->
- pathToOpen = path.join(temp.mkdirSync(), 'file.txt')
+ pathToOpen = path.join(temp.mkdirSync('atom-spec-project'), 'file.txt')
waitsForPromise ->
atom.workspace.open(pathToOpen)
@@ -64,7 +67,8 @@ describe "Project", ->
expect(deserializedProject.getBuffers().length).toBe 0
it "does not deserialize buffers when their path is inaccessible", ->
- pathToOpen = path.join(temp.mkdirSync(), 'file.txt')
+ return if process.platform is 'win32' # chmod not supported on win32
+ pathToOpen = path.join(temp.mkdirSync('atom-spec-project'), 'file.txt')
fs.writeFileSync(pathToOpen, '')
waitsForPromise ->
@@ -151,7 +155,7 @@ describe "Project", ->
expect(notification.getType()).toBe 'warning'
expect(notification.getDetail()).toBe 'SomeError'
expect(notification.getMessage()).toContain '`resurrect`'
- expect(notification.getMessage()).toContain 'fixtures/dir/a'
+ expect(notification.getMessage()).toContain path.join('fixtures', 'dir', 'a')
describe "when a custom repository-provider service is provided", ->
[fakeRepositoryProvider, fakeRepository] = []
@@ -610,3 +614,7 @@ describe "Project", ->
randomPath = path.join("some", "random", "path")
expect(atom.project.contains(randomPath)).toBe false
+
+ describe ".resolvePath(uri)", ->
+ it "normalizes disk drive letter in passed path on #win32", ->
+ expect(atom.project.resolvePath("d:\\file.txt")).toEqual "D:\\file.txt"
diff --git a/spec/selection-spec.coffee b/spec/selection-spec.coffee
index 1b21d7411..cb070310a 100644
--- a/spec/selection-spec.coffee
+++ b/spec/selection-spec.coffee
@@ -81,8 +81,9 @@ describe "Selection", ->
describe "when the selection is destroyed", ->
it "destroys its marker", ->
selection.setBufferRange([[2, 0], [2, 10]])
+ marker = selection.marker
selection.destroy()
- expect(selection.marker.isDestroyed()).toBeTruthy()
+ expect(marker.isDestroyed()).toBeTruthy()
describe ".insertText(text, options)", ->
it "allows pasting white space only lines when autoIndent is enabled", ->
diff --git a/spec/squirrel-update-spec.coffee b/spec/squirrel-update-spec.coffee
index 083b1f78d..4c7e796ac 100644
--- a/spec/squirrel-update-spec.coffee
+++ b/spec/squirrel-update-spec.coffee
@@ -1,7 +1,7 @@
{EventEmitter} = require 'events'
fs = require 'fs-plus'
path = require 'path'
-temp = require 'temp'
+temp = require('temp').track()
SquirrelUpdate = require '../src/main-process/squirrel-update'
Spawner = require '../src/main-process/spawner'
WinShell = require '../src/main-process/win-shell'
@@ -36,6 +36,9 @@ describe "Windows Squirrel Update", ->
WinShell.folderContextMenu = new FakeShellOption()
WinShell.folderBackgroundContextMenu = new FakeShellOption()
+ afterEach ->
+ temp.cleanupSync()
+
it "quits the app on all squirrel events", ->
app = quit: jasmine.createSpy('quit')
diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js
index 120eb1394..e6b8acae6 100644
--- a/spec/style-manager-spec.js
+++ b/spec/style-manager-spec.js
@@ -1,4 +1,4 @@
-const temp = require('temp')
+const temp = require('temp').track()
const StyleManager = require('../src/style-manager')
describe('StyleManager', () => {
@@ -14,6 +14,10 @@ describe('StyleManager', () => {
styleManager.onDidUpdateStyleElement((event) => { updateEvents.push(event) })
})
+ afterEach(() => {
+ temp.cleanupSync()
+ })
+
describe('::addStyleSheet(source, params)', () => {
it('adds a style sheet based on the given source and returns a disposable allowing it to be removed', () => {
const disposable = styleManager.addStyleSheet('a {color: red}')
@@ -94,6 +98,13 @@ describe('StyleManager', () => {
])
})
+ it('does not transform CSS rules with invalid syntax', () => {
+ styleManager.addStyleSheet("atom-text-editor::shadow .class-1 { font-family: inval'id }")
+ expect(Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map((r) => r.selectorText)).toEqual([
+ 'atom-text-editor::shadow .class-1'
+ ])
+ })
+
it('does not throw exceptions on rules with no selectors', () => {
styleManager.addStyleSheet('@media screen {font-size: 10px}', {context: 'atom-text-editor'})
})
diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js
index 4478df532..b26e64e34 100644
--- a/spec/text-editor-component-spec.js
+++ b/spec/text-editor-component-spec.js
@@ -566,7 +566,7 @@ describe('TextEditorComponent', function () {
editor.setSoftWrapped(true)
runAnimationFrames()
- componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
+ componentNode.style.width = 17 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
runAnimationFrames()
})
@@ -700,13 +700,9 @@ describe('TextEditorComponent', function () {
runAnimationFrames()
let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2))
- expect(line2LeafNodes.length).toBe(3)
- expect(line2LeafNodes[0].textContent).toBe(' ')
+ expect(line2LeafNodes.length).toBe(1)
+ expect(line2LeafNodes[0].textContent).toBe(' ')
expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(false)
- expect(line2LeafNodes[1].textContent).toBe(' ')
- expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(false)
- expect(line2LeafNodes[2].textContent).toBe(' ')
- expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(false)
})
})
@@ -939,13 +935,17 @@ describe('TextEditorComponent', function () {
})
it('pads line numbers to be right-justified based on the maximum number of line number digits', function () {
- editor.getBuffer().setText([1, 2, 3, 4, 5, 6, 7, 8, 9, 10].join('\n'))
+ const input = [];
+ for (let i = 1; i <= 100; ++i) {
+ input.push(i);
+ }
+ editor.getBuffer().setText(input.join('\n'))
runAnimationFrames()
for (let screenRow = 0; screenRow <= 8; ++screenRow) {
- expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe('' + NBSP + (screenRow + 1))
+ expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe('' + NBSP + NBSP + (screenRow + 1))
}
- expect(component.lineNumberNodeForScreenRow(9).textContent).toBe('10')
+ expect(component.lineNumberNodeForScreenRow(99).textContent).toBe('100')
let gutterNode = componentNode.querySelector('.gutter')
let initialGutterWidth = gutterNode.offsetWidth
editor.getBuffer().delete([[1, 0], [2, 0]])
@@ -953,7 +953,7 @@ describe('TextEditorComponent', function () {
runAnimationFrames()
for (let screenRow = 0; screenRow <= 8; ++screenRow) {
- expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe('' + (screenRow + 1))
+ expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe('' + NBSP + (screenRow + 1))
}
expect(gutterNode.offsetWidth).toBeLessThan(initialGutterWidth)
editor.getBuffer().insert([0, 0], '\n\n')
@@ -961,9 +961,9 @@ describe('TextEditorComponent', function () {
runAnimationFrames()
for (let screenRow = 0; screenRow <= 8; ++screenRow) {
- expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe('' + NBSP + (screenRow + 1))
+ expect(component.lineNumberNodeForScreenRow(screenRow).textContent).toBe('' + NBSP + NBSP + (screenRow + 1))
}
- expect(component.lineNumberNodeForScreenRow(9).textContent).toBe('10')
+ expect(component.lineNumberNodeForScreenRow(99).textContent).toBe('100')
expect(gutterNode.offsetWidth).toBe(initialGutterWidth)
})
@@ -1269,10 +1269,10 @@ describe('TextEditorComponent', function () {
let cursor = componentNode.querySelector('.cursor')
let cursorRect = cursor.getBoundingClientRect()
- let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.syntax--source.syntax--js').childNodes[2]
+ let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.syntax--source.syntax--js').childNodes[0]
let range = document.createRange(cursorLocationTextNode)
- range.setStart(cursorLocationTextNode, 0)
- range.setEnd(cursorLocationTextNode, 1)
+ range.setStart(cursorLocationTextNode, 3)
+ range.setEnd(cursorLocationTextNode, 4)
let rangeRect = range.getBoundingClientRect()
expect(cursorRect.left).toBeCloseTo(rangeRect.left, 0)
expect(cursorRect.width).toBeCloseTo(rangeRect.width, 0)
@@ -1347,7 +1347,19 @@ describe('TextEditorComponent', function () {
expect(cursorsNode.classList.contains('blink-off')).toBe(true)
})
- it('does not render cursors that are associated with non-empty selections', function () {
+ it('renders cursors that are associated with empty selections', function () {
+ editor.update({showCursorOnSelection: true})
+ editor.setSelectedScreenRange([[0, 4], [4, 6]])
+ editor.addCursorAtScreenPosition([6, 8])
+ runAnimationFrames()
+ let cursorNodes = componentNode.querySelectorAll('.cursor')
+ expect(cursorNodes.length).toBe(2)
+ expect(cursorNodes[0].style['-webkit-transform']).toBe('translate(' + (Math.round(6 * charWidth)) + 'px, ' + (4 * lineHeightInPixels) + 'px)')
+ expect(cursorNodes[1].style['-webkit-transform']).toBe('translate(' + (Math.round(8 * charWidth)) + 'px, ' + (6 * lineHeightInPixels) + 'px)')
+ })
+
+ it('does not render cursors that are associated with non-empty selections when showCursorOnSelection is false', function () {
+ editor.update({showCursorOnSelection: false})
editor.setSelectedScreenRange([[0, 4], [4, 6]])
editor.addCursorAtScreenPosition([6, 8])
runAnimationFrames()
@@ -1735,11 +1747,13 @@ describe('TextEditorComponent', function () {
})
describe('block decorations rendering', function () {
+ let markerLayer
+
function createBlockDecorationBeforeScreenRow(screenRow, {className}) {
let item = document.createElement("div")
item.className = className || ""
let blockDecoration = editor.decorateMarker(
- editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
+ markerLayer.markScreenPosition([screenRow, 0], {invalidate: "never"}),
{type: "block", item: item, position: "before"}
)
return [item, blockDecoration]
@@ -1749,13 +1763,14 @@ describe('TextEditorComponent', function () {
let item = document.createElement("div")
item.className = className || ""
let blockDecoration = editor.decorateMarker(
- editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
+ markerLayer.markScreenPosition([screenRow, 0], {invalidate: "never"}),
{type: "block", item: item, position: "after"}
)
return [item, blockDecoration]
}
beforeEach(function () {
+ markerLayer = editor.addMarkerLayer()
wrapperNode.style.height = 5 * lineHeightInPixels + 'px'
editor.update({autoHeight: false})
component.measureDimensions()
@@ -2291,7 +2306,9 @@ describe('TextEditorComponent', function () {
let position = wrapperNode.pixelPositionForBufferPosition([0, 26])
let overlay = component.getTopmostDOMNode().querySelector('atom-overlay')
- expect(overlay.style.left).toBe(Math.round(position.left + gutterWidth) + 'px')
+ if (process.platform == 'darwin') { // Result is 359px on win32, expects 375px
+ expect(overlay.style.left).toBe(Math.round(position.left + gutterWidth) + 'px')
+ }
expect(overlay.style.top).toBe(position.top + editor.getLineHeightInPixels() + 'px')
editor.insertText('a')
@@ -3846,6 +3863,40 @@ describe('TextEditorComponent', function () {
})
})
+ describe('when the mousewheel event\'s target is an SVG element inside a block decoration', function () {
+ it('keeps the block decoration on the DOM if it is scrolled off-screen', function () {
+ wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
+ wrapperNode.style.width = 20 * charWidth + 'px'
+ editor.update({autoHeight: false})
+ component.measureDimensions()
+ runAnimationFrames()
+
+ const item = document.createElement('div')
+ const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+ item.appendChild(svgElement)
+ editor.decorateMarker(
+ editor.markScreenPosition([0, 0], {invalidate: "never"}),
+ {type: "block", item: item}
+ )
+
+ runAnimationFrames()
+
+ let wheelEvent = new WheelEvent('mousewheel', {
+ wheelDeltaX: 0,
+ wheelDeltaY: -500
+ })
+ Object.defineProperty(wheelEvent, 'target', {
+ get: function () {
+ return svgElement
+ }
+ })
+ componentNode.dispatchEvent(wheelEvent)
+ runAnimationFrames()
+
+ expect(component.getTopmostDOMNode().contains(item)).toBe(true)
+ })
+ })
+
it('only prevents the default action of the mousewheel event if it actually lead to scrolling', function () {
spyOn(WheelEvent.prototype, 'preventDefault').andCallThrough()
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee
index 7ed4a106f..468adaf04 100644
--- a/spec/text-editor-element-spec.coffee
+++ b/spec/text-editor-element-spec.coffee
@@ -78,6 +78,19 @@ describe "TextEditorElement", ->
jasmine.attachToDOM(element)
expect(element.querySelectorAll('.decoration').length).toBe initialDecorationCount
+ it "can be re-focused using the previous `document.activeElement`", ->
+ editorElement = document.createElement('atom-text-editor')
+ jasmine.attachToDOM(editorElement)
+ editorElement.focus()
+
+ activeElement = document.activeElement
+
+ editorElement.remove()
+ jasmine.attachToDOM(editorElement)
+ activeElement.focus()
+
+ expect(editorElement.hasFocus()).toBe(true)
+
describe "focus and blur handling", ->
it "proxies focus/blur events to/from the hidden input", ->
element = new TextEditorElement
diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee
index 9eb4a15d2..2c4b6dbab 100644
--- a/spec/text-editor-presenter-spec.coffee
+++ b/spec/text-editor-presenter-spec.coffee
@@ -1583,6 +1583,7 @@ describe "TextEditorPresenter", ->
getState(presenter).content.cursors[presenter.model.getCursors()[cursorIndex].id]
it "contains pixelRects for empty selections that are visible on screen", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
@@ -1627,6 +1628,7 @@ describe "TextEditorPresenter", ->
expect(getState(presenter).content.cursors).not.toEqual({})
it "updates when block decorations change", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
@@ -1704,6 +1706,7 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 0)).toEqual {top: 20, left: 10 * 22, width: 10, height: 10}
it "updates when ::explicitHeight changes", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
@@ -1757,6 +1760,7 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[3, 4], [3, 5]]
@@ -2028,6 +2032,27 @@ describe "TextEditorPresenter", ->
expect(stateForHighlightInTile(presenter, highlight, 0)).toBeUndefined()
+ it "handles highlights that extend to the left of the visible area (regression)", ->
+ editor.setSelectedBufferRanges([
+ [[0, 2], [1, 4]],
+ ])
+
+ presenter = buildPresenter(explicitHeight: 20, scrollLeft: 0, tileSize: 2)
+ expectValues stateForSelectionInTile(presenter, 0, 0), {
+ regions: [
+ {top: 0 * 10, height: 10, left: 2 * 10, right: 0 * 10},
+ {top: 1 * 10, height: 10, left: 0 * 10, width: 4 * 10}
+ ]
+ }
+
+ presenter = buildPresenter(explicitHeight: 20, scrollLeft: 20, tileSize: 2)
+ expectValues stateForSelectionInTile(presenter, 0, 0), {
+ regions: [
+ {top: 0 * 10, height: 10, left: 2 * 10, right: 0 * 10},
+ {top: 1 * 10, height: 10, left: 0 * 10, width: 4 * 10}
+ ]
+ }
+
it "updates when ::scrollTop changes", ->
editor.setSelectedBufferRanges([
[[6, 2], [6, 4]],
@@ -2160,7 +2185,7 @@ describe "TextEditorPresenter", ->
editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
waitsForStateToUpdate presenter
- destroyedSelection = null
+ [destroyedSelection, destroyedDecoration] = []
runs ->
expectValues stateForSelectionInTile(presenter, 2, 0), {
regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}]
@@ -2168,10 +2193,11 @@ describe "TextEditorPresenter", ->
# destroying
destroyedSelection = editor.getSelections()[2]
+ destroyedDecoration = destroyedSelection.decoration
waitsForStateToUpdate presenter, -> destroyedSelection.destroy()
runs ->
- expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration)
+ expectUndefinedStateForHighlight(presenter, destroyedDecoration)
it "updates when highlight decorations' properties are updated", ->
marker = editor.markBufferPosition([2, 2])
@@ -2501,13 +2527,13 @@ describe "TextEditorPresenter", ->
pixelPosition: {top: 1 * 10, left: 26 * 10 + gutterWidth - scrollLeft}
}
- expectStateUpdate presenter, -> editor.insertText('a')
+ expectStateUpdate presenter, -> editor.insertText('abc', autoscroll: false)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: windowWidth - itemWidth}
}
- expectStateUpdate presenter, -> editor.insertText('b')
+ expectStateUpdate presenter, -> editor.insertText('d', autoscroll: false)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 1 * 10, left: windowWidth - itemWidth}
@@ -2528,14 +2554,55 @@ describe "TextEditorPresenter", ->
}
expectStateUpdate presenter, ->
- editor.insertNewline()
- presenter.setScrollTop(scrollTop) # I'm fighting the editor
+ editor.insertNewline(autoscroll: false)
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 6 * 10 - scrollTop - itemHeight, left: gutterWidth}
}
+ it "when avoidOverflow is false, does not move horizontally when overflowing the editor's scrollView horizontally", ->
+ scrollLeft = 20
+ marker = editor.markBufferPosition([0, 26], invalidate: 'never')
+ decoration = editor.decorateMarker(marker, {type: 'overlay', item, avoidOverflow: false})
+
+ presenter = buildPresenter({scrollLeft, windowWidth, windowHeight, contentFrameWidth, boundingClientRect, gutterWidth})
+ expectStateUpdate presenter, ->
+ presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
+
+ expectValues stateForOverlay(presenter, decoration), {
+ item: item
+ pixelPosition: {top: 1 * 10, left: 26 * 10 + gutterWidth - scrollLeft}
+ }
+
+ expectStateUpdate presenter, -> editor.insertText('a', autoscroll: false)
+ expectValues stateForOverlay(presenter, decoration), {
+ item: item
+ pixelPosition: {top: 1 * 10, left: 27 * 10 + gutterWidth - scrollLeft}
+ }
+
+ it "when avoidOverflow is false, does not flip vertically when overflowing the editor's scrollView vertically", ->
+ scrollTop = 10
+ marker = editor.markBufferPosition([5, 0], invalidate: 'never')
+ decoration = editor.decorateMarker(marker, {type: 'overlay', item, avoidOverflow: false})
+
+ presenter = buildPresenter({scrollTop, windowWidth, windowHeight, contentFrameWidth, boundingClientRect, gutterWidth})
+ expectStateUpdate presenter, ->
+ presenter.setOverlayDimensions(decoration.id, itemWidth, itemHeight, contentMargin)
+
+ expectValues stateForOverlay(presenter, decoration), {
+ item: item
+ pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth}
+ }
+
+ expectStateUpdate presenter, ->
+ editor.insertNewline(autoscroll: false)
+
+ expectValues stateForOverlay(presenter, decoration), {
+ item: item
+ pixelPosition: {top: 7 * 10 - scrollTop, left: gutterWidth}
+ }
+
describe "when the overlay item has a margin", ->
beforeEach ->
itemWidth = 12 * 10
@@ -2756,7 +2823,7 @@ describe "TextEditorPresenter", ->
expect(getLineNumberGutterState(presenter).content.maxLineNumberDigits).toBe 2
editor.setText("1\n2\n3")
- expect(getLineNumberGutterState(presenter).content.maxLineNumberDigits).toBe 1
+ expect(getLineNumberGutterState(presenter).content.maxLineNumberDigits).toBe 2
describe ".content.tiles", ->
lineNumberStateForScreenRow = (presenter, screenRow) ->
diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js
index 51027e63c..ac5183cab 100644
--- a/spec/text-editor-registry-spec.js
+++ b/spec/text-editor-registry-spec.js
@@ -436,6 +436,19 @@ describe('TextEditorRegistry', function () {
expect(editor.hasAtomicSoftTabs()).toBe(true)
})
+ it('enables or disables cursor on selection visibility based on the config', async function () {
+ editor.update({showCursorOnSelection: true})
+ expect(editor.getShowCursorOnSelection()).toBe(true)
+
+ atom.config.set('editor.showCursorOnSelection', false)
+ registry.maintainConfig(editor)
+ await initialPackageActivation
+ expect(editor.getShowCursorOnSelection()).toBe(false)
+
+ atom.config.set('editor.showCursorOnSelection', true)
+ expect(editor.getShowCursorOnSelection()).toBe(true)
+ })
+
it('enables or disables line numbers based on the config', async function () {
editor.update({showLineNumbers: true})
expect(editor.showLineNumbers).toBe(true)
diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee
index 4dc035f07..911270d16 100644
--- a/spec/text-editor-spec.coffee
+++ b/spec/text-editor-spec.coffee
@@ -89,7 +89,11 @@ describe "TextEditor", ->
describe ".copy()", ->
it "returns a different editor with the same initial state", ->
- editor.update({autoHeight: false, autoWidth: true})
+ expect(editor.getAutoHeight()).toBeFalsy()
+ expect(editor.getAutoWidth()).toBeFalsy()
+ expect(editor.getShowCursorOnSelection()).toBeTruthy()
+
+ editor.update({autoHeight: true, autoWidth: true, showCursorOnSelection: false})
editor.setSelectedBufferRange([[1, 2], [3, 4]])
editor.addSelectionForBufferRange([[5, 6], [7, 8]], reversed: true)
editor.firstVisibleScreenRow = 5
@@ -105,7 +109,8 @@ describe "TextEditor", ->
expect(editor2.getFirstVisibleScreenColumn()).toBe 5
expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor2.getAutoWidth()).toBeTruthy()
- expect(editor2.getAutoHeight()).toBeFalsy()
+ expect(editor2.getAutoHeight()).toBeTruthy()
+ expect(editor2.getShowCursorOnSelection()).toBeFalsy()
# editor2 can now diverge from its origin edit session
editor2.getLastSelection().setBufferRange([[2, 1], [4, 3]])
@@ -299,7 +304,7 @@ describe "TextEditor", ->
it "positions the cursor at the buffer position that corresponds to the given screen position", ->
editor.setCursorScreenPosition([9, 0])
- expect(editor.getCursorBufferPosition()).toEqual [8, 10]
+ expect(editor.getCursorBufferPosition()).toEqual [8, 11]
describe ".moveUp()", ->
it "moves the cursor up", ->
@@ -1186,6 +1191,15 @@ describe "TextEditor", ->
editor.getLastSelection().destroy()
expect(editor.getLastSelection().getBufferRange()).toEqual([[0, 0], [0, 0]])
+ it "doesn't get stuck in a infinite loop when called from ::onDidAddCursor after the last selection has been destroyed (regression)", ->
+ callCount = 0
+ editor.getLastSelection().destroy()
+ editor.onDidAddCursor (cursor) ->
+ callCount++
+ editor.getLastSelection()
+ expect(editor.getLastSelection().getBufferRange()).toEqual([[0, 0], [0, 0]])
+ expect(callCount).toBe(1)
+
describe ".getSelections()", ->
it "creates a new selection at (0, 0) if the last selection has been destroyed", ->
editor.getLastSelection().destroy()
@@ -1858,7 +1872,7 @@ describe "TextEditor", ->
[[4, 25], [4, 29]]
]
for cursor in editor.getCursors()
- expect(cursor.isVisible()).toBeFalsy()
+ expect(cursor.isVisible()).toBeTruthy()
it "skips lines that are too short to create a non-empty selection", ->
editor.setSelectedBufferRange([[3, 31], [3, 38]])
@@ -1991,7 +2005,7 @@ describe "TextEditor", ->
[[2, 37], [2, 40]]
]
for cursor in editor.getCursors()
- expect(cursor.isVisible()).toBeFalsy()
+ expect(cursor.isVisible()).toBeTruthy()
it "skips lines that are too short to create a non-empty selection", ->
editor.setSelectedBufferRange([[6, 31], [6, 38]])
@@ -2161,6 +2175,54 @@ describe "TextEditor", ->
editor.setCursorScreenPosition([3, 3])
expect(selection.isEmpty()).toBeTruthy()
+ describe "cursor visibility while there is a selection", ->
+ describe "when showCursorOnSelection is true", ->
+ it "is visible while there is no selection", ->
+ expect(selection.isEmpty()).toBeTruthy()
+ expect(editor.getShowCursorOnSelection()).toBeTruthy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+
+ it "is visible while there is a selection", ->
+ expect(selection.isEmpty()).toBeTruthy()
+ editor.setSelectedBufferRange([[1, 2], [1, 5]])
+ expect(selection.isEmpty()).toBeFalsy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+
+ it "is visible while there are multiple selections", ->
+ expect(editor.getSelections().length).toBe 1
+ editor.setSelectedBufferRanges([[[1, 2], [1, 5]], [[2, 2], [2, 5]]])
+ expect(editor.getSelections().length).toBe 2
+ expect(editor.getCursors().length).toBe 2
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+ expect(editor.getCursors()[1].isVisible()).toBeTruthy()
+
+ describe "when showCursorOnSelection is false", ->
+ it "is visible while there is no selection", ->
+ editor.update({showCursorOnSelection: false})
+ expect(selection.isEmpty()).toBeTruthy()
+ expect(editor.getShowCursorOnSelection()).toBeFalsy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+
+ it "is not visible while there is a selection", ->
+ editor.update({showCursorOnSelection: false})
+ expect(selection.isEmpty()).toBeTruthy()
+ editor.setSelectedBufferRange([[1, 2], [1, 5]])
+ expect(selection.isEmpty()).toBeFalsy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeFalsy()
+
+ it "is not visible while there are multiple selections", ->
+ editor.update({showCursorOnSelection: false})
+ expect(editor.getSelections().length).toBe 1
+ editor.setSelectedBufferRanges([[[1, 2], [1, 5]], [[2, 2], [2, 5]]])
+ expect(editor.getSelections().length).toBe 2
+ expect(editor.getCursors().length).toBe 2
+ expect(editor.getCursors()[0].isVisible()).toBeFalsy()
+ expect(editor.getCursors()[1].isVisible()).toBeFalsy()
+
it "does not share selections between different edit sessions for the same buffer", ->
editor2 = null
waitsForPromise ->
@@ -4325,15 +4387,10 @@ describe "TextEditor", ->
expect(editor.getLastSelection().isEmpty()).toBeTruthy()
it "does not explode if the current language mode has no comment regex", ->
- editor.destroy()
-
- waitsForPromise ->
- atom.workspace.open(null, autoIndent: false).then (o) -> editor = o
-
- runs ->
- editor.setSelectedBufferRange([[4, 5], [4, 5]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
+ editor = new TextEditor(buffer: new TextBuffer(text: 'hello'))
+ editor.setSelectedBufferRange([[0, 0], [0, 5]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe "hello"
it "does nothing for empty lines and null grammar", ->
runs ->
@@ -4867,15 +4924,13 @@ describe "TextEditor", ->
expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 2]]
describe '.setTabLength(tabLength)', ->
- it 'retokenizes the editor with the given tab length', ->
+ it 'clips atomic soft tabs to the given tab length', ->
expect(editor.getTabLength()).toBe 2
- leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> 'leading-whitespace' in token.scopes
- expect(leadingWhitespaceTokens.length).toBe(3)
+ expect(editor.clipScreenPosition([5, 1], clipDirection: 'forward')).toEqual([5, 2])
editor.setTabLength(6)
expect(editor.getTabLength()).toBe 6
- leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> 'leading-whitespace' in token.scopes
- expect(leadingWhitespaceTokens.length).toBe(1)
+ expect(editor.clipScreenPosition([5, 1], clipDirection: 'forward')).toEqual([5, 6])
changeHandler = jasmine.createSpy('changeHandler')
editor.onDidChange(changeHandler)
@@ -5058,11 +5113,13 @@ describe "TextEditor", ->
describe ".destroy()", ->
it "destroys marker layers associated with the text editor", ->
+ buffer.retain()
selectionsMarkerLayerId = editor.selectionsMarkerLayer.id
foldsMarkerLayerId = editor.displayLayer.foldsMarkerLayer.id
editor.destroy()
expect(buffer.getMarkerLayer(selectionsMarkerLayerId)).toBeUndefined()
expect(buffer.getMarkerLayer(foldsMarkerLayerId)).toBeUndefined()
+ buffer.release()
it "notifies ::onDidDestroy observers when the editor is destroyed", ->
destroyObserverCalled = false
@@ -5071,6 +5128,23 @@ describe "TextEditor", ->
editor.destroy()
expect(destroyObserverCalled).toBe true
+ it "does not blow up when query methods are called afterward", ->
+ editor.destroy()
+ editor.getGrammar()
+ editor.getLastCursor()
+ editor.lineTextForBufferRow(0)
+
+ it "emits the destroy event after destroying the editor's buffer", ->
+ events = []
+ editor.getBuffer().onDidDestroy ->
+ expect(editor.isDestroyed()).toBe(true)
+ events.push('buffer-destroyed')
+ editor.onDidDestroy ->
+ expect(buffer.isDestroyed()).toBe(true)
+ events.push('editor-destroyed')
+ editor.destroy()
+ expect(events).toEqual(['buffer-destroyed', 'editor-destroyed'])
+
describe ".joinLines()", ->
describe "when no text is selected", ->
describe "when the line below isn't empty", ->
@@ -5148,7 +5222,7 @@ describe "TextEditor", ->
expect(editor.lineTextForScreenRow(7)).toBe " while(items.length > 0) {" + editor.displayLayer.foldCharacter
expect(editor.lineTextForScreenRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
- it "duplicates all folded lines for empty selections on folded lines", ->
+ it "duplicates all folded lines for empty selections on lines containing folds", ->
editor.foldBufferRow(4)
editor.setCursorBufferPosition([4, 0])
@@ -5179,6 +5253,38 @@ describe "TextEditor", ->
"""
expect(editor.getSelectedBufferRange()).toEqual [[13, 0], [14, 2]]
+ it "only duplicates lines containing multiple selections once", ->
+ editor.setText("""
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd
+ """)
+ editor.setSelectedBufferRanges([
+ [[0, 1], [0, 2]],
+ [[0, 3], [0, 4]],
+ [[2, 1], [2, 2]],
+ [[2, 3], [3, 1]],
+ [[3, 3], [3, 4]],
+ ])
+ editor.duplicateLines()
+ expect(editor.getText()).toBe("""
+ aaaaaa
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd
+ cccccc
+ dddddd
+ """)
+ expect(editor.getSelectedBufferRanges()).toEqual([
+ [[1, 1], [1, 2]],
+ [[1, 3], [1, 4]],
+ [[5, 1], [5, 2]],
+ [[5, 3], [6, 1]],
+ [[6, 3], [6, 4]],
+ ])
+
describe ".shouldPromptToSave()", ->
it "returns true when buffer changed", ->
jasmine.unspy(editor, 'shouldPromptToSave')
diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee
index 68693dddc..795f1b43e 100644
--- a/spec/theme-manager-spec.coffee
+++ b/spec/theme-manager-spec.coffee
@@ -1,13 +1,15 @@
path = require 'path'
fs = require 'fs-plus'
-temp = require 'temp'
+temp = require('temp').track()
describe "atom.themes", ->
beforeEach ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
spyOn(console, 'warn')
afterEach ->
atom.themes.deactivateThemes()
+ temp.cleanupSync()
describe "theme getters and setters", ->
beforeEach ->
@@ -170,7 +172,7 @@ describe "atom.themes", ->
expect(styleElementAddedHandler).toHaveBeenCalled()
element = document.querySelector('head style[source-path*="css.css"]')
- expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(cssPath)
+ expect(element.getAttribute('source-path')).toEqualPath cssPath
expect(element.textContent).toBe fs.readFileSync(cssPath, 'utf8')
# doesn't append twice
@@ -189,7 +191,7 @@ describe "atom.themes", ->
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
element = document.querySelector('head style[source-path*="sample.less"]')
- expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(lessPath)
+ expect(element.getAttribute('source-path')).toEqualPath lessPath
expect(element.textContent).toBe """
#header {
color: #4d926f;
@@ -208,9 +210,9 @@ describe "atom.themes", ->
it "supports requiring css and less stylesheets without an explicit extension", ->
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'css')
- expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
+ expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toEqualPath atom.project.getDirectories()[0]?.resolve('css.css')
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
- expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
+ expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toEqualPath atom.project.getDirectories()[0]?.resolve('sample.less')
document.querySelector('head style[source-path*="css.css"]').remove()
document.querySelector('head style[source-path*="sample.less"]').remove()
diff --git a/spec/tooltip-manager-spec.coffee b/spec/tooltip-manager-spec.coffee
index a7e8ccb1f..35e563dae 100644
--- a/spec/tooltip-manager-spec.coffee
+++ b/spec/tooltip-manager-spec.coffee
@@ -185,8 +185,29 @@ describe "TooltipManager", ->
describe "when the window is resized", ->
it "hides the tooltips", ->
- manager.add element, title: "Title"
+ disposable = manager.add element, title: "Title"
hover element, ->
expect(document.body.querySelector(".tooltip")).not.toBeNull()
window.dispatchEvent(new CustomEvent('resize'))
expect(document.body.querySelector(".tooltip")).toBeNull()
+ disposable.dispose()
+
+ describe "findTooltips", ->
+ it "adds and remove tooltips correctly", ->
+ expect(manager.findTooltips(element).length).toBe(0)
+ disposable1 = manager.add element, title: "elem1"
+ expect(manager.findTooltips(element).length).toBe(1)
+ disposable2 = manager.add element, title: "elem2"
+ expect(manager.findTooltips(element).length).toBe(2)
+ disposable1.dispose()
+ expect(manager.findTooltips(element).length).toBe(1)
+ disposable2.dispose()
+ expect(manager.findTooltips(element).length).toBe(0)
+
+ it "lets us hide tooltips programatically", ->
+ disposable = manager.add element, title: "Title"
+ hover element, ->
+ expect(document.body.querySelector(".tooltip")).not.toBeNull()
+ manager.findTooltips(element)[0].hide()
+ expect(document.body.querySelector(".tooltip")).toBeNull()
+ disposable.dispose()
diff --git a/spec/update-process-env-spec.js b/spec/update-process-env-spec.js
index 73f0a1988..f730ae632 100644
--- a/spec/update-process-env-spec.js
+++ b/spec/update-process-env-spec.js
@@ -3,12 +3,12 @@
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
import path from 'path'
-import temp from 'temp'
import childProcess from 'child_process'
import {updateProcessEnv, shouldGetEnvFromShell} from '../src/update-process-env'
import dedent from 'dedent'
import {EventEmitter} from 'events'
import mockSpawn from 'mock-spawn'
+const temp = require('temp').track()
describe('updateProcessEnv(launchEnv)', function () {
let originalProcessEnv, originalProcessPlatform, originalSpawn, spawn
@@ -28,6 +28,7 @@ describe('updateProcessEnv(launchEnv)', function () {
}
process.env = originalProcessEnv
process.platform = originalProcessPlatform
+ temp.cleanupSync()
})
describe('when the launch environment appears to come from a shell', function () {
@@ -150,8 +151,10 @@ describe('updateProcessEnv(launchEnv)', function () {
})
describe('when the launch environment does not come from a shell', function () {
- describe('on osx', function () {
+ describe('on macOS', function () {
it('updates process.env to match the environment in the user\'s login shell', async function () {
+ if (process.platform === 'win32') return // TestsThatFailOnWin32
+
process.platform = 'darwin'
process.env.SHELL = '/my/custom/bash'
spawn.setDefault(spawn.simple(0, dedent`
@@ -176,6 +179,8 @@ describe('updateProcessEnv(launchEnv)', function () {
describe('on linux', function () {
it('updates process.env to match the environment in the user\'s login shell', async function () {
+ if (process.platform === 'win32') return // TestsThatFailOnWin32
+
process.platform = 'linux'
process.env.SHELL = '/my/custom/bash'
spawn.setDefault(spawn.simple(0, dedent`
@@ -212,6 +217,8 @@ describe('updateProcessEnv(launchEnv)', function () {
describe('shouldGetEnvFromShell()', function () {
it('indicates when the environment should be fetched from the shell', function () {
+ if (process.platform === 'win32') return // TestsThatFailOnWin32
+
process.platform = 'darwin'
expect(shouldGetEnvFromShell({SHELL: '/bin/sh'})).toBe(true)
expect(shouldGetEnvFromShell({SHELL: '/usr/local/bin/sh'})).toBe(true)
diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee
index 8e08fec35..e9a7894c3 100644
--- a/spec/window-event-handler-spec.coffee
+++ b/spec/window-event-handler-spec.coffee
@@ -23,6 +23,7 @@ describe "WindowEventHandler", ->
describe "when the window is loaded", ->
it "doesn't have .is-blurred on the body tag", ->
+ return if process.platform is 'win32' #Win32TestFailures - can not steal focus
expect(document.body.className).not.toMatch("is-blurred")
describe "when the window is blurred", ->
diff --git a/spec/workspace-element-spec.coffee b/spec/workspace-element-spec.coffee
index 9ffa3621a..a741dbbd4 100644
--- a/spec/workspace-element-spec.coffee
+++ b/spec/workspace-element-spec.coffee
@@ -4,6 +4,9 @@ temp = require('temp').track()
{Disposable} = require 'event-kit'
describe "WorkspaceElement", ->
+ afterEach ->
+ temp.cleanupSync()
+
describe "when the workspace element is focused", ->
it "transfers focus to the active pane", ->
workspaceElement = atom.views.getView(atom.workspace)
@@ -71,14 +74,14 @@ describe "WorkspaceElement", ->
atom.config.set('editor.fontSize', 12)
# Zoom out
- editorElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: -10,
ctrlKey: true
}))
expect(atom.config.get('editor.fontSize')).toBe(11)
# Zoom in
- editorElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: 10,
ctrlKey: true
}))
@@ -92,13 +95,13 @@ describe "WorkspaceElement", ->
expect(atom.config.get('editor.fontSize')).toBe(12)
# No ctrl key
- workspaceElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: 10,
}))
expect(atom.config.get('editor.fontSize')).toBe(12)
atom.config.set('editor.zoomFontWhenCtrlScrolling', false)
- editorElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: 10,
ctrlKey: true
}))
diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee
index 61f1e8266..153cc5dc3 100644
--- a/spec/workspace-spec.coffee
+++ b/spec/workspace-spec.coffee
@@ -1,5 +1,5 @@
path = require 'path'
-temp = require 'temp'
+temp = require('temp').track()
TextEditor = require '../src/text-editor'
Workspace = require '../src/workspace'
Project = require '../src/project'
@@ -19,6 +19,9 @@ describe "Workspace", ->
atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')])
waits(1)
+ afterEach ->
+ temp.cleanupSync()
+
describe "serialization", ->
simulateReload = ->
workspaceState = atom.workspace.serialize()
@@ -489,6 +492,7 @@ describe "Workspace", ->
expect(item).toEqual {bar: "bar://baz"}
it "adds the file to the application's recent documents list", ->
+ return unless process.platform is 'darwin' # Feature only supported on macOS
spyOn(atom.applicationDelegate, 'addRecentDocument')
waitsForPromise ->
@@ -881,12 +885,48 @@ describe "Workspace", ->
expect(coffeePackage.loadGrammarsSync.callCount).toBe 1
describe "document.title", ->
- describe "when the project has no path", ->
- it "sets the title to 'untitled'", ->
- atom.project.setPaths([])
- expect(document.title).toMatch ///^untitled///
+ describe "when there is no item open", ->
+ it "sets the title to the project path", ->
+ expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))
- describe "when the project has a path", ->
+ it "sets the title to 'untitled' if there is no project path", ->
+ atom.project.setPaths([])
+ expect(document.title).toMatch /^untitled/
+
+ describe "when the active pane item's path is not inside a project path", ->
+ beforeEach ->
+ waitsForPromise ->
+ atom.workspace.open('b').then ->
+ atom.project.setPaths([])
+
+ it "sets the title to the pane item's title plus the item's path", ->
+ item = atom.workspace.getActivePaneItem()
+ pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath())))
+ expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
+
+ describe "when the title of the active pane item changes", ->
+ it "updates the window title based on the item's new title", ->
+ editor = atom.workspace.getActivePaneItem()
+ editor.buffer.setPath(path.join(temp.dir, 'hi'))
+ pathEscaped = fs.tildify(escapeStringRegex(path.dirname(editor.getPath())))
+ expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}///
+
+ describe "when the active pane's item changes", ->
+ it "updates the title to the new item's title plus the project path", ->
+ atom.workspace.getActivePane().activateNextItem()
+ item = atom.workspace.getActivePaneItem()
+ pathEscaped = fs.tildify(escapeStringRegex(path.dirname(item.getPath())))
+ expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
+
+ describe "when an inactive pane's item changes", ->
+ it "does not update the title", ->
+ pane = atom.workspace.getActivePane()
+ pane.splitRight()
+ initialTitle = document.title
+ pane.activateNextItem()
+ expect(document.title).toBe initialTitle
+
+ describe "when the active pane item is inside a project path", ->
beforeEach ->
waitsForPromise ->
atom.workspace.open('b')
@@ -900,7 +940,7 @@ describe "Workspace", ->
describe "when the title of the active pane item changes", ->
it "updates the window title based on the item's new title", ->
editor = atom.workspace.getActivePaneItem()
- editor.buffer.setPath(path.join(temp.dir, 'hi'))
+ editor.buffer.setPath(path.join(atom.project.getPaths()[0], 'hi'))
pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0]))
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}///
@@ -912,11 +952,10 @@ describe "Workspace", ->
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the last pane item is removed", ->
- it "updates the title to contain the project's path", ->
+ it "updates the title to the project's first path", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
- pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0]))
- expect(document.title).toMatch ///^#{pathEscaped}///
+ expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))
describe "when an inactive pane's item changes", ->
it "does not update the title", ->
@@ -1139,6 +1178,7 @@ describe "Workspace", ->
range: [[2, 6], [2, 11]]
it "works on evil filenames", ->
+ atom.config.set('core.excludeVcsIgnoredPaths', false)
platform.generateEvilFiles()
atom.project.setPaths([path.join(__dirname, 'fixtures', 'evil-files')])
paths = []
@@ -1224,7 +1264,7 @@ describe "Workspace", ->
expect(matches.length).toBe 1
it "includes files and folders that begin with a '.'", ->
- projectPath = temp.mkdirSync()
+ projectPath = temp.mkdirSync('atom-spec-workspace')
filePath = path.join(projectPath, '.text')
fs.writeFileSync(filePath, 'match this')
atom.project.setPaths([projectPath])
diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee
index 72b0ef655..766ba7aa8 100644
--- a/src/application-delegate.coffee
+++ b/src/application-delegate.coffee
@@ -2,10 +2,12 @@ _ = require 'underscore-plus'
{screen, ipcRenderer, remote, shell, webFrame} = require 'electron'
ipcHelpers = require './ipc-helpers'
{Disposable} = require 'event-kit'
-{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
+getWindowLoadSettings = require './get-window-load-settings'
module.exports =
class ApplicationDelegate
+ getWindowLoadSettings: -> getWindowLoadSettings()
+
open: (params) ->
ipcRenderer.send('open', params)
@@ -109,9 +111,7 @@ class ApplicationDelegate
ipcRenderer.send("add-recent-document", filename)
setRepresentedDirectoryPaths: (paths) ->
- loadSettings = getWindowLoadSettings()
- loadSettings['initialPaths'] = paths
- setWindowLoadSettings(loadSettings)
+ ipcHelpers.call('window-method', 'setRepresentedDirectoryPaths', paths)
setAutoHideWindowMenuBar: (autoHide) ->
ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide)
@@ -148,13 +148,9 @@ class ApplicationDelegate
showMessageDialog: (params) ->
showSaveDialog: (params) ->
- if _.isString(params)
- params = defaultPath: params
- else
- params = _.clone(params)
- params.title ?= 'Save File'
- params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
- remote.dialog.showSaveDialog remote.getCurrentWindow(), params
+ if typeof params is 'string'
+ params = {defaultPath: params}
+ @getCurrentWindow().showSaveDialog(params)
playBeepSound: ->
shell.beep()
diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee
index 09c85afca..86753a999 100644
--- a/src/atom-environment.coffee
+++ b/src/atom-environment.coffee
@@ -11,7 +11,6 @@ Model = require './model'
WindowEventHandler = require './window-event-handler'
StateStore = require './state-store'
StorageFolder = require './storage-folder'
-{getWindowLoadSettings} = require './window-load-settings-helpers'
registerDefaultCommands = require './register-default-commands'
{updateProcessEnv} = require './update-process-env'
@@ -240,16 +239,6 @@ class AtomEnvironment extends Model
new ReopenProjectMenuManager({@menu, @commands, @history, @config, open: (paths) => @open(pathsToOpen: paths)})
- checkPortableHomeWritable = =>
- responseChannel = "check-portable-home-writable-response"
- ipcRenderer.on responseChannel, (event, response) ->
- ipcRenderer.removeAllListeners(responseChannel)
- @notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
- @disposables.add new Disposable -> ipcRenderer.removeAllListeners(responseChannel)
- ipcRenderer.send('check-portable-home-writable', responseChannel)
-
- checkPortableHomeWritable()
-
attachSaveStateListeners: ->
saveState = _.debounce((=>
window.requestIdleCallback => @saveState({isUnloading: false}) unless @unloaded
@@ -294,13 +283,13 @@ class AtomEnvironment extends Model
@workspace.addOpener (uri) =>
switch uri
when 'atom://.atom/stylesheet'
- @workspace.open(@styles.getUserStyleSheetPath())
+ @workspace.openTextFile(@styles.getUserStyleSheetPath())
when 'atom://.atom/keymap'
- @workspace.open(@keymaps.getUserKeymapPath())
+ @workspace.openTextFile(@keymaps.getUserKeymapPath())
when 'atom://.atom/config'
- @workspace.open(@config.getUserConfigPath())
+ @workspace.openTextFile(@config.getUserConfigPath())
when 'atom://.atom/init-script'
- @workspace.open(@getUserInitScriptPath())
+ @workspace.openTextFile(@getUserInitScriptPath())
registerDefaultTargetForKeymaps: ->
@keymaps.defaultTarget = @views.getView(@workspace)
@@ -468,7 +457,7 @@ class AtomEnvironment extends Model
#
# Returns an {Object} containing all the load setting key/value pairs.
getLoadSettings: ->
- getWindowLoadSettings()
+ @applicationDelegate.getWindowLoadSettings()
###
Section: Managing The Atom Window
@@ -831,12 +820,17 @@ class AtomEnvironment extends Model
Section: Private
###
- assert: (condition, message, callback) ->
+ assert: (condition, message, callbackOrMetadata) ->
return true if condition
error = new Error("Assertion failed: #{message}")
Error.captureStackTrace(error, @assert)
- callback?(error)
+
+ if callbackOrMetadata?
+ if typeof callbackOrMetadata is 'function'
+ callbackOrMetadata?(error)
+ else
+ error.metadata = callbackOrMetadata
@emitter.emit 'did-fail-assertion', error
diff --git a/src/atom-paths.js b/src/atom-paths.js
new file mode 100644
index 000000000..39a768e91
--- /dev/null
+++ b/src/atom-paths.js
@@ -0,0 +1,62 @@
+/** @babel */
+
+const fs = require('fs-plus')
+const path = require('path')
+
+const hasWriteAccess = (dir) => {
+ const testFilePath = path.join(dir, 'write.test')
+ try {
+ fs.writeFileSync(testFilePath, new Date().toISOString(), { flag: 'w+' })
+ fs.unlinkSync(testFilePath)
+ return true
+ } catch (err) {
+ return false
+ }
+}
+
+const getAppDirectory = () => {
+ switch (process.platform) {
+ case 'darwin':
+ return process.execPath.substring(0, process.execPath.indexOf('.app') + 4)
+ case 'linux':
+ case 'win32':
+ return path.join(process.execPath, '..')
+ }
+}
+
+module.exports = {
+ setAtomHome: (homePath) => {
+ // When a read-writeable .atom folder exists above app use that
+ const portableHomePath = path.join(getAppDirectory(), '..', '.atom')
+ if (fs.existsSync(portableHomePath)) {
+ if (hasWriteAccess(portableHomePath)) {
+ process.env.ATOM_HOME = portableHomePath
+ } else {
+ // A path exists so it was intended to be used but we didn't have rights, so warn.
+ console.log(`Insufficient permission to portable Atom home "${portableHomePath}".`)
+ }
+ }
+
+ // Check ATOM_HOME environment variable next
+ if (process.env.ATOM_HOME !== undefined) {
+ return
+ }
+
+ // Fall back to default .atom folder in users home folder
+ process.env.ATOM_HOME = path.join(homePath, '.atom')
+ },
+
+ setUserData: (app) => {
+ const electronUserDataPath = path.join(process.env.ATOM_HOME, 'electronUserData')
+ if (fs.existsSync(electronUserDataPath)) {
+ if (hasWriteAccess(electronUserDataPath)) {
+ app.setPath('userData', electronUserDataPath)
+ } else {
+ // A path exists so it was intended to be used but we didn't have rights, so warn.
+ console.log(`Insufficient permission to Electron user data "${electronUserDataPath}".`)
+ }
+ }
+ },
+
+ getAppDirectory: getAppDirectory
+}
diff --git a/src/babel.js b/src/babel.js
index a944f2e8c..d72b29ffd 100644
--- a/src/babel.js
+++ b/src/babel.js
@@ -6,6 +6,7 @@ var defaultOptions = require('../static/babelrc.json')
var babel = null
var babelVersionDirectory = null
+var options = null
var PREFIXES = [
'/** @babel */',
@@ -47,16 +48,27 @@ exports.compile = function (sourceCode, filePath) {
var noop = function () {}
Logger.prototype.debug = noop
Logger.prototype.verbose = noop
+
+ options = {ast: false, babelrc: false}
+ for (var key in defaultOptions) {
+ if (key === 'plugins') {
+ const plugins = []
+ for (const [pluginName, pluginOptions] of defaultOptions[key]) {
+ plugins.push([require.resolve(`babel-plugin-${pluginName}`), pluginOptions])
+ }
+ options[key] = plugins
+ } else {
+ options[key] = defaultOptions[key]
+ }
+ }
}
if (process.platform === 'win32') {
filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/')
}
- var options = {filename: filePath}
- for (var key in defaultOptions) {
- options[key] = defaultOptions[key]
- }
+ options.filename = filePath
+
return babel.transform(sourceCode, options).code
}
diff --git a/src/buffered-process.js b/src/buffered-process.js
index 4cc7d40d5..339bf05c5 100644
--- a/src/buffered-process.js
+++ b/src/buffered-process.js
@@ -46,43 +46,61 @@ export default class BufferedProcess {
// * `exit` {Function} (optional) The callback which receives a single
// argument containing the exit status.
// * `code` {Number}
- constructor ({command, args, options = {}, stdout, stderr, exit} = {}) {
+ // * `autoStart` {Boolean} (optional) Whether the command will automatically start
+ // when this BufferedProcess is created. Defaults to true. When set to false you
+ // must call the `start` method to start the process.
+ constructor ({command, args, options = {}, stdout, stderr, exit, autoStart = true} = {}) {
this.emitter = new Emitter()
this.command = command
+ this.args = args
+ this.options = options
+ this.stdout = stdout
+ this.stderr = stderr
+ this.exit = exit
+ if (autoStart === true) {
+ this.start()
+ }
+ this.killed = false
+ }
+
+ start () {
+ if (this.started === true) return
+
+ this.started = true
// Related to joyent/node#2318
- if (process.platform === 'win32' && !options.shell) {
- let cmdArgs = []
-
- // Quote all arguments and escapes inner quotes
- if (args) {
- cmdArgs = args.filter((arg) => arg != null)
- .map((arg) => {
- if (this.isExplorerCommand(command) && /^\/[a-zA-Z]+,.*$/.test(arg)) {
- // Don't wrap /root,C:\folder style arguments to explorer calls in
- // quotes since they will not be interpreted correctly if they are
- return arg
- } else {
- return `\"${arg.toString().replace(/"/g, '\\"')}\"`
- }
- })
- }
-
- if (/\s/.test(command)) {
- cmdArgs.unshift(`\"${command}\"`)
- } else {
- cmdArgs.unshift(command)
- }
-
- cmdArgs = ['/s', '/d', '/c', `\"${cmdArgs.join(' ')}\"`]
- const cmdOptions = _.clone(options)
- cmdOptions.windowsVerbatimArguments = true
- this.spawn(this.getCmdPath(), cmdArgs, cmdOptions)
+ if (process.platform === 'win32' && this.options.shell === undefined) {
+ this.spawnWithEscapedWindowsArgs(this.command, this.args, this.options)
} else {
- this.spawn(command, args, options)
+ this.spawn(this.command, this.args, this.options)
+ }
+ this.handleEvents(this.stdout, this.stderr, this.exit)
+ }
+
+ // Windows has a bunch of special rules that node still doesn't take care of for you
+ spawnWithEscapedWindowsArgs (command, args, options) {
+ let cmdArgs = []
+ // Quote all arguments and escapes inner quotes
+ if (args) {
+ cmdArgs = args.filter((arg) => arg != null)
+ .map((arg) => {
+ if (this.isExplorerCommand(command) && /^\/[a-zA-Z]+,.*$/.test(arg)) {
+ // Don't wrap /root,C:\folder style arguments to explorer calls in
+ // quotes since they will not be interpreted correctly if they are
+ return arg
+ } else {
+ // Escape double quotes by putting a backslash in front of them
+ return `\"${arg.toString().replace(/"/g, '\\"')}\"`
+ }
+ })
}
- this.killed = false
- this.handleEvents(stdout, stderr, exit)
+ // The command itself is quoted if it contains spaces, &, ^, | or # chars
+ cmdArgs.unshift(/\s|&|\^|\(|\)|\||#/.test(command) ? `\"${command}\"` : command)
+
+ const cmdOptions = _.clone(options)
+ cmdOptions.windowsVerbatimArguments = true
+
+ this.spawn(this.getCmdPath(), ['/s', '/d', '/c', `\"${cmdArgs.join(' ')}\"`], cmdOptions)
}
/*
diff --git a/src/config-schema.js b/src/config-schema.js
index 63be1273f..9bfa95a1a 100644
--- a/src/config-schema.js
+++ b/src/config-schema.js
@@ -12,7 +12,7 @@ const configSchema = {
properties: {
ignoredNames: {
type: 'array',
- default: ['.git', '.hg', '.svn', '.DS_Store', '._*', 'Thumbs.db'],
+ default: ['.git', '.hg', '.svn', '.DS_Store', '._*', 'Thumbs.db', 'desktop.ini'],
items: {
type: 'string'
},
@@ -68,6 +68,12 @@ const configSchema = {
default: true,
description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.'
},
+ closeDeletedFileTabs: {
+ type: 'boolean',
+ default: false,
+ title: 'Close Deleted File Tabs',
+ description: 'Close corresponding editors when a file is deleted outside Atom.'
+ },
destroyEmptyPanes: {
type: 'boolean',
default: true,
@@ -84,45 +90,175 @@ const configSchema = {
type: 'string',
default: 'utf8',
enum: [
- 'cp437',
- 'eucjp',
- 'euckr',
- 'gbk',
- 'iso88591',
- 'iso885910',
- 'iso885913',
- 'iso885914',
- 'iso885915',
- 'iso885916',
- 'iso88592',
- 'iso88593',
- 'iso88594',
- 'iso88595',
- 'iso88596',
- 'iso88597',
- 'iso88597',
- 'iso88598',
- 'koi8r',
- 'koi8u',
- 'macroman',
- 'shiftjis',
- 'utf16be',
- 'utf16le',
- 'utf8',
- 'windows1250',
- 'windows1251',
- 'windows1252',
- 'windows1253',
- 'windows1254',
- 'windows1255',
- 'windows1256',
- 'windows1257',
- 'windows1258',
- 'windows866'
+ {
+ value: 'iso88596',
+ description: 'Arabic (ISO 8859-6)'
+ },
+ {
+ value: 'windows1256',
+ description: 'Arabic (Windows 1256)'
+ },
+ {
+ value: 'iso88594',
+ description: 'Baltic (ISO 8859-4)'
+ },
+ {
+ value: 'windows1257',
+ description: 'Baltic (Windows 1257)'
+ },
+ {
+ value: 'iso885914',
+ description: 'Celtic (ISO 8859-14)'
+ },
+ {
+ value: 'iso88592',
+ description: 'Central European (ISO 8859-2)'
+ },
+ {
+ value: 'windows1250',
+ description: 'Central European (Windows 1250)'
+ },
+ {
+ value: 'gb18030',
+ description: 'Chinese (GB18030)'
+ },
+ {
+ value: 'gbk',
+ description: 'Chinese (GBK)'
+ },
+ {
+ value: 'cp950',
+ description: 'Traditional Chinese (Big5)'
+ },
+ {
+ value: 'big5hkscs',
+ description: 'Traditional Chinese (Big5-HKSCS)'
+ },
+ {
+ value: 'cp866',
+ description: 'Cyrillic (CP 866)'
+ },
+ {
+ value: 'iso88595',
+ description: 'Cyrillic (ISO 8859-5)'
+ },
+ {
+ value: 'koi8r',
+ description: 'Cyrillic (KOI8-R)'
+ },
+ {
+ value: 'koi8u',
+ description: 'Cyrillic (KOI8-U)'
+ },
+ {
+ value: 'windows1251',
+ description: 'Cyrillic (Windows 1251)'
+ },
+ {
+ value: 'cp437',
+ description: 'DOS (CP 437)'
+ },
+ {
+ value: 'cp850',
+ description: 'DOS (CP 850)'
+ },
+ {
+ value: 'iso885913',
+ description: 'Estonian (ISO 8859-13)'
+ },
+ {
+ value: 'iso88597',
+ description: 'Greek (ISO 8859-7)'
+ },
+ {
+ value: 'windows1253',
+ description: 'Greek (Windows 1253)'
+ },
+ {
+ value: 'iso88598',
+ description: 'Hebrew (ISO 8859-8)'
+ },
+ {
+ value: 'windows1255',
+ description: 'Hebrew (Windows 1255)'
+ },
+ {
+ value: 'cp932',
+ description: 'Japanese (CP 932)'
+ },
+ {
+ value: 'eucjp',
+ description: 'Japanese (EUC-JP)'
+ },
+ {
+ value: 'shiftjis',
+ description: 'Japanese (Shift JIS)'
+ },
+ {
+ value: 'euckr',
+ description: 'Korean (EUC-KR)'
+ },
+ {
+ value: 'iso885910',
+ description: 'Nordic (ISO 8859-10)'
+ },
+ {
+ value: 'iso885916',
+ description: 'Romanian (ISO 8859-16)'
+ },
+ {
+ value: 'iso88599',
+ description: 'Turkish (ISO 8859-9)'
+ },
+ {
+ value: 'windows1254',
+ description: 'Turkish (Windows 1254)'
+ },
+ {
+ value: 'utf8',
+ description: 'Unicode (UTF-8)'
+ },
+ {
+ value: 'utf16le',
+ description: 'Unicode (UTF-16 LE)'
+ },
+ {
+ value: 'utf16be',
+ description: 'Unicode (UTF-16 BE)'
+ },
+ {
+ value: 'windows1258',
+ description: 'Vietnamese (Windows 1258)'
+ },
+ {
+ value: 'iso88591',
+ description: 'Western (ISO 8859-1)'
+ },
+ {
+ value: 'iso88593',
+ description: 'Western (ISO 8859-3)'
+ },
+ {
+ value: 'iso885915',
+ description: 'Western (ISO 8859-15)'
+ },
+ {
+ value: 'macroman',
+ description: 'Western (Mac Roman)'
+ },
+ {
+ value: 'windows1252',
+ description: 'Western (Windows 1252)'
+ }
]
},
openEmptyEditorOnStart: {
- description: 'Automatically open an empty editor on startup.',
+ description: 'When checked opens an untitled editor when loading a blank environment (such as with _File > New Window_ or when "Restore Previous Windows On Start" is unchecked); otherwise no editor is opened when loading a blank environment. This setting has no effect when restoring a previous state.',
+ type: 'boolean',
+ default: true
+ },
+ restorePreviousWindowsOnStart: {
+ description: 'When checked restores the last state of all Atom windows when started from the icon or `atom` by itself from the command line; otherwise a blank environment is loaded.',
type: 'boolean',
default: true
},
@@ -136,6 +272,12 @@ const configSchema = {
type: 'boolean',
default: true
},
+ useProxySettingsWhenCallingApm: {
+ title: 'Use Proxy Settings When Calling APM',
+ description: 'Use detected proxy settings when calling the `apm` command-line tool.',
+ type: 'boolean',
+ default: true
+ },
allowPendingPaneItems: {
description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.',
type: 'boolean',
@@ -164,7 +306,7 @@ const configSchema = {
warnOnLargeFileLimit: {
description: 'Warn before opening files larger than this number of megabytes.',
type: 'number',
- default: 20
+ default: 40
}
}
},
@@ -205,6 +347,11 @@ const configSchema = {
default: 1.5,
description: 'Height of editor lines, as a multiplier of font size.'
},
+ showCursorOnSelection: {
+ type: 'boolean',
+ 'default': true,
+ description: 'Show cursor while there is a selection.'
+ },
showInvisibles: {
type: 'boolean',
default: false,
diff --git a/src/config.coffee b/src/config.coffee
index 2dc537dd4..e873a1348 100644
--- a/src/config.coffee
+++ b/src/config.coffee
@@ -336,6 +336,31 @@ ScopeDescriptor = require './scope-descriptor'
# order: 2
# ```
#
+# ## Manipulating values outside your configuration schema
+#
+# It is possible to manipulate(`get`, `set`, `observe` etc) values that do not
+# appear in your configuration schema. For example, if the config schema of the
+# package 'some-package' is
+#
+# ```coffee
+# config:
+# someSetting:
+# type: 'boolean'
+# default: false
+# ```
+#
+# You can still do the following
+#
+# ```coffee
+# let otherSetting = atom.config.get('some-package.otherSetting')
+# atom.config.set('some-package.stillAnotherSetting', otherSetting * 5)
+# ```
+#
+# In other words, if a function asks for a `key-path`, that path doesn't have to
+# be described in the config schema for the package or any package. However, as
+# highlighted in the best practices section, you are advised against doing the
+# above.
+#
# ## Best practices
#
# * Don't depend on (or write to) configuration keys outside of your keypath.
diff --git a/src/cursor.coffee b/src/cursor.coffee
index 85573f10e..47e8c0594 100644
--- a/src/cursor.coffee
+++ b/src/cursor.coffee
@@ -12,15 +12,18 @@ EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g
# of a {DisplayMarker}.
module.exports =
class Cursor extends Model
+ showCursorOnSelection: null
screenPosition: null
bufferPosition: null
goalColumn: null
visible: true
# Instantiated by a {TextEditor}
- constructor: ({@editor, @marker, id}) ->
+ constructor: ({@editor, @marker, @showCursorOnSelection, id}) ->
@emitter = new Emitter
+ @showCursorOnSelection ?= true
+
@assignId(id)
@updateVisibility()
@@ -575,7 +578,10 @@ class Cursor extends Model
isVisible: -> @visible
updateVisibility: ->
- @setVisible(@marker.getBufferRange().isEmpty())
+ if @showCursorOnSelection
+ @setVisible(true)
+ else
+ @setVisible(@marker.getBufferRange().isEmpty())
###
Section: Comparing to another cursor
@@ -645,6 +651,11 @@ class Cursor extends Model
Section: Private
###
+ setShowCursorOnSelection: (value) ->
+ if value isnt @showCursorOnSelection
+ @showCursorOnSelection = value
+ @updateVisibility()
+
getNonWordCharacters: ->
@editor.getNonWordCharacters(@getScopeDescriptor().getScopesArray())
@@ -653,9 +664,6 @@ class Cursor extends Model
fn()
@autoscroll() if options.autoscroll ? @isLastCursor()
- getPixelRect: ->
- @editor.pixelRectForScreenRange(@getScreenRange())
-
getScreenRange: ->
{row, column} = @getScreenPosition()
new Range(new Point(row, column), new Point(row, column + 1))
diff --git a/src/decoration-manager.coffee b/src/decoration-manager.coffee
index e1096f572..05935f018 100644
--- a/src/decoration-manager.coffee
+++ b/src/decoration-manager.coffee
@@ -8,7 +8,7 @@ class DecorationManager extends Model
didUpdateDecorationsEventScheduled: false
updatedSynchronously: false
- constructor: (@displayLayer, @defaultMarkerLayer) ->
+ constructor: (@displayLayer) ->
super
@emitter = new Emitter
@@ -71,9 +71,11 @@ class DecorationManager extends Model
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationsByMarkerId = {}
- for marker in @defaultMarkerLayer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
- if decorations = @decorationsByMarkerId[marker.id]
- decorationsByMarkerId[marker.id] = decorations
+ for layerId of @decorationCountsByLayerId
+ layer = @displayLayer.getMarkerLayer(layerId)
+ for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
+ if decorations = @decorationsByMarkerId[marker.id]
+ decorationsByMarkerId[marker.id] = decorations
decorationsByMarkerId
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
@@ -104,7 +106,14 @@ class DecorationManager extends Model
decorationsState
decorateMarker: (marker, decorationParams) ->
- throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
+ if marker.isDestroyed()
+ error = new Error("Cannot decorate a destroyed marker")
+ error.metadata = {markerLayerIsDestroyed: marker.layer.isDestroyed()}
+ if marker.destroyStackTrace?
+ error.metadata.destroyStackTrace = marker.destroyStackTrace
+ if marker.bufferMarker?.destroyStackTrace?
+ error.metadata.destroyStackTrace = marker.bufferMarker?.destroyStackTrace
+ throw error
marker = @displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id)
decoration = new Decoration(marker, this, decorationParams)
@decorationsByMarkerId[marker.id] ?= []
@@ -117,6 +126,7 @@ class DecorationManager extends Model
decoration
decorateMarkerLayer: (markerLayer, decorationParams) ->
+ throw new Error("Cannot decorate a destroyed marker layer") if markerLayer.isDestroyed()
decoration = new LayerDecoration(markerLayer, this, decorationParams)
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
diff --git a/src/default-directory-provider.coffee b/src/default-directory-provider.coffee
index ed4e9ba36..44d5298dd 100644
--- a/src/default-directory-provider.coffee
+++ b/src/default-directory-provider.coffee
@@ -15,7 +15,7 @@ class DefaultDirectoryProvider
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURISync: (uri) ->
- normalizedPath = path.normalize(uri)
+ normalizedPath = @normalizePath(uri)
{host} = url.parse(uri)
directoryPath = if host
uri
@@ -42,3 +42,17 @@ class DefaultDirectoryProvider
# * `null` if the given URI is not compatibile with this provider.
directoryForURI: (uri) ->
Promise.resolve(@directoryForURISync(uri))
+
+ # Public: Normalizes path.
+ #
+ # * `uri` {String} The path that should be normalized.
+ #
+ # Returns a {String} with normalized path.
+ normalizePath: (uri) ->
+ # Normalize disk drive letter on Windows to avoid opening two buffers for the same file
+ pathWithNormalizedDiskDriveLetter =
+ if process.platform is 'win32' and matchData = uri.match(/^([a-z]):/)
+ "#{matchData[1].toUpperCase()}#{uri.slice(1)}"
+ else
+ uri
+ path.normalize(pathWithNormalizedDiskDriveLetter)
diff --git a/src/dom-element-pool.coffee b/src/dom-element-pool.coffee
deleted file mode 100644
index f81a537f3..000000000
--- a/src/dom-element-pool.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-module.exports =
-class DOMElementPool
- constructor: ->
- @freeElementsByTagName = {}
- @freedElements = new Set
-
- clear: ->
- @freedElements.clear()
- for tagName, freeElements of @freeElementsByTagName
- freeElements.length = 0
- return
-
- build: (tagName, factory, reset) ->
- element = @freeElementsByTagName[tagName]?.pop()
- element ?= factory()
- reset(element)
- @freedElements.delete(element)
- element
-
- buildElement: (tagName, className) ->
- factory = -> document.createElement(tagName)
- reset = (element) ->
- delete element.dataset[dataId] for dataId of element.dataset
- element.removeAttribute("style")
- if className?
- element.className = className
- else
- element.removeAttribute("class")
- @build(tagName, factory, reset)
-
- buildText: (textContent) ->
- factory = -> document.createTextNode(textContent)
- reset = (element) -> element.textContent = textContent
- @build("#text", factory, reset)
-
- freeElementAndDescendants: (element) ->
- @free(element)
- @freeDescendants(element)
-
- freeDescendants: (element) ->
- for descendant in element.childNodes by -1
- @free(descendant)
- @freeDescendants(descendant)
- return
-
- free: (element) ->
- throw new Error("The element cannot be null or undefined.") unless element?
- throw new Error("The element has already been freed!") if @freedElements.has(element)
-
- tagName = element.nodeName.toLowerCase()
- @freeElementsByTagName[tagName] ?= []
- @freeElementsByTagName[tagName].push(element)
- @freedElements.add(element)
-
- element.remove()
diff --git a/src/dom-element-pool.js b/src/dom-element-pool.js
new file mode 100644
index 000000000..0fef02dee
--- /dev/null
+++ b/src/dom-element-pool.js
@@ -0,0 +1,89 @@
+module.exports =
+class DOMElementPool {
+ constructor () {
+ this.managedElements = new Set()
+ this.freeElementsByTagName = new Map()
+ this.freedElements = new Set()
+ }
+
+ clear () {
+ this.managedElements.clear()
+ this.freedElements.clear()
+ this.freeElementsByTagName.clear()
+ }
+
+ buildElement (tagName, className) {
+ const elements = this.freeElementsByTagName.get(tagName)
+ let element = elements ? elements.pop() : null
+ if (element) {
+ for (let dataId in element.dataset) { delete element.dataset[dataId] }
+ element.removeAttribute('style')
+ if (className) {
+ element.className = className
+ } else {
+ element.removeAttribute('class')
+ }
+ while (element.firstChild) {
+ element.removeChild(element.firstChild)
+ }
+ this.freedElements.delete(element)
+ } else {
+ element = document.createElement(tagName)
+ if (className) {
+ element.className = className
+ }
+ this.managedElements.add(element)
+ }
+ return element
+ }
+
+ buildText (textContent) {
+ const elements = this.freeElementsByTagName.get('#text')
+ let element = elements ? elements.pop() : null
+ if (element) {
+ element.textContent = textContent
+ this.freedElements.delete(element)
+ } else {
+ element = document.createTextNode(textContent)
+ this.managedElements.add(element)
+ }
+ return element
+ }
+
+ freeElementAndDescendants (element) {
+ this.free(element)
+ element.remove()
+ }
+
+ freeDescendants (element) {
+ while (element.firstChild) {
+ this.free(element.firstChild)
+ element.removeChild(element.firstChild)
+ }
+ }
+
+ free (element) {
+ if (element == null) { throw new Error('The element cannot be null or undefined.') }
+ if (!this.managedElements.has(element)) return
+ if (this.freedElements.has(element)) {
+ atom.assert(false, 'The element has already been freed!', {
+ content: element instanceof window.Text ? element.textContent : element.outerHTML
+ })
+ return
+ }
+
+ const tagName = element.nodeName.toLowerCase()
+ let elements = this.freeElementsByTagName.get(tagName)
+ if (!elements) {
+ elements = []
+ this.freeElementsByTagName.set(tagName, elements)
+ }
+ elements.push(element)
+ this.freedElements.add(element)
+
+ for (let i = element.childNodes.length - 1; i >= 0; i--) {
+ const descendant = element.childNodes[i]
+ this.free(descendant)
+ }
+ }
+}
diff --git a/src/get-window-load-settings.js b/src/get-window-load-settings.js
new file mode 100644
index 000000000..7ee465141
--- /dev/null
+++ b/src/get-window-load-settings.js
@@ -0,0 +1,10 @@
+const {remote} = require('electron')
+
+let windowLoadSettings = null
+
+module.exports = () => {
+ if (!windowLoadSettings) {
+ windowLoadSettings = remote.getCurrentWindow().loadSettings
+ }
+ return windowLoadSettings
+}
diff --git a/src/git-repository.coffee b/src/git-repository.coffee
index d47b2e37c..423a5ce2f 100644
--- a/src/git-repository.coffee
+++ b/src/git-repository.coffee
@@ -238,6 +238,7 @@ class GitRepository
# Public: Returns the git configuration value specified by the key.
#
+ # * `key` The {String} key for the configuration to lookup.
# * `path` An optional {String} path in the repository to get this information
# for, only needed if the repository has submodules.
getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key)
diff --git a/src/gutter-container-component.coffee b/src/gutter-container-component.coffee
index 56b0fea84..ebb2d8597 100644
--- a/src/gutter-container-component.coffee
+++ b/src/gutter-container-component.coffee
@@ -103,6 +103,7 @@ class GutterContainerComponent
@domNode.appendChild(gutterComponent.getDomNode())
else
@domNode.insertBefore(gutterComponent.getDomNode(), @domNode.children[indexInOldGutters])
+ indexInOldGutters += 1
# Remove any gutters that were not present in the new gutters state.
for gutterComponentDescription in @gutterComponents
diff --git a/src/history-manager.js b/src/history-manager.js
index 657beed97..f013957b9 100644
--- a/src/history-manager.js
+++ b/src/history-manager.js
@@ -47,6 +47,8 @@ export class HistoryManager {
}
addProject (paths, lastOpened) {
+ if (paths.length === 0) return
+
let project = this.getProject(paths)
if (!project) {
project = new HistoryProject(paths)
@@ -59,10 +61,22 @@ export class HistoryManager {
this.didChangeProjects()
}
+ removeProject (paths) {
+ if (paths.length === 0) return
+
+ let project = this.getProject(paths)
+ if (!project) return
+
+ let index = this.projects.indexOf(project)
+ this.projects.splice(index, 1)
+
+ this.saveState()
+ this.didChangeProjects()
+ }
+
getProject (paths) {
- const pathsString = paths.toString()
for (var i = 0; i < this.projects.length; i++) {
- if (this.projects[i].paths.toString() === pathsString) {
+ if (arrayEquivalent(paths, this.projects[i].paths)) {
return this.projects[i]
}
}
@@ -98,6 +112,14 @@ export class HistoryManager {
}
}
+function arrayEquivalent (a, b) {
+ if (a.length !== b.length) return false
+ for (var i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false
+ }
+ return true
+}
+
export class HistoryProject {
constructor (paths, lastOpened) {
this.paths = paths
diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee
index 3254e5ca5..9eda1ef3b 100644
--- a/src/initialize-application-window.coffee
+++ b/src/initialize-application-window.coffee
@@ -86,7 +86,7 @@ module.exports = ->
{updateProcessEnv} = require('./update-process-env')
path = require 'path'
require './window'
- {getWindowLoadSettings} = require './window-load-settings-helpers'
+ getWindowLoadSettings = require './get-window-load-settings'
{ipcRenderer} = require 'electron'
{resourcePath, devMode, env} = getWindowLoadSettings()
require './electron-shims'
diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js
index 29a210904..a223e0b03 100644
--- a/src/initialize-benchmark-window.js
+++ b/src/initialize-benchmark-window.js
@@ -6,7 +6,7 @@ import ipcHelpers from './ipc-helpers'
import util from 'util'
export default async function () {
- const {getWindowLoadSettings} = require('./window-load-settings-helpers')
+ const getWindowLoadSettings = require('./get-window-load-settings')
const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings()
try {
const Clipboard = require('../src/clipboard')
diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee
index fa1c70943..794db3174 100644
--- a/src/initialize-test-window.coffee
+++ b/src/initialize-test-window.coffee
@@ -18,7 +18,8 @@ module.exports = ({blobStore}) ->
try
path = require 'path'
{ipcRenderer} = require 'electron'
- {getWindowLoadSettings} = require './window-load-settings-helpers'
+ getWindowLoadSettings = require './get-window-load-settings'
+ CompileCache = require './compile-cache'
AtomEnvironment = require '../src/atom-environment'
ApplicationDelegate = require '../src/application-delegate'
Clipboard = require '../src/clipboard'
@@ -58,6 +59,13 @@ module.exports = ({blobStore}) ->
require('module').globalPaths.push(exportsPath)
process.env.NODE_PATH = exportsPath # Set NODE_PATH env variable since tasks may need it.
+ # Set up optional transpilation for packages under test if any
+ FindParentDir = require 'find-parent-dir'
+ if packageRoot = FindParentDir.sync(testPaths[0], 'package.json')
+ packageMetadata = require(path.join(packageRoot, 'package.json'))
+ if packageMetadata.atomTranspilers
+ CompileCache.addTranspilerConfigForPath(packageRoot, packageMetadata.name, packageMetadata, packageMetadata.atomTranspilers)
+
document.title = "Spec Suite"
clipboard = new Clipboard
diff --git a/src/input-component.coffee b/src/input-component.coffee
index b8081b0d6..27543a2fd 100644
--- a/src/input-component.coffee
+++ b/src/input-component.coffee
@@ -1,15 +1,6 @@
module.exports =
class InputComponent
- constructor: ->
- @domNode = document.createElement('input')
- @domNode.classList.add('hidden-input')
- @domNode.setAttribute('tabindex', -1)
- @domNode.setAttribute('data-react-skip-selection-restoration', true)
- @domNode.style['-webkit-transform'] = 'translateZ(0)'
- @domNode.addEventListener 'paste', (event) -> event.preventDefault()
-
- getDomNode: ->
- @domNode
+ constructor: (@domNode) ->
updateSync: (state) ->
@oldState ?= {}
diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js
index 6a7565968..4be9f9613 100644
--- a/src/ipc-helpers.js
+++ b/src/ipc-helpers.js
@@ -15,6 +15,7 @@ exports.on = function (emitter, eventName, callback) {
exports.call = function (channel, ...args) {
if (!ipcRenderer) {
ipcRenderer = require('electron').ipcRenderer
+ ipcRenderer.setMaxListeners(20)
}
var responseChannel = getResponseChannel(channel)
diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee
index b5c3964f9..bf8302f4c 100644
--- a/src/keymap-extensions.coffee
+++ b/src/keymap-extensions.coffee
@@ -8,6 +8,9 @@ bundledKeymaps = require('../package.json')?._atomKeymaps
KeymapManager::onDidLoadBundledKeymaps = (callback) ->
@emitter.on 'did-load-bundled-keymaps', callback
+KeymapManager::onDidLoadUserKeymap = (callback) ->
+ @emitter.on 'did-load-user-keymap', callback
+
KeymapManager::loadBundledKeymaps = ->
keymapsPath = path.join(@resourcePath, 'keymaps')
if bundledKeymaps?
@@ -49,6 +52,9 @@ KeymapManager::loadUserKeymap = ->
stack = error.stack
@notificationManager.addFatalError(error.message, {detail, stack, dismissable: true})
+ @emitter.emit 'did-load-user-keymap'
+
+
KeymapManager::subscribeToFileReadFailure = ->
@onDidFailToReadFile (error) =>
userKeymapPath = @getUserKeymapPath()
diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee
index d4979865c..308cc5af0 100644
--- a/src/lines-yardstick.coffee
+++ b/src/lines-yardstick.coffee
@@ -126,4 +126,8 @@ class LinesYardstick
clientRectForRange: (textNode, startIndex, endIndex) ->
@rangeForMeasurement.setStart(textNode, startIndex)
@rangeForMeasurement.setEnd(textNode, endIndex)
- @rangeForMeasurement.getClientRects()[0] ? @rangeForMeasurement.getBoundingClientRect()
+ clientRects = @rangeForMeasurement.getClientRects()
+ if clientRects.length is 1
+ clientRects[0]
+ else
+ @rangeForMeasurement.getBoundingClientRect()
diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee
index fc218782a..e2515ccb9 100644
--- a/src/main-process/atom-application.coffee
+++ b/src/main-process/atom-application.coffee
@@ -63,7 +63,7 @@ class AtomApplication
exit: (status) -> app.exit(status)
constructor: (options) ->
- {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @setPortable, @userDataDir} = options
+ {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @userDataDir} = options
@socketPath = null if options.test or options.benchmark or options.benchmarkTest
@pidsToOpenWindows = {}
@windows = []
@@ -385,6 +385,9 @@ class AtomApplication
@fileRecoveryService.didSavePath(@atomWindowForEvent(event), path)
event.returnValue = true
+ @disposable.add ipcHelpers.on ipcMain, 'did-change-paths', =>
+ @saveState(false)
+
setupDockMenu: ->
if process.platform is 'darwin'
dockMenu = Menu.buildFromTemplate [
@@ -584,8 +587,7 @@ class AtomApplication
states = []
for window in @windows
unless window.isSpec
- if loadSettings = window.getLoadSettings()
- states.push(initialPaths: loadSettings.initialPaths)
+ states.push({initialPaths: window.representedDirectoryPaths})
if states.length > 0 or allowEmpty
@storageFolder.storeSync('application.json', states)
@@ -796,7 +798,6 @@ class AtomApplication
restart: ->
args = []
args.push("--safe") if @safeMode
- args.push("--portable") if @setPortable
args.push("--log-file=#{@logFile}") if @logFile?
args.push("--socket-path=#{@socketPath}") if @socketPath?
args.push("--user-data-dir=#{@userDataDir}") if @userDataDir?
diff --git a/src/main-process/atom-portable.js b/src/main-process/atom-portable.js
deleted file mode 100644
index 7d395c0e7..000000000
--- a/src/main-process/atom-portable.js
+++ /dev/null
@@ -1,58 +0,0 @@
-const fs = require('fs-plus')
-const path = require('path')
-const {ipcMain} = require('electron')
-
-module.exports = class AtomPortable {
- static getPortableAtomHomePath () {
- const execDirectoryPath = path.dirname(process.execPath)
- return path.join(execDirectoryPath, '..', '.atom')
- }
-
- static setPortable (existingAtomHome) {
- fs.copySync(existingAtomHome, this.getPortableAtomHomePath())
- }
-
- static isPortableInstall (platform, environmentAtomHome, defaultHome) {
- if (!['linux', 'win32'].includes(platform)) {
- return false
- }
-
- if (environmentAtomHome) {
- return false
- }
-
- if (!fs.existsSync(this.getPortableAtomHomePath())) {
- return false
- }
-
- // Currently checking only that the directory exists and is writable,
- // probably want to do some integrity checks on contents in future.
- return this.isPortableAtomHomePathWritable(defaultHome)
- }
-
- static isPortableAtomHomePathWritable (defaultHome) {
- let writable = false
- let message = ''
- try {
- const writePermissionTestFile = path.join(this.getPortableAtomHomePath(), 'write.test')
-
- if (!fs.existsSync(writePermissionTestFile)) {
- fs.writeFileSync(writePermissionTestFile, 'test')
- }
-
- fs.removeSync(writePermissionTestFile)
- writable = true
- } catch (error) {
- message = `Failed to use portable Atom home directory (${this.getPortableAtomHomePath()}). Using the default instead (${defaultHome}). ${error.message}.`
- }
-
- ipcMain.on('check-portable-home-writable', function (event) {
- event.sender.send('check-portable-home-writable-response', {
- writable: writable,
- message: message
- })
- })
-
- return writable
- }
-}
diff --git a/src/main-process/atom-window.coffee b/src/main-process/atom-window.coffee
index 3c163ba25..f3a9b394c 100644
--- a/src/main-process/atom-window.coffee
+++ b/src/main-process/atom-window.coffee
@@ -46,9 +46,7 @@ class AtomWindow
if @shouldHideTitleBar()
options.titleBarStyle = 'hidden'
- @browserWindow = new BrowserWindow options
- @atomApplication.addWindow(this)
-
+ @browserWindow = new BrowserWindow(options)
@handleEvents()
loadSettings = Object.assign({}, settings)
@@ -60,11 +58,15 @@ class AtomWindow
loadSettings.clearWindowState ?= false
loadSettings.initialPaths ?=
for {pathToOpen} in locationsToOpen when pathToOpen
- if fs.statSyncNoException(pathToOpen).isFile?()
- path.dirname(pathToOpen)
- else
+ stat = fs.statSyncNoException(pathToOpen) or null
+ if stat?.isDirectory()
pathToOpen
-
+ else
+ parentDirectory = path.dirname(pathToOpen)
+ if stat?.isFile() or fs.existsSync(parentDirectory)
+ parentDirectory
+ else
+ pathToOpen
loadSettings.initialPaths.sort()
# Only send to the first non-spec window created
@@ -72,33 +74,31 @@ class AtomWindow
@constructor.includeShellLoadTime = false
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
+ @representedDirectoryPaths = loadSettings.initialPaths
+ @env = loadSettings.env if loadSettings.env?
+
@browserWindow.loadSettings = loadSettings
@browserWindow.on 'window:loaded', =>
@emit 'window:loaded'
@resolveLoadedPromise()
- @setLoadSettings(loadSettings)
- @env = loadSettings.env if loadSettings.env?
+ @browserWindow.loadURL url.format
+ protocol: 'file'
+ pathname: "#{@resourcePath}/static/index.html"
+ slashes: true
+
+ @browserWindow.showSaveDialog = @showSaveDialog.bind(this)
+
@browserWindow.focusOnWebView() if @isSpec
@browserWindow.temporaryState = {windowDimensions} if windowDimensions?
hasPathToOpen = not (locationsToOpen.length is 1 and not locationsToOpen[0].pathToOpen?)
@openLocations(locationsToOpen) if hasPathToOpen and not @isSpecWindow()
- setLoadSettings: (loadSettings) ->
- @browserWindow.loadURL url.format
- protocol: 'file'
- pathname: "#{@resourcePath}/static/index.html"
- slashes: true
- hash: encodeURIComponent(JSON.stringify(loadSettings))
+ @atomApplication.addWindow(this)
- getLoadSettings: ->
- if @browserWindow.webContents? and not @browserWindow.webContents.isLoading()
- hash = url.parse(@browserWindow.webContents.getURL()).hash.substr(1)
- JSON.parse(decodeURIComponent(hash))
-
- hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0
+ hasProjectPath: -> @representedDirectoryPaths.length > 0
setupContextMenu: ->
ContextMenu = require './context-menu'
@@ -112,7 +112,7 @@ class AtomWindow
true
containsPath: (pathToCheck) ->
- @getLoadSettings()?.initialPaths?.some (projectPath) ->
+ @representedDirectoryPaths.some (projectPath) ->
if not projectPath
false
else if not pathToCheck
@@ -150,7 +150,10 @@ class AtomWindow
@browserWindow.destroy() if chosen is 0
@browserWindow.webContents.on 'crashed', =>
- @atomApplication.exit(100) if @headless
+ if @headless
+ console.log "Renderer process crashed, exiting"
+ @atomApplication.exit(100)
+ return
@fileRecoveryService.didCrashWindow(this)
chosen = dialog.showMessageBox @browserWindow,
@@ -262,6 +265,13 @@ class AtomWindow
@saveState().then => @browserWindow.reload()
@loadedPromise
+ showSaveDialog: (params) ->
+ params = Object.assign({
+ title: 'Save File',
+ defaultPath: @representedDirectoryPaths[0]
+ }, params)
+ dialog.showSaveDialog(this, params)
+
toggleDevTools: -> @browserWindow.toggleDevTools()
openDevTools: -> @browserWindow.openDevTools()
@@ -272,4 +282,8 @@ class AtomWindow
setRepresentedFilename: (representedFilename) -> @browserWindow.setRepresentedFilename(representedFilename)
+ setRepresentedDirectoryPaths: (@representedDirectoryPaths) ->
+ @representedDirectoryPaths.sort()
+ @atomApplication.saveState()
+
copy: -> @browserWindow.copy()
diff --git a/src/main-process/auto-update-manager.coffee b/src/main-process/auto-update-manager.coffee
index 8fdba844d..ff29dd3d6 100644
--- a/src/main-process/auto-update-manager.coffee
+++ b/src/main-process/auto-update-manager.coffee
@@ -22,7 +22,7 @@ class AutoUpdateManager
setupAutoUpdater: ->
if process.platform is 'win32'
archSuffix = if process.arch is 'ia32' then '' else '-' + process.arch
- @feedUrl = "https://atom.io/api/updates#{archSuffix}"
+ @feedUrl = "https://atom.io/api/updates#{archSuffix}?version=#{@version}"
autoUpdater = require './auto-updater-win32'
else
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js
index 68a18fa30..3f9f2523a 100644
--- a/src/main-process/parse-command-line.js
+++ b/src/main-process/parse-command-line.js
@@ -41,10 +41,6 @@ module.exports = function parseCommandLine (processArgs) {
'safe',
'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.'
)
- options.boolean('portable').describe(
- 'portable',
- 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
- )
options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.')
options.boolean('benchmark-test').describe('benchmark--test', 'Run a faster version of the benchmarks in headless mode.')
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
@@ -104,21 +100,20 @@ module.exports = function parseCommandLine (processArgs) {
const profileStartup = args['profile-startup']
const clearWindowState = args['clear-window-state']
const urlsToOpen = []
- const setPortable = args.portable
let devMode = args['dev']
let devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH || path.join(app.getPath('home'), 'github', 'atom')
let resourcePath = null
if (args['resource-path']) {
devMode = true
- resourcePath = args['resource-path']
+ devResourcePath = args['resource-path']
}
if (test) {
devMode = true
}
- if (devMode && !resourcePath) {
+ if (devMode) {
resourcePath = devResourcePath
}
@@ -152,7 +147,6 @@ module.exports = function parseCommandLine (processArgs) {
userDataDir,
profileStartup,
timeout,
- setPortable,
clearWindowState,
addToLastWindow,
mainProcess,
diff --git a/src/main-process/start.js b/src/main-process/start.js
index d4161e325..f54d263e0 100644
--- a/src/main-process/start.js
+++ b/src/main-process/start.js
@@ -1,10 +1,10 @@
const {app} = require('electron')
-const fs = require('fs-plus')
const nslog = require('nslog')
const path = require('path')
const temp = require('temp')
const parseCommandLine = require('./parse-command-line')
const startCrashReporter = require('../crash-reporter-start')
+const atomPaths = require('../atom-paths')
module.exports = function start (resourcePath, startTime) {
global.shellStartTime = startTime
@@ -23,7 +23,8 @@ module.exports = function start (resourcePath, startTime) {
console.log = nslog
const args = parseCommandLine(process.argv.slice(1))
- setupAtomHome(args)
+ atomPaths.setAtomHome(app.getPath('home'))
+ atomPaths.setUserData()
setupCompileCache()
if (handleStartupEventWithSquirrel()) {
@@ -79,36 +80,6 @@ function handleStartupEventWithSquirrel () {
return SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
}
-function setupAtomHome ({setPortable}) {
- if (process.env.ATOM_HOME) {
- return
- }
-
- let atomHome = path.join(app.getPath('home'), '.atom')
- const AtomPortable = require('./atom-portable')
-
- if (setPortable && !AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) {
- try {
- AtomPortable.setPortable(atomHome)
- } catch (error) {
- console.log(`Failed copying portable directory '${atomHome}' to '${AtomPortable.getPortableAtomHomePath()}'`)
- console.log(`${error.message} ${error.stack}`)
- }
- }
-
- if (AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) {
- atomHome = AtomPortable.getPortableAtomHomePath()
- }
-
- try {
- atomHome = fs.realpathSync(atomHome)
- } catch (e) {
- // Don't throw an error if atomHome doesn't exist.
- }
-
- process.env.ATOM_HOME = atomHome
-}
-
function setupCompileCache () {
const CompileCache = require('../compile-cache')
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
diff --git a/src/main-process/win-shell.coffee b/src/main-process/win-shell.coffee
deleted file mode 100644
index baf02a9fc..000000000
--- a/src/main-process/win-shell.coffee
+++ /dev/null
@@ -1,63 +0,0 @@
-Registry = require 'winreg'
-Path = require 'path'
-
-exeName = Path.basename(process.execPath)
-appPath = "\"#{process.execPath}\""
-fileIconPath = "\"#{Path.join(process.execPath, '..', 'resources', 'cli', 'file.ico')}\""
-isBeta = appPath.includes(' Beta')
-appName = exeName.replace('atom', (if isBeta then 'Atom Beta' else 'Atom' )).replace('.exe', '')
-
-class ShellOption
- constructor: (key, parts) ->
- @key = key
- @parts = parts
-
- isRegistered: (callback) =>
- new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
- .get @parts[0].name, (err, val) =>
- callback(not err? and val? and val.value is @parts[0].value)
-
- register: (callback) =>
- doneCount = @parts.length
- @parts.forEach (part) =>
- reg = new Registry({hive: 'HKCU', key: if part.key? then "#{@key}\\#{part.key}" else @key})
- reg.create( -> reg.set part.name, Registry.REG_SZ, part.value, -> callback() if --doneCount is 0)
-
- deregister: (callback) =>
- @isRegistered (isRegistered) =>
- if isRegistered
- new Registry({hive: 'HKCU', key: @key}).destroy -> callback null, true
- else
- callback null, false
-
- update: (callback) =>
- new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
- .get @parts[0].name, (err, val) =>
- if err? or not val?
- callback(err)
- else
- @register callback
-
-exports.appName = appName
-
-exports.fileHandler = new ShellOption("\\Software\\Classes\\Applications\\#{exeName}",
- [
- {key: 'shell\\open\\command', name: '', value: "#{appPath} \"%1\""},
- {key: 'shell\\open', name: 'FriendlyAppName', value: "#{appName}"},
- {key: 'DefaultIcon', name: '', value: "#{fileIconPath}"}
- ]
-)
-
-contextParts = [
- {key: 'command', name: '', value: "#{appPath} \"%1\""},
- {name: '', value: "Open with #{appName}"},
- {name: 'Icon', value: "#{appPath}"}
-]
-
-exports.fileContextMenu = new ShellOption("\\Software\\Classes\\*\\shell\\#{appName}", contextParts)
-
-exports.folderContextMenu = new ShellOption("\\Software\\Classes\\Directory\\shell\\#{appName}", contextParts)
-
-exports.folderBackgroundContextMenu = new ShellOption("\\Software\\Classes\\Directory\\background\\shell\\#{appName}",
- JSON.parse(JSON.stringify(contextParts).replace('%1', '%V'))
-)
diff --git a/src/main-process/win-shell.js b/src/main-process/win-shell.js
new file mode 100644
index 000000000..9670936c7
--- /dev/null
+++ b/src/main-process/win-shell.js
@@ -0,0 +1,77 @@
+'use babel'
+
+import Registry from 'winreg'
+import Path from 'path'
+
+let exeName = Path.basename(process.execPath)
+let appPath = `\"${process.execPath}\"`
+let fileIconPath = `\"${Path.join(process.execPath, '..', 'resources', 'cli', 'file.ico')}\"`
+let isBeta = appPath.includes(' Beta')
+let appName = exeName.replace('atom', isBeta ? 'Atom Beta' : 'Atom').replace('.exe', '')
+
+class ShellOption {
+ constructor (key, parts) {
+ this.isRegistered = this.isRegistered.bind(this)
+ this.register = this.register.bind(this)
+ this.deregister = this.deregister.bind(this)
+ this.update = this.update.bind(this)
+ this.key = key
+ this.parts = parts
+ }
+
+ isRegistered (callback) {
+ new Registry({hive: 'HKCU', key: `${this.key}\\${this.parts[0].key}`})
+ .get(this.parts[0].name, (err, val) => callback((err == null) && (val != null) && val.value === this.parts[0].value))
+ }
+
+ register (callback) {
+ let doneCount = this.parts.length
+ this.parts.forEach(part => {
+ let reg = new Registry({hive: 'HKCU', key: (part.key != null) ? `${this.key}\\${part.key}` : this.key})
+ return reg.create(() => reg.set(part.name, Registry.REG_SZ, part.value, () => { if (--doneCount === 0) return callback() }))
+ })
+ }
+
+ deregister (callback) {
+ this.isRegistered(isRegistered => {
+ if (isRegistered) {
+ new Registry({hive: 'HKCU', key: this.key}).destroy(() => callback(null, true))
+ } else {
+ callback(null, false)
+ }
+ })
+ }
+
+ update (callback) {
+ new Registry({hive: 'HKCU', key: `${this.key}\\${this.parts[0].key}`})
+ .get(this.parts[0].name, (err, val) => {
+ if ((err != null) || (val == null)) {
+ callback(err)
+ } else {
+ this.register(callback)
+ }
+ })
+ }
+}
+
+exports.appName = appName
+
+exports.fileHandler = new ShellOption(`\\Software\\Classes\\Applications\\${exeName}`,
+ [
+ {key: 'shell\\open\\command', name: '', value: `${appPath} \"%1\"`},
+ {key: 'shell\\open', name: 'FriendlyAppName', value: `${appName}`},
+ {key: 'DefaultIcon', name: '', value: `${fileIconPath}`}
+ ]
+)
+
+let contextParts = [
+ {key: 'command', name: '', value: `${appPath} \"%1\"`},
+ {name: '', value: `Open with ${appName}`},
+ {name: 'Icon', value: `${appPath}`}
+]
+
+exports.fileContextMenu = new ShellOption(`\\Software\\Classes\\*\\shell\\${appName}`, contextParts)
+exports.folderContextMenu = new ShellOption(`\\Software\\Classes\\Directory\\shell\\${appName}`, contextParts)
+exports.folderBackgroundContextMenu = new ShellOption(`\\Software\\Classes\\Directory\\background\\shell\\${appName}`,
+ JSON.parse(JSON.stringify(contextParts).replace('%1', '%V'))
+)
diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js
index a9857fc0c..e4e7fc146 100644
--- a/src/native-compile-cache.js
+++ b/src/native-compile-cache.js
@@ -74,7 +74,13 @@ class NativeCompileCache {
self.cacheStore.delete(cacheKey)
}
} else {
- let compilationResult = cachedVm.runInThisContext(wrapper, filename)
+ let compilationResult
+ try {
+ compilationResult = cachedVm.runInThisContext(wrapper, filename)
+ } catch (err) {
+ console.error(`Error running script ${filename}`)
+ throw err
+ }
if (compilationResult.cacheBuffer) {
self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer)
}
diff --git a/src/package-manager.coffee b/src/package-manager.coffee
index 84a36dd78..fb4f7a658 100644
--- a/src/package-manager.coffee
+++ b/src/package-manager.coffee
@@ -39,6 +39,7 @@ class PackageManager
@activationHookEmitter = new Emitter
@packageDirPaths = []
@deferredActivationHooks = []
+ @triggeredActivationHooks = new Set()
if configDirPath? and not safeMode
if @devMode
@packageDirPaths.push(path.join(configDirPath, "dev", "packages"))
@@ -67,6 +68,7 @@ class PackageManager
@deactivatePackages()
@loadedPackages = {}
@packageStates = {}
+ @triggeredActivationHooks.clear()
###
Section: Event Subscription
@@ -460,12 +462,17 @@ class PackageManager
Promise.resolve(pack)
else if pack = @loadPackage(name)
@activatingPackages[pack.name] = pack
- pack.activate().then =>
+ activationPromise = pack.activate().then =>
if @activatingPackages[pack.name]?
delete @activatingPackages[pack.name]
@activePackages[pack.name] = pack
@emitter.emit 'did-activate-package', pack
pack
+
+ unless @deferredActivationHooks?
+ @triggeredActivationHooks.forEach((hook) => @activationHookEmitter.emit(hook))
+
+ activationPromise
else
Promise.reject(new Error("Failed to load package '#{name}'"))
@@ -476,6 +483,7 @@ class PackageManager
triggerActivationHook: (hook) ->
return new Error("Cannot trigger an empty activation hook") unless hook? and _.isString(hook) and hook.length > 0
+ @triggeredActivationHooks.add(hook)
if @deferredActivationHooks?
@deferredActivationHooks.push hook
else
diff --git a/src/package-transpilation-registry.js b/src/package-transpilation-registry.js
index 1e41d8f8b..b8e81ccad 100644
--- a/src/package-transpilation-registry.js
+++ b/src/package-transpilation-registry.js
@@ -96,7 +96,7 @@ class PackageTranspilationRegistry {
}
lastPath = thisPath
- thisPath = path.resolve(thisPath, '..')
+ thisPath = path.join(thisPath, '..')
}
this.specByFilePath[filePath] = null
diff --git a/src/package.coffee b/src/package.coffee
index 20236930b..63efbf02c 100644
--- a/src/package.coffee
+++ b/src/package.coffee
@@ -24,6 +24,7 @@ class Package
mainModulePath: null
resolvedMainModulePath: false
mainModule: null
+ mainInitialized: false
mainActivated: false
###
@@ -114,8 +115,24 @@ class Package
@menus = []
@grammars = []
@settings = []
+ @mainInitialized = false
@mainActivated = false
+ initializeIfNeeded: ->
+ return if @mainInitialized
+ @measure 'initializeTime', =>
+ try
+ # The main module's `initialize()` method is guaranteed to be called
+ # before its `activate()`. This gives you a chance to handle the
+ # serialized package state before the package's derserializers and view
+ # providers are used.
+ @requireMainModule() unless @mainModule?
+ @mainModule.initialize?(@packageManager.getPackageState(@name) ? {})
+ @mainInitialized = true
+ catch error
+ @handleError("Failed to initialize the #{@name} package", error)
+ return
+
activate: ->
@grammarsPromise ?= @loadGrammars()
@activationPromise ?=
@@ -140,10 +157,13 @@ class Package
@registerViewProviders()
@activateStylesheets()
if @mainModule? and not @mainActivated
+ @initializeIfNeeded()
@mainModule.activateConfig?()
@mainModule.activate?(@packageManager.getPackageState(@name) ? {})
@mainActivated = true
@activateServices()
+ @activationCommandSubscriptions?.dispose()
+ @activationHookSubscriptions?.dispose()
catch error
@handleError("Failed to activate the #{@name} package", error)
@@ -301,6 +321,7 @@ class Package
deserialize: (state, atomEnvironment) =>
@registerViewProviders()
@requireMainModule()
+ @initializeIfNeeded()
@mainModule[methodName](state, atomEnvironment)
return
@@ -318,6 +339,7 @@ class Package
@requireMainModule()
@metadata.viewProviders.forEach (methodName) =>
@viewRegistry.addViewProvider (model) =>
+ @initializeIfNeeded()
@mainModule[methodName](model)
@registeredViewProviders = true
@@ -420,6 +442,7 @@ class Package
@mainModule?.deactivate?()
@mainModule?.deactivateConfig?()
@mainActivated = false
+ @mainInitialized = false
catch e
console.error "Error deactivating package '#{@name}'", e.stack
@emitter.emit 'did-deactivate'
@@ -688,6 +711,9 @@ class Package
incompatibleNativeModules
handleError: (message, error) ->
+ if atom.inSpecMode()
+ throw error
+
if error.filename and error.location and (error instanceof SyntaxError)
location = "#{error.filename}:#{error.location.first_line + 1}:#{error.location.first_column + 1}"
detail = "#{error.message} in #{location}"
diff --git a/src/pane.coffee b/src/pane.coffee
index 268b728ec..c55c9f043 100644
--- a/src/pane.coffee
+++ b/src/pane.coffee
@@ -234,6 +234,39 @@ class Pane extends Model
onDidChangeActiveItem: (callback) ->
@emitter.on 'did-change-active-item', callback
+ # Public: Invoke the given callback when {::activateNextRecentlyUsedItem}
+ # has been called, either initiating or continuing a forward MRU traversal of
+ # pane items.
+ #
+ # * `callback` {Function} to be called with when the active item changes.
+ # * `nextRecentlyUsedItem` The next MRU item, now being set active
+ #
+ # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+ onChooseNextMRUItem: (callback) ->
+ @emitter.on 'choose-next-mru-item', callback
+
+ # Public: Invoke the given callback when {::activatePreviousRecentlyUsedItem}
+ # has been called, either initiating or continuing a reverse MRU traversal of
+ # pane items.
+ #
+ # * `callback` {Function} to be called with when the active item changes.
+ # * `previousRecentlyUsedItem` The previous MRU item, now being set active
+ #
+ # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+ onChooseLastMRUItem: (callback) ->
+ @emitter.on 'choose-last-mru-item', callback
+
+ # Public: Invoke the given callback when {::moveActiveItemToTopOfStack}
+ # has been called, terminating an MRU traversal of pane items and moving the
+ # current active item to the top of the stack. Typically bound to a modifier
+ # (e.g. CTRL) key up event.
+ #
+ # * `callback` {Function} to be called with when the MRU traversal is done.
+ #
+ # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+ onDoneChoosingMRUItem: (callback) ->
+ @emitter.on 'done-choosing-mru-item', callback
+
# Public: Invoke the given callback with the current and future values of
# {::getActiveItem}.
#
@@ -334,6 +367,7 @@ class Pane extends Model
@itemStackIndex = @itemStack.length if @itemStackIndex is 0
@itemStackIndex = @itemStackIndex - 1
nextRecentlyUsedItem = @itemStack[@itemStackIndex]
+ @emitter.emit 'choose-next-mru-item', nextRecentlyUsedItem
@setActiveItem(nextRecentlyUsedItem, modifyStack: false)
# Makes the previous item in the itemStack active.
@@ -343,12 +377,15 @@ class Pane extends Model
@itemStackIndex = -1
@itemStackIndex = @itemStackIndex + 1
previousRecentlyUsedItem = @itemStack[@itemStackIndex]
+ @emitter.emit 'choose-last-mru-item', previousRecentlyUsedItem
@setActiveItem(previousRecentlyUsedItem, modifyStack: false)
# Moves the active item to the end of the itemStack once the ctrl key is lifted
moveActiveItemToTopOfStack: ->
delete @itemStackIndex
@addItemToStack(@activeItem)
+ @emitter.emit 'done-choosing-mru-item'
+
# Public: Makes the next item active.
activateNextItem: ->
diff --git a/src/project.coffee b/src/project.coffee
index 52a3223e0..a02f27dac 100644
--- a/src/project.coffee
+++ b/src/project.coffee
@@ -21,7 +21,6 @@ class Project extends Model
constructor: ({@notificationManager, packageManager, config, @applicationDelegate}) ->
@emitter = new Emitter
@buffers = []
- @paths = []
@rootDirectories = []
@repositories = []
@directoryProviders = []
@@ -32,7 +31,9 @@ class Project extends Model
destroyed: ->
buffer.destroy() for buffer in @buffers
- @setPaths([])
+ repository?.destroy() for repository in @repositories
+ @rootDirectories = []
+ @repositories = []
reset: (packageManager) ->
@emitter.dispose()
@@ -62,6 +63,9 @@ class Project extends Model
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
+ unless bufferState.shouldDestroyOnFileDelete?
+ bufferState.shouldDestroyOnFileDelete =
+ -> atom.config.get('core.closeDeletedFileTabs')
TextBuffer.deserialize(bufferState)
@subscribeToBuffer(buffer) for buffer in @buffers
@@ -70,7 +74,15 @@ class Project extends Model
serialize: (options={}) ->
deserializer: 'Project'
paths: @getPaths()
- buffers: _.compact(@buffers.map (buffer) -> buffer.serialize({markerLayers: options.isUnloading is true}) if buffer.isRetained())
+ buffers: _.compact(@buffers.map (buffer) ->
+ if buffer.isRetained()
+ state = buffer.serialize({markerLayers: options.isUnloading is true})
+ # Skip saving large buffer text unless unloading to avoid blocking main thread
+ if not options.isUnloading and state.text.length > 2 * 1024 * 1024
+ delete state.text
+ delete state.digestWhenLastPersisted
+ state
+ )
###
Section: Event Subscription
@@ -197,7 +209,7 @@ class Project extends Model
removePath: (projectPath) ->
# The projectPath may be a URI, in which case it should not be normalized.
unless projectPath in @getPaths()
- projectPath = path.normalize(projectPath)
+ projectPath = @defaultDirectoryProvider.normalizePath(projectPath)
indexToRemove = null
for directory, i in @rootDirectories
@@ -225,11 +237,10 @@ class Project extends Model
uri
else
if fs.isAbsolute(uri)
- path.normalize(fs.absolute(uri))
-
+ @defaultDirectoryProvider.normalizePath(fs.resolveHome(uri))
# TODO: what should we do here when there are multiple directories?
else if projectPath = @getPaths()[0]
- path.normalize(fs.absolute(path.join(projectPath, uri)))
+ @defaultDirectoryProvider.normalizePath(fs.resolveHome(path.join(projectPath, uri)))
else
undefined
@@ -352,9 +363,14 @@ class Project extends Model
else
@buildBuffer(absoluteFilePath)
+ shouldDestroyBufferOnFileDelete: ->
+ atom.config.get('core.closeDeletedFileTabs')
+
# Still needed when deserializing a tokenized buffer
buildBufferSync: (absoluteFilePath) ->
- buffer = new TextBuffer({filePath: absoluteFilePath})
+ buffer = new TextBuffer({
+ filePath: absoluteFilePath
+ shouldDestroyOnFileDelete: @shouldDestroyBufferOnFileDelete})
@addBuffer(buffer)
buffer.loadSync()
buffer
@@ -366,7 +382,9 @@ class Project extends Model
#
# Returns a {Promise} that resolves to the {TextBuffer}.
buildBuffer: (absoluteFilePath) ->
- buffer = new TextBuffer({filePath: absoluteFilePath})
+ buffer = new TextBuffer({
+ filePath: absoluteFilePath
+ shouldDestroyOnFileDelete: @shouldDestroyBufferOnFileDelete})
@addBuffer(buffer)
buffer.load()
.then((buffer) -> buffer)
diff --git a/src/reopen-project-list-view.js b/src/reopen-project-list-view.js
index 0774c8db7..f08ee725a 100644
--- a/src/reopen-project-list-view.js
+++ b/src/reopen-project-list-view.js
@@ -1,59 +1,71 @@
/** @babel */
-import { SelectListView } from 'atom-space-pen-views'
+import SelectListView from 'atom-select-list'
-export default class ReopenProjectListView extends SelectListView {
- initialize (callback) {
+export default class ReopenProjectListView {
+ constructor (callback) {
this.callback = callback
- super.initialize()
- this.addClass('reopen-project')
- this.list.addClass('mark-active')
+ this.selectListView = new SelectListView({
+ emptyMessage: 'No projects in history.',
+ itemsClassList: ['mark-active'],
+ items: [],
+ filterKeyForItem: (project) => project.name,
+ elementForItem: (project) => {
+ let element = document.createElement('li')
+ if (project.name === this.currentProjectName) {
+ element.classList.add('active')
+ }
+ element.textContent = project.name
+ return element
+ },
+ didConfirmSelection: (project) => {
+ this.cancel()
+ this.callback(project.value)
+ },
+ didCancelSelection: () => {
+ this.cancel()
+ }
+ })
+ this.selectListView.element.classList.add('reopen-project')
}
- getFilterKey () {
- return 'name'
+ get element () {
+ return this.selectListView.element
}
- destroy () {
+ dispose () {
this.cancel()
+ return this.selectListView.destroy()
}
- viewForItem (project) {
- let element = document.createElement('li')
- if (project.name === this.currentProjectName) {
- element.classList.add('active')
- }
- element.textContent = project.name
- return element
- }
-
- cancelled () {
+ cancel () {
if (this.panel != null) {
this.panel.destroy()
}
this.panel = null
this.currentProjectName = null
- }
-
- confirmed (project) {
- this.cancel()
- this.callback(project.value)
+ if (this.previouslyFocusedElement) {
+ this.previouslyFocusedElement.focus()
+ this.previouslyFocusedElement = null
+ }
}
attach () {
- this.storeFocusedElement()
+ this.previouslyFocusedElement = document.activeElement
if (this.panel == null) {
this.panel = atom.workspace.addModalPanel({item: this})
}
- this.focusFilterEditor()
+ this.selectListView.focus()
+ this.selectListView.reset()
}
- toggle () {
+ async toggle () {
if (this.panel != null) {
this.cancel()
} else {
this.currentProjectName = atom.project != null ? this.makeName(atom.project.getPaths()) : null
- this.setItems(atom.history.getProjects().map(p => ({ name: this.makeName(p.paths), value: p.paths })))
+ const projects = atom.history.getProjects().map(p => ({ name: this.makeName(p.paths), value: p.paths }))
+ await this.selectListView.update({items: projects})
this.attach()
}
}
diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js
index 50c42e115..79acbba66 100644
--- a/src/reopen-project-menu-manager.js
+++ b/src/reopen-project-menu-manager.js
@@ -19,6 +19,8 @@ export default class ReopenProjectMenuManager {
}),
commands.add('atom-workspace', { 'application:reopen-project': this.reopenProjectCommand.bind(this) })
)
+
+ this.applyWindowsJumpListRemovals()
}
reopenProjectCommand (e) {
@@ -46,6 +48,58 @@ export default class ReopenProjectMenuManager {
this.projects = this.historyManager.getProjects().slice(0, this.config.get('core.reopenProjectMenuCount'))
const newMenu = ReopenProjectMenuManager.createProjectsMenu(this.projects)
this.lastProjectMenu = this.menuManager.add([newMenu])
+ this.updateWindowsJumpList()
+ }
+
+ static taskDescription (paths) {
+ return paths.map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`).join(' ')
+ }
+
+ // Windows users can right-click Atom taskbar and remove project from the jump list.
+ // We have to honor that or the group stops working. As we only get a partial list
+ // each time we remove them from history entirely.
+ applyWindowsJumpListRemovals () {
+ if (process.platform !== 'win32') return
+ if (this.app === undefined) {
+ this.app = require('remote').app
+ }
+
+ const removed = this.app.getJumpListSettings().removedItems.map(i => i.description)
+ if (removed.length === 0) return
+ for (let project of this.historyManager.getProjects()) {
+ if (removed.includes(ReopenProjectMenuManager.taskDescription(project.paths))) {
+ this.historyManager.removeProject(project.paths)
+ }
+ }
+ }
+
+ updateWindowsJumpList () {
+ if (process.platform !== 'win32') return
+ if (this.app === undefined) {
+ this.app = require('remote').app
+ }
+
+ this.app.setJumpList([
+ {
+ type: 'custom',
+ name: 'Recent Projects',
+ items: this.projects.map(project =>
+ ({
+ type: 'task',
+ title: project.paths.map(ReopenProjectMenuManager.betterBaseName).join(', '),
+ description: ReopenProjectMenuManager.taskDescription(project.paths),
+ program: process.execPath,
+ args: project.paths.map(path => `"${path}"`).join(' '),
+ iconPath: path.join(path.dirname(process.execPath), 'resources', 'cli', 'folder.ico'),
+ iconIndex: 0
+ })
+ )
+ },
+ { type: 'recent' },
+ { items: [
+ {type: 'task', title: 'New Window', program: process.execPath, args: '--new-window', description: 'Opens a new Atom window'}
+ ]}
+ ])
}
dispose () {
diff --git a/src/selection.coffee b/src/selection.coffee
index 5eaa9c8dd..8aa86157e 100644
--- a/src/selection.coffee
+++ b/src/selection.coffee
@@ -366,7 +366,7 @@ class Selection extends Model
insertText: (text, options={}) ->
oldBufferRange = @getBufferRange()
wasReversed = @isReversed()
- @clear()
+ @clear(options)
autoIndentFirstLine = false
precedingText = @editor.getTextInRange([[oldBufferRange.start.row, 0], oldBufferRange.start])
@@ -403,7 +403,7 @@ class Selection extends Model
else if options.autoDecreaseIndent and NonWhitespaceRegExp.test(text)
@editor.autoDecreaseIndentForBufferRow(newBufferRange.start.row)
- @autoscroll() if @isLastSelection()
+ @autoscroll() if options.autoscroll ? @isLastSelection()
newBufferRange
diff --git a/src/state-store.js b/src/state-store.js
index fe45bc086..b192d8b04 100644
--- a/src/state-store.js
+++ b/src/state-store.js
@@ -3,6 +3,7 @@
module.exports =
class StateStore {
constructor (databaseName, version) {
+ this.connected = false
this.dbPromise = new Promise((resolve) => {
let dbOpenRequest = indexedDB.open(databaseName, version)
dbOpenRequest.onupgradeneeded = (event) => {
@@ -10,15 +11,21 @@ class StateStore {
db.createObjectStore('states')
}
dbOpenRequest.onsuccess = () => {
+ this.connected = true
resolve(dbOpenRequest.result)
}
dbOpenRequest.onerror = (error) => {
console.error('Could not connect to indexedDB', error)
+ this.connected = false
resolve(null)
}
})
}
+ isConnected () {
+ return this.connected
+ }
+
connect () {
return this.dbPromise.then((db) => !!db)
}
diff --git a/src/style-manager.js b/src/style-manager.js
index 7ee11fd6d..0a0b401d3 100644
--- a/src/style-manager.js
+++ b/src/style-manager.js
@@ -250,59 +250,70 @@ module.exports = class StyleManager {
function transformDeprecatedShadowDOMSelectors (css, context) {
const transformedSelectors = []
- const transformedSource = postcss.parse(css)
- transformedSource.walkRules((rule) => {
- const transformedSelector = selectorParser((selectors) => {
- selectors.each((selector) => {
- const firstNode = selector.nodes[0]
- if (context === 'atom-text-editor' && firstNode.type === 'pseudo' && firstNode.value === ':host') {
- const atomTextEditorElementNode = selectorParser.tag({value: 'atom-text-editor'})
- firstNode.replaceWith(atomTextEditorElementNode)
- }
-
- let previousNodeIsAtomTextEditor = false
- let targetsAtomTextEditorShadow = context === 'atom-text-editor'
- let previousNode
- selector.each((node) => {
- if (targetsAtomTextEditorShadow && node.type === 'class') {
- if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) {
- node.value = `syntax--${node.value}`
- }
- } else {
- if (previousNodeIsAtomTextEditor && node.type === 'pseudo' && node.value === '::shadow') {
- node.type = 'className'
- node.value = '.editor'
- targetsAtomTextEditorShadow = true
- }
- }
-
- previousNode = node
- if (node.type === 'combinator') {
- previousNodeIsAtomTextEditor = false
- } else if (previousNode.type === 'tag' && previousNode.value === 'atom-text-editor') {
- previousNodeIsAtomTextEditor = true
- }
- })
- })
- }).process(rule.selector, {lossless: true}).result
- if (transformedSelector !== rule.selector) {
- transformedSelectors.push({before: rule.selector, after: transformedSelector})
- rule.selector = transformedSelector
- }
- })
- let deprecationMessage
- if (transformedSelectors.length > 0) {
- deprecationMessage = 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements '
- deprecationMessage += 'are no longer encapsulated within a shadow DOM boundary. '
- deprecationMessage += 'This means you should stop using `:host` and `::shadow` '
- deprecationMessage += 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. '
- deprecationMessage += 'To prevent breakage with existing style sheets, Atom will automatically '
- deprecationMessage += 'upgrade the following selectors:\n\n'
- deprecationMessage += transformedSelectors
- .map((selector) => `* \`${selector.before}\` => \`${selector.after}\``)
- .join('\n\n') + '\n\n'
- deprecationMessage += 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. '
- deprecationMessage += 'Please, make sure to upgrade the above selectors as soon as possible.'
+ let transformedSource
+ try {
+ transformedSource = postcss.parse(css)
+ } catch (e) {
+ transformedSource = null
+ }
+
+ if (transformedSource) {
+ transformedSource.walkRules((rule) => {
+ const transformedSelector = selectorParser((selectors) => {
+ selectors.each((selector) => {
+ const firstNode = selector.nodes[0]
+ if (context === 'atom-text-editor' && firstNode.type === 'pseudo' && firstNode.value === ':host') {
+ const atomTextEditorElementNode = selectorParser.tag({value: 'atom-text-editor'})
+ firstNode.replaceWith(atomTextEditorElementNode)
+ }
+
+ let previousNodeIsAtomTextEditor = false
+ let targetsAtomTextEditorShadow = context === 'atom-text-editor'
+ let previousNode
+ selector.each((node) => {
+ if (targetsAtomTextEditorShadow && node.type === 'class') {
+ if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) {
+ node.value = `syntax--${node.value}`
+ }
+ } else {
+ if (previousNodeIsAtomTextEditor && node.type === 'pseudo' && node.value === '::shadow') {
+ node.type = 'className'
+ node.value = '.editor'
+ targetsAtomTextEditorShadow = true
+ }
+ }
+
+ previousNode = node
+ if (node.type === 'combinator') {
+ previousNodeIsAtomTextEditor = false
+ } else if (previousNode.type === 'tag' && previousNode.value === 'atom-text-editor') {
+ previousNodeIsAtomTextEditor = true
+ }
+ })
+ })
+ }).process(rule.selector, {lossless: true}).result
+ if (transformedSelector !== rule.selector) {
+ transformedSelectors.push({before: rule.selector, after: transformedSelector})
+ rule.selector = transformedSelector
+ }
+ })
+ let deprecationMessage
+ if (transformedSelectors.length > 0) {
+ deprecationMessage = 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements '
+ deprecationMessage += 'are no longer encapsulated within a shadow DOM boundary. '
+ deprecationMessage += 'This means you should stop using `:host` and `::shadow` '
+ deprecationMessage += 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. '
+ deprecationMessage += 'To prevent breakage with existing style sheets, Atom will automatically '
+ deprecationMessage += 'upgrade the following selectors:\n\n'
+ deprecationMessage += transformedSelectors
+ .map((selector) => `* \`${selector.before}\` => \`${selector.after}\``)
+ .join('\n\n') + '\n\n'
+ deprecationMessage += 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. '
+ deprecationMessage += 'Please, make sure to upgrade the above selectors as soon as possible.'
+ }
+ return {source: transformedSource.toString(), deprecationMessage}
+ } else {
+ // CSS was malformed so we don't transform it.
+ return {source: css}
}
- return {source: transformedSource.toString(), deprecationMessage}
}
diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee
index 8fa732fea..5dfbced62 100644
--- a/src/text-editor-component.coffee
+++ b/src/text-editor-component.coffee
@@ -42,7 +42,7 @@ class TextEditorComponent
@assert domNode?, "TextEditorComponent::domNode was set to null."
@domNodeValue = domNode
- constructor: ({@editor, @hostElement, tileSize, @views, @themes, @styles, @assert}) ->
+ constructor: ({@editor, @hostElement, tileSize, @views, @themes, @styles, @assert, hiddenInputElement}) ->
@tileSize = tileSize if tileSize?
@disposables = new CompositeDisposable
@@ -70,12 +70,12 @@ class TextEditorComponent
@scrollViewNode.classList.add('scroll-view')
@domNode.appendChild(@scrollViewNode)
- @hiddenInputComponent = new InputComponent
- @scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
+ @hiddenInputComponent = new InputComponent(hiddenInputElement)
+ @scrollViewNode.appendChild(hiddenInputElement)
# Add a getModel method to the hidden input component to make it easy to
# access the editor in response to DOM events or when using
# document.activeElement.
- @hiddenInputComponent.getDomNode().getModel = => @editor
+ hiddenInputElement.getModel = => @editor
@linesComponent = new LinesComponent({@presenter, @domElementPool, @assert, @grammars, @views})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
@@ -346,7 +346,6 @@ class TextEditorComponent
focused: ->
if @mounted
@presenter.setFocused(true)
- @hiddenInputComponent.getDomNode().focus()
blurred: ->
if @mounted
@@ -420,7 +419,6 @@ class TextEditorComponent
onScrollViewScroll: =>
if @mounted
- console.warn "TextEditorScrollView scrolled when it shouldn't have."
@scrollViewNode.scrollTop = 0
@scrollViewNode.scrollLeft = 0
@@ -616,7 +614,7 @@ class TextEditorComponent
screenRange = new Range(startPosition, startPosition).union(initialRange)
@editor.getLastSelection().setScreenRange(screenRange, reversed: true, autoscroll: false, preserveFolds: true)
else
- endPosition = [dragRow + 1, 0]
+ endPosition = @editor.clipScreenPosition([dragRow + 1, 0], clipDirection: 'backward')
screenRange = new Range(endPosition, endPosition).union(initialRange)
@editor.getLastSelection().setScreenRange(screenRange, reversed: false, autoscroll: false, preserveFolds: true)
@@ -909,7 +907,7 @@ class TextEditorComponent
screenRowForNode: (node) ->
while node?
- if screenRow = node.dataset.screenRow
+ if screenRow = node.dataset?.screenRow
return parseInt(screenRow)
node = node.parentElement
null
diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee
index 4a7d1598d..26e3bae12 100644
--- a/src/text-editor-element.coffee
+++ b/src/text-editor-element.coffee
@@ -25,8 +25,17 @@ class TextEditorElement extends HTMLElement
@emitter = new Emitter
@subscriptions = new CompositeDisposable
+ @hiddenInputElement = document.createElement('input')
+ @hiddenInputElement.classList.add('hidden-input')
+ @hiddenInputElement.setAttribute('tabindex', -1)
+ @hiddenInputElement.setAttribute('data-react-skip-selection-restoration', true)
+ @hiddenInputElement.style['-webkit-transform'] = 'translateZ(0)'
+ @hiddenInputElement.addEventListener 'paste', (event) -> event.preventDefault()
+
@addEventListener 'focus', @focused.bind(this)
@addEventListener 'blur', @blurred.bind(this)
+ @hiddenInputElement.addEventListener 'focus', @focused.bind(this)
+ @hiddenInputElement.addEventListener 'blur', @inputNodeBlurred.bind(this)
@classList.add('editor')
@setAttribute('tabindex', -1)
@@ -99,7 +108,10 @@ class TextEditorElement extends HTMLElement
buildModel: ->
@setModel(@workspace.buildTextEditor(
- buffer: new TextBuffer(@textContent)
+ buffer: new TextBuffer({
+ text: @textContent
+ shouldDestroyOnFileDelete:
+ -> atom.config.get('core.closeDeletedFileTabs')})
softWrapped: false
tabLength: 2
softTabs: true
@@ -117,12 +129,10 @@ class TextEditorElement extends HTMLElement
themes: @themes
styles: @styles
workspace: @workspace
- assert: @assert
+ assert: @assert,
+ hiddenInputElement: @hiddenInputElement
)
@rootElement.appendChild(@component.getDomNode())
- inputNode = @component.hiddenInputComponent.getDomNode()
- inputNode.addEventListener 'focus', @focused.bind(this)
- inputNode.addEventListener 'blur', @inputNodeBlurred.bind(this)
unmountComponent: ->
if @component?
@@ -132,16 +142,17 @@ class TextEditorElement extends HTMLElement
focused: (event) ->
@component?.focused()
+ @hiddenInputElement.focus()
blurred: (event) ->
- if event.relatedTarget is @component?.hiddenInputComponent.getDomNode()
+ if event.relatedTarget is @hiddenInputElement
event.stopImmediatePropagation()
return
@component?.blurred()
inputNodeBlurred: (event) ->
if event.relatedTarget isnt this
- @dispatchEvent(new FocusEvent('blur', bubbles: false))
+ @dispatchEvent(new FocusEvent('blur', relatedTarget: event.relatedTarget, bubbles: false))
addGrammarScopeAttribute: ->
@dataset.grammar = @model.getGrammar()?.scopeName?.replace(/\./g, ' ')
diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee
index cc988bbea..1106cee09 100644
--- a/src/text-editor-presenter.coffee
+++ b/src/text-editor-presenter.coffee
@@ -451,7 +451,7 @@ class TextEditorPresenter
for decoration in @model.getOverlayDecorations()
continue unless decoration.getMarker().isValid()
- {item, position, class: klass} = decoration.getProperties()
+ {item, position, class: klass, avoidOverflow} = decoration.getProperties()
if position is 'tail'
screenPosition = decoration.getMarker().getTailScreenPosition()
else
@@ -466,15 +466,16 @@ class TextEditorPresenter
if overlayDimensions = @overlayDimensions[decoration.id]
{itemWidth, itemHeight, contentMargin} = overlayDimensions
- rightDiff = left + itemWidth + contentMargin - @windowWidth
- left -= rightDiff if rightDiff > 0
+ if avoidOverflow isnt false
+ rightDiff = left + itemWidth + contentMargin - @windowWidth
+ left -= rightDiff if rightDiff > 0
- leftDiff = left + contentMargin
- left -= leftDiff if leftDiff < 0
+ leftDiff = left + contentMargin
+ left -= leftDiff if leftDiff < 0
- if top + itemHeight > @windowHeight and
- top - (itemHeight + @lineHeight) >= 0
- top -= itemHeight + @lineHeight
+ if top + itemHeight > @windowHeight and
+ top - (itemHeight + @lineHeight) >= 0
+ top -= itemHeight + @lineHeight
pixelPosition.top = top
pixelPosition.left = left
@@ -493,7 +494,10 @@ class TextEditorPresenter
return
updateLineNumberGutterState: ->
- @lineNumberGutter.maxLineNumberDigits = @model.getLineCount().toString().length
+ @lineNumberGutter.maxLineNumberDigits = Math.max(
+ 2,
+ @model.getLineCount().toString().length
+ )
updateCommonGutterState: ->
@sharedGutterStyles.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
@@ -596,7 +600,8 @@ class TextEditorPresenter
line = @linesByScreenRow.get(screenRow)
continue unless line?
lineId = line.id
- {bufferRow, softWrappedAtStart: softWrapped} = @displayLayer.softWrapDescriptorForScreenRow(screenRow)
+ {row: bufferRow, column: bufferColumn} = @displayLayer.translateScreenPosition(Point(screenRow, 0))
+ softWrapped = bufferColumn isnt 0
foldable = not softWrapped and @model.isFoldableAtBufferRow(bufferRow)
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow)
@@ -617,6 +622,18 @@ class TextEditorPresenter
return unless @scrollTop? and @lineHeight?
@startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop))
+ atom.assert(
+ Number.isFinite(@startRow),
+ 'Invalid start row',
+ (error) =>
+ error.metadata = {
+ startRow: @startRow?.toString(),
+ scrollTop: @scrollTop?.toString(),
+ scrollHeight: @scrollHeight?.toString(),
+ clientHeight: @clientHeight?.toString(),
+ lineHeight: @lineHeight?.toString()
+ }
+ )
updateEndRow: ->
return unless @scrollTop? and @lineHeight? and @height?
@@ -1001,8 +1018,7 @@ class TextEditorPresenter
@lineHeight? and @baseCharacterWidth?
pixelPositionForScreenPosition: (screenPosition) ->
- position =
- @linesYardstick.pixelPositionForScreenPosition(screenPosition)
+ position = @linesYardstick.pixelPositionForScreenPosition(screenPosition)
position.top -= @getScrollTop()
position.left -= @getScrollLeft()
@@ -1140,7 +1156,9 @@ class TextEditorPresenter
@lineNumberDecorationsByScreenRow[screenRow] ?= {}
@lineNumberDecorationsByScreenRow[screenRow][decorationId] = properties
else
- for row in [screenRange.start.row..screenRange.end.row] by 1
+ startRow = Math.max(screenRange.start.row, @getStartTileRow())
+ endRow = Math.min(screenRange.end.row, @getEndTileRow() + @tileSize)
+ for row in [startRow..endRow] by 1
continue if properties.onlyHead and row isnt headScreenPosition.row
continue if omitLastRow and row is screenRange.end.row
@@ -1225,13 +1243,14 @@ class TextEditorPresenter
screenRange.end.column = 0
repositionRegionWithinTile: (region, tileStartRow) ->
- region.top += @scrollTop - @lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow)
- region.left += @scrollLeft
+ region.top += @scrollTop - @lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow)
buildHighlightRegions: (screenRange) ->
lineHeightInPixels = @lineHeight
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start)
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end)
+ startPixelPosition.left += @scrollLeft
+ endPixelPosition.left += @scrollLeft
spannedRows = screenRange.end.row - screenRange.start.row + 1
regions = []
@@ -1408,11 +1427,10 @@ class TextEditorPresenter
@emitDidUpdateState()
pauseCursorBlinking: ->
- if @isCursorBlinking()
- @stopBlinkingCursors(true)
- @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
- @startBlinkingCursorsAfterDelay()
- @emitDidUpdateState()
+ @stopBlinkingCursors(true)
+ @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
+ @startBlinkingCursorsAfterDelay()
+ @emitDidUpdateState()
requestAutoscroll: (position) ->
@pendingScrollLogicalPosition = position
diff --git a/src/text-editor-registry.js b/src/text-editor-registry.js
index 2c66ae90e..3343ce89c 100644
--- a/src/text-editor-registry.js
+++ b/src/text-editor-registry.js
@@ -11,6 +11,7 @@ const EDITOR_PARAMS_BY_SETTING_KEY = [
['editor.showInvisibles', 'showInvisibles'],
['editor.tabLength', 'tabLength'],
['editor.invisibles', 'invisibles'],
+ ['editor.showCursorOnSelection', 'showCursorOnSelection'],
['editor.showIndentGuide', 'showIndentGuide'],
['editor.showLineNumbers', 'showLineNumbers'],
['editor.softWrap', 'softWrapped'],
diff --git a/src/text-editor.coffee b/src/text-editor.coffee
index 497dd3c20..13b8a0bfa 100644
--- a/src/text-editor.coffee
+++ b/src/text-editor.coffee
@@ -16,12 +16,11 @@ TextEditorElement = require './text-editor-element'
{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary} = require './text-utils'
ZERO_WIDTH_NBSP = '\ufeff'
+MAX_SCREEN_LINE_LENGTH = 500
# Essential: This class represents all essential editing state for a single
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
-# If you're manipulating the state of an editor, use this class. If you're
-# interested in the visual appearance of editors, use {TextEditorElement}
-# instead.
+# If you're manipulating the state of an editor, use this class.
#
# A single {TextBuffer} can belong to multiple editors. For example, if the
# same file is open in two different panes, Atom creates a separate editor for
@@ -67,6 +66,7 @@ class TextEditor extends Model
buffer: null
languageMode: null
cursors: null
+ showCursorOnSelection: null
selections: null
suppressSelectionMerging: false
selectionFlashDuration: 500
@@ -114,9 +114,6 @@ class TextEditor extends Model
throw error
state.buffer = state.tokenizedBuffer.buffer
- if state.displayLayer = state.buffer.getDisplayLayer(state.displayLayerId)
- state.selectionsMarkerLayer = state.displayLayer.getMarkerLayer(state.selectionsMarkerLayerId)
-
state.assert = atomEnvironment.assert.bind(atomEnvironment)
editor = new this(state)
if state.registered
@@ -136,7 +133,8 @@ class TextEditor extends Model
@mini, @placeholderText, lineNumberGutterVisible, @largeFileMode,
@assert, grammar, @showInvisibles, @autoHeight, @autoWidth, @scrollPastEnd, @editorWidthInChars,
@tokenizedBuffer, @displayLayer, @invisibles, @showIndentGuide,
- @softWrapped, @softWrapAtPreferredLineLength, @preferredLineLength
+ @softWrapped, @softWrapAtPreferredLineLength, @preferredLineLength,
+ @showCursorOnSelection
} = params
@assert ?= (condition) -> condition
@@ -156,33 +154,37 @@ class TextEditor extends Model
tabLength ?= 2
@autoIndent ?= true
@autoIndentOnPaste ?= true
+ @showCursorOnSelection ?= true
@undoGroupingInterval ?= 300
@nonWordCharacters ?= "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…"
@softWrapped ?= false
@softWrapAtPreferredLineLength ?= false
@preferredLineLength ?= 80
- @buffer ?= new TextBuffer
+ @buffer ?= new TextBuffer({shouldDestroyOnFileDelete: ->
+ atom.config.get('core.closeDeletedFileTabs')})
@tokenizedBuffer ?= new TokenizedBuffer({
grammar, tabLength, @buffer, @largeFileMode, @assert
})
- displayLayerParams = {
- invisibles: @getInvisibles(),
- softWrapColumn: @getSoftWrapColumn(),
- showIndentGuides: not @isMini() and @doesShowIndentGuide(),
- atomicSoftTabs: params.atomicSoftTabs ? true,
- tabLength: tabLength,
- ratioForCharacter: @ratioForCharacter.bind(this),
- isWrapBoundary: isWrapBoundary,
- foldCharacter: ZERO_WIDTH_NBSP,
- softWrapHangingIndent: params.softWrapHangingIndentLength ? 0
- }
+ unless @displayLayer?
+ displayLayerParams = {
+ invisibles: @getInvisibles(),
+ softWrapColumn: @getSoftWrapColumn(),
+ showIndentGuides: not @isMini() and @doesShowIndentGuide(),
+ atomicSoftTabs: params.atomicSoftTabs ? true,
+ tabLength: tabLength,
+ ratioForCharacter: @ratioForCharacter.bind(this),
+ isWrapBoundary: isWrapBoundary,
+ foldCharacter: ZERO_WIDTH_NBSP,
+ softWrapHangingIndent: params.softWrapHangingIndentLength ? 0
+ }
- if @displayLayer?
- @displayLayer.reset(displayLayerParams)
- else
- @displayLayer = @buffer.addDisplayLayer(displayLayerParams)
+ if @displayLayer = @buffer.getDisplayLayer(params.displayLayerId)
+ @displayLayer.reset(displayLayerParams)
+ @selectionsMarkerLayer = @displayLayer.getMarkerLayer(params.selectionsMarkerLayerId)
+ else
+ @displayLayer = @buffer.addDisplayLayer(displayLayerParams)
@backgroundWorkHandle = requestIdleCallback(@doBackgroundWork)
@disposables.add new Disposable =>
@@ -191,8 +193,9 @@ class TextEditor extends Model
@displayLayer.setTextDecorationLayer(@tokenizedBuffer)
@defaultMarkerLayer = @displayLayer.addMarkerLayer()
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true)
+ @selectionsMarkerLayer.trackDestructionInOnDidCreateMarkerCallbacks = true
- @decorationManager = new DecorationManager(@displayLayer, @defaultMarkerLayer)
+ @decorationManager = new DecorationManager(@displayLayer)
@decorateMarkerLayer(@displayLayer.foldsMarkerLayer, {type: 'line-number', class: 'folded'})
for marker in @selectionsMarkerLayer.getMarkers()
@@ -222,7 +225,6 @@ class TextEditor extends Model
@backgroundWorkHandle = null
update: (params) ->
- currentSoftWrapColumn = @getSoftWrapColumn()
displayLayerParams = {}
for param in Object.keys(params)
@@ -273,16 +275,12 @@ class TextEditor extends Model
when 'softWrapAtPreferredLineLength'
if value isnt @softWrapAtPreferredLineLength
@softWrapAtPreferredLineLength = value
- softWrapColumn = @getSoftWrapColumn()
- if softWrapColumn isnt currentSoftWrapColumn
- displayLayerParams.softWrapColumn = softWrapColumn
+ displayLayerParams.softWrapColumn = @getSoftWrapColumn()
when 'preferredLineLength'
if value isnt @preferredLineLength
@preferredLineLength = value
- softWrapColumn = @getSoftWrapColumn()
- if softWrapColumn isnt currentSoftWrapColumn
- displayLayerParams.softWrapColumn = softWrapColumn
+ displayLayerParams.softWrapColumn = @getSoftWrapColumn()
when 'mini'
if value isnt @mini
@@ -327,16 +325,12 @@ class TextEditor extends Model
when 'editorWidthInChars'
if value > 0 and value isnt @editorWidthInChars
@editorWidthInChars = value
- softWrapColumn = @getSoftWrapColumn()
- if softWrapColumn isnt currentSoftWrapColumn
- displayLayerParams.softWrapColumn = softWrapColumn
+ displayLayerParams.softWrapColumn = @getSoftWrapColumn()
when 'width'
if value isnt @width
@width = value
- softWrapColumn = @getSoftWrapColumn()
- if softWrapColumn isnt currentSoftWrapColumn
- displayLayerParams.softWrapColumn = softWrapColumn
+ displayLayerParams.softWrapColumn = @getSoftWrapColumn()
when 'scrollPastEnd'
if value isnt @scrollPastEnd
@@ -352,11 +346,16 @@ class TextEditor extends Model
if value isnt @autoWidth
@autoWidth = value
@presenter?.didChangeAutoWidth()
+
+ when 'showCursorOnSelection'
+ if value isnt @showCursorOnSelection
+ @showCursorOnSelection = value
+ cursor.setShowCursorOnSelection(value) for cursor in @getCursors()
+
else
throw new TypeError("Invalid TextEditor parameter: '#{param}'")
- if Object.keys(displayLayerParams).length > 0
- @displayLayer.reset(displayLayerParams)
+ @displayLayer.reset(displayLayerParams)
if @editorElement?
@editorElement.views.getNextUpdatePromise()
@@ -421,14 +420,15 @@ class TextEditor extends Model
destroyed: ->
@disposables.dispose()
@displayLayer.destroy()
- @disposables.dispose()
@tokenizedBuffer.destroy()
selection.destroy() for selection in @selections.slice()
- @selectionsMarkerLayer.destroy()
@buffer.release()
@languageMode.destroy()
@gutterContainer.destroy()
@emitter.emit 'did-destroy'
+ @emitter.clear()
+ @editorElement = null
+ @presenter = null
###
Section: Event Subscription
@@ -732,7 +732,7 @@ class TextEditor extends Model
tabLength: @tokenizedBuffer.getTabLength(),
@firstVisibleScreenRow, @firstVisibleScreenColumn,
@assert, displayLayer, grammar: @getGrammar(),
- @autoWidth, @autoHeight
+ @autoWidth, @autoHeight, @showCursorOnSelection
})
# Controls visibility based on the given {Boolean}.
@@ -902,7 +902,7 @@ class TextEditor extends Model
# Determine whether the user should be prompted to save before closing
# this editor.
shouldPromptToSave: ({windowCloseRequested, projectHasPaths}={}) ->
- if windowCloseRequested and projectHasPaths
+ if windowCloseRequested and projectHasPaths and atom.stateStore.isConnected()
false
else
@isModified() and not @buffer.hasMultipleEditors()
@@ -991,10 +991,7 @@ class TextEditor extends Model
@bufferRowForScreenRow(screenRow)
screenRowForBufferRow: (row) ->
- if @largeFileMode
- row
- else
- @displayLayer.translateBufferPosition(Point(row, 0)).row
+ @displayLayer.translateBufferPosition(Point(row, 0)).row
getRightmostScreenPosition: -> @displayLayer.getRightmostScreenPosition()
@@ -1085,8 +1082,8 @@ class TextEditor extends Model
)
# Essential: For each selection, replace the selected text with a newline.
- insertNewline: ->
- @insertText('\n')
+ insertNewline: (options) ->
+ @insertText('\n', options)
# Essential: For each selection, if the selection is empty, delete the character
# following the cursor. Otherwise delete the selected text.
@@ -1143,13 +1140,13 @@ class TextEditor extends Model
# Don't move the last line of a multi-line selection if the selection ends at column 0
endRow--
- {bufferRow: startRow} = @displayLayer.lineStartBoundaryForBufferRow(startRow)
- {bufferRow: endRow} = @displayLayer.lineEndBoundaryForBufferRow(endRow)
+ startRow = @displayLayer.findBoundaryPrecedingBufferRow(startRow)
+ endRow = @displayLayer.findBoundaryFollowingBufferRow(endRow + 1)
linesRange = new Range(Point(startRow, 0), Point(endRow, 0))
# If selected line range is preceded by a fold, one line above on screen
# could be multiple lines in the buffer.
- {bufferRow: precedingRow} = @displayLayer.lineStartBoundaryForBufferRow(startRow - 1)
+ precedingRow = @displayLayer.findBoundaryPrecedingBufferRow(startRow - 1)
insertDelta = linesRange.start.row - precedingRow
# Any folds in the text that is moved will need to be re-created.
@@ -1205,15 +1202,15 @@ class TextEditor extends Model
# Don't move the last line of a multi-line selection if the selection ends at column 0
endRow--
- {bufferRow: startRow} = @displayLayer.lineStartBoundaryForBufferRow(startRow)
- {bufferRow: endRow} = @displayLayer.lineEndBoundaryForBufferRow(endRow)
+ startRow = @displayLayer.findBoundaryPrecedingBufferRow(startRow)
+ endRow = @displayLayer.findBoundaryFollowingBufferRow(endRow + 1)
linesRange = new Range(Point(startRow, 0), Point(endRow, 0))
# If selected line range is followed by a fold, one line below on screen
# could be multiple lines in the buffer. But at the same time, if the
# next buffer row is wrapped, one line in the buffer can represent many
# screen rows.
- {bufferRow: followingRow} = @displayLayer.lineEndBoundaryForBufferRow(endRow)
+ followingRow = Math.min(@buffer.getLineCount(), @displayLayer.findBoundaryFollowingBufferRow(endRow + 1))
insertDelta = followingRow - linesRange.end.row
# Any folds in the text that is moved will need to be re-created.
@@ -1285,30 +1282,44 @@ class TextEditor extends Model
@setSelectedBufferRanges(translatedRanges)
- # Duplicate the most recent cursor's current line.
duplicateLines: ->
@transact =>
- for selection in @getSelectionsOrderedByBufferPosition().reverse()
- selectedBufferRange = selection.getBufferRange()
- if selection.isEmpty()
- {start} = selection.getScreenRange()
- selection.setScreenRange([[start.row, 0], [start.row + 1, 0]], preserveFolds: true)
+ selections = @getSelectionsOrderedByBufferPosition()
+ previousSelectionRanges = []
- [startRow, endRow] = selection.getBufferRowRange()
+ i = selections.length - 1
+ while i >= 0
+ j = i
+ previousSelectionRanges[i] = selections[i].getBufferRange()
+ if selections[i].isEmpty()
+ {start} = selections[i].getScreenRange()
+ selections[i].setScreenRange([[start.row, 0], [start.row + 1, 0]], preserveFolds: true)
+ [startRow, endRow] = selections[i].getBufferRowRange()
endRow++
+ while i > 0
+ [previousSelectionStartRow, previousSelectionEndRow] = selections[i - 1].getBufferRowRange()
+ if previousSelectionEndRow is startRow
+ startRow = previousSelectionStartRow
+ previousSelectionRanges[i - 1] = selections[i - 1].getBufferRange()
+ i--
+ else
+ break
intersectingFolds = @displayLayer.foldsIntersectingBufferRange([[startRow, 0], [endRow, 0]])
- rangeToDuplicate = [[startRow, 0], [endRow, 0]]
- textToDuplicate = @getTextInBufferRange(rangeToDuplicate)
+ textToDuplicate = @getTextInBufferRange([[startRow, 0], [endRow, 0]])
textToDuplicate = '\n' + textToDuplicate if endRow > @getLastBufferRow()
@buffer.insert([endRow, 0], textToDuplicate)
- delta = endRow - startRow
- selection.setBufferRange(selectedBufferRange.translate([delta, 0]))
+ insertedRowCount = endRow - startRow
+
+ for k in [i..j] by 1
+ selections[k].setBufferRange(previousSelectionRanges[k].translate([insertedRowCount, 0]))
+
for fold in intersectingFolds
foldRange = @displayLayer.bufferRangeForFold(fold)
- @displayLayer.foldBufferRange(foldRange.translate([delta, 0]))
- return
+ @displayLayer.foldBufferRange(foldRange.translate([insertedRowCount, 0]))
+
+ i--
replaceSelectedText: (options={}, fn) ->
{selectWordIfEmpty} = options
@@ -1749,10 +1760,14 @@ class TextEditor extends Model
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
# if the associated `DisplayMarker` is non-empty. Only applicable to the
# `gutter`, `line`, and `line-number` types.
- # * `position` (optional) Only applicable to decorations of type `overlay` and `block`,
- # controls where the view is positioned relative to the `TextEditorMarker`.
+ # * `position` (optional) Only applicable to decorations of type `overlay` and `block`.
+ # Controls where the view is positioned relative to the `TextEditorMarker`.
# Values can be `'head'` (the default) or `'tail'` for overlay decorations, and
# `'before'` (the default) or `'after'` for block decorations.
+ # * `avoidOverflow` (optional) Only applicable to decorations of type
+ # `overlay`. Determines whether the decoration adjusts its horizontal or
+ # vertical position to remain fully visible when it would otherwise
+ # overflow the editor. Defaults to `true`.
#
# Returns a {Decoration} object
decorateMarker: (marker, decorationParams) ->
@@ -2264,13 +2279,12 @@ class TextEditor extends Model
# Add a cursor based on the given {DisplayMarker}.
addCursor: (marker) ->
- cursor = new Cursor(editor: this, marker: marker)
+ cursor = new Cursor(editor: this, marker: marker, showCursorOnSelection: @showCursorOnSelection)
@cursors.push(cursor)
@cursorsByMarkerId.set(marker.id, cursor)
@decorateMarker(marker, type: 'line-number', class: 'cursor-line')
@decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
@decorateMarker(marker, type: 'line', class: 'cursor-line', onlyEmpty: true)
- @emitter.emit 'did-add-cursor', cursor
cursor
moveCursors: (fn) ->
@@ -2759,6 +2773,7 @@ class TextEditor extends Model
if selection.intersectsBufferRange(selectionBufferRange)
return selection
else
+ @emitter.emit 'did-add-cursor', cursor
@emitter.emit 'did-add-selection', selection
selection
@@ -2925,11 +2940,7 @@ class TextEditor extends Model
# Essential: Determine whether lines in this editor are soft-wrapped.
#
# Returns a {Boolean}.
- isSoftWrapped: ->
- if @largeFileMode
- false
- else
- @softWrapped
+ isSoftWrapped: -> @softWrapped
# Essential: Enable or disable soft wrapping for this editor.
#
@@ -2955,7 +2966,7 @@ class TextEditor extends Model
else
@getEditorWidthInChars()
else
- Infinity
+ MAX_SCREEN_LINE_LENGTH
###
Section: Indentation
@@ -3465,6 +3476,11 @@ class TextEditor extends Model
# Returns a positive {Number}.
getScrollSensitivity: -> @scrollSensitivity
+ # Experimental: Does this editor show cursors while there is a selection?
+ #
+ # Returns a positive {Boolean}.
+ getShowCursorOnSelection: -> @showCursorOnSelection
+
# Experimental: Are line numbers enabled for this editor?
#
# Returns a {Boolean}
diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee
index 32fabf724..58297b2db 100644
--- a/src/theme-manager.coffee
+++ b/src/theme-manager.coffee
@@ -178,7 +178,8 @@ class ThemeManager
@requireStylesheet(nativeStylesheetPath)
stylesheetElementForId: (id) ->
- document.head.querySelector("atom-styles style[source-path=\"#{id}\"]")
+ escapedId = id.replace(/\\/g, '\\\\')
+ document.head.querySelector("atom-styles style[source-path=\"#{escapedId}\"]")
resolveStylesheet: (stylesheetPath) ->
if path.extname(stylesheetPath).length > 0
@@ -231,9 +232,6 @@ class ThemeManager
applyStylesheet: (path, text) ->
@styleSheetDisposablesBySourcePath[path] = @styleManager.addStyleSheet(text, sourcePath: path)
- stringToId: (string) ->
- string.replace(/\\/g, '/')
-
activateThemes: ->
new Promise (resolve) =>
# @config.observe runs the callback once, then on subsequent changes.
diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee
index ce56e0388..77221f52e 100644
--- a/src/tokenized-buffer.coffee
+++ b/src/tokenized-buffer.coffee
@@ -8,6 +8,8 @@ ScopeDescriptor = require './scope-descriptor'
TokenizedBufferIterator = require './tokenized-buffer-iterator'
NullGrammar = require './null-grammar'
+MAX_LINE_LENGTH_TO_TOKENIZE = 500
+
module.exports =
class TokenizedBuffer extends Model
grammar: null
@@ -41,6 +43,7 @@ class TokenizedBuffer extends Model
destroyed: ->
@disposables.dispose()
+ @tokenizedLines.length = 0
buildIterator: ->
new TokenizedBufferIterator(this)
@@ -94,6 +97,7 @@ class TokenizedBuffer extends Model
false
retokenizeLines: ->
+ return unless @alive
@fullyTokenized = false
@tokenizedLines = new Array(@buffer.getLineCount())
@invalidRows = []
@@ -198,10 +202,7 @@ class TokenizedBuffer extends Model
@invalidateRow(end + delta + 1)
isFoldableAtRow: (row) ->
- if @largeFileMode
- false
- else
- @isFoldableCodeAtRow(row) or @isFoldableCommentAtRow(row)
+ @isFoldableCodeAtRow(row) or @isFoldableCommentAtRow(row)
# Returns a {Boolean} indicating whether the given buffer row starts
# a a foldable row range due to the code's indentation patterns.
@@ -252,6 +253,8 @@ class TokenizedBuffer extends Model
buildTokenizedLineForRowWithText: (row, text, ruleStack = @stackForRow(row - 1), openScopes = @openScopesForRow(row)) ->
lineEnding = @buffer.lineEndingForRow(row)
+ if text.length > MAX_LINE_LENGTH_TO_TOKENIZE
+ text = text.slice(0, MAX_LINE_LENGTH_TO_TOKENIZE)
{tags, ruleStack} = @grammar.tokenizeLine(text, ruleStack, row is 0, false)
new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator})
diff --git a/src/tooltip-manager.coffee b/src/tooltip-manager.coffee
index 4419ec740..03630c87f 100644
--- a/src/tooltip-manager.coffee
+++ b/src/tooltip-manager.coffee
@@ -56,6 +56,7 @@ class TooltipManager
{delay: {show: 1000, hide: 100}}
constructor: ({@keymapManager, @viewRegistry}) ->
+ @tooltips = new Map()
# Essential: Add a tooltip to the given element.
#
@@ -129,19 +130,42 @@ class TooltipManager
tooltip = new Tooltip(target, options, @viewRegistry)
+ if not @tooltips.has(target)
+ @tooltips.set(target, [])
+ @tooltips.get(target).push(tooltip)
+
hideTooltip = ->
tooltip.leave(currentTarget: target)
tooltip.hide()
window.addEventListener('resize', hideTooltip)
- disposable = new Disposable ->
+ disposable = new Disposable =>
window.removeEventListener('resize', hideTooltip)
hideTooltip()
tooltip.destroy()
+ if @tooltips.has(target)
+ tooltipsForTarget = @tooltips.get(target)
+ index = tooltipsForTarget.indexOf(tooltip)
+ if index isnt -1
+ tooltipsForTarget.splice(index, 1)
+ if tooltipsForTarget.length is 0
+ @tooltips.delete(target)
+
disposable
+ # Extended: Find the tooltips that have been applied to the given element.
+ #
+ # * `target` The `HTMLElement` to find tooltips on.
+ #
+ # Returns an {Array} of `Tooltip` objects that match the `target`.
+ findTooltips: (target) ->
+ if @tooltips.has(target)
+ @tooltips.get(target).slice()
+ else
+ []
+
humanizeKeystrokes = (keystroke) ->
keystrokes = keystroke.split(' ')
keystrokes = (_.humanizeKeystroke(stroke) for stroke in keystrokes)
diff --git a/src/update-process-env.js b/src/update-process-env.js
index a7b7527a8..00bb13927 100644
--- a/src/update-process-env.js
+++ b/src/update-process-env.js
@@ -62,17 +62,32 @@ function shouldGetEnvFromShell (env) {
async function getEnvFromShell (env) {
let {stdout, error} = await new Promise((resolve) => {
+ let child
let error
let stdout = ''
- const child = childProcess.spawn(env.SHELL, ['-ilc', 'command env'], {encoding: 'utf8', stdio: ['ignore', 'pipe', process.stderr]})
+ let done = false
+ const cleanup = () => {
+ if (!done && child) {
+ child.kill()
+ done = true
+ }
+ }
+ process.once('exit', cleanup)
+ setTimeout(() => {
+ cleanup()
+ }, 5000)
+ child = childProcess.spawn(env.SHELL, ['-ilc', 'command env'], {encoding: 'utf8', detached: true, stdio: ['ignore', 'pipe', process.stderr]})
const buffers = []
child.on('error', (e) => {
+ done = true
error = e
})
child.stdout.on('data', (data) => {
buffers.push(data)
})
child.on('close', (code, signal) => {
+ done = true
+ process.removeListener('exit', cleanup)
if (buffers.length) {
stdout = Buffer.concat(buffers).toString('utf8')
}
diff --git a/src/window-load-settings-helpers.coffee b/src/window-load-settings-helpers.coffee
deleted file mode 100644
index 639dc751d..000000000
--- a/src/window-load-settings-helpers.coffee
+++ /dev/null
@@ -1,8 +0,0 @@
-windowLoadSettings = null
-
-exports.getWindowLoadSettings = ->
- windowLoadSettings ?= JSON.parse(window.decodeURIComponent(window.location.hash.substr(1)))
-
-exports.setWindowLoadSettings = (settings) ->
- windowLoadSettings = settings
- location.hash = encodeURIComponent(JSON.stringify(settings))
diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee
index be0af81ed..f598bef0b 100644
--- a/src/workspace-element.coffee
+++ b/src/workspace-element.coffee
@@ -102,7 +102,7 @@ class WorkspaceElement extends HTMLElement
getModel: -> @model
handleMousewheel: (event) ->
- if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling') and event.target.matches('atom-text-editor')
+ if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling') and event.target.closest('atom-text-editor')?
if event.wheelDeltaY > 0
@model.increaseFontSize()
else if event.wheelDeltaY < 0
diff --git a/src/workspace.coffee b/src/workspace.coffee
index 89c53b678..2a46ce57a 100644
--- a/src/workspace.coffee
+++ b/src/workspace.coffee
@@ -182,7 +182,7 @@ class Workspace extends Model
projectPath = _.find projectPaths, (projectPath) ->
itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep)
itemTitle ?= "untitled"
- projectPath ?= projectPaths[0]
+ projectPath ?= if itemPath then path.dirname(itemPath) else projectPaths[0]
if projectPath?
projectPath = fs.tildify(projectPath)
@@ -441,7 +441,7 @@ class Workspace extends Model
# Avoid adding URLs as recent documents to work-around this Spotlight crash:
# https://github.com/atom/atom/issues/10071
- if uri? and not url.parse(uri).protocol?
+ if uri? and (not url.parse(uri).protocol? or process.platform is 'win32')
@applicationDelegate.addRecentDocument(uri)
pane = @paneContainer.paneForURI(uri) if searchAllPanes
diff --git a/static/babelrc.json b/static/babelrc.json
index 26b70dc41..11474dd8d 100644
--- a/static/babelrc.json
+++ b/static/babelrc.json
@@ -1,7 +1,16 @@
{
- "breakConfig": true,
"sourceMap": "inline",
- "blacklist": ["es6.forOf", "useStrict"],
- "optional": ["asyncToGenerator"],
- "stage": 0
+ "plugins": [
+ ["add-module-exports", {}],
+ ["transform-async-to-generator", {}],
+ ["transform-decorators-legacy", {}],
+ ["transform-class-properties", {}],
+ ["transform-es2015-modules-commonjs", {"strictMode": false}],
+ ["transform-export-extensions", {}],
+ ["transform-do-expressions", {}],
+ ["transform-function-bind", {}],
+ ["transform-object-rest-spread", {}],
+ ["transform-flow-strip-types", {}],
+ ["transform-react-jsx", {}]
+ ]
}
diff --git a/static/jasmine.less b/static/jasmine.less
index 7dd1fcdb2..ab2695179 100644
--- a/static/jasmine.less
+++ b/static/jasmine.less
@@ -187,7 +187,7 @@ body {
margin: 5px 0 0 0;
border-radius: 2px;
line-height: 18px;
- color: #666;
+ color: #ccc;
border: 1px solid #ddd;
overflow: auto;
}