mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Merge branch 'master' into sm-atom-workspace-axis
This commit is contained in:
@@ -1,24 +1,46 @@
|
||||
# Contributor Code of Conduct
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
## Our Pledge
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery
|
||||
- Personal attacks
|
||||
- Trolling or insulting/derogatory comments
|
||||
- Public or private harassment
|
||||
- Publishing other's private information, such as physical or electronic addresses, without explicit permission
|
||||
- Other unethical or unprofessional conduct
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
## Enforcement
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
@@ -50,7 +50,7 @@ To get a sense for the packages that are bundled with Atom, you can go to Settin
|
||||
|
||||
Here's a list of the big ones:
|
||||
|
||||
* [atom/atom](https://github.com/atom/atom) - Atom Core! The core editor component is responsible for basic text editing (e.g. cursors, selections, scrolling), text indentation, wrapping, and folding, text rendering, editor rendering, file system operations (e.g. saving), and installation and auto-updating. You should also use this repository for feedback related to the [core API](https://atom.io/docs/api/latest/Notification) and for large, overarching design proposals.
|
||||
* [atom/atom](https://github.com/atom/atom) - Atom Core! The core editor component is responsible for basic text editing (e.g. cursors, selections, scrolling), text indentation, wrapping, and folding, text rendering, editor rendering, file system operations (e.g. saving), and installation and auto-updating. You should also use this repository for feedback related to the [core API](https://atom.io/docs/api/latest) and for large, overarching design proposals.
|
||||
* [tree-view](https://github.com/atom/tree-view) - file and directory listing on the left of the UI.
|
||||
* [fuzzy-finder](https://github.com/atom/fuzzy-finder) - the quick file opener.
|
||||
* [find-and-replace](https://github.com/atom/find-and-replace) - all search and replace functionality.
|
||||
@@ -62,7 +62,7 @@ Here's a list of the big ones:
|
||||
* [git-diff](https://github.com/atom/git-diff) - Git change indicators shown in the editor's gutter.
|
||||
* [language-javascript](https://github.com/atom/language-javascript) - all bundled languages are packages too, and each one has a separate package `language-[name]`. Use these for feedback on syntax highlighting issues that only appear for a specific language.
|
||||
* [one-dark-ui](https://github.com/atom/one-dark-ui) - the default UI styling for anything but the text editor. UI theme packages (i.e. packages with a `-ui` suffix) provide only styling and it's possible that a bundled package is responsible for a UI issue. There are other other bundled UI themes, such as [one-light-ui](https://github.com/atom/one-light-ui).
|
||||
* [one-dark-syntax](https://github.com/atom/one-dark-syntax) - the default syntax highlighting styles applied for all languages. There are other other bundled syntax themes, such as [solarized-dark](https://github.com/atom/solarized-dark). You should use these packages for reporting issues that appear in many languages, but disappear if you change to another syntax theme.
|
||||
* [one-dark-syntax](https://github.com/atom/one-dark-syntax) - the default syntax highlighting styles applied for all languages. There are other other bundled syntax themes, such as [solarized-dark-syntax](https://github.com/atom/solarized-dark-syntax). You should use these packages for reporting issues that appear in many languages, but disappear if you change to another syntax theme.
|
||||
* [apm](https://github.com/atom/apm) - the `apm` command line tool (Atom Package Manager). You should use this repository for any contributions related to the `apm` tool and to publishing packages.
|
||||
* [atom.io](https://github.com/atom/atom.io) - the repository for feedback on the [Atom.io website](https://atom.io) and the [Atom.io package API](https://github.com/atom/atom/blob/master/docs/apm-rest-api.md) used by [apm](https://github.com/atom/apm).
|
||||
|
||||
@@ -82,7 +82,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
|
||||
|
||||
#### Before Submitting A Bug Report
|
||||
|
||||
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
|
||||
* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings).
|
||||
* **Check the [FAQs on the forum](https://discuss.atom.io/c/faq)** for a list of common questions and problems.
|
||||
* **Determine [which repository the problem should be reported in](#atom-and-packages)**.
|
||||
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
@@ -100,13 +100,13 @@ Explain the problem and include additional details to help maintainers reproduce
|
||||
* **Explain which behavior you expected to see instead and why.**
|
||||
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
* **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On OSX, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
|
||||
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](https://atom.io/docs/latest/hacking-atom-debugging#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
|
||||
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
|
||||
* **If the Chrome's developer tools pane is shown without you triggering it**, that normally means that an exception was thrown. The Console tab will include an entry for the exception. Expand the exception so that the stack trace is visible, and provide the full exception and stack trace in a [code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines) and as a screenshot.
|
||||
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
|
||||
|
||||
Provide more context by answering these questions:
|
||||
|
||||
* **Can you reproduce the problem in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode)?**
|
||||
* **Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance-problems-with-the-dev-tools-cpu-profiler)?**
|
||||
* **Did the problem start happening recently** (e.g. after updating to a new version of Atom) or was this always a problem?
|
||||
* If the problem started happening recently, **can you reproduce the problem in an older version of Atom?** What's the most recent version in which the problem doesn't happen? You can download older versions of Atom from [the releases page](https://github.com/atom/atom/releases).
|
||||
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
|
||||
@@ -118,7 +118,7 @@ Include details about your configuration and environment:
|
||||
* **What's the name and version of the OS you're using**?
|
||||
* **Are you running Atom in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest?
|
||||
* **Which [packages](#atom-and-packages) do you have installed?** You can get that list by running `apm list --installed`.
|
||||
* **Are you using [local configuration files](https://atom.io/docs/latest/using-atom-basic-customization)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
|
||||
* **Are you using [local configuration files](http://flight-manual.atom.io/using-atom/sections/basic-customization/)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
|
||||
* **Are you using Atom with multiple monitors?** If so, can you reproduce the problem when you use a single monitor?
|
||||
* **Which keyboard layout are you using?** Are you using a US layout or some other layout?
|
||||
|
||||
@@ -166,7 +166,7 @@ Before creating enhancement suggestions, please check [this list](#before-submit
|
||||
|
||||
#### Before Submitting An Enhancement Suggestion
|
||||
|
||||
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
|
||||
* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings).
|
||||
* **Check if there's already [a package](https://atom.io/packages) which provides that enhancement.**
|
||||
* **Determine [which repository the enhancement should be suggested in](#atom-and-packages).**
|
||||
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
@@ -376,7 +376,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
|
||||
| `windows` | [search][search-atom-repo-label-windows] | [search][search-atom-org-label-windows] | Related to Atom running on Windows. |
|
||||
| `linux` | [search][search-atom-repo-label-linux] | [search][search-atom-org-label-linux] | Related to Atom running on Linux. |
|
||||
| `mac` | [search][search-atom-repo-label-mac] | [search][search-atom-org-label-mac] | Related to Atom running on OSX. |
|
||||
| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/Atom) and the [flight manual](https://atom.io/docs/latest/)). |
|
||||
| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/) and the [flight manual](http://flight-manual.atom.io/)). |
|
||||
| `performance` | [search][search-atom-repo-label-performance] | [search][search-atom-org-label-performance] | Related to performance. |
|
||||
| `security` | [search][search-atom-repo-label-security] | [search][search-atom-org-label-security] | Related to security. |
|
||||
| `ui` | [search][search-atom-repo-label-ui] | [search][search-atom-org-label-ui] | Related to visual design. |
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
### Prerequisites
|
||||
|
||||
* [ ] Can you reproduce the problem in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode)?
|
||||
* [ ] Are you running the [latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version)?
|
||||
* [ ] Did you check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging)?
|
||||
* [ ] Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode)?
|
||||
* [ ] Are you running the [latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version)?
|
||||
* [ ] Did you check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)?
|
||||
* [ ] Did you check the [FAQs on Discuss](https://discuss.atom.io/c/faq)?
|
||||
* [ ] Are you reporting to the [correct repository](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#atom-and-packages)?
|
||||
* [ ] Did you [perform a cursory search](https://github.com/issues?q=is%3Aissue+user%3Aatom+-repo%3Aatom%2Felectron) to see if your bug or enhancement is already reported?
|
||||
@@ -25,4 +25,4 @@ For more information on how to write a good [bug report](https://github.com/atom
|
||||
|
||||
### Versions
|
||||
|
||||
You can get this information from executing `atom --version` and `apm --version` at the command line.
|
||||
You can get this information from executing `atom --version` and `apm --version` at the command line. Also, please include the OS and what version of the OS you're running.
|
||||
|
||||
16
README.md
16
README.md
@@ -76,6 +76,22 @@ Currently only a 64-bit version is available.
|
||||
The Linux version does not currently automatically update so you will need to
|
||||
repeat these steps to upgrade to future releases.
|
||||
|
||||
### Archive extraction
|
||||
|
||||
An archive is available for people who don't want to install `atom` as root.
|
||||
|
||||
This version enables you to install multiple Atom versions in parallel. It has been built on Ubuntu 64-bit,
|
||||
but should be compatible with other Linux distributions.
|
||||
|
||||
1. Install dependencies (on Ubuntu): `sudo apt install git gconf2 gconf-service libgtk2.0-0 libudev1 libgcrypt20
|
||||
libnotify4 libxtst6 libnss3 python gvfs-bin xdg-utils libcap2`
|
||||
2. Download `atom-amd64.tar.gz` from the [Atom releases page](https://github.com/atom/atom/releases/latest).
|
||||
3. Run `tar xf atom-amd64.tar.gz` in the directory where you want to extract the Atom folder.
|
||||
4. Launch Atom using the installed `atom` command from the newly extracted directory.
|
||||
|
||||
The Linux version does not currently automatically update so you will need to
|
||||
repeat these steps to upgrade to future releases.
|
||||
|
||||
## Building
|
||||
|
||||
* [Linux](docs/build-instructions/linux.md)
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "1.9.1"
|
||||
"atom-package-manager": "1.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ module.exports = (grunt) ->
|
||||
homeDir = process.env.USERPROFILE
|
||||
contentsDir = shellAppDir
|
||||
appDir = path.join(shellAppDir, 'resources', 'app')
|
||||
installDir ?= path.join(process.env.ProgramFiles, appName)
|
||||
installDir ?= path.join(process.env.LOCALAPPDATA, appName, 'app-dev')
|
||||
killCommand = 'taskkill /F /IM atom.exe'
|
||||
else if process.platform is 'darwin'
|
||||
homeDir = process.env.HOME
|
||||
@@ -285,6 +285,7 @@ module.exports = (grunt) ->
|
||||
ciTasks.push('dump-symbols') if process.platform is 'darwin'
|
||||
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
|
||||
ciTasks.push('mkdeb') if process.platform is 'linux'
|
||||
ciTasks.push('mktar') if process.platform is 'linux'
|
||||
ciTasks.push('codesign:exe') if process.platform is 'win32' and not process.env.CI
|
||||
ciTasks.push('create-windows-installer:installer') if process.platform is 'win32'
|
||||
ciTasks.push('test') if process.platform is 'darwin'
|
||||
@@ -297,6 +298,7 @@ module.exports = (grunt) ->
|
||||
unless process.platform is 'linux' or grunt.option('no-install')
|
||||
defaultTasks.push 'install'
|
||||
grunt.registerTask('default', defaultTasks)
|
||||
grunt.registerTask('build-and-sign', ['download-electron', 'download-electron-chromedriver', 'build', 'set-version', 'generate-asar', 'codesign:app', 'install'])
|
||||
|
||||
getDefaultChannelAndReleaseBranch = (version) ->
|
||||
if version.match(/dev/) or isBuildingPR()
|
||||
|
||||
BIN
build/certs/AtomDevTestSignKey.p12
Normal file
BIN
build/certs/AtomDevTestSignKey.p12
Normal file
Binary file not shown.
@@ -54,9 +54,9 @@ module.exports = (grunt) ->
|
||||
# so that it doesn't becomes larger than it needs to be.
|
||||
ignoredPaths = [
|
||||
path.join('git-utils', 'deps')
|
||||
path.join('nodegit', 'vendor')
|
||||
path.join('nodegit', 'node_modules', 'node-pre-gyp')
|
||||
path.join('nodegit', 'node_modules', '.bin')
|
||||
path.join('ohnogit', 'node_modules', 'nodegit', 'vendor')
|
||||
path.join('ohnogit', 'node_modules', 'nodegit', 'node_modules', 'node-pre-gyp')
|
||||
path.join('ohnogit', 'node_modules', 'nodegit', 'node_modules', '.bin')
|
||||
path.join('oniguruma', 'deps')
|
||||
path.join('less', 'dist')
|
||||
path.join('bootstrap', 'docs')
|
||||
@@ -122,9 +122,9 @@ module.exports = (grunt) ->
|
||||
# Ignore *.cc and *.h files from native modules
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('ctags', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('git-utils', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('nodegit', 'include') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'src') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'generate') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('ohnogit', 'node_modules', 'nodegit', 'include') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('keytar', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('nslog', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('oniguruma', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
@@ -133,7 +133,6 @@ module.exports = (grunt) ->
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('scrollbar-style', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('spellchecker', 'src') + path.sep)}.*\\.(cc|h)*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('cached-run-in-this-context', 'src') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('marker-index', 'src') + path.sep)}.*\\.(cc|h)?"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('keyboard-layout', 'src') + path.sep)}.*\\.(cc|h|mm)*"
|
||||
|
||||
# Ignore build files
|
||||
|
||||
@@ -1,39 +1,71 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
request = require 'request'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
grunt.registerTask 'codesign:exe', 'Codesign atom.exe and Update.exe', ->
|
||||
signUsingWindowsSDK = (exeToSign, callback) ->
|
||||
{WIN_P12KEY_PASSWORD, WIN_P12KEY_URL} = process.env
|
||||
if WIN_P12KEY_URL?
|
||||
grunt.log.ok("Obtaining signing key")
|
||||
downloadedKeyFile = path.resolve(__dirname, 'DownloadedSignKey.p12')
|
||||
downloadFile WIN_P12KEY_URL, downloadedKeyFile, (done) ->
|
||||
signUsingWindowsSDKTool exeToSign, downloadedKeyFile, WIN_P12KEY_PASSWORD, (done) ->
|
||||
fs.unlinkSync(downloadedKeyFile)
|
||||
callback()
|
||||
else
|
||||
signUsingWindowsSDKTool exeToSign, path.resolve(__dirname, '..', 'certs', 'AtomDevTestSignKey.p12'), 'password', callback
|
||||
|
||||
signUsingWindowsSDKTool = (exeToSign, keyFilePath, password, callback) ->
|
||||
grunt.log.ok("Signing #{exeToSign}")
|
||||
args = ['sign', '/v', '/p', password, '/f', keyFilePath, exeToSign]
|
||||
spawn {cmd: 'C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.1A\\bin\\signtool.exe', args: args}, callback
|
||||
|
||||
signUsingJanky = (exeToSign, callback) ->
|
||||
spawn {cmd: process.env.JANKY_SIGNTOOL, args: [exeToSign]}, callback
|
||||
|
||||
signWindowsExecutable = if process.env.JANKY_SIGNTOOL then signUsingJanky else signUsingWindowsSDK
|
||||
|
||||
grunt.registerTask 'codesign:exe', 'CodeSign Atom.exe and Update.exe', ->
|
||||
done = @async()
|
||||
spawn {cmd: 'taskkill', args: ['/F', '/IM', 'atom.exe']}, ->
|
||||
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
|
||||
atomExePath = path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')
|
||||
spawn {cmd, args: [atomExePath]}, (error) ->
|
||||
signWindowsExecutable atomExePath, (error) ->
|
||||
return done(error) if error?
|
||||
|
||||
updateExePath = path.resolve(__dirname, '..', 'node_modules', 'grunt-electron-installer', 'vendor', 'Update.exe')
|
||||
spawn {cmd, args: [updateExePath]}, (error) -> done(error)
|
||||
signWindowsExecutable updateExePath, (error) -> done(error)
|
||||
|
||||
grunt.registerTask 'codesign:installer', 'Codesign AtomSetup.exe', ->
|
||||
grunt.registerTask 'codesign:installer', 'CodeSign AtomSetup.exe', ->
|
||||
done = @async()
|
||||
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
|
||||
atomSetupExePath = path.resolve(grunt.config.get('atom.buildDir'), 'installer', 'AtomSetup.exe')
|
||||
spawn {cmd, args: [atomSetupExePath]}, (error) -> done(error)
|
||||
signWindowsExecutable atomSetupExePath, (error) -> done(error)
|
||||
|
||||
grunt.registerTask 'codesign:app', 'Codesign Atom.app', ->
|
||||
grunt.registerTask 'codesign:app', 'CodeSign Atom.app', ->
|
||||
done = @async()
|
||||
|
||||
unlockKeychain (error) ->
|
||||
return done(error) if error?
|
||||
|
||||
cmd = 'codesign'
|
||||
args = ['--deep', '--force', '--verbose', '--sign', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
|
||||
spawn {cmd, args}, (error) -> done(error)
|
||||
spawn {cmd: 'codesign', args: args}, (error) -> done(error)
|
||||
|
||||
unlockKeychain = (callback) ->
|
||||
return callback() unless process.env.XCODE_KEYCHAIN
|
||||
|
||||
cmd = 'security'
|
||||
{XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env
|
||||
args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN]
|
||||
spawn {cmd, args}, (error) -> callback(error)
|
||||
spawn {cmd: 'security', args: args}, (error) -> callback(error)
|
||||
|
||||
downloadFile = (sourceUrl, targetPath, callback) ->
|
||||
options = {
|
||||
url: sourceUrl
|
||||
headers: {
|
||||
'User-Agent': 'Atom Signing Key build task',
|
||||
'Accept': 'application/vnd.github.VERSION.raw'
|
||||
}
|
||||
}
|
||||
request(options)
|
||||
.pipe(fs.createWriteStream(targetPath))
|
||||
.on('finish', callback)
|
||||
|
||||
@@ -16,10 +16,22 @@ module.exports = (grunt) ->
|
||||
{description} = grunt.config.get('atom.metadata')
|
||||
|
||||
if process.platform is 'win32'
|
||||
runas ?= require 'runas'
|
||||
copyFolder = path.resolve 'script', 'copy-folder.cmd'
|
||||
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: true) isnt 0
|
||||
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
|
||||
done = @async()
|
||||
fs.access(installDir, fs.W_OK, (err) ->
|
||||
adminRequired = true if err
|
||||
if adminRequired
|
||||
grunt.log.ok("User does not have write access to #{installDir}, elevating to admin")
|
||||
runas ?= require 'runas'
|
||||
copyFolder = path.resolve 'script', 'copy-folder.cmd'
|
||||
|
||||
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: adminRequired) isnt 0
|
||||
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
|
||||
else
|
||||
grunt.log.ok("Installed into #{installDir}")
|
||||
|
||||
done()
|
||||
)
|
||||
|
||||
else if process.platform is 'darwin'
|
||||
rm installDir
|
||||
mkdir path.dirname(installDir)
|
||||
|
||||
@@ -123,10 +123,10 @@ module.exports =
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
'tweetnacl@0.13.2':
|
||||
'tweetnacl@0.14.3':
|
||||
repository: 'https://github.com/dchest/tweetnacl-js'
|
||||
license: 'Public Domain'
|
||||
source: 'https://github.com/dchest/tweetnacl-js/blob/2f328394f74d83564634fb89ea2798caa3a4edb9/README.md says public domain.'
|
||||
source: 'https://github.com/dchest/tweetnacl-js/blob/1042c9c65dc8f1dcb9e981d962d7dbbcf58f1fdc/COPYING.txt says public domain.'
|
||||
'json-schema@0.2.2':
|
||||
repository: 'https://github.com/kriszyp/json-schema'
|
||||
license: 'BSD'
|
||||
|
||||
30
build/tasks/mktar-task.coffee
Normal file
30
build/tasks/mktar-task.coffee
Normal file
@@ -0,0 +1,30 @@
|
||||
path = require 'path'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{spawn, fillTemplate} = require('./task-helpers')(grunt)
|
||||
|
||||
grunt.registerTask 'mktar', 'Create an archive', ->
|
||||
done = @async()
|
||||
|
||||
appFileName = grunt.config.get('atom.appFileName')
|
||||
buildDir = grunt.config.get('atom.buildDir')
|
||||
shellAppDir = grunt.config.get('atom.shellAppDir')
|
||||
{version, description} = grunt.config.get('atom.metadata')
|
||||
|
||||
if process.arch is 'ia32'
|
||||
arch = 'i386'
|
||||
else if process.arch is 'x64'
|
||||
arch = 'amd64'
|
||||
else
|
||||
return done("Unsupported arch #{process.arch}")
|
||||
|
||||
iconPath = path.join(shellAppDir, 'resources', 'app.asar.unpacked', 'resources', 'atom.png')
|
||||
|
||||
cmd = path.join('script', 'mktar')
|
||||
args = [appFileName, version, arch, iconPath, buildDir]
|
||||
spawn {cmd, args}, (error) ->
|
||||
if error?
|
||||
done(error)
|
||||
else
|
||||
grunt.log.ok "Created " + path.join(buildDir, "#{appFileName}-#{version}-#{arch}.tar.gz")
|
||||
done()
|
||||
@@ -74,7 +74,7 @@ getAssets = ->
|
||||
]
|
||||
when 'win32'
|
||||
assets = [{assetName: 'atom-windows.zip', sourcePath: appName}]
|
||||
for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{version}-full.nupkg", "atom-#{version}-delta.nupkg"]
|
||||
for squirrelAsset in ['AtomSetup.exe', 'AtomSetup.msi', 'RELEASES', "atom-#{version}-full.nupkg", "atom-#{version}-delta.nupkg"]
|
||||
cp path.join(buildDir, 'installer', squirrelAsset), path.join(buildDir, squirrelAsset)
|
||||
assets.push({assetName: squirrelAsset, sourcePath: assetName})
|
||||
assets
|
||||
@@ -85,13 +85,13 @@ getAssets = ->
|
||||
arch = 'amd64'
|
||||
|
||||
# Check for a Debian build
|
||||
sourcePath = "#{buildDir}/#{appFileName}-#{version}-#{arch}.deb"
|
||||
sourcePath = path.join(buildDir, "#{appFileName}-#{version}-#{arch}.deb")
|
||||
assetName = "atom-#{arch}.deb"
|
||||
|
||||
# Check for a Fedora build
|
||||
unless fs.isFileSync(sourcePath)
|
||||
rpmName = fs.readdirSync("#{buildDir}/rpm")[0]
|
||||
sourcePath = "#{buildDir}/rpm/#{rpmName}"
|
||||
sourcePath = path.join(buildDir, "rpm", rpmName)
|
||||
if process.arch is 'ia32'
|
||||
arch = 'i386'
|
||||
else
|
||||
@@ -99,10 +99,17 @@ getAssets = ->
|
||||
assetName = "atom.#{arch}.rpm"
|
||||
|
||||
cp sourcePath, path.join(buildDir, assetName)
|
||||
assets = [{assetName, sourcePath}]
|
||||
|
||||
[
|
||||
{assetName, sourcePath}
|
||||
]
|
||||
# Check for an archive build on a debian build machine.
|
||||
# We could provide a Fedora version if some libraries are not compatible
|
||||
sourcePath = path.join(buildDir, "#{appFileName}-#{version}-#{arch}.tar.gz")
|
||||
if fs.isFileSync(sourcePath)
|
||||
assetName = "atom-#{arch}.tar.gz"
|
||||
cp sourcePath, path.join(buildDir, assetName)
|
||||
assets.push({assetName, sourcePath})
|
||||
|
||||
assets
|
||||
|
||||
logError = (message, error, details) ->
|
||||
grunt.log.error(message)
|
||||
|
||||
@@ -17,20 +17,6 @@ module.exports = (grunt) ->
|
||||
|
||||
packageSpecQueue = null
|
||||
|
||||
logDeprecations = (label, {stderr}={}) ->
|
||||
return unless process.env.JANKY_SHA1 or process.env.CI
|
||||
stderr ?= ''
|
||||
deprecatedStart = stderr.indexOf('Calls to deprecated functions')
|
||||
return if deprecatedStart is -1
|
||||
|
||||
grunt.log.error(label)
|
||||
stderr = stderr.substring(deprecatedStart)
|
||||
stderr = stderr.replace(/^\s*\[[^\]]+\]\s+/gm, '')
|
||||
stderr = stderr.replace(/source: .*$/gm, '')
|
||||
stderr = stderr.replace(/^"/gm, '')
|
||||
stderr = stderr.replace(/",\s*$/gm, '')
|
||||
grunt.log.error(stderr)
|
||||
|
||||
getAppPath = ->
|
||||
contentsDir = grunt.config.get('atom.contentsDir')
|
||||
switch process.platform
|
||||
@@ -57,14 +43,14 @@ module.exports = (grunt) ->
|
||||
args: ['--test', "--resource-path=#{resourcePath}", path.join(packagePath, 'spec')]
|
||||
opts:
|
||||
cwd: packagePath
|
||||
env: _.extend({}, process.env, ATOM_PATH: rootDir)
|
||||
env: _.extend({}, process.env, ELECTRON_ENABLE_LOGGING: true, ATOM_PATH: rootDir)
|
||||
else if process.platform is 'win32'
|
||||
options =
|
||||
cmd: process.env.comspec
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--log-file=ci.log", path.join(packagePath, 'spec')]
|
||||
opts:
|
||||
cwd: packagePath
|
||||
env: _.extend({}, process.env, ATOM_PATH: rootDir)
|
||||
env: _.extend({}, process.env, ELECTRON_ENABLE_LOGGING: true, ATOM_PATH: rootDir)
|
||||
|
||||
grunt.log.ok "Launching #{path.basename(packagePath)} specs."
|
||||
spawn options, (error, results, code) ->
|
||||
@@ -74,7 +60,6 @@ module.exports = (grunt) ->
|
||||
fs.unlinkSync(path.join(packagePath, 'ci.log'))
|
||||
|
||||
failedPackages.push path.basename(packagePath) if error
|
||||
logDeprecations("#{path.basename(packagePath)} Specs", results)
|
||||
callback()
|
||||
|
||||
modulesDirectory = path.resolve('node_modules')
|
||||
@@ -87,7 +72,7 @@ module.exports = (grunt) ->
|
||||
packageSpecQueue.concurrency = Math.max(1, concurrency - 1)
|
||||
packageSpecQueue.drain = -> callback(null, failedPackages)
|
||||
|
||||
runCoreSpecs = (callback, logOutput = false) ->
|
||||
runCoreSpecs = (callback) ->
|
||||
appPath = getAppPath()
|
||||
resourcePath = process.cwd()
|
||||
coreSpecsPath = path.resolve('spec')
|
||||
@@ -97,21 +82,16 @@ module.exports = (grunt) ->
|
||||
cmd: appPath
|
||||
args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath, "--user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"]
|
||||
opts:
|
||||
env: _.extend({}, process.env,
|
||||
ATOM_INTEGRATION_TESTS_ENABLED: true
|
||||
)
|
||||
env: _.extend({}, process.env, {ELECTRON_ENABLE_LOGGING: true, ATOM_INTEGRATION_TESTS_ENABLED: true})
|
||||
stdio: 'inherit'
|
||||
|
||||
else if process.platform is 'win32'
|
||||
options =
|
||||
cmd: process.env.comspec
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", '--log-file=ci.log', coreSpecsPath]
|
||||
opts:
|
||||
env: _.extend({}, process.env,
|
||||
ATOM_INTEGRATION_TESTS_ENABLED: true
|
||||
)
|
||||
|
||||
if logOutput
|
||||
options.opts.stdio = 'inherit'
|
||||
env: _.extend({}, process.env, {ELECTRON_ENABLE_LOGGING: true, ATOM_INTEGRATION_TESTS_ENABLED: true})
|
||||
stdio: 'inherit'
|
||||
|
||||
grunt.log.ok "Launching core specs."
|
||||
spawn options, (error, results, code) ->
|
||||
@@ -121,7 +101,6 @@ module.exports = (grunt) ->
|
||||
else
|
||||
# TODO: Restore concurrency on Windows
|
||||
packageSpecQueue?.concurrency = concurrency
|
||||
logDeprecations('Core Specs', results)
|
||||
|
||||
callback(null, error)
|
||||
|
||||
@@ -134,17 +113,11 @@ module.exports = (grunt) ->
|
||||
else
|
||||
async.parallel
|
||||
|
||||
# If we're just running the core specs then we won't have any output to
|
||||
# indicate the tests actually *are* running. This upsets Travis:
|
||||
# https://github.com/atom/atom/issues/10837. So pass the test output
|
||||
# through.
|
||||
runCoreSpecsWithLogging = (callback) -> runCoreSpecs(callback, true)
|
||||
|
||||
specs =
|
||||
if process.env.ATOM_SPECS_TASK is 'packages'
|
||||
[runPackageSpecs]
|
||||
else if process.env.ATOM_SPECS_TASK is 'core'
|
||||
[runCoreSpecsWithLogging]
|
||||
[runCoreSpecs]
|
||||
else
|
||||
[runCoreSpecs, runPackageSpecs]
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
|
||||
* OS with 64-bit or 32-bit architecture
|
||||
* C++ toolchain
|
||||
* [Git](http://git-scm.com/)
|
||||
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
|
||||
* [Git](https://git-scm.com/)
|
||||
* [Node.js](https://nodejs.org/en/download/) (0.10.x or above)
|
||||
* [npm](https://www.npmjs.com/) v1.4.x or above (automatically bundled with Node.js)
|
||||
* `npm -v` to check the version.
|
||||
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
|
||||
@@ -74,18 +74,24 @@ If you have problems with permissions don't forget to prefix with `sudo`
|
||||
|
||||
To use the newly installed Atom, quit and restart all running Atom instances.
|
||||
|
||||
5. *Optionally*, you may generate distributable packages of Atom at `out`. Currently, `.deb` and `.rpm` package types are supported. To create a `.deb` package run:
|
||||
5. *Optionally*, you may generate distributable packages of Atom at `out`. Currently, `.deb` and `.rpm` package types are supported, as well as a `.tar.gz` archive. To create a `.deb` package run:
|
||||
|
||||
```sh
|
||||
script/grunt mkdeb
|
||||
```
|
||||
|
||||
To create an `.rpm` package run
|
||||
To create a `.rpm` package run
|
||||
|
||||
```sh
|
||||
script/grunt mkrpm
|
||||
```
|
||||
|
||||
To create a `.tar.gz` archive run
|
||||
|
||||
```sh
|
||||
script/grunt mktar
|
||||
```
|
||||
|
||||
## Advanced Options
|
||||
|
||||
### Custom build directory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Requirements
|
||||
|
||||
* OS X 10.8 or later
|
||||
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
|
||||
* [Node.js](https://nodejs.org/en/download/) (0.10.x or above)
|
||||
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
|
||||
|
||||
## Instructions
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Requirements
|
||||
|
||||
### General
|
||||
* [Node.js](http://nodejs.org/en/download/) v4.x
|
||||
* [Node.js](https://nodejs.org/en/download/) v4.x
|
||||
* [Python](https://www.python.org/downloads/) v2.7.x
|
||||
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`.
|
||||
If it is installed elsewhere, you can create a symbolic link to the
|
||||
@@ -14,29 +14,27 @@
|
||||
|
||||
You can use either:
|
||||
|
||||
* [Visual Studio 2013 Update 5](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express or better) on Windows 7, 8 or 10
|
||||
* [Visual Studio 2015](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community or better) with Windows 8 or 10
|
||||
* [Visual Studio 2013 Update 5](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express or better) on Windows 7, 8 or 10
|
||||
* [Visual Studio 2015](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community or better) with Windows 8 or 10
|
||||
|
||||
Whichever version you use, ensure that:
|
||||
|
||||
* The default installation folder is chosen so the build tools can find it
|
||||
* Visual C++ support is installed
|
||||
* You set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (`2013` or `2015`), e.g. , e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell or set it in Windows advanced system settings control panel.
|
||||
* The git command is in your path
|
||||
* A `git` command is in your path
|
||||
* If you have both VS2013 and VS2015 installed set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (`2013` or `2015`) you wish to use, e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell or set it in Windows advanced system settings control panel.
|
||||
|
||||
## Instructions
|
||||
|
||||
You can run these commands using Command Prompt, PowerShell or Git Shell via [GitHub Desktop](https://desktop.github.com/). These instructions will assume the use of Bash from Git Shell - if you are using Command Prompt use a backslash instead: i.e. `script\build`.
|
||||
|
||||
**VS2015 + Git Shell users** should note that the default path supplied with Git Shell includes reference to an older version of msbuild that will fail. It is recommended you use a PowerShell window that has git in the path at this time.
|
||||
|
||||
```bash
|
||||
cd C:\
|
||||
git clone https://github.com/atom/atom/
|
||||
cd atom
|
||||
script/build
|
||||
```
|
||||
This will create the Atom application in the `Program Files` folder.
|
||||
This will create the Atom application in the `out\Atom` folder as well as copy it to a subfolder of your user profile (e.g. `c:\Users\Bob`) called `AppData\Local\atom\app-dev`.
|
||||
|
||||
### `script/build` Options
|
||||
* `--install-dir` - Creates the final built application in this directory. Example (trailing slash is optional):
|
||||
@@ -47,6 +45,7 @@ This will create the Atom application in the `Program Files` folder.
|
||||
```bash
|
||||
./script/build --build-dir Z:\Some\Temporary\Directory\
|
||||
```
|
||||
* `--no-install` - Skips the installation task after building.
|
||||
* `--verbose` - Verbose mode. A lot more information output.
|
||||
|
||||
## Do I have to use GitHub Desktop?
|
||||
@@ -63,37 +62,34 @@ If none of this works, do install Github Desktop and use its Git Shell as it mak
|
||||
|
||||
### Common Errors
|
||||
* `node is not recognized`
|
||||
|
||||
* If you just installed Node.js, you'll need to restart your PowerShell/Command Prompt/Git Shell before the node
|
||||
command is available on your Path.
|
||||
|
||||
* `script/build` outputs only the Node.js and Python versions before returning
|
||||
* `msbuild.exe failed with exit code: 1`
|
||||
* Ensure you have Visual C++ support installed. Go into Add/Remove Programs, select Visual Studio and press Modify and then check the Visual C++ box.
|
||||
|
||||
* `script/build` stops with no error or warning shortly after displaying the versions of node, npm and Python
|
||||
* Make sure that the path where you have checked out Atom does not include a space. e.g. use `c:\atom` and not `c:\my stuff\atom`
|
||||
|
||||
* `script/build` outputs only the Node.js and Python versions before returning
|
||||
* Try moving the repository to `C:\atom`. Most likely, the path is too long.
|
||||
See [issue #2200](https://github.com/atom/atom/issues/2200).
|
||||
|
||||
* `error MSB4025: The project file could not be loaded. Invalid character in the given encoding.`
|
||||
|
||||
* This can occur because your home directory (`%USERPROFILE%`) has non-ASCII
|
||||
characters in it. This is a bug in [gyp](https://code.google.com/p/gyp/)
|
||||
which is used to build native Node.js modules and there is no known workaround.
|
||||
* https://github.com/TooTallNate/node-gyp/issues/297
|
||||
* https://code.google.com/p/gyp/issues/detail?id=393
|
||||
|
||||
* `script/build` stops at installing runas with `Failed at the runas@x.y.z install script.`
|
||||
* `'node_modules\.bin\npm' is not recognized as an internal or external command, operable program or batch file.`
|
||||
* This occurs if the previous build left things in a bad state. Run `script\clean` and then `script\build` again.
|
||||
|
||||
* `script/build` stops at installing runas with `Failed at the runas@x.y.z install script.`
|
||||
* See the next item.
|
||||
|
||||
* `error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.`
|
||||
|
||||
* If you're building Atom with Visual Studio 2013 or above make sure the `GYP_MSVS_VERSION` environment variable is set, and then re-run `script/build` after a clean:
|
||||
|
||||
```bash
|
||||
$env:GYP_MSVS_VERSION='2013' # '2015' if using Visual Studio 2015, and so on
|
||||
script/clean
|
||||
script/build
|
||||
```
|
||||
* If you are using Visual Studio 2013 or above and the build fails with some other error message this environment variable might still be required and ensure you have Visual C++ language support installed.
|
||||
* If you're building Atom with Visual Studio 2013 try setting the `GYP_MSVS_VERSION` environment variable to 2013 and then `script/clean` followed by `script/build` (re-open your command prompt or Powershell window if you set it using the GUI)
|
||||
|
||||
* Other `node-gyp` errors on first build attempt, even though the right Node.js and Python versions are installed.
|
||||
* Do try the build command one more time, as experience shows it often works on second try in many of these cases.
|
||||
|
||||
18
docs/native-profiling.md
Normal file
18
docs/native-profiling.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Profiling the Atom Render Process on OS X with Instruments
|
||||
|
||||

|
||||
|
||||
* Determine the version of Electron for your version of Atom.
|
||||
* Open the dev tools with `alt-cmd-i`
|
||||
* Evaluate `process.versions.electron` in the console.
|
||||
* Based on this version, download the appropriate Electron symbols from the [releases](https://github.com/atom/electron/releases) page.
|
||||
* The file name should look like `electron-v0.X.Y-darwin-x64-dsym.zip`.
|
||||
* Decompress these symbols in your `~/Downloads` directory.
|
||||
* Now create a time profile in Instruments.
|
||||
* Open `Instruments.app`.
|
||||
* Select `Time Profiler`
|
||||
* In Atom, determine the pid to attach to by evaluating `process.pid` in the dev tools console.
|
||||
* Attach to this pid via the menu at the upper left corner of the Instruments profiler.
|
||||
* Click record, do your thing.
|
||||
* Click stop.
|
||||
* The symbols should have been automatically located by Instruments (via Spotlight or something?), giving you a readable profile.
|
||||
@@ -18,15 +18,15 @@
|
||||
# 'ctrl-p': 'core:move-down'
|
||||
#
|
||||
# You can find more information about keymaps in these guides:
|
||||
# * https://atom.io/docs/latest/using-atom-basic-customization#customizing-key-bindings
|
||||
# * https://atom.io/docs/latest/behind-atom-keymaps-in-depth
|
||||
# * http://flight-manual.atom.io/using-atom/sections/basic-customization/#_customizing_keybindings
|
||||
# * http://flight-manual.atom.io/behind-atom/sections/keymaps-in-depth/
|
||||
#
|
||||
# If you're having trouble with your keybindings not working, try the
|
||||
# Keybinding Resolver: `Cmd+.` on OS X and `Ctrl+.` on other platforms. See the
|
||||
# Debugging Guide for more information:
|
||||
# * https://atom.io/docs/latest/hacking-atom-debugging#check-the-keybindings
|
||||
# * http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-the-keybindings
|
||||
#
|
||||
# This file uses CoffeeScript Object Notation (CSON).
|
||||
# If you are unfamiliar with CSON, you can read more about it in the
|
||||
# Atom Flight Manual:
|
||||
# https://atom.io/docs/latest/using-atom-basic-customization#cson
|
||||
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
# This file uses CoffeeScript Object Notation (CSON).
|
||||
# If you are unfamiliar with CSON, you can read more about it in the
|
||||
# Atom Flight Manual:
|
||||
# https://atom.io/docs/latest/using-atom-basic-customization#cson
|
||||
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson
|
||||
|
||||
@@ -130,6 +130,8 @@
|
||||
|
||||
# Atom Specific
|
||||
'ctrl-W': 'editor:select-word'
|
||||
'cmd-ctrl-left': 'editor:move-selection-left'
|
||||
'cmd-ctrl-right': 'editor:move-selection-right'
|
||||
|
||||
# Sublime Parity
|
||||
'cmd-a': 'core:select-all'
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
'ctrl-shift-pageup': 'pane:move-item-left'
|
||||
'ctrl-shift-pagedown': 'pane:move-item-right'
|
||||
'f11': 'window:toggle-full-screen'
|
||||
'alt-shift-left': 'editor:move-selection-left'
|
||||
'alt-shift-right': 'editor:move-selection-right'
|
||||
|
||||
# Sublime Parity
|
||||
'ctrl-,': 'application:show-settings'
|
||||
@@ -25,6 +27,7 @@
|
||||
'ctrl-n': 'application:new-file'
|
||||
'ctrl-s': 'core:save'
|
||||
'ctrl-S': 'core:save-as'
|
||||
'ctrl-f4': 'core:close'
|
||||
'ctrl-w': 'core:close'
|
||||
'ctrl-z': 'core:undo'
|
||||
'ctrl-y': 'core:redo'
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
'ctrl-shift-left': 'pane:move-item-left'
|
||||
'ctrl-shift-right': 'pane:move-item-right'
|
||||
'f11': 'window:toggle-full-screen'
|
||||
'alt-shift-left': 'editor:move-selection-left'
|
||||
'alt-shift-right': 'editor:move-selection-right'
|
||||
|
||||
# Sublime Parity
|
||||
'ctrl-,': 'application:show-settings'
|
||||
|
||||
@@ -75,6 +75,13 @@
|
||||
{ label: 'Join Lines', command: 'editor:join-lines' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Columns',
|
||||
submenu: [
|
||||
{ label: 'Move Selection Left', command: 'editor:move-selection-left' }
|
||||
{ label: 'Move Selection Right', command: 'editor:move-selection-right' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Text',
|
||||
submenu: [
|
||||
@@ -221,10 +228,10 @@
|
||||
{label: 'Delete', command: 'core:delete'}
|
||||
{label: 'Select All', command: 'core:select-all'}
|
||||
{type: 'separator'}
|
||||
{label: 'Split Up', command: 'pane:split-up'}
|
||||
{label: 'Split Down', command: 'pane:split-down'}
|
||||
{label: 'Split Left', command: 'pane:split-left'}
|
||||
{label: 'Split Right', command: 'pane:split-right'}
|
||||
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
|
||||
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
|
||||
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
|
||||
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
|
||||
{label: 'Close Pane', command: 'pane:close'}
|
||||
{type: 'separator'}
|
||||
]
|
||||
|
||||
@@ -48,6 +48,13 @@
|
||||
{ label: '&Join Lines', command: 'editor:join-lines' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Columns',
|
||||
submenu: [
|
||||
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
|
||||
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Text',
|
||||
submenu: [
|
||||
@@ -197,10 +204,10 @@
|
||||
{label: 'Delete', command: 'core:delete'}
|
||||
{label: 'Select All', command: 'core:select-all'}
|
||||
{type: 'separator'}
|
||||
{label: 'Split Up', command: 'pane:split-up'}
|
||||
{label: 'Split Down', command: 'pane:split-down'}
|
||||
{label: 'Split Left', command: 'pane:split-left'}
|
||||
{label: 'Split Right', command: 'pane:split-right'}
|
||||
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
|
||||
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
|
||||
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
|
||||
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
|
||||
{label: 'Close Pane', command: 'pane:close'}
|
||||
{type: 'separator'}
|
||||
]
|
||||
|
||||
@@ -56,6 +56,13 @@
|
||||
{ label: '&Join Lines', command: 'editor:join-lines' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Columns',
|
||||
submenu: [
|
||||
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
|
||||
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Text',
|
||||
submenu: [
|
||||
@@ -200,10 +207,10 @@
|
||||
{label: 'Delete', command: 'core:delete'}
|
||||
{label: 'Select All', command: 'core:select-all'}
|
||||
{type: 'separator'}
|
||||
{label: 'Split Up', command: 'pane:split-up'}
|
||||
{label: 'Split Down', command: 'pane:split-down'}
|
||||
{label: 'Split Left', command: 'pane:split-left'}
|
||||
{label: 'Split Right', command: 'pane:split-right'}
|
||||
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
|
||||
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
|
||||
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
|
||||
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
|
||||
{label: 'Close Pane', command: 'pane:close'}
|
||||
{type: 'separator'}
|
||||
]
|
||||
|
||||
97
package.json
97
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "1.8.0-dev",
|
||||
"version": "1.9.0-dev",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -12,16 +12,17 @@
|
||||
"url": "https://github.com/atom/atom/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"electronVersion": "0.36.8",
|
||||
"electronVersion": "0.36.12",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^6.3.1",
|
||||
"atom-keymap": "6.3.2",
|
||||
"babel-core": "^5.8.21",
|
||||
"bootstrap": "^3.3.4",
|
||||
"cached-run-in-this-context": "0.4.1",
|
||||
"clear-cut": "^2.0.1",
|
||||
"coffee-script": "1.8.0",
|
||||
"color": "^0.7.3",
|
||||
"devtron": "1.1.0",
|
||||
"event-kit": "^1.5.0",
|
||||
"find-parent-dir": "^0.3.0",
|
||||
"first-mate": "^5.1.1",
|
||||
@@ -36,12 +37,12 @@
|
||||
"key-path-helpers": "^0.4.0",
|
||||
"less-cache": "0.23",
|
||||
"line-top-index": "0.2.0",
|
||||
"marked": "^0.3.4",
|
||||
"nodegit": "0.11.9",
|
||||
"marked": "^0.3.5",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^3",
|
||||
"ohnogit": "0.0.11",
|
||||
"oniguruma": "^5",
|
||||
"pathwatcher": "~6.2",
|
||||
"pathwatcher": "~6.5",
|
||||
"property-accessors": "^1.1.3",
|
||||
"random-words": "0.0.1",
|
||||
"resolve": "^1.1.6",
|
||||
@@ -54,7 +55,7 @@
|
||||
"service-hub": "^0.7.0",
|
||||
"source-map-support": "^0.3.2",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "8.4.2",
|
||||
"text-buffer": "9.1.0",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": "^3.23.0"
|
||||
@@ -66,89 +67,89 @@
|
||||
"atom-light-ui": "0.43.0",
|
||||
"base16-tomorrow-dark-theme": "1.1.0",
|
||||
"base16-tomorrow-light-theme": "1.1.1",
|
||||
"one-dark-ui": "1.3.0",
|
||||
"one-light-ui": "1.3.0",
|
||||
"one-dark-ui": "1.3.1",
|
||||
"one-light-ui": "1.3.1",
|
||||
"one-dark-syntax": "1.2.0",
|
||||
"one-light-syntax": "1.2.0",
|
||||
"solarized-dark-syntax": "1.0.0",
|
||||
"solarized-light-syntax": "1.0.0",
|
||||
"about": "1.4.2",
|
||||
"solarized-dark-syntax": "1.0.2",
|
||||
"solarized-light-syntax": "1.0.2",
|
||||
"about": "1.5.2",
|
||||
"archive-view": "0.61.1",
|
||||
"autocomplete-atom-api": "0.10.0",
|
||||
"autocomplete-css": "0.11.0",
|
||||
"autocomplete-css": "0.11.1",
|
||||
"autocomplete-html": "0.7.2",
|
||||
"autocomplete-plus": "2.29.1",
|
||||
"autocomplete-snippets": "1.10.0",
|
||||
"autocomplete-plus": "2.31.0",
|
||||
"autocomplete-snippets": "1.11.0",
|
||||
"autoflow": "0.27.0",
|
||||
"autosave": "0.23.1",
|
||||
"background-tips": "0.26.0",
|
||||
"bookmarks": "0.38.2",
|
||||
"bracket-matcher": "0.81.0",
|
||||
"bookmarks": "0.41.0",
|
||||
"bracket-matcher": "0.82.1",
|
||||
"command-palette": "0.38.0",
|
||||
"deprecation-cop": "0.54.1",
|
||||
"dev-live-reload": "0.47.0",
|
||||
"encoding-selector": "0.21.0",
|
||||
"exception-reporting": "0.37.0",
|
||||
"find-and-replace": "0.197.4",
|
||||
"fuzzy-finder": "1.0.3",
|
||||
"encoding-selector": "0.22.0",
|
||||
"exception-reporting": "0.38.1",
|
||||
"fuzzy-finder": "1.2.0",
|
||||
"git-diff": "1.0.1",
|
||||
"find-and-replace": "0.198.0",
|
||||
"go-to-line": "0.30.0",
|
||||
"grammar-selector": "0.48.1",
|
||||
"image-view": "0.57.0",
|
||||
"incompatible-packages": "0.26.1",
|
||||
"keybinding-resolver": "0.35.0",
|
||||
"line-ending-selector": "0.4.1",
|
||||
"line-ending-selector": "0.5.0",
|
||||
"link": "0.31.1",
|
||||
"markdown-preview": "0.158.0",
|
||||
"metrics": "0.53.1",
|
||||
"notifications": "0.63.1",
|
||||
"open-on-github": "1.0.1",
|
||||
"notifications": "0.64.1",
|
||||
"open-on-github": "1.1.0",
|
||||
"package-generator": "1.0.0",
|
||||
"settings-view": "0.235.1",
|
||||
"settings-view": "0.237.0",
|
||||
"snippets": "1.0.2",
|
||||
"spell-check": "0.67.0",
|
||||
"status-bar": "1.2.0",
|
||||
"spell-check": "0.67.1",
|
||||
"status-bar": "1.2.6",
|
||||
"styleguide": "0.45.2",
|
||||
"symbols-view": "0.112.0",
|
||||
"tabs": "0.92.0",
|
||||
"symbols-view": "0.113.0",
|
||||
"tabs": "0.95.0",
|
||||
"timecop": "0.33.1",
|
||||
"tree-view": "0.203.2",
|
||||
"tree-view": "0.207.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.34.0",
|
||||
"whitespace": "0.32.2",
|
||||
"wrap-guide": "0.38.1",
|
||||
"language-c": "0.51.1",
|
||||
"language-c": "0.52.0",
|
||||
"language-clojure": "0.20.0",
|
||||
"language-coffee-script": "0.46.1",
|
||||
"language-csharp": "0.12.0",
|
||||
"language-css": "0.36.0",
|
||||
"language-gfm": "0.85.0",
|
||||
"language-git": "0.12.1",
|
||||
"language-coffee-script": "0.47.0",
|
||||
"language-csharp": "0.12.1",
|
||||
"language-css": "0.36.1",
|
||||
"language-gfm": "0.86.0",
|
||||
"language-git": "0.13.0",
|
||||
"language-go": "0.42.0",
|
||||
"language-html": "0.44.1",
|
||||
"language-hyperlink": "0.16.0",
|
||||
"language-java": "0.17.0",
|
||||
"language-java": "0.19.0",
|
||||
"language-javascript": "0.110.0",
|
||||
"language-json": "0.17.6",
|
||||
"language-less": "0.29.0",
|
||||
"language-make": "0.21.0",
|
||||
"language-json": "0.18.0",
|
||||
"language-less": "0.29.3",
|
||||
"language-make": "0.22.0",
|
||||
"language-mustache": "0.13.0",
|
||||
"language-objective-c": "0.15.1",
|
||||
"language-perl": "0.32.0",
|
||||
"language-perl": "0.35.0",
|
||||
"language-php": "0.37.0",
|
||||
"language-property-list": "0.8.0",
|
||||
"language-python": "0.43.0",
|
||||
"language-ruby": "0.68.3",
|
||||
"language-python": "0.44.0",
|
||||
"language-ruby": "0.68.5",
|
||||
"language-ruby-on-rails": "0.25.0",
|
||||
"language-sass": "0.46.0",
|
||||
"language-shellscript": "0.21.1",
|
||||
"language-sass": "0.52.0",
|
||||
"language-shellscript": "0.22.2",
|
||||
"language-source": "0.9.0",
|
||||
"language-sql": "0.20.0",
|
||||
"language-sql": "0.21.0",
|
||||
"language-text": "0.7.1",
|
||||
"language-todo": "0.27.0",
|
||||
"language-toml": "0.18.0",
|
||||
"language-xml": "0.34.4",
|
||||
"language-yaml": "0.25.2"
|
||||
"language-xml": "0.34.6",
|
||||
"language-yaml": "0.26.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
SET EXPECT_OUTPUT=
|
||||
SET WAIT=
|
||||
SET PSARGS=%*
|
||||
|
||||
FOR %%a IN (%*) DO (
|
||||
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
|
||||
@@ -25,7 +26,8 @@ FOR %%a IN (%*) DO (
|
||||
IF "%EXPECT_OUTPUT%"=="YES" (
|
||||
SET ELECTRON_ENABLE_LOGGING=YES
|
||||
IF "%WAIT%"=="YES" (
|
||||
powershell -noexit "%~dp0\..\..\atom.exe" --pid=$pid %* ; wait-event
|
||||
powershell -noexit "Start-Process -FilePath \"%~dp0\..\..\atom.exe\" -ArgumentList \"--pid=$pid $env:PSARGS\" ; wait-event"
|
||||
exit 0
|
||||
) ELSE (
|
||||
"%~dp0\..\..\atom.exe" %*
|
||||
)
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
#!/bin/sh
|
||||
$(dirname "$0")/atom.cmd "$@"
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
ATOMCMD=""$(pwd -W)"/atom.cmd"
|
||||
popd > /dev/null
|
||||
cmd.exe //c "$ATOMCMD" "$@"
|
||||
|
||||
15
script/build
15
script/build
@@ -2,9 +2,24 @@
|
||||
var cp = require('./utils/child-process-wrapper.js');
|
||||
var runGrunt = require('./utils/run-grunt.js');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
process.env['PATH'] = process.env['PATH']
|
||||
.split(';')
|
||||
.filter(function(p) {
|
||||
if (fs.existsSync(path.resolve(p, 'msbuild.exe'))) {
|
||||
console.log('Excluding "' + p + '" from PATH to avoid msbuild.exe mismatch')
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.join(';');
|
||||
}
|
||||
|
||||
cp.safeExec('node script/bootstrap', function() {
|
||||
// build/node_modules/.bin/grunt "$@"
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
50
script/clean
50
script/clean
@@ -1,11 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
var cp = require('./utils/child-process-wrapper.js');
|
||||
var childProcess = require('./utils/child-process-wrapper.js');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
|
||||
var isWindows = process.platform === 'win32';
|
||||
var removeCommand = isWindows ? 'rmdir /S /Q ' : 'rm -rf ';
|
||||
var productName = require('../package.json').productName;
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
@@ -13,10 +12,10 @@ var home = process.env[isWindows ? 'USERPROFILE' : 'HOME'];
|
||||
var tmpdir = os.tmpdir();
|
||||
|
||||
// Windows: Use START as a way to ignore error if Atom.exe isnt running
|
||||
var killatom = isWindows ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true';
|
||||
var killAtomCommand = isWindows ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true';
|
||||
//childProcess.safeExec(killAtomCommand);
|
||||
|
||||
var commands = [
|
||||
killatom,
|
||||
var pathsToRemove = [
|
||||
[__dirname, '..', 'node_modules'],
|
||||
[__dirname, '..', 'build', 'node_modules'],
|
||||
[__dirname, '..', 'apm', 'node_modules'],
|
||||
@@ -32,37 +31,30 @@ var commands = [
|
||||
[home, '.atom', 'electron'],
|
||||
[tmpdir, 'atom-build'],
|
||||
[tmpdir, 'atom-cached-atom-shells'],
|
||||
];
|
||||
var run = function() {
|
||||
var next = commands.shift();
|
||||
if (!next)
|
||||
process.exit(0);
|
||||
].map(function(pathSegments) {
|
||||
return path.resolve.apply(null, pathSegments);
|
||||
});
|
||||
|
||||
if (Array.isArray(next)) {
|
||||
var pathToRemove = path.resolve.apply(path.resolve, next);
|
||||
if (fs.existsSync(pathToRemove)) {
|
||||
if (isWindows) {
|
||||
removeFolderRecursive(pathToRemove);
|
||||
} else {
|
||||
next = removeCommand + pathToRemove;
|
||||
cp.safeExec(next, run);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return run();
|
||||
}
|
||||
pathsToRemove.forEach(function(pathToRemove) {
|
||||
if (fs.existsSync(pathToRemove)) {
|
||||
removePath(pathToRemove);
|
||||
}
|
||||
else
|
||||
cp.safeExec(next, run);
|
||||
};
|
||||
run();
|
||||
});
|
||||
|
||||
function removePath(pathToRemove) {
|
||||
if (isWindows) {
|
||||
removePathOnWindows(pathToRemove);
|
||||
} else {
|
||||
childProcess.safeExec('rm -rf ' + pathToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
// Windows has a 260-char path limit for rmdir etc. Just recursively delete in Node.
|
||||
var removeFolderRecursive = function(folderPath) {
|
||||
function removePathOnWindows(folderPath) {
|
||||
fs.readdirSync(folderPath).forEach(function(entry, index) {
|
||||
var entryPath = path.join(folderPath, entry);
|
||||
if (fs.lstatSync(entryPath).isDirectory()) {
|
||||
removeFolderRecursive(entryPath);
|
||||
removePathOnWindows(entryPath);
|
||||
} else {
|
||||
fs.unlinkSync(entryPath);
|
||||
}
|
||||
|
||||
39
script/mktar
Executable file
39
script/mktar
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# mktar name version arch icon-path build-root-path
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT=`readlink -f "$0"`
|
||||
ROOT=`readlink -f $(dirname $SCRIPT)/..`
|
||||
cd $ROOT
|
||||
|
||||
NAME="$1"
|
||||
VERSION="$2"
|
||||
ARCH="$3"
|
||||
ICON_FILE="$4"
|
||||
BUILD_ROOT_PATH="$5"
|
||||
FILE_MODE=755
|
||||
|
||||
TAR_PATH=$BUILD_ROOT_PATH
|
||||
ATOM_PATH="$BUILD_ROOT_PATH/Atom"
|
||||
|
||||
TARGET_ROOT="`mktemp -d`"
|
||||
chmod $FILE_MODE "$TARGET_ROOT"
|
||||
NAME_IN_TAR="$NAME-$VERSION-$ARCH"
|
||||
TARGET="$TARGET_ROOT/$NAME_IN_TAR"
|
||||
|
||||
# Copy executable and resources
|
||||
cp -a "$ATOM_PATH" "$TARGET"
|
||||
|
||||
# Copy icon file
|
||||
cp "$ICON_FILE" "$TARGET/$NAME.png"
|
||||
|
||||
# Remove executable bit from .node files
|
||||
find "$TARGET" -type f -name "*.node" -exec chmod a-x {} \;
|
||||
|
||||
# Create the archive
|
||||
pushd "$TARGET_ROOT"
|
||||
tar caf "$TAR_PATH/$NAME_IN_TAR.tar.gz" "$NAME_IN_TAR"
|
||||
popd
|
||||
|
||||
rm -rf "$TARGET_ROOT"
|
||||
@@ -17,7 +17,7 @@ exports.safeExec = function(command, options, callback) {
|
||||
var child = childProcess.exec(command, options, function(error, stdout, stderr) {
|
||||
if (error)
|
||||
process.exit(error.code || 1);
|
||||
else
|
||||
else if (callback)
|
||||
callback(null);
|
||||
});
|
||||
child.stderr.pipe(process.stderr);
|
||||
|
||||
@@ -4,6 +4,7 @@ temp = require 'temp'
|
||||
Package = require '../src/package'
|
||||
ThemeManager = require '../src/theme-manager'
|
||||
AtomEnvironment = require '../src/atom-environment'
|
||||
StorageFolder = require '../src/storage-folder'
|
||||
|
||||
describe "AtomEnvironment", ->
|
||||
describe 'window sizing methods', ->
|
||||
@@ -27,8 +28,10 @@ describe "AtomEnvironment", ->
|
||||
atom.setSize(originalSize.width, originalSize.height)
|
||||
|
||||
it 'sets the size of the window, and can retrieve the size just set', ->
|
||||
atom.setSize(100, 400)
|
||||
expect(atom.getSize()).toEqual width: 100, height: 400
|
||||
newWidth = originalSize.width + 12
|
||||
newHeight = originalSize.height + 23
|
||||
atom.setSize(newWidth, newHeight)
|
||||
expect(atom.getSize()).toEqual width: newWidth, height: newHeight
|
||||
|
||||
describe ".isReleasedVersion()", ->
|
||||
it "returns false if the version is a SHA and true otherwise", ->
|
||||
@@ -172,19 +175,48 @@ describe "AtomEnvironment", ->
|
||||
waitsForPromise ->
|
||||
atom.saveState().then ->
|
||||
atom.loadState().then (state) ->
|
||||
expect(state).toBeNull()
|
||||
expect(state).toBeFalsy()
|
||||
|
||||
waitsForPromise ->
|
||||
loadSettings.initialPaths = [dir2, dir1]
|
||||
atom.loadState().then (state) ->
|
||||
expect(state).toEqual({stuff: 'cool'})
|
||||
|
||||
it "saves state on keydown, mousedown, and when the editor window unloads", ->
|
||||
it "loads state from the storage folder when it can't be found in atom.stateStore", ->
|
||||
jasmine.useRealClock()
|
||||
|
||||
storageFolderState = {foo: 1, bar: 2}
|
||||
serializedState = {someState: 42}
|
||||
loadSettings = _.extend(atom.getLoadSettings(), {initialPaths: [temp.mkdirSync("project-directory")]})
|
||||
spyOn(atom, 'getLoadSettings').andReturn(loadSettings)
|
||||
spyOn(atom, 'serialize').andReturn(serializedState)
|
||||
spyOn(atom, 'getStorageFolder').andReturn(new StorageFolder(temp.mkdirSync("config-directory")))
|
||||
atom.project.setPaths(atom.getLoadSettings().initialPaths)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.stateStore.connect()
|
||||
|
||||
runs ->
|
||||
atom.getStorageFolder().storeSync(atom.getStateKey(loadSettings.initialPaths), storageFolderState)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.loadState().then (state) -> expect(state).toEqual(storageFolderState)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.saveState()
|
||||
|
||||
waitsForPromise ->
|
||||
atom.loadState().then (state) -> expect(state).toEqual(serializedState)
|
||||
|
||||
it "saves state when the CPU is idle after a keydown or mousedown event", ->
|
||||
spyOn(atom, 'saveState')
|
||||
idleCallbacks = []
|
||||
spyOn(window, 'requestIdleCallback').andCallFake (callback) -> idleCallbacks.push(callback)
|
||||
|
||||
keydown = new KeyboardEvent('keydown')
|
||||
atom.document.dispatchEvent(keydown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
@@ -192,17 +224,33 @@ describe "AtomEnvironment", ->
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atom.saveState.reset()
|
||||
it "saves state immediately when unloading the editor window, ignoring pending and successive mousedown/keydown events", ->
|
||||
spyOn(atom, 'saveState')
|
||||
idleCallbacks = []
|
||||
spyOn(window, 'requestIdleCallback').andCallFake (callback) -> idleCallbacks.push(callback)
|
||||
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
atom.unloadEditorWindow()
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: true})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: false})
|
||||
|
||||
atom.saveState.reset()
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atom.saveState).not.toHaveBeenCalled()
|
||||
|
||||
atom.saveState.reset()
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
idleCallbacks.shift()()
|
||||
expect(atom.saveState).not.toHaveBeenCalled()
|
||||
|
||||
it "serializes the project state with all the options supplied in saveState", ->
|
||||
spyOn(atom.project, 'serialize').andReturn({foo: 42})
|
||||
|
||||
@@ -338,7 +386,7 @@ describe "AtomEnvironment", ->
|
||||
updateAvailableHandler = jasmine.createSpy("update-available-handler")
|
||||
subscription = atom.onUpdateAvailable updateAvailableHandler
|
||||
|
||||
autoUpdater = require('remote').require('auto-updater')
|
||||
autoUpdater = require('electron').remote.require('auto-updater')
|
||||
autoUpdater.emit 'update-downloaded', null, "notes", "version"
|
||||
|
||||
waitsFor ->
|
||||
|
||||
@@ -64,6 +64,16 @@ describe('AutoUpdateManager (renderer)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onUpdateError', () => {
|
||||
it('subscribes to "update-error" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onUpdateError(spy)
|
||||
electronAutoUpdater.emit('error', {}, 'an error message')
|
||||
waitsFor(() => spy.callCount === 1)
|
||||
runs(() => expect(autoUpdateManager.getErrorMessage()).toBe('an error message'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('::platformSupportsUpdates', () => {
|
||||
let state, releaseChannel
|
||||
it('returns true on OS X and Windows when in stable', () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
ChildProcess = require 'child_process'
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
BufferedProcess = require '../src/buffered-process'
|
||||
|
||||
describe "BufferedProcess", ->
|
||||
@@ -15,20 +16,20 @@ describe "BufferedProcess", ->
|
||||
describe "when there is an error handler specified", ->
|
||||
describe "when an error event is emitted by the process", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
process = new BufferedProcess
|
||||
command: 'bad-command-nope'
|
||||
bufferedProcess = new BufferedProcess
|
||||
command: 'bad-command-nope1'
|
||||
args: ['nothing']
|
||||
options: {}
|
||||
options: {shell: false}
|
||||
|
||||
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
|
||||
process.onWillThrowError(errorSpy)
|
||||
bufferedProcess.onWillThrowError(errorSpy)
|
||||
|
||||
waitsFor -> errorSpy.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(window.onerror).not.toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope ENOENT'
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope1 ENOENT'
|
||||
|
||||
describe "when an error is thrown spawning the process", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
@@ -37,13 +38,13 @@ describe "BufferedProcess", ->
|
||||
error.code = 'EAGAIN'
|
||||
throw error
|
||||
|
||||
process = new BufferedProcess
|
||||
bufferedProcess = new BufferedProcess
|
||||
command: 'ls'
|
||||
args: []
|
||||
options: {}
|
||||
|
||||
errorSpy = jasmine.createSpy().andCallFake (error) -> error.handle()
|
||||
process.onWillThrowError(errorSpy)
|
||||
bufferedProcess.onWillThrowError(errorSpy)
|
||||
|
||||
waitsFor -> errorSpy.callCount > 0
|
||||
|
||||
@@ -53,55 +54,24 @@ describe "BufferedProcess", ->
|
||||
expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'Something is really wrong'
|
||||
|
||||
describe "when there is not an error handler specified", ->
|
||||
it "calls the error handler and does not throw an exception", ->
|
||||
process = new BufferedProcess
|
||||
command: 'bad-command-nope'
|
||||
it "does throw an exception", ->
|
||||
new BufferedProcess
|
||||
command: 'bad-command-nope2'
|
||||
args: ['nothing']
|
||||
options: {}
|
||||
options: {shell: false}
|
||||
|
||||
waitsFor -> window.onerror.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(window.onerror).toHaveBeenCalled()
|
||||
expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope`'
|
||||
expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope2`'
|
||||
expect(window.onerror.mostRecentCall.args[4].name).toBe 'BufferedProcessError'
|
||||
|
||||
describe "on Windows", ->
|
||||
originalPlatform = null
|
||||
|
||||
beforeEach ->
|
||||
# Prevent any commands from actually running and affecting the host
|
||||
originalSpawn = ChildProcess.spawn
|
||||
spyOn(ChildProcess, 'spawn').andCallFake ->
|
||||
# Just spawn something that won't actually modify the host
|
||||
if originalPlatform is 'win32'
|
||||
originalSpawn('dir')
|
||||
else
|
||||
originalSpawn('ls')
|
||||
|
||||
originalPlatform = process.platform
|
||||
Object.defineProperty process, 'platform', value: 'win32'
|
||||
|
||||
afterEach ->
|
||||
Object.defineProperty process, 'platform', value: originalPlatform
|
||||
|
||||
describe "when the explorer command is spawned on Windows", ->
|
||||
it "doesn't quote arguments of the form /root,C...", ->
|
||||
new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"explorer.exe /root,C:\\foo"'
|
||||
|
||||
it "spawns the command using a cmd.exe wrapper", ->
|
||||
new BufferedProcess({command: 'dir'})
|
||||
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/c'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"dir"'
|
||||
|
||||
it "calls the specified stdout, stderr, and exit callbacks", ->
|
||||
stdout = ''
|
||||
stderr = ''
|
||||
exitCallback = jasmine.createSpy('exit callback')
|
||||
process = new BufferedProcess
|
||||
new BufferedProcess
|
||||
command: atom.packages.getApmPath()
|
||||
args: ['-h']
|
||||
options: {}
|
||||
@@ -115,29 +85,51 @@ describe "BufferedProcess", ->
|
||||
expect(stderr).toContain 'apm - Atom Package Manager'
|
||||
expect(stdout).toEqual ''
|
||||
|
||||
it "calls the specified stdout callback only with whole lines", ->
|
||||
it "calls the specified stdout callback with whole lines", ->
|
||||
exitCallback = jasmine.createSpy('exit callback')
|
||||
baseContent = "There are dozens of us! Dozens! It's as Ann as the nose on Plain's face. Can you believe that the only reason the club is going under is because it's in a terrifying neighborhood? She calls it a Mayonegg. Waiting for the Emmys. BTW did you know won 6 Emmys and was still canceled early by Fox? COME ON. I'll buy you a hundred George Michaels that you can teach to drive! Never once touched my per diem. I'd go to Craft Service, get some raw veggies, bacon, Cup-A-Soup…baby, I got a stew goin'"
|
||||
content = (baseContent for _ in [1..200]).join('\n')
|
||||
loremPath = require.resolve("./fixtures/lorem.txt")
|
||||
content = fs.readFileSync(loremPath).toString()
|
||||
baseContent = content.split('\n')
|
||||
stdout = ''
|
||||
endLength = 10
|
||||
outputAlwaysEndsWithStew = true
|
||||
process = new BufferedProcess
|
||||
command: '/bin/echo'
|
||||
args: [content]
|
||||
allLinesEndWithNewline = true
|
||||
new BufferedProcess
|
||||
command: if process.platform is 'win32' then 'type' else 'cat'
|
||||
args: [loremPath]
|
||||
options: {}
|
||||
stdout: (lines) ->
|
||||
endsWithNewline = (lines.charAt lines.length - 1) is '\n'
|
||||
if not endsWithNewline then allLinesEndWithNewline = false
|
||||
stdout += lines
|
||||
|
||||
end = baseContent.substr(baseContent.length - endLength, endLength)
|
||||
lineEndsWithStew = lines.substr(lines.length - endLength, endLength) is end
|
||||
expect(lineEndsWithStew).toBeTrue
|
||||
|
||||
outputAlwaysEndsWithStew = outputAlwaysEndsWithStew and lineEndsWithStew
|
||||
exit: exitCallback
|
||||
|
||||
waitsFor -> exitCallback.callCount is 1
|
||||
|
||||
runs ->
|
||||
expect(outputAlwaysEndsWithStew).toBeTrue
|
||||
expect(stdout).toBe content += '\n'
|
||||
expect(allLinesEndWithNewline).toBeTrue
|
||||
expect(stdout).toBe content
|
||||
|
||||
describe "on Windows", ->
|
||||
originalPlatform = null
|
||||
|
||||
beforeEach ->
|
||||
# Prevent any commands from actually running and affecting the host
|
||||
originalSpawn = ChildProcess.spawn
|
||||
spyOn(ChildProcess, 'spawn')
|
||||
originalPlatform = process.platform
|
||||
Object.defineProperty process, 'platform', value: 'win32'
|
||||
|
||||
afterEach ->
|
||||
Object.defineProperty process, 'platform', value: originalPlatform
|
||||
|
||||
describe "when the explorer command is spawned on Windows", ->
|
||||
it "doesn't quote arguments of the form /root,C...", ->
|
||||
new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"explorer.exe /root,C:\\foo"'
|
||||
|
||||
it "spawns the command using a cmd.exe wrapper when options.shell is undefined", ->
|
||||
new BufferedProcess({command: 'dir'})
|
||||
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/d'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '/c'
|
||||
expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"dir"'
|
||||
|
||||
@@ -74,6 +74,13 @@ describe "CommandRegistry", ->
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['.foo.bar', '.bar', '.foo']
|
||||
|
||||
it "orders inline listeners by reverse registration order", ->
|
||||
calls = []
|
||||
registry.add child, 'command', -> calls.push('child1')
|
||||
registry.add child, 'command', -> calls.push('child2')
|
||||
child.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['child2', 'child1']
|
||||
|
||||
it "stops bubbling through ancestors when .stopPropagation() is called on the event", ->
|
||||
calls = []
|
||||
|
||||
|
||||
85
spec/decoration-manager-spec.coffee
Normal file
85
spec/decoration-manager-spec.coffee
Normal file
@@ -0,0 +1,85 @@
|
||||
DecorationManager = require '../src/decoration-manager'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
describe "DecorationManager", ->
|
||||
[decorationManager, buffer, defaultMarkerLayer] = []
|
||||
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
displayLayer = buffer.addDisplayLayer()
|
||||
defaultMarkerLayer = displayLayer.addMarkerLayer()
|
||||
decorationManager = new DecorationManager(displayLayer, defaultMarkerLayer)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
afterEach ->
|
||||
decorationManager.destroy()
|
||||
buffer.release()
|
||||
|
||||
describe "decorations", ->
|
||||
[marker, decoration, decorationProperties] = []
|
||||
beforeEach ->
|
||||
marker = defaultMarkerLayer.markBufferRange([[2, 13], [3, 15]])
|
||||
decorationProperties = {type: 'line-number', class: 'one'}
|
||||
decoration = decorationManager.decorateMarker(marker, decorationProperties)
|
||||
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
expect(decoration).toBeDefined()
|
||||
expect(decoration.getProperties()).toBe decorationProperties
|
||||
expect(decorationManager.decorationForId(decoration.id)).toBe decoration
|
||||
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
|
||||
|
||||
decoration.destroy()
|
||||
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
|
||||
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "will not fail if the decoration is removed twice", ->
|
||||
decoration.destroy()
|
||||
decoration.destroy()
|
||||
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "does not allow destroyed markers to be decorated", ->
|
||||
marker.destroy()
|
||||
expect(->
|
||||
decorationManager.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
|
||||
).toThrow("Cannot decorate a destroyed marker")
|
||||
expect(decorationManager.getOverlayDecorations()).toEqual []
|
||||
|
||||
describe "when a decoration is updated via Decoration::update()", ->
|
||||
it "emits an 'updated' event containing the new and old params", ->
|
||||
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
|
||||
decoration.setProperties type: 'line-number', class: 'two'
|
||||
|
||||
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
|
||||
expect(oldProperties).toEqual decorationProperties
|
||||
expect(newProperties).toEqual {type: 'line-number', gutterName: 'line-number', class: 'two'}
|
||||
|
||||
describe "::getDecorations(properties)", ->
|
||||
it "returns decorations matching the given optional properties", ->
|
||||
expect(decorationManager.getDecorations()).toEqual [decoration]
|
||||
expect(decorationManager.getDecorations(class: 'two').length).toEqual 0
|
||||
expect(decorationManager.getDecorations(class: 'one').length).toEqual 1
|
||||
|
||||
describe "::decorateMarker", ->
|
||||
describe "when decorating gutters", ->
|
||||
[marker] = []
|
||||
|
||||
beforeEach ->
|
||||
marker = defaultMarkerLayer.markBufferRange([[1, 0], [1, 0]])
|
||||
|
||||
it "creates a decoration that is both of 'line-number' and 'gutter' type when called with the 'line-number' type", ->
|
||||
decorationProperties = {type: 'line-number', class: 'one'}
|
||||
decoration = decorationManager.decorateMarker(marker, decorationProperties)
|
||||
expect(decoration.isType('line-number')).toBe true
|
||||
expect(decoration.isType('gutter')).toBe true
|
||||
expect(decoration.getProperties().gutterName).toBe 'line-number'
|
||||
expect(decoration.getProperties().class).toBe 'one'
|
||||
|
||||
it "creates a decoration that is only of 'gutter' type if called with the 'gutter' type and a 'gutterName'", ->
|
||||
decorationProperties = {type: 'gutter', gutterName: 'test-gutter', class: 'one'}
|
||||
decoration = decorationManager.decorateMarker(marker, decorationProperties)
|
||||
expect(decoration.isType('gutter')).toBe true
|
||||
expect(decoration.isType('line-number')).toBe false
|
||||
expect(decoration.getProperties().gutterName).toBe 'test-gutter'
|
||||
expect(decoration.getProperties().class).toBe 'one'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
{Point} = require 'text-buffer'
|
||||
{isPairedCharacter} = require '../src/text-utils'
|
||||
|
||||
module.exports =
|
||||
class FakeLinesYardstick
|
||||
constructor: (@model, @lineTopIndex) ->
|
||||
{@displayLayer} = @model
|
||||
@characterWidthsByScope = {}
|
||||
|
||||
getScopedCharacterWidth: (scopeNames, char) ->
|
||||
@@ -24,31 +26,38 @@ class FakeLinesYardstick
|
||||
|
||||
targetRow = screenPosition.row
|
||||
targetColumn = screenPosition.column
|
||||
baseCharacterWidth = @model.getDefaultCharWidth()
|
||||
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
|
||||
left = 0
|
||||
column = 0
|
||||
|
||||
iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator()
|
||||
while iterator.next()
|
||||
characterWidths = @getScopedCharacterWidths(iterator.getScopes())
|
||||
scopes = []
|
||||
startIndex = 0
|
||||
{tagCodes, lineText} = @model.screenLineForScreenRow(targetRow)
|
||||
for tagCode in tagCodes
|
||||
if @displayLayer.isOpenTagCode(tagCode)
|
||||
scopes.push(@displayLayer.tagForCode(tagCode))
|
||||
else if @displayLayer.isCloseTagCode(tagCode)
|
||||
scopes.splice(scopes.lastIndexOf(@displayLayer.tagForCode(tagCode)), 1)
|
||||
else
|
||||
text = lineText.substr(startIndex, tagCode)
|
||||
startIndex += tagCode
|
||||
characterWidths = @getScopedCharacterWidths(scopes)
|
||||
|
||||
valueIndex = 0
|
||||
text = iterator.getText()
|
||||
while valueIndex < text.length
|
||||
if iterator.isPairedCharacter()
|
||||
char = text
|
||||
charLength = 2
|
||||
valueIndex += 2
|
||||
else
|
||||
char = text[valueIndex]
|
||||
charLength = 1
|
||||
valueIndex++
|
||||
valueIndex = 0
|
||||
while valueIndex < text.length
|
||||
if isPairedCharacter(text, valueIndex)
|
||||
char = text[valueIndex...valueIndex + 2]
|
||||
charLength = 2
|
||||
valueIndex += 2
|
||||
else
|
||||
char = text[valueIndex]
|
||||
charLength = 1
|
||||
valueIndex++
|
||||
|
||||
break if column is targetColumn
|
||||
break if column is targetColumn
|
||||
|
||||
left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
|
||||
column += charLength
|
||||
left += characterWidths[char] ? @model.getDefaultCharWidth() unless char is '\0'
|
||||
column += charLength
|
||||
|
||||
{top, left}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[branch "master"]
|
||||
remote = origin
|
||||
merge = refs/heads/master
|
||||
[remote "origin"]
|
||||
url = git@github.com:atom/some-repo-i-guess.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[submodule "jstips"]
|
||||
url = https://github.com/loverajoel/jstips
|
||||
[submodule "You-Dont-Need-jQuery"]
|
||||
|
||||
1
spec/fixtures/git/repo-with-submodules/git.git/refs/remotes/origin/master
vendored
Normal file
1
spec/fixtures/git/repo-with-submodules/git.git/refs/remotes/origin/master
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5
|
||||
3
spec/fixtures/lorem.txt
vendored
Normal file
3
spec/fixtures/lorem.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ultricies nulla id nibh aliquam, vitae euismod ipsum scelerisque. Vestibulum vulputate facilisis nisi, eu rhoncus turpis pretium ut. Curabitur facilisis urna in diam efficitur, vel maximus tellus consectetur. Suspendisse pulvinar felis sed metus tristique, a posuere dui suscipit. Ut vehicula, tellus ac blandit consequat, libero dui hendrerit elit, non pretium metus odio sed dolor. Vivamus quis volutpat ipsum. In convallis magna nec nunc tristique malesuada. Sed sed hendrerit lacus. Etiam arcu dui, consequat vel neque vitae, iaculis egestas justo. Donec lacinia odio nulla, condimentum porta erat accumsan at. Nunc vulputate nulla vel nunc fermentum egestas.
|
||||
Duis ultricies libero elit, nec facilisis mi rhoncus ornare. Aliquam aliquet libero vitae arcu porttitor mattis. Vestibulum ultricies consectetur arcu, non gravida magna eleifend vel. Phasellus varius mattis ultricies. Vestibulum placerat lacus non consectetur fringilla. Duis congue, arcu iaculis vehicula hendrerit, purus odio faucibus ipsum, et fermentum massa tellus euismod nulla. Vivamus pellentesque blandit massa, sit amet hendrerit turpis congue eu. Suspendisse diam dui, vestibulum nec semper varius, maximus eu nunc. Vivamus facilisis pulvinar viverra. Praesent luctus lectus id est porttitor volutpat. Suspendisse est augue, mattis a tincidunt id, condimentum in turpis. Curabitur at erat commodo orci interdum tincidunt. Sed sodales elit odio, a placerat ipsum luctus nec. Sed maximus, justo ut pharetra pellentesque, orci mi faucibus enim, quis viverra arcu dui sed nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent quis velit libero.
|
||||
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus a rutrum tortor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce bibendum odio et neque vestibulum rutrum. Vestibulum commodo, nibh non sodales lobortis, dui ex consectetur leo, a finibus libero lectus ac diam. Etiam dui nunc, bibendum a tempor vel, vestibulum lacinia neque. Mauris consectetur odio sit amet maximus pretium. Sed rutrum nunc at ante ullamcorper fermentum. Proin at quam a mauris pellentesque viverra. Nunc pretium pulvinar ipsum. Vestibulum eu nibh ut ex gravida tempus. Praesent ut elit ut ligula tristique dapibus ut sit amet leo. Proin non molestie erat.
|
||||
2
spec/fixtures/shebang
vendored
2
spec/fixtures/shebang
vendored
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
puts "America – fuck yeah!"
|
||||
puts "Atom fixture test"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import fs from 'fs-plus'
|
||||
import path from 'path'
|
||||
import temp from 'temp'
|
||||
import Git from 'nodegit'
|
||||
|
||||
import {it, beforeEach, afterEach} from './async-spec-helpers'
|
||||
|
||||
@@ -47,7 +46,7 @@ describe('GitRepositoryAsync', () => {
|
||||
|
||||
let threw = false
|
||||
try {
|
||||
await repo.repoPromise
|
||||
await repo.getRepo()
|
||||
} catch (e) {
|
||||
threw = true
|
||||
}
|
||||
@@ -56,6 +55,14 @@ describe('GitRepositoryAsync', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('openedPath', () => {
|
||||
it('is the path passed to .open', () => {
|
||||
const workingDirPath = copyRepository()
|
||||
repo = GitRepositoryAsync.open(workingDirPath)
|
||||
expect(repo.openedPath).toBe(workingDirPath)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getRepo()', () => {
|
||||
beforeEach(() => {
|
||||
const workingDirectory = copySubmoduleRepository()
|
||||
@@ -64,19 +71,19 @@ describe('GitRepositoryAsync', () => {
|
||||
})
|
||||
|
||||
it('returns the repository when not given a path', async () => {
|
||||
const nodeGitRepo1 = await repo.repoPromise
|
||||
const nodeGitRepo1 = await repo.getRepo()
|
||||
const nodeGitRepo2 = await repo.getRepo()
|
||||
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
|
||||
})
|
||||
|
||||
it('returns the repository when given a non-submodule path', async () => {
|
||||
const nodeGitRepo1 = await repo.repoPromise
|
||||
const nodeGitRepo1 = await repo.getRepo()
|
||||
const nodeGitRepo2 = await repo.getRepo('README')
|
||||
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
|
||||
})
|
||||
|
||||
it('returns the submodule repository when given a submodule path', async () => {
|
||||
const nodeGitRepo1 = await repo.repoPromise
|
||||
const nodeGitRepo1 = await repo.getRepo()
|
||||
const nodeGitRepo2 = await repo.getRepo('jstips')
|
||||
expect(nodeGitRepo1.workdir()).not.toBe(nodeGitRepo2.workdir())
|
||||
|
||||
@@ -103,7 +110,7 @@ describe('GitRepositoryAsync', () => {
|
||||
it('returns the repository path for a repository path', async () => {
|
||||
repo = openFixture('master.git')
|
||||
const repoPath = await repo.getPath()
|
||||
expect(repoPath).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
expect(repoPath).toEqualPath(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -230,9 +237,7 @@ describe('GitRepositoryAsync', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// @joshaber: Disabling for now. There seems to be some race with path
|
||||
// subscriptions leading to intermittent test failures, e.g.: https://travis-ci.org/atom/atom/jobs/102702554
|
||||
xdescribe('.checkoutHeadForEditor(editor)', () => {
|
||||
describe('.checkoutHeadForEditor(editor)', () => {
|
||||
let filePath
|
||||
let editor
|
||||
|
||||
@@ -305,7 +310,7 @@ describe('GitRepositoryAsync', () => {
|
||||
await repo.getPathStatus(filePath)
|
||||
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
const status = Git.Status.STATUS.WT_MODIFIED
|
||||
const status = GitRepositoryAsync.Git.Status.STATUS.WT_MODIFIED
|
||||
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
|
||||
fs.writeFileSync(filePath, 'abc')
|
||||
|
||||
@@ -880,4 +885,34 @@ describe('GitRepositoryAsync', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getOriginURL()', () => {
|
||||
beforeEach(() => {
|
||||
const workingDirectory = copyRepository('repo-with-submodules')
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
})
|
||||
|
||||
it('returns the origin URL', async () => {
|
||||
const url = await repo.getOriginURL()
|
||||
expect(url).toBe('git@github.com:atom/some-repo-i-guess.git')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getUpstreamBranch()', () => {
|
||||
it('returns null when there is no upstream branch', async () => {
|
||||
const workingDirectory = copyRepository()
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
|
||||
const upstream = await repo.getUpstreamBranch()
|
||||
expect(upstream).toBe(null)
|
||||
})
|
||||
|
||||
it('returns the upstream branch', async () => {
|
||||
const workingDirectory = copyRepository('repo-with-submodules')
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
|
||||
const upstream = await repo.getUpstreamBranch()
|
||||
expect(upstream).toBe('refs/remotes/origin/master')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ describe "GitRepository", ->
|
||||
waitsForPromise ->
|
||||
repo.async.getPath().then(onSuccess)
|
||||
runs ->
|
||||
expect(onSuccess.mostRecentCall.args[0]).toBe(repoPath)
|
||||
expect(onSuccess.mostRecentCall.args[0]).toEqualPath(repoPath)
|
||||
|
||||
describe "new GitRepository(path)", ->
|
||||
it "throws an exception when no repository is found", ->
|
||||
@@ -289,6 +289,16 @@ describe "GitRepository", ->
|
||||
expect(repo.isStatusModified(status)).toBe true
|
||||
expect(repo.isStatusNew(status)).toBe false
|
||||
|
||||
it 'caches statuses that were looked up synchronously', ->
|
||||
originalContent = 'undefined'
|
||||
fs.writeFileSync(modifiedPath, 'making this path modified')
|
||||
repo.getPathStatus('file.txt')
|
||||
|
||||
fs.writeFileSync(modifiedPath, originalContent)
|
||||
waitsForPromise -> repo.refreshStatus()
|
||||
runs ->
|
||||
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeFalsy()
|
||||
|
||||
describe "buffer events", ->
|
||||
[editor] = []
|
||||
|
||||
|
||||
@@ -268,6 +268,36 @@ describe "Starting Atom", ->
|
||||
[otherTempDirPath]
|
||||
].sort()
|
||||
|
||||
it "doesn't reopen any previously opened windows if restorePreviousWindowsOnStart is disabled", ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace")
|
||||
|
||||
configPath = path.join(atomHome, 'config.cson')
|
||||
config = CSON.readFileSync(configPath)
|
||||
config['*'].core = {restorePreviousWindowsOnStart: false}
|
||||
CSON.writeFileSync(configPath, config)
|
||||
|
||||
runAtom [], {ATOM_HOME: atomHome}, (client) ->
|
||||
windowProjectPaths = []
|
||||
|
||||
client
|
||||
.waitForWindowCount(1, 10000)
|
||||
.then ({value: windowHandles}) ->
|
||||
@window(windowHandles[0])
|
||||
.waitForExist("atom-workspace")
|
||||
.treeViewRootDirectories()
|
||||
.then ({value: directories}) -> windowProjectPaths.push(directories)
|
||||
|
||||
.call ->
|
||||
expect(windowProjectPaths).toEqual [
|
||||
[]
|
||||
]
|
||||
|
||||
describe "opening a remote directory", ->
|
||||
it "opens the parent directory and creates an empty text editor", ->
|
||||
remoteDirectory = 'remote://server:3437/some/directory/path'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
Grim = require 'grim'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
@@ -96,13 +97,10 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
|
||||
log(str)
|
||||
onComplete: (runner) ->
|
||||
fs.closeSync(logStream) if logStream?
|
||||
if process.env.JANKY_SHA1 or process.env.CI
|
||||
grim = require 'grim'
|
||||
|
||||
if grim.getDeprecationsLength() > 0
|
||||
grim.logDeprecations()
|
||||
resolveWithExitCode(1)
|
||||
return
|
||||
if Grim.getDeprecationsLength() > 0
|
||||
Grim.logDeprecations()
|
||||
resolveWithExitCode(1)
|
||||
return
|
||||
|
||||
if runner.results().failedCount > 0
|
||||
resolveWithExitCode(1)
|
||||
|
||||
@@ -334,66 +334,56 @@ describe "LanguageMode", ->
|
||||
it "folds every foldable line", ->
|
||||
languageMode.foldAll()
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(1).fold
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
|
||||
fold2.destroy()
|
||||
|
||||
fold3 = editor.tokenizedLineForScreenRow(4).fold
|
||||
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]
|
||||
[fold1, fold2, fold3] = languageMode.unfoldAll()
|
||||
expect([fold1.start.row, fold1.end.row]).toEqual [0, 12]
|
||||
expect([fold2.start.row, fold2.end.row]).toEqual [1, 9]
|
||||
expect([fold3.start.row, fold3.end.row]).toEqual [4, 7]
|
||||
|
||||
describe ".foldBufferRow(bufferRow)", ->
|
||||
describe "when bufferRow can be folded", ->
|
||||
it "creates a fold based on the syntactic region starting at the given row", ->
|
||||
languageMode.foldBufferRow(1)
|
||||
fold = editor.tokenizedLineForScreenRow(1).fold
|
||||
expect(fold.getStartRow()).toBe 1
|
||||
expect(fold.getEndRow()).toBe 9
|
||||
[fold] = languageMode.unfoldAll()
|
||||
expect([fold.start.row, fold.end.row]).toEqual [1, 9]
|
||||
|
||||
describe "when bufferRow can't be folded", ->
|
||||
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
|
||||
languageMode.foldBufferRow(8)
|
||||
fold = editor.tokenizedLineForScreenRow(1).fold
|
||||
expect(fold.getStartRow()).toBe 1
|
||||
expect(fold.getEndRow()).toBe 9
|
||||
[fold] = languageMode.unfoldAll()
|
||||
expect([fold.start.row, fold.end.row]).toEqual [1, 9]
|
||||
|
||||
describe "when the bufferRow is already folded", ->
|
||||
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
|
||||
languageMode.foldBufferRow(2)
|
||||
expect(editor.tokenizedLineForScreenRow(1).fold).toBeDefined()
|
||||
expect(editor.tokenizedLineForScreenRow(0).fold).not.toBeDefined()
|
||||
expect(editor.isFoldedAtBufferRow(0)).toBe(false)
|
||||
expect(editor.isFoldedAtBufferRow(1)).toBe(true)
|
||||
|
||||
languageMode.foldBufferRow(1)
|
||||
expect(editor.tokenizedLineForScreenRow(0).fold).toBeDefined()
|
||||
expect(editor.isFoldedAtBufferRow(0)).toBe(true)
|
||||
|
||||
describe "when the bufferRow is in a multi-line comment", ->
|
||||
it "searches upward and downward for surrounding comment lines and folds them as a single fold", ->
|
||||
buffer.insert([1, 0], " //this is a comment\n // and\n //more docs\n\n//second comment")
|
||||
languageMode.foldBufferRow(1)
|
||||
fold = editor.tokenizedLineForScreenRow(1).fold
|
||||
expect(fold.getStartRow()).toBe 1
|
||||
expect(fold.getEndRow()).toBe 3
|
||||
[fold] = languageMode.unfoldAll()
|
||||
expect([fold.start.row, fold.end.row]).toEqual [1, 3]
|
||||
|
||||
describe "when the bufferRow is a single-line comment", ->
|
||||
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
|
||||
buffer.insert([1, 0], " //this is a single line comment\n")
|
||||
languageMode.foldBufferRow(1)
|
||||
fold = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect(fold.getStartRow()).toBe 0
|
||||
expect(fold.getEndRow()).toBe 13
|
||||
[fold] = languageMode.unfoldAll()
|
||||
expect([fold.start.row, fold.end.row]).toEqual [0, 13]
|
||||
|
||||
describe ".foldAllAtIndentLevel(indentLevel)", ->
|
||||
it "folds blocks of text at the given indentation level", ->
|
||||
languageMode.foldAllAtIndentLevel(0)
|
||||
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {"
|
||||
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {" + editor.displayLayer.foldCharacter
|
||||
expect(editor.getLastScreenRow()).toBe 0
|
||||
|
||||
languageMode.foldAllAtIndentLevel(1)
|
||||
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {"
|
||||
expect(editor.lineTextForScreenRow(1)).toBe " var sort = function(items) {"
|
||||
expect(editor.lineTextForScreenRow(1)).toBe " var sort = function(items) {" + editor.displayLayer.foldCharacter
|
||||
expect(editor.getLastScreenRow()).toBe 4
|
||||
|
||||
languageMode.foldAllAtIndentLevel(2)
|
||||
@@ -429,59 +419,35 @@ describe "LanguageMode", ->
|
||||
it "folds every foldable line", ->
|
||||
languageMode.foldAll()
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(1).fold
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 4]
|
||||
|
||||
fold3 = editor.tokenizedLineForScreenRow(2).fold.destroy()
|
||||
|
||||
fold4 = editor.tokenizedLineForScreenRow(3).fold
|
||||
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
|
||||
|
||||
fold5 = editor.tokenizedLineForScreenRow(6).fold
|
||||
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [11, 16]
|
||||
fold5.destroy()
|
||||
|
||||
fold6 = editor.tokenizedLineForScreenRow(13).fold
|
||||
expect([fold6.getStartRow(), fold6.getEndRow()]).toEqual [21, 22]
|
||||
fold6.destroy()
|
||||
folds = languageMode.unfoldAll()
|
||||
expect(folds.length).toBe 8
|
||||
expect([folds[0].start.row, folds[0].end.row]).toEqual [0, 30]
|
||||
expect([folds[1].start.row, folds[1].end.row]).toEqual [1, 4]
|
||||
expect([folds[2].start.row, folds[2].end.row]).toEqual [5, 27]
|
||||
expect([folds[3].start.row, folds[3].end.row]).toEqual [6, 8]
|
||||
expect([folds[4].start.row, folds[4].end.row]).toEqual [11, 16]
|
||||
expect([folds[5].start.row, folds[5].end.row]).toEqual [17, 20]
|
||||
expect([folds[6].start.row, folds[6].end.row]).toEqual [21, 22]
|
||||
expect([folds[7].start.row, folds[7].end.row]).toEqual [24, 25]
|
||||
|
||||
describe ".foldAllAtIndentLevel()", ->
|
||||
it "folds every foldable range at a given indentLevel", ->
|
||||
languageMode.foldAllAtIndentLevel(2)
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(6).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [6, 8]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(11).fold
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 16]
|
||||
fold2.destroy()
|
||||
|
||||
fold3 = editor.tokenizedLineForScreenRow(17).fold
|
||||
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [17, 20]
|
||||
fold3.destroy()
|
||||
|
||||
fold4 = editor.tokenizedLineForScreenRow(21).fold
|
||||
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [21, 22]
|
||||
fold4.destroy()
|
||||
|
||||
fold5 = editor.tokenizedLineForScreenRow(24).fold
|
||||
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [24, 25]
|
||||
fold5.destroy()
|
||||
folds = languageMode.unfoldAll()
|
||||
expect(folds.length).toBe 5
|
||||
expect([folds[0].start.row, folds[0].end.row]).toEqual [6, 8]
|
||||
expect([folds[1].start.row, folds[1].end.row]).toEqual [11, 16]
|
||||
expect([folds[2].start.row, folds[2].end.row]).toEqual [17, 20]
|
||||
expect([folds[3].start.row, folds[3].end.row]).toEqual [21, 22]
|
||||
expect([folds[4].start.row, folds[4].end.row]).toEqual [24, 25]
|
||||
|
||||
it "does not fold anything but the indentLevel", ->
|
||||
languageMode.foldAllAtIndentLevel(0)
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(5).fold
|
||||
expect(fold2).toBeFalsy()
|
||||
folds = languageMode.unfoldAll()
|
||||
expect(folds.length).toBe 1
|
||||
expect([folds[0].start.row, folds[0].end.row]).toEqual [0, 30]
|
||||
|
||||
describe ".isFoldableAtBufferRow(bufferRow)", ->
|
||||
it "returns true if the line starts a multi-line comment", ->
|
||||
|
||||
@@ -19,36 +19,45 @@ describe "LinesYardstick", ->
|
||||
screenRowsToMeasure = []
|
||||
|
||||
buildLineNode = (screenRow) ->
|
||||
tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
|
||||
iterator = tokenizedLine.getTokenIterator()
|
||||
startIndex = 0
|
||||
scopes = []
|
||||
screenLine = editor.screenLineForScreenRow(screenRow)
|
||||
lineNode = document.createElement("div")
|
||||
lineNode.style.whiteSpace = "pre"
|
||||
while iterator.next()
|
||||
span = document.createElement("span")
|
||||
span.className = iterator.getScopes().join(' ').replace(/\.+/g, ' ')
|
||||
span.textContent = iterator.getText()
|
||||
lineNode.appendChild(span)
|
||||
for tagCode in screenLine.tagCodes when tagCode isnt 0
|
||||
if editor.displayLayer.isCloseTagCode(tagCode)
|
||||
scopes.pop()
|
||||
else if editor.displayLayer.isOpenTagCode(tagCode)
|
||||
scopes.push(editor.displayLayer.tagForCode(tagCode))
|
||||
else
|
||||
text = screenLine.lineText.substr(startIndex, tagCode)
|
||||
startIndex += tagCode
|
||||
|
||||
span = document.createElement("span")
|
||||
span.className = scopes.join(' ').replace(/\.+/g, ' ')
|
||||
span.textContent = text
|
||||
lineNode.appendChild(span)
|
||||
jasmine.attachToDOM(lineNode)
|
||||
createdLineNodes.push(lineNode)
|
||||
lineNode
|
||||
|
||||
mockLineNodesProvider =
|
||||
lineNodeForLineIdAndScreenRow: (lineId, screenRow) ->
|
||||
buildLineNode(screenRow)
|
||||
lineNodesById: {}
|
||||
lineIdForScreenRow: (screenRow) ->
|
||||
editor.screenLineForScreenRow(screenRow).id
|
||||
|
||||
textNodesForLineIdAndScreenRow: (lineId, screenRow) ->
|
||||
lineNode = @lineNodeForLineIdAndScreenRow(lineId, screenRow)
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesById[@lineIdForScreenRow(screenRow)] ?= buildLineNode(screenRow)
|
||||
|
||||
textNodesForScreenRow: (screenRow) ->
|
||||
lineNode = @lineNodeForScreenRow(screenRow)
|
||||
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT)
|
||||
textNodes = []
|
||||
while textNode = iterator.nextNode()
|
||||
textNodes.push(textNode)
|
||||
textNodes.push(textNode) while textNode = iterator.nextNode()
|
||||
textNodes
|
||||
|
||||
editor.setLineHeightInPixels(14)
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: editor.getLineHeightInPixels()
|
||||
})
|
||||
lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
|
||||
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
|
||||
|
||||
afterEach ->
|
||||
@@ -69,9 +78,9 @@ describe "LinesYardstick", ->
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 37.78125, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43.171875, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72.171875, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 38, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})
|
||||
|
||||
it "reuses already computed pixel positions unless it is invalidated", ->
|
||||
@@ -82,9 +91,9 @@ describe "LinesYardstick", ->
|
||||
}
|
||||
"""
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 96, top: 70})
|
||||
|
||||
atom.styles.addStyleSheet """
|
||||
* {
|
||||
@@ -92,9 +101,9 @@ describe "LinesYardstick", ->
|
||||
}
|
||||
"""
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 96, top: 70})
|
||||
|
||||
linesYardstick.invalidateCache()
|
||||
|
||||
@@ -102,23 +111,6 @@ describe "LinesYardstick", ->
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 72, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 120, top: 70})
|
||||
|
||||
it "correctly handles RTL characters", ->
|
||||
atom.styles.addStyleSheet """
|
||||
* {
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
}
|
||||
"""
|
||||
|
||||
editor.setText("السلام عليكم")
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0)).left).toBe 0
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1)).left).toBe 8
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 2)).left).toBe 16
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5)).left).toBe 33
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 7)).left).toBe 50
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 9)).left).toBe 67
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 11)).left).toBe 84
|
||||
|
||||
it "doesn't report a width greater than 0 when the character to measure is at the beginning of a text node", ->
|
||||
# This spec documents what seems to be a bug in Chromium, because we'd
|
||||
# expect that Range(0, 0).getBoundingClientRect().width to always be zero.
|
||||
@@ -163,9 +155,38 @@ describe "LinesYardstick", ->
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100})).toEqual([2, 14])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 99.9})).toEqual([5, 14])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 224.2365234375})).toEqual([5, 29])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 225})).toEqual([5, 30])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
|
||||
|
||||
it "overshoots to the nearest character when text nodes are not spatially contiguous", ->
|
||||
atom.styles.addStyleSheet """
|
||||
* {
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
}
|
||||
"""
|
||||
|
||||
buildLineNode = (screenRow) ->
|
||||
lineNode = document.createElement("div")
|
||||
lineNode.style.whiteSpace = "pre"
|
||||
lineNode.innerHTML = '<span>foo</span><span style="margin-left: 40px">bar</span>'
|
||||
jasmine.attachToDOM(lineNode)
|
||||
createdLineNodes.push(lineNode)
|
||||
lineNode
|
||||
editor.setText("foobar")
|
||||
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 7})).toEqual([0, 1])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 14})).toEqual([0, 2])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 21})).toEqual([0, 3])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 30})).toEqual([0, 3])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 50})).toEqual([0, 3])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 62})).toEqual([0, 3])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 69})).toEqual([0, 4])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 76})).toEqual([0, 5])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 100})).toEqual([0, 6])
|
||||
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 200})).toEqual([0, 6])
|
||||
|
||||
it "clips pixel positions above buffer start", ->
|
||||
expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
|
||||
@@ -178,3 +199,7 @@ describe "LinesYardstick", ->
|
||||
expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
|
||||
expect(linesYardstick.screenPositionForPixelPosition(top: (editor.getLastScreenRow() + 1) * 14, left: 0)).toEqual [12, 2]
|
||||
expect(linesYardstick.screenPositionForPixelPosition(top: editor.getLastScreenRow() * 14, left: 0)).toEqual [12, 0]
|
||||
|
||||
it "clips negative horizontal pixel positions", ->
|
||||
expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: -10)).toEqual [0, 0]
|
||||
expect(linesYardstick.screenPositionForPixelPosition(top: 1 * 14, left: -10)).toEqual [1, 0]
|
||||
|
||||
@@ -8,13 +8,13 @@ describe 'ModuleCache', ->
|
||||
beforeEach ->
|
||||
spyOn(Module, '_findPath').andCallThrough()
|
||||
|
||||
it 'resolves atom shell module paths without hitting the filesystem', ->
|
||||
it 'resolves Electron module paths without hitting the filesystem', ->
|
||||
builtins = ModuleCache.cache.builtins
|
||||
expect(Object.keys(builtins).length).toBeGreaterThan 0
|
||||
|
||||
for builtinName, builtinPath of builtins
|
||||
expect(require.resolve(builtinName)).toBe builtinPath
|
||||
expect(fs.isFileSync(require.resolve(builtinName)))
|
||||
expect(fs.isFileSync(require.resolve(builtinName))).toBeTruthy()
|
||||
|
||||
expect(Module._findPath.callCount).toBe 0
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ describe "PackageManager", ->
|
||||
expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-with-broken-package-json package")
|
||||
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-with-broken-package-json"
|
||||
|
||||
it "returns null if the package name or path starts with a dot", ->
|
||||
expect(atom.packages.loadPackage("/Users/user/.atom/packages/.git")).toBeNull()
|
||||
|
||||
it "normalizes short repository urls in package.json", ->
|
||||
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
|
||||
34
spec/pane-axis-element-spec.coffee
Normal file
34
spec/pane-axis-element-spec.coffee
Normal file
@@ -0,0 +1,34 @@
|
||||
PaneAxis = require '../src/pane-axis'
|
||||
PaneContainer = require '../src/pane-container'
|
||||
Pane = require '../src/pane'
|
||||
|
||||
buildPane = ->
|
||||
new Pane({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
config: atom.config,
|
||||
deserializerManager: atom.deserializers,
|
||||
notificationManager: atom.notifications
|
||||
})
|
||||
|
||||
describe "PaneAxisElement", ->
|
||||
it "correctly subscribes and unsubscribes to the underlying model events on attach/detach", ->
|
||||
container = new PaneContainer(config: atom.config, applicationDelegate: atom.applicationDelegate)
|
||||
axis = new PaneAxis
|
||||
axis.setContainer(container)
|
||||
axisElement = atom.views.getView(axis)
|
||||
|
||||
panes = [buildPane(), buildPane(), buildPane()]
|
||||
|
||||
jasmine.attachToDOM(axisElement)
|
||||
axis.addChild(panes[0])
|
||||
expect(axisElement.children[0]).toBe(atom.views.getView(panes[0]))
|
||||
|
||||
axisElement.remove()
|
||||
axis.addChild(panes[1])
|
||||
expect(axisElement.children[2]).toBeUndefined()
|
||||
|
||||
jasmine.attachToDOM(axisElement)
|
||||
expect(axisElement.children[2]).toBe(atom.views.getView(panes[1]))
|
||||
|
||||
axis.addChild(panes[2])
|
||||
expect(axisElement.children[4]).toBe(atom.views.getView(panes[2]))
|
||||
@@ -262,6 +262,26 @@ describe "Pane", ->
|
||||
pane.setPendingItem("fake item two")
|
||||
expect(callbackCalled).toBeTruthy()
|
||||
|
||||
it "isn't called when a pending item is replaced with a new one", ->
|
||||
pane = null
|
||||
pendingSpy = jasmine.createSpy("onItemDidTerminatePendingState")
|
||||
destroySpy = jasmine.createSpy("onWillDestroyItem")
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
pane.onItemDidTerminatePendingState pendingSpy
|
||||
pane.onWillDestroyItem destroySpy
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true)
|
||||
|
||||
runs ->
|
||||
expect(destroySpy).toHaveBeenCalled()
|
||||
expect(pendingSpy).not.toHaveBeenCalled()
|
||||
|
||||
describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
|
||||
it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))
|
||||
@@ -897,6 +917,82 @@ describe "Pane", ->
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
expect(pane.isDestroyed()).toBe false
|
||||
|
||||
describe "when item fails to save", ->
|
||||
[pane, item1, item2] = []
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane({items: [new Item("A"), new Item("B")], applicationDelegate: atom.applicationDelegate, config: atom.config})
|
||||
[item1, item2] = pane.getItems()
|
||||
|
||||
item1.shouldPromptToSave = -> true
|
||||
item1.getURI = -> "/test/path"
|
||||
|
||||
item1.save = jasmine.createSpy("save").andCallFake ->
|
||||
error = new Error("EACCES, permission denied '/test/path'")
|
||||
error.path = '/test/path'
|
||||
error.code = 'EACCES'
|
||||
throw error
|
||||
|
||||
it "does not destroy the pane if save fails and user clicks cancel", ->
|
||||
confirmations = 0
|
||||
confirm.andCallFake ->
|
||||
confirmations++
|
||||
if confirmations is 1
|
||||
return 0 # click save
|
||||
else
|
||||
return 1 # click cancel
|
||||
|
||||
pane.close()
|
||||
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(confirmations).toBe(2)
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(pane.isDestroyed()).toBe false
|
||||
|
||||
it "does destroy the pane if the user saves the file under a new name", ->
|
||||
item1.saveAs = jasmine.createSpy("saveAs").andReturn(true)
|
||||
|
||||
confirmations = 0
|
||||
confirm.andCallFake ->
|
||||
confirmations++
|
||||
return 0 # save and then save as
|
||||
|
||||
showSaveDialog.andReturn("new/path")
|
||||
|
||||
pane.close()
|
||||
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(confirmations).toBe(2)
|
||||
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(item1.saveAs).toHaveBeenCalled()
|
||||
expect(pane.isDestroyed()).toBe true
|
||||
|
||||
it "asks again if the saveAs also fails", ->
|
||||
item1.saveAs = jasmine.createSpy("saveAs").andCallFake ->
|
||||
error = new Error("EACCES, permission denied '/test/path'")
|
||||
error.path = '/test/path'
|
||||
error.code = 'EACCES'
|
||||
throw error
|
||||
|
||||
confirmations = 0
|
||||
confirm.andCallFake ->
|
||||
confirmations++
|
||||
if confirmations < 3
|
||||
return 0 # save, save as, save as
|
||||
return 2 # don't save
|
||||
|
||||
showSaveDialog.andReturn("new/path")
|
||||
|
||||
pane.close()
|
||||
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(confirmations).toBe(3)
|
||||
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalled()
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(item1.saveAs).toHaveBeenCalled()
|
||||
expect(pane.isDestroyed()).toBe true
|
||||
|
||||
describe "::destroy()", ->
|
||||
[container, pane1, pane2] = []
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ describe "Project", ->
|
||||
|
||||
runs ->
|
||||
bufferA = atom.project.getBuffers()[0]
|
||||
layerA = bufferA.addMarkerLayer(maintainHistory: true)
|
||||
layerA = bufferA.addMarkerLayer(persistent: true)
|
||||
markerA = layerA.markPosition([0, 3])
|
||||
|
||||
notQuittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
@@ -526,7 +526,7 @@ describe "Project", ->
|
||||
expect(atom.project.getDirectories()[1].contains(inputPath)).toBe true
|
||||
expect(atom.project.relativizePath(inputPath)).toEqual [
|
||||
atom.project.getPaths()[1],
|
||||
'somewhere/something.txt'
|
||||
path.join('somewhere', 'something.txt')
|
||||
]
|
||||
|
||||
describe ".contains(path)", ->
|
||||
|
||||
@@ -17,7 +17,7 @@ describe "TextEditor", ->
|
||||
buffer = new TextBuffer
|
||||
editor = atom.workspace.buildTextEditor({buffer})
|
||||
editor.setEditorWidthInChars(80)
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
steps = []
|
||||
|
||||
times 30, ->
|
||||
@@ -33,8 +33,8 @@ describe "TextEditor", ->
|
||||
logLines()
|
||||
throw new Error("Invalid buffer row #{actualBufferRow} for screen row #{screenRow}", )
|
||||
|
||||
actualScreenLine = editor.tokenizedLineForScreenRow(screenRow)
|
||||
unless actualScreenLine.text is referenceScreenLine.text
|
||||
actualScreenLine = editor.lineTextForScreenRow(screenRow)
|
||||
unless actualScreenLine is referenceScreenLine
|
||||
logLines()
|
||||
throw new Error("Invalid line text at screen row #{screenRow}")
|
||||
|
||||
@@ -84,7 +84,8 @@ describe "TextEditor", ->
|
||||
referenceEditor.setEditorWidthInChars(80)
|
||||
referenceEditor.setText(editor.getText())
|
||||
referenceEditor.setSoftWrapped(editor.isSoftWrapped())
|
||||
screenLines = referenceEditor.tokenizedLinesForScreenRows(0, referenceEditor.getLastScreenRow())
|
||||
|
||||
screenLines = [0..referenceEditor.getLastScreenRow()].map (row) => referenceEditor.lineTextForScreenRow(row)
|
||||
bufferRows = referenceEditor.bufferRowsForScreenRows(0, referenceEditor.getLastScreenRow())
|
||||
|
||||
{screenLines, bufferRows}
|
||||
|
||||
@@ -91,3 +91,32 @@ describe "Selection", ->
|
||||
expect(buffer.lineForRow(0)).toBe " "
|
||||
expect(buffer.lineForRow(1)).toBe " "
|
||||
expect(buffer.lineForRow(2)).toBe ""
|
||||
|
||||
it "auto-indents if only a newline is inserted", ->
|
||||
selection.setBufferRange [[2, 0], [3, 0]]
|
||||
selection.insertText("\n", autoIndent: true)
|
||||
expect(buffer.lineForRow(2)).toBe " "
|
||||
|
||||
it "auto-indents if only a carriage return + newline is inserted", ->
|
||||
selection.setBufferRange [[2, 0], [3, 0]]
|
||||
selection.insertText("\r\n", autoIndent: true)
|
||||
expect(buffer.lineForRow(2)).toBe " "
|
||||
|
||||
describe ".fold()", ->
|
||||
it "folds the buffer range spanned by the selection", ->
|
||||
selection.setBufferRange([[0, 3], [1, 6]])
|
||||
selection.fold()
|
||||
|
||||
expect(selection.getScreenRange()).toEqual([[0, 4], [0, 4]])
|
||||
expect(selection.getBufferRange()).toEqual([[1, 6], [1, 6]])
|
||||
expect(editor.lineTextForScreenRow(0)).toBe "var#{editor.displayLayer.foldCharacter}sort = function(items) {"
|
||||
expect(editor.isFoldedAtBufferRow(0)).toBe(true)
|
||||
|
||||
it "doesn't create a fold when the selection is empty", ->
|
||||
selection.setBufferRange([[0, 3], [0, 3]])
|
||||
selection.fold()
|
||||
|
||||
expect(selection.getScreenRange()).toEqual([[0, 3], [0, 3]])
|
||||
expect(selection.getBufferRange()).toEqual([[0, 3], [0, 3]])
|
||||
expect(editor.lineTextForScreenRow(0)).toBe "var quicksort = function () {"
|
||||
expect(editor.isFoldedAtBufferRow(0)).toBe(false)
|
||||
|
||||
57
spec/spawner-spec.coffee
Normal file
57
spec/spawner-spec.coffee
Normal file
@@ -0,0 +1,57 @@
|
||||
ChildProcess = require 'child_process'
|
||||
Spawner = require '../src/browser/spawner'
|
||||
|
||||
describe "Spawner", ->
|
||||
beforeEach ->
|
||||
# Prevent any commands from actually running and affecting the host
|
||||
originalSpawn = ChildProcess.spawn
|
||||
|
||||
harmlessSpawn =
|
||||
# Just spawn something that won't actually modify the host
|
||||
if process.platform is 'win32'
|
||||
originalSpawn('dir')
|
||||
else
|
||||
originalSpawn('ls')
|
||||
|
||||
spyOn(ChildProcess, 'spawn').andCallFake (command, args, callback) ->
|
||||
harmlessSpawn
|
||||
|
||||
it "invokes passed callback", ->
|
||||
someCallback = jasmine.createSpy('someCallback')
|
||||
|
||||
Spawner.spawn('some-command', 'some-args', someCallback)
|
||||
|
||||
waitsFor ->
|
||||
someCallback.callCount is 1
|
||||
|
||||
it "spawns passed command with arguments", ->
|
||||
actualCommand = null
|
||||
actualArgs = null
|
||||
|
||||
# Redefine fake invocation, so to remember passed arguments
|
||||
jasmine.unspy(ChildProcess, 'spawn')
|
||||
spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
|
||||
actualCommand = command
|
||||
actualArgs = args
|
||||
harmlessSpawn
|
||||
|
||||
expectedCommand = 'some-command'
|
||||
expectedArgs = 'some-args'
|
||||
someCallback = jasmine.createSpy('someCallback')
|
||||
|
||||
Spawner.spawn(expectedCommand, expectedArgs, someCallback)
|
||||
|
||||
expect(actualCommand).toBe expectedCommand
|
||||
expect(actualArgs).toBe expectedArgs
|
||||
|
||||
it "ignores errors by spawned process", ->
|
||||
# Redefine fake invocation, so to cause an error
|
||||
jasmine.unspy(ChildProcess, 'spawn')
|
||||
spyOn(ChildProcess, 'spawn').andCallFake -> throw new Error("EBUSY")
|
||||
|
||||
someCallback = jasmine.createSpy('someCallback')
|
||||
|
||||
expect(Spawner.spawn('some-command', 'some-args', someCallback)).toBe undefined
|
||||
|
||||
waitsFor ->
|
||||
someCallback.callCount is 1
|
||||
@@ -112,14 +112,14 @@ afterEach ->
|
||||
|
||||
document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent
|
||||
|
||||
ensureNoPathSubscriptions()
|
||||
warnIfLeakingPathSubscriptions()
|
||||
waits(0) # yield to ui thread to make screen update more frequently
|
||||
|
||||
ensureNoPathSubscriptions = ->
|
||||
warnIfLeakingPathSubscriptions = ->
|
||||
watchedPaths = pathwatcher.getWatchedPaths()
|
||||
pathwatcher.closeAllWatchers()
|
||||
if watchedPaths.length > 0
|
||||
throw new Error("Leaking subscriptions for paths: " + watchedPaths.join(", "))
|
||||
console.error("WARNING: Leaking subscriptions for paths: " + watchedPaths.join(", "))
|
||||
pathwatcher.closeAllWatchers()
|
||||
|
||||
ensureNoDeprecatedFunctionsCalled = ->
|
||||
deprecations = Grim.getDeprecations()
|
||||
@@ -172,8 +172,8 @@ jasmine.useRealClock = ->
|
||||
addCustomMatchers = (spec) ->
|
||||
spec.addMatchers
|
||||
toBeInstanceOf: (expected) ->
|
||||
notText = if @isNot then " not" else ""
|
||||
this.message = => "Expected #{jasmine.pp(@actual)} to#{notText} be instance of #{expected.name} class"
|
||||
beOrNotBe = if @isNot then "not be" else "be"
|
||||
this.message = => "Expected #{jasmine.pp(@actual)} to #{beOrNotBe} instance of #{expected.name} class"
|
||||
@actual instanceof expected
|
||||
|
||||
toHaveLength: (expected) ->
|
||||
@@ -181,32 +181,38 @@ addCustomMatchers = (spec) ->
|
||||
this.message = => "Expected object #{@actual} has no length method"
|
||||
false
|
||||
else
|
||||
notText = if @isNot then " not" else ""
|
||||
this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
|
||||
haveOrNotHave = if @isNot then "not have" else "have"
|
||||
this.message = => "Expected object with length #{@actual.length} to #{haveOrNotHave} length #{expected}"
|
||||
@actual.length is expected
|
||||
|
||||
toExistOnDisk: (expected) ->
|
||||
notText = this.isNot and " not" or ""
|
||||
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
|
||||
toOrNotTo = this.isNot and "not to" or "to"
|
||||
@message = -> return "Expected path '#{@actual}' #{toOrNotTo} exist."
|
||||
fs.existsSync(@actual)
|
||||
|
||||
toHaveFocus: ->
|
||||
notText = this.isNot and " not" or ""
|
||||
toOrNotTo = this.isNot and "not to" or "to"
|
||||
if not document.hasFocus()
|
||||
console.error "Specs will fail because the Dev Tools have focus. To fix this close the Dev Tools or click the spec runner."
|
||||
|
||||
@message = -> return "Expected element '" + @actual + "' or its descendants" + notText + " to have focus."
|
||||
@message = -> return "Expected element '#{@actual}' or its descendants #{toOrNotTo} have focus."
|
||||
element = @actual
|
||||
element = element.get(0) if element.jquery
|
||||
element is document.activeElement or element.contains(document.activeElement)
|
||||
|
||||
toShow: ->
|
||||
notText = if @isNot then " not" else ""
|
||||
toOrNotTo = this.isNot and "not to" or "to"
|
||||
element = @actual
|
||||
element = element.get(0) if element.jquery
|
||||
@message = -> return "Expected element '#{element}' or its descendants#{notText} to show."
|
||||
@message = -> return "Expected element '#{element}' or its descendants #{toOrNotTo} show."
|
||||
element.style.display in ['block', 'inline-block', 'static', 'fixed']
|
||||
|
||||
toEqualPath: (expected) ->
|
||||
actualPath = path.normalize(@actual)
|
||||
expectedPath = path.normalize(expected)
|
||||
@message = -> return "Expected path '#{actualPath}' to be equal to '#{expectedPath}'."
|
||||
actualPath is expectedPath
|
||||
|
||||
window.waitsForPromise = (args...) ->
|
||||
label = null
|
||||
if args.length > 1
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
ChildProcess = require 'child_process'
|
||||
{EventEmitter} = require 'events'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
SquirrelUpdate = require '../src/browser/squirrel-update'
|
||||
Spawner = require '../src/browser/spawner'
|
||||
WinPowerShell = require '../src/browser/win-powershell'
|
||||
WinRegistry = require '../src/browser/win-registry'
|
||||
|
||||
# Run passed callback as Spawner.spawn() would do
|
||||
invokeCallback = (callback) ->
|
||||
error = null
|
||||
stdout = ''
|
||||
callback?(error, stdout)
|
||||
|
||||
describe "Windows Squirrel Update", ->
|
||||
tempHomeDirectory = null
|
||||
originalSpawn = ChildProcess.spawn
|
||||
|
||||
harmlessSpawn = ->
|
||||
# Just spawn something that won't actually modify the host
|
||||
if process.platform is 'win32'
|
||||
originalSpawn('dir')
|
||||
else
|
||||
originalSpawn('ls')
|
||||
|
||||
beforeEach ->
|
||||
# Prevent the actual home directory from being manipulated
|
||||
tempHomeDirectory = temp.mkdirSync('atom-temp-home-')
|
||||
spyOn(fs, 'getHomeDirectory').andReturn(tempHomeDirectory)
|
||||
|
||||
# Prevent any commands from actually running and affecting the host
|
||||
spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
|
||||
harmlessSpawn()
|
||||
# Prevent any spawned command from actually running and affecting the host
|
||||
spyOn(Spawner, 'spawn').andCallFake (command, args, callback) ->
|
||||
# do nothing on command, just run passed callback
|
||||
invokeCallback callback
|
||||
|
||||
it "ignores errors spawning Squirrel", ->
|
||||
jasmine.unspy(ChildProcess, 'spawn')
|
||||
spyOn(ChildProcess, 'spawn').andCallFake -> throw new Error("EBUSY")
|
||||
|
||||
app = quit: jasmine.createSpy('quit')
|
||||
expect(SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')).toBe true
|
||||
|
||||
waitsFor ->
|
||||
app.quit.callCount is 1
|
||||
# Prevent any actual change to Windows registry
|
||||
for own method of WinRegistry
|
||||
# all WinRegistry APIs share the same signature
|
||||
spyOn(WinRegistry, method).andCallFake (callback) ->
|
||||
# do nothing on registry, just run passed callback
|
||||
invokeCallback callback
|
||||
|
||||
it "quits the app on all squirrel events", ->
|
||||
app = quit: jasmine.createSpy('quit')
|
||||
@@ -69,51 +67,52 @@ describe "Windows Squirrel Update", ->
|
||||
|
||||
describe "Desktop shortcut", ->
|
||||
desktopShortcutPath = '/non/existing/path'
|
||||
|
||||
|
||||
beforeEach ->
|
||||
desktopShortcutPath = path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk')
|
||||
|
||||
jasmine.unspy(ChildProcess, 'spawn')
|
||||
spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
|
||||
jasmine.unspy(Spawner, 'spawn')
|
||||
spyOn(Spawner, 'spawn').andCallFake (command, args, callback) ->
|
||||
if path.basename(command) is 'Update.exe' and args?[0] is '--createShortcut'
|
||||
fs.writeFileSync(path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk'), '')
|
||||
harmlessSpawn()
|
||||
fs.writeFileSync(desktopShortcutPath, '')
|
||||
else
|
||||
throw new Error("API not mocked")
|
||||
|
||||
# simply ignore other commands
|
||||
|
||||
invokeCallback callback
|
||||
|
||||
it "does not exist before install", ->
|
||||
expect(fs.existsSync(desktopShortcutPath)).toBe false
|
||||
|
||||
|
||||
describe "on install", ->
|
||||
beforeEach ->
|
||||
app = quit: jasmine.createSpy('quit')
|
||||
SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')
|
||||
waitsFor ->
|
||||
app.quit.callCount is 1
|
||||
|
||||
|
||||
it "creates desktop shortcut", ->
|
||||
expect(fs.existsSync(desktopShortcutPath)).toBe true
|
||||
|
||||
|
||||
describe "when shortcut is deleted and then app is updated", ->
|
||||
beforeEach ->
|
||||
fs.removeSync(desktopShortcutPath)
|
||||
expect(fs.existsSync(desktopShortcutPath)).toBe false
|
||||
|
||||
|
||||
app = quit: jasmine.createSpy('quit')
|
||||
SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')
|
||||
waitsFor ->
|
||||
app.quit.callCount is 1
|
||||
|
||||
|
||||
it "does not recreate shortcut", ->
|
||||
expect(fs.existsSync(desktopShortcutPath)).toBe false
|
||||
|
||||
|
||||
describe "when shortcut is kept and app is updated", ->
|
||||
beforeEach ->
|
||||
app = quit: jasmine.createSpy('quit')
|
||||
SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')
|
||||
waitsFor ->
|
||||
app.quit.callCount is 1
|
||||
|
||||
|
||||
it "still has desktop shortcut", ->
|
||||
expect(fs.existsSync(desktopShortcutPath)).toBe true
|
||||
|
||||
@@ -125,7 +124,7 @@ describe "Windows Squirrel Update", ->
|
||||
SquirrelUpdate.restartAtom(app)
|
||||
expect(app.quit.callCount).toBe 1
|
||||
|
||||
expect(ChildProcess.spawn.callCount).toBe 0
|
||||
expect(Spawner.spawn.callCount).toBe 0
|
||||
app.emit('will-quit')
|
||||
expect(ChildProcess.spawn.callCount).toBe 1
|
||||
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'atom.cmd'
|
||||
expect(Spawner.spawn.callCount).toBe 1
|
||||
expect(path.basename(Spawner.spawn.argsForCall[0][0])).toBe 'atom.cmd'
|
||||
|
||||
@@ -69,13 +69,12 @@ describe('TextEditorComponent', function () {
|
||||
describe('line rendering', async function () {
|
||||
function expectTileContainsRow (tileNode, screenRow, {top}) {
|
||||
let lineNode = tileNode.querySelector('[data-screen-row="' + screenRow + '"]')
|
||||
let tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
|
||||
|
||||
let text = editor.lineTextForScreenRow(screenRow)
|
||||
expect(lineNode.offsetTop).toBe(top)
|
||||
if (tokenizedLine.text === '') {
|
||||
expect(lineNode.innerHTML).toBe(' ')
|
||||
if (text === '') {
|
||||
expect(lineNode.textContent).toBe(' ')
|
||||
} else {
|
||||
expect(lineNode.textContent).toBe(tokenizedLine.text)
|
||||
expect(lineNode.textContent).toBe(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,12 +293,12 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.tokenizedLineForScreenRow(3).text)
|
||||
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.lineTextForScreenRow(3))
|
||||
buffer.delete([[0, 0], [3, 0]])
|
||||
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.tokenizedLineForScreenRow(3).text)
|
||||
expect(component.lineNodeForScreenRow(3).textContent).toBe(editor.lineTextForScreenRow(3))
|
||||
})
|
||||
|
||||
it('updates the top position of lines when the line height changes', async function () {
|
||||
@@ -361,9 +360,9 @@ describe('TextEditorComponent', function () {
|
||||
}
|
||||
})
|
||||
|
||||
it('renders an nbsp on empty lines when no line-ending character is defined', function () {
|
||||
it('renders an placeholder space on empty lines when no line-ending character is defined', function () {
|
||||
atom.config.set('editor.showInvisibles', false)
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(NBSP)
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(' ')
|
||||
})
|
||||
|
||||
it('gives the lines and tiles divs the same background color as the editor to improve GPU performance', async function () {
|
||||
@@ -429,13 +428,14 @@ describe('TextEditorComponent', function () {
|
||||
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps rebuilding lines when continuous reflow is on', function () {
|
||||
it('keeps rebuilding lines when continuous reflow is on', async function () {
|
||||
wrapperNode.setContinuousReflow(true)
|
||||
let oldLineNode = componentNode.querySelector('.line')
|
||||
let oldLineNode = componentNode.querySelectorAll('.line')[1]
|
||||
|
||||
waitsFor(function () {
|
||||
return componentNode.querySelector('.line') !== oldLineNode
|
||||
})
|
||||
while (true) {
|
||||
await nextViewUpdatePromise()
|
||||
if (componentNode.querySelectorAll('.line')[1] !== oldLineNode) break
|
||||
}
|
||||
})
|
||||
|
||||
describe('when showInvisibles is enabled', function () {
|
||||
@@ -484,7 +484,7 @@ describe('TextEditorComponent', function () {
|
||||
it('displays newlines as their own token outside of the other tokens\' scopeDescriptor', async function () {
|
||||
editor.setText('let\n')
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage type var js">let</span></span><span class="invisible-character">' + invisibles.eol + '</span>')
|
||||
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage type var js">let</span><span class="invisible-character eol">' + invisibles.eol + '</span></span>')
|
||||
})
|
||||
|
||||
it('displays trailing carriage returns using a visible, non-empty value', async function () {
|
||||
@@ -497,20 +497,20 @@ describe('TextEditorComponent', function () {
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(invisibles.eol)
|
||||
})
|
||||
|
||||
it('renders an nbsp on empty lines when the line-ending character is an empty string', async function () {
|
||||
it('renders a placeholder space on empty lines when the line-ending character is an empty string', async function () {
|
||||
atom.config.set('editor.invisibles', {
|
||||
eol: ''
|
||||
})
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(NBSP)
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(' ')
|
||||
})
|
||||
|
||||
it('renders an nbsp on empty lines when the line-ending character is false', async function () {
|
||||
it('renders an placeholder space on empty lines when the line-ending character is false', async function () {
|
||||
atom.config.set('editor.invisibles', {
|
||||
eol: false
|
||||
})
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(NBSP)
|
||||
expect(component.lineNodeForScreenRow(10).textContent).toBe(' ')
|
||||
})
|
||||
|
||||
it('interleaves invisible line-ending characters with indent guides on empty lines', async function () {
|
||||
@@ -518,24 +518,25 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
editor.setTabLength(2)
|
||||
editor.setTextInBufferRange([[10, 0], [11, 0]], '\r\n', {
|
||||
normalizeLineEndings: false
|
||||
})
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
|
||||
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span></span>')
|
||||
editor.setTabLength(3)
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
|
||||
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span> </span>')
|
||||
editor.setTabLength(1)
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
|
||||
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span></span><span class="indent-guide"><span class="invisible-character">E</span></span>')
|
||||
editor.setTextInBufferRange([[9, 0], [9, Infinity]], ' ')
|
||||
editor.setTextInBufferRange([[11, 0], [11, Infinity]], ' ')
|
||||
await nextViewUpdatePromise()
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="indent-guide"><span class="invisible-character">C</span></span><span class="invisible-character">E</span>')
|
||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
|
||||
})
|
||||
|
||||
describe('when soft wrapping is enabled', function () {
|
||||
@@ -550,8 +551,8 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
|
||||
it('does not show end of line invisibles at the end of wrapped lines', function () {
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe('a line that ')
|
||||
expect(component.lineNodeForScreenRow(1).textContent).toBe('wraps' + invisibles.space + invisibles.eol)
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe('a line ')
|
||||
expect(component.lineNodeForScreenRow(1).textContent).toBe('that wraps' + invisibles.space + invisibles.eol)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -986,13 +987,14 @@ describe('TextEditorComponent', function () {
|
||||
expect(component.lineNumberNodeForScreenRow(3) != null).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps rebuilding line numbers when continuous reflow is on', function () {
|
||||
it('keeps rebuilding line numbers when continuous reflow is on', async function () {
|
||||
wrapperNode.setContinuousReflow(true)
|
||||
let oldLineNode = componentNode.querySelectorAll('.line-number')[1]
|
||||
|
||||
waitsFor(function () {
|
||||
return componentNode.querySelectorAll('.line-number')[1] !== oldLineNode
|
||||
})
|
||||
while (true) {
|
||||
await nextViewUpdatePromise()
|
||||
if (componentNode.querySelectorAll('.line-number')[1] !== oldLineNode) break
|
||||
}
|
||||
})
|
||||
|
||||
describe('fold decorations', function () {
|
||||
@@ -1051,7 +1053,7 @@ describe('TextEditorComponent', function () {
|
||||
beforeEach(async function () {
|
||||
editor.setSoftWrapped(true)
|
||||
await nextViewUpdatePromise()
|
||||
componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
|
||||
componentNode.style.width = 20 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
})
|
||||
@@ -1060,6 +1062,14 @@ describe('TextEditorComponent', function () {
|
||||
expect(lineNumberHasClass(0, 'foldable')).toBe(true)
|
||||
expect(lineNumberHasClass(1, 'foldable')).toBe(false)
|
||||
})
|
||||
|
||||
it('does not add the folded class for soft-wrapped lines that contain a fold', async function () {
|
||||
editor.foldBufferRange([[3, 19], [3, 21]])
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
expect(lineNumberHasClass(11, 'folded')).toBe(true)
|
||||
expect(lineNumberHasClass(12, 'folded')).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1082,7 +1092,7 @@ describe('TextEditorComponent', function () {
|
||||
component.destroy()
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
target = lineNumber.querySelector('.icon-right')
|
||||
return target.dispatchEvent(buildClickEvent(target))
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1106,6 +1116,37 @@ describe('TextEditorComponent', function () {
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe(false)
|
||||
})
|
||||
|
||||
it('unfolds all the free-form folds intersecting the buffer row when clicked', async function () {
|
||||
expect(lineNumberHasClass(3, 'foldable')).toBe(false)
|
||||
|
||||
editor.foldBufferRange([[3, 4], [5, 4]])
|
||||
editor.foldBufferRange([[5, 5], [8, 10]])
|
||||
await nextViewUpdatePromise()
|
||||
expect(lineNumberHasClass(3, 'folded')).toBe(true)
|
||||
expect(lineNumberHasClass(5, 'folded')).toBe(false)
|
||||
|
||||
let lineNumber = component.lineNumberNodeForScreenRow(3)
|
||||
let target = lineNumber.querySelector('.icon-right')
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
await nextViewUpdatePromise()
|
||||
expect(lineNumberHasClass(3, 'folded')).toBe(false)
|
||||
expect(lineNumberHasClass(5, 'folded')).toBe(true)
|
||||
|
||||
editor.setSoftWrapped(true)
|
||||
componentNode.style.width = 20 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
editor.foldBufferRange([[3, 19], [3, 21]]) // fold starting on a soft-wrapped portion of the line
|
||||
await nextViewUpdatePromise()
|
||||
expect(lineNumberHasClass(11, 'folded')).toBe(true)
|
||||
|
||||
lineNumber = component.lineNumberNodeForScreenRow(11)
|
||||
target = lineNumber.querySelector('.icon-right')
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
await nextViewUpdatePromise()
|
||||
expect(lineNumberHasClass(11, 'folded')).toBe(false)
|
||||
})
|
||||
|
||||
it('does not fold when the line number componentNode is clicked', function () {
|
||||
let lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
lineNumber.dispatchEvent(buildClickEvent(lineNumber))
|
||||
@@ -1200,7 +1241,7 @@ describe('TextEditorComponent', function () {
|
||||
let cursor = componentNode.querySelector('.cursor')
|
||||
let cursorRect = cursor.getBoundingClientRect()
|
||||
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').childNodes[2]
|
||||
let range = document.createRange()
|
||||
let range = document.createRange(cursorLocationTextNode)
|
||||
range.setStart(cursorLocationTextNode, 0)
|
||||
range.setEnd(cursorLocationTextNode, 1)
|
||||
let rangeRect = range.getBoundingClientRect()
|
||||
@@ -1208,6 +1249,17 @@ describe('TextEditorComponent', function () {
|
||||
expect(cursorRect.width).toBeCloseTo(rangeRect.width, 0)
|
||||
})
|
||||
|
||||
it('positions cursors after the fold-marker when a fold ends the line', async function () {
|
||||
editor.foldBufferRow(0)
|
||||
await nextViewUpdatePromise()
|
||||
editor.setCursorScreenPosition([0, 30])
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let cursorRect = componentNode.querySelector('.cursor').getBoundingClientRect()
|
||||
let foldMarkerRect = componentNode.querySelector('.fold-marker').getBoundingClientRect()
|
||||
expect(cursorRect.left).toBeCloseTo(foldMarkerRect.right, 0)
|
||||
})
|
||||
|
||||
it('positions cursors correctly after character widths are changed via a stylesheet change', async function () {
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
editor.setCursorScreenPosition([0, 16])
|
||||
@@ -1475,7 +1527,7 @@ describe('TextEditorComponent', function () {
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let marker2 = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]])
|
||||
let marker2 = editor.markBufferRange([[9, 0], [9, 0]])
|
||||
editor.decorateMarker(marker2, {
|
||||
type: ['line-number', 'line'],
|
||||
'class': 'b'
|
||||
@@ -1887,7 +1939,7 @@ describe('TextEditorComponent', function () {
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
marker = editor.displayBuffer.markBufferRange([[9, 2], [9, 4]], {
|
||||
marker = editor.markBufferRange([[9, 2], [9, 4]], {
|
||||
invalidate: 'inside'
|
||||
})
|
||||
editor.decorateMarker(marker, {
|
||||
@@ -2082,7 +2134,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
describe('when the marker is empty', function () {
|
||||
it('renders an overlay decoration when added and removes the overlay when the decoration is destroyed', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[2, 13], [2, 13]], {
|
||||
let marker = editor.markBufferRange([[2, 13], [2, 13]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@@ -2104,7 +2156,7 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
|
||||
it('renders the overlay element with the CSS class specified by the decoration', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[2, 13], [2, 13]], {
|
||||
let marker = editor.markBufferRange([[2, 13], [2, 13]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@@ -2125,7 +2177,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
describe('when the marker is not empty', function () {
|
||||
it('renders at the head of the marker by default', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], {
|
||||
let marker = editor.markBufferRange([[2, 5], [2, 10]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@@ -2171,7 +2223,7 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
|
||||
it('slides horizontally left when near the right edge on #win32 and #darwin', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[0, 26], [0, 26]], {
|
||||
let marker = editor.markBufferRange([[0, 26], [0, 26]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@@ -2753,20 +2805,60 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a line is folded', function () {
|
||||
beforeEach(async function () {
|
||||
editor.foldBufferRow(4)
|
||||
describe('when a fold marker is clicked', function () {
|
||||
function clickElementAtPosition (marker, position) {
|
||||
linesNode.dispatchEvent(
|
||||
buildMouseEvent('mousedown', clientCoordinatesForScreenPosition(position), {target: marker})
|
||||
)
|
||||
}
|
||||
|
||||
it('unfolds only the selected fold when other folds are on the same line', async function () {
|
||||
editor.foldBufferRange([[4, 6], [4, 10]])
|
||||
editor.foldBufferRange([[4, 15], [4, 20]])
|
||||
await nextViewUpdatePromise()
|
||||
let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(2)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
|
||||
|
||||
clickElementAtPosition(foldMarkers[0], [4, 6])
|
||||
await nextViewUpdatePromise()
|
||||
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(1)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
|
||||
|
||||
clickElementAtPosition(foldMarkers[0], [4, 15])
|
||||
await nextViewUpdatePromise()
|
||||
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(0)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
|
||||
})
|
||||
|
||||
describe('when the folded line\'s fold-marker is clicked', function () {
|
||||
it('unfolds the buffer row', function () {
|
||||
let target = component.lineNodeForScreenRow(4).querySelector('.fold-marker')
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8]), {
|
||||
target: target
|
||||
}))
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
|
||||
})
|
||||
it('unfolds only the selected fold when other folds are inside it', async function () {
|
||||
editor.foldBufferRange([[4, 10], [4, 15]])
|
||||
editor.foldBufferRange([[4, 4], [4, 5]])
|
||||
editor.foldBufferRange([[4, 4], [4, 20]])
|
||||
await nextViewUpdatePromise()
|
||||
let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(1)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
|
||||
|
||||
clickElementAtPosition(foldMarkers[0], [4, 4])
|
||||
await nextViewUpdatePromise()
|
||||
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(1)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
|
||||
|
||||
clickElementAtPosition(foldMarkers[0], [4, 4])
|
||||
await nextViewUpdatePromise()
|
||||
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(1)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
|
||||
|
||||
clickElementAtPosition(foldMarkers[0], [4, 10])
|
||||
await nextViewUpdatePromise()
|
||||
foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
|
||||
expect(foldMarkers.length).toBe(0)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3101,7 +3193,7 @@ describe('TextEditorComponent', function () {
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(11), {
|
||||
shiftKey: true
|
||||
}))
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[7, 4], [16, 0]])
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[7, 4], [17, 0]])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3175,7 +3267,7 @@ describe('TextEditorComponent', function () {
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(11), {
|
||||
metaKey: true
|
||||
}))
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([[[7, 4], [7, 6]], [[11, 4], [19, 0]]])
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([[[7, 4], [7, 6]], [[11, 4], [20, 0]]])
|
||||
})
|
||||
|
||||
it('merges overlapping selections on mouseup', async function () {
|
||||
@@ -3189,7 +3281,7 @@ describe('TextEditorComponent', function () {
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(5), {
|
||||
metaKey: true
|
||||
}))
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([[[5, 0], [19, 0]]])
|
||||
expect(editor.getSelectedScreenRanges()).toEqual([[[5, 0], [20, 0]]])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3204,7 +3296,7 @@ describe('TextEditorComponent', function () {
|
||||
}))
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(11)))
|
||||
await nextAnimationFramePromise()
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [11, 14]])
|
||||
expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [11, 5]])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3745,6 +3837,21 @@ describe('TextEditorComponent', function () {
|
||||
return event
|
||||
}
|
||||
|
||||
function buildKeydownEvent ({keyCode, target}) {
|
||||
let event = new KeyboardEvent('keydown')
|
||||
Object.defineProperty(event, 'keyCode', {
|
||||
get: function () {
|
||||
return keyCode
|
||||
}
|
||||
})
|
||||
Object.defineProperty(event, 'target', {
|
||||
get: function () {
|
||||
return target
|
||||
}
|
||||
})
|
||||
return event
|
||||
}
|
||||
|
||||
let inputNode
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -3767,11 +3874,12 @@ describe('TextEditorComponent', function () {
|
||||
expect(editor.lineTextForBufferRow(0)).toBe('xyvar quicksort = function () {')
|
||||
})
|
||||
|
||||
it('replaces the last character if the length of the input\'s value does not increase, as occurs with the accented character menu', async function () {
|
||||
componentNode.dispatchEvent(buildTextInputEvent({
|
||||
data: 'u',
|
||||
target: inputNode
|
||||
}))
|
||||
it('replaces the last character if a keypress event is bracketed by keydown events with matching keyCodes, which occurs when the accented character menu is shown', async function () {
|
||||
componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode}))
|
||||
componentNode.dispatchEvent(buildTextInputEvent({data: 'u', target: inputNode}))
|
||||
componentNode.dispatchEvent(new KeyboardEvent('keypress'))
|
||||
componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode}))
|
||||
componentNode.dispatchEvent(new KeyboardEvent('keyup'))
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
expect(editor.lineTextForBufferRow(0)).toBe('uvar quicksort = function () {')
|
||||
@@ -4006,15 +4114,33 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when changing the font', async function () {
|
||||
it('measures the default char, the korean char, the double width char and the half width char widths', async function () {
|
||||
expect(editor.getDefaultCharWidth()).toBeCloseTo(12, 0)
|
||||
describe('when decreasing the fontSize', async function () {
|
||||
it('decreases the widths of the korean char, the double width char and the half width char', async function () {
|
||||
originalDefaultCharWidth = editor.getDefaultCharWidth()
|
||||
koreanDefaultCharWidth = editor.getKoreanCharWidth()
|
||||
doubleWidthDefaultCharWidth = editor.getDoubleWidthCharWidth()
|
||||
halfWidthDefaultCharWidth = editor.getHalfWidthCharWidth()
|
||||
component.setFontSize(10)
|
||||
await nextViewUpdatePromise()
|
||||
expect(editor.getDefaultCharWidth()).toBeCloseTo(6, 0)
|
||||
expect(editor.getKoreanCharWidth()).toBeCloseTo(9, 0)
|
||||
expect(editor.getDoubleWidthCharWidth()).toBe(10)
|
||||
expect(editor.getHalfWidthCharWidth()).toBe(5)
|
||||
expect(editor.getDefaultCharWidth()).toBeLessThan(originalDefaultCharWidth)
|
||||
expect(editor.getKoreanCharWidth()).toBeLessThan(koreanDefaultCharWidth)
|
||||
expect(editor.getDoubleWidthCharWidth()).toBeLessThan(doubleWidthDefaultCharWidth)
|
||||
expect(editor.getHalfWidthCharWidth()).toBeLessThan(halfWidthDefaultCharWidth)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when increasing the fontSize', function() {
|
||||
it('increases the widths of the korean char, the double width char and the half width char', async function () {
|
||||
originalDefaultCharWidth = editor.getDefaultCharWidth()
|
||||
koreanDefaultCharWidth = editor.getKoreanCharWidth()
|
||||
doubleWidthDefaultCharWidth = editor.getDoubleWidthCharWidth()
|
||||
halfWidthDefaultCharWidth = editor.getHalfWidthCharWidth()
|
||||
component.setFontSize(25)
|
||||
await nextViewUpdatePromise()
|
||||
expect(editor.getDefaultCharWidth()).toBeGreaterThan(originalDefaultCharWidth)
|
||||
expect(editor.getKoreanCharWidth()).toBeGreaterThan(koreanDefaultCharWidth)
|
||||
expect(editor.getDoubleWidthCharWidth()).toBeGreaterThan(doubleWidthDefaultCharWidth)
|
||||
expect(editor.getHalfWidthCharWidth()).toBeGreaterThan(halfWidthDefaultCharWidth)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4932,7 +5058,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
function lineNumberForBufferRowHasClass (bufferRow, klass) {
|
||||
let screenRow
|
||||
screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
|
||||
screenRow = editor.screenRowForBufferRow(bufferRow)
|
||||
return component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
}
|
||||
|
||||
|
||||
@@ -1143,53 +1143,6 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> presenter.setScrollLeft(-300)
|
||||
expect(getState(presenter).content.scrollLeft).toBe 0
|
||||
|
||||
describe ".indentGuidesVisible", ->
|
||||
it "is initialized based on the editor.showIndentGuide config setting", ->
|
||||
presenter = buildPresenter()
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
|
||||
atom.config.set('editor.showIndentGuide', true)
|
||||
presenter = buildPresenter()
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe true
|
||||
|
||||
it "updates when the editor.showIndentGuide config setting changes", ->
|
||||
presenter = buildPresenter()
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set('editor.showIndentGuide', true)
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe true
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set('editor.showIndentGuide', false)
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
|
||||
it "updates when the editor's grammar changes", ->
|
||||
atom.config.set('editor.showIndentGuide', true, scopeSelector: ".source.js")
|
||||
|
||||
presenter = buildPresenter()
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
|
||||
stateUpdated = false
|
||||
presenter.onDidUpdateState -> stateUpdated = true
|
||||
|
||||
waitsForPromise -> atom.packages.activatePackage('language-javascript')
|
||||
|
||||
runs ->
|
||||
expect(stateUpdated).toBe true
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe true
|
||||
|
||||
expectStateUpdate presenter, -> editor.setGrammar(atom.grammars.selectGrammar('.txt'))
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
|
||||
it "is always false when the editor is mini", ->
|
||||
atom.config.set('editor.showIndentGuide', true)
|
||||
editor.setMini(true)
|
||||
presenter = buildPresenter()
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
editor.setMini(false)
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe true
|
||||
editor.setMini(true)
|
||||
expect(getState(presenter).content.indentGuidesVisible).toBe false
|
||||
|
||||
describe ".backgroundColor", ->
|
||||
it "is assigned to ::backgroundColor unless the editor is mini", ->
|
||||
presenter = buildPresenter()
|
||||
@@ -1229,9 +1182,19 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
describe ".tiles", ->
|
||||
lineStateForScreenRow = (presenter, row) ->
|
||||
lineId = presenter.model.tokenizedLineForScreenRow(row).id
|
||||
tileRow = presenter.tileForRow(row)
|
||||
getState(presenter).content.tiles[tileRow]?.lines[lineId]
|
||||
tilesState = getState(presenter).content.tiles
|
||||
lineId = presenter.linesByScreenRow.get(row)?.id
|
||||
tilesState[presenter.tileForRow(row)]?.lines[lineId]
|
||||
|
||||
tagsForCodes = (presenter, tagCodes) ->
|
||||
openTags = []
|
||||
closeTags = []
|
||||
for tagCode in tagCodes when tagCode < 0 # skip text codes
|
||||
if presenter.isOpenTagCode(tagCode)
|
||||
openTags.push(presenter.tagForCode(tagCode))
|
||||
else
|
||||
closeTags.push(presenter.tagForCode(tagCode))
|
||||
{openTags, closeTags}
|
||||
|
||||
tiledContentContract (presenter) -> getState(presenter).content
|
||||
|
||||
@@ -1241,73 +1204,12 @@ describe "TextEditorPresenter", ->
|
||||
presenter.setExplicitHeight(3)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
|
||||
line3 = editor.tokenizedLineForScreenRow(3)
|
||||
expectValues lineStateForScreenRow(presenter, 3), {
|
||||
screenRow: 3
|
||||
text: line3.text
|
||||
tags: line3.tags
|
||||
specialTokens: line3.specialTokens
|
||||
firstNonWhitespaceIndex: line3.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line3.firstTrailingWhitespaceIndex
|
||||
invisibles: line3.invisibles
|
||||
}
|
||||
|
||||
line4 = editor.tokenizedLineForScreenRow(4)
|
||||
expectValues lineStateForScreenRow(presenter, 4), {
|
||||
screenRow: 4
|
||||
text: line4.text
|
||||
tags: line4.tags
|
||||
specialTokens: line4.specialTokens
|
||||
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
|
||||
invisibles: line4.invisibles
|
||||
}
|
||||
|
||||
line5 = editor.tokenizedLineForScreenRow(5)
|
||||
expectValues lineStateForScreenRow(presenter, 5), {
|
||||
screenRow: 5
|
||||
text: line5.text
|
||||
tags: line5.tags
|
||||
specialTokens: line5.specialTokens
|
||||
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
|
||||
invisibles: line5.invisibles
|
||||
}
|
||||
|
||||
line6 = editor.tokenizedLineForScreenRow(6)
|
||||
expectValues lineStateForScreenRow(presenter, 6), {
|
||||
screenRow: 6
|
||||
text: line6.text
|
||||
tags: line6.tags
|
||||
specialTokens: line6.specialTokens
|
||||
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
|
||||
invisibles: line6.invisibles
|
||||
}
|
||||
|
||||
line7 = editor.tokenizedLineForScreenRow(7)
|
||||
expectValues lineStateForScreenRow(presenter, 7), {
|
||||
screenRow: 7
|
||||
text: line7.text
|
||||
tags: line7.tags
|
||||
specialTokens: line7.specialTokens
|
||||
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
|
||||
invisibles: line7.invisibles
|
||||
}
|
||||
|
||||
line8 = editor.tokenizedLineForScreenRow(8)
|
||||
expectValues lineStateForScreenRow(presenter, 8), {
|
||||
screenRow: 8
|
||||
text: line8.text
|
||||
tags: line8.tags
|
||||
specialTokens: line8.specialTokens
|
||||
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
|
||||
invisibles: line8.invisibles
|
||||
}
|
||||
|
||||
expectValues lineStateForScreenRow(presenter, 3), {screenRow: 3, tagCodes: editor.screenLineForScreenRow(3).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 4), {screenRow: 4, tagCodes: editor.screenLineForScreenRow(4).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 5), {screenRow: 5, tagCodes: editor.screenLineForScreenRow(5).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 6), {screenRow: 6, tagCodes: editor.screenLineForScreenRow(6).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 7), {screenRow: 7, tagCodes: editor.screenLineForScreenRow(7).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 8), {screenRow: 8, tagCodes: editor.screenLineForScreenRow(8).tagCodes}
|
||||
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
|
||||
|
||||
it "updates when the editor's content changes", ->
|
||||
@@ -1315,34 +1217,20 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n")
|
||||
|
||||
line1 = editor.tokenizedLineForScreenRow(1)
|
||||
expectValues lineStateForScreenRow(presenter, 1), {
|
||||
text: line1.text
|
||||
tags: line1.tags
|
||||
}
|
||||
|
||||
line2 = editor.tokenizedLineForScreenRow(2)
|
||||
expectValues lineStateForScreenRow(presenter, 2), {
|
||||
text: line2.text
|
||||
tags: line2.tags
|
||||
}
|
||||
|
||||
line3 = editor.tokenizedLineForScreenRow(3)
|
||||
expectValues lineStateForScreenRow(presenter, 3), {
|
||||
text: line3.text
|
||||
tags: line3.tags
|
||||
}
|
||||
expectValues lineStateForScreenRow(presenter, 1), {screenRow: 1, tagCodes: editor.screenLineForScreenRow(1).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 2), {screenRow: 2, tagCodes: editor.screenLineForScreenRow(2).tagCodes}
|
||||
expectValues lineStateForScreenRow(presenter, 3), {screenRow: 3, tagCodes: editor.screenLineForScreenRow(3).tagCodes}
|
||||
|
||||
it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", ->
|
||||
editor.setText("hello\nworld\r\n")
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10)
|
||||
expect(lineStateForScreenRow(presenter, 0).endOfLineInvisibles).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 1).endOfLineInvisibles).toBeNull()
|
||||
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 0).tagCodes).openTags).not.toContain('invisible-character eol')
|
||||
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 1).tagCodes).openTags).not.toContain('invisible-character eol')
|
||||
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10)
|
||||
expect(lineStateForScreenRow(presenter, 0).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.eol')]
|
||||
expect(lineStateForScreenRow(presenter, 1).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.cr'), atom.config.get('editor.invisibles.eol')]
|
||||
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 0).tagCodes).openTags).toContain('invisible-character eol')
|
||||
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 1).tagCodes).openTags).toContain('invisible-character eol')
|
||||
|
||||
describe ".blockDecorations", ->
|
||||
it "contains all block decorations that are present before/after a line, both initially and when decorations change", ->
|
||||
@@ -2905,12 +2793,9 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
describe ".content.tiles", ->
|
||||
lineNumberStateForScreenRow = (presenter, screenRow) ->
|
||||
editor = presenter.model
|
||||
tileRow = presenter.tileForRow(screenRow)
|
||||
line = editor.tokenizedLineForScreenRow(screenRow)
|
||||
|
||||
gutterState = getLineNumberGutterState(presenter)
|
||||
gutterState.content.tiles[tileRow]?.lineNumbers[line?.id]
|
||||
tilesState = getLineNumberGutterState(presenter).content.tiles
|
||||
line = presenter.linesByScreenRow.get(screenRow)
|
||||
tilesState[presenter.tileForRow(screenRow)]?.lineNumbers[line?.id]
|
||||
|
||||
tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content
|
||||
|
||||
@@ -2919,17 +2804,21 @@ describe "TextEditorPresenter", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setDefaultCharWidth(1)
|
||||
editor.setEditorWidthInChars(50)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 2)
|
||||
editor.setEditorWidthInChars(51)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 3)
|
||||
presenter.setScreenRowsToMeasure([9, 11])
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false}
|
||||
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false}
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 8), {screenRow: 8, bufferRow: 8, softWrapped: true}
|
||||
expect(lineNumberStateForScreenRow(presenter, 9)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 10)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 11)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 12)).toBeUndefined()
|
||||
|
||||
it "updates when the editor's content changes", ->
|
||||
editor.foldBufferRow(4)
|
||||
@@ -3184,6 +3073,29 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
|
||||
describe "when a fold spans a single soft-wrapped buffer row", ->
|
||||
it "applies the 'folded' decoration only to its initial screen row", ->
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setDefaultCharWidth(1)
|
||||
editor.setEditorWidthInChars(20)
|
||||
editor.foldBufferRange([[0, 20], [0, 22]])
|
||||
editor.foldBufferRange([[0, 10], [0, 14]])
|
||||
presenter = buildPresenter(explicitHeight: 35, scrollTop: 0, tileSize: 2)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain('folded')
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
|
||||
describe "when a fold is at the end of a soft-wrapped buffer row", ->
|
||||
it "applies the 'folded' decoration only to its initial screen row", ->
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setDefaultCharWidth(1)
|
||||
editor.setEditorWidthInChars(25)
|
||||
editor.foldBufferRow(1)
|
||||
presenter = buildPresenter(explicitHeight: 35, scrollTop: 0, tileSize: 2)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toContain('folded')
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
|
||||
describe ".foldable", ->
|
||||
it "marks line numbers at the start of a foldable region as foldable", ->
|
||||
presenter = buildPresenter()
|
||||
|
||||
@@ -10,6 +10,7 @@ describe "TextEditorRegistry", ->
|
||||
it "gets added to the list of registered editors", ->
|
||||
editor = {}
|
||||
registry.add(editor)
|
||||
expect(editor.registered).toBe true
|
||||
expect(registry.editors.size).toBe 1
|
||||
expect(registry.editors.has(editor)).toBe(true)
|
||||
|
||||
@@ -19,6 +20,16 @@ describe "TextEditorRegistry", ->
|
||||
expect(registry.editors.size).toBe 1
|
||||
disposable.dispose()
|
||||
expect(registry.editors.size).toBe 0
|
||||
expect(editor.registered).toBe false
|
||||
|
||||
it "can be removed", ->
|
||||
editor = {}
|
||||
registry.add(editor)
|
||||
expect(registry.editors.size).toBe 1
|
||||
success = registry.remove(editor)
|
||||
expect(success).toBe true
|
||||
expect(registry.editors.size).toBe 0
|
||||
expect(editor.registered).toBe false
|
||||
|
||||
describe "when the registry is observed", ->
|
||||
it "calls the callback for current and future editors until unsubscribed", ->
|
||||
|
||||
@@ -39,21 +39,19 @@ describe "TextEditor", ->
|
||||
|
||||
it "preserves the invisibles setting", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles
|
||||
|
||||
previousLineText = editor.lineTextForScreenRow(0)
|
||||
editor2 = TextEditor.deserialize(editor.serialize(), atom)
|
||||
|
||||
expect(previousInvisibles).toBeDefined()
|
||||
expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles
|
||||
expect(editor2.lineTextForScreenRow(0)).toBe(previousLineText)
|
||||
|
||||
it "updates invisibles if the settings have changed between serialization and deserialization", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
|
||||
previousLineText = editor.lineTextForScreenRow(0)
|
||||
state = editor.serialize()
|
||||
atom.config.set('editor.invisibles', eol: '?')
|
||||
editor2 = TextEditor.deserialize(state, atom)
|
||||
|
||||
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
|
||||
expect(editor2.lineTextForScreenRow(0)).not.toBe(previousLineText)
|
||||
expect(editor2.lineTextForScreenRow(0).endsWith('?')).toBe(true)
|
||||
|
||||
describe "when the editor is constructed with the largeFileMode option set to true", ->
|
||||
it "loads the editor but doesn't tokenize", ->
|
||||
@@ -64,15 +62,14 @@ describe "TextEditor", ->
|
||||
|
||||
runs ->
|
||||
buffer = editor.getBuffer()
|
||||
expect(editor.tokenizedLineForScreenRow(0).text).toBe buffer.lineForRow(0)
|
||||
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
|
||||
expect(editor.tokenizedLineForScreenRow(1).tokens.length).toBe 2 # soft tab
|
||||
expect(editor.tokenizedLineForScreenRow(12).text).toBe buffer.lineForRow(12)
|
||||
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
|
||||
expect(editor.lineTextForScreenRow(0)).toBe buffer.lineForRow(0)
|
||||
expect(editor.tokensForScreenRow(0).length).toBe 1
|
||||
expect(editor.tokensForScreenRow(1).length).toBe 2 # soft tab
|
||||
expect(editor.lineTextForScreenRow(12)).toBe buffer.lineForRow(12)
|
||||
expect(editor.getCursorScreenPosition()).toEqual [0, 0]
|
||||
editor.insertText('hey"')
|
||||
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
|
||||
expect(editor.tokenizedLineForScreenRow(1).tokens.length).toBe 2 # sof tab
|
||||
expect(editor.tokensForScreenRow(0).length).toBe 1
|
||||
expect(editor.tokensForScreenRow(1).length).toBe 2 # soft tab
|
||||
|
||||
describe ".copy()", ->
|
||||
it "returns a different edit session with the same initial state", ->
|
||||
@@ -182,17 +179,19 @@ describe "TextEditor", ->
|
||||
expect(editor1.getLongTitle()).toBe "readme \u2014 sample-theme-1"
|
||||
expect(editor2.getLongTitle()).toBe "readme \u2014 sample-theme-2"
|
||||
|
||||
it "returns '<filename> — <parent-directories>' when opened files have identical file and dir names", ->
|
||||
it "returns '<filename> — <parent-directories>' when opened files have identical file names in subdirectories", ->
|
||||
editor1 = null
|
||||
editor2 = null
|
||||
path1 = path.join('sample-theme-1', 'src', 'js')
|
||||
path2 = path.join('sample-theme-2', 'src', 'js')
|
||||
waitsForPromise ->
|
||||
atom.workspace.open(path.join('sample-theme-1', 'src', 'js', 'main.js')).then (o) ->
|
||||
atom.workspace.open(path.join(path1, 'main.js')).then (o) ->
|
||||
editor1 = o
|
||||
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) ->
|
||||
atom.workspace.open(path.join(path2, 'main.js')).then (o) ->
|
||||
editor2 = o
|
||||
runs ->
|
||||
expect(editor1.getLongTitle()).toBe "main.js \u2014 sample-theme-1/src/js"
|
||||
expect(editor2.getLongTitle()).toBe "main.js \u2014 sample-theme-2/src/js"
|
||||
expect(editor1.getLongTitle()).toBe "main.js \u2014 #{path1}"
|
||||
expect(editor2.getLongTitle()).toBe "main.js \u2014 #{path2}"
|
||||
|
||||
it "returns '<filename> — <parent-directories>' when opened files have identical file and same parent dir name", ->
|
||||
editor1 = null
|
||||
@@ -204,7 +203,7 @@ describe "TextEditor", ->
|
||||
editor2 = o
|
||||
runs ->
|
||||
expect(editor1.getLongTitle()).toBe "main.js \u2014 js"
|
||||
expect(editor2.getLongTitle()).toBe "main.js \u2014 js/plugin"
|
||||
expect(editor2.getLongTitle()).toBe "main.js \u2014 " + path.join('js', 'plugin')
|
||||
|
||||
it "notifies ::onDidChangeTitle observers when the underlying buffer path changes", ->
|
||||
observed = []
|
||||
@@ -312,7 +311,7 @@ describe "TextEditor", ->
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setDefaultCharWidth(1)
|
||||
editor.setEditorWidthInChars(50)
|
||||
editor.createFold(2, 3)
|
||||
editor.foldBufferRowRange(2, 3)
|
||||
|
||||
it "positions the cursor at the buffer position that corresponds to the given screen position", ->
|
||||
editor.setCursorScreenPosition([9, 0])
|
||||
@@ -493,7 +492,7 @@ describe "TextEditor", ->
|
||||
it "wraps to the end of the previous line", ->
|
||||
editor.setCursorScreenPosition([4, 4])
|
||||
editor.moveLeft()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [3, 50]
|
||||
expect(editor.getCursorScreenPosition()).toEqual [3, 46]
|
||||
|
||||
describe "when the cursor is on the first line", ->
|
||||
it "remains in the same position (0,0)", ->
|
||||
@@ -681,7 +680,7 @@ describe "TextEditor", ->
|
||||
editor.setCursorScreenPosition([0, 2])
|
||||
editor.moveToEndOfLine()
|
||||
cursor = editor.getLastCursor()
|
||||
expect(cursor.getScreenPosition()).toEqual [3, 4]
|
||||
expect(cursor.getScreenPosition()).toEqual [4, 4]
|
||||
|
||||
describe ".moveToFirstCharacterOfLine()", ->
|
||||
describe "when soft wrap is on", ->
|
||||
@@ -1187,14 +1186,10 @@ describe "TextEditor", ->
|
||||
cursor2 = editor.addCursorAtBufferPosition([1, 4])
|
||||
expect(cursor2.marker).toBe cursor1.marker
|
||||
|
||||
describe '.logCursorScope()', ->
|
||||
beforeEach ->
|
||||
spyOn(atom.notifications, 'addInfo')
|
||||
|
||||
it 'opens a notification', ->
|
||||
editor.logCursorScope()
|
||||
|
||||
expect(atom.notifications.addInfo).toHaveBeenCalled()
|
||||
describe '.getCursorScope()', ->
|
||||
it 'returns the current scope', ->
|
||||
descriptor = editor.getCursorScope()
|
||||
expect(descriptor.scopes).toContain('source.js')
|
||||
|
||||
describe "selection", ->
|
||||
selection = null
|
||||
@@ -1800,22 +1795,22 @@ describe "TextEditor", ->
|
||||
describe "when the 'preserveFolds' option is false (the default)", ->
|
||||
it "removes folds that contain the selections", ->
|
||||
editor.setSelectedBufferRange([[0, 0], [0, 0]])
|
||||
editor.createFold(1, 4)
|
||||
editor.createFold(2, 3)
|
||||
editor.createFold(6, 8)
|
||||
editor.createFold(10, 11)
|
||||
editor.foldBufferRowRange(1, 4)
|
||||
editor.foldBufferRowRange(2, 3)
|
||||
editor.foldBufferRowRange(6, 8)
|
||||
editor.foldBufferRowRange(10, 11)
|
||||
|
||||
editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]])
|
||||
expect(editor.tokenizedLineForScreenRow(1).fold).toBeUndefined()
|
||||
expect(editor.tokenizedLineForScreenRow(2).fold).toBeUndefined()
|
||||
expect(editor.tokenizedLineForScreenRow(6).fold).toBeUndefined()
|
||||
expect(editor.tokenizedLineForScreenRow(10).fold).toBeDefined()
|
||||
expect(editor.isFoldedAtScreenRow(1)).toBeFalsy()
|
||||
expect(editor.isFoldedAtScreenRow(2)).toBeFalsy()
|
||||
expect(editor.isFoldedAtScreenRow(6)).toBeFalsy()
|
||||
expect(editor.isFoldedAtScreenRow(10)).toBeTruthy()
|
||||
|
||||
describe "when the 'preserveFolds' option is true", ->
|
||||
it "does not remove folds that contain the selections", ->
|
||||
editor.setSelectedBufferRange([[0, 0], [0, 0]])
|
||||
editor.createFold(1, 4)
|
||||
editor.createFold(6, 8)
|
||||
editor.foldBufferRowRange(1, 4)
|
||||
editor.foldBufferRowRange(6, 8)
|
||||
editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 0], [6, 1]]], preserveFolds: true)
|
||||
expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
|
||||
@@ -2227,7 +2222,7 @@ describe "TextEditor", ->
|
||||
it "moves the line to the previous row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRange([[4, 2], [4, 9]], preserveFolds: true)
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[4, 2], [4, 9]]
|
||||
|
||||
@@ -2255,7 +2250,7 @@ describe "TextEditor", ->
|
||||
expect(editor.lineTextForBufferRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(editor.lineTextForBufferRow(9)).toBe " };"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
@@ -2293,7 +2288,7 @@ describe "TextEditor", ->
|
||||
it "moves the lines to the previous row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRange([[3, 2], [4, 9]], preserveFolds: true)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(3)).toBeFalsy()
|
||||
@@ -2321,7 +2316,7 @@ describe "TextEditor", ->
|
||||
it "moves the lines to the previous row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRange([[4, 2], [8, 9]], preserveFolds: true)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
@@ -2365,7 +2360,7 @@ describe "TextEditor", ->
|
||||
expect(editor.lineTextForBufferRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(editor.lineTextForBufferRow(9)).toBe " };"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
|
||||
@@ -2405,7 +2400,7 @@ describe "TextEditor", ->
|
||||
it "moves the lines to the previous row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRanges([
|
||||
[[2, 2], [2, 9]],
|
||||
[[4, 2], [4, 9]]
|
||||
@@ -2443,7 +2438,7 @@ describe "TextEditor", ->
|
||||
|
||||
describe "when there is a fold", ->
|
||||
it "moves all lines that spanned by a selection to preceding row, preserving all folds", ->
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
@@ -2470,8 +2465,8 @@ describe "TextEditor", ->
|
||||
|
||||
describe 'and many selections intersects folded rows', ->
|
||||
it 'moves and preserves all the folds', ->
|
||||
editor.createFold(2, 4)
|
||||
editor.createFold(7, 9)
|
||||
editor.foldBufferRowRange(2, 4)
|
||||
editor.foldBufferRowRange(7, 9)
|
||||
|
||||
editor.setSelectedBufferRanges([
|
||||
[[1, 0], [5, 4]],
|
||||
@@ -2555,7 +2550,7 @@ describe "TextEditor", ->
|
||||
it "moves the line to the following row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRange([[4, 2], [4, 9]], preserveFolds: true)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
@@ -2581,7 +2576,7 @@ describe "TextEditor", ->
|
||||
expect(editor.lineTextForBufferRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
@@ -2635,7 +2630,7 @@ describe "TextEditor", ->
|
||||
it "moves the lines to the following row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRange([[3, 2], [4, 9]], preserveFolds: true)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(3)).toBeFalsy()
|
||||
@@ -2663,7 +2658,7 @@ describe "TextEditor", ->
|
||||
it "moves the lines to the following row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRange([[4, 2], [8, 9]], preserveFolds: true)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
@@ -2693,7 +2688,7 @@ describe "TextEditor", ->
|
||||
expect(editor.lineTextForBufferRow(2)).toBe " if (items.length <= 1) return items;"
|
||||
expect(editor.lineTextForBufferRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
|
||||
@@ -2735,8 +2730,8 @@ describe "TextEditor", ->
|
||||
|
||||
describe 'and many selections intersects folded rows', ->
|
||||
it 'moves and preserves all the folds', ->
|
||||
editor.createFold(2, 4)
|
||||
editor.createFold(7, 9)
|
||||
editor.foldBufferRowRange(2, 4)
|
||||
editor.foldBufferRowRange(7, 9)
|
||||
|
||||
editor.setSelectedBufferRanges([
|
||||
[[2, 0], [2, 4]],
|
||||
@@ -2765,7 +2760,7 @@ describe "TextEditor", ->
|
||||
|
||||
describe "when there is a fold below one of the selected row", ->
|
||||
it "moves all lines spanned by a selection to the following row, preserving the fold", ->
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
@@ -2788,7 +2783,7 @@ describe "TextEditor", ->
|
||||
|
||||
describe "when there is a fold below a group of multiple selections without any lines with no selection in-between", ->
|
||||
it "moves all the lines below the fold, preserving the fold", ->
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
|
||||
@@ -2813,7 +2808,7 @@ describe "TextEditor", ->
|
||||
it "moves the lines to the previous row without breaking the fold", ->
|
||||
expect(editor.lineTextForBufferRow(4)).toBe " while(items.length > 0) {"
|
||||
|
||||
editor.createFold(4, 7)
|
||||
editor.foldBufferRowRange(4, 7)
|
||||
editor.setSelectedBufferRanges([
|
||||
[[2, 2], [2, 9]],
|
||||
[[4, 2], [4, 9]]
|
||||
@@ -2880,6 +2875,13 @@ describe "TextEditor", ->
|
||||
expect(editor.lineTextForBufferRow(1)).toBe "1"
|
||||
expect(editor.lineTextForBufferRow(2)).toBe "2"
|
||||
|
||||
describe "when the line is the last buffer row", ->
|
||||
it "doesn't move it", ->
|
||||
editor.setText("abc\ndef")
|
||||
editor.setCursorBufferPosition([1, 0])
|
||||
editor.moveLineDown()
|
||||
expect(editor.getText()).toBe("abc\ndef")
|
||||
|
||||
describe ".insertText(text)", ->
|
||||
describe "when there is a single selection", ->
|
||||
beforeEach ->
|
||||
@@ -2951,10 +2953,10 @@ describe "TextEditor", ->
|
||||
|
||||
describe "when there is a selection that ends on a folded line", ->
|
||||
it "destroys the selection", ->
|
||||
editor.createFold(2, 4)
|
||||
editor.foldBufferRowRange(2, 4)
|
||||
editor.setSelectedBufferRange([[1, 0], [2, 0]])
|
||||
editor.insertText('holy cow')
|
||||
expect(editor.tokenizedLineForScreenRow(2).fold).toBeUndefined()
|
||||
expect(editor.isFoldedAtScreenRow(2)).toBeFalsy()
|
||||
|
||||
describe "when there are ::onWillInsertText and ::onDidInsertText observers", ->
|
||||
beforeEach ->
|
||||
@@ -3176,7 +3178,7 @@ describe "TextEditor", ->
|
||||
expect(editor.indentationForBufferRow(0)).toBe 1
|
||||
expect(editor.indentationForBufferRow(1)).toBe 1
|
||||
|
||||
it "indents the new line to the correct level when editor.autoIndent is true and using a off-side rule language", ->
|
||||
it "indents the new line to the correct level when editor.autoIndent is true and using an off-side rule language", ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
@@ -3248,15 +3250,14 @@ describe "TextEditor", ->
|
||||
editor.setCursorScreenPosition(row: 0, column: 0)
|
||||
editor.backspace()
|
||||
|
||||
describe "when the cursor is on the first column of a line below a fold", ->
|
||||
it "deletes the folded lines", ->
|
||||
editor.setCursorScreenPosition([4, 0])
|
||||
editor.foldCurrentRow()
|
||||
editor.setCursorScreenPosition([5, 0])
|
||||
describe "when the cursor is after a fold", ->
|
||||
it "deletes the folded range", ->
|
||||
editor.foldBufferRange([[4, 7], [5, 8]])
|
||||
editor.setCursorBufferPosition([5, 8])
|
||||
editor.backspace()
|
||||
|
||||
expect(buffer.lineForRow(4)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(buffer.lineForRow(4).fold).toBeUndefined()
|
||||
expect(buffer.lineForRow(4)).toBe " whirrent = items.shift();"
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBe(false)
|
||||
|
||||
describe "when the cursor is in the middle of a line below a fold", ->
|
||||
it "backspaces as normal", ->
|
||||
@@ -3269,14 +3270,13 @@ describe "TextEditor", ->
|
||||
expect(buffer.lineForRow(8)).toBe " eturn sort(left).concat(pivot).concat(sort(right));"
|
||||
|
||||
describe "when the cursor is on a folded screen line", ->
|
||||
it "deletes all of the folded lines along with the fold", ->
|
||||
it "deletes the contents of the fold before the cursor", ->
|
||||
editor.setCursorBufferPosition([3, 0])
|
||||
editor.foldCurrentRow()
|
||||
editor.backspace()
|
||||
|
||||
expect(buffer.lineForRow(1)).toBe ""
|
||||
expect(buffer.lineForRow(2)).toBe " return sort(Array.apply(this, arguments));"
|
||||
expect(editor.getCursorScreenPosition()).toEqual [1, 0]
|
||||
expect(buffer.lineForRow(1)).toBe " var sort = function(items) var pivot = items.shift(), current, left = [], right = [];"
|
||||
expect(editor.getCursorScreenPosition()).toEqual [1, 29]
|
||||
|
||||
describe "when there are multiple cursors", ->
|
||||
describe "when cursors are on the same line", ->
|
||||
@@ -3343,7 +3343,7 @@ describe "TextEditor", ->
|
||||
editor.backspace()
|
||||
|
||||
expect(buffer.lineForRow(3)).toBe " while(items.length > 0) {"
|
||||
expect(editor.tokenizedLineForScreenRow(3).fold).toBeDefined()
|
||||
expect(editor.isFoldedAtScreenRow(3)).toBe(true)
|
||||
|
||||
describe "when there are multiple selections", ->
|
||||
it "removes all selected text", ->
|
||||
@@ -3516,16 +3516,16 @@ describe "TextEditor", ->
|
||||
editor.delete()
|
||||
expect(buffer.lineForRow(12)).toBe '};'
|
||||
|
||||
describe "when the cursor is on the end of a line above a fold", ->
|
||||
describe "when the cursor is before a fold", ->
|
||||
it "only deletes the lines inside the fold", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setCursorScreenPosition([3, Infinity])
|
||||
editor.foldBufferRange([[3, 6], [4, 8]])
|
||||
editor.setCursorScreenPosition([3, 6])
|
||||
cursorPositionBefore = editor.getCursorScreenPosition()
|
||||
|
||||
editor.delete()
|
||||
|
||||
expect(buffer.lineForRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
|
||||
expect(buffer.lineForRow(4)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(buffer.lineForRow(3)).toBe " vae(items.length > 0) {"
|
||||
expect(buffer.lineForRow(4)).toBe " current = items.shift();"
|
||||
expect(editor.getCursorScreenPosition()).toEqual cursorPositionBefore
|
||||
|
||||
describe "when the cursor is in the middle a line above a fold", ->
|
||||
@@ -3537,20 +3537,21 @@ describe "TextEditor", ->
|
||||
editor.delete()
|
||||
|
||||
expect(buffer.lineForRow(3)).toBe " ar pivot = items.shift(), current, left = [], right = [];"
|
||||
expect(editor.tokenizedLineForScreenRow(4).fold).toBeDefined()
|
||||
expect(editor.isFoldedAtScreenRow(4)).toBe(true)
|
||||
expect(editor.getCursorScreenPosition()).toEqual [3, 4]
|
||||
|
||||
describe "when the cursor is on a folded line", ->
|
||||
it "removes the lines contained by the fold", ->
|
||||
editor.setSelectedBufferRange([[2, 0], [2, 0]])
|
||||
editor.createFold(2, 4)
|
||||
editor.createFold(2, 6)
|
||||
oldLine7 = buffer.lineForRow(7)
|
||||
oldLine8 = buffer.lineForRow(8)
|
||||
describe "when the cursor is inside a fold", ->
|
||||
it "removes the folded content after the cursor", ->
|
||||
editor.foldBufferRange([[2, 6], [6, 21]])
|
||||
editor.setCursorBufferPosition([4, 9])
|
||||
|
||||
editor.delete()
|
||||
expect(editor.tokenizedLineForScreenRow(2).text).toBe oldLine7
|
||||
expect(editor.tokenizedLineForScreenRow(3).text).toBe oldLine8
|
||||
|
||||
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;'
|
||||
expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
|
||||
expect(buffer.lineForRow(4)).toBe ' while ? left.push(current) : right.push(current);'
|
||||
expect(buffer.lineForRow(5)).toBe ' }'
|
||||
expect(editor.getCursorBufferPosition()).toEqual [4, 9]
|
||||
|
||||
describe "when there are multiple cursors", ->
|
||||
describe "when cursors are on the same line", ->
|
||||
@@ -3807,10 +3808,10 @@ describe "TextEditor", ->
|
||||
it "cuts up to the end of the line", ->
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setDefaultCharWidth(1)
|
||||
editor.setEditorWidthInChars(10)
|
||||
editor.setCursorScreenPosition([2, 2])
|
||||
editor.setEditorWidthInChars(25)
|
||||
editor.setCursorScreenPosition([2, 6])
|
||||
editor.cutToEndOfLine()
|
||||
expect(editor.tokenizedLineForScreenRow(2).text).toBe '= () {'
|
||||
expect(editor.lineTextForScreenRow(2)).toBe ' var function(items) {'
|
||||
|
||||
describe "when soft wrap is off", ->
|
||||
describe "when nothing is selected", ->
|
||||
@@ -4561,11 +4562,142 @@ describe "TextEditor", ->
|
||||
expect(cursor1.getBufferPosition()).toEqual [0, 0]
|
||||
expect(cursor3.getBufferPosition()).toEqual [1, 2]
|
||||
|
||||
describe ".moveSelectionLeft()", ->
|
||||
it "moves one active selection on one line one column to the left", ->
|
||||
editor.setSelectedBufferRange [[0, 4], [0, 13]]
|
||||
expect(editor.getSelectedText()).toBe 'quicksort'
|
||||
|
||||
editor.moveSelectionLeft()
|
||||
|
||||
expect(editor.getSelectedText()).toBe 'quicksort'
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0, 3], [0, 12]]
|
||||
|
||||
it "moves multiple active selections on one line one column to the left", ->
|
||||
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'function'
|
||||
|
||||
editor.moveSelectionLeft()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'function'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 3], [0, 12]], [[0, 15], [0, 23]]]
|
||||
|
||||
it "moves multiple active selections on multiple lines one column to the left", ->
|
||||
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'sort'
|
||||
|
||||
editor.moveSelectionLeft()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'sort'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 3], [0, 12]], [[1, 5], [1, 9]]]
|
||||
|
||||
describe "when a selection is at the first column of a line", ->
|
||||
it "does not change the selection", ->
|
||||
editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[1, 0], [1, 3]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'var'
|
||||
expect(selections[1].getText()).toBe ' v'
|
||||
|
||||
editor.moveSelectionLeft()
|
||||
editor.moveSelectionLeft()
|
||||
|
||||
expect(selections[0].getText()).toBe 'var'
|
||||
expect(selections[1].getText()).toBe ' v'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]], [[1, 0], [1, 3]]]
|
||||
|
||||
describe "when multiple selections are active on one line", ->
|
||||
it "does not change the selection", ->
|
||||
editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[0, 4], [0, 13]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'var'
|
||||
expect(selections[1].getText()).toBe 'quicksort'
|
||||
|
||||
editor.moveSelectionLeft()
|
||||
|
||||
expect(selections[0].getText()).toBe 'var'
|
||||
expect(selections[1].getText()).toBe 'quicksort'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]], [[0, 4], [0, 13]]]
|
||||
|
||||
describe ".moveSelectionRight()", ->
|
||||
it "moves one active selection on one line one column to the right", ->
|
||||
editor.setSelectedBufferRange [[0, 4], [0, 13]]
|
||||
expect(editor.getSelectedText()).toBe 'quicksort'
|
||||
|
||||
editor.moveSelectionRight()
|
||||
|
||||
expect(editor.getSelectedText()).toBe 'quicksort'
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0, 5], [0, 14]]
|
||||
|
||||
it "moves multiple active selections on one line one column to the right", ->
|
||||
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'function'
|
||||
|
||||
editor.moveSelectionRight()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'function'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 5], [0, 14]], [[0, 17], [0, 25]]]
|
||||
|
||||
it "moves multiple active selections on multiple lines one column to the right", ->
|
||||
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'sort'
|
||||
|
||||
editor.moveSelectionRight()
|
||||
|
||||
expect(selections[0].getText()).toBe 'quicksort'
|
||||
expect(selections[1].getText()).toBe 'sort'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 5], [0, 14]], [[1, 7], [1, 11]]]
|
||||
|
||||
describe "when a selection is at the last column of a line", ->
|
||||
it "does not change the selection", ->
|
||||
editor.setSelectedBufferRanges([[[2, 34], [2, 40]], [[5, 22], [5, 30]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'items;'
|
||||
expect(selections[1].getText()).toBe 'shift();'
|
||||
|
||||
editor.moveSelectionRight()
|
||||
editor.moveSelectionRight()
|
||||
|
||||
expect(selections[0].getText()).toBe 'items;'
|
||||
expect(selections[1].getText()).toBe 'shift();'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[2, 34], [2, 40]], [[5, 22], [5, 30]]]
|
||||
|
||||
describe "when multiple selections are active on one line", ->
|
||||
it "does not change the selection", ->
|
||||
editor.setSelectedBufferRanges([[[2, 27], [2, 33]], [[2, 34], [2, 40]]])
|
||||
selections = editor.getSelections()
|
||||
|
||||
expect(selections[0].getText()).toBe 'return'
|
||||
expect(selections[1].getText()).toBe 'items;'
|
||||
|
||||
editor.moveSelectionRight()
|
||||
|
||||
expect(selections[0].getText()).toBe 'return'
|
||||
expect(selections[1].getText()).toBe 'items;'
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[2, 27], [2, 33]], [[2, 34], [2, 40]]]
|
||||
|
||||
describe 'reading text', ->
|
||||
it '.lineTextForScreenRow(row)', ->
|
||||
editor.foldBufferRow(4)
|
||||
expect(editor.lineTextForScreenRow(5)).toEqual ' return sort(left).concat(pivot).concat(sort(right));'
|
||||
expect(editor.lineTextForScreenRow(100)).not.toBeDefined()
|
||||
expect(editor.lineTextForScreenRow(9)).toEqual '};'
|
||||
expect(editor.lineTextForScreenRow(10)).toBeUndefined()
|
||||
|
||||
describe ".deleteLine()", ->
|
||||
it "deletes the first line when the cursor is there", ->
|
||||
@@ -4922,11 +5054,13 @@ describe "TextEditor", ->
|
||||
|
||||
it 'retokenizes when the tab length is updated via .setTabLength()', ->
|
||||
expect(editor.getTabLength()).toBe 2
|
||||
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 2
|
||||
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
|
||||
expect(leadingWhitespaceTokens.length).toBe(3)
|
||||
|
||||
editor.setTabLength(6)
|
||||
expect(editor.getTabLength()).toBe 6
|
||||
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 6
|
||||
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
|
||||
expect(leadingWhitespaceTokens.length).toBe(1)
|
||||
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
editor.onDidChange(changeHandler)
|
||||
@@ -4935,21 +5069,25 @@ describe "TextEditor", ->
|
||||
|
||||
it 'retokenizes when the editor.tabLength setting is updated', ->
|
||||
expect(editor.getTabLength()).toBe 2
|
||||
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 2
|
||||
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
|
||||
expect(leadingWhitespaceTokens.length).toBe(3)
|
||||
|
||||
atom.config.set 'editor.tabLength', 6, scopeSelector: '.source.js'
|
||||
expect(editor.getTabLength()).toBe 6
|
||||
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 6
|
||||
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
|
||||
expect(leadingWhitespaceTokens.length).toBe(1)
|
||||
|
||||
it 'updates the tab length when the grammar changes', ->
|
||||
atom.config.set 'editor.tabLength', 6, scopeSelector: '.source.coffee'
|
||||
|
||||
expect(editor.getTabLength()).toBe 2
|
||||
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 2
|
||||
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
|
||||
expect(leadingWhitespaceTokens.length).toBe(3)
|
||||
|
||||
editor.setGrammar(coffeeEditor.getGrammar())
|
||||
expect(editor.getTabLength()).toBe 6
|
||||
expect(editor.tokenizedLineForScreenRow(5).tokens[0].firstNonWhitespaceIndex).toBe 6
|
||||
leadingWhitespaceTokens = editor.tokensForScreenRow(5).filter (token) -> token is 'leading-whitespace'
|
||||
expect(leadingWhitespaceTokens.length).toBe(1)
|
||||
|
||||
describe ".indentLevelForLine(line)", ->
|
||||
it "returns the indent level when the line has only leading whitespace", ->
|
||||
@@ -4985,11 +5123,11 @@ describe "TextEditor", ->
|
||||
|
||||
runs ->
|
||||
expect(editor.getGrammar()).toBe atom.grammars.nullGrammar
|
||||
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBe 1
|
||||
expect(editor.tokensForScreenRow(0).length).toBe(1)
|
||||
|
||||
atom.grammars.addGrammar(jsGrammar)
|
||||
expect(editor.getGrammar()).toBe jsGrammar
|
||||
expect(editor.tokenizedLineForScreenRow(0).tokens.length).toBeGreaterThan 1
|
||||
expect(editor.tokensForScreenRow(0).length).toBeGreaterThan 1
|
||||
|
||||
describe "editor.autoIndent", ->
|
||||
describe "when editor.autoIndent is false (default)", ->
|
||||
@@ -5025,7 +5163,7 @@ describe "TextEditor", ->
|
||||
expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
|
||||
|
||||
describe "when the line preceding the newline does't add a level of indentation", ->
|
||||
it "indents the new line to the same level a as the preceding line", ->
|
||||
it "indents the new line to the same level as the preceding line", ->
|
||||
editor.setCursorBufferPosition([5, 14])
|
||||
editor.insertText('\n')
|
||||
expect(editor.indentationForBufferRow(6)).toBe editor.indentationForBufferRow(5)
|
||||
@@ -5130,10 +5268,34 @@ describe "TextEditor", ->
|
||||
coffeeEditor.insertText("\n")
|
||||
expect(coffeeEditor.lineTextForBufferRow(2)).toBe ""
|
||||
|
||||
describe "editor.atomicSoftTabs", ->
|
||||
it "skips tab-length runs of leading whitespace when moving the cursor", ->
|
||||
atom.config.set('editor.tabLength', 4)
|
||||
|
||||
atom.config.set('editor.atomicSoftTabs', true)
|
||||
editor.setCursorScreenPosition([2, 3])
|
||||
expect(editor.getCursorScreenPosition()).toEqual [2, 4]
|
||||
|
||||
atom.config.set('editor.atomicSoftTabs', false)
|
||||
editor.setCursorScreenPosition([2, 3])
|
||||
expect(editor.getCursorScreenPosition()).toEqual [2, 3]
|
||||
|
||||
atom.config.set('editor.atomicSoftTabs', true)
|
||||
editor.setCursorScreenPosition([2, 3])
|
||||
expect(editor.getCursorScreenPosition()).toEqual [2, 4]
|
||||
|
||||
atom.config.set('editor.atomicSoftTabs', false, scopeSelector: '.source.foo')
|
||||
editor.setCursorScreenPosition([2, 3])
|
||||
expect(editor.getCursorScreenPosition()).toEqual [2, 4]
|
||||
|
||||
atom.config.set('editor.atomicSoftTabs', false, scopeSelector: '.source.js')
|
||||
editor.setCursorScreenPosition([2, 3])
|
||||
expect(editor.getCursorScreenPosition()).toEqual [2, 3]
|
||||
|
||||
describe ".destroy()", ->
|
||||
it "destroys marker layers associated with the text editor", ->
|
||||
selectionsMarkerLayerId = editor.selectionsMarkerLayer.id
|
||||
foldsMarkerLayerId = editor.displayBuffer.foldsMarkerLayer.id
|
||||
foldsMarkerLayerId = editor.displayLayer.foldsMarkerLayer.id
|
||||
editor.destroy()
|
||||
expect(buffer.getMarkerLayer(selectionsMarkerLayerId)).toBeUndefined()
|
||||
expect(buffer.getMarkerLayer(foldsMarkerLayerId)).toBeUndefined()
|
||||
@@ -5217,10 +5379,10 @@ describe "TextEditor", ->
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[3, 5], [3, 5]], [[9, 0], [14, 0]]]
|
||||
|
||||
# folds are also duplicated
|
||||
expect(editor.tokenizedLineForScreenRow(5).fold).toBeDefined()
|
||||
expect(editor.tokenizedLineForScreenRow(7).fold).toBeDefined()
|
||||
expect(editor.tokenizedLineForScreenRow(7).text).toBe " while(items.length > 0) {"
|
||||
expect(editor.tokenizedLineForScreenRow(8).text).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
expect(editor.isFoldedAtScreenRow(5)).toBe(true)
|
||||
expect(editor.isFoldedAtScreenRow(7)).toBe(true)
|
||||
expect(editor.lineTextForScreenRow(7)).toBe " while(items.length > 0) {" + editor.displayLayer.foldCharacter
|
||||
expect(editor.lineTextForScreenRow(8)).toBe " return sort(left).concat(pivot).concat(sort(right));"
|
||||
|
||||
it "duplicates all folded lines for empty selections on folded lines", ->
|
||||
editor.foldBufferRow(4)
|
||||
@@ -5416,17 +5578,15 @@ describe "TextEditor", ->
|
||||
runs ->
|
||||
editor.setText("// http://github.com")
|
||||
|
||||
{tokens} = editor.tokenizedLineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " http://github.com"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
tokens = editor.tokensForScreenRow(0)
|
||||
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-hyperlink')
|
||||
|
||||
runs ->
|
||||
{tokens} = editor.tokenizedLineForScreenRow(0)
|
||||
expect(tokens[2].value).toBe "http://github.com"
|
||||
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
|
||||
tokens = editor.tokensForScreenRow(0)
|
||||
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js', 'markup.underline.link.http.hyperlink']
|
||||
|
||||
describe "when the grammar is updated", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
@@ -5436,25 +5596,22 @@ describe "TextEditor", ->
|
||||
runs ->
|
||||
editor.setText("// SELECT * FROM OCTOCATS")
|
||||
|
||||
{tokens} = editor.tokenizedLineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
tokens = editor.tokensForScreenRow(0)
|
||||
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-injection-selector')
|
||||
|
||||
runs ->
|
||||
{tokens} = editor.tokenizedLineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
tokens = editor.tokensForScreenRow(0)
|
||||
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-sql')
|
||||
|
||||
runs ->
|
||||
{tokens} = editor.tokenizedLineForScreenRow(0)
|
||||
expect(tokens[2].value).toBe "SELECT"
|
||||
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
|
||||
tokens = editor.tokensForScreenRow(0)
|
||||
expect(tokens).toEqual ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js', 'keyword.other.DML.sql', 'keyword.operator.star.sql', 'keyword.other.DML.sql']
|
||||
|
||||
describe ".normalizeTabsInBufferRange()", ->
|
||||
it "normalizes tabs depending on the editor's soft tab/tab length settings", ->
|
||||
@@ -5582,6 +5739,19 @@ describe "TextEditor", ->
|
||||
expect(editor.getFirstVisibleScreenRow()).toEqual 89
|
||||
expect(editor.getVisibleRowRange()).toEqual [89, 99]
|
||||
|
||||
describe "::scrollToScreenPosition(position, [options])", ->
|
||||
it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", ->
|
||||
scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll")
|
||||
editor.onDidRequestAutoscroll(scrollSpy)
|
||||
|
||||
editor.scrollToScreenPosition([8, 20])
|
||||
editor.scrollToScreenPosition([8, 20], center: true)
|
||||
editor.scrollToScreenPosition([8, 20], center: false, reversed: true)
|
||||
|
||||
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {})
|
||||
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true})
|
||||
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true})
|
||||
|
||||
describe '.get/setPlaceholderText()', ->
|
||||
it 'can be created with placeholderText', ->
|
||||
newEditor = atom.workspace.buildTextEditor(
|
||||
@@ -5599,28 +5769,6 @@ describe "TextEditor", ->
|
||||
expect(handler).toHaveBeenCalledWith 'OK'
|
||||
expect(editor.getPlaceholderText()).toBe 'OK'
|
||||
|
||||
describe ".checkoutHeadRevision()", ->
|
||||
it "reverts to the version of its file checked into the project repository", ->
|
||||
atom.config.set("editor.confirmCheckoutHeadRevision", false)
|
||||
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertText("---\n")
|
||||
expect(editor.lineTextForBufferRow(0)).toBe "---"
|
||||
|
||||
waitsForPromise ->
|
||||
editor.checkoutHeadRevision()
|
||||
|
||||
runs ->
|
||||
expect(editor.lineTextForBufferRow(0)).toBe "var quicksort = function () {"
|
||||
|
||||
describe "when there's no repository for the editor's file", ->
|
||||
it "doesn't do anything", ->
|
||||
editor = atom.workspace.buildTextEditor()
|
||||
editor.setText("stuff")
|
||||
editor.checkoutHeadRevision()
|
||||
|
||||
waitsForPromise -> editor.checkoutHeadRevision()
|
||||
|
||||
describe 'gutters', ->
|
||||
describe 'the TextEditor constructor', ->
|
||||
it 'creates a line-number gutter', ->
|
||||
@@ -5749,6 +5897,7 @@ describe "TextEditor", ->
|
||||
expect(editor.decorationsStateForScreenRowRange(0, 5)[decoration.id]).toEqual {
|
||||
properties: {type: 'highlight', class: 'foo'}
|
||||
screenRange: marker.getScreenRange(),
|
||||
bufferRange: marker.getBufferRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
@@ -5769,26 +5918,31 @@ describe "TextEditor", ->
|
||||
expect(decorationState["#{layer1Decoration1.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'foo'},
|
||||
screenRange: marker1.getRange(),
|
||||
bufferRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration1.id}-#{marker2.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'foo'},
|
||||
screenRange: marker2.getRange(),
|
||||
bufferRange: marker2.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker1.getRange(),
|
||||
bufferRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker2.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker2.getRange(),
|
||||
bufferRange: marker2.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer2Decoration.id}-#{marker3.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'baz'},
|
||||
screenRange: marker3.getRange(),
|
||||
bufferRange: marker3.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
@@ -5800,16 +5954,19 @@ describe "TextEditor", ->
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker1.getRange(),
|
||||
bufferRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker2.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker2.getRange(),
|
||||
bufferRange: marker2.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer2Decoration.id}-#{marker3.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'baz'},
|
||||
screenRange: marker3.getRange(),
|
||||
bufferRange: marker3.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
@@ -5818,6 +5975,7 @@ describe "TextEditor", ->
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'quux'},
|
||||
screenRange: marker1.getRange(),
|
||||
bufferRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
@@ -5826,6 +5984,7 @@ describe "TextEditor", ->
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker1.getRange(),
|
||||
bufferRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
@@ -5837,8 +5996,21 @@ describe "TextEditor", ->
|
||||
|
||||
it "ignores invisibles even if editor.showInvisibles is true", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
invisibles = editor.tokenizedLineForScreenRow(0).invisibles
|
||||
expect(invisibles).toBe(null)
|
||||
expect(editor.lineTextForScreenRow(0).indexOf(atom.config.get('editor.invisibles.eol'))).toBe(-1)
|
||||
|
||||
describe "indent guides", ->
|
||||
it "shows indent guides when `editor.showIndentGuide` is set to true and the editor is not mini", ->
|
||||
editor.setText(" foo")
|
||||
atom.config.set('editor.tabLength', 2)
|
||||
|
||||
atom.config.set('editor.showIndentGuide', false)
|
||||
expect(editor.tokensForScreenRow(0)).toEqual ['source.js', 'leading-whitespace']
|
||||
|
||||
atom.config.set('editor.showIndentGuide', true)
|
||||
expect(editor.tokensForScreenRow(0)).toEqual ['source.js', 'leading-whitespace indent-guide']
|
||||
|
||||
editor.setMini(true)
|
||||
expect(editor.tokensForScreenRow(0)).toEqual ['source.js', 'leading-whitespace']
|
||||
|
||||
describe "when the editor is constructed with the grammar option set", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -75,22 +75,23 @@ describe 'text utilities', ->
|
||||
|
||||
expect(textUtils.isKoreanCharacter("O")).toBe(false)
|
||||
|
||||
describe ".isCJKCharacter(character)", ->
|
||||
it "returns true when the character is either a korean, half-width or double-width character", ->
|
||||
expect(textUtils.isCJKCharacter("我")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("私")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("B")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter(",")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("¢")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("ハ")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("ヒ")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("ᆲ")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("■")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("우")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("가")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("ㅢ")).toBe(true)
|
||||
expect(textUtils.isCJKCharacter("ㄼ")).toBe(true)
|
||||
describe ".isWrapBoundary(previousCharacter, character)", ->
|
||||
it "returns true when the character is CJK or when the previous character is a space/tab", ->
|
||||
anyCharacter = 'x'
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "我")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "私")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "B")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, ",")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "¢")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "ハ")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "ヒ")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "ᆲ")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "■")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "우")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "가")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "ㅢ")).toBe(true)
|
||||
expect(textUtils.isWrapBoundary(anyCharacter, "ㄼ")).toBe(true)
|
||||
|
||||
expect(textUtils.isDoubleWidthCharacter("a")).toBe(false)
|
||||
expect(textUtils.isDoubleWidthCharacter("O")).toBe(false)
|
||||
expect(textUtils.isDoubleWidthCharacter("z")).toBe(false)
|
||||
expect(textUtils.isWrapBoundary(' ', 'h')).toBe(true)
|
||||
expect(textUtils.isWrapBoundary('\t', 'h')).toBe(true)
|
||||
expect(textUtils.isWrapBoundary('a', 'h')).toBe(false)
|
||||
|
||||
@@ -175,7 +175,7 @@ describe "atom.themes", ->
|
||||
expect(styleElementAddedHandler).toHaveBeenCalled()
|
||||
|
||||
element = document.querySelector('head style[source-path*="css.css"]')
|
||||
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(cssPath)
|
||||
expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(cssPath)
|
||||
expect(element.textContent).toBe fs.readFileSync(cssPath, 'utf8')
|
||||
|
||||
# doesn't append twice
|
||||
@@ -194,7 +194,7 @@ describe "atom.themes", ->
|
||||
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
|
||||
|
||||
element = document.querySelector('head style[source-path*="sample.less"]')
|
||||
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(lessPath)
|
||||
expect(element.getAttribute('source-path')).toEqualPath atom.themes.stringToId(lessPath)
|
||||
expect(element.textContent).toBe """
|
||||
#header {
|
||||
color: #4d926f;
|
||||
@@ -213,9 +213,9 @@ describe "atom.themes", ->
|
||||
|
||||
it "supports requiring css and less stylesheets without an explicit extension", ->
|
||||
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'css')
|
||||
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
|
||||
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
|
||||
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
|
||||
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
|
||||
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toEqualPath atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
|
||||
|
||||
document.querySelector('head style[source-path*="css.css"]').remove()
|
||||
document.querySelector('head style[source-path*="sample.less"]').remove()
|
||||
|
||||
103
spec/tokenized-buffer-iterator-spec.js
Normal file
103
spec/tokenized-buffer-iterator-spec.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/** @babel */
|
||||
|
||||
import TokenizedBufferIterator from '../src/tokenized-buffer-iterator'
|
||||
import {Point} from 'text-buffer'
|
||||
|
||||
describe('TokenizedBufferIterator', () => {
|
||||
it('reports two boundaries at the same position when tags close, open, then close again without a non-negative integer separating them (regression)', () => {
|
||||
const tokenizedBuffer = {
|
||||
tokenizedLineForRow () {
|
||||
return {
|
||||
tags: [-1, -2, -1, -2],
|
||||
text: '',
|
||||
openScopes: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const grammarRegistry = {
|
||||
scopeForId () {
|
||||
return 'foo'
|
||||
}
|
||||
}
|
||||
|
||||
const iterator = new TokenizedBufferIterator(tokenizedBuffer, grammarRegistry)
|
||||
|
||||
iterator.seek(Point(0, 0))
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 0))
|
||||
expect(iterator.getCloseTags()).toEqual([])
|
||||
expect(iterator.getOpenTags()).toEqual(['foo'])
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 0))
|
||||
expect(iterator.getCloseTags()).toEqual(['foo'])
|
||||
expect(iterator.getOpenTags()).toEqual(['foo'])
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getCloseTags()).toEqual(['foo'])
|
||||
expect(iterator.getOpenTags()).toEqual([])
|
||||
})
|
||||
|
||||
it("reports a boundary at line end if the next line's open scopes don't match the containing tags for the current line", () => {
|
||||
const tokenizedBuffer = {
|
||||
tokenizedLineForRow (row) {
|
||||
if (row === 0) {
|
||||
return {
|
||||
tags: [-1, 3, -2, -3],
|
||||
text: 'bar',
|
||||
openScopes: []
|
||||
}
|
||||
} else if (row === 1) {
|
||||
return {
|
||||
tags: [3],
|
||||
text: 'baz',
|
||||
openScopes: [-1]
|
||||
}
|
||||
} else if (row === 2) {
|
||||
return {
|
||||
tags: [-2],
|
||||
text: '',
|
||||
openScopes: [-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const grammarRegistry = {
|
||||
scopeForId (id) {
|
||||
if (id === -2 || id === -1) {
|
||||
return 'foo'
|
||||
} else if (id === -3) {
|
||||
return 'qux'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const iterator = new TokenizedBufferIterator(tokenizedBuffer, grammarRegistry)
|
||||
|
||||
iterator.seek(Point(0, 0))
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 0))
|
||||
expect(iterator.getCloseTags()).toEqual([])
|
||||
expect(iterator.getOpenTags()).toEqual(['foo'])
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 3))
|
||||
expect(iterator.getCloseTags()).toEqual(['foo'])
|
||||
expect(iterator.getOpenTags()).toEqual(['qux'])
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 3))
|
||||
expect(iterator.getCloseTags()).toEqual(['qux'])
|
||||
expect(iterator.getOpenTags()).toEqual([])
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition()).toEqual(Point(1, 0))
|
||||
expect(iterator.getCloseTags()).toEqual([])
|
||||
expect(iterator.getOpenTags()).toEqual(['foo'])
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition()).toEqual(Point(2, 0))
|
||||
expect(iterator.getCloseTags()).toEqual(['foo'])
|
||||
expect(iterator.getOpenTags()).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
TextBuffer = require 'text-buffer'
|
||||
{Point} = TextBuffer = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
describe "TokenizedBuffer", ->
|
||||
@@ -134,13 +134,10 @@ describe "TokenizedBuffer", ->
|
||||
describe "on construction", ->
|
||||
it "initially creates un-tokenized screen lines, then tokenizes lines chunk at a time in the background", ->
|
||||
line0 = tokenizedBuffer.tokenizedLineForRow(0)
|
||||
expect(line0.tokens.length).toBe 1
|
||||
expect(line0.tokens[0]).toEqual(value: line0.text, scopes: ['source.js'])
|
||||
expect(line0.tokens).toEqual([value: line0.text, scopes: ['source.js']])
|
||||
|
||||
line11 = tokenizedBuffer.tokenizedLineForRow(11)
|
||||
expect(line11.tokens.length).toBe 2
|
||||
expect(line11.tokens[0]).toEqual(value: " ", scopes: ['source.js'], isAtomic: true)
|
||||
expect(line11.tokens[1]).toEqual(value: "return sort(Array.apply(this, arguments));", scopes: ['source.js'])
|
||||
expect(line11.tokens).toEqual([value: " return sort(Array.apply(this, arguments));", scopes: ['source.js']])
|
||||
|
||||
# background tokenization has not begun
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack).toBeUndefined()
|
||||
@@ -236,7 +233,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js'])
|
||||
# line 2 is unchanged
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
@@ -283,9 +280,9 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
|
||||
# lines below deleted regions should be shifted upward
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
@@ -331,7 +328,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
|
||||
# previous line 3 is pushed down to become line 5
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
@@ -377,32 +374,6 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy()
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeTruthy()
|
||||
|
||||
it "tokenizes leading whitespace based on the new tab length", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].isAtomic).toBeTruthy()
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].value).toBe " "
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].isAtomic).toBeTruthy()
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].value).toBe " "
|
||||
|
||||
tokenizedBuffer.setTabLength(4)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].isAtomic).toBeTruthy()
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].value).toBe " "
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].isAtomic).toBeFalsy()
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[1].value).toBe " current "
|
||||
|
||||
it "does not tokenize whitespaces followed by combining characters as leading whitespace", ->
|
||||
buffer.setText(" \u030b")
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
{tokens} = tokenizedBuffer.tokenizedLineForRow(0)
|
||||
expect(tokens[0].value).toBe " "
|
||||
expect(tokens[0].hasLeadingWhitespace()).toBe true
|
||||
expect(tokens[1].value).toBe " "
|
||||
expect(tokens[1].hasLeadingWhitespace()).toBe true
|
||||
expect(tokens[2].value).toBe " \u030b"
|
||||
expect(tokens[2].hasLeadingWhitespace()).toBe false
|
||||
|
||||
it "does not break out soft tabs across a scope boundary", ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-gfm')
|
||||
@@ -439,133 +410,6 @@ describe "TokenizedBuffer", ->
|
||||
beforeEach ->
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
it "renders each tab as its own atomic token with a value of size tabLength", ->
|
||||
tabAsSpaces = _.multiplyString(' ', tokenizedBuffer.getTabLength())
|
||||
screenLine0 = tokenizedBuffer.tokenizedLineForRow(0)
|
||||
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
|
||||
{tokens} = screenLine0
|
||||
|
||||
expect(tokens.length).toBe 4
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[1].value).toBe " Econ 101"
|
||||
expect(tokens[2].value).toBe tabAsSpaces
|
||||
expect(tokens[2].scopes).toEqual tokens[1].scopes
|
||||
expect(tokens[2].isAtomic).toBeTruthy()
|
||||
expect(tokens[3].value).toBe ""
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
|
||||
|
||||
it "aligns the hard tabs to the correct tab stop column", ->
|
||||
buffer.setText """
|
||||
1\t2 \t3\t4
|
||||
12\t3 \t4\t5
|
||||
123\t4 \t5\t6
|
||||
"""
|
||||
|
||||
tokenizedBuffer.setTabLength(4)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 3
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 2
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
tokenizedBuffer.setTabLength(3)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 2
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 3
|
||||
|
||||
tokenizedBuffer.setTabLength(2)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 2
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
tokenizedBuffer.setTabLength(1)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
describe "when the buffer contains UTF-8 surrogate pairs", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
runs ->
|
||||
buffer = atom.project.bufferForPathSync 'sample-with-pairs.js'
|
||||
buffer.setText """
|
||||
'abc\uD835\uDF97def'
|
||||
//\uD835\uDF97xyz
|
||||
"""
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
afterEach ->
|
||||
tokenizedBuffer.destroy()
|
||||
buffer.release()
|
||||
|
||||
it "renders each UTF-8 surrogate pair as its own atomic token", ->
|
||||
screenLine0 = tokenizedBuffer.tokenizedLineForRow(0)
|
||||
expect(screenLine0.text).toBe "'abc\uD835\uDF97def'"
|
||||
{tokens} = screenLine0
|
||||
|
||||
expect(tokens.length).toBe 5
|
||||
expect(tokens[0].value).toBe "'"
|
||||
expect(tokens[1].value).toBe "abc"
|
||||
expect(tokens[2].value).toBe "\uD835\uDF97"
|
||||
expect(tokens[2].isAtomic).toBeTruthy()
|
||||
expect(tokens[3].value).toBe "def"
|
||||
expect(tokens[4].value).toBe "'"
|
||||
|
||||
screenLine1 = tokenizedBuffer.tokenizedLineForRow(1)
|
||||
expect(screenLine1.text).toBe "//\uD835\uDF97xyz"
|
||||
{tokens} = screenLine1
|
||||
|
||||
expect(tokens.length).toBe 4
|
||||
expect(tokens[0].value).toBe '//'
|
||||
expect(tokens[1].value).toBe '\uD835\uDF97'
|
||||
expect(tokens[1].value).toBeTruthy()
|
||||
expect(tokens[2].value).toBe 'xyz'
|
||||
expect(tokens[3].value).toBe ''
|
||||
|
||||
describe "when the grammar is tokenized", ->
|
||||
it "emits the `tokenized` event", ->
|
||||
editor = null
|
||||
@@ -575,7 +419,7 @@ describe "TokenizedBuffer", ->
|
||||
atom.workspace.open('sample.js').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
tokenizedBuffer.onDidTokenize tokenizedHandler
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedHandler.callCount).toBe(1)
|
||||
@@ -588,7 +432,7 @@ describe "TokenizedBuffer", ->
|
||||
atom.workspace.open('sample.js').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
tokenizedBuffer.onDidTokenize tokenizedHandler
|
||||
@@ -606,7 +450,7 @@ describe "TokenizedBuffer", ->
|
||||
atom.workspace.open('coffee.coffee').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
tokenizedBuffer.onDidTokenize tokenizedHandler
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
tokenizedHandler.reset()
|
||||
@@ -683,132 +527,7 @@ describe "TokenizedBuffer", ->
|
||||
it "returns the range covered by all contigous tokens (within a single line)", ->
|
||||
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.function', [1, 18])).toEqual [[1, 6], [1, 28]]
|
||||
|
||||
describe "when the editor.tabLength config value changes", ->
|
||||
it "updates the tab length of the tokenized lines", ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
buffer.setText('\ttest')
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
|
||||
atom.config.set('editor.tabLength', 6)
|
||||
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
|
||||
|
||||
it "does not allow the tab length to be less than 1", ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
buffer.setText('\ttest')
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
|
||||
atom.config.set('editor.tabLength', 1)
|
||||
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
|
||||
atom.config.set('editor.tabLength', 0)
|
||||
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
|
||||
|
||||
describe "when the invisibles value changes", ->
|
||||
beforeEach ->
|
||||
|
||||
it "updates the tokens with the appropriate invisible characters", ->
|
||||
buffer = new TextBuffer(text: " \t a line with tabs\tand \tspaces \t ")
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "SST Sa line with tabsTand T spacesSTS"
|
||||
# Also needs to work for copies
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).copy().text).toBe "SST Sa line with tabsTand T spacesSTS"
|
||||
|
||||
it "assigns endOfLineInvisibles to tokenized lines", ->
|
||||
buffer = new TextBuffer(text: "a line that ends in a carriage-return-line-feed \r\na line that ends in just a line-feed\na line with no ending")
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
atom.config.set("editor.invisibles", cr: 'R', eol: 'N')
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R', 'N']
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual ['N']
|
||||
|
||||
# Lines ending in soft wraps get no invisibles
|
||||
[left, right] = tokenizedBuffer.tokenizedLineForRow(0).softWrapAt(20)
|
||||
expect(left.endOfLineInvisibles).toBe null
|
||||
expect(right.endOfLineInvisibles).toEqual ['R', 'N']
|
||||
|
||||
atom.config.set("editor.invisibles", cr: 'R', eol: false)
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R']
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual []
|
||||
|
||||
describe "leading and trailing whitespace", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
it "assigns ::firstNonWhitespaceIndex on tokens that have leading whitespace", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0].firstNonWhitespaceIndex).toBe null
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].firstNonWhitespaceIndex).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[1].firstNonWhitespaceIndex).toBe null
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].firstNonWhitespaceIndex).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1].firstNonWhitespaceIndex).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2].firstNonWhitespaceIndex).toBe null
|
||||
|
||||
# The 4th token *has* leading whitespace, but isn't entirely whitespace
|
||||
buffer.insert([5, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3].firstNonWhitespaceIndex).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4].firstNonWhitespaceIndex).toBe null
|
||||
|
||||
# Lines that are *only* whitespace are not considered to have leading whitespace
|
||||
buffer.insert([10, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(10).tokens[0].firstNonWhitespaceIndex).toBe null
|
||||
|
||||
it "assigns ::firstTrailingWhitespaceIndex on tokens that have trailing whitespace", ->
|
||||
buffer.insert([0, Infinity], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[11].firstTrailingWhitespaceIndex).toBe null
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[12].firstTrailingWhitespaceIndex).toBe 0
|
||||
|
||||
# The last token *has* trailing whitespace, but isn't entirely whitespace
|
||||
buffer.setTextInRange([[2, 39], [2, 40]], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[14].firstTrailingWhitespaceIndex).toBe null
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[15].firstTrailingWhitespaceIndex).toBe 6
|
||||
|
||||
# Lines that are *only* whitespace are considered to have trailing whitespace
|
||||
buffer.insert([10, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(10).tokens[0].firstTrailingWhitespaceIndex).toBe 0
|
||||
|
||||
it "only marks trailing whitespace on the last segment of a soft-wrapped line", ->
|
||||
buffer.insert([0, Infinity], ' ')
|
||||
tokenizedLine = tokenizedBuffer.tokenizedLineForRow(0)
|
||||
[segment1, segment2] = tokenizedLine.softWrapAt(16)
|
||||
expect(segment1.tokens[5].value).toBe ' '
|
||||
expect(segment1.tokens[5].firstTrailingWhitespaceIndex).toBe null
|
||||
expect(segment2.tokens[6].value).toBe ' '
|
||||
expect(segment2.tokens[6].firstTrailingWhitespaceIndex).toBe 0
|
||||
|
||||
it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", ->
|
||||
buffer.setText(" \t a line with tabs\tand \tspaces \t ")
|
||||
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
atom.config.set("editor.invisibles", space: 'S', tab: 'T')
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
line = tokenizedBuffer.tokenizedLineForRow(0).copy()
|
||||
expect(line.tokens[0].firstNonWhitespaceIndex).toBe 2
|
||||
expect(line.tokens[line.tokens.length - 1].firstTrailingWhitespaceIndex).toBe 0
|
||||
|
||||
describe ".indentLevel on tokenized lines", ->
|
||||
describe ".indentLevelForRow(row)", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
@@ -818,43 +537,43 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when the line is non-empty", ->
|
||||
it "has an indent level based on the leading whitespace on the line", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).indentLevel).toBe 0
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).indentLevel).toBe 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(0)).toBe 0
|
||||
expect(tokenizedBuffer.indentLevelForRow(1)).toBe 1
|
||||
expect(tokenizedBuffer.indentLevelForRow(2)).toBe 2
|
||||
buffer.insert([2, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2.5
|
||||
expect(tokenizedBuffer.indentLevelForRow(2)).toBe 2.5
|
||||
|
||||
describe "when the line is empty", ->
|
||||
it "assumes the indentation level of the first non-empty line below or above if one exists", ->
|
||||
buffer.insert([12, 0], ' ')
|
||||
buffer.insert([12, Infinity], '\n\n')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(14).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(14)).toBe 2
|
||||
|
||||
buffer.insert([1, Infinity], '\n\n')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(2)).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(3)).toBe 2
|
||||
|
||||
buffer.setText('\n\n\n')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).indentLevel).toBe 0
|
||||
expect(tokenizedBuffer.indentLevelForRow(1)).toBe 0
|
||||
|
||||
describe "when the changed lines are surrounded by whitespace-only lines", ->
|
||||
it "updates the indentLevel of empty lines that precede the change", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 0
|
||||
expect(tokenizedBuffer.indentLevelForRow(12)).toBe 0
|
||||
|
||||
buffer.insert([12, 0], '\n')
|
||||
buffer.insert([13, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 1
|
||||
expect(tokenizedBuffer.indentLevelForRow(12)).toBe 1
|
||||
|
||||
it "updates empty line indent guides when the empty line is the last line", ->
|
||||
buffer.insert([12, 2], '\n')
|
||||
|
||||
# The newline and the tab need to be in two different operations to surface the bug
|
||||
buffer.insert([12, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 1
|
||||
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 1
|
||||
|
||||
buffer.insert([12, 0], ' ')
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(14)).not.toBeDefined()
|
||||
|
||||
it "updates the indentLevel of empty lines surrounding a change that inserts lines", ->
|
||||
@@ -862,24 +581,24 @@ describe "TokenizedBuffer", ->
|
||||
buffer.insert([7, 0], '\n\n')
|
||||
buffer.insert([5, 0], '\n\n')
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(11).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(5)).toBe 3
|
||||
expect(tokenizedBuffer.indentLevelForRow(6)).toBe 3
|
||||
expect(tokenizedBuffer.indentLevelForRow(9)).toBe 3
|
||||
expect(tokenizedBuffer.indentLevelForRow(10)).toBe 3
|
||||
expect(tokenizedBuffer.indentLevelForRow(11)).toBe 2
|
||||
|
||||
tokenizedBuffer.onDidChange changeHandler = jasmine.createSpy('changeHandler')
|
||||
|
||||
buffer.setTextInRange([[7, 0], [8, 65]], ' one\n two\n three\n four')
|
||||
|
||||
delete changeHandler.argsForCall[0][0].bufferChange
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: 2)
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, delta: 2)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(11).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(12).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(13).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(5)).toBe 4
|
||||
expect(tokenizedBuffer.indentLevelForRow(6)).toBe 4
|
||||
expect(tokenizedBuffer.indentLevelForRow(11)).toBe 4
|
||||
expect(tokenizedBuffer.indentLevelForRow(12)).toBe 4
|
||||
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
|
||||
|
||||
it "updates the indentLevel of empty lines surrounding a change that removes lines", ->
|
||||
# create some new lines
|
||||
@@ -891,14 +610,14 @@ describe "TokenizedBuffer", ->
|
||||
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
|
||||
|
||||
delete changeHandler.argsForCall[0][0].bufferChange
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, delta: -1)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(7).indentLevel).toBe 2 # new text
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(8).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # }
|
||||
expect(tokenizedBuffer.indentLevelForRow(5)).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(6)).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(7)).toBe 2 # new text
|
||||
expect(tokenizedBuffer.indentLevelForRow(8)).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(9)).toBe 2
|
||||
expect(tokenizedBuffer.indentLevelForRow(10)).toBe 2 # }
|
||||
|
||||
describe "::isFoldableAtRow(row)", ->
|
||||
changes = null
|
||||
@@ -1050,3 +769,107 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
runs ->
|
||||
expect(coffeeCalled).toBe true
|
||||
|
||||
describe "text decoration layer API", ->
|
||||
describe "iterator", ->
|
||||
it "iterates over the syntactic scope boundaries", ->
|
||||
buffer = new TextBuffer(text: "var foo = 1 /*\nhello*/var bar = 2\n")
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".js"))
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
iterator = tokenizedBuffer.buildIterator()
|
||||
iterator.seek(Point(0, 0))
|
||||
|
||||
expectedBoundaries = [
|
||||
{position: Point(0, 0), closeTags: [], openTags: ["source.js", "storage.type.var.js"]}
|
||||
{position: Point(0, 3), closeTags: ["storage.type.var.js"], openTags: []}
|
||||
{position: Point(0, 8), closeTags: [], openTags: ["keyword.operator.assignment.js"]}
|
||||
{position: Point(0, 9), closeTags: ["keyword.operator.assignment.js"], openTags: []}
|
||||
{position: Point(0, 10), closeTags: [], openTags: ["constant.numeric.decimal.js"]}
|
||||
{position: Point(0, 11), closeTags: ["constant.numeric.decimal.js"], openTags: []}
|
||||
{position: Point(0, 12), closeTags: [], openTags: ["comment.block.js", "punctuation.definition.comment.js"]}
|
||||
{position: Point(0, 14), closeTags: ["punctuation.definition.comment.js"], openTags: []}
|
||||
{position: Point(1, 5), closeTags: [], openTags: ["punctuation.definition.comment.js"]}
|
||||
{position: Point(1, 7), closeTags: ["punctuation.definition.comment.js", "comment.block.js"], openTags: ["storage.type.var.js"]}
|
||||
{position: Point(1, 10), closeTags: ["storage.type.var.js"], openTags: []}
|
||||
{position: Point(1, 15), closeTags: [], openTags: ["keyword.operator.assignment.js"]}
|
||||
{position: Point(1, 16), closeTags: ["keyword.operator.assignment.js"], openTags: []}
|
||||
{position: Point(1, 17), closeTags: [], openTags: ["constant.numeric.decimal.js"]}
|
||||
{position: Point(1, 18), closeTags: ["constant.numeric.decimal.js"], openTags: []}
|
||||
]
|
||||
|
||||
loop
|
||||
boundary = {
|
||||
position: iterator.getPosition(),
|
||||
closeTags: iterator.getCloseTags(),
|
||||
openTags: iterator.getOpenTags()
|
||||
}
|
||||
|
||||
expect(boundary).toEqual(expectedBoundaries.shift())
|
||||
break unless iterator.moveToSuccessor()
|
||||
|
||||
expect(iterator.seek(Point(0, 1))).toEqual(["source.js", "storage.type.var.js"])
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 3))
|
||||
expect(iterator.seek(Point(0, 8))).toEqual(["source.js"])
|
||||
expect(iterator.getPosition()).toEqual(Point(0, 8))
|
||||
expect(iterator.seek(Point(1, 0))).toEqual(["source.js", "comment.block.js"])
|
||||
expect(iterator.getPosition()).toEqual(Point(1, 5))
|
||||
expect(iterator.seek(Point(1, 18))).toEqual(["source.js", "constant.numeric.decimal.js"])
|
||||
expect(iterator.getPosition()).toEqual(Point(1, 18))
|
||||
|
||||
expect(iterator.seek(Point(2, 0))).toEqual(["source.js"])
|
||||
iterator.moveToSuccessor() # ensure we don't infinitely loop (regression test)
|
||||
|
||||
it "does not report columns beyond the length of the line", ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
runs ->
|
||||
buffer = new TextBuffer(text: "# hello\n# world")
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".coffee"))
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
iterator = tokenizedBuffer.buildIterator()
|
||||
iterator.seek(Point(0, 0))
|
||||
iterator.moveToSuccessor()
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition().column).toBe(7)
|
||||
|
||||
iterator.moveToSuccessor()
|
||||
expect(iterator.getPosition().column).toBe(0)
|
||||
|
||||
iterator.seek(Point(0, 7))
|
||||
expect(iterator.getPosition().column).toBe(7)
|
||||
|
||||
iterator.seek(Point(0, 8))
|
||||
expect(iterator.getPosition().column).toBe(7)
|
||||
|
||||
it "correctly terminates scopes at the beginning of the line (regression)", ->
|
||||
grammar = atom.grammars.createGrammar('test', {
|
||||
'scopeName': 'text.broken'
|
||||
'name': 'Broken grammar'
|
||||
'patterns': [
|
||||
{'begin': 'start', 'end': '(?=end)', 'name': 'blue.broken'},
|
||||
{'match': '.', 'name': 'yellow.broken'}
|
||||
]
|
||||
})
|
||||
|
||||
buffer = new TextBuffer(text: 'start x\nend x\nx')
|
||||
tokenizedBuffer = new TokenizedBuffer({
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
tokenizedBuffer.setGrammar(grammar)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
iterator = tokenizedBuffer.buildIterator()
|
||||
iterator.seek(Point(1, 0))
|
||||
|
||||
expect(iterator.getPosition()).toEqual([1, 0])
|
||||
expect(iterator.getCloseTags()).toEqual ['blue.broken']
|
||||
expect(iterator.getOpenTags()).toEqual ['yellow.broken']
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
describe "TokenizedLine", ->
|
||||
editor = null
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise -> atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
describe "::isOnlyWhitespace()", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('coffee.coffee').then (o) -> editor = o
|
||||
|
||||
it "returns true when the line is only whitespace", ->
|
||||
expect(editor.tokenizedLineForScreenRow(3).isOnlyWhitespace()).toBe true
|
||||
expect(editor.tokenizedLineForScreenRow(7).isOnlyWhitespace()).toBe true
|
||||
expect(editor.tokenizedLineForScreenRow(23).isOnlyWhitespace()).toBe true
|
||||
|
||||
it "returns false when the line is not only whitespace", ->
|
||||
expect(editor.tokenizedLineForScreenRow(0).isOnlyWhitespace()).toBe false
|
||||
expect(editor.tokenizedLineForScreenRow(2).isOnlyWhitespace()).toBe false
|
||||
@@ -75,7 +75,7 @@ describe "WindowEventHandler", ->
|
||||
|
||||
describe "when a link is clicked", ->
|
||||
it "opens the http/https links in an external application", ->
|
||||
shell = require 'shell'
|
||||
{shell} = require 'electron'
|
||||
spyOn(shell, 'openExternal')
|
||||
|
||||
link = document.createElement('a')
|
||||
|
||||
@@ -47,9 +47,14 @@ describe "WorkspaceElement", ->
|
||||
|
||||
it "updates the font-family based on the 'editor.fontFamily' config value", ->
|
||||
initialCharWidth = editor.getDefaultCharWidth()
|
||||
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
|
||||
fontFamily = atom.config.get('editor.fontFamily')
|
||||
fontFamily += ", 'Apple Color Emoji'" if process.platform is 'darwin'
|
||||
expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily
|
||||
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
|
||||
fontFamily = atom.config.get('editor.fontFamily')
|
||||
fontFamily += ", 'Apple Color Emoji'" if process.platform is 'darwin'
|
||||
expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily
|
||||
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
|
||||
|
||||
it "updates the line-height based on the 'editor.lineHeight' config value", ->
|
||||
|
||||
@@ -80,7 +80,8 @@ describe "Workspace", ->
|
||||
expect(untitledEditor.getText()).toBe("An untitled editor.")
|
||||
|
||||
expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath()
|
||||
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{atom.project.getPaths()[0]}///
|
||||
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
|
||||
expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{pathEscaped}///
|
||||
|
||||
describe "where there are no open panes or editors", ->
|
||||
it "constructs the view with no open editors", ->
|
||||
@@ -428,7 +429,7 @@ describe "Workspace", ->
|
||||
workspace.open('sample.js').then (e) -> editor = e
|
||||
|
||||
runs ->
|
||||
expect(editor.displayBuffer.largeFileMode).toBe true
|
||||
expect(editor.largeFileMode).toBe true
|
||||
|
||||
describe "when the file is over 20MB", ->
|
||||
it "prompts the user to make sure they want to open a file this big", ->
|
||||
@@ -453,7 +454,7 @@ describe "Workspace", ->
|
||||
|
||||
runs ->
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(editor.displayBuffer.largeFileMode).toBe true
|
||||
expect(editor.largeFileMode).toBe true
|
||||
|
||||
describe "when passed a path that matches a custom opener", ->
|
||||
it "returns the resource returned by the custom opener", ->
|
||||
@@ -833,25 +834,29 @@ describe "Workspace", ->
|
||||
describe "when there is an active pane item", ->
|
||||
it "sets the title to the pane item's title plus the project path", ->
|
||||
item = atom.workspace.getActivePaneItem()
|
||||
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
|
||||
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
|
||||
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
|
||||
|
||||
describe "when the title of the active pane item changes", ->
|
||||
it "updates the window title based on the item's new title", ->
|
||||
editor = atom.workspace.getActivePaneItem()
|
||||
editor.buffer.setPath(path.join(temp.dir, 'hi'))
|
||||
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
|
||||
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
|
||||
expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}///
|
||||
|
||||
describe "when the active pane's item changes", ->
|
||||
it "updates the title to the new item's title plus the project path", ->
|
||||
atom.workspace.getActivePane().activateNextItem()
|
||||
item = atom.workspace.getActivePaneItem()
|
||||
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
|
||||
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
|
||||
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
|
||||
|
||||
describe "when the last pane item is removed", ->
|
||||
it "updates the title to contain the project's path", ->
|
||||
atom.workspace.getActivePane().destroy()
|
||||
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
|
||||
expect(document.title).toMatch ///^#{atom.project.getPaths()[0]}///
|
||||
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
|
||||
expect(document.title).toMatch ///^#{pathEscaped}///
|
||||
|
||||
describe "when an inactive pane's item changes", ->
|
||||
it "does not update the title", ->
|
||||
@@ -875,7 +880,8 @@ describe "Workspace", ->
|
||||
})
|
||||
workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
|
||||
item = workspace2.getActivePaneItem()
|
||||
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{atom.project.getPaths()[0]}///
|
||||
pathEscaped = escapeStringRegex(atom.project.getPaths()[0])
|
||||
expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{pathEscaped}///
|
||||
workspace2.destroy()
|
||||
|
||||
describe "document edited status", ->
|
||||
@@ -1610,3 +1616,48 @@ describe "Workspace", ->
|
||||
|
||||
runs ->
|
||||
expect(pane.getPendingItem()).toBeFalsy()
|
||||
|
||||
describe "grammar activation", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
it "notifies the workspace of which grammar is used", ->
|
||||
editor = null
|
||||
|
||||
grammarUsed = jasmine.createSpy()
|
||||
atom.workspace.handleGrammarUsed = grammarUsed
|
||||
|
||||
waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
|
||||
waitsFor -> grammarUsed.callCount is 1
|
||||
runs ->
|
||||
expect(grammarUsed.argsForCall[0][0].name).toBe 'JavaScript'
|
||||
|
||||
describe ".checkoutHeadRevision()", ->
|
||||
editor = null
|
||||
beforeEach ->
|
||||
atom.config.set("editor.confirmCheckoutHeadRevision", false)
|
||||
|
||||
waitsForPromise -> atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
|
||||
|
||||
it "reverts to the version of its file checked into the project repository", ->
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertText("---\n")
|
||||
expect(editor.lineTextForBufferRow(0)).toBe "---"
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.checkoutHeadRevision(editor)
|
||||
|
||||
runs ->
|
||||
expect(editor.lineTextForBufferRow(0)).toBe ""
|
||||
|
||||
describe "when there's no repository for the editor's file", ->
|
||||
it "doesn't do anything", ->
|
||||
editor = atom.workspace.buildTextEditor()
|
||||
editor.setText("stuff")
|
||||
atom.workspace.checkoutHeadRevision(editor)
|
||||
|
||||
waitsForPromise -> atom.workspace.checkoutHeadRevision(editor)
|
||||
|
||||
escapeStringRegex = (str) ->
|
||||
str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
_ = require 'underscore-plus'
|
||||
{ipcRenderer, remote, shell, webFrame} = require 'electron'
|
||||
{screen, ipcRenderer, remote, shell, webFrame} = require 'electron'
|
||||
ipcHelpers = require './ipc-helpers'
|
||||
{Disposable} = require 'event-kit'
|
||||
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
@@ -211,6 +211,14 @@ class ApplicationDelegate
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onUpdateError: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'update-error'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onApplicationMenuCommand: (callback) ->
|
||||
outerCallback = (event, args...) ->
|
||||
callback(args...)
|
||||
@@ -233,14 +241,28 @@ class ApplicationDelegate
|
||||
openExternal: (url) ->
|
||||
shell.openExternal(url)
|
||||
|
||||
disablePinchToZoom: ->
|
||||
webFrame.setZoomLevelLimits(1, 1)
|
||||
disableZoom: ->
|
||||
outerCallback = ->
|
||||
webFrame.setZoomLevelLimits(1, 1)
|
||||
|
||||
outerCallback()
|
||||
# Set the limits every time a display is added or removed, otherwise the
|
||||
# configuration gets reset to the default, which allows zooming the
|
||||
# webframe.
|
||||
screen.on('display-added', outerCallback)
|
||||
screen.on('display-removed', outerCallback)
|
||||
new Disposable ->
|
||||
screen.removeListener('display-added', outerCallback)
|
||||
screen.removeListener('display-removed', outerCallback)
|
||||
|
||||
checkForUpdate: ->
|
||||
ipcRenderer.send('check-for-update')
|
||||
ipcRenderer.send('command', 'application:check-for-update')
|
||||
|
||||
restartAndInstallUpdate: ->
|
||||
ipcRenderer.send('install-update')
|
||||
ipcRenderer.send('command', 'application:install-update')
|
||||
|
||||
getAutoUpdateManagerState: ->
|
||||
ipcRenderer.sendSync('get-auto-update-manager-state')
|
||||
|
||||
getAutoUpdateManagerErrorMessage: ->
|
||||
ipcRenderer.sendSync('get-auto-update-manager-error')
|
||||
|
||||
@@ -11,6 +11,7 @@ Model = require './model'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
StylesElement = require './styles-element'
|
||||
StateStore = require './state-store'
|
||||
StorageFolder = require './storage-folder'
|
||||
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
registerDefaultCommands = require './register-default-commands'
|
||||
|
||||
@@ -127,7 +128,7 @@ class AtomEnvironment extends Model
|
||||
|
||||
# Call .loadOrCreate instead
|
||||
constructor: (params={}) ->
|
||||
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
|
||||
{@blobStore, @applicationDelegate, @window, @document, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
|
||||
|
||||
@unloaded = false
|
||||
@loadTime = null
|
||||
@@ -138,7 +139,9 @@ class AtomEnvironment extends Model
|
||||
|
||||
@stateStore = new StateStore('AtomEnvironments', 1)
|
||||
|
||||
@stateStore.clear() if clearWindowState
|
||||
if clearWindowState
|
||||
@getStorageFolder().clear()
|
||||
@stateStore.clear()
|
||||
|
||||
@deserializers = new DeserializerManager(this)
|
||||
@deserializeTimings = {}
|
||||
@@ -147,10 +150,10 @@ class AtomEnvironment extends Model
|
||||
|
||||
@notifications = new NotificationManager
|
||||
|
||||
@config = new Config({configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence})
|
||||
@config = new Config({@configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence})
|
||||
@setConfigSchema()
|
||||
|
||||
@keymaps = new KeymapManager({configDirPath, resourcePath, notificationManager: @notifications})
|
||||
@keymaps = new KeymapManager({@configDirPath, resourcePath, notificationManager: @notifications})
|
||||
|
||||
@tooltips = new TooltipManager(keymapManager: @keymaps)
|
||||
|
||||
@@ -159,16 +162,16 @@ class AtomEnvironment extends Model
|
||||
|
||||
@grammars = new GrammarRegistry({@config})
|
||||
|
||||
@styles = new StyleManager({configDirPath})
|
||||
@styles = new StyleManager({@configDirPath})
|
||||
|
||||
@packages = new PackageManager({
|
||||
devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
|
||||
devMode, @configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
|
||||
commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications,
|
||||
grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views
|
||||
})
|
||||
|
||||
@themes = new ThemeManager({
|
||||
packageManager: @packages, configDirPath, resourcePath, safeMode, @config,
|
||||
packageManager: @packages, @configDirPath, resourcePath, safeMode, @config,
|
||||
styleManager: @styles, notificationManager: @notifications, viewRegistry: @views
|
||||
})
|
||||
|
||||
@@ -205,7 +208,7 @@ class AtomEnvironment extends Model
|
||||
@stylesElement = @styles.buildStylesElement()
|
||||
@document.head.appendChild(@stylesElement)
|
||||
|
||||
@applicationDelegate.disablePinchToZoom()
|
||||
@disposables.add(@applicationDelegate.disableZoom())
|
||||
|
||||
@keymaps.subscribeToFileReadFailure()
|
||||
@keymaps.loadBundledKeymaps()
|
||||
@@ -231,13 +234,14 @@ class AtomEnvironment extends Model
|
||||
checkPortableHomeWritable()
|
||||
|
||||
attachSaveStateListeners: ->
|
||||
saveState = => @saveState({isUnloading: false}) unless @unloaded
|
||||
debouncedSaveState = _.debounce(saveState, @saveStateDebounceInterval)
|
||||
@document.addEventListener('mousedown', debouncedSaveState, true)
|
||||
@document.addEventListener('keydown', debouncedSaveState, true)
|
||||
saveState = _.debounce((=>
|
||||
window.requestIdleCallback => @saveState({isUnloading: false}) unless @unloaded
|
||||
), @saveStateDebounceInterval)
|
||||
@document.addEventListener('mousedown', saveState, true)
|
||||
@document.addEventListener('keydown', saveState, true)
|
||||
@disposables.add new Disposable =>
|
||||
@document.removeEventListener('mousedown', debouncedSaveState, true)
|
||||
@document.removeEventListener('keydown', debouncedSaveState, true)
|
||||
@document.removeEventListener('mousedown', saveState, true)
|
||||
@document.removeEventListener('keydown', saveState, true)
|
||||
|
||||
setConfigSchema: ->
|
||||
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
|
||||
@@ -252,7 +256,7 @@ class AtomEnvironment extends Model
|
||||
@deserializers.add(TextBuffer)
|
||||
|
||||
registerDefaultCommands: ->
|
||||
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller})
|
||||
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller, notificationManager: @notifications, @project, @clipboard})
|
||||
|
||||
registerDefaultViewProviders: ->
|
||||
@views.addViewProvider Workspace, (model, env) ->
|
||||
@@ -652,6 +656,7 @@ class AtomEnvironment extends Model
|
||||
|
||||
# Call this method when establishing a real application window.
|
||||
startEditorWindow: ->
|
||||
@unloaded = false
|
||||
@loadState().then (state) =>
|
||||
@windowDimensions = state?.windowDimensions
|
||||
@displayWindow().then =>
|
||||
@@ -785,6 +790,7 @@ class AtomEnvironment extends Model
|
||||
# Returns a {Promise} that resolves when the DevTools have been opened or
|
||||
# closed.
|
||||
toggleDevTools: ->
|
||||
require("devtron").install()
|
||||
@applicationDelegate.toggleWindowDevTools()
|
||||
|
||||
# Extended: Execute code in dev tools.
|
||||
@@ -839,21 +845,25 @@ class AtomEnvironment extends Model
|
||||
return Promise.resolve() unless @enablePersistence
|
||||
|
||||
new Promise (resolve, reject) =>
|
||||
window.requestIdleCallback =>
|
||||
return if not @project
|
||||
return if not @project
|
||||
|
||||
state = @serialize(options)
|
||||
savePromise =
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@stateStore.save(storageKey, state)
|
||||
else
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
savePromise.catch(reject).then(resolve)
|
||||
state = @serialize(options)
|
||||
savePromise =
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@stateStore.save(storageKey, state)
|
||||
else
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
savePromise.catch(reject).then(resolve)
|
||||
|
||||
loadState: ->
|
||||
if @enablePersistence
|
||||
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
|
||||
@stateStore.load(stateKey)
|
||||
@stateStore.load(stateKey).then (state) =>
|
||||
if state
|
||||
state
|
||||
else
|
||||
# TODO: remove this when every user has migrated to the IndexedDb state store.
|
||||
@getStorageFolder().load(stateKey)
|
||||
else
|
||||
@applicationDelegate.getTemporaryWindowState()
|
||||
else
|
||||
@@ -882,6 +892,9 @@ class AtomEnvironment extends Model
|
||||
else
|
||||
null
|
||||
|
||||
getStorageFolder: ->
|
||||
@storageFolder ?= new StorageFolder(@getConfigDirPath())
|
||||
|
||||
getConfigDirPath: ->
|
||||
@configDirPath ?= process.env.ATOM_HOME
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ export default class AutoUpdateManager {
|
||||
}),
|
||||
applicationDelegate.onUpdateNotAvailable(() => {
|
||||
this.emitter.emit('update-not-available')
|
||||
}),
|
||||
applicationDelegate.onUpdateError(() => {
|
||||
this.emitter.emit('update-error')
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -41,6 +44,10 @@ export default class AutoUpdateManager {
|
||||
return this.applicationDelegate.getAutoUpdateManagerState()
|
||||
}
|
||||
|
||||
getErrorMessage () {
|
||||
return this.applicationDelegate.getAutoUpdateManagerErrorMessage()
|
||||
}
|
||||
|
||||
platformSupportsUpdates () {
|
||||
return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported'
|
||||
}
|
||||
@@ -67,6 +74,10 @@ export default class AutoUpdateManager {
|
||||
return this.emitter.on('update-not-available', callback)
|
||||
}
|
||||
|
||||
onUpdateError (callback) {
|
||||
return this.emitter.on('update-error', callback)
|
||||
}
|
||||
|
||||
getPlatform () {
|
||||
return process.platform
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ ApplicationMenu = require './application-menu'
|
||||
AtomProtocolHandler = require './atom-protocol-handler'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
StorageFolder = require '../storage-folder'
|
||||
Config = require '../config'
|
||||
ipcHelpers = require '../ipc-helpers'
|
||||
{BrowserWindow, Menu, app, dialog, ipcMain, shell} = require 'electron'
|
||||
fs = require 'fs-plus'
|
||||
@@ -70,7 +71,11 @@ class AtomApplication
|
||||
@pidsToOpenWindows = {}
|
||||
@windows = []
|
||||
|
||||
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath)
|
||||
@config = new Config({configDirPath: process.env.ATOM_HOME, @resourcePath, enablePersistence: true})
|
||||
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
|
||||
@config.load()
|
||||
|
||||
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config)
|
||||
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
|
||||
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
|
||||
|
||||
@@ -133,7 +138,11 @@ class AtomApplication
|
||||
return unless @socketPath?
|
||||
@deleteSocketFile()
|
||||
server = net.createServer (connection) =>
|
||||
connection.on 'data', (data) =>
|
||||
data = ''
|
||||
connection.on 'data', (chunk) ->
|
||||
data = data + chunk
|
||||
|
||||
connection.on 'end', =>
|
||||
options = JSON.parse(data)
|
||||
@openWithOptions(options)
|
||||
|
||||
@@ -165,9 +174,6 @@ class AtomApplication
|
||||
@on 'application:quit', -> app.quit()
|
||||
@on 'application:new-window', -> @openPath(getLoadSettings())
|
||||
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
|
||||
@on 'application:open', -> @promptForPathToOpen('all', getLoadSettings())
|
||||
@on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings())
|
||||
@on 'application:open-folder', -> @promptForPathToOpen('folder', getLoadSettings())
|
||||
@on 'application:open-dev', -> @promptForPathToOpen('all', devMode: true)
|
||||
@on 'application:open-safe', -> @promptForPathToOpen('all', safeMode: true)
|
||||
@on 'application:inspect', ({x, y, atomWindow}) ->
|
||||
@@ -250,6 +256,14 @@ class AtomApplication
|
||||
ipcMain.on 'command', (event, command) =>
|
||||
@emit(command)
|
||||
|
||||
ipcMain.on 'open-command', (event, command, args...) =>
|
||||
defaultPath = args[0] if args.length > 0
|
||||
switch command
|
||||
when 'application:open' then @promptForPathToOpen('all', getLoadSettings(), defaultPath)
|
||||
when 'application:open-file' then @promptForPathToOpen('file', getLoadSettings(), defaultPath)
|
||||
when 'application:open-folder' then @promptForPathToOpen('folder', getLoadSettings(), defaultPath)
|
||||
else console.log "Invalid open-command received: " + command
|
||||
|
||||
ipcMain.on 'window-command', (event, command, args...) ->
|
||||
win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.emit(command, args...)
|
||||
@@ -305,12 +319,12 @@ class AtomApplication
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
ipcMain.on 'check-for-update', =>
|
||||
@autoUpdateManager.check()
|
||||
|
||||
ipcMain.on 'get-auto-update-manager-state', (event) =>
|
||||
event.returnValue = @autoUpdateManager.getState()
|
||||
|
||||
ipcMain.on 'get-auto-update-manager-error', (event) =>
|
||||
event.returnValue = @autoUpdateManager.getErrorMessage()
|
||||
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
@@ -461,6 +475,7 @@ class AtomApplication
|
||||
openedWindow.restore()
|
||||
else
|
||||
openedWindow.focus()
|
||||
openedWindow.replaceEnvironment(env)
|
||||
else
|
||||
if devMode
|
||||
try
|
||||
@@ -510,7 +525,8 @@ class AtomApplication
|
||||
@storageFolder.storeSync('application.json', states)
|
||||
|
||||
loadState: (options) ->
|
||||
if (states = @storageFolder.load('application.json'))?.length > 0
|
||||
restorePreviousState = @config.get('core.restorePreviousWindowsOnStart') ? true
|
||||
if restorePreviousState and (states = @storageFolder.load('application.json'))?.length > 0
|
||||
for state in states
|
||||
@openWithOptions(_.extend(options, {
|
||||
initialPaths: state.initialPaths
|
||||
@@ -646,11 +662,13 @@ class AtomApplication
|
||||
# :safeMode - A Boolean which controls whether any newly opened windows
|
||||
# should be in safe mode or not.
|
||||
# :window - An {AtomWindow} to use for opening a selected file path.
|
||||
promptForPathToOpen: (type, {devMode, safeMode, window}) ->
|
||||
@promptForPath type, (pathsToOpen) =>
|
||||
@openPaths({pathsToOpen, devMode, safeMode, window})
|
||||
# :path - An optional String which controls the default path to which the
|
||||
# file dialog opens.
|
||||
promptForPathToOpen: (type, {devMode, safeMode, window}, path=null) ->
|
||||
@promptForPath type, ((pathsToOpen) =>
|
||||
@openPaths({pathsToOpen, devMode, safeMode, window})), path
|
||||
|
||||
promptForPath: (type, callback) ->
|
||||
promptForPath: (type, callback, path) ->
|
||||
properties =
|
||||
switch type
|
||||
when 'file' then ['openFile']
|
||||
@@ -673,8 +691,8 @@ class AtomApplication
|
||||
when 'folder' then 'Open Folder'
|
||||
else 'Open'
|
||||
|
||||
if process.platform is 'linux'
|
||||
if projectPath = @lastFocusedWindow?.projectPath
|
||||
openOptions.defaultPath = projectPath
|
||||
# File dialog defaults to project directory of currently active editor
|
||||
if path?
|
||||
openOptions.defaultPath = path
|
||||
|
||||
dialog.showOpenDialog(parentWindow, openOptions, callback)
|
||||
|
||||
@@ -68,6 +68,7 @@ class AtomWindow
|
||||
@loaded = true
|
||||
|
||||
@setLoadSettings(loadSettings)
|
||||
@env = loadSettings.env if loadSettings.env?
|
||||
@browserWindow.focusOnWebView() if @isSpec
|
||||
@browserWindow.temporaryState = {windowDimensions} if windowDimensions?
|
||||
|
||||
@@ -169,6 +170,9 @@ class AtomWindow
|
||||
else
|
||||
@browserWindow.once 'window:loaded', => @openLocations(locationsToOpen)
|
||||
|
||||
replaceEnvironment: (env) ->
|
||||
@browserWindow.webContents.send 'environment', env
|
||||
|
||||
sendMessage: (message, detail) ->
|
||||
@browserWindow.webContents.send 'message', message, detail
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
autoUpdater = null
|
||||
_ = require 'underscore-plus'
|
||||
Config = require '../config'
|
||||
{EventEmitter} = require 'events'
|
||||
path = require 'path'
|
||||
|
||||
@@ -16,13 +15,10 @@ module.exports =
|
||||
class AutoUpdateManager
|
||||
_.extend @prototype, EventEmitter.prototype
|
||||
|
||||
constructor: (@version, @testMode, resourcePath) ->
|
||||
constructor: (@version, @testMode, resourcePath, @config) ->
|
||||
@state = IdleState
|
||||
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
|
||||
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
|
||||
@config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true})
|
||||
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
|
||||
@config.load()
|
||||
process.nextTick => @setupAutoUpdater()
|
||||
|
||||
setupAutoUpdater: ->
|
||||
@@ -32,7 +28,8 @@ class AutoUpdateManager
|
||||
{autoUpdater} = require 'electron'
|
||||
|
||||
autoUpdater.on 'error', (event, message) =>
|
||||
@setState(ErrorState)
|
||||
@setState(ErrorState, message)
|
||||
@emitWindowEvent('update-error')
|
||||
console.error "Error Downloading Update: #{message}"
|
||||
|
||||
autoUpdater.setFeedURL @feedUrl
|
||||
@@ -82,14 +79,18 @@ class AutoUpdateManager
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
return
|
||||
|
||||
setState: (state) ->
|
||||
setState: (state, errorMessage) ->
|
||||
return if @state is state
|
||||
@state = state
|
||||
@errorMessage = errorMessage
|
||||
@emit 'state-changed', @state
|
||||
|
||||
getState: ->
|
||||
@state
|
||||
|
||||
getErrorMessage: ->
|
||||
@errorMessage
|
||||
|
||||
scheduleUpdateCheck: ->
|
||||
# Only schedule update check periodically if running in release version and
|
||||
# and there is no existing scheduled update check.
|
||||
|
||||
36
src/browser/spawner.coffee
Normal file
36
src/browser/spawner.coffee
Normal file
@@ -0,0 +1,36 @@
|
||||
ChildProcess = require 'child_process'
|
||||
|
||||
# Spawn a command and invoke the callback when it completes with an error
|
||||
# and the output from standard out.
|
||||
#
|
||||
# * `command` The underlying OS command {String} to execute.
|
||||
# * `args` (optional) The {Array} with arguments to be passed to command.
|
||||
# * `callback` (optional) The {Function} to call after the command has run. It will be invoked with arguments:
|
||||
# * `error` (optional) An {Error} object returned by the command, `null` if no error was thrown.
|
||||
# * `code` Error code returned by the command.
|
||||
# * `stdout` The {String} output text generated by the command.
|
||||
# * `stdout` The {String} output text generated by the command.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
exports.spawn = (command, args, callback) ->
|
||||
stdout = ''
|
||||
|
||||
try
|
||||
spawnedProcess = ChildProcess.spawn(command, args)
|
||||
catch error
|
||||
# Spawn can throw an error
|
||||
process.nextTick -> callback?(error, stdout)
|
||||
return
|
||||
|
||||
spawnedProcess.stdout.on 'data', (data) -> stdout += data
|
||||
|
||||
error = null
|
||||
spawnedProcess.on 'error', (processError) -> error ?= processError
|
||||
spawnedProcess.on 'close', (code, signal) ->
|
||||
error ?= new Error("Command failed: #{signal ? code}") if code isnt 0
|
||||
error?.code ?= code
|
||||
error?.stdout ?= stdout
|
||||
callback?(error, stdout)
|
||||
# This is necessary if using Powershell 2 on Windows 7 to get the events to raise
|
||||
# http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
|
||||
spawnedProcess.stdin.end()
|
||||
@@ -1,6 +1,8 @@
|
||||
ChildProcess = require 'child_process'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
Spawner = require './spawner'
|
||||
WinRegistry = require './win-registry'
|
||||
WinPowerShell = require './win-powershell'
|
||||
|
||||
appFolder = path.resolve(process.execPath, '..')
|
||||
rootAtomFolder = path.resolve(appFolder, '..')
|
||||
@@ -10,118 +12,18 @@ exeName = path.basename(process.execPath)
|
||||
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
regPath = path.join(system32Path, 'reg.exe')
|
||||
powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
||||
setxPath = path.join(system32Path, 'setx.exe')
|
||||
else
|
||||
regPath = 'reg.exe'
|
||||
powershellPath = 'powershell.exe'
|
||||
setxPath = 'setx.exe'
|
||||
|
||||
# Registry keys used for context menu
|
||||
fileKeyPath = 'HKCU\\Software\\Classes\\*\\shell\\Atom'
|
||||
directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom'
|
||||
backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom'
|
||||
applicationsKeyPath = 'HKCU\\Software\\Classes\\Applications\\atom.exe'
|
||||
environmentKeyPath = 'HKCU\\Environment'
|
||||
|
||||
# Spawn a command and invoke the callback when it completes with an error
|
||||
# and the output from standard out.
|
||||
spawn = (command, args, callback) ->
|
||||
stdout = ''
|
||||
|
||||
try
|
||||
spawnedProcess = ChildProcess.spawn(command, args)
|
||||
catch error
|
||||
# Spawn can throw an error
|
||||
process.nextTick -> callback?(error, stdout)
|
||||
return
|
||||
|
||||
spawnedProcess.stdout.on 'data', (data) -> stdout += data
|
||||
|
||||
error = null
|
||||
spawnedProcess.on 'error', (processError) -> error ?= processError
|
||||
spawnedProcess.on 'close', (code, signal) ->
|
||||
error ?= new Error("Command failed: #{signal ? code}") if code isnt 0
|
||||
error?.code ?= code
|
||||
error?.stdout ?= stdout
|
||||
callback?(error, stdout)
|
||||
# This is necessary if using Powershell 2 on Windows 7 to get the events to raise
|
||||
# http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
|
||||
spawnedProcess.stdin.end()
|
||||
|
||||
|
||||
# Spawn reg.exe and callback when it completes
|
||||
spawnReg = (args, callback) ->
|
||||
spawn(regPath, args, callback)
|
||||
|
||||
# Spawn powershell.exe and callback when it completes
|
||||
spawnPowershell = (args, callback) ->
|
||||
# set encoding and execute the command, capture the output, and return it via .NET's console in order to have consistent UTF-8 encoding
|
||||
# http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
|
||||
# to address https://github.com/atom/atom/issues/5063
|
||||
args[0] = """
|
||||
[Console]::OutputEncoding=[System.Text.Encoding]::UTF8
|
||||
$output=#{args[0]}
|
||||
[Console]::WriteLine($output)
|
||||
"""
|
||||
args.unshift('-command')
|
||||
args.unshift('RemoteSigned')
|
||||
args.unshift('-ExecutionPolicy')
|
||||
args.unshift('-noprofile')
|
||||
spawn(powershellPath, args, callback)
|
||||
|
||||
# Spawn setx.exe and callback when it completes
|
||||
spawnSetx = (args, callback) ->
|
||||
spawn(setxPath, args, callback)
|
||||
Spawner.spawn(setxPath, args, callback)
|
||||
|
||||
# Spawn the Update.exe with the given arguments and invoke the callback when
|
||||
# the command completes.
|
||||
spawnUpdate = (args, callback) ->
|
||||
spawn(updateDotExe, args, callback)
|
||||
|
||||
# Install the Open with Atom explorer context menu items via the registry.
|
||||
installContextMenu = (callback) ->
|
||||
addToRegistry = (args, callback) ->
|
||||
args.unshift('add')
|
||||
args.push('/f')
|
||||
spawnReg(args, callback)
|
||||
|
||||
installFileHandler = (callback) ->
|
||||
args = ["#{applicationsKeyPath}\\shell\\open\\command", '/ve', '/d', "\"#{process.execPath}\" \"%1\""]
|
||||
addToRegistry(args, callback)
|
||||
|
||||
installMenu = (keyPath, arg, callback) ->
|
||||
args = [keyPath, '/ve', '/d', 'Open with Atom']
|
||||
addToRegistry args, ->
|
||||
args = [keyPath, '/v', 'Icon', '/d', "\"#{process.execPath}\""]
|
||||
addToRegistry args, ->
|
||||
args = ["#{keyPath}\\command", '/ve', '/d', "\"#{process.execPath}\" \"#{arg}\""]
|
||||
addToRegistry(args, callback)
|
||||
|
||||
installMenu fileKeyPath, '%1', ->
|
||||
installMenu directoryKeyPath, '%1', ->
|
||||
installMenu backgroundKeyPath, '%V', ->
|
||||
installFileHandler(callback)
|
||||
|
||||
# Get the user's PATH environment variable registry value.
|
||||
getPath = (callback) ->
|
||||
spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) ->
|
||||
if error?
|
||||
return callback(error)
|
||||
|
||||
pathOutput = stdout.replace(/^\s+|\s+$/g, '')
|
||||
callback(null, pathOutput)
|
||||
|
||||
# Uninstall the Open with Atom explorer context menu items via the registry.
|
||||
uninstallContextMenu = (callback) ->
|
||||
deleteFromRegistry = (keyPath, callback) ->
|
||||
spawnReg(['delete', keyPath, '/f'], callback)
|
||||
|
||||
deleteFromRegistry fileKeyPath, ->
|
||||
deleteFromRegistry directoryKeyPath, ->
|
||||
deleteFromRegistry backgroundKeyPath, ->
|
||||
deleteFromRegistry(applicationsKeyPath, callback)
|
||||
Spawner.spawn(updateDotExe, args, callback)
|
||||
|
||||
# Add atom and apm to the PATH
|
||||
#
|
||||
@@ -160,7 +62,7 @@ addCommandsToPath = (callback) ->
|
||||
installCommands (error) ->
|
||||
return callback(error) if error?
|
||||
|
||||
getPath (error, pathEnv) ->
|
||||
WinPowerShell.getPath (error, pathEnv) ->
|
||||
return callback(error) if error?
|
||||
|
||||
pathSegments = pathEnv.split(/;+/).filter (pathSegment) -> pathSegment
|
||||
@@ -171,7 +73,7 @@ addCommandsToPath = (callback) ->
|
||||
|
||||
# Remove atom and apm from the PATH
|
||||
removeCommandsFromPath = (callback) ->
|
||||
getPath (error, pathEnv) ->
|
||||
WinPowerShell.getPath (error, pathEnv) ->
|
||||
return callback(error) if error?
|
||||
|
||||
pathSegments = pathEnv.split(/;+/).filter (pathSegment) ->
|
||||
@@ -220,7 +122,7 @@ exports.existsSync = ->
|
||||
exports.restartAtom = (app) ->
|
||||
if projectPath = global.atomApplication?.lastFocusedWindow?.projectPath
|
||||
args = [projectPath]
|
||||
app.once 'will-quit', -> spawn(path.join(binFolder, 'atom.cmd'), args)
|
||||
app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args)
|
||||
app.quit()
|
||||
|
||||
# Handle squirrel events denoted by --squirrel-* command line arguments.
|
||||
@@ -228,19 +130,19 @@ exports.handleStartupEvent = (app, squirrelCommand) ->
|
||||
switch squirrelCommand
|
||||
when '--squirrel-install'
|
||||
createShortcuts ->
|
||||
installContextMenu ->
|
||||
WinRegistry.installContextMenu ->
|
||||
addCommandsToPath ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-updated'
|
||||
updateShortcuts ->
|
||||
installContextMenu ->
|
||||
WinRegistry.installContextMenu ->
|
||||
addCommandsToPath ->
|
||||
app.quit()
|
||||
true
|
||||
when '--squirrel-uninstall'
|
||||
removeShortcuts ->
|
||||
uninstallContextMenu ->
|
||||
WinRegistry.uninstallContextMenu ->
|
||||
removeCommandsFromPath ->
|
||||
app.quit()
|
||||
true
|
||||
|
||||
39
src/browser/win-powershell.coffee
Normal file
39
src/browser/win-powershell.coffee
Normal file
@@ -0,0 +1,39 @@
|
||||
path = require 'path'
|
||||
Spawner = require './spawner'
|
||||
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
||||
else
|
||||
powershellPath = 'powershell.exe'
|
||||
|
||||
# Spawn powershell.exe and callback when it completes
|
||||
spawnPowershell = (args, callback) ->
|
||||
# Set encoding and execute the command, capture the output, and return it
|
||||
# via .NET's console in order to have consistent UTF-8 encoding.
|
||||
# See http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
|
||||
# to address https://github.com/atom/atom/issues/5063
|
||||
args[0] = """
|
||||
[Console]::OutputEncoding=[System.Text.Encoding]::UTF8
|
||||
$output=#{args[0]}
|
||||
[Console]::WriteLine($output)
|
||||
"""
|
||||
args.unshift('-command')
|
||||
args.unshift('RemoteSigned')
|
||||
args.unshift('-ExecutionPolicy')
|
||||
args.unshift('-noprofile')
|
||||
Spawner.spawn(powershellPath, args, callback)
|
||||
|
||||
# Get the user's PATH environment variable registry value.
|
||||
#
|
||||
# * `callback` The {Function} to call after registry operation is done.
|
||||
# It will be invoked with the same arguments provided by {Spawner.spawn}.
|
||||
#
|
||||
# Returns the user's path {String}.
|
||||
exports.getPath = (callback) ->
|
||||
spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) ->
|
||||
if error?
|
||||
return callback(error)
|
||||
|
||||
pathOutput = stdout.replace(/^\s+|\s+$/g, '')
|
||||
callback(null, pathOutput)
|
||||
62
src/browser/win-registry.coffee
Normal file
62
src/browser/win-registry.coffee
Normal file
@@ -0,0 +1,62 @@
|
||||
path = require 'path'
|
||||
Spawner = require './spawner'
|
||||
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
regPath = path.join(system32Path, 'reg.exe')
|
||||
else
|
||||
regPath = 'reg.exe'
|
||||
|
||||
# Registry keys used for context menu
|
||||
fileKeyPath = 'HKCU\\Software\\Classes\\*\\shell\\Atom'
|
||||
directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom'
|
||||
backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom'
|
||||
applicationsKeyPath = 'HKCU\\Software\\Classes\\Applications\\atom.exe'
|
||||
|
||||
# Spawn reg.exe and callback when it completes
|
||||
spawnReg = (args, callback) ->
|
||||
Spawner.spawn(regPath, args, callback)
|
||||
|
||||
# Install the Open with Atom explorer context menu items via the registry.
|
||||
#
|
||||
# * `callback` The {Function} to call after registry operation is done.
|
||||
# It will be invoked with the same arguments provided by {Spawner.spawn}.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
exports.installContextMenu = (callback) ->
|
||||
addToRegistry = (args, callback) ->
|
||||
args.unshift('add')
|
||||
args.push('/f')
|
||||
spawnReg(args, callback)
|
||||
|
||||
installFileHandler = (callback) ->
|
||||
args = ["#{applicationsKeyPath}\\shell\\open\\command", '/ve', '/d', "\"#{process.execPath}\" \"%1\""]
|
||||
addToRegistry(args, callback)
|
||||
|
||||
installMenu = (keyPath, arg, callback) ->
|
||||
args = [keyPath, '/ve', '/d', 'Open with Atom']
|
||||
addToRegistry args, ->
|
||||
args = [keyPath, '/v', 'Icon', '/d', "\"#{process.execPath}\""]
|
||||
addToRegistry args, ->
|
||||
args = ["#{keyPath}\\command", '/ve', '/d', "\"#{process.execPath}\" \"#{arg}\""]
|
||||
addToRegistry(args, callback)
|
||||
|
||||
installMenu fileKeyPath, '%1', ->
|
||||
installMenu directoryKeyPath, '%1', ->
|
||||
installMenu backgroundKeyPath, '%V', ->
|
||||
installFileHandler(callback)
|
||||
|
||||
# Uninstall the Open with Atom explorer context menu items via the registry.
|
||||
#
|
||||
# * `callback` The {Function} to call after registry operation is done.
|
||||
# It will be invoked with the same arguments provided by {Spawner.spawn}.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
exports.uninstallContextMenu = (callback) ->
|
||||
deleteFromRegistry = (keyPath, callback) ->
|
||||
spawnReg(['delete', keyPath, '/f'], callback)
|
||||
|
||||
deleteFromRegistry fileKeyPath, ->
|
||||
deleteFromRegistry directoryKeyPath, ->
|
||||
deleteFromRegistry backgroundKeyPath, ->
|
||||
deleteFromRegistry(applicationsKeyPath, callback)
|
||||
@@ -47,6 +47,7 @@ class BufferedNodeProcess extends BufferedProcess
|
||||
options ?= {}
|
||||
options.env ?= Object.create(process.env)
|
||||
options.env['ELECTRON_RUN_AS_NODE'] = 1
|
||||
options.env['ELECTRON_NO_ATTACH_CONSOLE'] = 1
|
||||
|
||||
args = args?.slice() ? []
|
||||
args.unshift(command)
|
||||
|
||||
@@ -50,7 +50,7 @@ class BufferedProcess
|
||||
options ?= {}
|
||||
@command = command
|
||||
# Related to joyent/node#2318
|
||||
if process.platform is 'win32'
|
||||
if process.platform is 'win32' and not options.shell?
|
||||
# Quote all arguments and escapes inner quotes
|
||||
if args?
|
||||
cmdArgs = args.filter (arg) -> arg?
|
||||
@@ -67,7 +67,7 @@ class BufferedProcess
|
||||
cmdArgs.unshift("\"#{command}\"")
|
||||
else
|
||||
cmdArgs.unshift(command)
|
||||
cmdArgs = ['/s', '/c', "\"#{cmdArgs.join(' ')}\""]
|
||||
cmdArgs = ['/s', '/d', '/c', "\"#{cmdArgs.join(' ')}\""]
|
||||
cmdOptions = _.clone(options)
|
||||
cmdOptions.windowsVerbatimArguments = true
|
||||
@spawn(@getCmdPath(), cmdArgs, cmdOptions)
|
||||
|
||||
@@ -244,11 +244,14 @@ class CommandRegistry
|
||||
(@selectorBasedListenersByCommandName[event.type] ? [])
|
||||
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
listeners = listeners.concat(selectorBasedListeners)
|
||||
listeners = selectorBasedListeners.concat(listeners)
|
||||
|
||||
matched = true if listeners.length > 0
|
||||
|
||||
for listener in listeners
|
||||
# Call inline listeners first in reverse registration order,
|
||||
# and selector-based listeners by specificity and reverse
|
||||
# registration order.
|
||||
for listener in listeners by -1
|
||||
break if immediatePropagationStopped
|
||||
listener.callback.call(currentTarget, dispatchedEvent)
|
||||
|
||||
@@ -271,8 +274,8 @@ class SelectorBasedListener
|
||||
@sequenceNumber = SequenceCount++
|
||||
|
||||
compare: (other) ->
|
||||
other.specificity - @specificity or
|
||||
other.sequenceNumber - @sequenceNumber
|
||||
@specificity - other.specificity or
|
||||
@sequenceNumber - other.sequenceNumber
|
||||
|
||||
class InlineListener
|
||||
constructor: (@callback) ->
|
||||
|
||||
@@ -155,6 +155,10 @@ module.exports =
|
||||
type: 'boolean'
|
||||
default: true
|
||||
description: 'Show line numbers in the editor\'s gutter.'
|
||||
atomicSoftTabs:
|
||||
type: 'boolean'
|
||||
default: true
|
||||
description: 'Skip over tab-length runs of leading whitespace when moving the cursor.'
|
||||
autoIndent:
|
||||
type: 'boolean'
|
||||
default: true
|
||||
|
||||
@@ -9,7 +9,7 @@ EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g
|
||||
# where text can be inserted.
|
||||
#
|
||||
# Cursors belong to {TextEditor}s and have some metadata attached in the form
|
||||
# of a {TextEditorMarker}.
|
||||
# of a {DisplayMarker}.
|
||||
module.exports =
|
||||
class Cursor extends Model
|
||||
screenPosition: null
|
||||
@@ -129,7 +129,7 @@ class Cursor extends Model
|
||||
Section: Cursor Position Details
|
||||
###
|
||||
|
||||
# Public: Returns the underlying {TextEditorMarker} for the cursor.
|
||||
# Public: Returns the underlying {DisplayMarker} for the cursor.
|
||||
# Useful with overlay {Decoration}s.
|
||||
getMarker: -> @marker
|
||||
|
||||
@@ -261,11 +261,11 @@ class Cursor extends Model
|
||||
|
||||
while columnCount > column and row > 0
|
||||
columnCount -= column
|
||||
column = @editor.lineTextForScreenRow(--row).length
|
||||
column = @editor.lineLengthForScreenRow(--row)
|
||||
columnCount-- # subtract 1 for the row move
|
||||
|
||||
column = column - columnCount
|
||||
@setScreenPosition({row, column}, clip: 'backward')
|
||||
@setScreenPosition({row, column}, clipDirection: 'backward')
|
||||
|
||||
# Public: Moves the cursor right one screen column.
|
||||
#
|
||||
@@ -280,7 +280,7 @@ class Cursor extends Model
|
||||
else
|
||||
{row, column} = @getScreenPosition()
|
||||
maxLines = @editor.getScreenLineCount()
|
||||
rowLength = @editor.lineTextForScreenRow(row).length
|
||||
rowLength = @editor.lineLengthForScreenRow(row)
|
||||
columnsRemainingInLine = rowLength - column
|
||||
|
||||
while columnCount > columnsRemainingInLine and row < maxLines - 1
|
||||
@@ -288,11 +288,11 @@ class Cursor extends Model
|
||||
columnCount-- # subtract 1 for the row move
|
||||
|
||||
column = 0
|
||||
rowLength = @editor.lineTextForScreenRow(++row).length
|
||||
rowLength = @editor.lineLengthForScreenRow(++row)
|
||||
columnsRemainingInLine = rowLength
|
||||
|
||||
column = column + columnCount
|
||||
@setScreenPosition({row, column}, clip: 'forward', wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
|
||||
@setScreenPosition({row, column}, clipDirection: 'forward')
|
||||
|
||||
# Public: Moves the cursor to the top of the buffer.
|
||||
moveToTop: ->
|
||||
|
||||
181
src/decoration-manager.coffee
Normal file
181
src/decoration-manager.coffee
Normal file
@@ -0,0 +1,181 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
Model = require './model'
|
||||
Decoration = require './decoration'
|
||||
LayerDecoration = require './layer-decoration'
|
||||
|
||||
module.exports =
|
||||
class DecorationManager extends Model
|
||||
didUpdateDecorationsEventScheduled: false
|
||||
updatedSynchronously: false
|
||||
|
||||
constructor: (@displayLayer, @defaultMarkerLayer) ->
|
||||
super
|
||||
|
||||
@emitter = new Emitter
|
||||
@decorationsById = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@overlayDecorationsById = {}
|
||||
@layerDecorationsByMarkerLayerId = {}
|
||||
@decorationCountsByLayerId = {}
|
||||
@layerUpdateDisposablesByLayerId = {}
|
||||
|
||||
observeDecorations: (callback) ->
|
||||
callback(decoration) for decoration in @getDecorations()
|
||||
@onDidAddDecoration(callback)
|
||||
|
||||
onDidAddDecoration: (callback) ->
|
||||
@emitter.on 'did-add-decoration', callback
|
||||
|
||||
onDidRemoveDecoration: (callback) ->
|
||||
@emitter.on 'did-remove-decoration', callback
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@emitter.on 'did-update-decorations', callback
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
|
||||
decorationForId: (id) ->
|
||||
@decorationsById[id]
|
||||
|
||||
getDecorations: (propertyFilter) ->
|
||||
allDecorations = []
|
||||
for markerId, decorations of @decorationsByMarkerId
|
||||
allDecorations.push(decorations...) if decorations?
|
||||
if propertyFilter?
|
||||
allDecorations = allDecorations.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
return false unless decoration.properties[key] is value
|
||||
true
|
||||
allDecorations
|
||||
|
||||
getLineDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line')
|
||||
|
||||
getLineNumberDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line-number')
|
||||
|
||||
getHighlightDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('highlight')
|
||||
|
||||
getOverlayDecorations: (propertyFilter) ->
|
||||
result = []
|
||||
for id, decoration of @overlayDecorationsById
|
||||
result.push(decoration)
|
||||
if propertyFilter?
|
||||
result.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
return false unless decoration.properties[key] is value
|
||||
true
|
||||
else
|
||||
result
|
||||
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsByMarkerId = {}
|
||||
for marker in @defaultMarkerLayer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsState = {}
|
||||
|
||||
for layerId of @decorationCountsByLayerId
|
||||
layer = @displayLayer.getMarkerLayer(layerId)
|
||||
|
||||
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) when marker.isValid()
|
||||
screenRange = marker.getScreenRange()
|
||||
bufferRange = marker.getBufferRange()
|
||||
rangeIsReversed = marker.isReversed()
|
||||
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
for decoration in decorations
|
||||
decorationsState[decoration.id] = {
|
||||
properties: decoration.properties
|
||||
screenRange, bufferRange, rangeIsReversed
|
||||
}
|
||||
|
||||
if layerDecorations = @layerDecorationsByMarkerLayerId[layerId]
|
||||
for layerDecoration in layerDecorations
|
||||
decorationsState["#{layerDecoration.id}-#{marker.id}"] = {
|
||||
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
|
||||
screenRange, bufferRange, rangeIsReversed
|
||||
}
|
||||
|
||||
decorationsState
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
|
||||
marker = @displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id)
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
|
||||
@decorationsById[decoration.id] = decoration
|
||||
@observeDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-add-decoration', decoration
|
||||
decoration
|
||||
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
decoration = new LayerDecoration(markerLayer, this, decorationParams)
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
|
||||
@observeDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
decoration
|
||||
|
||||
decorationsForMarkerId: (markerId) ->
|
||||
@decorationsByMarkerId[markerId]
|
||||
|
||||
scheduleUpdateDecorationsEvent: ->
|
||||
if @updatedSynchronously
|
||||
@emitter.emit 'did-update-decorations'
|
||||
return
|
||||
|
||||
unless @didUpdateDecorationsEventScheduled
|
||||
@didUpdateDecorationsEventScheduled = true
|
||||
process.nextTick =>
|
||||
@didUpdateDecorationsEventScheduled = false
|
||||
@emitter.emit 'did-update-decorations'
|
||||
|
||||
decorationDidChangeType: (decoration) ->
|
||||
if decoration.isType('overlay')
|
||||
@overlayDecorationsById[decoration.id] = decoration
|
||||
else
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
@unobserveDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
didDestroyLayerDecoration: (decoration) ->
|
||||
{markerLayer} = decoration
|
||||
return unless decorations = @layerDecorationsByMarkerLayerId[markerLayer.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @layerDecorationsByMarkerLayerId[markerLayer.id] if decorations.length is 0
|
||||
@unobserveDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
observeDecoratedLayer: (layer) ->
|
||||
@decorationCountsByLayerId[layer.id] ?= 0
|
||||
if ++@decorationCountsByLayerId[layer.id] is 1
|
||||
@layerUpdateDisposablesByLayerId[layer.id] = layer.onDidUpdate(@scheduleUpdateDecorationsEvent.bind(this))
|
||||
|
||||
unobserveDecoratedLayer: (layer) ->
|
||||
if --@decorationCountsByLayerId[layer.id] is 0
|
||||
@layerUpdateDisposablesByLayerId[layer.id].dispose()
|
||||
delete @decorationCountsByLayerId[layer.id]
|
||||
delete @layerUpdateDisposablesByLayerId[layer.id]
|
||||
@@ -11,7 +11,7 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
decorationParams.gutterName = 'line-number'
|
||||
decorationParams
|
||||
|
||||
# Essential: Represents a decoration that follows a {TextEditorMarker}. A decoration is
|
||||
# Essential: Represents a decoration that follows a {DisplayMarker}. A decoration is
|
||||
# basically a visual representation of a marker. It allows you to add CSS
|
||||
# classes to line numbers in the gutter, lines, and add selection-line regions
|
||||
# around marked ranges of text.
|
||||
@@ -25,7 +25,7 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
# decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
|
||||
# ```
|
||||
#
|
||||
# Best practice for destroying the decoration is by destroying the {TextEditorMarker}.
|
||||
# Best practice for destroying the decoration is by destroying the {DisplayMarker}.
|
||||
#
|
||||
# ```coffee
|
||||
# marker.destroy()
|
||||
@@ -62,7 +62,7 @@ class Decoration
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: (@marker, @displayBuffer, properties) ->
|
||||
constructor: (@marker, @decorationManager, properties) ->
|
||||
@emitter = new Emitter
|
||||
@id = nextId()
|
||||
@setProperties properties
|
||||
@@ -71,14 +71,14 @@ class Decoration
|
||||
|
||||
# Essential: Destroy this marker.
|
||||
#
|
||||
# If you own the marker, you should use {TextEditorMarker::destroy} which will destroy
|
||||
# If you own the marker, you should use {DisplayMarker::destroy} which will destroy
|
||||
# this decoration.
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@markerDestroyDisposable.dispose()
|
||||
@markerDestroyDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyDecoration(this)
|
||||
@decorationManager.didDestroyDecoration(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
@@ -149,8 +149,8 @@ class Decoration
|
||||
oldProperties = @properties
|
||||
@properties = translateDecorationParamsOldToNew(newProperties)
|
||||
if newProperties.type?
|
||||
@displayBuffer.decorationDidChangeType(this)
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.decorationDidChangeType(this)
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
@@ -175,5 +175,5 @@ class Decoration
|
||||
@properties.flashCount++
|
||||
@properties.flashClass = klass
|
||||
@properties.flashDuration = duration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-flash'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@ function getFromShell () {
|
||||
function needsPatching (options = { platform: process.platform, env: process.env }) {
|
||||
if (options.platform === 'darwin' && !options.env.PWD) {
|
||||
let shell = getUserShell()
|
||||
if (shell.endsWith('csh') || shell.endsWith('tcsh')) {
|
||||
if (shell.endsWith('csh') || shell.endsWith('tcsh') || shell.endsWith('fish')) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -67,9 +67,20 @@ function needsPatching (options = { platform: process.platform, env: process.env
|
||||
return false
|
||||
}
|
||||
|
||||
// Fix for #11302 because `process.env` on Windows is a magic object that offers case-insensitive
|
||||
// environment variable matching. By always cloning to `process.env` we prevent breaking the
|
||||
// underlying functionality.
|
||||
function clone (to, from) {
|
||||
for (var key in to) {
|
||||
delete to[key]
|
||||
}
|
||||
|
||||
Object.assign(to, from)
|
||||
}
|
||||
|
||||
function normalize (options = {}) {
|
||||
if (options && options.env) {
|
||||
process.env = options.env
|
||||
clone(process.env, options.env)
|
||||
}
|
||||
|
||||
if (!options.env) {
|
||||
@@ -85,10 +96,18 @@ function normalize (options = {}) {
|
||||
// in #4126. Retain the original in case someone needs it.
|
||||
let shellEnv = getFromShell()
|
||||
if (shellEnv && shellEnv.PATH) {
|
||||
process._originalEnv = process.env
|
||||
process.env = shellEnv
|
||||
process._originalEnv = Object.assign({}, process.env)
|
||||
clone(process.env, shellEnv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default { getFromShell, needsPatching, normalize }
|
||||
function replace (env) {
|
||||
if (!env || !env.PATH) {
|
||||
return
|
||||
}
|
||||
|
||||
clone(process.env, env)
|
||||
}
|
||||
|
||||
export default { getFromShell, needsPatching, normalize, replace }
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
{Point, Range} = require 'text-buffer'
|
||||
|
||||
# Represents a fold that collapses multiple buffer lines into a single
|
||||
# line on the screen.
|
||||
#
|
||||
# Their creation is managed by the {DisplayBuffer}.
|
||||
module.exports =
|
||||
class Fold
|
||||
id: null
|
||||
displayBuffer: null
|
||||
marker: null
|
||||
|
||||
constructor: (@displayBuffer, @marker) ->
|
||||
@id = @marker.id
|
||||
@displayBuffer.foldsByMarkerId[@marker.id] = this
|
||||
@marker.onDidDestroy => @destroyed()
|
||||
@marker.onDidChange ({isValid}) => @destroy() unless isValid
|
||||
|
||||
# Returns whether this fold is contained within another fold
|
||||
isInsideLargerFold: ->
|
||||
largestContainingFoldMarker = @displayBuffer.findFoldMarker(containsRange: @getBufferRange())
|
||||
largestContainingFoldMarker and
|
||||
not largestContainingFoldMarker.getRange().isEqual(@getBufferRange())
|
||||
|
||||
# Destroys this fold
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
# Returns the fold's {Range} in buffer coordinates
|
||||
#
|
||||
# includeNewline - A {Boolean} which, if `true`, includes the trailing newline
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ({includeNewline}={}) ->
|
||||
range = @marker.getRange()
|
||||
|
||||
if range.end.row > range.start.row and nextFold = @displayBuffer.largestFoldStartingAtBufferRow(range.end.row)
|
||||
nextRange = nextFold.getBufferRange()
|
||||
range = new Range(range.start, nextRange.end)
|
||||
|
||||
if includeNewline
|
||||
range = range.copy()
|
||||
range.end.row++
|
||||
range.end.column = 0
|
||||
range
|
||||
|
||||
getBufferRowRange: ->
|
||||
{start, end} = @getBufferRange()
|
||||
[start.row, end.row]
|
||||
|
||||
# Returns the fold's start row as a {Number}.
|
||||
getStartRow: ->
|
||||
@getBufferRange().start.row
|
||||
|
||||
# Returns the fold's end row as a {Number}.
|
||||
getEndRow: ->
|
||||
@getBufferRange().end.row
|
||||
|
||||
# Returns a {String} representation of the fold.
|
||||
inspect: ->
|
||||
"Fold(#{@getStartRow()}, #{@getEndRow()})"
|
||||
|
||||
# Retrieves the number of buffer rows spanned by the fold.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getBufferRowCount: ->
|
||||
@getEndRow() - @getStartRow() + 1
|
||||
|
||||
# Identifies if a fold is nested within a fold.
|
||||
#
|
||||
# fold - A {Fold} to check
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isContainedByFold: (fold) ->
|
||||
@isContainedByRange(fold.getBufferRange())
|
||||
|
||||
updateDisplayBuffer: ->
|
||||
unless @isInsideLargerFold()
|
||||
@displayBuffer.updateScreenLines(@getStartRow(), @getEndRow() + 1, 0, updateMarkers: true)
|
||||
|
||||
destroyed: ->
|
||||
delete @displayBuffer.foldsByMarkerId[@marker.id]
|
||||
@updateDisplayBuffer()
|
||||
@@ -1,19 +1,7 @@
|
||||
'use babel'
|
||||
|
||||
import fs from 'fs-plus'
|
||||
import path from 'path'
|
||||
import Git from 'nodegit'
|
||||
import {Emitter, CompositeDisposable, Disposable} from 'event-kit'
|
||||
|
||||
const modifiedStatusFlags = Git.Status.STATUS.WT_MODIFIED | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.WT_TYPECHANGE | Git.Status.STATUS.INDEX_TYPECHANGE
|
||||
const newStatusFlags = Git.Status.STATUS.WT_NEW | Git.Status.STATUS.INDEX_NEW
|
||||
const deletedStatusFlags = Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED
|
||||
const indexStatusFlags = Git.Status.STATUS.INDEX_NEW | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.INDEX_RENAMED | Git.Status.STATUS.INDEX_TYPECHANGE
|
||||
const ignoredStatusFlags = 1 << 14 // TODO: compose this from libgit2 constants
|
||||
const submoduleMode = 57344 // TODO: compose this from libgit2 constants
|
||||
|
||||
// Just using this for _.isEqual and _.object, we should impl our own here
|
||||
import _ from 'underscore-plus'
|
||||
import {Repository} from 'ohnogit'
|
||||
import {CompositeDisposable, Disposable} from 'event-kit'
|
||||
|
||||
// For the most part, this class behaves the same as `GitRepository`, with a few
|
||||
// notable differences:
|
||||
@@ -28,32 +16,19 @@ export default class GitRepositoryAsync {
|
||||
}
|
||||
|
||||
static get Git () {
|
||||
return Git
|
||||
return Repository.Git
|
||||
}
|
||||
|
||||
// The name of the error thrown when an action is attempted on a destroyed
|
||||
// repository.
|
||||
static get DestroyedErrorName () {
|
||||
return 'GitRepositoryAsync.destroyed'
|
||||
return Repository.DestroyedErrorName
|
||||
}
|
||||
|
||||
constructor (_path, options = {}) {
|
||||
Git.enableThreadSafety()
|
||||
this.repo = Repository.open(_path, options)
|
||||
|
||||
this.emitter = new Emitter()
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.pathStatusCache = {}
|
||||
|
||||
// NB: These needs to happen before the following .openRepository call.
|
||||
this.openedPath = _path
|
||||
this._openExactPath = options.openExactPath || false
|
||||
|
||||
this.repoPromise = this.openRepository()
|
||||
this.isCaseInsensitive = fs.isCaseInsensitive()
|
||||
this.upstream = {}
|
||||
this.submodules = {}
|
||||
|
||||
this._refreshingPromise = Promise.resolve()
|
||||
|
||||
let {refreshOnWindowFocus = true} = options
|
||||
if (refreshOnWindowFocus) {
|
||||
@@ -70,22 +45,26 @@ export default class GitRepositoryAsync {
|
||||
}
|
||||
}
|
||||
|
||||
// This exists to provide backwards compatibility.
|
||||
get _refreshingPromise () {
|
||||
return this.repo._refreshingPromise
|
||||
}
|
||||
|
||||
get openedPath () {
|
||||
return this.repo.openedPath
|
||||
}
|
||||
|
||||
// Public: Destroy this {GitRepositoryAsync} object.
|
||||
//
|
||||
// This destroys any tasks and subscriptions and releases the underlying
|
||||
// libgit2 repository handle. This method is idempotent.
|
||||
destroy () {
|
||||
if (this.emitter) {
|
||||
this.emitter.emit('did-destroy')
|
||||
this.emitter.dispose()
|
||||
this.emitter = null
|
||||
}
|
||||
this.repo.destroy()
|
||||
|
||||
if (this.subscriptions) {
|
||||
this.subscriptions.dispose()
|
||||
this.subscriptions = null
|
||||
}
|
||||
|
||||
this.repoPromise = null
|
||||
}
|
||||
|
||||
// Event subscription
|
||||
@@ -98,7 +77,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidDestroy (callback) {
|
||||
return this.emitter.on('did-destroy', callback)
|
||||
return this.repo.onDidDestroy(callback)
|
||||
}
|
||||
|
||||
// Public: Invoke the given callback when a specific file's status has
|
||||
@@ -113,7 +92,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeStatus (callback) {
|
||||
return this.emitter.on('did-change-status', callback)
|
||||
return this.repo.onDidChangeStatus(callback)
|
||||
}
|
||||
|
||||
// Public: Invoke the given callback when a multiple files' statuses have
|
||||
@@ -125,7 +104,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeStatuses (callback) {
|
||||
return this.emitter.on('did-change-statuses', callback)
|
||||
return this.repo.onDidChangeStatuses(callback)
|
||||
}
|
||||
|
||||
// Repository details
|
||||
@@ -142,13 +121,13 @@ export default class GitRepositoryAsync {
|
||||
// Public: Returns a {Promise} which resolves to the {String} path of the
|
||||
// repository.
|
||||
getPath () {
|
||||
return this.getRepo().then(repo => repo.path().replace(/\/$/, ''))
|
||||
return this.repo.getPath()
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} which resolves to the {String} working
|
||||
// directory path of the repository.
|
||||
getWorkingDirectory () {
|
||||
return this.getRepo().then(repo => repo.workdir())
|
||||
getWorkingDirectory (_path) {
|
||||
return this.repo.getWorkingDirectory()
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} that resolves to true if at the root, false if
|
||||
@@ -157,8 +136,8 @@ export default class GitRepositoryAsync {
|
||||
if (!this.project) return Promise.resolve(false)
|
||||
|
||||
if (!this.projectAtRoot) {
|
||||
this.projectAtRoot = this.getRepo()
|
||||
.then(repo => this.project.relativize(repo.workdir()) === '')
|
||||
this.projectAtRoot = this.getWorkingDirectory()
|
||||
.then(wd => this.project.relativize(wd) === '')
|
||||
}
|
||||
|
||||
return this.projectAtRoot
|
||||
@@ -170,8 +149,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Promise} which resolves to the relative {String} path.
|
||||
relativizeToWorkingDirectory (_path) {
|
||||
return this.getRepo()
|
||||
.then(repo => this.relativize(_path, repo.workdir()))
|
||||
return this.repo.relativizeToWorkingDirectory(_path)
|
||||
}
|
||||
|
||||
// Public: Makes a path relative to the repository's working directory.
|
||||
@@ -181,76 +159,13 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns the relative {String} path.
|
||||
relativize (_path, workingDirectory) {
|
||||
// The original implementation also handled null workingDirectory as it
|
||||
// pulled it from a sync function that could return null. We require it
|
||||
// to be passed here.
|
||||
let openedWorkingDirectory
|
||||
if (!_path || !workingDirectory) {
|
||||
return _path
|
||||
}
|
||||
|
||||
// If the opened directory and the workdir differ, this is a symlinked repo
|
||||
// root, so we have to do all the checks below twice--once against the realpath
|
||||
// and one against the opened path
|
||||
const opened = this.openedPath.replace(/\/\.git$/, '')
|
||||
if (path.relative(opened, workingDirectory) !== '') {
|
||||
openedWorkingDirectory = opened
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
_path = _path.replace(/\\/g, '/')
|
||||
} else {
|
||||
if (_path[0] !== '/') {
|
||||
return _path
|
||||
}
|
||||
}
|
||||
|
||||
workingDirectory = workingDirectory.replace(/\/$/, '')
|
||||
|
||||
// Depending on where the paths come from, they may have a '/private/'
|
||||
// prefix. Standardize by stripping that out.
|
||||
_path = _path.replace(/^\/private\//i, '/')
|
||||
workingDirectory = workingDirectory.replace(/^\/private\//i, '/')
|
||||
|
||||
const originalPath = _path
|
||||
const originalWorkingDirectory = workingDirectory
|
||||
if (this.isCaseInsensitive) {
|
||||
_path = _path.toLowerCase()
|
||||
workingDirectory = workingDirectory.toLowerCase()
|
||||
}
|
||||
|
||||
if (_path.indexOf(workingDirectory) === 0) {
|
||||
return originalPath.substring(originalWorkingDirectory.length + 1)
|
||||
} else if (_path === workingDirectory) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (openedWorkingDirectory) {
|
||||
openedWorkingDirectory = openedWorkingDirectory.replace(/\/$/, '')
|
||||
openedWorkingDirectory = openedWorkingDirectory.replace(/^\/private\//i, '/')
|
||||
|
||||
const originalOpenedWorkingDirectory = openedWorkingDirectory
|
||||
if (this.isCaseInsensitive) {
|
||||
openedWorkingDirectory = openedWorkingDirectory.toLowerCase()
|
||||
}
|
||||
|
||||
if (_path.indexOf(openedWorkingDirectory) === 0) {
|
||||
return originalPath.substring(originalOpenedWorkingDirectory.length + 1)
|
||||
} else if (_path === openedWorkingDirectory) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
return _path
|
||||
return this.repo.relativize(_path, workingDirectory)
|
||||
}
|
||||
|
||||
// Public: Returns a {Promise} which resolves to whether the given branch
|
||||
// exists.
|
||||
hasBranch (branch) {
|
||||
return this.getRepo()
|
||||
.then(repo => repo.getBranch(branch))
|
||||
.then(branch => branch != null)
|
||||
.catch(_ => false)
|
||||
return this.repo.hasBranch(branch)
|
||||
}
|
||||
|
||||
// Public: Retrieves a shortened version of the HEAD reference value.
|
||||
@@ -264,9 +179,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {String}.
|
||||
getShortHead (_path) {
|
||||
return this.getRepo(_path)
|
||||
.then(repo => repo.getCurrentBranch())
|
||||
.then(branch => branch.shorthand())
|
||||
return this.repo.getShortHead(_path)
|
||||
}
|
||||
|
||||
// Public: Is the given path a submodule in the repository?
|
||||
@@ -276,15 +189,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} that resolves true if the given path is a submodule in
|
||||
// the repository.
|
||||
isSubmodule (_path) {
|
||||
return this.getRepo()
|
||||
.then(repo => repo.openIndex())
|
||||
.then(index => Promise.all([index, this.relativizeToWorkingDirectory(_path)]))
|
||||
.then(([index, relativePath]) => {
|
||||
const entry = index.getByPath(relativePath)
|
||||
if (!entry) return false
|
||||
|
||||
return entry.mode === submoduleMode
|
||||
})
|
||||
return this.repo.isSubmodule(_path)
|
||||
}
|
||||
|
||||
// Public: Returns the number of commits behind the current branch is from the
|
||||
@@ -298,16 +203,7 @@ export default class GitRepositoryAsync {
|
||||
// * `ahead` The {Number} of commits ahead.
|
||||
// * `behind` The {Number} of commits behind.
|
||||
getAheadBehindCount (reference, _path) {
|
||||
return this.getRepo(_path)
|
||||
.then(repo => Promise.all([repo, repo.getBranch(reference)]))
|
||||
.then(([repo, local]) => {
|
||||
const upstream = Git.Branch.upstream(local)
|
||||
return Promise.all([repo, local, upstream])
|
||||
})
|
||||
.then(([repo, local, upstream]) => {
|
||||
return Git.Graph.aheadBehind(repo, local.target(), upstream.target())
|
||||
})
|
||||
.catch(_ => ({ahead: 0, behind: 0}))
|
||||
return this.repo.getAheadBehindCount(reference, _path)
|
||||
}
|
||||
|
||||
// Public: Get the cached ahead/behind commit counts for the current branch's
|
||||
@@ -320,15 +216,7 @@ export default class GitRepositoryAsync {
|
||||
// * `ahead` The {Number} of commits ahead.
|
||||
// * `behind` The {Number} of commits behind.
|
||||
getCachedUpstreamAheadBehindCount (_path) {
|
||||
return this.relativizeToWorkingDirectory(_path)
|
||||
.then(relativePath => this._submoduleForPath(_path))
|
||||
.then(submodule => {
|
||||
if (submodule) {
|
||||
return submodule.getCachedUpstreamAheadBehindCount(_path)
|
||||
} else {
|
||||
return this.upstream
|
||||
}
|
||||
})
|
||||
return this.repo.getCachedUpstreamAheadBehindCount(_path)
|
||||
}
|
||||
|
||||
// Public: Returns the git configuration value specified by the key.
|
||||
@@ -339,10 +227,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to the {String} git configuration value
|
||||
// specified by the key.
|
||||
getConfigValue (key, _path) {
|
||||
return this.getRepo(_path)
|
||||
.then(repo => repo.configSnapshot())
|
||||
.then(config => config.getStringBuf(key))
|
||||
.catch(_ => null)
|
||||
return this.repo.getConfigValue(key, _path)
|
||||
}
|
||||
|
||||
// Public: Get the URL for the 'origin' remote.
|
||||
@@ -353,7 +238,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to the {String} origin url of the
|
||||
// repository.
|
||||
getOriginURL (_path) {
|
||||
return this.getConfigValue('remote.origin.url', _path)
|
||||
return this.repo.getOriginURL(_path)
|
||||
}
|
||||
|
||||
// Public: Returns the upstream branch for the current HEAD, or null if there
|
||||
@@ -365,9 +250,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to a {String} branch name such as
|
||||
// `refs/remotes/origin/master`.
|
||||
getUpstreamBranch (_path) {
|
||||
return this.getRepo(_path)
|
||||
.then(repo => repo.getCurrentBranch())
|
||||
.then(branch => Git.Branch.upstream(branch))
|
||||
return this.repo.getUpstreamBranch(_path)
|
||||
}
|
||||
|
||||
// Public: Gets all the local and remote references.
|
||||
@@ -380,23 +263,7 @@ export default class GitRepositoryAsync {
|
||||
// * `remotes` An {Array} of remote reference names.
|
||||
// * `tags` An {Array} of tag reference names.
|
||||
getReferences (_path) {
|
||||
return this.getRepo(_path)
|
||||
.then(repo => repo.getReferences(Git.Reference.TYPE.LISTALL))
|
||||
.then(refs => {
|
||||
const heads = []
|
||||
const remotes = []
|
||||
const tags = []
|
||||
for (const ref of refs) {
|
||||
if (ref.isTag()) {
|
||||
tags.push(ref.name())
|
||||
} else if (ref.isRemote()) {
|
||||
remotes.push(ref.name())
|
||||
} else if (ref.isBranch()) {
|
||||
heads.push(ref.name())
|
||||
}
|
||||
}
|
||||
return {heads, remotes, tags}
|
||||
})
|
||||
return this.repo.getReferences(_path)
|
||||
}
|
||||
|
||||
// Public: Get the SHA for the given reference.
|
||||
@@ -408,9 +275,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to the current {String} SHA for the
|
||||
// given reference.
|
||||
getReferenceTarget (reference, _path) {
|
||||
return this.getRepo(_path)
|
||||
.then(repo => Git.Reference.nameToId(repo, reference))
|
||||
.then(oid => oid.tostrS())
|
||||
return this.repo.getReferenceTarget(reference, _path)
|
||||
}
|
||||
|
||||
// Reading Status
|
||||
@@ -423,9 +288,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
|
||||
// is modified.
|
||||
isPathModified (_path) {
|
||||
return this.relativizeToWorkingDirectory(_path)
|
||||
.then(relativePath => this._getStatus([relativePath]))
|
||||
.then(statuses => statuses.some(status => status.isModified()))
|
||||
return this.repo.isPathModified(_path)
|
||||
}
|
||||
|
||||
// Public: Resolves true if the given path is new.
|
||||
@@ -435,9 +298,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
|
||||
// is new.
|
||||
isPathNew (_path) {
|
||||
return this.relativizeToWorkingDirectory(_path)
|
||||
.then(relativePath => this._getStatus([relativePath]))
|
||||
.then(statuses => statuses.some(status => status.isNew()))
|
||||
return this.repo.isPathNew(_path)
|
||||
}
|
||||
|
||||
// Public: Is the given path ignored?
|
||||
@@ -447,12 +308,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
|
||||
// is ignored.
|
||||
isPathIgnored (_path) {
|
||||
return this.getRepo()
|
||||
.then(repo => {
|
||||
const relativePath = this.relativize(_path, repo.workdir())
|
||||
return Git.Ignore.pathIsIgnored(repo, relativePath)
|
||||
})
|
||||
.then(ignored => Boolean(ignored))
|
||||
return this.repo.isPathIgnored(_path)
|
||||
}
|
||||
|
||||
// Get the status of a directory in the repository's working directory.
|
||||
@@ -463,18 +319,7 @@ export default class GitRepositoryAsync {
|
||||
// value can be passed to {::isStatusModified} or {::isStatusNew} to get more
|
||||
// information.
|
||||
getDirectoryStatus (directoryPath) {
|
||||
return this.relativizeToWorkingDirectory(directoryPath)
|
||||
.then(relativePath => {
|
||||
const pathspec = relativePath + '/**'
|
||||
return this._getStatus([pathspec])
|
||||
})
|
||||
.then(statuses => {
|
||||
return Promise.all(statuses.map(s => s.statusBit())).then(bits => {
|
||||
return bits
|
||||
.filter(b => b > 0)
|
||||
.reduce((status, bit) => status | bit, 0)
|
||||
})
|
||||
})
|
||||
return this.repo.getDirectoryStatus(directoryPath)
|
||||
}
|
||||
|
||||
// Refresh the status bit for the given path.
|
||||
@@ -487,27 +332,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to a {Number} which is the refreshed
|
||||
// status bit for the path.
|
||||
refreshStatusForPath (_path) {
|
||||
let relativePath
|
||||
return this.getRepo()
|
||||
.then(repo => {
|
||||
relativePath = this.relativize(_path, repo.workdir())
|
||||
return this._getStatus([relativePath])
|
||||
})
|
||||
.then(statuses => {
|
||||
const cachedStatus = this.pathStatusCache[relativePath] || 0
|
||||
const status = statuses[0] ? statuses[0].statusBit() : Git.Status.STATUS.CURRENT
|
||||
if (status !== cachedStatus) {
|
||||
if (status === Git.Status.STATUS.CURRENT) {
|
||||
delete this.pathStatusCache[relativePath]
|
||||
} else {
|
||||
this.pathStatusCache[relativePath] = status
|
||||
}
|
||||
|
||||
this.emitter.emit('did-change-status', {path: _path, pathStatus: status})
|
||||
}
|
||||
|
||||
return status
|
||||
})
|
||||
return this.repo.refreshStatusForPath(_path)
|
||||
}
|
||||
|
||||
// Returns a Promise that resolves to the status bit of a given path if it has
|
||||
@@ -523,8 +348,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} which resolves to a status {Number} or null if the
|
||||
// path is not in the cache.
|
||||
getCachedPathStatus (_path) {
|
||||
return this.relativizeToWorkingDirectory(_path)
|
||||
.then(relativePath => this.pathStatusCache[relativePath])
|
||||
return this.repo.getCachedPathStatus(_path)
|
||||
}
|
||||
|
||||
// Public: Get the cached statuses for the repository.
|
||||
@@ -532,7 +356,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns an {Object} of {Number} statuses, keyed by {String} working
|
||||
// directory-relative file names.
|
||||
getCachedPathStatuses () {
|
||||
return this.pathStatusCache
|
||||
return this.repo.pathStatusCache
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates modification.
|
||||
@@ -541,7 +365,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates modification.
|
||||
isStatusModified (statusBit) {
|
||||
return (statusBit & modifiedStatusFlags) > 0
|
||||
return this.repo.isStatusModified(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates a new path.
|
||||
@@ -550,7 +374,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates a new path.
|
||||
isStatusNew (statusBit) {
|
||||
return (statusBit & newStatusFlags) > 0
|
||||
return this.repo.isStatusNew(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates the path is staged.
|
||||
@@ -560,7 +384,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
|
||||
// staged.
|
||||
isStatusStaged (statusBit) {
|
||||
return (statusBit & indexStatusFlags) > 0
|
||||
return this.repo.isStatusStaged(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates the path is ignored.
|
||||
@@ -570,7 +394,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
|
||||
// ignored.
|
||||
isStatusIgnored (statusBit) {
|
||||
return (statusBit & ignoredStatusFlags) > 0
|
||||
return this.repo.isStatusIgnored(statusBit)
|
||||
}
|
||||
|
||||
// Public: Returns true if the given status indicates the path is deleted.
|
||||
@@ -580,7 +404,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
|
||||
// deleted.
|
||||
isStatusDeleted (statusBit) {
|
||||
return (statusBit & deletedStatusFlags) > 0
|
||||
return this.repo.isStatusDeleted(statusBit)
|
||||
}
|
||||
|
||||
// Retrieving Diffs
|
||||
@@ -596,35 +420,7 @@ export default class GitRepositoryAsync {
|
||||
// * `added` The {Number} of added lines.
|
||||
// * `deleted` The {Number} of deleted lines.
|
||||
getDiffStats (_path) {
|
||||
return this.getRepo()
|
||||
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
|
||||
.then(([repo, headCommit]) => Promise.all([repo, headCommit.getTree()]))
|
||||
.then(([repo, tree]) => {
|
||||
const options = new Git.DiffOptions()
|
||||
options.contextLines = 0
|
||||
options.flags = Git.Diff.OPTION.DISABLE_PATHSPEC_MATCH
|
||||
options.pathspec = this.relativize(_path, repo.workdir())
|
||||
if (process.platform === 'win32') {
|
||||
// Ignore eol of line differences on windows so that files checked in
|
||||
// as LF don't report every line modified when the text contains CRLF
|
||||
// endings.
|
||||
options.flags |= Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
|
||||
}
|
||||
return Git.Diff.treeToWorkdir(repo, tree, options)
|
||||
})
|
||||
.then(diff => this._getDiffLines(diff))
|
||||
.then(lines => {
|
||||
const stats = {added: 0, deleted: 0}
|
||||
for (const line of lines) {
|
||||
const origin = line.origin()
|
||||
if (origin === Git.Diff.LINE.ADDITION) {
|
||||
stats.added++
|
||||
} else if (origin === Git.Diff.LINE.DELETION) {
|
||||
stats.deleted++
|
||||
}
|
||||
}
|
||||
return stats
|
||||
})
|
||||
return this.repo.getDiffStats(_path)
|
||||
}
|
||||
|
||||
// Public: Retrieves the line diffs comparing the `HEAD` version of the given
|
||||
@@ -639,25 +435,7 @@ export default class GitRepositoryAsync {
|
||||
// * `oldLines` The {Number} of lines in the old hunk.
|
||||
// * `newLines` The {Number} of lines in the new hunk
|
||||
getLineDiffs (_path, text) {
|
||||
let relativePath = null
|
||||
return this.getRepo()
|
||||
.then(repo => {
|
||||
relativePath = this.relativize(_path, repo.workdir())
|
||||
return repo.getHeadCommit()
|
||||
})
|
||||
.then(commit => commit.getEntry(relativePath))
|
||||
.then(entry => entry.getBlob())
|
||||
.then(blob => {
|
||||
const options = new Git.DiffOptions()
|
||||
options.contextLines = 0
|
||||
if (process.platform === 'win32') {
|
||||
// Ignore eol of line differences on windows so that files checked in
|
||||
// as LF don't report every line modified when the text contains CRLF
|
||||
// endings.
|
||||
options.flags = Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
|
||||
}
|
||||
return this._diffBlobToBuffer(blob, text, options)
|
||||
})
|
||||
return this.repo.getLineDiffs(_path, text)
|
||||
}
|
||||
|
||||
// Checking Out
|
||||
@@ -678,14 +456,7 @@ export default class GitRepositoryAsync {
|
||||
// Returns a {Promise} that resolves or rejects depending on whether the
|
||||
// method was successful.
|
||||
checkoutHead (_path) {
|
||||
return this.getRepo()
|
||||
.then(repo => {
|
||||
const checkoutOptions = new Git.CheckoutOptions()
|
||||
checkoutOptions.paths = [this.relativize(_path, repo.workdir())]
|
||||
checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH
|
||||
return Git.Checkout.head(repo, checkoutOptions)
|
||||
})
|
||||
.then(() => this.refreshStatusForPath(_path))
|
||||
return this.repo.checkoutHead(_path)
|
||||
}
|
||||
|
||||
// Public: Checks out a branch in your repository.
|
||||
@@ -696,17 +467,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Promise} that resolves if the method was successful.
|
||||
checkoutReference (reference, create) {
|
||||
return this.getRepo()
|
||||
.then(repo => repo.checkoutBranch(reference))
|
||||
.catch(error => {
|
||||
if (create) {
|
||||
return this._createBranch(reference)
|
||||
.then(_ => this.checkoutReference(reference, false))
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
.then(_ => null)
|
||||
return this.repo.checkoutReference(reference, create)
|
||||
}
|
||||
|
||||
// Private
|
||||
@@ -725,103 +486,10 @@ export default class GitRepositoryAsync {
|
||||
return this.checkoutHead(filePath)
|
||||
}
|
||||
|
||||
// Create a new branch with the given name.
|
||||
// Refreshes the git status.
|
||||
//
|
||||
// * `name` The {String} name of the new branch.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {NodeGit.Ref} reference to the
|
||||
// created branch.
|
||||
_createBranch (name) {
|
||||
return this.getRepo()
|
||||
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
|
||||
.then(([repo, commit]) => repo.createBranch(name, commit))
|
||||
}
|
||||
|
||||
// Get all the hunks in the diff.
|
||||
//
|
||||
// * `diff` The {NodeGit.Diff} whose hunks should be retrieved.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Array} of {NodeGit.Hunk}.
|
||||
_getDiffHunks (diff) {
|
||||
return diff.patches()
|
||||
.then(patches => Promise.all(patches.map(p => p.hunks()))) // patches :: Array<Patch>
|
||||
.then(hunks => _.flatten(hunks)) // hunks :: Array<Array<Hunk>>
|
||||
}
|
||||
|
||||
// Get all the lines contained in the diff.
|
||||
//
|
||||
// * `diff` The {NodeGit.Diff} use lines should be retrieved.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Array} of {NodeGit.Line}.
|
||||
_getDiffLines (diff) {
|
||||
return this._getDiffHunks(diff)
|
||||
.then(hunks => Promise.all(hunks.map(h => h.lines())))
|
||||
.then(lines => _.flatten(lines)) // lines :: Array<Array<Line>>
|
||||
}
|
||||
|
||||
// Diff the given blob and buffer with the provided options.
|
||||
//
|
||||
// * `blob` The {NodeGit.Blob}
|
||||
// * `buffer` The {String} buffer.
|
||||
// * `options` The {NodeGit.DiffOptions}
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Array} of {Object}s which have
|
||||
// the following keys:
|
||||
// * `oldStart` The {Number} of the old starting line.
|
||||
// * `newStart` The {Number} of the new starting line.
|
||||
// * `oldLines` The {Number} of old lines.
|
||||
// * `newLines` The {Number} of new lines.
|
||||
_diffBlobToBuffer (blob, buffer, options) {
|
||||
const hunks = []
|
||||
const hunkCallback = (delta, hunk, payload) => {
|
||||
hunks.push({
|
||||
oldStart: hunk.oldStart(),
|
||||
newStart: hunk.newStart(),
|
||||
oldLines: hunk.oldLines(),
|
||||
newLines: hunk.newLines()
|
||||
})
|
||||
}
|
||||
|
||||
return Git.Diff.blobToBuffer(blob, null, buffer, null, options, null, null, hunkCallback, null)
|
||||
.then(_ => hunks)
|
||||
}
|
||||
|
||||
// Get the current branch and update this.branch.
|
||||
//
|
||||
// Returns a {Promise} which resolves to a {boolean} indicating whether the
|
||||
// branch name changed.
|
||||
_refreshBranch () {
|
||||
return this.getRepo()
|
||||
.then(repo => repo.getCurrentBranch())
|
||||
.then(ref => ref.name())
|
||||
.then(branchName => {
|
||||
const changed = branchName !== this.branch
|
||||
this.branch = branchName
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh the cached ahead/behind count with the given branch.
|
||||
//
|
||||
// * `branchName` The {String} name of the branch whose ahead/behind should be
|
||||
// used for the refresh.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// the ahead/behind count changed.
|
||||
_refreshAheadBehindCount (branchName) {
|
||||
return this.getAheadBehindCount(branchName)
|
||||
.then(counts => {
|
||||
const changed = !_.isEqual(counts, this.upstream)
|
||||
this.upstream = counts
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Get the status for this repository.
|
||||
//
|
||||
// Returns a {Promise} that will resolve to an object of {String} paths to the
|
||||
// {Number} status.
|
||||
_getRepositoryStatus () {
|
||||
// Returns a {Promise} which will resolve to {null} when refresh is complete.
|
||||
refreshStatus () {
|
||||
let projectPathsPromises = [Promise.resolve('')]
|
||||
if (this.project) {
|
||||
projectPathsPromises = this.project.getPaths()
|
||||
@@ -830,162 +498,7 @@ export default class GitRepositoryAsync {
|
||||
|
||||
return Promise.all(projectPathsPromises)
|
||||
.then(paths => paths.map(p => p.length > 0 ? p + '/**' : '*'))
|
||||
.then(projectPaths => {
|
||||
return this._getStatus(projectPaths.length > 0 ? projectPaths : null)
|
||||
})
|
||||
.then(statuses => {
|
||||
const statusPairs = statuses.map(status => [status.path(), status.statusBit()])
|
||||
return _.object(statusPairs)
|
||||
})
|
||||
}
|
||||
|
||||
// Get the status for the given submodule.
|
||||
//
|
||||
// * `submodule` The {GitRepositoryAsync} for the submodule.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Object}, keyed by {String}
|
||||
// repo-relative {Number} statuses.
|
||||
async _getSubmoduleStatus (submodule) {
|
||||
// At this point, we've called submodule._refreshSubmodules(), which would
|
||||
// have refreshed the status on *its* submodules, etc. So we know that its
|
||||
// cached path statuses are up-to-date.
|
||||
//
|
||||
// Now we just need to hoist those statuses into our repository by changing
|
||||
// their paths to be relative to us.
|
||||
|
||||
const statuses = submodule.getCachedPathStatuses()
|
||||
const repoRelativeStatuses = {}
|
||||
const submoduleRepo = await submodule.getRepo()
|
||||
const submoduleWorkDir = submoduleRepo.workdir()
|
||||
for (const relativePath in statuses) {
|
||||
const statusBit = statuses[relativePath]
|
||||
const absolutePath = path.join(submoduleWorkDir, relativePath)
|
||||
const repoRelativePath = await this.relativizeToWorkingDirectory(absolutePath)
|
||||
repoRelativeStatuses[repoRelativePath] = statusBit
|
||||
}
|
||||
|
||||
return repoRelativeStatuses
|
||||
}
|
||||
|
||||
// Refresh the list of submodules in the repository.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Object} keyed by {String}
|
||||
// submodule names with {GitRepositoryAsync} values.
|
||||
async _refreshSubmodules () {
|
||||
const repo = await this.getRepo()
|
||||
const submoduleNames = await repo.getSubmoduleNames()
|
||||
for (const name of submoduleNames) {
|
||||
const alreadyExists = Boolean(this.submodules[name])
|
||||
if (alreadyExists) continue
|
||||
|
||||
const submodule = await Git.Submodule.lookup(repo, name)
|
||||
const absolutePath = path.join(repo.workdir(), submodule.path())
|
||||
const submoduleRepo = GitRepositoryAsync.open(absolutePath, {openExactPath: true, refreshOnWindowFocus: false})
|
||||
this.submodules[name] = submoduleRepo
|
||||
}
|
||||
|
||||
for (const name in this.submodules) {
|
||||
const repo = this.submodules[name]
|
||||
const gone = submoduleNames.indexOf(name) < 0
|
||||
if (gone) {
|
||||
repo.destroy()
|
||||
delete this.submodules[name]
|
||||
} else {
|
||||
try {
|
||||
await repo.refreshStatus()
|
||||
} catch (e) {
|
||||
// libgit2 will sometimes report submodules that aren't actually valid
|
||||
// (https://github.com/libgit2/libgit2/issues/3580). So check the
|
||||
// validity of the submodules by removing any that fail.
|
||||
repo.destroy()
|
||||
delete this.submodules[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _.values(this.submodules)
|
||||
}
|
||||
|
||||
// Get the status for the submodules in the repository.
|
||||
//
|
||||
// Returns a {Promise} that will resolve to an object of {String} paths to the
|
||||
// {Number} status.
|
||||
_getSubmoduleStatuses () {
|
||||
return this._refreshSubmodules()
|
||||
.then(repos => {
|
||||
return Promise.all(repos.map(repo => this._getSubmoduleStatus(repo)))
|
||||
})
|
||||
.then(statuses => _.extend({}, ...statuses))
|
||||
}
|
||||
|
||||
// Refresh the cached status.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// any statuses changed.
|
||||
_refreshStatus () {
|
||||
return Promise.all([this._getRepositoryStatus(), this._getSubmoduleStatuses()])
|
||||
.then(([repositoryStatus, submoduleStatus]) => {
|
||||
const statusesByPath = _.extend({}, repositoryStatus, submoduleStatus)
|
||||
const changed = !_.isEqual(this.pathStatusCache, statusesByPath)
|
||||
this.pathStatusCache = statusesByPath
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Refreshes the git status.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null} when refresh is complete.
|
||||
refreshStatus () {
|
||||
const status = this._refreshStatus()
|
||||
const branch = this._refreshBranch()
|
||||
const aheadBehind = branch.then(() => this._refreshAheadBehindCount(this.branch))
|
||||
|
||||
this._refreshingPromise = this._refreshingPromise.then(_ => {
|
||||
return Promise.all([status, branch, aheadBehind])
|
||||
.then(([statusChanged, branchChanged, aheadBehindChanged]) => {
|
||||
if (this.emitter && (statusChanged || branchChanged || aheadBehindChanged)) {
|
||||
this.emitter.emit('did-change-statuses')
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
// Because all these refresh steps happen asynchronously, it's entirely
|
||||
// possible the repository was destroyed while we were working. In which
|
||||
// case we should just swallow the error.
|
||||
.catch(e => {
|
||||
if (this._isDestroyed()) {
|
||||
return null
|
||||
} else {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error refreshing repository status:')
|
||||
console.error(e)
|
||||
return Promise.reject(e)
|
||||
})
|
||||
})
|
||||
return this._refreshingPromise
|
||||
}
|
||||
|
||||
// Get the submodule for the given path.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {GitRepositoryAsync} submodule or
|
||||
// null if it isn't a submodule path.
|
||||
async _submoduleForPath (_path) {
|
||||
let relativePath = await this.relativizeToWorkingDirectory(_path)
|
||||
for (const submodulePath in this.submodules) {
|
||||
const submoduleRepo = this.submodules[submodulePath]
|
||||
if (relativePath === submodulePath) {
|
||||
return submoduleRepo
|
||||
} else if (relativePath.indexOf(`${submodulePath}/`) === 0) {
|
||||
relativePath = relativePath.substring(submodulePath.length + 1)
|
||||
const innerSubmodule = await submoduleRepo._submoduleForPath(relativePath)
|
||||
return innerSubmodule || submoduleRepo
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
.then(pathspecs => this.repo.refreshStatus(pathspecs))
|
||||
}
|
||||
|
||||
// Get the NodeGit repository for the given path.
|
||||
@@ -996,16 +509,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {NodeGit.Repository}.
|
||||
getRepo (_path) {
|
||||
if (this._isDestroyed()) {
|
||||
const error = new Error('Repository has been destroyed')
|
||||
error.name = GitRepositoryAsync.DestroyedErrorName
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (!_path) return this.repoPromise
|
||||
|
||||
return this._submoduleForPath(_path)
|
||||
.then(submodule => submodule ? submodule.getRepo() : this.repoPromise)
|
||||
return this.repo.getRepo(_path)
|
||||
}
|
||||
|
||||
// Open a new instance of the underlying {NodeGit.Repository}.
|
||||
@@ -1015,11 +519,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns the new {NodeGit.Repository}.
|
||||
openRepository () {
|
||||
if (this._openExactPath) {
|
||||
return Git.Repository.open(this.openedPath)
|
||||
} else {
|
||||
return Git.Repository.openExt(this.openedPath, 0, '')
|
||||
}
|
||||
return this.repo.openRepository()
|
||||
}
|
||||
|
||||
// Section: Private
|
||||
@@ -1029,7 +529,7 @@ export default class GitRepositoryAsync {
|
||||
//
|
||||
// Returns a {Boolean}.
|
||||
_isDestroyed () {
|
||||
return this.repoPromise == null
|
||||
return this.repo._isDestroyed()
|
||||
}
|
||||
|
||||
// Subscribe to events on the given buffer.
|
||||
@@ -1055,26 +555,4 @@ export default class GitRepositoryAsync {
|
||||
|
||||
this.subscriptions.add(bufferSubscriptions)
|
||||
}
|
||||
|
||||
// Get the status for the given paths.
|
||||
//
|
||||
// * `paths` The {String} paths whose status is wanted. If undefined, get the
|
||||
// status for the whole repository.
|
||||
//
|
||||
// Returns a {Promise} which resolves to an {Array} of {NodeGit.StatusFile}
|
||||
// statuses for the paths.
|
||||
_getStatus (paths, repo) {
|
||||
return this.getRepo()
|
||||
.then(repo => {
|
||||
const opts = {
|
||||
flags: Git.Status.OPT.INCLUDE_UNTRACKED | Git.Status.OPT.RECURSE_UNTRACKED_DIRS
|
||||
}
|
||||
|
||||
if (paths) {
|
||||
opts.pathspec = paths
|
||||
}
|
||||
|
||||
return repo.getStatusExt(opts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +83,12 @@ class GitRepository
|
||||
asyncOptions.subscribeToBuffers = false
|
||||
@async = GitRepositoryAsync.open(path, asyncOptions)
|
||||
|
||||
@statuses = {}
|
||||
@upstream = {ahead: 0, behind: 0}
|
||||
for submodulePath, submoduleRepo of @repo.submodules
|
||||
submoduleRepo.upstream = {ahead: 0, behind: 0}
|
||||
|
||||
@statusesByPath = {}
|
||||
|
||||
{@project, @config, refreshOnWindowFocus} = options
|
||||
|
||||
refreshOnWindowFocus ?= true
|
||||
@@ -317,7 +318,7 @@ class GitRepository
|
||||
getDirectoryStatus: (directoryPath) ->
|
||||
directoryPath = "#{@relativize(directoryPath)}/"
|
||||
directoryStatus = 0
|
||||
for path, status of @statuses
|
||||
for path, status of _.extend({}, @async.getCachedPathStatuses(), @statusesByPath)
|
||||
directoryStatus |= status if path.indexOf(directoryPath) is 0
|
||||
directoryStatus
|
||||
|
||||
@@ -328,18 +329,26 @@ class GitRepository
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
|
||||
# This is a bit particular. If a package calls `getPathStatus` like this:
|
||||
# - change the file
|
||||
# - getPathStatus
|
||||
# - change the file
|
||||
# - getPathStatus
|
||||
# We need to preserve the guarantee that each call to `getPathStatus` will
|
||||
# synchronously emit 'did-change-status'. So we need to keep a cache of the
|
||||
# statuses found from this call.
|
||||
currentPathStatus = @getCachedRelativePathStatus(relativePath) ? 0
|
||||
|
||||
# Trigger events emitted on the async repo as well
|
||||
@async.refreshStatusForPath(path)
|
||||
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
delete @statuses[relativePath]
|
||||
@statusesByPath[relativePath] = pathStatus
|
||||
|
||||
if currentPathStatus isnt pathStatus
|
||||
@emitter.emit 'did-change-status', {path, pathStatus}
|
||||
|
||||
@@ -351,7 +360,11 @@ class GitRepository
|
||||
#
|
||||
# Returns a status {Number} or null if the path is not in the cache.
|
||||
getCachedPathStatus: (path) ->
|
||||
@statuses[@relativize(path)]
|
||||
relativePath = @relativize(path)
|
||||
@getCachedRelativePathStatus(relativePath)
|
||||
|
||||
getCachedRelativePathStatus: (relativePath) ->
|
||||
@statusesByPath[relativePath] ? @async.getCachedPathStatuses()[relativePath]
|
||||
|
||||
# Public: Returns true if the given status indicates modification.
|
||||
#
|
||||
@@ -478,24 +491,34 @@ class GitRepository
|
||||
#
|
||||
# Returns a promise that resolves when the repository has been refreshed.
|
||||
refreshStatus: ->
|
||||
asyncRefresh = @async.refreshStatus()
|
||||
statusesChanged = false
|
||||
|
||||
# Listen for `did-change-statuses` so we know if something changed. But we
|
||||
# need to wait to propagate it until after we've set the branch and cleared
|
||||
# the `statusesByPath` cache. So just set a flag, and we'll emit the event
|
||||
# after refresh is done.
|
||||
subscription = @async.onDidChangeStatuses ->
|
||||
subscription?.dispose()
|
||||
subscription = null
|
||||
|
||||
statusesChanged = true
|
||||
|
||||
asyncRefresh = @async.refreshStatus().then =>
|
||||
subscription?.dispose()
|
||||
subscription = null
|
||||
|
||||
@branch = @async?.branch
|
||||
@statusesByPath = {}
|
||||
|
||||
if statusesChanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
|
||||
syncRefresh = new Promise (resolve, reject) =>
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.map (path) -> if path.length > 0 then path + '/**' else '*'
|
||||
|
||||
@statusTask?.terminate()
|
||||
@statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and
|
||||
_.isEqual(upstream, @upstream) and
|
||||
_.isEqual(branch, @branch) and
|
||||
_.isEqual(submodules, @submodules)
|
||||
|
||||
@statuses = statuses
|
||||
@statusTask = Task.once @handlerPath, @getPath(), ({upstream, submodules}) =>
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@submodules = submodules
|
||||
|
||||
for submodulePath, submoduleRepo of @getRepo().submodules
|
||||
@@ -503,7 +526,4 @@ class GitRepository
|
||||
|
||||
resolve()
|
||||
|
||||
unless statusesUnchanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
|
||||
return Promise.all([asyncRefresh, syncRefresh])
|
||||
|
||||
@@ -71,13 +71,13 @@ class Gutter
|
||||
isVisible: ->
|
||||
@visible
|
||||
|
||||
# Essential: Add a decoration that tracks a {TextEditorMarker}. When the marker moves,
|
||||
# Essential: Add a decoration that tracks a {DisplayMarker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect
|
||||
# the marker's state.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
# * `marker` A {TextEditorMarker} you want this decoration to follow.
|
||||
# * `marker` A {DisplayMarker} you want this decoration to follow.
|
||||
# * `decorationParams` An {Object} representing the decoration. It is passed
|
||||
# to {TextEditor::decorateMarker} as its `decorationParams` and so supports
|
||||
# all options documented there.
|
||||
|
||||
@@ -4,11 +4,12 @@ module.exports = ({blobStore}) ->
|
||||
path = require 'path'
|
||||
require './window'
|
||||
{getWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
|
||||
{ipcRenderer} = require 'electron'
|
||||
{resourcePath, isSpec, devMode, env} = getWindowLoadSettings()
|
||||
|
||||
# Set baseline environment
|
||||
environmentHelpers.normalize({env: env})
|
||||
env = process.env
|
||||
|
||||
# Add application-specific exports to module search path.
|
||||
exportsPath = path.join(resourcePath, 'exports')
|
||||
@@ -25,7 +26,7 @@ module.exports = ({blobStore}) ->
|
||||
applicationDelegate: new ApplicationDelegate,
|
||||
configDirPath: process.env.ATOM_HOME
|
||||
enablePersistence: true
|
||||
env: env
|
||||
env: process.env
|
||||
})
|
||||
|
||||
atom.startEditorWindow().then ->
|
||||
@@ -35,3 +36,6 @@ module.exports = ({blobStore}) ->
|
||||
window.removeEventListener('focus', windowFocused)
|
||||
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
|
||||
window.addEventListener('focus', windowFocused)
|
||||
ipcRenderer.on('environment', (event, env) ->
|
||||
environmentHelpers.replace(env)
|
||||
)
|
||||
|
||||
@@ -90,30 +90,36 @@ class LanguageMode
|
||||
|
||||
# Folds all the foldable lines in the buffer.
|
||||
foldAll: ->
|
||||
@unfoldAll()
|
||||
foldedRowRanges = {}
|
||||
for currentRow in [0..@buffer.getLastRow()] by 1
|
||||
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
rowRange = [startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
continue unless startRow?
|
||||
@editor.createFold(startRow, endRow)
|
||||
continue if foldedRowRanges[rowRange]
|
||||
|
||||
@editor.foldBufferRowRange(startRow, endRow)
|
||||
foldedRowRanges[rowRange] = true
|
||||
return
|
||||
|
||||
# Unfolds all the foldable lines in the buffer.
|
||||
unfoldAll: ->
|
||||
for fold in @editor.displayBuffer.foldsIntersectingBufferRowRange(0, @buffer.getLastRow()) by -1
|
||||
fold.destroy()
|
||||
return
|
||||
@editor.displayLayer.destroyAllFolds()
|
||||
|
||||
# Fold all comment and code blocks at a given indentLevel
|
||||
#
|
||||
# indentLevel - A {Number} indicating indentLevel; 0 based.
|
||||
foldAllAtIndentLevel: (indentLevel) ->
|
||||
@unfoldAll()
|
||||
foldedRowRanges = {}
|
||||
for currentRow in [0..@buffer.getLastRow()] by 1
|
||||
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
rowRange = [startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
continue unless startRow?
|
||||
continue if foldedRowRanges[rowRange]
|
||||
|
||||
# assumption: startRow will always be the min indent level for the entire range
|
||||
if @editor.indentationForBufferRow(startRow) is indentLevel
|
||||
@editor.createFold(startRow, endRow)
|
||||
@editor.foldBufferRowRange(startRow, endRow)
|
||||
foldedRowRanges[rowRange] = true
|
||||
return
|
||||
|
||||
# Given a buffer row, creates a fold at it.
|
||||
@@ -125,8 +131,8 @@ class LanguageMode
|
||||
for currentRow in [bufferRow..0] by -1
|
||||
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
continue unless startRow? and startRow <= bufferRow <= endRow
|
||||
fold = @editor.displayBuffer.largestFoldStartingAtBufferRow(startRow)
|
||||
return @editor.createFold(startRow, endRow) unless fold
|
||||
unless @editor.isFoldedAtBufferRow(startRow)
|
||||
return @editor.foldBufferRowRange(startRow, endRow)
|
||||
|
||||
# Find the row range for a fold at a given bufferRow. Will handle comments
|
||||
# and code.
|
||||
@@ -140,19 +146,19 @@ class LanguageMode
|
||||
rowRange
|
||||
|
||||
rowRangeForCommentAtBufferRow: (bufferRow) ->
|
||||
return unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
return unless @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
|
||||
startRow = bufferRow
|
||||
endRow = bufferRow
|
||||
|
||||
if bufferRow > 0
|
||||
for currentRow in [bufferRow-1..0] by -1
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
|
||||
if bufferRow < @buffer.getLastRow()
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
return [startRow, endRow] if startRow isnt endRow
|
||||
@@ -175,13 +181,13 @@ class LanguageMode
|
||||
[bufferRow, foldEndRow]
|
||||
|
||||
isFoldableAtBufferRow: (bufferRow) ->
|
||||
@editor.displayBuffer.tokenizedBuffer.isFoldableAtRow(bufferRow)
|
||||
@editor.tokenizedBuffer.isFoldableAtRow(bufferRow)
|
||||
|
||||
# Returns a {Boolean} indicating whether the line at the given buffer
|
||||
# row is a comment.
|
||||
isLineCommentedAtBufferRow: (bufferRow) ->
|
||||
return false unless 0 <= bufferRow <= @editor.getLastBufferRow()
|
||||
@editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
@editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
|
||||
# Find a row range for a 'paragraph' around specified bufferRow. A paragraph
|
||||
# is a block of text bounded by and empty line or a block of text that is not
|
||||
@@ -234,11 +240,11 @@ class LanguageMode
|
||||
# Returns a {Number}.
|
||||
suggestedIndentForBufferRow: (bufferRow, options) ->
|
||||
line = @buffer.lineForRow(bufferRow)
|
||||
tokenizedLine = @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow)
|
||||
tokenizedLine = @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow)
|
||||
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
|
||||
|
||||
suggestedIndentForLineAtBufferRow: (bufferRow, line, options) ->
|
||||
tokenizedLine = @editor.displayBuffer.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
|
||||
tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
|
||||
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
|
||||
|
||||
suggestedIndentForTokenizedLineAtBufferRow: (bufferRow, line, tokenizedLine, options) ->
|
||||
|
||||
@@ -7,7 +7,7 @@ nextId = -> idCounter++
|
||||
# layer. Created via {TextEditor::decorateMarkerLayer}.
|
||||
module.exports =
|
||||
class LayerDecoration
|
||||
constructor: (@markerLayer, @displayBuffer, @properties) ->
|
||||
constructor: (@markerLayer, @decorationManager, @properties) ->
|
||||
@id = nextId()
|
||||
@destroyed = false
|
||||
@markerLayerDestroyedDisposable = @markerLayer.onDidDestroy => @destroy()
|
||||
@@ -19,7 +19,7 @@ class LayerDecoration
|
||||
@markerLayerDestroyedDisposable.dispose()
|
||||
@markerLayerDestroyedDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyLayerDecoration(this)
|
||||
@decorationManager.didDestroyLayerDecoration(this)
|
||||
|
||||
# Essential: Determine whether this decoration is destroyed.
|
||||
#
|
||||
@@ -44,11 +44,11 @@ class LayerDecoration
|
||||
setProperties: (newProperties) ->
|
||||
return if @destroyed
|
||||
@properties = newProperties
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
|
||||
# Essential: Override the decoration properties for a specific marker.
|
||||
#
|
||||
# * `marker` The {TextEditorMarker} or {Marker} for which to override
|
||||
# * `marker` The {DisplayMarker} or {Marker} for which to override
|
||||
# properties.
|
||||
# * `properties` An {Object} containing properties to apply to this marker.
|
||||
# Pass `null` to clear the override.
|
||||
@@ -58,4 +58,4 @@ class LayerDecoration
|
||||
@overridePropertiesByMarkerId[marker.id] = properties
|
||||
else
|
||||
delete @overridePropertiesByMarkerId[marker.id]
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
|
||||
@@ -93,9 +93,9 @@ class LineNumberGutterComponent extends TiledComponent
|
||||
{target} = event
|
||||
lineNumber = target.parentNode
|
||||
|
||||
if target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
|
||||
if target.classList.contains('icon-right')
|
||||
bufferRow = parseInt(lineNumber.getAttribute('data-buffer-row'))
|
||||
if lineNumber.classList.contains('folded')
|
||||
@editor.unfoldBufferRow(bufferRow)
|
||||
else
|
||||
else if lineNumber.classList.contains('foldable')
|
||||
@editor.foldBufferRow(bufferRow)
|
||||
|
||||
@@ -43,7 +43,7 @@ class LinesComponent extends TiledComponent
|
||||
@domNode
|
||||
|
||||
shouldRecreateAllTilesOnUpdate: ->
|
||||
@oldState.indentGuidesVisible isnt @newState.indentGuidesVisible or @newState.continuousReflow
|
||||
@newState.continuousReflow
|
||||
|
||||
beforeUpdateSync: (state) ->
|
||||
if @newState.maxHeight isnt @oldState.maxHeight
|
||||
@@ -70,8 +70,6 @@ class LinesComponent extends TiledComponent
|
||||
|
||||
@cursorsComponent.updateSync(state)
|
||||
|
||||
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
|
||||
|
||||
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert, @grammars})
|
||||
|
||||
buildEmptyState: ->
|
||||
@@ -97,10 +95,14 @@ class LinesComponent extends TiledComponent
|
||||
@presenter.setLineHeight(lineHeightInPixels)
|
||||
@presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
|
||||
|
||||
lineNodeForLineIdAndScreenRow: (lineId, screenRow) ->
|
||||
lineIdForScreenRow: (screenRow) ->
|
||||
tile = @presenter.tileForRow(screenRow)
|
||||
@getComponentForTile(tile)?.lineNodeForLineId(lineId)
|
||||
@getComponentForTile(tile)?.lineIdForScreenRow(screenRow)
|
||||
|
||||
textNodesForLineIdAndScreenRow: (lineId, screenRow) ->
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
tile = @presenter.tileForRow(screenRow)
|
||||
@getComponentForTile(tile)?.textNodesForLineId(lineId)
|
||||
@getComponentForTile(tile)?.lineNodeForScreenRow(screenRow)
|
||||
|
||||
textNodesForScreenRow: (screenRow) ->
|
||||
tile = @presenter.tileForRow(screenRow)
|
||||
@getComponentForTile(tile)?.textNodesForScreenRow(screenRow)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user