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** - - ![Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url) - - **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** - - ![Screenshots and GIFs which demonstrate the steps or part of Atom the enhancement suggestion is related to](url) - - **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 @@ ![Atom](https://cloud.githubusercontent.com/assets/72919/2874231/3af1db48-d3dd-11e3-98dc-6066f8bc766f.png) -[![macOS Build Status](https://circleci.com/gh/atom/atom.svg?style=svg)](https://circleci.com/gh/atom/atom) [![Linux Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom) +[![macOS Build Status](https://circleci.com/gh/atom/atom/tree/master.svg?style=shield)](https://circleci.com/gh/atom/atom) [![Linux Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom) [![Dependency Status](https://david-dm.org/atom/atom.svg)](https://david-dm.org/atom/atom) [![Join the Atom Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](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 | [![macOS Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom) | [![Dependency Status](https://david-dm.org/atom/atom.svg)](https://david-dm.org/atom/atom) | -| APM | [![macOS Build Status](https://travis-ci.org/atom/apm.svg?branch=master)](https://travis-ci.org/atom/apm) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/j6ixw374a397ugkb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/apm/branch/master) | [![Dependency Status](https://david-dm.org/atom/apm.svg)](https://david-dm.org/atom/apm) | -| Electron | [![macOS Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) | [![Dependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron) +| System | Travis | AppVeyor/Win | Circle/Mac | Dependencies | +|--------|--------|--------------|------------|--------------| +| [Atom](https://github.com/atom/atom) | [![Travis Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) | [![AppVeyor/Wi Build Status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom) | [![Circle/Mac Build Status](https://circleci.com/gh/atom/atom.svg?style=shield)](https://circleci.com/gh/atom/atom) | [![Dependency Status](https://david-dm.org/atom/atom.svg)](https://david-dm.org/atom/atom) | +| [APM](https://github.com/atom/apm) | [![Travis Build Status](https://travis-ci.org/atom/apm.svg?branch=master)](https://travis-ci.org/atom/apm) | [![AppVeyor/Wi Build Status](https://ci.appveyor.com/api/projects/status/j6ixw374a397ugkb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/apm/branch/master) | | [![Dependency Status](https://david-dm.org/atom/apm.svg)](https://david-dm.org/atom/apm) | +| [Electron](https://github.com/electron/electron) | [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) | [![AppVeyor/Wi Build Status](https://ci.appveyor.com/api/projects/status/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) | | [![Dependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron) ## Packages -| Package | macOS | Windows | Dependencies | -|---------|------|---------|--------------| -| About | [![macOS Build Status](https://travis-ci.org/atom/about.svg?branch=master)](https://travis-ci.org/atom/about) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/msprea3vq47l8oce/branch/master?svg=true)](https://ci.appveyor.com/project/atom/about/branch/master) | [![Dependency Status](https://david-dm.org/atom/about.svg)](https://david-dm.org/atom/about) | -| Archive View | [![macOS Build Status](https://travis-ci.org/atom/archive-view.svg?branch=master)](https://travis-ci.org/atom/archive-view) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/u3qfgaod4lhriqlj/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/archive-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/archive-view.svg)](https://david-dm.org/atom/archive-view) | -| AutoComplete Atom API | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-atom-api.svg?branch=master)](https://travis-ci.org/atom/autocomplete-atom-api) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1x3uqd9ddchpe555/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-atom-api/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-atom-api.svg)](https://david-dm.org/atom/autocomplete-atom-api) | -| Atom Space Pen Views | [![macOS Build Status](https://travis-ci.org/atom/atom-space-pen-views.svg?branch=master)](https://travis-ci.org/atom/atom-space-pen-views) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5lgv47has6n8uhuv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/atom-space-pen-views/branch/master) | [![Dependency Status](https://david-dm.org/atom/atom-space-pen-views.svg)](https://david-dm.org/atom/atom-space-pen-views) | -| AutoComplete CSS | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-css.svg?branch=master)](https://travis-ci.org/atom/autocomplete-css) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/k3e5uvpmpc5bkja9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-css/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-css.svg)](https://david-dm.org/atom/autocomplete-css) | -| AutoComplete HTML | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-html.svg?branch=master)](https://travis-ci.org/atom/autocomplete-html) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/bsaqbg1fljpd9q1b/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-html/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-html.svg)](https://david-dm.org/atom/autocomplete-html) | -| AutoComplete+ | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-plus.svg?branch=master)](https://travis-ci.org/atom/autocomplete-plus) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/9bpokrud2apgqsq0/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-plus.svg)](https://david-dm.org/atom/autocomplete-plus) | -| AutoComplete Snippets | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-snippets.svg)](https://travis-ci.org/atom/autocomplete-snippets) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/72kfi83l6cw90joy/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-snippets/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-snippets.svg)](https://david-dm.org/atom/autocomplete-snippets) | -| AutoFlow | [![macOS Build Status](https://travis-ci.org/atom/autoflow.svg?branch=master)](https://travis-ci.org/atom/autoflow) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kpmsnkbooa29x907/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autoflow/branch/master) | [![Dependency Status](https://david-dm.org/atom/autoflow.svg)](https://david-dm.org/atom/autoflow) | -| AutoSave | [![macOS Build Status](https://travis-ci.org/atom/autosave.svg?branch=master)](https://travis-ci.org/atom/autosave) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/3aktr9updp722fqx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autosave/branch/master) | [![Dependency Status](https://david-dm.org/atom/autosave.svg)](https://david-dm.org/atom/autosave) | -| Background Tips | [![macOS Build Status](https://travis-ci.org/atom/background-tips.svg?branch=master)](https://travis-ci.org/atom/background-tips) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/2utcugietl5vjc7w/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/background-tips/branch/master) | [![Dependency Status](https://david-dm.org/atom/background-tips.svg)](https://david-dm.org/atom/background-tips) | -| Bookmarks | [![macOS Build Status](https://travis-ci.org/atom/bookmarks.svg?branch=master)](https://travis-ci.org/atom/bookmarks) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/vjsf78pj4rw6ibcw/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/bookmarks/branch/master) | [![Dependency Status](https://david-dm.org/atom/bookmarks.svg)](https://david-dm.org/atom/bookmarks) | -| Bracket Matcher | [![macOS Build Status](https://travis-ci.org/atom/bracket-matcher.svg?branch=master)](https://travis-ci.org/atom/bracket-matcher) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/rrsl2h7e0od26k54/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/bracket-matcher/branch/master) | [![Dependency Status](https://david-dm.org/atom/bracket-matcher.svg)](https://david-dm.org/atom/bracket-matcher) | -| Command Palette | [![macOS Build Status](https://travis-ci.org/atom/command-palette.svg?branch=master)](https://travis-ci.org/atom/command-palette) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/jqgwetayr0enorun/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/command-palette/branch/master) | [![Dependency Status](https://david-dm.org/atom/command-palette.svg)](https://david-dm.org/atom/command-palette) | -| Deprecation Cop | [![macOS Build Status](https://travis-ci.org/atom/deprecation-cop.svg?branch=master)](https://travis-ci.org/atom/deprecation-cop) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/0s870q5fj3vwihjx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/deprecation-cop/branch/master) | [![Dependency Status](https://david-dm.org/atom/deprecation-cop.svg)](https://david-dm.org/atom/deprecation-cop) | -| Dev Live Reload | [![macOS Build Status](https://travis-ci.org/atom/dev-live-reload.svg?branch=master)](https://travis-ci.org/atom/dev-live-reload) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/g3sd27ylba1fun1v/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/dev-live-reload/branch/master) | [![Dependency Status](https://david-dm.org/atom/dev-live-reload.svg)](https://david-dm.org/atom/dev-live-reload) | -| Encoding Selector | [![macOS Build Status](https://travis-ci.org/atom/encoding-selector.svg?branch=master)](https://travis-ci.org/atom/encoding-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/e08x6k2b68wpwxxc/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/encoding-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/encoding-selector.svg)](https://david-dm.org/atom/encoding-selector) | -| Exception Reporting | [![macOS Build Status](https://travis-ci.org/atom/exception-reporting.svg?branch=master)](https://travis-ci.org/atom/exception-reporting) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/i0pla7qbpv7celg2/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/exception-reporting/branch/master) | [![Dependency Status](https://david-dm.org/atom/exception-reporting.svg)](https://david-dm.org/atom/exception-reporting) | -| Find and Replace | [![macOS Build Status](https://travis-ci.org/atom/find-and-replace.svg?branch=master)](https://travis-ci.org/atom/find-and-replace) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/6w4baiiq5mw4nxky/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/find-and-replace/branch/master) | [![Dependency Status](https://david-dm.org/atom/find-and-replace.svg)](https://david-dm.org/atom/find-and-replace) | -| Fuzzy Finder | [![macOS Build Status](https://travis-ci.org/atom/fuzzy-finder.svg?branch=master)](https://travis-ci.org/atom/fuzzy-finder) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b4b2dg5n9r1wdqad/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/fuzzy-finder/branch/master) | [![Dependency Status](https://david-dm.org/atom/fuzzy-finder.svg)](https://david-dm.org/atom/fuzzy-finder) | -| Git Diff | [![macOS Build Status](https://travis-ci.org/atom/git-diff.svg?branch=master)](https://travis-ci.org/atom/git-diff) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/9auj52cs0vso66nv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/git-diff/branch/master) | [![Dependency Status](https://david-dm.org/atom/git-diff.svg)](https://david-dm.org/atom/git-diff) | -| Go to Line | [![macOS Build Status](https://travis-ci.org/atom/go-to-line.svg?branch=master)](https://travis-ci.org/atom/go-to-line) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/qf0isc8ulw4wxi0b/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/go-to-line/branch/master) | [![Dependency Status](https://david-dm.org/atom/go-to-line.svg)](https://david-dm.org/atom/go-to-line) | -| Grammar Selector | [![macOS Build Status](https://travis-ci.org/atom/grammar-selector.svg?branch=master)](https://travis-ci.org/atom/grammar-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/pg8qss03bfh4ngqm/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/grammar-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/grammar-selector.svg)](https://david-dm.org/atom/grammar-selector) | -| Image View | [![macOS Build Status](https://travis-ci.org/atom/image-view.svg?branch=master)](https://travis-ci.org/atom/image-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/notavaawrswk0g10/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/image-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/image-view.svg)](https://david-dm.org/atom/image-view) | -| Incompatible Packages | [![macOS Build Status](https://travis-ci.org/atom/incompatible-packages.svg?branch=master)](https://travis-ci.org/atom/incompatible-packages) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/neet595s038x7w70/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/incompatible-packages/branch/master) | [![Dependency Status](https://david-dm.org/atom/incompatible-packages.svg)](https://david-dm.org/atom/incompatible-packages) | -| Keybinding Resolver | [![macOS Build Status](https://travis-ci.org/atom/keybinding-resolver.svg?branch=master)](https://travis-ci.org/atom/keybinding-resolver) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/9jf31itx01hnn4nh/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/keybinding-resolver/branch/master) | [![Dependency Status](https://david-dm.org/atom/keybinding-resolver.svg)](https://david-dm.org/atom/keybinding-resolver) | -| Line Ending Selector | [![macOS Build Status](https://travis-ci.org/atom/line-ending-selector.svg?branch=master)](https://travis-ci.org/atom/line-ending-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b3743n9ojomlpn1g/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/line-ending-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/line-ending-selector.svg)](https://david-dm.org/atom/line-ending-selector) | -| Link | [![macOS Build Status](https://travis-ci.org/atom/link.svg?branch=master)](https://travis-ci.org/atom/link) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1d3cb8ktd48k9vnl/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/link/branch/master) | [![Dependency Status](https://david-dm.org/atom/link.svg)](https://david-dm.org/atom/link) | -| Markdown Preview | [![macOS Build Status](https://travis-ci.org/atom/markdown-preview.svg?branch=master)](https://travis-ci.org/atom/markdown-preview) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/bvh0evhh4v6w9b29/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/markdown-preview/branch/master) | [![Dependency Status](https://david-dm.org/atom/markdown-preview.svg)](https://david-dm.org/atom/markdown-preview) | -| Metrics | [![macOS Build Status](https://travis-ci.org/atom/metrics.svg?branch=master)](https://travis-ci.org/atom/metrics) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b5doi205xl3iex04/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/metrics/branch/master) | [![Dependency Status](https://david-dm.org/atom/metrics.svg)](https://david-dm.org/atom/metrics) | -| Notifications | [![macOS Build Status](https://travis-ci.org/atom/notifications.svg?branch=master)](https://travis-ci.org/atom/notifications) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ps3p8tj2okw57x0e/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/notifications/branch/master) | [![Dependency Status](https://david-dm.org/atom/notifications.svg)](https://david-dm.org/atom/notifications) | -| Open on Github | [![macOS Build Status](https://travis-ci.org/atom/open-on-github.svg?branch=master)](https://travis-ci.org/atom/open-on-github) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ccl6na4qsna5wncr/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/open-on-github/branch/master) | [![Dependency Status](https://david-dm.org/atom/open-on-github.svg)](https://david-dm.org/atom/open-on-github) | -| Package Generator | [![macOS Build Status](https://travis-ci.org/atom/package-generator.svg?branch=master)](https://travis-ci.org/atom/package-generator)| [![Windows Build Status](https://ci.appveyor.com/api/projects/status/7t1i4hdmljhigp9u/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/package-generator/branch/master) | [![Dependency Status](https://david-dm.org/atom/package-generator.svg)](https://david-dm.org/atom/package-generator) | -| Settings View | [![macOS Build Status](https://travis-ci.org/atom/settings-view.svg?branch=master)](https://travis-ci.org/atom/settings-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/hatgxg6k2g3grafq/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/settings-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/settings-view.svg)](https://david-dm.org/atom/settings-view) | -| Snippets | [![macOS Build Status](https://travis-ci.org/atom/snippets.svg?branch=master)](https://travis-ci.org/atom/snippets) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/8hlc0onofkgbxw53/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/snippets/branch/master) | [![Dependency Status](https://david-dm.org/atom/snippets.svg)](https://david-dm.org/atom/snippets) | -| Spell Check | [![macOS Build Status](https://travis-ci.org/atom/spell-check.svg?branch=master)](https://travis-ci.org/atom/spell-check) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1620a5reqw6kdolv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/spell-check/branch/master) | [![Dependency Status](https://david-dm.org/atom/spell-check.svg)](https://david-dm.org/atom/spell-check) | -| Status Bar | [![macOS Build Status](https://travis-ci.org/atom/status-bar.svg?branch=master)](https://travis-ci.org/atom/status-bar) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gu8tv4h6cnpeesg2/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/status-bar/branch/master) | [![Dependency Status](https://david-dm.org/atom/status-bar.svg)](https://david-dm.org/atom/status-bar) | -| Styleguide | [![macOS Build Status](https://travis-ci.org/atom/styleguide.svg?branch=master)](https://travis-ci.org/atom/styleguide) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/88dt9jxexkpindhw/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/styleguide/branch/master) | [![Dependency Status](https://david-dm.org/atom/styleguide.svg)](https://david-dm.org/atom/styleguide) | -| Symbols View | [![macOS Build Status](https://travis-ci.org/atom/symbols-view.svg?branch=master)](https://travis-ci.org/atom/symbols-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/al68vtv83x49eu5d/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/symbols-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/symbols-view.svg)](https://david-dm.org/atom/symbols-view) | -| Tabs | [![macOS Build Status](https://travis-ci.org/atom/tabs.svg?branch=master)](https://travis-ci.org/atom/tabs) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/nf4hdmuk4i9xkfmb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/tabs/branch/master) | [![Dependency Status](https://david-dm.org/atom/tabs.svg)](https://david-dm.org/atom/tabs) | -| Timecop | [![macOS Build Status](https://travis-ci.org/atom/timecop.svg?branch=master)](https://travis-ci.org/atom/timecop) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/37fhichmvx90sd97/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/timecop/branch/master) | [![Dependency Status](https://david-dm.org/atom/timecop.svg)](https://david-dm.org/atom/timecop) | -| Tree View | [![macOS Build Status](https://travis-ci.org/atom/tree-view.svg?branch=master)](https://travis-ci.org/atom/tree-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/com793ehi0hajrkd/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/tree-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/tree-view.svg)](https://david-dm.org/atom/tree-view) | -| Update Package Dependencies | [![macOS Build Status](https://travis-ci.org/atom/update-package-dependencies.svg?branch=master)](https://travis-ci.org/atom/update-package-dependencies) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5xqtoc3xk1e7lt2y/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/update-package-dependencies/branch/master) | [![Dependency Status](https://david-dm.org/atom/update-package-dependencies.svg)](https://david-dm.org/atom/update-package-dependencies) | -| Welcome | [![macOS Build Status](https://travis-ci.org/atom/welcome.svg?branch=master)](https://travis-ci.org/atom/welcome) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/c3ssyte35ivvnt62/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/welcome/branch/master) | [![Dependency Status](https://david-dm.org/atom/welcome.svg)](https://david-dm.org/atom/welcome) | -| Whitespace | [![macOS Build Status](https://travis-ci.org/atom/whitespace.svg?branch=master)](https://travis-ci.org/atom/whitespace) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/sf8pdb3ausdk1vtb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/whitespace/branch/master) | [![Dependency Status](https://david-dm.org/atom/whitespace.svg)](https://david-dm.org/atom/whitespace) | -| Wrap Guide | [![macOS Build Status](https://travis-ci.org/atom/wrap-guide.svg?branch=master)](https://travis-ci.org/atom/wrap-guide) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5qk1io3uar5j8hol/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/wrap-guide/branch/master) | [![Dependency Status](https://david-dm.org/atom/wrap-guide.svg)](https://david-dm.org/atom/wrap-guide) | - +| Package | Travis | AppVeyor/Win | Dependencies | +|---------|--------|--------------|--------------| +| [About](https://github.com/atom/about) | [![macOS Build Status](https://travis-ci.org/atom/about.svg?branch=master)](https://travis-ci.org/atom/about) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/msprea3vq47l8oce/branch/master?svg=true)](https://ci.appveyor.com/project/atom/about/branch/master) | [![Dependency Status](https://david-dm.org/atom/about.svg)](https://david-dm.org/atom/about) | +| [Archive View](https://github.com/atom/archive-view) | [![macOS Build Status](https://travis-ci.org/atom/archive-view.svg?branch=master)](https://travis-ci.org/atom/archive-view) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/u3qfgaod4lhriqlj/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/archive-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/archive-view.svg)](https://david-dm.org/atom/archive-view) | +| [AutoComplete Atom API](https://github.com/atom/autocomplete-atom-api) | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-atom-api.svg?branch=master)](https://travis-ci.org/atom/autocomplete-atom-api) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1x3uqd9ddchpe555/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-atom-api/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-atom-api.svg)](https://david-dm.org/atom/autocomplete-atom-api) | +| [AutoComplete CSS](https://github.com/atom/autocomplete-css) | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-css.svg?branch=master)](https://travis-ci.org/atom/autocomplete-css) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/k3e5uvpmpc5bkja9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-css/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-css.svg)](https://david-dm.org/atom/autocomplete-css) | +| [AutoComplete HTML](https://github.com/atom/autocomplete-html) | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-html.svg?branch=master)](https://travis-ci.org/atom/autocomplete-html) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/bsaqbg1fljpd9q1b/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-html/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-html.svg)](https://david-dm.org/atom/autocomplete-html) | +| [AutoComplete+](https://github.com/atom/autocomplete-plus) | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-plus.svg?branch=master)](https://travis-ci.org/atom/autocomplete-plus) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/9bpokrud2apgqsq0/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-plus.svg)](https://david-dm.org/atom/autocomplete-plus) | +| [AutoComplete Snippets](https://github.com/atom/autocomplete-snippets) | [![macOS Build Status](https://travis-ci.org/atom/autocomplete-snippets.svg)](https://travis-ci.org/atom/autocomplete-snippets) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/72kfi83l6cw90joy/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autocomplete-snippets/branch/master) | [![Dependency Status](https://david-dm.org/atom/autocomplete-snippets.svg)](https://david-dm.org/atom/autocomplete-snippets) | +| [AutoFlow](https://github.com/atom/autoflow) | [![macOS Build Status](https://travis-ci.org/atom/autoflow.svg?branch=master)](https://travis-ci.org/atom/autoflow) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kpmsnkbooa29x907/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autoflow/branch/master) | [![Dependency Status](https://david-dm.org/atom/autoflow.svg)](https://david-dm.org/atom/autoflow) | +| [AutoSave](https://github.com/atom/autosave) | [![macOS Build Status](https://travis-ci.org/atom/autosave.svg?branch=master)](https://travis-ci.org/atom/autosave) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/3aktr9updp722fqx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/autosave/branch/master) | [![Dependency Status](https://david-dm.org/atom/autosave.svg)](https://david-dm.org/atom/autosave) | +| [Background Tips](https://github.com/atom/background-tips) | [![macOS Build Status](https://travis-ci.org/atom/background-tips.svg?branch=master)](https://travis-ci.org/atom/background-tips) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/2utcugietl5vjc7w/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/background-tips/branch/master) | [![Dependency Status](https://david-dm.org/atom/background-tips.svg)](https://david-dm.org/atom/background-tips) | +| [Bookmarks](https://github.com/atom/bookmarks) | [![macOS Build Status](https://travis-ci.org/atom/bookmarks.svg?branch=master)](https://travis-ci.org/atom/bookmarks) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/vjsf78pj4rw6ibcw/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/bookmarks/branch/master) | [![Dependency Status](https://david-dm.org/atom/bookmarks.svg)](https://david-dm.org/atom/bookmarks) | +| [Bracket Matcher](https://github.com/atom/bracket-matcher) | [![macOS Build Status](https://travis-ci.org/atom/bracket-matcher.svg?branch=master)](https://travis-ci.org/atom/bracket-matcher) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/rrsl2h7e0od26k54/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/bracket-matcher/branch/master) | [![Dependency Status](https://david-dm.org/atom/bracket-matcher.svg)](https://david-dm.org/atom/bracket-matcher) | +| [Command Palette](https://github.com/atom/command-palette) | [![macOS Build Status](https://travis-ci.org/atom/command-palette.svg?branch=master)](https://travis-ci.org/atom/command-palette) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/jqgwetayr0enorun/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/command-palette/branch/master) | [![Dependency Status](https://david-dm.org/atom/command-palette.svg)](https://david-dm.org/atom/command-palette) | +| [Deprecation Cop](https://github.com/atom/deprecation-cop) | [![macOS Build Status](https://travis-ci.org/atom/deprecation-cop.svg?branch=master)](https://travis-ci.org/atom/deprecation-cop) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/0s870q5fj3vwihjx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/deprecation-cop/branch/master) | [![Dependency Status](https://david-dm.org/atom/deprecation-cop.svg)](https://david-dm.org/atom/deprecation-cop) | +| [Dev Live Reload](https://github.com/atom/dev-live-reload) | [![macOS Build Status](https://travis-ci.org/atom/dev-live-reload.svg?branch=master)](https://travis-ci.org/atom/dev-live-reload) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/g3sd27ylba1fun1v/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/dev-live-reload/branch/master) | [![Dependency Status](https://david-dm.org/atom/dev-live-reload.svg)](https://david-dm.org/atom/dev-live-reload) | +| [Encoding Selector](https://github.com/atom/encoding-selector) | [![macOS Build Status](https://travis-ci.org/atom/encoding-selector.svg?branch=master)](https://travis-ci.org/atom/encoding-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/e08x6k2b68wpwxxc/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/encoding-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/encoding-selector.svg)](https://david-dm.org/atom/encoding-selector) | +| [Exception Reporting](https://github.com/atom/exception-reporting) | [![macOS Build Status](https://travis-ci.org/atom/exception-reporting.svg?branch=master)](https://travis-ci.org/atom/exception-reporting) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/i0pla7qbpv7celg2/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/exception-reporting/branch/master) | [![Dependency Status](https://david-dm.org/atom/exception-reporting.svg)](https://david-dm.org/atom/exception-reporting) | +| [Find and Replace](https://github.com/atom/find-and-replace) | [![macOS Build Status](https://travis-ci.org/atom/find-and-replace.svg?branch=master)](https://travis-ci.org/atom/find-and-replace) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/6w4baiiq5mw4nxky/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/find-and-replace/branch/master) | [![Dependency Status](https://david-dm.org/atom/find-and-replace.svg)](https://david-dm.org/atom/find-and-replace) | +| [Fuzzy Finder](https://github.com/atom/fuzzy-finder) | [![macOS Build Status](https://travis-ci.org/atom/fuzzy-finder.svg?branch=master)](https://travis-ci.org/atom/fuzzy-finder) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b4b2dg5n9r1wdqad/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/fuzzy-finder/branch/master) | [![Dependency Status](https://david-dm.org/atom/fuzzy-finder.svg)](https://david-dm.org/atom/fuzzy-finder) | +| [Git Diff](https://github.com/atom/git-diff) | [![macOS Build Status](https://travis-ci.org/atom/git-diff.svg?branch=master)](https://travis-ci.org/atom/git-diff) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/9auj52cs0vso66nv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/git-diff/branch/master) | [![Dependency Status](https://david-dm.org/atom/git-diff.svg)](https://david-dm.org/atom/git-diff) | +| [Go to Line](https://github.com/atom/go-to-line) | [![macOS Build Status](https://travis-ci.org/atom/go-to-line.svg?branch=master)](https://travis-ci.org/atom/go-to-line) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/qf0isc8ulw4wxi0b/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/go-to-line/branch/master) | [![Dependency Status](https://david-dm.org/atom/go-to-line.svg)](https://david-dm.org/atom/go-to-line) | +| [Grammar Selector](https://github.com/atom/grammar-selector) | [![macOS Build Status](https://travis-ci.org/atom/grammar-selector.svg?branch=master)](https://travis-ci.org/atom/grammar-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/pg8qss03bfh4ngqm/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/grammar-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/grammar-selector.svg)](https://david-dm.org/atom/grammar-selector) | +| [Image View](https://github.com/atom/image-view) | [![macOS Build Status](https://travis-ci.org/atom/image-view.svg?branch=master)](https://travis-ci.org/atom/image-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/notavaawrswk0g10/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/image-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/image-view.svg)](https://david-dm.org/atom/image-view) | +| [Incompatible Packages](https://github.com/atom/incompatible-packages) | [![macOS Build Status](https://travis-ci.org/atom/incompatible-packages.svg?branch=master)](https://travis-ci.org/atom/incompatible-packages) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/neet595s038x7w70/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/incompatible-packages/branch/master) | [![Dependency Status](https://david-dm.org/atom/incompatible-packages.svg)](https://david-dm.org/atom/incompatible-packages) | +| [Keybinding Resolver](https://github.com/atom/keybinding-resolver) | [![macOS Build Status](https://travis-ci.org/atom/keybinding-resolver.svg?branch=master)](https://travis-ci.org/atom/keybinding-resolver) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/9jf31itx01hnn4nh/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/keybinding-resolver/branch/master) | [![Dependency Status](https://david-dm.org/atom/keybinding-resolver.svg)](https://david-dm.org/atom/keybinding-resolver) | +| [Line Ending Selector](https://github.com/atom/line-ending-selector) | [![macOS Build Status](https://travis-ci.org/atom/line-ending-selector.svg?branch=master)](https://travis-ci.org/atom/line-ending-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b3743n9ojomlpn1g/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/line-ending-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/line-ending-selector.svg)](https://david-dm.org/atom/line-ending-selector) | +| [Link](https://github.com/atom/link) | [![macOS Build Status](https://travis-ci.org/atom/link.svg?branch=master)](https://travis-ci.org/atom/link) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1d3cb8ktd48k9vnl/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/link/branch/master) | [![Dependency Status](https://david-dm.org/atom/link.svg)](https://david-dm.org/atom/link) | +| [Markdown Preview](https://github.com/atom/markdown-preview) | [![macOS Build Status](https://travis-ci.org/atom/markdown-preview.svg?branch=master)](https://travis-ci.org/atom/markdown-preview) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/bvh0evhh4v6w9b29/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/markdown-preview/branch/master) | [![Dependency Status](https://david-dm.org/atom/markdown-preview.svg)](https://david-dm.org/atom/markdown-preview) | +| [Metrics](https://github.com/atom/metrics) | [![macOS Build Status](https://travis-ci.org/atom/metrics.svg?branch=master)](https://travis-ci.org/atom/metrics) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b5doi205xl3iex04/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/metrics/branch/master) | [![Dependency Status](https://david-dm.org/atom/metrics.svg)](https://david-dm.org/atom/metrics) | +| [Notifications](https://github.com/atom/notifications) | [![macOS Build Status](https://travis-ci.org/atom/notifications.svg?branch=master)](https://travis-ci.org/atom/notifications) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ps3p8tj2okw57x0e/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/notifications/branch/master) | [![Dependency Status](https://david-dm.org/atom/notifications.svg)](https://david-dm.org/atom/notifications) | +| [Open on Github](https://github.com/atom/open-on-github) | [![macOS Build Status](https://travis-ci.org/atom/open-on-github.svg?branch=master)](https://travis-ci.org/atom/open-on-github) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ccl6na4qsna5wncr/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/open-on-github/branch/master) | [![Dependency Status](https://david-dm.org/atom/open-on-github.svg)](https://david-dm.org/atom/open-on-github) | +| [Package Generator](https://github.com/atom/package-generator) | [![macOS Build Status](https://travis-ci.org/atom/package-generator.svg?branch=master)](https://travis-ci.org/atom/package-generator)| [![Windows Build Status](https://ci.appveyor.com/api/projects/status/7t1i4hdmljhigp9u/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/package-generator/branch/master) | [![Dependency Status](https://david-dm.org/atom/package-generator.svg)](https://david-dm.org/atom/package-generator) | +| [Settings View](https://github.com/atom/settings-view) | [![macOS Build Status](https://travis-ci.org/atom/settings-view.svg?branch=master)](https://travis-ci.org/atom/settings-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/hatgxg6k2g3grafq/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/settings-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/settings-view.svg)](https://david-dm.org/atom/settings-view) | +| [Snippets](https://github.com/atom/snippets) | [![macOS Build Status](https://travis-ci.org/atom/snippets.svg?branch=master)](https://travis-ci.org/atom/snippets) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/8hlc0onofkgbxw53/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/snippets/branch/master) | [![Dependency Status](https://david-dm.org/atom/snippets.svg)](https://david-dm.org/atom/snippets) | +| [Spell Check](https://github.com/atom/spell-check) | [![macOS Build Status](https://travis-ci.org/atom/spell-check.svg?branch=master)](https://travis-ci.org/atom/spell-check) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1620a5reqw6kdolv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/spell-check/branch/master) | [![Dependency Status](https://david-dm.org/atom/spell-check.svg)](https://david-dm.org/atom/spell-check) | +| [Status Bar](https://github.com/atom/status-bar) | [![macOS Build Status](https://travis-ci.org/atom/status-bar.svg?branch=master)](https://travis-ci.org/atom/status-bar) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gu8tv4h6cnpeesg2/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/status-bar/branch/master) | [![Dependency Status](https://david-dm.org/atom/status-bar.svg)](https://david-dm.org/atom/status-bar) | +| [Styleguide](https://github.com/atom/styleguide) | [![macOS Build Status](https://travis-ci.org/atom/styleguide.svg?branch=master)](https://travis-ci.org/atom/styleguide) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/88dt9jxexkpindhw/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/styleguide/branch/master) | [![Dependency Status](https://david-dm.org/atom/styleguide.svg)](https://david-dm.org/atom/styleguide) | +| [Symbols View](https://github.com/atom/symbols-view) | [![macOS Build Status](https://travis-ci.org/atom/symbols-view.svg?branch=master)](https://travis-ci.org/atom/symbols-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/al68vtv83x49eu5d/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/symbols-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/symbols-view.svg)](https://david-dm.org/atom/symbols-view) | +| [Tabs](https://github.com/atom/tabs) | [![macOS Build Status](https://travis-ci.org/atom/tabs.svg?branch=master)](https://travis-ci.org/atom/tabs) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/nf4hdmuk4i9xkfmb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/tabs/branch/master) | [![Dependency Status](https://david-dm.org/atom/tabs.svg)](https://david-dm.org/atom/tabs) | +| [Timecop](https://github.com/atom/timecop) | [![macOS Build Status](https://travis-ci.org/atom/timecop.svg?branch=master)](https://travis-ci.org/atom/timecop) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/37fhichmvx90sd97/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/timecop/branch/master) | [![Dependency Status](https://david-dm.org/atom/timecop.svg)](https://david-dm.org/atom/timecop) | +| [Tree View](https://github.com/atom/tree-view) | [![macOS Build Status](https://travis-ci.org/atom/tree-view.svg?branch=master)](https://travis-ci.org/atom/tree-view) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/com793ehi0hajrkd/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/tree-view/branch/master) | [![Dependency Status](https://david-dm.org/atom/tree-view.svg)](https://david-dm.org/atom/tree-view) | +| [Update Package Dependencies](https://github.com/atom/update-package-dependencies) | [![macOS Build Status](https://travis-ci.org/atom/update-package-dependencies.svg?branch=master)](https://travis-ci.org/atom/update-package-dependencies) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5xqtoc3xk1e7lt2y/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/update-package-dependencies/branch/master) | [![Dependency Status](https://david-dm.org/atom/update-package-dependencies.svg)](https://david-dm.org/atom/update-package-dependencies) | +| [Welcome](https://github.com/atom/welcome) | [![macOS Build Status](https://travis-ci.org/atom/welcome.svg?branch=master)](https://travis-ci.org/atom/welcome) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/c3ssyte35ivvnt62/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/welcome/branch/master) | [![Dependency Status](https://david-dm.org/atom/welcome.svg)](https://david-dm.org/atom/welcome) | +| [Whitespace](https://github.com/atom/whitespace) | [![macOS Build Status](https://travis-ci.org/atom/whitespace.svg?branch=master)](https://travis-ci.org/atom/whitespace) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/sf8pdb3ausdk1vtb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/whitespace/branch/master) | [![Dependency Status](https://david-dm.org/atom/whitespace.svg)](https://david-dm.org/atom/whitespace) | +| [Wrap Guide](https://github.com/atom/wrap-guide) | [![macOS Build Status](https://travis-ci.org/atom/wrap-guide.svg?branch=master)](https://travis-ci.org/atom/wrap-guide) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5qk1io3uar5j8hol/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/wrap-guide/branch/master) | [![Dependency Status](https://david-dm.org/atom/wrap-guide.svg)](https://david-dm.org/atom/wrap-guide) | ## Libraries -| Library | macOS | Windows | Dependencies | -|---------|------|---------|--------------| -| Clear Cut | [![macOS Build Status](https://travis-ci.org/atom/clear-cut.svg?branch=master)](https://travis-ci.org/atom/clear-cut) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/civ54x89l06286m9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/clear-cut/branch/master) | [![Dependency Status](https://david-dm.org/atom/clear-cut.svg)](https://david-dm.org/atom/clear-cut) | -| Event Kit | [![macOS Build Status](https://travis-ci.org/atom/event-kit.svg?branch=master)](https://travis-ci.org/atom/event-kit) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/lb32q70204lpmlxo/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/event-kit/branch/master) | [![Dependency Status](https://david-dm.org/atom/event-kit.svg)](https://david-dm.org/atom/event-kit) | -| Fs Plus | [![macOS Build Status](https://travis-ci.org/atom/fs-plus.svg?branch=master)](https://travis-ci.org/atom/fs-plus) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gf2tleqp0hdek3o3/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/fs-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/fs-plus.svg)](https://david-dm.org/atom/fs-plus) | -| Grim | [![macOS Build Status](https://travis-ci.org/atom/grim.svg)](https://travis-ci.org/atom/grim) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/i4m37pol77vygrvb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/grim/branch/master) | [![Dependency Status](https://david-dm.org/atom/grim.svg)](https://david-dm.org/atom/grim) | -| Jasmine Focused | [![macOS Build Status](https://travis-ci.org/atom/grim.svg)](https://travis-ci.org/atom/grim) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/af0ipfqqxn7aygoe/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/jasmine-focused/branch/master) | [![Dependency Status](https://david-dm.org/atom/jasmine-focused.svg)](https://david-dm.org/atom/jasmine-focused) | -| Property Accessors | [![macOS Build Status](https://travis-ci.org/atom/property-accessors.svg?branch=master)](https://travis-ci.org/atom/property-accessors) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ww4d10hi4v5h7kbp/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/property-accessors/branch/master) | [![Dependency Status](https://david-dm.org/atom/property-accessors.svg)](https://david-dm.org/atom/property-accessors) | -| TextBuffer | [![macOS Build Status](https://travis-ci.org/atom/text-buffer.svg?branch=master)](https://travis-ci.org/atom/text-buffer) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/48xl8do1sm2thf5p/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/text-buffer/branch/master) | [![Dependency Status](https://david-dm.org/atom/text-buffer.svg)](https://david-dm.org/atom/text-buffer) | -| Underscore-Plus | [![macOS Build Status](https://travis-ci.org/atom/underscore-plus.svg?branch=master)](https://travis-ci.org/atom/underscore-plus) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/c7l8009vgpaojxcd/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/underscore-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/underscore-plus.svg)](https://david-dm.org/atom/underscore-plus) | - +| Library | Travis | AppVeyor/Win | Dependencies | +|---------|--------|--------------|--------------| +| [Clear Cut](https://github.com/atom/clear-cut) | [![macOS Build Status](https://travis-ci.org/atom/clear-cut.svg?branch=master)](https://travis-ci.org/atom/clear-cut) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/civ54x89l06286m9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/clear-cut/branch/master) | [![Dependency Status](https://david-dm.org/atom/clear-cut.svg)](https://david-dm.org/atom/clear-cut) | +| [Event Kit](https://github.com/atom/event-kit) | [![macOS Build Status](https://travis-ci.org/atom/event-kit.svg?branch=master)](https://travis-ci.org/atom/event-kit) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/lb32q70204lpmlxo/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/event-kit/branch/master) | [![Dependency Status](https://david-dm.org/atom/event-kit.svg)](https://david-dm.org/atom/event-kit) | +| [First Mate](https://github.com/atom/first-mate) | [![macOS Build Status](https://travis-ci.org/atom/first-mate.svg?branch=master)](https://travis-ci.org/atom/first-mate) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/p5im21uq22cwgb6d/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/first-mate) | [![Dependency Status](https://david-dm.org/atom/first-mate/status.svg)](https://david-dm.org/atom/first-mate) | +| [Fs Plus](https://github.com/atom/fs-plus) | [![macOS Build Status](https://travis-ci.org/atom/fs-plus.svg?branch=master)](https://travis-ci.org/atom/fs-plus) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gf2tleqp0hdek3o3/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/fs-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/fs-plus.svg)](https://david-dm.org/atom/fs-plus) | +| [Grim](https://github.com/atom/grim) | [![macOS Build Status](https://travis-ci.org/atom/grim.svg)](https://travis-ci.org/atom/grim) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/i4m37pol77vygrvb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/grim/branch/master) | [![Dependency Status](https://david-dm.org/atom/grim.svg)](https://david-dm.org/atom/grim) | +| [Jasmine Focused](https://github.com/atom/jasmine-focused) | [![macOS Build Status](https://travis-ci.org/atom/grim.svg)](https://travis-ci.org/atom/grim) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/af0ipfqqxn7aygoe/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/jasmine-focused/branch/master) | [![Dependency Status](https://david-dm.org/atom/jasmine-focused.svg)](https://david-dm.org/atom/jasmine-focused) | +| [Keyboard Layout](https://github.com/atom/keyboard-layout) | [![macOS Build Status](https://travis-ci.org/atom/keyboard-layout.svg?branch=master)](https://travis-ci.org/atom/keyboard-layout) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/rk8wooeyh689apgd/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/keyboard-layout) | [![Dependency Status](https://david-dm.org/atom/keyboard-layout/status.svg)](https://david-dm.org/atom/keyboard-layout) | +| [Oniguruma](https://github.com/atom/node-oniguruma) | [![macOS Build Status](https://travis-ci.org/atom/node-oniguruma.svg?branch=master)](https://travis-ci.org/atom/node-oniguruma) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/s9twhi451ef2butr/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/node-oniguruma/branch/master) | [![Dependency Status](https://david-dm.org/atom/node-oniguruma.svg)](https://david-dm.org/atom/node-oniguruma) | +| [PathWatcher](https://github.com/atom/node-pathwatcher) | [![macOS Build Status](https://travis-ci.org/atom/node-pathwatcher.svg?branch=master)](https://travis-ci.org/atom/node-pathwatcher) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/li8dkoucdrc2ryts/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/node-pathwatcher) | [![Dependency Status](https://david-dm.org/atom/node-pathwatcher/status.svg)](https://david-dm.org/atom/node-pathwatcher) | +| [Property Accessors](https://github.com/atom/property-accessors) | [![macOS Build Status](https://travis-ci.org/atom/property-accessors.svg?branch=master)](https://travis-ci.org/atom/property-accessors) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ww4d10hi4v5h7kbp/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/property-accessors/branch/master) | [![Dependency Status](https://david-dm.org/atom/property-accessors.svg)](https://david-dm.org/atom/property-accessors) | +| [Season](https://github.com/atom/season) | [![macOS Build Status](https://travis-ci.org/atom/season.svg?branch=master)](https://travis-ci.org/atom/season) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/v3bth3ooq5q8k8lx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/season) | [![Dependency Status](https://david-dm.org/atom/season.svg)](https://david-dm.org/atom/season) | +| [Superstring](https://github.com/atom/superstring) | [![macOS Build Status](https://travis-ci.org/atom/superstring.svg?branch=master)](https://travis-ci.org/atom/superstring) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/n5pack4yk7w80fso/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/superstring/branch/master) | | [![Dependency Status](https://david-dm.org/atom/superstring.svg)](https://david-dm.org/atom/superstring) | +| [TextBuffer](https://github.com/atom/text-buffer) | [![macOS Build Status](https://travis-ci.org/atom/text-buffer.svg?branch=master)](https://travis-ci.org/atom/text-buffer) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/48xl8do1sm2thf5p/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/text-buffer/branch/master) | [![Dependency Status](https://david-dm.org/atom/text-buffer.svg)](https://david-dm.org/atom/text-buffer) | +| [Underscore-Plus](https://github.com/atom/underscore-plus) | [![macOS Build Status](https://travis-ci.org/atom/underscore-plus.svg?branch=master)](https://travis-ci.org/atom/underscore-plus) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/c7l8009vgpaojxcd/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/underscore-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/underscore-plus.svg)](https://david-dm.org/atom/underscore-plus) | ## Tools -| Language | macOS | Windows | Dependencies | -|----------|------|---------|--------------| -| AtomDoc | [![macOS Build Status](https://travis-ci.org/atom/atomdoc.svg?branch=master)](https://travis-ci.org/atom/atomdoc) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/chi2bmaafr3puyq2/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/atomdoc/branch/master) | [![Dependency Status](https://david-dm.org/atom/atomdoc.svg)](https://david-dm.org/atom/atomdoc) +| Language | Travis | AppVeyor/Win | Dependencies | +|----------|--------|--------------|--------------| +| [AtomDoc](https://github.com/atom/atomdoc) | [![macOS Build Status](https://travis-ci.org/atom/atomdoc.svg?branch=master)](https://travis-ci.org/atom/atomdoc) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/chi2bmaafr3puyq2/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/atomdoc/branch/master) | [![Dependency Status](https://david-dm.org/atom/atomdoc.svg)](https://david-dm.org/atom/atomdoc) ## Languages -| Language | macOS | Windows | -|----------|------|---------| -| C/C++ | [![macOS Build Status](https://travis-ci.org/atom/language-c.svg?branch=master)](https://travis-ci.org/atom/language-c) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/8oy1hmp4yrij7c32/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-c/branch/master) | -| C# | [![macOS Build Status](https://travis-ci.org/atom/language-csharp.svg?branch=master)](https://travis-ci.org/atom/language-csharp) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/j1as3753y5t90obn/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-csharp/branch/master) | -| Clojure | [![macOS Build Status](https://travis-ci.org/atom/language-clojure.svg?branch=master)](https://travis-ci.org/atom/language-clojure) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/6kd5fs48y5hixde6/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-clojure/branch/master) | -| CoffeeScript | [![macOS Build Status](https://travis-ci.org/atom/language-coffee-script.svg?branch=master)](https://travis-ci.org/atom/language-coffee-script) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/4j9aak7iwn2f2x7a/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-coffee-script/branch/master) | -| CSS | [![macOS Build Status](https://travis-ci.org/atom/language-css.svg?branch=master)](https://travis-ci.org/atom/language-css) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/v8rvm88dxp73ko2y/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-css/branch/master) | -| Git | [![macOS Build Status](https://travis-ci.org/atom/language-git.svg?branch=master)](https://travis-ci.org/atom/language-git) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/481319gyrr1feo8b/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-git/branch/master) | -| GitHub Flavored Markdown | [![macOS Build Status](https://travis-ci.org/atom/language-gfm.svg?branch=master)](https://travis-ci.org/atom/language-gfm) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/rpub8qjyd8lt7wai/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-gfm/branch/master) | -| Go | [![macOS Build Status](https://travis-ci.org/atom/language-go.svg?branch=master)](https://travis-ci.org/atom/language-go) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/3fxxvv05p4hv92pn/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-go/branch/master) | -| HTML | [![macOS Build Status](https://travis-ci.org/atom/language-html.svg?branch=master)](https://travis-ci.org/atom/language-html) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/t6pk6mmdgcelfg85/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-html/branch/master) | -| Hyperlink | [![macOS Build Status](https://travis-ci.org/atom/language-hyperlink.svg?branch=master)](https://travis-ci.org/atom/language-hyperlink) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5tgvhph394r684l8/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-hyperlink/branch/master) | -| Java | [![macOS Build Status](https://travis-ci.org/atom/language-java.svg?branch=master)](https://travis-ci.org/atom/language-java) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/utoftje56n9u5x4h/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-java/branch/master) | -| JavaScript | [![macOS Build Status](https://travis-ci.org/atom/language-javascript.svg?branch=master)](https://travis-ci.org/atom/language-javascript) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ktooccwna96ssiyr/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-javascript-dijf8/branch/master) | -| JSON | [![macOS Build Status](https://travis-ci.org/atom/language-json.svg?branch=master)](https://travis-ci.org/atom/language-json) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5rx05vhdikk6c4cl/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-json/branch/master) | -| Less | [![macOS Build Status](https://travis-ci.org/atom/language-less.svg?branch=master)](https://travis-ci.org/atom/language-less) | [![Windows Build Sstatus](https://ci.appveyor.com/api/projects/status/aeina4fr4b0i7yay/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-less/branch/master) | -| Make | [![macOS Build Status](https://travis-ci.org/atom/language-make.svg?branch=master)](https://travis-ci.org/atom/language-make) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/vq1aascey21wxjh7/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-make/branch/master) | -| Mustache | [![macOS Build Status](https://travis-ci.org/atom/language-mustache.svg?branch=master)](https://travis-ci.org/atom/language-mustache) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/mbxnxaojqp0g7ldv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-mustache/branch/master) | -| Objective-C | [![macOS Build Status](https://travis-ci.org/atom/language-objective-c.svg?branch=master)](https://travis-ci.org/atom/language-objective-c) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/27j8vfv5u95fjhkw/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-objective-c/branch/master) | -| Perl | [![macOS Build Status](https://travis-ci.org/atom/language-perl.svg?branch=master)](https://travis-ci.org/atom/language-perl) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/dfs9inkkg40hchf8/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-perl/branch/master) | -| PHP | [![macOS Build Status](https://travis-ci.org/atom/language-php.svg?branch=master)](https://travis-ci.org/atom/language-php) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/y9h45ag4b72726jy/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-php/branch/master) | -| Python | [![macOS Build Status](https://travis-ci.org/atom/language-python.svg?branch=master)](https://travis-ci.org/atom/language-python) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/hmxrb9jttjh41es9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-python/branch/master) | -| Ruby | [![macOS Build Status](https://travis-ci.org/atom/language-ruby.svg?branch=master)](https://travis-ci.org/atom/language-ruby) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/71as182rm1adf2br/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-ruby/branch/master) | -| Ruby on Rails | [![macOS Build Status](https://travis-ci.org/atom/language-ruby-on-rails.svg?branch=master)](https://travis-ci.org/atom/language-ruby-on-rails) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5t4pa451fu5e0ghg/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-ruby-on-rails/branch/master) | -| Sass | [![macOS Build Status](https://travis-ci.org/atom/language-sass.svg?branch=master)](https://travis-ci.org/atom/language-sass) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/g7p16vainm4iuoot/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-sass/branch/master) | -| ShellScript | [![macOS Build Status](https://travis-ci.org/atom/language-shellscript.svg?branch=master)](https://travis-ci.org/atom/language-shellscript) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/p4um3lowgrg8y0ty/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-shellscript/branch/master) | -| SQL | [![macOS Build Status](https://travis-ci.org/atom/language-sql.svg?branch=master)](https://travis-ci.org/atom/language-sql) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ji31ouk5ehs4jdu1/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-sql/branch/master) | -| TODO | [![macOS Build Status](https://travis-ci.org/atom/language-todo.svg?branch=master)](https://travis-ci.org/atom/language-todo) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gcgb9m7h146lv6qp/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-todo/branch/master) | -| TOML | [![macOS Build Status](https://travis-ci.org/atom/language-toml.svg?branch=master)](https://travis-ci.org/atom/language-toml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kohao3fjyk6xv0sc/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-toml/branch/master) | -| XML | [![macOS Build Status](https://travis-ci.org/atom/language-xml.svg?branch=master)](https://travis-ci.org/atom/language-xml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/m5f6rn74a6h3q5uq/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-xml/branch/master) | -| YAML | [![macOS Build Status](https://travis-ci.org/atom/language-yaml.svg?branch=master)](https://travis-ci.org/atom/language-yaml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/eaa4ql7kipgphc2n/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-yaml/branch/master) | +| Language | Travis | AppVeyor/Win | +|----------|--------|--------------| +| [C/C++](https://github.com/atom/language-c) | [![macOS Build Status](https://travis-ci.org/atom/language-c.svg?branch=master)](https://travis-ci.org/atom/language-c) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/8oy1hmp4yrij7c32/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-c/branch/master) | +| [C#](https://github.com/atom/language-csharp) | [![macOS Build Status](https://travis-ci.org/atom/language-csharp.svg?branch=master)](https://travis-ci.org/atom/language-csharp) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/j1as3753y5t90obn/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-csharp/branch/master) | +| [Clojure](https://github.com/atom/language-clojure) | [![macOS Build Status](https://travis-ci.org/atom/language-clojure.svg?branch=master)](https://travis-ci.org/atom/language-clojure) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/6kd5fs48y5hixde6/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-clojure/branch/master) | +| [CoffeeScript](https://github.com/atom/language-coffee-script) | [![macOS Build Status](https://travis-ci.org/atom/language-coffee-script.svg?branch=master)](https://travis-ci.org/atom/language-coffee-script) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/4j9aak7iwn2f2x7a/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-coffee-script/branch/master) | +| [CSS](https://github.com/atom/language-css) | [![macOS Build Status](https://travis-ci.org/atom/language-css.svg?branch=master)](https://travis-ci.org/atom/language-css) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/v8rvm88dxp73ko2y/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-css/branch/master) | +| [Git](https://github.com/atom/language-git) | [![macOS Build Status](https://travis-ci.org/atom/language-git.svg?branch=master)](https://travis-ci.org/atom/language-git) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/481319gyrr1feo8b/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-git/branch/master) | +| [GitHub Flavored Markdown](https://github.com/atom/language-gfm) | [![macOS Build Status](https://travis-ci.org/atom/language-gfm.svg?branch=master)](https://travis-ci.org/atom/language-gfm) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/rpub8qjyd8lt7wai/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-gfm/branch/master) | +| [Go](https://github.com/atom/language-go) | [![macOS Build Status](https://travis-ci.org/atom/language-go.svg?branch=master)](https://travis-ci.org/atom/language-go) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/3fxxvv05p4hv92pn/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-go/branch/master) | +| [HTML](https://github.com/atom/language-html) | [![macOS Build Status](https://travis-ci.org/atom/language-html.svg?branch=master)](https://travis-ci.org/atom/language-html) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/t6pk6mmdgcelfg85/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-html/branch/master) | +| [Hyperlink](https://github.com/atom/language-hyperlink) | [![macOS Build Status](https://travis-ci.org/atom/language-hyperlink.svg?branch=master)](https://travis-ci.org/atom/language-hyperlink) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5tgvhph394r684l8/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-hyperlink/branch/master) | +| [Java](https://github.com/atom/language-java) | [![macOS Build Status](https://travis-ci.org/atom/language-java.svg?branch=master)](https://travis-ci.org/atom/language-java) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/utoftje56n9u5x4h/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-java/branch/master) | +| [JavaScript](https://github.com/atom/language-javascript) | [![macOS Build Status](https://travis-ci.org/atom/language-javascript.svg?branch=master)](https://travis-ci.org/atom/language-javascript) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ktooccwna96ssiyr/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-javascript-dijf8/branch/master) | +| [JSON](https://github.com/atom/language-json) | [![macOS Build Status](https://travis-ci.org/atom/language-json.svg?branch=master)](https://travis-ci.org/atom/language-json) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5rx05vhdikk6c4cl/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-json/branch/master) | +| [Less](https://github.com/atom/language-less) | [![macOS Build Status](https://travis-ci.org/atom/language-less.svg?branch=master)](https://travis-ci.org/atom/language-less) | [![Windows Build Sstatus](https://ci.appveyor.com/api/projects/status/aeina4fr4b0i7yay/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-less/branch/master) | +| [Make](https://github.com/atom/language-make) | [![macOS Build Status](https://travis-ci.org/atom/language-make.svg?branch=master)](https://travis-ci.org/atom/language-make) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/vq1aascey21wxjh7/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-make/branch/master) | +| [Mustache](https://github.com/atom/language-mustache) | [![macOS Build Status](https://travis-ci.org/atom/language-mustache.svg?branch=master)](https://travis-ci.org/atom/language-mustache) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/mbxnxaojqp0g7ldv/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-mustache/branch/master) | +| [Objective-C](https://github.com/atom/language-objective-c) | [![macOS Build Status](https://travis-ci.org/atom/language-objective-c.svg?branch=master)](https://travis-ci.org/atom/language-objective-c) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/27j8vfv5u95fjhkw/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-objective-c/branch/master) | +| [Perl](https://github.com/atom/language-perl) | [![macOS Build Status](https://travis-ci.org/atom/language-perl.svg?branch=master)](https://travis-ci.org/atom/language-perl) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/dfs9inkkg40hchf8/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-perl/branch/master) | +| [PHP](https://github.com/atom/language-php) | [![macOS Build Status](https://travis-ci.org/atom/language-php.svg?branch=master)](https://travis-ci.org/atom/language-php) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/y9h45ag4b72726jy/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-php/branch/master) | +| [Python](https://github.com/atom/language-python) | [![macOS Build Status](https://travis-ci.org/atom/language-python.svg?branch=master)](https://travis-ci.org/atom/language-python) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/hmxrb9jttjh41es9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-python/branch/master) | +| [Ruby](https://github.com/atom/language-ruby) | [![macOS Build Status](https://travis-ci.org/atom/language-ruby.svg?branch=master)](https://travis-ci.org/atom/language-ruby) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/71as182rm1adf2br/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-ruby/branch/master) | +| [Ruby on Rails](https://github.com/atom/language-ruby-on-rails) | [![macOS Build Status](https://travis-ci.org/atom/language-ruby-on-rails.svg?branch=master)](https://travis-ci.org/atom/language-ruby-on-rails) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/5t4pa451fu5e0ghg/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-ruby-on-rails/branch/master) | +| [Sass](https://github.com/atom/language-sass) | [![macOS Build Status](https://travis-ci.org/atom/language-sass.svg?branch=master)](https://travis-ci.org/atom/language-sass) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/g7p16vainm4iuoot/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-sass/branch/master) | +| [ShellScript](https://github.com/atom/language-shellscript) | [![macOS Build Status](https://travis-ci.org/atom/language-shellscript.svg?branch=master)](https://travis-ci.org/atom/language-shellscript) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/p4um3lowgrg8y0ty/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-shellscript/branch/master) | +| [SQL](https://github.com/atom/language-sql) | [![macOS Build Status](https://travis-ci.org/atom/language-sql.svg?branch=master)](https://travis-ci.org/atom/language-sql) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ji31ouk5ehs4jdu1/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-sql/branch/master) | +| [TODO](https://github.com/atom/language-todo) | [![macOS Build Status](https://travis-ci.org/atom/language-todo.svg?branch=master)](https://travis-ci.org/atom/language-todo) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gcgb9m7h146lv6qp/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-todo/branch/master) | +| [TOML](https://github.com/atom/language-toml) | [![macOS Build Status](https://travis-ci.org/atom/language-toml.svg?branch=master)](https://travis-ci.org/atom/language-toml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kohao3fjyk6xv0sc/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-toml/branch/master) | +| [XML](https://github.com/atom/language-xml) | [![macOS Build Status](https://travis-ci.org/atom/language-xml.svg?branch=master)](https://travis-ci.org/atom/language-xml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/m5f6rn74a6h3q5uq/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-xml/branch/master) | +| [YAML](https://github/atom/language-yaml) | [![macOS Build Status](https://travis-ci.org/atom/language-yaml.svg?branch=master)](https://travis-ci.org/atom/language-yaml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/eaa4ql7kipgphc2n/branch/master?svg=true)](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; }