mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge branch 'master' into ns-use-display-layers
# Conflicts: # package.json # src/display-buffer.coffee # src/text-editor.coffee # src/tokenized-buffer.coffee
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ debug.log
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
out/
|
||||
|
||||
@@ -365,7 +365,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
|
||||
| `blocked` | [search][search-atom-repo-label-blocked] | [search][search-atom-org-label-blocked] | Issues blocked on other issues. |
|
||||
| `duplicate` | [search][search-atom-repo-label-duplicate] | [search][search-atom-org-label-duplicate] | Issues which are duplicates of other issues, i.e. they have been reported before. |
|
||||
| `wontfix` | [search][search-atom-repo-label-wontfix] | [search][search-atom-org-label-wontfix] | The Atom core team has decided not to fix these issues for now, either because they're working as intended or for some other reason. |
|
||||
| `invalid` | [search][search-atom-repo-label-invalid] | [search][search-atom-org-label-invalid] | Issues which are't valid (e.g. user errors). |
|
||||
| `invalid` | [search][search-atom-repo-label-invalid] | [search][search-atom-org-label-invalid] | Issues which aren't valid (e.g. user errors). |
|
||||
| `package-idea` | [search][search-atom-repo-label-package-idea] | [search][search-atom-org-label-package-idea] | Feature request which might be good candidates for new packages, instead of extending Atom or core Atom packages. |
|
||||
| `wrong-repo` | [search][search-atom-repo-label-wrong-repo] | [search][search-atom-org-label-wrong-repo] | Issues reported on the wrong repository (e.g. a bug related to the [Settings View package](https://github.com/atom/settings-view) was reported on [Atom core](https://github.com/atom/atom)). |
|
||||
|
||||
@@ -406,10 +406,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
|
||||
|
||||
| Label name | `atom/atom` :mag_right: | `atom`‑org :mag_right: | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `in-progress` | [search][search-atom-repo-label-in-progress] | [search][search-atom-org-label-in-progress] | Tasks which the Atom core team is working on currently. |
|
||||
| `on-deck` | [search][search-atom-repo-label-on-deck] | [search][search-atom-org-label-on-deck] | Tasks which the Atom core team plans to work on next. |
|
||||
| `shipping` | [search][search-atom-repo-label-shipping] | [search][search-atom-org-label-shipping] | Tasks which the Atom core team completed and will be released in one of the next releases. |
|
||||
| `post-1.0-roadmap` | [search][search-atom-repo-label-post-1.0-roadmap] | [search][search-atom-org-label-post-1.0-roadmap] | The Atom core team's roadmap post version 1.0.0. |
|
||||
| `atom` | [search][search-atom-repo-label-atom] | [search][search-atom-org-label-atom] | Topics discussed for prioritization at the next meeting of Atom core team members. |
|
||||
|
||||
#### Pull Request Labels
|
||||
@@ -498,14 +494,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
|
||||
[search-atom-org-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Adeprecation-help
|
||||
[search-atom-repo-label-electron]: https://github.com/issues?q=is%3Aissue+repo%3Aatom%2Fatom+is%3Aopen+label%3Aelectron
|
||||
[search-atom-org-label-electron]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aelectron
|
||||
[search-atom-repo-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aon-deck
|
||||
[search-atom-org-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aon-deck
|
||||
[search-atom-repo-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ain-progress
|
||||
[search-atom-org-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Ain-progress
|
||||
[search-atom-repo-label-shipping]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ashipping
|
||||
[search-atom-org-label-shipping]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Ashipping
|
||||
[search-atom-repo-label-post-1.0-roadmap]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Apost-1.0-roadmap
|
||||
[search-atom-org-label-post-1.0-roadmap]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Apost-1.0-roadmap
|
||||
[search-atom-repo-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aatom
|
||||
[search-atom-org-label-atom]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aatom
|
||||
[search-atom-repo-label-work-in-progress]: https://github.com/pulls?q=is%3Aopen+is%3Apr+repo%3Aatom%2Fatom+label%3Awork-in-progress
|
||||
|
||||
28
ISSUE_TEMPLATE.md
Normal file
28
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,28 @@
|
||||
### 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)?
|
||||
* [ ] Did you check the [FAQs on Discuss](https://discuss.atom.io/c/faq)?
|
||||
* [ ] Are you reporting to the [correct repository](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#atom-and-packages)?
|
||||
* [ ] Did you [perform a cursory search](https://github.com/issues?q=is%3Aissue+user%3Aatom+-repo%3Aatom%2Felectron) to see if your bug or enhancement is already reported?
|
||||
|
||||
For more information on how to write a good [bug report](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#how-do-i-submit-a-good-bug-report) or [enhancement request](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#how-do-i-submit-a-good-enhancement-suggestion), see the `CONTRIBUTING` guide.
|
||||
|
||||
### Description
|
||||
|
||||
[Description of the bug or feature]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [and so on...]
|
||||
|
||||
**Expected behavior:** [What you expected to happen]
|
||||
|
||||
**Actual behavior:** [What actually happened]
|
||||
|
||||
### Versions
|
||||
|
||||
You can get this information from executing `atom --version` and `apm --version` at the command line.
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "1.6.0"
|
||||
"atom-package-manager": "1.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
2
atom.sh
2
atom.sh
@@ -4,8 +4,6 @@ if [ "$(uname)" == 'Darwin' ]; then
|
||||
OS='Mac'
|
||||
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
|
||||
OS='Linux'
|
||||
elif [ "$(expr substr $(uname -s) 1 10)" == 'MINGW32_NT' ]; then
|
||||
OS='Cygwin'
|
||||
else
|
||||
echo "Your platform ($(uname -a)) is not supported."
|
||||
exit 1
|
||||
|
||||
@@ -34,23 +34,10 @@ module.exports = (grunt) ->
|
||||
grunt.file.setBase(path.resolve('..'))
|
||||
|
||||
# Options
|
||||
[defaultChannel, releaseBranch] = getDefaultChannelAndReleaseBranch(packageJson.version)
|
||||
installDir = grunt.option('install-dir')
|
||||
buildDir = grunt.option('build-dir')
|
||||
buildDir ?= 'out'
|
||||
buildDir = path.resolve(buildDir)
|
||||
|
||||
channel = grunt.option('channel')
|
||||
releasableBranches = ['stable', 'beta']
|
||||
if process.env.APPVEYOR and not process.env.APPVEYOR_PULL_REQUEST_NUMBER
|
||||
channel ?= process.env.APPVEYOR_REPO_BRANCH if process.env.APPVEYOR_REPO_BRANCH in releasableBranches
|
||||
|
||||
if process.env.TRAVIS and not process.env.TRAVIS_PULL_REQUEST
|
||||
channel ?= process.env.TRAVIS_BRANCH if process.env.TRAVIS_BRANCH in releasableBranches
|
||||
|
||||
if process.env.JANKY_BRANCH
|
||||
channel ?= process.env.JANKY_BRANCH if process.env.JANKY_BRANCH in releasableBranches
|
||||
|
||||
channel ?= 'dev'
|
||||
buildDir = path.resolve(grunt.option('build-dir') ? 'out')
|
||||
channel = grunt.option('channel') ? defaultChannel
|
||||
|
||||
metadata = packageJson
|
||||
appName = packageJson.productName
|
||||
@@ -189,7 +176,7 @@ module.exports = (grunt) ->
|
||||
pkg: grunt.file.readJSON('package.json')
|
||||
|
||||
atom: {
|
||||
appName, channel, metadata,
|
||||
appName, channel, metadata, releaseBranch,
|
||||
appFileName, apmFileName,
|
||||
appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir,
|
||||
}
|
||||
@@ -310,3 +297,20 @@ module.exports = (grunt) ->
|
||||
unless process.platform is 'linux' or grunt.option('no-install')
|
||||
defaultTasks.push 'install'
|
||||
grunt.registerTask('default', defaultTasks)
|
||||
|
||||
getDefaultChannelAndReleaseBranch = (version) ->
|
||||
if version.match(/dev/) or isBuildingPR()
|
||||
channel = 'dev'
|
||||
releaseBranch = null
|
||||
else
|
||||
if version.match(/beta/)
|
||||
channel = 'beta'
|
||||
else
|
||||
channel = 'stable'
|
||||
|
||||
minorVersion = version.match(/^\d\.\d/)[0]
|
||||
releaseBranch = "#{minorVersion}-releases"
|
||||
[channel, releaseBranch]
|
||||
|
||||
isBuildingPR = ->
|
||||
process.env.APPVEYOR_PULL_REQUEST_NUMBER? or process.env.TRAVIS_PULL_REQUEST?
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"grunt-contrib-less": "~0.8.0",
|
||||
"grunt-cson": "0.16.0",
|
||||
"grunt-download-electron": "^2.1.1",
|
||||
"grunt-electron-installer": "1.0.6",
|
||||
"grunt-electron-installer": "1.2.2",
|
||||
"grunt-lesslint": "0.17.0",
|
||||
"grunt-peg": "~1.1.0",
|
||||
"grunt-shell": "~0.3.1",
|
||||
|
||||
@@ -31,14 +31,9 @@ module.exports = (gruntObject) ->
|
||||
cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')
|
||||
|
||||
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
|
||||
channel = grunt.config.get('atom.channel')
|
||||
switch channel
|
||||
when 'stable'
|
||||
isPrerelease = false
|
||||
when 'beta'
|
||||
isPrerelease = true
|
||||
else
|
||||
return
|
||||
releaseBranch = grunt.config.get('atom.releaseBranch')
|
||||
isPrerelease = grunt.config.get('atom.channel') is 'beta'
|
||||
return unless releaseBranch?
|
||||
|
||||
doneCallback = @async()
|
||||
startTime = Date.now()
|
||||
@@ -55,7 +50,7 @@ module.exports = (gruntObject) ->
|
||||
|
||||
zipAssets buildDir, assets, (error) ->
|
||||
return done(error) if error?
|
||||
getAtomDraftRelease isPrerelease, channel, (error, release) ->
|
||||
getAtomDraftRelease isPrerelease, releaseBranch, (error, release) ->
|
||||
return done(error) if error?
|
||||
assetNames = (asset.assetName for asset in assets)
|
||||
deleteExistingAssets release, assetNames, (error) ->
|
||||
|
||||
@@ -5,9 +5,7 @@ module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
getVersion = (callback) ->
|
||||
releasableBranches = ['stable', 'beta']
|
||||
channel = grunt.config.get('atom.channel')
|
||||
shouldUseCommitHash = if channel in releasableBranches then false else true
|
||||
shouldUseCommitHash = grunt.config.get('atom.channel') is 'dev'
|
||||
inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git'))
|
||||
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
|
||||
if shouldUseCommitHash and inRepository
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
temp = require('temp').track()
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
async = require 'async'
|
||||
@@ -86,7 +87,7 @@ module.exports = (grunt) ->
|
||||
packageSpecQueue.concurrency = Math.max(1, concurrency - 1)
|
||||
packageSpecQueue.drain = -> callback(null, failedPackages)
|
||||
|
||||
runCoreSpecs = (callback) ->
|
||||
runCoreSpecs = (callback, logOutput = false) ->
|
||||
appPath = getAppPath()
|
||||
resourcePath = process.cwd()
|
||||
coreSpecsPath = path.resolve('spec')
|
||||
@@ -94,7 +95,7 @@ module.exports = (grunt) ->
|
||||
if process.platform in ['darwin', 'linux']
|
||||
options =
|
||||
cmd: appPath
|
||||
args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath]
|
||||
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
|
||||
@@ -109,6 +110,9 @@ module.exports = (grunt) ->
|
||||
ATOM_INTEGRATION_TESTS_ENABLED: true
|
||||
)
|
||||
|
||||
if logOutput
|
||||
options.opts.stdio = 'inherit'
|
||||
|
||||
grunt.log.ok "Launching core specs."
|
||||
spawn options, (error, results, code) ->
|
||||
if process.platform is 'win32'
|
||||
@@ -130,11 +134,17 @@ 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'
|
||||
[runCoreSpecs]
|
||||
[runCoreSpecsWithLogging]
|
||||
else
|
||||
[runCoreSpecs, runPackageSpecs]
|
||||
|
||||
|
||||
@@ -52,8 +52,10 @@ module.exports = (grunt) ->
|
||||
stderr = []
|
||||
error = null
|
||||
proc = childProcess.spawn(options.cmd, options.args, options.opts)
|
||||
proc.stdout.on 'data', (data) -> stdout.push(data.toString())
|
||||
proc.stderr.on 'data', (data) -> stderr.push(data.toString())
|
||||
if proc.stdout?
|
||||
proc.stdout.on 'data', (data) -> stdout.push(data.toString())
|
||||
if proc.stderr?
|
||||
proc.stderr.on 'data', (data) -> stderr.push(data.toString())
|
||||
proc.on 'error', (processError) -> error ?= processError
|
||||
proc.on 'close', (exitCode, signal) ->
|
||||
error ?= new Error(signal) if exitCode isnt 0
|
||||
|
||||
4
circle.yml
Normal file
4
circle.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
general:
|
||||
branches:
|
||||
only:
|
||||
- io-circle-ci
|
||||
@@ -64,7 +64,7 @@ If you have problems with permissions don't forget to prefix with `sudo`
|
||||
script/build
|
||||
```
|
||||
|
||||
This will create the atom application at `$TMPDIR/atom-build/Atom`.
|
||||
This will create the atom application at `out/Atom`.
|
||||
|
||||
4. Install the `atom` and `apm` commands to `/usr/local/bin` by executing:
|
||||
|
||||
@@ -74,7 +74,7 @@ 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 `$TMPDIR/atom-build`. 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. To create a `.deb` package run:
|
||||
|
||||
```sh
|
||||
script/grunt mkdeb
|
||||
|
||||
@@ -3,32 +3,40 @@
|
||||
## Requirements
|
||||
|
||||
### General
|
||||
* [Node.js](http://nodejs.org/en/download/) v4.x
|
||||
* [Python](https://www.python.org/downloads/) v2.7.x
|
||||
* [Node.js](http://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
|
||||
directory containing the python.exe using:
|
||||
`mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27`
|
||||
* [GitHub Desktop](http://desktop.github.com/)
|
||||
|
||||
### On Windows 7
|
||||
* [Visual Studio 2013 Update 5](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_4)
|
||||
### Visual Studio
|
||||
|
||||
### On Windows 8 or 10
|
||||
* [Visual Studio Express 2013 or 2015 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2)
|
||||
* To ensure that node-gyp knows what version of Visual Studio is installed, set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (e.g. `2013` or `2015`)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
## 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
|
||||
# Use the Git Shell program which was installed by GitHub Desktop
|
||||
cd C:\
|
||||
git clone https://github.com/atom/atom/
|
||||
cd atom
|
||||
script/build # Creates application in the `Program Files` directory
|
||||
script/build
|
||||
```
|
||||
Note: If you use cmd or Powershell instead of Git Shell, use a backslash instead: i.e. `script\build`.
|
||||
These instructions will assume the use of Git Shell.
|
||||
This will create the Atom application in the `Program Files` folder.
|
||||
|
||||
### `script/build` Options
|
||||
* `--install-dir` - Creates the final built application in this directory. Example (trailing slash is optional):
|
||||
@@ -41,24 +49,25 @@ These instructions will assume the use of Git Shell.
|
||||
```
|
||||
* `--verbose` - Verbose mode. A lot more information output.
|
||||
|
||||
## Why do I have to use GitHub Desktop?
|
||||
## Do I have to use GitHub Desktop?
|
||||
|
||||
You don't. You can use your existing Git! GitHub Desktop's Git Shell is just easier to set up.
|
||||
No, you can use your existing Git! GitHub Desktop's Git Shell is just easier to set up.
|
||||
|
||||
If you _prefer_ using your existing Git installation, make sure git's cmd directory is in your PATH env variable (e.g. `C:\Program Files (x86)\Git\cmd`) before you open your powershell or command window.
|
||||
Note that you may have to open your command window as administrator. For powershell that doesn't seem to always be the case, though.
|
||||
If you _prefer_ using your existing Git installation, make sure git's cmd directory is in your PATH env variable (e.g. `C:\Program Files (x86)\Git\cmd`) before you open your PowerShell or Command Prompt.
|
||||
|
||||
If none of this works, do install Github Desktop and use its Git shell. Makes life easier.
|
||||
It is also recommended you open your Command Prompt or PowerShell as Administrator.
|
||||
|
||||
If none of this works, do install Github Desktop and use its Git Shell as it makes life easier.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Errors
|
||||
* `node is not recognized`
|
||||
|
||||
* If you just installed node, you'll need to restart your computer before node is
|
||||
available on your Path.
|
||||
* 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 and Python versions before returning
|
||||
* `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).
|
||||
@@ -67,7 +76,7 @@ If none of this works, do install Github Desktop and use its Git shell. Makes li
|
||||
|
||||
* 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 modules and there is no known workaround.
|
||||
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
|
||||
|
||||
@@ -75,19 +84,20 @@ If none of this works, do install Github Desktop and use its Git shell. Makes li
|
||||
|
||||
* See the next item.
|
||||
|
||||
* `error MSB8020: The build tools for Visual Studio 2010 (Platform Toolset = 'v100') cannot be found.`
|
||||
* `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`:
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
* Other `node-gyp` errors on first build attempt, even though the right node and python versions are installed.
|
||||
* 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.
|
||||
|
||||
### Windows build error reports in atom/atom
|
||||
* If all fails, use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues) to get a list of reports about build errors on Windows, and see if yours has already been reported.
|
||||
* If it hasn't, please open a new issue with your Windows version, architecture (x86 or amd64), and a screenshot of your build output, including the Node and Python versions.
|
||||
* If it hasn't, please open a new issue with your Windows version, architecture (x86 or amd64), and a screenshot of your build output, including the Node.js and Python versions.
|
||||
|
||||
@@ -73,8 +73,10 @@
|
||||
'cmd-alt-right': 'pane:show-next-item'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-tab': 'pane:show-next-recently-used-item'
|
||||
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
|
||||
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'cmd-=': 'window:increase-font-size'
|
||||
'cmd-+': 'window:increase-font-size'
|
||||
'cmd--': 'window:decrease-font-size'
|
||||
|
||||
@@ -46,8 +46,10 @@
|
||||
'pagedown': 'core:page-down'
|
||||
'backspace': 'core:backspace'
|
||||
'shift-backspace': 'core:backspace'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-tab': 'pane:show-next-recently-used-item'
|
||||
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
|
||||
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-up': 'core:move-up'
|
||||
|
||||
@@ -52,8 +52,10 @@
|
||||
'pagedown': 'core:page-down'
|
||||
'backspace': 'core:backspace'
|
||||
'shift-backspace': 'core:backspace'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-tab': 'pane:show-next-recently-used-item'
|
||||
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
|
||||
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-shift-up': 'core:move-up'
|
||||
|
||||
@@ -200,7 +200,6 @@
|
||||
submenu: [
|
||||
{ label: 'Terms of Use', command: 'application:open-terms-of-use' }
|
||||
{ label: 'Documentation', command: 'application:open-documentation' }
|
||||
{ label: 'Roadmap', command: 'application:open-roadmap' }
|
||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
||||
|
||||
@@ -174,7 +174,6 @@
|
||||
{ label: "VERSION", enabled: false }
|
||||
{ type: 'separator' }
|
||||
{ label: '&Documentation', command: 'application:open-documentation' }
|
||||
{ label: 'Roadmap', command: 'application:open-roadmap' }
|
||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
||||
|
||||
@@ -177,7 +177,6 @@
|
||||
{ label: 'Downloading Update', enabled: false, visible: false}
|
||||
{ type: 'separator' }
|
||||
{ label: '&Documentation', command: 'application:open-documentation' }
|
||||
{ label: 'Roadmap', command: 'application:open-roadmap' }
|
||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
||||
|
||||
87
package.json
87
package.json
@@ -12,10 +12,10 @@
|
||||
"url": "https://github.com/atom/atom/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"electronVersion": "0.34.5",
|
||||
"electronVersion": "0.36.8",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^6.2.0",
|
||||
"atom-keymap": "^6.3.1",
|
||||
"babel-core": "^5.8.21",
|
||||
"bootstrap": "^3.3.4",
|
||||
"cached-run-in-this-context": "0.4.1",
|
||||
@@ -28,7 +28,7 @@
|
||||
"fs-plus": "^2.8.0",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"git-utils": "^4.1.0",
|
||||
"git-utils": "^4.1.2",
|
||||
"grim": "1.5.0",
|
||||
"jasmine-json": "~0.0",
|
||||
"jasmine-tagged": "^1.1.4",
|
||||
@@ -38,7 +38,7 @@
|
||||
"line-top-index": "0.2.0",
|
||||
"marked": "^0.3.4",
|
||||
"marker-index": "^3.0.4",
|
||||
"nodegit": "0.9.0",
|
||||
"nodegit": "0.11.9",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^3",
|
||||
"oniguruma": "^5",
|
||||
@@ -55,7 +55,7 @@
|
||||
"service-hub": "^0.7.0",
|
||||
"source-map-support": "^0.3.2",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "8.2.1",
|
||||
"text-buffer": "8.4.1",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": "^3.23.0"
|
||||
@@ -67,70 +67,70 @@
|
||||
"atom-light-ui": "0.43.0",
|
||||
"base16-tomorrow-dark-theme": "1.1.0",
|
||||
"base16-tomorrow-light-theme": "1.1.1",
|
||||
"one-dark-ui": "1.1.9",
|
||||
"one-light-ui": "1.1.9",
|
||||
"one-dark-ui": "1.2.0",
|
||||
"one-light-ui": "1.2.0",
|
||||
"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.3.0",
|
||||
"archive-view": "0.61.0",
|
||||
"about": "1.4.0",
|
||||
"archive-view": "0.61.1",
|
||||
"autocomplete-atom-api": "0.10.0",
|
||||
"autocomplete-css": "0.11.0",
|
||||
"autocomplete-html": "0.7.2",
|
||||
"autocomplete-plus": "2.25.0",
|
||||
"autocomplete-plus": "2.29.1",
|
||||
"autocomplete-snippets": "1.10.0",
|
||||
"autoflow": "0.27.0",
|
||||
"autosave": "0.23.0",
|
||||
"autosave": "0.23.1",
|
||||
"background-tips": "0.26.0",
|
||||
"bookmarks": "0.38.2",
|
||||
"bracket-matcher": "0.79.0",
|
||||
"bracket-matcher": "0.81.0",
|
||||
"command-palette": "0.38.0",
|
||||
"deprecation-cop": "0.54.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.1",
|
||||
"fuzzy-finder": "0.94.0",
|
||||
"git-diff": "0.57.0",
|
||||
"find-and-replace": "0.197.4",
|
||||
"fuzzy-finder": "1.0.3",
|
||||
"git-diff": "1.0.0",
|
||||
"go-to-line": "0.30.0",
|
||||
"grammar-selector": "0.48.0",
|
||||
"image-view": "0.56.0",
|
||||
"incompatible-packages": "0.25.0",
|
||||
"keybinding-resolver": "0.33.0",
|
||||
"line-ending-selector": "0.3.0",
|
||||
"grammar-selector": "0.48.1",
|
||||
"image-view": "0.57.0",
|
||||
"incompatible-packages": "0.25.1",
|
||||
"keybinding-resolver": "0.35.0",
|
||||
"line-ending-selector": "0.3.1",
|
||||
"link": "0.31.0",
|
||||
"markdown-preview": "0.157.2",
|
||||
"markdown-preview": "0.157.3",
|
||||
"metrics": "0.53.1",
|
||||
"notifications": "0.62.1",
|
||||
"open-on-github": "0.41.0",
|
||||
"package-generator": "0.41.0",
|
||||
"settings-view": "0.232.3",
|
||||
"notifications": "0.62.3",
|
||||
"open-on-github": "1.0.0",
|
||||
"package-generator": "0.41.1",
|
||||
"settings-view": "0.232.4",
|
||||
"snippets": "1.0.1",
|
||||
"spell-check": "0.65.0",
|
||||
"status-bar": "0.83.0",
|
||||
"styleguide": "0.45.1",
|
||||
"symbols-view": "0.110.1",
|
||||
"tabs": "0.90.0",
|
||||
"timecop": "0.33.0",
|
||||
"tree-view": "0.201.0",
|
||||
"spell-check": "0.67.0",
|
||||
"status-bar": "1.1.0",
|
||||
"styleguide": "0.45.2",
|
||||
"symbols-view": "0.112.0",
|
||||
"tabs": "0.91.3",
|
||||
"timecop": "0.33.1",
|
||||
"tree-view": "0.203.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.33.0",
|
||||
"whitespace": "0.32.1",
|
||||
"welcome": "0.34.0",
|
||||
"whitespace": "0.32.2",
|
||||
"wrap-guide": "0.38.1",
|
||||
"language-c": "0.51.1",
|
||||
"language-clojure": "0.19.1",
|
||||
"language-coffee-script": "0.46.0",
|
||||
"language-coffee-script": "0.46.1",
|
||||
"language-csharp": "0.11.0",
|
||||
"language-css": "0.36.0",
|
||||
"language-gfm": "0.84.0",
|
||||
"language-gfm": "0.85.0",
|
||||
"language-git": "0.12.1",
|
||||
"language-go": "0.42.0",
|
||||
"language-html": "0.44.0",
|
||||
"language-html": "0.44.1",
|
||||
"language-hyperlink": "0.16.0",
|
||||
"language-java": "0.17.0",
|
||||
"language-javascript": "0.110.0",
|
||||
"language-json": "0.17.4",
|
||||
"language-json": "0.17.5",
|
||||
"language-less": "0.29.0",
|
||||
"language-make": "0.21.0",
|
||||
"language-mustache": "0.13.0",
|
||||
@@ -139,16 +139,16 @@
|
||||
"language-php": "0.37.0",
|
||||
"language-property-list": "0.8.0",
|
||||
"language-python": "0.43.0",
|
||||
"language-ruby": "0.68.0",
|
||||
"language-ruby": "0.68.1",
|
||||
"language-ruby-on-rails": "0.25.0",
|
||||
"language-sass": "0.45.0",
|
||||
"language-shellscript": "0.21.0",
|
||||
"language-source": "0.9.0",
|
||||
"language-sql": "0.20.0",
|
||||
"language-text": "0.7.0",
|
||||
"language-text": "0.7.1",
|
||||
"language-todo": "0.27.0",
|
||||
"language-toml": "0.18.0",
|
||||
"language-xml": "0.34.2",
|
||||
"language-xml": "0.34.4",
|
||||
"language-yaml": "0.25.1"
|
||||
},
|
||||
"private": true,
|
||||
@@ -174,7 +174,8 @@
|
||||
"runs",
|
||||
"spyOn",
|
||||
"waitsFor",
|
||||
"waitsForPromise"
|
||||
"waitsForPromise",
|
||||
"indexedDB"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,31 +22,13 @@ FOR %%a IN (%*) DO (
|
||||
)
|
||||
)
|
||||
|
||||
rem Getting the process ID in cmd of the current cmd process: http://superuser.com/questions/881789/identify-and-kill-batch-script-started-before
|
||||
set T=%TEMP%\atomCmdProcessId-%time::=%.tmp
|
||||
wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") get ParentProcessId /value | find "ParentProcessId" >%T%
|
||||
set /P A=<%T%
|
||||
set PID=%A:~16%
|
||||
del %T%
|
||||
|
||||
IF "%EXPECT_OUTPUT%"=="YES" (
|
||||
SET ELECTRON_ENABLE_LOGGING=YES
|
||||
IF "%WAIT%"=="YES" (
|
||||
"%~dp0\..\..\atom.exe" --pid=%PID% %*
|
||||
rem If the wait flag is set, don't exit this process until Atom tells it to.
|
||||
goto waitLoop
|
||||
)
|
||||
ELSE (
|
||||
powershell -noexit "%~dp0\..\..\atom.exe" --pid=$pid %* ; wait-event
|
||||
) ELSE (
|
||||
"%~dp0\..\..\atom.exe" %*
|
||||
)
|
||||
) ELSE (
|
||||
"%~dp0\..\app\apm\bin\node.exe" "%~dp0\atom.js" %*
|
||||
)
|
||||
|
||||
goto end
|
||||
|
||||
:waitLoop
|
||||
sleep 1
|
||||
goto waitLoop
|
||||
|
||||
:end
|
||||
|
||||
@@ -1,49 +1,2 @@
|
||||
#!/bin/sh
|
||||
|
||||
while getopts ":fhtvw-:" opt; do
|
||||
case "$opt" in
|
||||
-)
|
||||
case "${OPTARG}" in
|
||||
wait)
|
||||
WAIT=1
|
||||
;;
|
||||
help|version)
|
||||
REDIRECT_STDERR=1
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
foreground|test)
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
w)
|
||||
WAIT=1
|
||||
;;
|
||||
h|v)
|
||||
REDIRECT_STDERR=1
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
f|t)
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
directory=$(dirname "$0")
|
||||
|
||||
WINPS=`ps | grep -i $$`
|
||||
PID=`echo $WINPS | cut -d' ' -f 4`
|
||||
|
||||
if [ $EXPECT_OUTPUT ]; then
|
||||
export ELECTRON_ENABLE_LOGGING=1
|
||||
"$directory/../../atom.exe" --executed-from="$(pwd)" --pid=$PID "$@"
|
||||
else
|
||||
"$directory/../app/apm/bin/node.exe" "$directory/atom.js" "$@"
|
||||
fi
|
||||
|
||||
# If the wait flag is set, don't exit this process until Atom tells it to.
|
||||
if [ $WAIT ]; then
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
$(dirname "$0")/atom.cmd "$@"
|
||||
|
||||
37
script/clean
37
script/clean
@@ -4,15 +4,16 @@ var fs = require('fs');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
|
||||
var removeCommand = process.platform === 'win32' ? 'rmdir /S /Q ' : 'rm -rf ';
|
||||
var isWindows = process.platform === 'win32';
|
||||
var removeCommand = isWindows ? 'rmdir /S /Q ' : 'rm -rf ';
|
||||
var productName = require('../package.json').productName;
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
var home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
|
||||
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 = process.platform === 'win32' ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true';
|
||||
var killatom = isWindows ? 'START taskkill /F /IM ' + productName + '.exe' : 'pkill -9 ' + productName + ' || true';
|
||||
|
||||
var commands = [
|
||||
killatom,
|
||||
@@ -39,12 +40,32 @@ var run = function() {
|
||||
|
||||
if (Array.isArray(next)) {
|
||||
var pathToRemove = path.resolve.apply(path.resolve, next);
|
||||
if (fs.existsSync(pathToRemove))
|
||||
next = removeCommand + pathToRemove;
|
||||
else
|
||||
if (fs.existsSync(pathToRemove)) {
|
||||
if (isWindows) {
|
||||
removeFolderRecursive(pathToRemove);
|
||||
} else {
|
||||
next = removeCommand + pathToRemove;
|
||||
cp.safeExec(next, run);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return run();
|
||||
}
|
||||
}
|
||||
|
||||
cp.safeExec(next, run);
|
||||
else
|
||||
cp.safeExec(next, run);
|
||||
};
|
||||
run();
|
||||
|
||||
// Windows has a 260-char path limit for rmdir etc. Just recursively delete in Node.
|
||||
var removeFolderRecursive = function(folderPath) {
|
||||
fs.readdirSync(folderPath).forEach(function(entry, index) {
|
||||
var entryPath = path.join(folderPath, entry);
|
||||
if (fs.lstatSync(entryPath).isDirectory()) {
|
||||
removeFolderRecursive(entryPath);
|
||||
} else {
|
||||
fs.unlinkSync(entryPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(folderPath);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,9 @@ exports.afterEach = (fn) ->
|
||||
|
||||
waitsForPromise = (fn) ->
|
||||
promise = fn()
|
||||
waitsFor 'spec promise to resolve', 30000, (done) ->
|
||||
# This timeout is 3 minutes. We need to bump it back down once we fix backgrounding
|
||||
# of the renderer process on CI. See https://github.com/atom/electron/issues/4317
|
||||
waitsFor 'spec promise to resolve', 3 * 60 * 1000, (done) ->
|
||||
promise.then(
|
||||
done,
|
||||
(error) ->
|
||||
|
||||
@@ -152,6 +152,8 @@ describe "AtomEnvironment", ->
|
||||
atom.enablePersistence = false
|
||||
|
||||
it "selects the state based on the current project paths", ->
|
||||
jasmine.useRealClock()
|
||||
|
||||
[dir1, dir2] = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
|
||||
|
||||
loadSettings = _.extend atom.getLoadSettings(),
|
||||
@@ -159,20 +161,55 @@ describe "AtomEnvironment", ->
|
||||
windowState: null
|
||||
|
||||
spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings
|
||||
spyOn(atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
|
||||
spyOn(atom, 'serialize').andReturn({stuff: 'cool'})
|
||||
|
||||
atom.state.stuff = "cool"
|
||||
atom.project.setPaths([dir1, dir2])
|
||||
atom.saveStateSync()
|
||||
# State persistence will fail if other Atom instances are running
|
||||
waitsForPromise ->
|
||||
atom.stateStore.connect().then (isConnected) ->
|
||||
expect(isConnected).toBe true
|
||||
|
||||
atom.state = {}
|
||||
atom.loadStateSync()
|
||||
expect(atom.state.stuff).toBeUndefined()
|
||||
waitsForPromise ->
|
||||
atom.saveState().then ->
|
||||
atom.loadState().then (state) ->
|
||||
expect(state).toBeNull()
|
||||
|
||||
loadSettings.initialPaths = [dir2, dir1]
|
||||
atom.state = {}
|
||||
atom.loadStateSync()
|
||||
expect(atom.state.stuff).toBe("cool")
|
||||
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", ->
|
||||
spyOn(atom, 'saveState')
|
||||
|
||||
keydown = new KeyboardEvent('keydown')
|
||||
atom.document.dispatchEvent(keydown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atom.saveState.reset()
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atom.saveState.reset()
|
||||
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})
|
||||
|
||||
it "serializes the project state with all the options supplied in saveState", ->
|
||||
spyOn(atom.project, 'serialize').andReturn({foo: 42})
|
||||
|
||||
waitsForPromise -> atom.saveState({anyOption: 'any option'})
|
||||
runs ->
|
||||
expect(atom.project.serialize.calls.length).toBe(1)
|
||||
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'})
|
||||
|
||||
describe "openInitialEmptyEditorIfNecessary", ->
|
||||
describe "when there are no paths set", ->
|
||||
@@ -230,23 +267,6 @@ describe "AtomEnvironment", ->
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
it "saves the serialized state of the window so it can be deserialized after reload", ->
|
||||
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
|
||||
spyOn(atomEnvironment, 'saveStateSync')
|
||||
|
||||
workspaceState = atomEnvironment.workspace.serialize()
|
||||
grammarsState = {grammarOverridesByPath: atomEnvironment.grammars.grammarOverridesByPath}
|
||||
projectState = atomEnvironment.project.serialize()
|
||||
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
|
||||
expect(atomEnvironment.state.workspace).toEqual workspaceState
|
||||
expect(atomEnvironment.state.grammars).toEqual grammarsState
|
||||
expect(atomEnvironment.state.project).toEqual projectState
|
||||
expect(atomEnvironment.saveStateSync).toHaveBeenCalled()
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
describe "::destroy()", ->
|
||||
it "does not throw exceptions when unsubscribing from ipc events (regression)", ->
|
||||
configDirPath = temp.mkdirSync()
|
||||
@@ -258,6 +278,7 @@ describe "AtomEnvironment", ->
|
||||
}
|
||||
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document: fakeDocument})
|
||||
spyOn(atomEnvironment.packages, 'getAvailablePackagePaths').andReturn []
|
||||
spyOn(atomEnvironment, 'displayWindow').andReturn Promise.resolve()
|
||||
atomEnvironment.startEditorWindow()
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
atomEnvironment.destroy()
|
||||
@@ -273,6 +294,14 @@ describe "AtomEnvironment", ->
|
||||
atom.openLocations([{pathToOpen}])
|
||||
expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
|
||||
describe "then a second path is opened with forceAddToWindow", ->
|
||||
it "adds the second path to the project's paths", ->
|
||||
firstPathToOpen = __dirname
|
||||
secondPathToOpen = path.resolve(__dirname, './fixtures')
|
||||
atom.openLocations([{pathToOpen: firstPathToOpen}])
|
||||
atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
|
||||
expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])
|
||||
|
||||
describe "when the opened path does not exist but its parent directory does", ->
|
||||
it "adds the parent directory to the project paths", ->
|
||||
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
|
||||
@@ -318,3 +347,18 @@ describe "AtomEnvironment", ->
|
||||
runs ->
|
||||
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
|
||||
expect(releaseVersion).toBe 'version'
|
||||
|
||||
describe "::getReleaseChannel()", ->
|
||||
[version] = []
|
||||
beforeEach ->
|
||||
spyOn(atom, 'getVersion').andCallFake -> version
|
||||
|
||||
it "returns the correct channel based on the version number", ->
|
||||
version = '1.5.6'
|
||||
expect(atom.getReleaseChannel()).toBe 'stable'
|
||||
|
||||
version = '1.5.0-beta10'
|
||||
expect(atom.getReleaseChannel()).toBe 'beta'
|
||||
|
||||
version = '1.7.0-dev-5340c91'
|
||||
expect(atom.getReleaseChannel()).toBe 'dev'
|
||||
|
||||
@@ -172,7 +172,7 @@ class AtomReporter
|
||||
listen document, 'click', '.stack-trace', (event) ->
|
||||
event.currentTarget.classList.toggle('expanded')
|
||||
|
||||
@reloadButton.addEventListener('click', -> require('ipc').send('call-window-method', 'restart'))
|
||||
@reloadButton.addEventListener('click', -> require('electron').ipcRenderer.send('call-window-method', 'restart'))
|
||||
|
||||
updateSpecCounts: ->
|
||||
if @skippedCount
|
||||
|
||||
115
spec/auto-update-manager-spec.js
Normal file
115
spec/auto-update-manager-spec.js
Normal file
@@ -0,0 +1,115 @@
|
||||
'use babel'
|
||||
|
||||
import AutoUpdateManager from '../src/auto-update-manager'
|
||||
import {remote} from 'electron'
|
||||
const electronAutoUpdater = remote.require('electron').autoUpdater
|
||||
|
||||
describe('AutoUpdateManager (renderer)', () => {
|
||||
let autoUpdateManager
|
||||
|
||||
beforeEach(() => {
|
||||
autoUpdateManager = new AutoUpdateManager({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
autoUpdateManager.destroy()
|
||||
})
|
||||
|
||||
describe('::onDidBeginCheckingForUpdate', () => {
|
||||
it('subscribes to "did-begin-checking-for-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
|
||||
electronAutoUpdater.emit('checking-for-update')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidBeginDownloadingUpdate', () => {
|
||||
it('subscribes to "did-begin-downloading-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
|
||||
electronAutoUpdater.emit('update-available')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidCompleteDownloadingUpdate', () => {
|
||||
it('subscribes to "did-complete-downloading-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
|
||||
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
runs(() => {
|
||||
expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onUpdateNotAvailable', () => {
|
||||
it('subscribes to "update-not-available" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onUpdateNotAvailable(spy)
|
||||
electronAutoUpdater.emit('update-not-available')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::platformSupportsUpdates', () => {
|
||||
let state, releaseChannel
|
||||
it('returns true on OS X and Windows when in stable', () => {
|
||||
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
|
||||
spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel)
|
||||
|
||||
state = 'idle'
|
||||
releaseChannel = 'stable'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(true)
|
||||
|
||||
state = 'idle'
|
||||
releaseChannel = 'dev'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
|
||||
state = 'unsupported'
|
||||
releaseChannel = 'stable'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
|
||||
state = 'unsupported'
|
||||
releaseChannel = 'dev'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('::destroy', () => {
|
||||
it('unsubscribes from all events', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
const doneIndicator = jasmine.createSpy('spy')
|
||||
atom.applicationDelegate.onUpdateNotAvailable(doneIndicator)
|
||||
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
|
||||
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
|
||||
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
|
||||
autoUpdateManager.onUpdateNotAvailable(spy)
|
||||
autoUpdateManager.destroy()
|
||||
electronAutoUpdater.emit('checking-for-update')
|
||||
electronAutoUpdater.emit('update-available')
|
||||
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
|
||||
electronAutoUpdater.emit('update-not-available')
|
||||
|
||||
waitsFor(() => {
|
||||
return doneIndicator.callCount === 1
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
expect(spy.callCount).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -15,7 +15,6 @@ describe "Babel transpiler support", ->
|
||||
CompileCache.setCacheDirectory(temp.mkdirSync('compile-cache'))
|
||||
for cacheKey in Object.keys(require.cache)
|
||||
if cacheKey.startsWith(path.join(__dirname, 'fixtures', 'babel'))
|
||||
console.log('deleting', cacheKey)
|
||||
delete require.cache[cacheKey]
|
||||
|
||||
afterEach ->
|
||||
|
||||
@@ -1621,6 +1621,16 @@ describe "Config", ->
|
||||
expect(color.toHexString()).toBe '#ff0000'
|
||||
expect(color.toRGBAString()).toBe 'rgba(255, 0, 0, 1)'
|
||||
|
||||
color.red = 11
|
||||
color.green = 11
|
||||
color.blue = 124
|
||||
color.alpha = 1
|
||||
atom.config.set('foo.bar.aColor', color)
|
||||
|
||||
color = atom.config.get('foo.bar.aColor')
|
||||
expect(color.toHexString()).toBe '#0b0b7c'
|
||||
expect(color.toRGBAString()).toBe 'rgba(11, 11, 124, 1)'
|
||||
|
||||
it 'coerces various types to a color object', ->
|
||||
atom.config.set('foo.bar.aColor', 'red')
|
||||
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = function (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'Deserializer1',
|
||||
state: state
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = function (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'Deserializer2',
|
||||
state: state
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
module.exports = {
|
||||
activate: function() {}
|
||||
activate () {},
|
||||
|
||||
deserializeMethod1 (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'deserializeMethod1',
|
||||
state: state
|
||||
}
|
||||
},
|
||||
|
||||
deserializeMethod2 (state) {
|
||||
return {
|
||||
wasDeserializedBy: 'deserializeMethod2',
|
||||
state: state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"main": "./index",
|
||||
"deserializers": {
|
||||
"Deserializer1": "./deserializer-1.js",
|
||||
"Deserializer2": "./deserializer-2.js"
|
||||
"Deserializer1": "deserializeMethod1",
|
||||
"Deserializer2": "deserializeMethod2"
|
||||
}
|
||||
}
|
||||
|
||||
8
spec/fixtures/packages/package-with-prefixed-and-suffixed-repo-url/package.json
vendored
Normal file
8
spec/fixtures/packages/package-with-prefixed-and-suffixed-repo-url/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "package-with-a-git-prefixed-git-repo-url",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/example/repo.git"
|
||||
},
|
||||
"_id": "this is here to simulate the URL being already normalized by npm. we still need to stript git+ from the beginning and .git from the end."
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function (state) {
|
||||
return {state: state}
|
||||
}
|
||||
@@ -1,3 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
activate: function() {}
|
||||
activate () {},
|
||||
|
||||
theDeserializerMethod (state) {
|
||||
return {state: state}
|
||||
},
|
||||
|
||||
viewProviderMethod1 (model) {
|
||||
if (model.worksWithViewProvider1) {
|
||||
let element = document.createElement('div')
|
||||
element.dataset['createdBy'] = 'view-provider-1'
|
||||
return element
|
||||
}
|
||||
},
|
||||
|
||||
viewProviderMethod2 (model) {
|
||||
if (model.worksWithViewProvider2) {
|
||||
let element = document.createElement('div')
|
||||
element.dataset['createdBy'] = 'view-provider-2'
|
||||
return element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"main": "./index",
|
||||
"version": "1.0.0",
|
||||
"deserializers": {
|
||||
"DeserializerFromPackageWithViewProviders": "./deserializer"
|
||||
"DeserializerFromPackageWithViewProviders": "theDeserializerMethod"
|
||||
},
|
||||
"viewProviders": [
|
||||
"./view-provider-1",
|
||||
"./view-provider-2"
|
||||
"viewProviderMethod1",
|
||||
"viewProviderMethod2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (model) {
|
||||
if (model.worksWithViewProvider1) {
|
||||
let element = document.createElement('div')
|
||||
element.dataset['createdBy'] = 'view-provider-1'
|
||||
return element
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (model) {
|
||||
if (model.worksWithViewProvider2) {
|
||||
let element = document.createElement('div')
|
||||
element.dataset['createdBy'] = 'view-provider-2'
|
||||
return element
|
||||
}
|
||||
}
|
||||
13
spec/fixtures/sample-with-comments.js
vendored
13
spec/fixtures/sample-with-comments.js
vendored
@@ -9,12 +9,23 @@ var quicksort = function () {
|
||||
// Wowza
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
/*
|
||||
This is a multiline comment block with
|
||||
an empty line inside of it.
|
||||
|
||||
Awesome.
|
||||
*/
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
// This is a collection of
|
||||
// single line comments
|
||||
|
||||
// ...with an empty line
|
||||
// among it, geez!
|
||||
return sort(left).concat(pivot).concat(sort(right));
|
||||
};
|
||||
// this is a single-line comment
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -338,10 +338,10 @@ describe('GitRepositoryAsync', () => {
|
||||
})
|
||||
|
||||
describe('.refreshStatus()', () => {
|
||||
let newPath, modifiedPath, cleanPath
|
||||
let newPath, modifiedPath, cleanPath, workingDirectory
|
||||
|
||||
beforeEach(() => {
|
||||
const workingDirectory = copyRepository()
|
||||
workingDirectory = copyRepository()
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
modifiedPath = path.join(workingDirectory, 'file.txt')
|
||||
newPath = path.join(workingDirectory, 'untracked.txt')
|
||||
@@ -362,7 +362,7 @@ describe('GitRepositoryAsync', () => {
|
||||
|
||||
describe('in a repository with submodules', () => {
|
||||
beforeEach(() => {
|
||||
const workingDirectory = copySubmoduleRepository()
|
||||
workingDirectory = copySubmoduleRepository()
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
modifiedPath = path.join(workingDirectory, 'jstips', 'README.md')
|
||||
newPath = path.join(workingDirectory, 'You-Dont-Need-jQuery', 'untracked.txt')
|
||||
@@ -380,6 +380,86 @@ describe('GitRepositoryAsync', () => {
|
||||
expect(repo.isStatusModified(await repo.getCachedPathStatus(modifiedPath))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('caches the proper statuses when a subdir is open', async () => {
|
||||
const subDir = path.join(workingDirectory, 'dir')
|
||||
fs.mkdirSync(subDir)
|
||||
|
||||
const filePath = path.join(subDir, 'b.txt')
|
||||
fs.writeFileSync(filePath, '')
|
||||
|
||||
atom.project.setPaths([subDir])
|
||||
|
||||
await atom.workspace.open('b.txt')
|
||||
|
||||
const repo = atom.project.getRepositories()[0].async
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
const status = await repo.getCachedPathStatus(filePath)
|
||||
expect(repo.isStatusModified(status)).toBe(false)
|
||||
expect(repo.isStatusNew(status)).toBe(false)
|
||||
})
|
||||
|
||||
it('caches the proper statuses when multiple project are open', async () => {
|
||||
const otherWorkingDirectory = copyRepository()
|
||||
|
||||
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
|
||||
|
||||
await atom.workspace.open('b.txt')
|
||||
|
||||
const repo = atom.project.getRepositories()[0].async
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
const subDir = path.join(workingDirectory, 'dir')
|
||||
fs.mkdirSync(subDir)
|
||||
|
||||
const filePath = path.join(subDir, 'b.txt')
|
||||
fs.writeFileSync(filePath, 'some content!')
|
||||
|
||||
const status = await repo.getCachedPathStatus(filePath)
|
||||
expect(repo.isStatusModified(status)).toBe(true)
|
||||
expect(repo.isStatusNew(status)).toBe(false)
|
||||
})
|
||||
|
||||
it('emits did-change-statuses if the status changes', async () => {
|
||||
const someNewPath = path.join(workingDirectory, 'MyNewJSFramework.md')
|
||||
fs.writeFileSync(someNewPath, '')
|
||||
|
||||
const statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses(statusHandler)
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
|
||||
})
|
||||
|
||||
it('emits did-change-statuses if the branch changes', async () => {
|
||||
const statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses(statusHandler)
|
||||
|
||||
repo._refreshBranch = jasmine.createSpy('_refreshBranch').andCallFake(() => {
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
|
||||
})
|
||||
|
||||
it('emits did-change-statuses if the ahead/behind changes', async () => {
|
||||
const statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses(statusHandler)
|
||||
|
||||
repo._refreshAheadBehindCount = jasmine.createSpy('_refreshAheadBehindCount').andCallFake(() => {
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isProjectAtRoot()', () => {
|
||||
@@ -499,7 +579,7 @@ describe('GitRepositoryAsync', () => {
|
||||
await atom.workspace.open('file.txt')
|
||||
|
||||
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
project2.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
project2.deserialize(atom.project.serialize({isUnloading: true}))
|
||||
|
||||
const repo = project2.getRepositories()[0].async
|
||||
waitsForPromise(() => repo.refreshStatus())
|
||||
@@ -548,6 +628,14 @@ describe('GitRepositoryAsync', () => {
|
||||
const relativizedPath = repo.relativize(`${workdir}/a/b.txt`, workdir)
|
||||
expect(relativizedPath).toBe('a/b.txt')
|
||||
})
|
||||
|
||||
it('preserves file case', () => {
|
||||
repo.isCaseInsensitive = true
|
||||
|
||||
const workdir = '/tmp/foo/bar/baz/'
|
||||
const relativizedPath = repo.relativize(`${workdir}a/README.txt`, workdir)
|
||||
expect(relativizedPath).toBe('a/README.txt')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getShortHead(path)', () => {
|
||||
@@ -626,7 +714,7 @@ describe('GitRepositoryAsync', () => {
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
})
|
||||
|
||||
it('returns 0, 0 for a branch with no upstream', async () => {
|
||||
it('returns 1, 0 for a branch which is ahead by 1', async () => {
|
||||
await repo.refreshStatus()
|
||||
|
||||
const {ahead, behind} = await repo.getCachedUpstreamAheadBehindCount('You-Dont-Need-jQuery')
|
||||
|
||||
@@ -205,7 +205,7 @@ describe "GitRepository", ->
|
||||
expect(repo.isStatusModified(repo.getDirectoryStatus(directoryPath))).toBe true
|
||||
|
||||
describe ".refreshStatus()", ->
|
||||
[newPath, modifiedPath, cleanPath, originalModifiedPathText] = []
|
||||
[newPath, modifiedPath, cleanPath, originalModifiedPathText, workingDirectory] = []
|
||||
|
||||
beforeEach ->
|
||||
workingDirectory = copyRepository()
|
||||
@@ -231,6 +231,64 @@ describe "GitRepository", ->
|
||||
expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
|
||||
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()
|
||||
|
||||
it 'caches the proper statuses when a subdir is open', ->
|
||||
subDir = path.join(workingDirectory, 'dir')
|
||||
fs.mkdirSync(subDir)
|
||||
|
||||
filePath = path.join(subDir, 'b.txt')
|
||||
fs.writeFileSync(filePath, '')
|
||||
|
||||
atom.project.setPaths([subDir])
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('b.txt')
|
||||
|
||||
statusHandler = null
|
||||
runs ->
|
||||
repo = atom.project.getRepositories()[0]
|
||||
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses statusHandler
|
||||
repo.refreshStatus()
|
||||
|
||||
waitsFor ->
|
||||
statusHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
status = repo.getCachedPathStatus(filePath)
|
||||
expect(repo.isStatusModified(status)).toBe false
|
||||
expect(repo.isStatusNew(status)).toBe false
|
||||
|
||||
it 'caches the proper statuses when multiple project are open', ->
|
||||
otherWorkingDirectory = copyRepository()
|
||||
|
||||
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('b.txt')
|
||||
|
||||
statusHandler = null
|
||||
runs ->
|
||||
repo = atom.project.getRepositories()[0]
|
||||
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses statusHandler
|
||||
repo.refreshStatus()
|
||||
|
||||
waitsFor ->
|
||||
statusHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
subDir = path.join(workingDirectory, 'dir')
|
||||
fs.mkdirSync(subDir)
|
||||
|
||||
filePath = path.join(subDir, 'b.txt')
|
||||
fs.writeFileSync(filePath, '')
|
||||
|
||||
status = repo.getCachedPathStatus(filePath)
|
||||
expect(repo.isStatusModified(status)).toBe true
|
||||
expect(repo.isStatusNew(status)).toBe false
|
||||
|
||||
describe "buffer events", ->
|
||||
[editor] = []
|
||||
|
||||
@@ -289,7 +347,7 @@ describe "GitRepository", ->
|
||||
|
||||
runs ->
|
||||
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
project2.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
project2.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
buffer = project2.getBuffers()[0]
|
||||
|
||||
waitsFor ->
|
||||
|
||||
@@ -15,6 +15,8 @@ ChromedriverPort = 9515
|
||||
ChromedriverURLBase = "/wd/hub"
|
||||
ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status"
|
||||
|
||||
userDataDir = temp.mkdirSync('atom-user-data-dir')
|
||||
|
||||
chromeDriverUp = (done) ->
|
||||
checkStatus = ->
|
||||
http
|
||||
@@ -48,7 +50,7 @@ buildAtomClient = (args, env) ->
|
||||
"atom-env=#{map(env, (value, key) -> "#{key}=#{value}").join(" ")}"
|
||||
"dev"
|
||||
"safe"
|
||||
"user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"
|
||||
"user-data-dir=#{userDataDir}"
|
||||
"socket-path=#{SocketPath}"
|
||||
])
|
||||
|
||||
@@ -124,7 +126,7 @@ buildAtomClient = (args, env) ->
|
||||
|
||||
.addCommand "simulateQuit", (done) ->
|
||||
@execute -> atom.unloadEditorWindow()
|
||||
.execute -> require("remote").require("app").emit("before-quit")
|
||||
.execute -> require("electron").remote.app.emit("before-quit")
|
||||
.call(done)
|
||||
|
||||
module.exports = (args, env, fn) ->
|
||||
|
||||
@@ -28,13 +28,12 @@ describe "Starting Atom", ->
|
||||
it "opens the parent directory and creates an empty text editor", ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForPaneItemCount(1, 1000)
|
||||
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([tempDirPath])
|
||||
|
||||
.waitForExist("atom-text-editor", 5000)
|
||||
.then (exists) -> expect(exists).toBe true
|
||||
.waitForPaneItemCount(1, 1000)
|
||||
.click("atom-text-editor")
|
||||
.keys("Hello!")
|
||||
.execute -> atom.workspace.getActiveTextEditor().getText()
|
||||
@@ -124,6 +123,34 @@ describe "Starting Atom", ->
|
||||
.waitForPaneItemCount(0, 1000)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
|
||||
describe "when using the -a, --add option", ->
|
||||
it "reuses that window and add the folder to project paths", ->
|
||||
fourthTempDir = temp.mkdirSync("a-fourth-dir")
|
||||
fourthTempFilePath = path.join(fourthTempDir, "a-file")
|
||||
fs.writeFileSync(fourthTempFilePath, "4 - This file was already here.")
|
||||
|
||||
fifthTempDir = temp.mkdirSync("a-fifth-dir")
|
||||
fifthTempFilePath = path.join(fifthTempDir, "a-file")
|
||||
fs.writeFileSync(fifthTempFilePath, "5 - This file was already here.")
|
||||
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
# Opening another file reuses the same window and add parent dir to
|
||||
# project paths.
|
||||
.startAnotherAtom(['-a', fourthTempFilePath], ATOM_HOME: atomHome)
|
||||
.waitForPaneItemCount(2, 5000)
|
||||
.waitForWindowCount(1, 1000)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir])
|
||||
.execute -> atom.workspace.getActiveTextEditor().getText()
|
||||
.then ({value: text}) -> expect(text).toBe "4 - This file was already here."
|
||||
|
||||
# Opening another directory resuses the same window and add the folder to project paths.
|
||||
.startAnotherAtom(['--add', fifthTempDir], ATOM_HOME: atomHome)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir, fifthTempDir])
|
||||
|
||||
it "opens the new window offset from the other window", ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
@@ -153,6 +180,8 @@ describe "Starting Atom", ->
|
||||
.waitForPaneItemCount(0, 3000)
|
||||
.execute -> atom.workspace.open()
|
||||
.waitForPaneItemCount(1, 3000)
|
||||
.keys("Hello!")
|
||||
.waitUntil((-> Promise.resolve(false)), 1100)
|
||||
|
||||
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
|
||||
window[key] = value for key, value of require '../vendor/jasmine'
|
||||
@@ -88,7 +88,7 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
|
||||
if logStream?
|
||||
fs.writeSync(logStream, str)
|
||||
else
|
||||
ipc.send 'write-to-stderr', str
|
||||
ipcRenderer.send 'write-to-stderr', str
|
||||
|
||||
{TerminalReporter} = require 'jasmine-tagged'
|
||||
new TerminalReporter
|
||||
|
||||
@@ -430,7 +430,7 @@ describe "LanguageMode", ->
|
||||
languageMode.foldAll()
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(1).fold
|
||||
@@ -441,6 +441,14 @@ describe "LanguageMode", ->
|
||||
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()
|
||||
|
||||
describe ".foldAllAtIndentLevel()", ->
|
||||
it "folds every foldable range at a given indentLevel", ->
|
||||
languageMode.foldAllAtIndentLevel(2)
|
||||
@@ -450,14 +458,26 @@ describe "LanguageMode", ->
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(11).fold
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
|
||||
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()
|
||||
|
||||
it "does not fold anything but the indentLevel", ->
|
||||
languageMode.foldAllAtIndentLevel(0)
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(5).fold
|
||||
@@ -467,7 +487,13 @@ describe "LanguageMode", ->
|
||||
it "returns true if the line starts a multi-line comment", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(6)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(17)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(8)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(11)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(15)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(17)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(21)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(24)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(28)).toBe false
|
||||
|
||||
it "does not return true for a line in the middle of a comment that's followed by an indented line", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
|
||||
|
||||
@@ -55,12 +55,17 @@ describe "PackageManager", ->
|
||||
it "normalizes short repository urls in package.json", ->
|
||||
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo.git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo"
|
||||
|
||||
{metadata} = atom.packages.loadPackage("package-with-invalid-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "foo"
|
||||
|
||||
it "trims git+ from the beginning and .git from the end of repository URLs, even if npm already normalized them ", ->
|
||||
{metadata} = atom.packages.loadPackage("package-with-prefixed-and-suffixed-repo-url")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo"
|
||||
|
||||
it "returns null if the package is not found in any package directory", ->
|
||||
spyOn(console, 'warn')
|
||||
expect(atom.packages.loadPackage("this-package-cannot-be-found")).toBeNull()
|
||||
@@ -88,18 +93,16 @@ describe "PackageManager", ->
|
||||
|
||||
state1 = {deserializer: 'Deserializer1', a: 'b'}
|
||||
expect(atom.deserializers.deserialize(state1)).toEqual {
|
||||
wasDeserializedBy: 'Deserializer1'
|
||||
wasDeserializedBy: 'deserializeMethod1'
|
||||
state: state1
|
||||
}
|
||||
|
||||
state2 = {deserializer: 'Deserializer2', c: 'd'}
|
||||
expect(atom.deserializers.deserialize(state2)).toEqual {
|
||||
wasDeserializedBy: 'Deserializer2'
|
||||
wasDeserializedBy: 'deserializeMethod2'
|
||||
state: state2
|
||||
}
|
||||
|
||||
expect(pack.mainModule).toBeNull()
|
||||
|
||||
describe "when there are view providers specified in the package's package.json", ->
|
||||
model1 = {worksWithViewProvider1: true}
|
||||
model2 = {worksWithViewProvider2: true}
|
||||
@@ -448,16 +451,15 @@ describe "PackageManager", ->
|
||||
pack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p
|
||||
|
||||
runs ->
|
||||
expect(pack.mainModule.someNumber).not.toBe 77
|
||||
pack.mainModule.someNumber = 77
|
||||
atom.packages.deactivatePackage("package-with-serialization")
|
||||
spyOn(pack.mainModule, 'activate').andCallThrough()
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-serialization")
|
||||
runs ->
|
||||
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-serialization")
|
||||
runs ->
|
||||
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
|
||||
|
||||
it "invokes ::onDidActivatePackage listeners with the activated package", ->
|
||||
activatedPackage = null
|
||||
@@ -821,6 +823,34 @@ describe "PackageManager", ->
|
||||
expect(atom.packages.isPackageActive("package-with-missing-provided-services")).toBe true
|
||||
expect(addErrorHandler.callCount).toBe 0
|
||||
|
||||
describe "::serialize", ->
|
||||
it "does not serialize packages that threw an error during activation", ->
|
||||
spyOn(console, 'warn')
|
||||
badPack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
|
||||
|
||||
runs ->
|
||||
spyOn(badPack.mainModule, 'serialize').andCallThrough()
|
||||
|
||||
atom.packages.serialize()
|
||||
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
|
||||
|
||||
it "absorbs exceptions that are thrown by the package module's serialize method", ->
|
||||
spyOn(console, 'error')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-serialize-error')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-serialization')
|
||||
|
||||
runs ->
|
||||
atom.packages.serialize()
|
||||
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
|
||||
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
|
||||
describe "::deactivatePackage(id)", ->
|
||||
afterEach ->
|
||||
atom.packages.unloadPackages()
|
||||
@@ -852,33 +882,6 @@ describe "PackageManager", ->
|
||||
expect(badPack.mainModule.deactivate).not.toHaveBeenCalled()
|
||||
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy()
|
||||
|
||||
it "does not serialize packages that have not been activated called on their main module", ->
|
||||
spyOn(console, 'warn')
|
||||
badPack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
|
||||
|
||||
runs ->
|
||||
spyOn(badPack.mainModule, 'serialize').andCallThrough()
|
||||
|
||||
atom.packages.deactivatePackage("package-that-throws-on-activate")
|
||||
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
|
||||
|
||||
it "absorbs exceptions that are thrown by the package module's serialize method", ->
|
||||
spyOn(console, 'error')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-serialize-error')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-serialization')
|
||||
|
||||
runs ->
|
||||
atom.packages.deactivatePackages()
|
||||
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
|
||||
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
|
||||
it "absorbs exceptions that are thrown by the package module's deactivate method", ->
|
||||
spyOn(console, 'error')
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{extend} = require 'underscore-plus'
|
||||
{Emitter} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
Pane = require '../src/pane'
|
||||
PaneAxis = require '../src/pane-axis'
|
||||
PaneContainer = require '../src/pane-container'
|
||||
@@ -18,8 +19,8 @@ describe "Pane", ->
|
||||
onDidDestroy: (fn) -> @emitter.on('did-destroy', fn)
|
||||
destroy: -> @destroyed = true; @emitter.emit('did-destroy')
|
||||
isDestroyed: -> @destroyed
|
||||
isPending: -> @pending
|
||||
pending: false
|
||||
onDidTerminatePendingState: (callback) -> @emitter.on 'terminate-pending-state', callback
|
||||
terminatePendingState: -> @emitter.emit 'terminate-pending-state'
|
||||
|
||||
beforeEach ->
|
||||
confirm = spyOn(atom.applicationDelegate, 'confirm')
|
||||
@@ -92,7 +93,7 @@ describe "Pane", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
|
||||
[item1, item2] = pane.getItems()
|
||||
item3 = new Item("C")
|
||||
pane.addItem(item3, 1)
|
||||
pane.addItem(item3, index: 1)
|
||||
expect(pane.getItems()).toEqual [item1, item3, item2]
|
||||
|
||||
it "adds the item after the active item if no index is provided", ->
|
||||
@@ -115,7 +116,7 @@ describe "Pane", ->
|
||||
pane.onDidAddItem (event) -> events.push(event)
|
||||
|
||||
item = new Item("C")
|
||||
pane.addItem(item, 1)
|
||||
pane.addItem(item, index: 1)
|
||||
expect(events).toEqual [{item, index: 1, moved: false}]
|
||||
|
||||
it "throws an exception if the item is already present on a pane", ->
|
||||
@@ -132,15 +133,56 @@ describe "Pane", ->
|
||||
expect(-> pane.addItem('foo')).toThrow()
|
||||
expect(-> pane.addItem(1)).toThrow()
|
||||
|
||||
it "destroys any existing pending item if the new item is pending", ->
|
||||
it "destroys any existing pending item", ->
|
||||
pane = new Pane(paneParams(items: []))
|
||||
itemA = new Item("A")
|
||||
itemB = new Item("B")
|
||||
itemA.pending = true
|
||||
itemB.pending = true
|
||||
pane.addItem(itemA)
|
||||
itemC = new Item("C")
|
||||
pane.addItem(itemA, pending: false)
|
||||
pane.addItem(itemB, pending: true)
|
||||
pane.addItem(itemC, pending: false)
|
||||
expect(itemB.isDestroyed()).toBe true
|
||||
|
||||
it "adds the new item before destroying any existing pending item", ->
|
||||
eventOrder = []
|
||||
|
||||
pane = new Pane(paneParams(items: []))
|
||||
itemA = new Item("A")
|
||||
itemB = new Item("B")
|
||||
pane.addItem(itemA, pending: true)
|
||||
|
||||
pane.onDidAddItem ({item}) ->
|
||||
eventOrder.push("add") if item is itemB
|
||||
|
||||
pane.onDidRemoveItem ({item}) ->
|
||||
eventOrder.push("remove") if item is itemA
|
||||
|
||||
pane.addItem(itemB)
|
||||
expect(itemA.isDestroyed()).toBe true
|
||||
|
||||
waitsFor ->
|
||||
eventOrder.length is 2
|
||||
|
||||
runs ->
|
||||
expect(eventOrder).toEqual ["add", "remove"]
|
||||
|
||||
describe "when using the old API of ::addItem(item, index)", ->
|
||||
beforeEach ->
|
||||
spyOn Grim, "deprecate"
|
||||
|
||||
it "supports the older public API", ->
|
||||
pane = new Pane(paneParams(items: []))
|
||||
itemA = new Item("A")
|
||||
itemB = new Item("B")
|
||||
itemC = new Item("C")
|
||||
pane.addItem(itemA, 0)
|
||||
pane.addItem(itemB, 0)
|
||||
pane.addItem(itemC, 0)
|
||||
expect(pane.getItems()).toEqual [itemC, itemB, itemA]
|
||||
|
||||
it "shows a deprecation warning", ->
|
||||
pane = new Pane(paneParams(items: []))
|
||||
pane.addItem(new Item(), 2)
|
||||
expect(Grim.deprecate).toHaveBeenCalledWith "Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})"
|
||||
|
||||
describe "::activateItem(item)", ->
|
||||
pane = null
|
||||
@@ -172,21 +214,83 @@ describe "Pane", ->
|
||||
beforeEach ->
|
||||
itemC = new Item("C")
|
||||
itemD = new Item("D")
|
||||
itemC.pending = true
|
||||
itemD.pending = true
|
||||
|
||||
it "replaces the active item if it is pending", ->
|
||||
pane.activateItem(itemC)
|
||||
pane.activateItem(itemC, pending: true)
|
||||
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'C', 'B']
|
||||
pane.activateItem(itemD)
|
||||
pane.activateItem(itemD, pending: true)
|
||||
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'D', 'B']
|
||||
|
||||
it "adds the item after the active item if it is not pending", ->
|
||||
pane.activateItem(itemC)
|
||||
pane.activateItem(itemC, pending: true)
|
||||
pane.activateItemAtIndex(2)
|
||||
pane.activateItem(itemD)
|
||||
pane.activateItem(itemD, pending: true)
|
||||
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D']
|
||||
|
||||
describe "::setPendingItem", ->
|
||||
pane = null
|
||||
|
||||
beforeEach ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
it "changes the pending item", ->
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
pane.setPendingItem("fake item")
|
||||
expect(pane.getPendingItem()).toEqual "fake item"
|
||||
|
||||
describe "::onItemDidTerminatePendingState callback", ->
|
||||
pane = null
|
||||
callbackCalled = false
|
||||
|
||||
beforeEach ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
callbackCalled = false
|
||||
|
||||
it "is called when the pending item changes", ->
|
||||
pane.setPendingItem("fake item one")
|
||||
pane.onItemDidTerminatePendingState (item) ->
|
||||
callbackCalled = true
|
||||
expect(item).toEqual "fake item one"
|
||||
pane.setPendingItem("fake item two")
|
||||
expect(callbackCalled).toBeTruthy()
|
||||
|
||||
it "has access to the new pending item via ::getPendingItem", ->
|
||||
pane.setPendingItem("fake item one")
|
||||
pane.onItemDidTerminatePendingState (item) ->
|
||||
callbackCalled = true
|
||||
expect(pane.getPendingItem()).toEqual "fake item two"
|
||||
pane.setPendingItem("fake item two")
|
||||
expect(callbackCalled).toBeTruthy()
|
||||
|
||||
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")]))
|
||||
[item1, item2, item3, item4, item5] = pane.getItems()
|
||||
pane.itemStack = [item3, item1, item2, item5, item4]
|
||||
|
||||
pane.activateItem(item4)
|
||||
expect(pane.getActiveItem()).toBe item4
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item5
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item2
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item5
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item4
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item3
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item3
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item4
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
pane.moveActiveItemToTopOfStack()
|
||||
expect(pane.getActiveItem()).toBe item5
|
||||
expect(pane.itemStack[4]).toBe item5
|
||||
|
||||
describe "::activateNextItem() and ::activatePreviousItem()", ->
|
||||
it "sets the active item to the next/previous item, looping around at either end", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
|
||||
@@ -253,7 +357,7 @@ describe "Pane", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
|
||||
it "removes the item from the items list and destroyes it", ->
|
||||
it "removes the item from the items list and destroys it", ->
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
pane.destroyItem(item2)
|
||||
expect(item2 in pane.getItems()).toBe false
|
||||
@@ -264,6 +368,23 @@ describe "Pane", ->
|
||||
expect(item1 in pane.getItems()).toBe false
|
||||
expect(item1.isDestroyed()).toBe true
|
||||
|
||||
it "removes the item from the itemStack", ->
|
||||
pane.itemStack = [item2, item3, item1]
|
||||
|
||||
pane.activateItem(item1)
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
pane.destroyItem(item3)
|
||||
expect(pane.itemStack).toEqual [item2, item1]
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
|
||||
pane.destroyItem(item1)
|
||||
expect(pane.itemStack).toEqual [item2]
|
||||
expect(pane.getActiveItem()).toBe item2
|
||||
|
||||
pane.destroyItem(item2)
|
||||
expect(pane.itemStack).toEqual []
|
||||
expect(pane.getActiveItem()).toBeUndefined()
|
||||
|
||||
it "invokes ::onWillDestroyItem() observers before destroying the item", ->
|
||||
events = []
|
||||
pane.onWillDestroyItem (event) ->
|
||||
@@ -605,6 +726,23 @@ describe "Pane", ->
|
||||
expect(pane2.isDestroyed()).toBe true
|
||||
expect(item4.isDestroyed()).toBe false
|
||||
|
||||
describe "when the item being moved is pending", ->
|
||||
it "is made permanent in the new pane", ->
|
||||
item6 = new Item("F")
|
||||
pane1.addItem(item6, pending: true)
|
||||
expect(pane1.getPendingItem()).toEqual item6
|
||||
pane1.moveItemToPane(item6, pane2, 0)
|
||||
expect(pane2.getPendingItem()).not.toEqual item6
|
||||
|
||||
describe "when the target pane has a pending item", ->
|
||||
it "does not destroy the pending item", ->
|
||||
item6 = new Item("F")
|
||||
pane1.addItem(item6, pending: true)
|
||||
expect(pane1.getPendingItem()).toEqual item6
|
||||
pane2.moveItemToPane(item5, pane1, 0)
|
||||
expect(pane1.getPendingItem()).toEqual item6
|
||||
|
||||
|
||||
describe "split methods", ->
|
||||
[pane1, item1, container] = []
|
||||
|
||||
@@ -806,6 +944,67 @@ describe "Pane", ->
|
||||
pane2.destroy()
|
||||
expect(container.root).toBe pane1
|
||||
|
||||
describe "pending state", ->
|
||||
editor1 = null
|
||||
pane = null
|
||||
eventCount = null
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then (o) ->
|
||||
editor1 = o
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
eventCount = 0
|
||||
editor1.onDidTerminatePendingState -> eventCount++
|
||||
|
||||
it "does not open file in pending state by default", ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js').then (o) ->
|
||||
editor1 = o
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
|
||||
it "opens file in pending state if 'pending' option is true", ->
|
||||
expect(pane.getPendingItem()).toEqual editor1
|
||||
|
||||
it "terminates pending state if ::terminatePendingState is invoked", ->
|
||||
editor1.terminatePendingState()
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "terminates pending state when buffer is changed", ->
|
||||
editor1.insertText('I\'ll be back!')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls terminate handler once when text is modified twice", ->
|
||||
editor1.insertText('Some text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
editor1.save()
|
||||
|
||||
editor1.insertText('More text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls clearPendingItem if there is a pending item to clear", ->
|
||||
spyOn(pane, "clearPendingItem").andCallThrough()
|
||||
|
||||
editor1.terminatePendingState()
|
||||
editor1.terminatePendingState()
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(pane.clearPendingItem.callCount).toBe 1
|
||||
|
||||
describe "serialization", ->
|
||||
pane = null
|
||||
|
||||
@@ -837,3 +1036,30 @@ describe "Pane", ->
|
||||
pane.focus()
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.focused).toBe true
|
||||
|
||||
it "can serialize and deserialize the order of the items in the itemStack", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
pane.itemStack = [item3, item1, item2]
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.itemStack).toEqual pane.itemStack
|
||||
expect(newPane.itemStack[2]).toEqual item2
|
||||
|
||||
it "builds the itemStack if the itemStack is not serialized", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.getItems()).toEqual newPane.itemStack
|
||||
|
||||
it "rebuilds the itemStack if items.length does not match itemStack.length", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
pane.itemStack = [item2, item3]
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.getItems()).toEqual newPane.itemStack
|
||||
|
||||
it "does not serialize the reference to the items in the itemStack for pane items that will not be serialized", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
pane.itemStack = [item2, item1, item3]
|
||||
unserializable = {}
|
||||
pane.activateItem(unserializable)
|
||||
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.itemStack).toEqual [item2, item1, item3]
|
||||
|
||||
@@ -21,6 +21,14 @@ describe "Project", ->
|
||||
afterEach ->
|
||||
deserializedProject?.destroy()
|
||||
|
||||
it "does not deserialize paths to non directories", ->
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
state = atom.project.serialize()
|
||||
state.paths.push('/directory/that/does/not/exist')
|
||||
state.paths.push(path.join(__dirname, 'fixtures', 'sample.js'))
|
||||
deserializedProject.deserialize(state, atom.deserializers)
|
||||
expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths())
|
||||
|
||||
it "does not include unretained buffers in the serialized state", ->
|
||||
waitsForPromise ->
|
||||
atom.project.bufferForPath('a')
|
||||
@@ -29,7 +37,7 @@ describe "Project", ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
|
||||
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
|
||||
@@ -39,7 +47,7 @@ describe "Project", ->
|
||||
runs ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
|
||||
expect(deserializedProject.getBuffers().length).toBe 1
|
||||
deserializedProject.getBuffers()[0].destroy()
|
||||
@@ -56,7 +64,7 @@ describe "Project", ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
fs.mkdirSync(pathToOpen)
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
|
||||
it "does not deserialize buffers when their path is inaccessible", ->
|
||||
@@ -70,9 +78,26 @@ describe "Project", ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
fs.chmodSync(pathToOpen, '000')
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
|
||||
it "serializes marker layers only if Atom is quitting", ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('a')
|
||||
|
||||
runs ->
|
||||
bufferA = atom.project.getBuffers()[0]
|
||||
layerA = bufferA.addMarkerLayer(maintainHistory: true)
|
||||
markerA = layerA.markPosition([0, 3])
|
||||
|
||||
notQuittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
notQuittingProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).toBeUndefined()
|
||||
|
||||
quittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
quittingProject.deserialize(atom.project.serialize({isUnloading: true}))
|
||||
expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).not.toBeUndefined()
|
||||
|
||||
describe "when an editor is saved and the project has no path", ->
|
||||
it "sets the project's path to the saved file's parent directory", ->
|
||||
tempFile = temp.openSync().path
|
||||
|
||||
@@ -83,3 +83,11 @@ describe "Selection", ->
|
||||
selection.setBufferRange([[2, 0], [2, 10]])
|
||||
selection.destroy()
|
||||
expect(selection.marker.isDestroyed()).toBeTruthy()
|
||||
|
||||
describe ".insertText(text, options)", ->
|
||||
it "allows pasting white space only lines when autoIndent is enabled", ->
|
||||
selection.setBufferRange [[0, 0], [0, 0]]
|
||||
selection.insertText(" \n \n\n", autoIndent: true)
|
||||
expect(buffer.lineForRow(0)).toBe " "
|
||||
expect(buffer.lineForRow(1)).toBe " "
|
||||
expect(buffer.lineForRow(2)).toBe ""
|
||||
|
||||
61
spec/state-store-spec.js
Normal file
61
spec/state-store-spec.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/** @babel */
|
||||
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
|
||||
|
||||
const StateStore = require('../src/state-store.js')
|
||||
|
||||
describe("StateStore", () => {
|
||||
let databaseName = `test-database-${Date.now()}`
|
||||
let version = 1
|
||||
|
||||
it("can save and load states", () => {
|
||||
const store = new StateStore(databaseName, version)
|
||||
return store.save('key', {foo:'bar'})
|
||||
.then(() => store.load('key'))
|
||||
.then((state) => {
|
||||
expect(state).toEqual({foo:'bar'})
|
||||
})
|
||||
})
|
||||
|
||||
it("resolves with null when a non-existent key is loaded", () => {
|
||||
const store = new StateStore(databaseName, version)
|
||||
return store.load('no-such-key').then((value) => {
|
||||
expect(value).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
it("can clear the state object store", () => {
|
||||
const store = new StateStore(databaseName, version)
|
||||
return store.save('key', {foo:'bar'})
|
||||
.then(() => store.count())
|
||||
.then((count) =>
|
||||
expect(count).toBe(1)
|
||||
)
|
||||
.then(() => store.clear())
|
||||
.then(() => store.count())
|
||||
.then((count) => {
|
||||
expect(count).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when there is an error reading from the database", () => {
|
||||
it("rejects the promise returned by load", () => {
|
||||
const store = new StateStore(databaseName, version)
|
||||
|
||||
const fakeErrorEvent = {target: {errorCode: "Something bad happened"}}
|
||||
|
||||
spyOn(IDBObjectStore.prototype, 'get').andCallFake((key) => {
|
||||
let request = {}
|
||||
process.nextTick(() => request.onerror(fakeErrorEvent))
|
||||
return request
|
||||
})
|
||||
|
||||
return store.load('nonexistentKey')
|
||||
.then(() => {
|
||||
throw new Error("Promise should have been rejected")
|
||||
})
|
||||
.catch((event) => {
|
||||
expect(event).toBe(fakeErrorEvent)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @babel */
|
||||
|
||||
import {it, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
|
||||
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
|
||||
import TextEditorElement from '../src/text-editor-element'
|
||||
import _, {extend, flatten, last, toArray} from 'underscore-plus'
|
||||
|
||||
@@ -2151,10 +2151,11 @@ describe('TextEditorComponent', function () {
|
||||
item.style.height = itemHeight + 'px'
|
||||
wrapperNode.style.width = windowWidth + 'px'
|
||||
wrapperNode.style.height = windowHeight + 'px'
|
||||
atom.setWindowDimensions({
|
||||
await atom.setWindowDimensions({
|
||||
width: windowWidth,
|
||||
height: windowHeight
|
||||
})
|
||||
|
||||
component.measureDimensions()
|
||||
component.measureWindowSize()
|
||||
await nextViewUpdatePromise()
|
||||
@@ -4834,7 +4835,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
it('pastes the previously selected text at the clicked location', async function () {
|
||||
let clipboardWrittenTo = false
|
||||
spyOn(require('ipc'), 'send').andCallFake(function (eventName, selectedText) {
|
||||
spyOn(require('electron').ipcRenderer, 'send').andCallFake(function (eventName, selectedText) {
|
||||
if (eventName === 'write-text-to-selection-clipboard') {
|
||||
require('../src/safe-clipboard').writeText(selectedText, 'selection')
|
||||
clipboardWrittenTo = true
|
||||
|
||||
@@ -91,11 +91,13 @@ describe "TextEditorPresenter", ->
|
||||
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
|
||||
|
||||
waitsForStateToUpdate = (presenter, fn) ->
|
||||
waitsFor "presenter state to update", 1000, (done) ->
|
||||
fn?()
|
||||
line = new Error().stack.split('\n')[2].split(':')[1]
|
||||
|
||||
waitsFor "presenter state to update at line #{line}", 1000, (done) ->
|
||||
disposable = presenter.onDidUpdateState ->
|
||||
disposable.dispose()
|
||||
process.nextTick(done)
|
||||
fn?()
|
||||
|
||||
tiledContentContract = (stateFn) ->
|
||||
it "contains states for tiles that are visible on screen", ->
|
||||
@@ -633,16 +635,28 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> presenter.setExplicitHeight(500)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500
|
||||
|
||||
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
|
||||
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(300)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
describe "scrollPastEnd", ->
|
||||
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
|
||||
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(300)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
it "doesn't add the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true but the presenter is created with scrollPastEnd as false", ->
|
||||
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10, scrollPastEnd: false)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(300)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
describe ".scrollTop", ->
|
||||
it "tracks the value of ::scrollTop", ->
|
||||
@@ -1336,9 +1350,11 @@ describe "TextEditorPresenter", ->
|
||||
presenter = buildPresenter()
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
|
||||
blockDecoration4 = null
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
@@ -1472,9 +1488,9 @@ describe "TextEditorPresenter", ->
|
||||
decoration1 = editor.decorateMarker(marker1, type: 'line', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
|
||||
decoration2 = null
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
@@ -2150,31 +2166,40 @@ describe "TextEditorPresenter", ->
|
||||
}
|
||||
|
||||
# becoming empty
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
|
||||
runs ->
|
||||
editor.getSelections()[1].clear(autoscroll: false)
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# becoming non-empty
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
runs ->
|
||||
editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# moving out of view
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
|
||||
runs ->
|
||||
editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# adding
|
||||
waitsForStateToUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
|
||||
runs -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# moving added selection
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
|
||||
runs ->
|
||||
editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
|
||||
waitsForStateToUpdate presenter
|
||||
|
||||
destroyedSelection = null
|
||||
runs ->
|
||||
@@ -2208,8 +2233,9 @@ describe "TextEditorPresenter", ->
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
|
||||
|
||||
marker = editor.markBufferPosition([2, 2])
|
||||
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
|
||||
highlight = null
|
||||
waitsForStateToUpdate presenter, ->
|
||||
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
|
||||
marker.setBufferRange([[2, 2], [5, 2]])
|
||||
highlight.flash('b', 500)
|
||||
runs ->
|
||||
@@ -2969,9 +2995,8 @@ describe "TextEditorPresenter", ->
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 35)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
waitsForStateToUpdate presenter, -> presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(10)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
|
||||
@@ -3460,9 +3485,9 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter-2'
|
||||
class: 'test-class'
|
||||
marker4 = editor.markBufferRange([[0, 0], [1, 0]])
|
||||
decoration4 = editor.decorateMarker(marker4, decorationParams)
|
||||
decoration4 = null
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
waitsForStateToUpdate presenter, -> decoration4 = editor.decorateMarker(marker4, decorationParams)
|
||||
|
||||
runs ->
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
|
||||
38
spec/text-editor-registry-spec.coffee
Normal file
38
spec/text-editor-registry-spec.coffee
Normal file
@@ -0,0 +1,38 @@
|
||||
TextEditorRegistry = require '../src/text-editor-registry'
|
||||
|
||||
describe "TextEditorRegistry", ->
|
||||
[registry, editor] = []
|
||||
|
||||
beforeEach ->
|
||||
registry = new TextEditorRegistry
|
||||
|
||||
describe "when a TextEditor is added", ->
|
||||
it "gets added to the list of registered editors", ->
|
||||
editor = {}
|
||||
registry.add(editor)
|
||||
expect(registry.editors.size).toBe 1
|
||||
expect(registry.editors.has(editor)).toBe(true)
|
||||
|
||||
it "returns a Disposable that can unregister the editor", ->
|
||||
editor = {}
|
||||
disposable = registry.add(editor)
|
||||
expect(registry.editors.size).toBe 1
|
||||
disposable.dispose()
|
||||
expect(registry.editors.size).toBe 0
|
||||
|
||||
describe "when the registry is observed", ->
|
||||
it "calls the callback for current and future editors until unsubscribed", ->
|
||||
[editor1, editor2, editor3] = [{}, {}, {}]
|
||||
|
||||
registry.add(editor1)
|
||||
subscription = registry.observe spy = jasmine.createSpy()
|
||||
expect(spy.calls.length).toBe 1
|
||||
|
||||
registry.add(editor2)
|
||||
expect(spy.calls.length).toBe 2
|
||||
expect(spy.argsForCall[0][0]).toBe editor1
|
||||
expect(spy.argsForCall[1][0]).toBe editor2
|
||||
|
||||
subscription.dispose()
|
||||
registry.add(editor3)
|
||||
expect(spy.calls.length).toBe 2
|
||||
@@ -55,16 +55,6 @@ describe "TextEditor", ->
|
||||
|
||||
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
|
||||
|
||||
it "restores pending tabs in pending state", ->
|
||||
expect(editor.isPending()).toBe false
|
||||
editor2 = TextEditor.deserialize(editor.serialize(), atom)
|
||||
expect(editor2.isPending()).toBe false
|
||||
|
||||
pendingEditor = atom.workspace.buildTextEditor(pending: true)
|
||||
expect(pendingEditor.isPending()).toBe true
|
||||
editor3 = TextEditor.deserialize(pendingEditor.serialize(), atom)
|
||||
expect(editor3.isPending()).toBe true
|
||||
|
||||
describe "when the editor is constructed with the largeFileMode option set to true", ->
|
||||
it "loads the editor but doesn't tokenize", ->
|
||||
editor = null
|
||||
@@ -139,6 +129,15 @@ describe "TextEditor", ->
|
||||
expect(editor2.getSoftTabs()).toBe true
|
||||
expect(editor2.getEncoding()).toBe 'macroman'
|
||||
|
||||
atom.config.set('editor.tabLength', -1)
|
||||
expect(editor2.getTabLength()).toBe 1
|
||||
atom.config.set('editor.tabLength', 2)
|
||||
expect(editor2.getTabLength()).toBe 2
|
||||
atom.config.set('editor.tabLength', 17)
|
||||
expect(editor2.getTabLength()).toBe 17
|
||||
atom.config.set('editor.tabLength', 128)
|
||||
expect(editor2.getTabLength()).toBe 128
|
||||
|
||||
it "uses scoped `core.fileEncoding` values", ->
|
||||
editor1 = null
|
||||
editor2 = null
|
||||
@@ -2133,20 +2132,31 @@ describe "TextEditor", ->
|
||||
editor.splitSelectionsIntoLines()
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
|
||||
|
||||
describe ".consolidateSelections()", ->
|
||||
it "destroys all selections but the least recent, returning true if any selections were destroyed", ->
|
||||
editor.setSelectedBufferRange([[3, 16], [3, 21]])
|
||||
selection1 = editor.getLastSelection()
|
||||
describe "::consolidateSelections()", ->
|
||||
makeMultipleSelections = ->
|
||||
selection.setBufferRange [[3, 16], [3, 21]]
|
||||
selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
|
||||
selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
|
||||
selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]])
|
||||
expect(editor.getSelections()).toEqual [selection, selection2, selection3, selection4]
|
||||
[selection, selection2, selection3, selection4]
|
||||
|
||||
it "destroys all selections but the oldest selection and autoscrolls to it, returning true if any selections were destroyed", ->
|
||||
[selection1] = makeMultipleSelections()
|
||||
|
||||
autoscrollEvents = []
|
||||
editor.onDidRequestAutoscroll (event) -> autoscrollEvents.push(event)
|
||||
|
||||
expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
|
||||
expect(editor.consolidateSelections()).toBeTruthy()
|
||||
expect(editor.getSelections()).toEqual [selection1]
|
||||
expect(selection1.isEmpty()).toBeFalsy()
|
||||
expect(editor.consolidateSelections()).toBeFalsy()
|
||||
expect(editor.getSelections()).toEqual [selection1]
|
||||
|
||||
expect(autoscrollEvents).toEqual([
|
||||
{screenRange: selection1.getScreenRange(), options: {center: true, reversed: false}}
|
||||
])
|
||||
|
||||
describe "when the cursor is moved while there is a selection", ->
|
||||
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
|
||||
|
||||
@@ -5819,52 +5829,29 @@ describe "TextEditor", ->
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
describe "pending state", ->
|
||||
editor1 = null
|
||||
eventCount = null
|
||||
|
||||
describe "when the editor is constructed with the showInvisibles option set to false", ->
|
||||
beforeEach ->
|
||||
atom.workspace.destroyActivePane()
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then (o) -> editor1 = o
|
||||
atom.workspace.open('sample.js', showInvisibles: false).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
eventCount = 0
|
||||
editor1.onDidTerminatePendingState -> eventCount++
|
||||
it "ignores invisibles even if editor.showInvisibles is true", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
invisibles = editor.tokenizedLineForScreenRow(0).invisibles
|
||||
expect(invisibles).toBe(null)
|
||||
|
||||
it "does not open file in pending state by default", ->
|
||||
expect(editor.isPending()).toBe false
|
||||
describe "when the editor is constructed with the grammar option set", ->
|
||||
beforeEach ->
|
||||
atom.workspace.destroyActivePane()
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
it "opens file in pending state if 'pending' option is true", ->
|
||||
expect(editor1.isPending()).toBe true
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', grammar: atom.grammars.grammarForScopeName('source.coffee')).then (o) -> editor = o
|
||||
|
||||
it "terminates pending state if ::terminatePendingState is invoked", ->
|
||||
editor1.terminatePendingState()
|
||||
it "sets the grammar", ->
|
||||
expect(editor.getGrammar().name).toBe 'CoffeeScript'
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "terminates pending state when buffer is changed", ->
|
||||
editor1.insertText('I\'ll be back!')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls terminate handler once when text is modified twice", ->
|
||||
editor1.insertText('Some text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
editor1.save()
|
||||
|
||||
editor1.insertText('More text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls terminate handler once when terminatePendingState is called twice", ->
|
||||
editor1.terminatePendingState()
|
||||
editor1.terminatePendingState()
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
describe "::getElement", ->
|
||||
it "returns an element", ->
|
||||
expect(editor.getElement() instanceof HTMLElement).toBe(true)
|
||||
|
||||
@@ -202,8 +202,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.firstInvalidRow()).toBe 3
|
||||
|
||||
advanceClock()
|
||||
# we discover that row 2 starts a foldable region when line 3 gets tokenized
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0)
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0)
|
||||
expect(tokenizedBuffer.firstInvalidRow()).toBe 8
|
||||
|
||||
describe "when there is a buffer change surrounding an invalid row", ->
|
||||
@@ -253,7 +252,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
expect(event).toEqual(start: 1, end: 2, delta: 0)
|
||||
expect(event).toEqual(start: 2, end: 2, delta: 0)
|
||||
changeHandler.reset()
|
||||
|
||||
advanceClock()
|
||||
@@ -263,8 +262,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
# we discover that row 2 starts a foldable region when line 3 gets tokenized
|
||||
expect(event).toEqual(start: 2, end: 5, delta: 0)
|
||||
expect(event).toEqual(start: 3, end: 5, delta: 0)
|
||||
|
||||
it "resumes highlighting with the state of the previous line", ->
|
||||
buffer.insert([0, 0], '/*')
|
||||
@@ -292,7 +290,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false
|
||||
expect(event).toEqual(start: 1, end: 3, delta: -2)
|
||||
|
||||
describe "when the change invalidates the tokenization of subsequent lines", ->
|
||||
it "schedules the invalidated lines to be tokenized in the background", ->
|
||||
@@ -305,7 +303,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
expect(event).toEqual(start: 1, end: 3, delta: -1)
|
||||
expect(event).toEqual(start: 2, end: 3, delta: -1)
|
||||
changeHandler.reset()
|
||||
|
||||
advanceClock()
|
||||
@@ -314,8 +312,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
# we discover that row 2 starts a foldable region when line 3 gets tokenized
|
||||
expect(event).toEqual(start: 2, end: 4, delta: 0)
|
||||
expect(event).toEqual(start: 3, end: 4, delta: 0)
|
||||
|
||||
describe "when lines are both updated and inserted", ->
|
||||
it "updates tokens to reflect the change", ->
|
||||
@@ -339,7 +336,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0
|
||||
expect(event).toEqual(start: 1, end: 2, delta: 2)
|
||||
|
||||
describe "when the change invalidates the tokenization of subsequent lines", ->
|
||||
it "schedules the invalidated lines to be tokenized in the background", ->
|
||||
@@ -350,7 +347,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
delete event.bufferChange
|
||||
expect(event).toEqual(start: 1, end: 2, delta: 2)
|
||||
expect(event).toEqual(start: 2, end: 2, delta: 2)
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
|
||||
@@ -894,7 +891,7 @@ describe "TokenizedBuffer", ->
|
||||
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
|
||||
|
||||
delete changeHandler.argsForCall[0][0].bufferChange
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2
|
||||
@@ -903,7 +900,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # }
|
||||
|
||||
describe ".foldable on tokenized lines", ->
|
||||
describe "::isFoldableAtRow(row)", ->
|
||||
changes = null
|
||||
|
||||
beforeEach ->
|
||||
@@ -915,74 +912,66 @@ describe "TokenizedBuffer", ->
|
||||
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
|
||||
})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
tokenizedBuffer.onDidChange (change) ->
|
||||
delete change.bufferChange
|
||||
changes.push(change)
|
||||
|
||||
it "sets .foldable to true on the first line of multi-line comments", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(13).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(14).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(15).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(16).foldable).toBe false
|
||||
it "includes the first line of multi-line comments", ->
|
||||
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent
|
||||
expect(tokenizedBuffer.isFoldableAtRow(13)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(14)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(15)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(16)).toBe false
|
||||
|
||||
buffer.insert([0, Infinity], '\n')
|
||||
expect(changes).toEqual [{start: 0, end: 1, delta: 1}]
|
||||
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe false
|
||||
|
||||
changes = []
|
||||
buffer.undo()
|
||||
expect(changes).toEqual [{start: 0, end: 2, delta: -1}]
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
|
||||
|
||||
it "sets .foldable to true on non-comment lines that precede an increase in indentation", ->
|
||||
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent
|
||||
|
||||
it "includes non-comment lines that precede an increase in indentation", ->
|
||||
buffer.insert([2, 0], ' ') # commented lines preceding an indent aren't foldable
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
|
||||
|
||||
changes = []
|
||||
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(4)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(5)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
|
||||
|
||||
buffer.insert([7, 0], ' ')
|
||||
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
|
||||
|
||||
changes = []
|
||||
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
|
||||
|
||||
buffer.undo()
|
||||
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
|
||||
|
||||
changes = []
|
||||
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
|
||||
|
||||
buffer.insert([7, 0], " \n x\n")
|
||||
expect(changes).toEqual [{start: 6, end: 7, delta: 2}]
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
|
||||
|
||||
changes = []
|
||||
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
|
||||
|
||||
buffer.insert([9, 0], " ")
|
||||
expect(changes).toEqual [{start: 9, end: 9, delta: 0}]
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
|
||||
|
||||
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
|
||||
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
|
||||
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
|
||||
|
||||
describe "when the buffer is configured with the null grammar", ->
|
||||
it "uses the placeholder tokens and does not actually tokenize using the grammar", ->
|
||||
|
||||
@@ -28,6 +28,12 @@ describe "TooltipManager", ->
|
||||
hover element, ->
|
||||
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
|
||||
|
||||
it "creates a tooltip immediately if the trigger type is manual", ->
|
||||
disposable = manager.add element, title: "Title", trigger: "manual"
|
||||
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
|
||||
disposable.dispose()
|
||||
expect(document.body.querySelector(".tooltip")).toBeNull()
|
||||
|
||||
it "allows jQuery elements to be passed as the target", ->
|
||||
element2 = document.createElement('div')
|
||||
jasmine.attachToDOM(element2)
|
||||
|
||||
@@ -23,6 +23,15 @@ describe "ViewRegistry", ->
|
||||
component = new TestComponent
|
||||
expect(registry.getView(component)).toBe component.element
|
||||
|
||||
describe "when passed an object with a getElement function", ->
|
||||
it "returns the return value of getElement if it's an instance of HTMLElement", ->
|
||||
class TestComponent
|
||||
getElement: ->
|
||||
@myElement ?= document.createElement('div')
|
||||
|
||||
component = new TestComponent
|
||||
expect(registry.getView(component)).toBe component.myElement
|
||||
|
||||
describe "when passed a model object", ->
|
||||
describe "when a view provider is registered matching the object's constructor", ->
|
||||
it "constructs a view element and assigns the model on it", ->
|
||||
|
||||
@@ -4,7 +4,7 @@ fs = require 'fs-plus'
|
||||
temp = require 'temp'
|
||||
TextEditor = require '../src/text-editor'
|
||||
WindowEventHandler = require '../src/window-event-handler'
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
describe "WindowEventHandler", ->
|
||||
[projectPath, windowEventHandler] = []
|
||||
@@ -53,7 +53,7 @@ describe "WindowEventHandler", ->
|
||||
describe "beforeunload event", ->
|
||||
beforeEach ->
|
||||
jasmine.unspy(TextEditor.prototype, "shouldPromptToSave")
|
||||
spyOn(ipc, 'send')
|
||||
spyOn(ipcRenderer, 'send')
|
||||
|
||||
describe "when pane items are modified", ->
|
||||
editor = null
|
||||
@@ -65,13 +65,13 @@ describe "WindowEventHandler", ->
|
||||
spyOn(atom.workspace, 'confirmClose').andReturn(true)
|
||||
window.dispatchEvent(new CustomEvent('beforeunload'))
|
||||
expect(atom.workspace.confirmClose).toHaveBeenCalled()
|
||||
expect(ipc.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
|
||||
expect(ipcRenderer.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
|
||||
|
||||
it "cancels the unload if the user selects cancel", ->
|
||||
spyOn(atom.workspace, 'confirmClose').andReturn(false)
|
||||
window.dispatchEvent(new CustomEvent('beforeunload'))
|
||||
expect(atom.workspace.confirmClose).toHaveBeenCalled()
|
||||
expect(ipc.send).toHaveBeenCalledWith('did-cancel-window-unload')
|
||||
expect(ipcRenderer.send).toHaveBeenCalledWith('did-cancel-window-unload')
|
||||
|
||||
describe "when a link is clicked", ->
|
||||
it "opens the http/https links in an external application", ->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
path = require 'path'
|
||||
temp = require('temp').track()
|
||||
|
||||
@@ -127,35 +127,35 @@ describe "WorkspaceElement", ->
|
||||
describe "the 'window:run-package-specs' command", ->
|
||||
it "runs the package specs for the active item's project path, or the first project path", ->
|
||||
workspaceElement = atom.views.getView(atom.workspace)
|
||||
spyOn(ipc, 'send')
|
||||
spyOn(ipcRenderer, 'send')
|
||||
|
||||
# No project paths. Don't try to run specs.
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).not.toHaveBeenCalledWith("run-package-specs")
|
||||
expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs")
|
||||
|
||||
projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
|
||||
atom.project.setPaths(projectPaths)
|
||||
|
||||
# No active item. Use first project directory.
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipc.send.reset()
|
||||
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipcRenderer.send.reset()
|
||||
|
||||
# Active item doesn't implement ::getPath(). Use first project directory.
|
||||
item = document.createElement("div")
|
||||
atom.workspace.getActivePane().activateItem(item)
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipc.send.reset()
|
||||
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipcRenderer.send.reset()
|
||||
|
||||
# Active item has no path. Use first project directory.
|
||||
item.getPath = -> null
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipc.send.reset()
|
||||
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
|
||||
ipcRenderer.send.reset()
|
||||
|
||||
# Active item has path. Use project path for item path.
|
||||
item.getPath = -> path.join(projectPaths[1], "a-file.txt")
|
||||
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
|
||||
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
|
||||
ipc.send.reset()
|
||||
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
|
||||
ipcRenderer.send.reset()
|
||||
|
||||
@@ -22,11 +22,11 @@ describe "Workspace", ->
|
||||
describe "serialization", ->
|
||||
simulateReload = ->
|
||||
workspaceState = atom.workspace.serialize()
|
||||
projectState = atom.project.serialize()
|
||||
projectState = atom.project.serialize({isUnloading: true})
|
||||
atom.workspace.destroy()
|
||||
atom.project.destroy()
|
||||
atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom)})
|
||||
atom.project.deserialize(projectState, atom.deserializers)
|
||||
atom.project.deserialize(projectState)
|
||||
atom.workspace = new Workspace({
|
||||
config: atom.config, project: atom.project, packageManager: atom.packages,
|
||||
grammarRegistry: atom.grammars, deserializerManager: atom.deserializers,
|
||||
@@ -585,6 +585,72 @@ describe "Workspace", ->
|
||||
open = -> workspace.open('file1', workspace.getActivePane())
|
||||
expect(open).toThrow()
|
||||
|
||||
describe "when the file is already open in pending state", ->
|
||||
it "should terminate the pending state", ->
|
||||
editor = null
|
||||
pane = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true).then (o) ->
|
||||
editor = o
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
expect(pane.getPendingItem()).toEqual editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js')
|
||||
|
||||
runs ->
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
|
||||
describe "when opening will switch from a pending tab to a permanent tab", ->
|
||||
it "keeps the pending tab open", ->
|
||||
editor1 = null
|
||||
editor2 = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt').then (o) ->
|
||||
editor1 = o
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample2.txt', pending: true).then (o) ->
|
||||
editor2 = o
|
||||
|
||||
runs ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
pane.activateItem(editor1)
|
||||
expect(pane.getItems().length).toBe 2
|
||||
expect(pane.getItems()).toEqual [editor1, editor2]
|
||||
|
||||
describe "when replacing a pending item which is the last item in a second pane", ->
|
||||
it "does not destory the pane even if core.destroyEmptyPanes is on", ->
|
||||
atom.config.set('core.destroyEmptyPanes', true)
|
||||
editor1 = null
|
||||
editor2 = null
|
||||
leftPane = atom.workspace.getActivePane()
|
||||
rightPane = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true, split: 'right').then (o) ->
|
||||
editor1 = o
|
||||
rightPane = atom.workspace.getActivePane()
|
||||
spyOn rightPane, "destroyed"
|
||||
|
||||
runs ->
|
||||
expect(leftPane).not.toBe rightPane
|
||||
expect(atom.workspace.getActivePane()).toBe rightPane
|
||||
expect(atom.workspace.getActivePane().getItems().length).toBe 1
|
||||
expect(rightPane.getPendingItem()).toBe editor1
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then (o) ->
|
||||
editor2 = o
|
||||
|
||||
runs ->
|
||||
expect(rightPane.getPendingItem()).toBe editor2
|
||||
expect(rightPane.destroyed.callCount).toBe 0
|
||||
|
||||
describe "::reopenItem()", ->
|
||||
it "opens the uri associated with the last closed pane that isn't currently open", ->
|
||||
pane = workspace.getActivePane()
|
||||
@@ -1532,3 +1598,15 @@ describe "Workspace", ->
|
||||
|
||||
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
|
||||
expect(atom.close).toHaveBeenCalled()
|
||||
|
||||
describe "when the core.allowPendingPaneItems option is falsey", ->
|
||||
it "does not open item with `pending: true` option as pending", ->
|
||||
pane = null
|
||||
atom.config.set('core.allowPendingPaneItems', false)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true).then ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
expect(pane.getPendingItem()).toBeFalsy()
|
||||
|
||||
@@ -1,69 +1,73 @@
|
||||
_ = require 'underscore-plus'
|
||||
ipc = require 'ipc'
|
||||
remote = require 'remote'
|
||||
shell = require 'shell'
|
||||
webFrame = require 'web-frame'
|
||||
{ipcRenderer, remote, shell, webFrame} = require 'electron'
|
||||
ipcHelpers = require './ipc-helpers'
|
||||
{Disposable} = require 'event-kit'
|
||||
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
|
||||
module.exports =
|
||||
class ApplicationDelegate
|
||||
open: (params) ->
|
||||
ipc.send('open', params)
|
||||
ipcRenderer.send('open', params)
|
||||
|
||||
pickFolder: (callback) ->
|
||||
responseChannel = "atom-pick-folder-response"
|
||||
ipc.on responseChannel, (path) ->
|
||||
ipc.removeAllListeners(responseChannel)
|
||||
ipcRenderer.on responseChannel, (event, path) ->
|
||||
ipcRenderer.removeAllListeners(responseChannel)
|
||||
callback(path)
|
||||
ipc.send("pick-folder", responseChannel)
|
||||
ipcRenderer.send("pick-folder", responseChannel)
|
||||
|
||||
getCurrentWindow: ->
|
||||
remote.getCurrentWindow()
|
||||
|
||||
closeWindow: ->
|
||||
ipc.send("call-window-method", "close")
|
||||
ipcRenderer.send("call-window-method", "close")
|
||||
|
||||
getTemporaryWindowState: ->
|
||||
ipcHelpers.call('get-temporary-window-state')
|
||||
|
||||
setTemporaryWindowState: (state) ->
|
||||
ipcHelpers.call('set-temporary-window-state', state)
|
||||
|
||||
getWindowSize: ->
|
||||
[width, height] = remote.getCurrentWindow().getSize()
|
||||
{width, height}
|
||||
|
||||
setWindowSize: (width, height) ->
|
||||
remote.getCurrentWindow().setSize(width, height)
|
||||
ipcHelpers.call('set-window-size', width, height)
|
||||
|
||||
getWindowPosition: ->
|
||||
[x, y] = remote.getCurrentWindow().getPosition()
|
||||
{x, y}
|
||||
|
||||
setWindowPosition: (x, y) ->
|
||||
ipc.send("call-window-method", "setPosition", x, y)
|
||||
ipcHelpers.call('set-window-position', x, y)
|
||||
|
||||
centerWindow: ->
|
||||
ipc.send("call-window-method", "center")
|
||||
ipcHelpers.call('center-window')
|
||||
|
||||
focusWindow: ->
|
||||
ipc.send("call-window-method", "focus")
|
||||
ipcHelpers.call('focus-window')
|
||||
|
||||
showWindow: ->
|
||||
ipc.send("call-window-method", "show")
|
||||
ipcHelpers.call('show-window')
|
||||
|
||||
hideWindow: ->
|
||||
ipc.send("call-window-method", "hide")
|
||||
ipcHelpers.call('hide-window')
|
||||
|
||||
restartWindow: ->
|
||||
ipc.send("call-window-method", "restart")
|
||||
reloadWindow: ->
|
||||
ipcRenderer.send("call-window-method", "reload")
|
||||
|
||||
isWindowMaximized: ->
|
||||
remote.getCurrentWindow().isMaximized()
|
||||
|
||||
maximizeWindow: ->
|
||||
ipc.send("call-window-method", "maximize")
|
||||
ipcRenderer.send("call-window-method", "maximize")
|
||||
|
||||
isWindowFullScreen: ->
|
||||
remote.getCurrentWindow().isFullScreen()
|
||||
|
||||
setWindowFullScreen: (fullScreen=false) ->
|
||||
ipc.send("call-window-method", "setFullScreen", fullScreen)
|
||||
ipcRenderer.send("call-window-method", "setFullScreen", fullScreen)
|
||||
|
||||
openWindowDevTools: ->
|
||||
new Promise (resolve) ->
|
||||
@@ -75,7 +79,7 @@ class ApplicationDelegate
|
||||
resolve()
|
||||
else
|
||||
remote.getCurrentWindow().once("devtools-opened", -> resolve())
|
||||
ipc.send("call-window-method", "openDevTools")
|
||||
ipcRenderer.send("call-window-method", "openDevTools")
|
||||
|
||||
closeWindowDevTools: ->
|
||||
new Promise (resolve) ->
|
||||
@@ -87,7 +91,7 @@ class ApplicationDelegate
|
||||
resolve()
|
||||
else
|
||||
remote.getCurrentWindow().once("devtools-closed", -> resolve())
|
||||
ipc.send("call-window-method", "closeDevTools")
|
||||
ipcRenderer.send("call-window-method", "closeDevTools")
|
||||
|
||||
toggleWindowDevTools: ->
|
||||
new Promise (resolve) =>
|
||||
@@ -101,16 +105,16 @@ class ApplicationDelegate
|
||||
@openWindowDevTools().then(resolve)
|
||||
|
||||
executeJavaScriptInWindowDevTools: (code) ->
|
||||
ipc.send("call-window-method", "executeJavaScriptInDevTools", code)
|
||||
ipcRenderer.send("execute-javascript-in-dev-tools", code)
|
||||
|
||||
setWindowDocumentEdited: (edited) ->
|
||||
ipc.send("call-window-method", "setDocumentEdited", edited)
|
||||
ipcRenderer.send("call-window-method", "setDocumentEdited", edited)
|
||||
|
||||
setRepresentedFilename: (filename) ->
|
||||
ipc.send("call-window-method", "setRepresentedFilename", filename)
|
||||
ipcRenderer.send("call-window-method", "setRepresentedFilename", filename)
|
||||
|
||||
addRecentDocument: (filename) ->
|
||||
ipc.send("add-recent-document", filename)
|
||||
ipcRenderer.send("add-recent-document", filename)
|
||||
|
||||
setRepresentedDirectoryPaths: (paths) ->
|
||||
loadSettings = getWindowLoadSettings()
|
||||
@@ -118,14 +122,13 @@ class ApplicationDelegate
|
||||
setWindowLoadSettings(loadSettings)
|
||||
|
||||
setAutoHideWindowMenuBar: (autoHide) ->
|
||||
ipc.send("call-window-method", "setAutoHideMenuBar", autoHide)
|
||||
ipcRenderer.send("call-window-method", "setAutoHideMenuBar", autoHide)
|
||||
|
||||
setWindowMenuBarVisibility: (visible) ->
|
||||
remote.getCurrentWindow().setMenuBarVisibility(visible)
|
||||
|
||||
getPrimaryDisplayWorkAreaSize: ->
|
||||
screen = remote.require 'screen'
|
||||
screen.getPrimaryDisplay().workAreaSize
|
||||
remote.screen.getPrimaryDisplay().workAreaSize
|
||||
|
||||
confirm: ({message, detailedMessage, buttons}) ->
|
||||
buttons ?= {}
|
||||
@@ -134,8 +137,7 @@ class ApplicationDelegate
|
||||
else
|
||||
buttonLabels = Object.keys(buttons)
|
||||
|
||||
dialog = remote.require('dialog')
|
||||
chosen = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
chosen = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'info'
|
||||
message: message
|
||||
detail: detailedMessage
|
||||
@@ -157,45 +159,88 @@ class ApplicationDelegate
|
||||
params = _.clone(params)
|
||||
params.title ?= 'Save File'
|
||||
params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
|
||||
dialog = remote.require('dialog')
|
||||
dialog.showSaveDialog remote.getCurrentWindow(), params
|
||||
remote.dialog.showSaveDialog remote.getCurrentWindow(), params
|
||||
|
||||
playBeepSound: ->
|
||||
shell.beep()
|
||||
|
||||
onDidOpenLocations: (callback) ->
|
||||
outerCallback = (message, detail) ->
|
||||
if message is 'open-locations'
|
||||
callback(detail)
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'open-locations'
|
||||
|
||||
ipc.on('message', outerCallback)
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipc.removeListener('message', outerCallback)
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onUpdateAvailable: (callback) ->
|
||||
outerCallback = (message, detail) ->
|
||||
if message is 'update-available'
|
||||
callback(detail)
|
||||
outerCallback = (event, message, detail) ->
|
||||
# TODO: Yes, this is strange that `onUpdateAvailable` is listening for
|
||||
# `did-begin-downloading-update`. We currently have no mechanism to know
|
||||
# if there is an update, so begin of downloading is a good proxy.
|
||||
callback(detail) if message is 'did-begin-downloading-update'
|
||||
|
||||
ipc.on('message', outerCallback)
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipc.removeListener('message', outerCallback)
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidBeginDownloadingUpdate: (callback) ->
|
||||
@onUpdateAvailable(callback)
|
||||
|
||||
onDidBeginCheckingForUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'checking-for-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidCompleteDownloadingUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
# TODO: We could rename this event to `did-complete-downloading-update`
|
||||
callback(detail) if message is 'update-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onUpdateNotAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'update-not-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onApplicationMenuCommand: (callback) ->
|
||||
ipc.on('command', callback)
|
||||
outerCallback = (event, args...) ->
|
||||
callback(args...)
|
||||
|
||||
ipcRenderer.on('command', outerCallback)
|
||||
new Disposable ->
|
||||
ipc.removeListener('command', callback)
|
||||
ipcRenderer.removeListener('command', outerCallback)
|
||||
|
||||
onContextMenuCommand: (callback) ->
|
||||
ipc.on('context-command', callback)
|
||||
outerCallback = (event, args...) ->
|
||||
callback(args...)
|
||||
|
||||
ipcRenderer.on('context-command', outerCallback)
|
||||
new Disposable ->
|
||||
ipc.removeListener('context-command', callback)
|
||||
ipcRenderer.removeListener('context-command', outerCallback)
|
||||
|
||||
didCancelWindowUnload: ->
|
||||
ipc.send('did-cancel-window-unload')
|
||||
ipcRenderer.send('did-cancel-window-unload')
|
||||
|
||||
openExternal: (url) ->
|
||||
shell.openExternal(url)
|
||||
|
||||
disablePinchToZoom: ->
|
||||
webFrame.setZoomLevelLimits(1, 1)
|
||||
|
||||
checkForUpdate: ->
|
||||
ipcRenderer.send('check-for-update')
|
||||
|
||||
restartAndInstallUpdate: ->
|
||||
ipcRenderer.send('install-update')
|
||||
|
||||
getAutoUpdateManagerState: ->
|
||||
ipcRenderer.sendSync('get-auto-update-manager-state')
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
crypto = require 'crypto'
|
||||
path = require 'path'
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
{deprecate} = require 'grim'
|
||||
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||
{CompositeDisposable, Disposable, Emitter} = require 'event-kit'
|
||||
fs = require 'fs-plus'
|
||||
{mapSourcePosition} = require 'source-map-support'
|
||||
Model = require './model'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
StylesElement = require './styles-element'
|
||||
StorageFolder = require './storage-folder'
|
||||
{getWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
StateStore = require './state-store'
|
||||
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
registerDefaultCommands = require './register-default-commands'
|
||||
|
||||
DeserializerManager = require './deserializer-manager'
|
||||
@@ -40,6 +40,8 @@ Project = require './project'
|
||||
TextEditor = require './text-editor'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Gutter = require './gutter'
|
||||
TextEditorRegistry = require './text-editor-registry'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
|
||||
WorkspaceElement = require './workspace-element'
|
||||
PanelContainerElement = require './panel-container-element'
|
||||
@@ -111,6 +113,14 @@ class AtomEnvironment extends Model
|
||||
# Public: A {Workspace} instance
|
||||
workspace: null
|
||||
|
||||
# Public: A {TextEditorRegistry} instance
|
||||
textEditors: null
|
||||
|
||||
# Private: An {AutoUpdateManager} instance
|
||||
autoUpdater: null
|
||||
|
||||
saveStateDebounceInterval: 1000
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
@@ -119,14 +129,17 @@ class AtomEnvironment extends Model
|
||||
constructor: (params={}) ->
|
||||
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
|
||||
|
||||
@state = {version: @constructor.version}
|
||||
|
||||
@unloaded = false
|
||||
@loadTime = null
|
||||
{devMode, safeMode, resourcePath} = @getLoadSettings()
|
||||
{devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings()
|
||||
|
||||
@emitter = new Emitter
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@stateStore = new StateStore('AtomEnvironments', 1)
|
||||
|
||||
@stateStore.clear() if clearWindowState
|
||||
|
||||
@deserializers = new DeserializerManager(this)
|
||||
@deserializeTimings = {}
|
||||
|
||||
@@ -179,6 +192,9 @@ class AtomEnvironment extends Model
|
||||
})
|
||||
@themes.workspace = @workspace
|
||||
|
||||
@textEditors = new TextEditorRegistry
|
||||
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
|
||||
|
||||
@config.load()
|
||||
|
||||
@themes.loadBaseStylesheets()
|
||||
@@ -200,19 +216,29 @@ class AtomEnvironment extends Model
|
||||
@registerDefaultViewProviders()
|
||||
|
||||
@installUncaughtErrorHandler()
|
||||
@attachSaveStateListeners()
|
||||
@installWindowEventHandler()
|
||||
|
||||
@observeAutoHideMenuBar()
|
||||
|
||||
checkPortableHomeWritable = ->
|
||||
responseChannel = "check-portable-home-writable-response"
|
||||
ipc.on responseChannel, (response) ->
|
||||
ipc.removeAllListeners(responseChannel)
|
||||
ipcRenderer.on responseChannel, (event, response) ->
|
||||
ipcRenderer.removeAllListeners(responseChannel)
|
||||
atom.notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
|
||||
ipc.send('check-portable-home-writable', responseChannel)
|
||||
ipcRenderer.send('check-portable-home-writable', responseChannel)
|
||||
|
||||
checkPortableHomeWritable()
|
||||
|
||||
attachSaveStateListeners: ->
|
||||
saveState = => @saveState({isUnloading: false}) unless @unloaded
|
||||
debouncedSaveState = _.debounce(saveState, @saveStateDebounceInterval)
|
||||
@document.addEventListener('mousedown', debouncedSaveState, true)
|
||||
@document.addEventListener('keydown', debouncedSaveState, true)
|
||||
@disposables.add new Disposable =>
|
||||
@document.removeEventListener('mousedown', debouncedSaveState, true)
|
||||
@document.removeEventListener('keydown', debouncedSaveState, true)
|
||||
|
||||
setConfigSchema: ->
|
||||
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
|
||||
|
||||
@@ -241,8 +267,6 @@ class AtomEnvironment extends Model
|
||||
new PaneAxisElement().initialize(model, env)
|
||||
@views.addViewProvider Pane, (model, env) ->
|
||||
new PaneElement().initialize(model, env)
|
||||
@views.addViewProvider TextEditor, (model, env) ->
|
||||
new TextEditorElement().initialize(model, env)
|
||||
@views.addViewProvider(Gutter, createGutterView)
|
||||
|
||||
registerDefaultOpeners: ->
|
||||
@@ -302,9 +326,6 @@ class AtomEnvironment extends Model
|
||||
@views.clear()
|
||||
@registerDefaultViewProviders()
|
||||
|
||||
@state.packageStates = {}
|
||||
delete @state.workspace
|
||||
|
||||
destroy: ->
|
||||
return if not @project
|
||||
|
||||
@@ -317,6 +338,7 @@ class AtomEnvironment extends Model
|
||||
@commands.clear()
|
||||
@stylesElement.remove()
|
||||
@config.unobserveUserConfig()
|
||||
@autoUpdater.destroy()
|
||||
|
||||
@uninstallWindowEventHandler()
|
||||
|
||||
@@ -384,12 +406,27 @@ class AtomEnvironment extends Model
|
||||
inSpecMode: ->
|
||||
@specMode ?= @getLoadSettings().isSpec
|
||||
|
||||
# Returns a {Boolean} indicating whether this the first time the window's been
|
||||
# loaded.
|
||||
isFirstLoad: ->
|
||||
@firstLoad ?= @getLoadSettings().firstLoad
|
||||
|
||||
# Public: Get the version of the Atom application.
|
||||
#
|
||||
# Returns the version text {String}.
|
||||
getVersion: ->
|
||||
@appVersion ?= @getLoadSettings().appVersion
|
||||
|
||||
# Returns the release channel as a {String}. Will return one of `'dev', 'beta', 'stable'`
|
||||
getReleaseChannel: ->
|
||||
version = @getVersion()
|
||||
if version.indexOf('beta') > -1
|
||||
'beta'
|
||||
else if version.indexOf('dev') > -1
|
||||
'dev'
|
||||
else
|
||||
'stable'
|
||||
|
||||
# Public: Returns a {Boolean} that is `true` if the current version is an official release.
|
||||
isReleasedVersion: ->
|
||||
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
|
||||
@@ -492,7 +529,7 @@ class AtomEnvironment extends Model
|
||||
|
||||
# Extended: Reload the current window.
|
||||
reload: ->
|
||||
@applicationDelegate.restartWindow()
|
||||
@applicationDelegate.reloadWindow()
|
||||
|
||||
# Extended: Returns a {Boolean} that is `true` if the current window is maximized.
|
||||
isMaximized: ->
|
||||
@@ -519,16 +556,18 @@ class AtomEnvironment extends Model
|
||||
|
||||
# Restore the window to its previous dimensions and show it.
|
||||
#
|
||||
# Also restores the full screen and maximized state on the next tick to
|
||||
# Restores the full screen and maximized state after the window has resized to
|
||||
# prevent resize glitches.
|
||||
displayWindow: ->
|
||||
dimensions = @restoreWindowDimensions()
|
||||
@show()
|
||||
@focus()
|
||||
|
||||
setImmediate =>
|
||||
@setFullScreen(true) if @workspace?.fullScreen
|
||||
@maximize() if dimensions?.maximized and process.platform isnt 'darwin'
|
||||
@restoreWindowDimensions().then =>
|
||||
steps = [
|
||||
@restoreWindowBackground(),
|
||||
@show(),
|
||||
@focus()
|
||||
]
|
||||
steps.push(@setFullScreen(true)) if @windowDimensions?.fullScreen
|
||||
steps.push(@maximize()) if @windowDimensions?.maximized and process.platform isnt 'darwin'
|
||||
Promise.all(steps)
|
||||
|
||||
# Get the dimensions of this window.
|
||||
#
|
||||
@@ -556,22 +595,24 @@ class AtomEnvironment extends Model
|
||||
# * `width` The new width.
|
||||
# * `height` The new height.
|
||||
setWindowDimensions: ({x, y, width, height}) ->
|
||||
steps = []
|
||||
if width? and height?
|
||||
@setSize(width, height)
|
||||
steps.push(@setSize(width, height))
|
||||
if x? and y?
|
||||
@setPosition(x, y)
|
||||
steps.push(@setPosition(x, y))
|
||||
else
|
||||
@center()
|
||||
steps.push(@center())
|
||||
Promise.all(steps)
|
||||
|
||||
# Returns true if the dimensions are useable, false if they should be ignored.
|
||||
# Work around for https://github.com/atom/atom-shell/issues/473
|
||||
isValidDimensions: ({x, y, width, height}={}) ->
|
||||
width > 0 and height > 0 and x + width > 0 and y + height > 0
|
||||
|
||||
storeDefaultWindowDimensions: ->
|
||||
dimensions = @getWindowDimensions()
|
||||
if @isValidDimensions(dimensions)
|
||||
localStorage.setItem("defaultWindowDimensions", JSON.stringify(dimensions))
|
||||
storeWindowDimensions: ->
|
||||
@windowDimensions = @getWindowDimensions()
|
||||
if @isValidDimensions(@windowDimensions)
|
||||
localStorage.setItem("defaultWindowDimensions", JSON.stringify(@windowDimensions))
|
||||
|
||||
getDefaultWindowDimensions: ->
|
||||
{windowDimensions} = @getLoadSettings()
|
||||
@@ -591,15 +632,16 @@ class AtomEnvironment extends Model
|
||||
{x: 0, y: 0, width: Math.min(1024, width), height}
|
||||
|
||||
restoreWindowDimensions: ->
|
||||
dimensions = @state.windowDimensions
|
||||
unless @isValidDimensions(dimensions)
|
||||
dimensions = @getDefaultWindowDimensions()
|
||||
@setWindowDimensions(dimensions)
|
||||
dimensions
|
||||
unless @windowDimensions? and @isValidDimensions(@windowDimensions)
|
||||
@windowDimensions = @getDefaultWindowDimensions()
|
||||
@setWindowDimensions(@windowDimensions).then -> @windowDimensions
|
||||
|
||||
storeWindowDimensions: ->
|
||||
dimensions = @getWindowDimensions()
|
||||
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
|
||||
restoreWindowBackground: ->
|
||||
if backgroundColor = window.localStorage.getItem('atom:window-background-color')
|
||||
@backgroundStylesheet = document.createElement('style')
|
||||
@backgroundStylesheet.type = 'text/css'
|
||||
@backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + ' !important; }'
|
||||
document.head.appendChild(@backgroundStylesheet)
|
||||
|
||||
storeWindowBackground: ->
|
||||
return if @inSpecMode()
|
||||
@@ -610,44 +652,57 @@ class AtomEnvironment extends Model
|
||||
|
||||
# Call this method when establishing a real application window.
|
||||
startEditorWindow: ->
|
||||
@commandInstaller.installAtomCommand false, (error) ->
|
||||
console.warn error.message if error?
|
||||
@commandInstaller.installApmCommand false, (error) ->
|
||||
console.warn error.message if error?
|
||||
@loadState().then (state) =>
|
||||
@windowDimensions = state?.windowDimensions
|
||||
@displayWindow().then =>
|
||||
@commandInstaller.installAtomCommand false, (error) ->
|
||||
console.warn error.message if error?
|
||||
@commandInstaller.installApmCommand false, (error) ->
|
||||
console.warn error.message if error?
|
||||
|
||||
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
|
||||
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
|
||||
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
|
||||
@listenForUpdates()
|
||||
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
|
||||
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
|
||||
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
|
||||
@listenForUpdates()
|
||||
|
||||
@registerDefaultTargetForKeymaps()
|
||||
@registerDefaultTargetForKeymaps()
|
||||
|
||||
@packages.loadPackages()
|
||||
@loadStateSync()
|
||||
@document.body.appendChild(@views.getView(@workspace))
|
||||
@packages.loadPackages()
|
||||
|
||||
@watchProjectPath()
|
||||
startTime = Date.now()
|
||||
@deserialize(state) if state?
|
||||
@deserializeTimings.atom = Date.now() - startTime
|
||||
|
||||
@packages.activate()
|
||||
@keymaps.loadUserKeymap()
|
||||
@requireUserInitScript() unless @getLoadSettings().safeMode
|
||||
@document.body.appendChild(@views.getView(@workspace))
|
||||
@backgroundStylesheet?.remove()
|
||||
|
||||
@menu.update()
|
||||
@watchProjectPaths()
|
||||
|
||||
@openInitialEmptyEditorIfNecessary()
|
||||
@packages.activate()
|
||||
@keymaps.loadUserKeymap()
|
||||
@requireUserInitScript() unless @getLoadSettings().safeMode
|
||||
|
||||
@menu.update()
|
||||
|
||||
@openInitialEmptyEditorIfNecessary()
|
||||
|
||||
serialize: (options) ->
|
||||
version: @constructor.version
|
||||
project: @project.serialize(options)
|
||||
workspace: @workspace.serialize()
|
||||
packageStates: @packages.serialize()
|
||||
grammars: {grammarOverridesByPath: @grammars.grammarOverridesByPath}
|
||||
fullScreen: @isFullScreen()
|
||||
windowDimensions: @windowDimensions
|
||||
|
||||
unloadEditorWindow: ->
|
||||
return if not @project
|
||||
|
||||
@saveState({isUnloading: true})
|
||||
@storeWindowBackground()
|
||||
@state.grammars = {grammarOverridesByPath: @grammars.grammarOverridesByPath}
|
||||
@state.project = @project.serialize()
|
||||
@state.workspace = @workspace.serialize()
|
||||
@packages.deactivatePackages()
|
||||
@state.packageStates = @packages.packageStates
|
||||
@state.fullScreen = @isFullScreen()
|
||||
@saveStateSync()
|
||||
@saveBlobStoreSync()
|
||||
@unloaded = true
|
||||
|
||||
openInitialEmptyEditorIfNecessary: ->
|
||||
return unless @config.get('core.openEmptyEditorOnStart')
|
||||
@@ -755,7 +810,7 @@ class AtomEnvironment extends Model
|
||||
@themes.load()
|
||||
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
watchProjectPaths: ->
|
||||
@disposables.add @project.onDidChangePaths =>
|
||||
@applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths())
|
||||
|
||||
@@ -780,45 +835,44 @@ class AtomEnvironment extends Model
|
||||
|
||||
@blobStore.save()
|
||||
|
||||
saveStateSync: ->
|
||||
return unless @enablePersistence
|
||||
saveState: (options) ->
|
||||
return Promise.resolve() unless @enablePersistence
|
||||
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@getStorageFolder().store(storageKey, @state)
|
||||
new Promise (resolve, reject) =>
|
||||
window.requestIdleCallback =>
|
||||
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)
|
||||
|
||||
loadState: ->
|
||||
if @enablePersistence
|
||||
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
|
||||
@stateStore.load(stateKey)
|
||||
else
|
||||
@applicationDelegate.getTemporaryWindowState()
|
||||
else
|
||||
@getCurrentWindow().loadSettings.windowState = JSON.stringify(@state)
|
||||
Promise.resolve(null)
|
||||
|
||||
loadStateSync: ->
|
||||
return unless @enablePersistence
|
||||
|
||||
startTime = Date.now()
|
||||
|
||||
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
|
||||
if state = @getStorageFolder().load(stateKey)
|
||||
@state = state
|
||||
|
||||
if not @state? and windowState = @getLoadSettings().windowState
|
||||
try
|
||||
if state = JSON.parse(@getLoadSettings().windowState)
|
||||
@state = state
|
||||
catch error
|
||||
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
|
||||
|
||||
@deserializeTimings.atom = Date.now() - startTime
|
||||
|
||||
if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath
|
||||
deserialize: (state) ->
|
||||
if grammarOverridesByPath = state.grammars?.grammarOverridesByPath
|
||||
@grammars.grammarOverridesByPath = grammarOverridesByPath
|
||||
|
||||
@setFullScreen(@state.fullScreen)
|
||||
@setFullScreen(state.fullScreen)
|
||||
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
@packages.packageStates = state.packageStates ? {}
|
||||
|
||||
startTime = Date.now()
|
||||
@project.deserialize(@state.project, @deserializers) if @state.project?
|
||||
@project.deserialize(state.project, @deserializers) if state.project?
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
startTime = Date.now()
|
||||
@workspace.deserialize(@state.workspace, @deserializers) if @state.workspace?
|
||||
@workspace.deserialize(state.workspace, @deserializers) if state.workspace?
|
||||
@deserializeTimings.workspace = Date.now() - startTime
|
||||
|
||||
getStateKey: (paths) ->
|
||||
@@ -831,9 +885,6 @@ class AtomEnvironment extends Model
|
||||
getConfigDirPath: ->
|
||||
@configDirPath ?= process.env.ATOM_HOME
|
||||
|
||||
getStorageFolder: ->
|
||||
@storageFolder ?= new StorageFolder(@getConfigDirPath())
|
||||
|
||||
getUserInitScriptPath: ->
|
||||
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
|
||||
initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee')
|
||||
@@ -847,6 +898,7 @@ class AtomEnvironment extends Model
|
||||
detail: error.message
|
||||
dismissable: true
|
||||
|
||||
# TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead
|
||||
onUpdateAvailable: (callback) ->
|
||||
@emitter.on 'update-available', callback
|
||||
|
||||
@@ -854,7 +906,8 @@ class AtomEnvironment extends Model
|
||||
@emitter.emit 'update-available', details
|
||||
|
||||
listenForUpdates: ->
|
||||
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
|
||||
# listen for updates available locally (that have been successfully downloaded)
|
||||
@disposables.add(@autoUpdater.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this)))
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
@document.body.classList.add("platform-#{process.platform}")
|
||||
@@ -876,8 +929,8 @@ class AtomEnvironment extends Model
|
||||
openLocations: (locations) ->
|
||||
needsProjectPaths = @project?.getPaths().length is 0
|
||||
|
||||
for {pathToOpen, initialLine, initialColumn} in locations
|
||||
if pathToOpen? and needsProjectPaths
|
||||
for {pathToOpen, initialLine, initialColumn, forceAddToWindow} in locations
|
||||
if pathToOpen? and (needsProjectPaths or forceAddToWindow)
|
||||
if fs.existsSync(pathToOpen)
|
||||
@project.addPath(pathToOpen)
|
||||
else if fs.existsSync(path.dirname(pathToOpen))
|
||||
|
||||
73
src/auto-update-manager.js
Normal file
73
src/auto-update-manager.js
Normal file
@@ -0,0 +1,73 @@
|
||||
'use babel'
|
||||
|
||||
import {Emitter, CompositeDisposable} from 'event-kit'
|
||||
|
||||
export default class AutoUpdateManager {
|
||||
constructor ({applicationDelegate}) {
|
||||
this.applicationDelegate = applicationDelegate
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.emitter = new Emitter()
|
||||
|
||||
this.subscriptions.add(
|
||||
applicationDelegate.onDidBeginCheckingForUpdate(() => {
|
||||
this.emitter.emit('did-begin-checking-for-update')
|
||||
}),
|
||||
applicationDelegate.onDidBeginDownloadingUpdate(() => {
|
||||
this.emitter.emit('did-begin-downloading-update')
|
||||
}),
|
||||
applicationDelegate.onDidCompleteDownloadingUpdate((details) => {
|
||||
this.emitter.emit('did-complete-downloading-update', details)
|
||||
}),
|
||||
applicationDelegate.onUpdateNotAvailable(() => {
|
||||
this.emitter.emit('update-not-available')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.subscriptions.dispose()
|
||||
this.emitter.dispose()
|
||||
}
|
||||
|
||||
checkForUpdate () {
|
||||
this.applicationDelegate.checkForUpdate()
|
||||
}
|
||||
|
||||
restartAndInstallUpdate () {
|
||||
this.applicationDelegate.restartAndInstallUpdate()
|
||||
}
|
||||
|
||||
getState () {
|
||||
return this.applicationDelegate.getAutoUpdateManagerState()
|
||||
}
|
||||
|
||||
platformSupportsUpdates () {
|
||||
return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported'
|
||||
}
|
||||
|
||||
onDidBeginCheckingForUpdate (callback) {
|
||||
return this.emitter.on('did-begin-checking-for-update', callback)
|
||||
}
|
||||
|
||||
onDidBeginDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-begin-downloading-update', callback)
|
||||
}
|
||||
|
||||
onDidCompleteDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-complete-downloading-update', callback)
|
||||
}
|
||||
|
||||
// TODO: When https://github.com/atom/electron/issues/4587 is closed, we can
|
||||
// add an update-available event.
|
||||
// onUpdateAvailable (callback) {
|
||||
// return this.emitter.on('update-available', callback)
|
||||
// }
|
||||
|
||||
onUpdateNotAvailable (callback) {
|
||||
return this.emitter.on('update-not-available', callback)
|
||||
}
|
||||
|
||||
getPlatform () {
|
||||
return process.platform
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
app = require 'app'
|
||||
ipc = require 'ipc'
|
||||
Menu = require 'menu'
|
||||
{app, Menu} = require 'electron'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
# Used to manage the global application menu.
|
||||
|
||||
@@ -2,14 +2,10 @@ AtomWindow = require './atom-window'
|
||||
ApplicationMenu = require './application-menu'
|
||||
AtomProtocolHandler = require './atom-protocol-handler'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
BrowserWindow = require 'browser-window'
|
||||
StorageFolder = require '../storage-folder'
|
||||
Menu = require 'menu'
|
||||
app = require 'app'
|
||||
dialog = require 'dialog'
|
||||
shell = require 'shell'
|
||||
ipcHelpers = require '../ipc-helpers'
|
||||
{BrowserWindow, Menu, app, dialog, ipcMain, shell} = require 'electron'
|
||||
fs = require 'fs-plus'
|
||||
ipc = require 'ipc'
|
||||
path = require 'path'
|
||||
os = require 'os'
|
||||
net = require 'net'
|
||||
@@ -51,7 +47,7 @@ class AtomApplication
|
||||
client = net.connect {path: options.socketPath}, ->
|
||||
client.write JSON.stringify(options), ->
|
||||
client.end()
|
||||
app.terminate()
|
||||
app.quit()
|
||||
|
||||
client.on 'error', createAtomApplication
|
||||
|
||||
@@ -65,7 +61,7 @@ class AtomApplication
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
constructor: (options) ->
|
||||
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout} = options
|
||||
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout, clearWindowState} = options
|
||||
|
||||
@socketPath = null if options.test
|
||||
|
||||
@@ -89,16 +85,16 @@ class AtomApplication
|
||||
else
|
||||
@loadState(options) or @openPath(options)
|
||||
|
||||
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout}) ->
|
||||
openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow}) ->
|
||||
if test
|
||||
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup})
|
||||
@openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen
|
||||
else
|
||||
# Always open a editor window if this is the first instance of Atom.
|
||||
@openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup})
|
||||
@openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow})
|
||||
|
||||
# Public: Removes the {AtomWindow} from the global window list.
|
||||
removeWindow: (window) ->
|
||||
@@ -179,7 +175,6 @@ class AtomApplication
|
||||
|
||||
@on 'application:open-documentation', -> shell.openExternal('https://atom.io/docs/latest/?app')
|
||||
@on 'application:open-discussions', -> shell.openExternal('https://discuss.atom.io')
|
||||
@on 'application:open-roadmap', -> shell.openExternal('https://atom.io/roadmap?app')
|
||||
@on 'application:open-faq', -> shell.openExternal('https://atom.io/faq')
|
||||
@on 'application:open-terms-of-use', -> shell.openExternal('https://atom.io/terms')
|
||||
@on 'application:report-issue', -> shell.openExternal('https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues')
|
||||
@@ -212,7 +207,6 @@ class AtomApplication
|
||||
@openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))
|
||||
|
||||
app.on 'before-quit', =>
|
||||
@saveState(false)
|
||||
@quitting = true
|
||||
|
||||
app.on 'will-quit', =>
|
||||
@@ -232,7 +226,7 @@ class AtomApplication
|
||||
@emit('application:new-window')
|
||||
|
||||
# A request from the associated render process to open a new render process.
|
||||
ipc.on 'open', (event, options) =>
|
||||
ipcMain.on 'open', (event, options) =>
|
||||
window = @windowForEvent(event)
|
||||
if options?
|
||||
if typeof options.pathsToOpen is 'string'
|
||||
@@ -245,44 +239,80 @@ class AtomApplication
|
||||
else
|
||||
@promptForPathToOpen('all', {window})
|
||||
|
||||
ipc.on 'update-application-menu', (event, template, keystrokesByCommand) =>
|
||||
ipcMain.on 'update-application-menu', (event, template, keystrokesByCommand) =>
|
||||
win = BrowserWindow.fromWebContents(event.sender)
|
||||
@applicationMenu.update(win, template, keystrokesByCommand)
|
||||
|
||||
ipc.on 'run-package-specs', (event, packageSpecPath) =>
|
||||
ipcMain.on 'run-package-specs', (event, packageSpecPath) =>
|
||||
@runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false})
|
||||
|
||||
ipc.on 'command', (event, command) =>
|
||||
ipcMain.on 'command', (event, command) =>
|
||||
@emit(command)
|
||||
|
||||
ipc.on 'window-command', (event, command, args...) ->
|
||||
ipcMain.on 'window-command', (event, command, args...) ->
|
||||
win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.emit(command, args...)
|
||||
|
||||
ipc.on 'call-window-method', (event, method, args...) ->
|
||||
ipcMain.on 'call-window-method', (event, method, args...) ->
|
||||
win = BrowserWindow.fromWebContents(event.sender)
|
||||
win[method](args...)
|
||||
|
||||
ipc.on 'pick-folder', (event, responseChannel) =>
|
||||
ipcMain.on 'pick-folder', (event, responseChannel) =>
|
||||
@promptForPath "folder", (selectedPaths) ->
|
||||
event.sender.send(responseChannel, selectedPaths)
|
||||
|
||||
ipc.on 'did-cancel-window-unload', =>
|
||||
ipcHelpers.respondTo 'set-window-size', (win, width, height) ->
|
||||
win.setSize(width, height)
|
||||
|
||||
ipcHelpers.respondTo 'set-window-position', (win, x, y) ->
|
||||
win.setPosition(x, y)
|
||||
|
||||
ipcHelpers.respondTo 'center-window', (win) ->
|
||||
win.center()
|
||||
|
||||
ipcHelpers.respondTo 'focus-window', (win) ->
|
||||
win.focus()
|
||||
|
||||
ipcHelpers.respondTo 'show-window', (win) ->
|
||||
win.show()
|
||||
|
||||
ipcHelpers.respondTo 'hide-window', (win) ->
|
||||
win.hide()
|
||||
|
||||
ipcHelpers.respondTo 'get-temporary-window-state', (win) ->
|
||||
win.temporaryState
|
||||
|
||||
ipcHelpers.respondTo 'set-temporary-window-state', (win, state) ->
|
||||
win.temporaryState = state
|
||||
|
||||
ipcMain.on 'did-cancel-window-unload', =>
|
||||
@quitting = false
|
||||
|
||||
clipboard = require '../safe-clipboard'
|
||||
ipc.on 'write-text-to-selection-clipboard', (event, selectedText) ->
|
||||
ipcMain.on 'write-text-to-selection-clipboard', (event, selectedText) ->
|
||||
clipboard.writeText(selectedText, 'selection')
|
||||
|
||||
ipc.on 'write-to-stdout', (event, output) ->
|
||||
ipcMain.on 'write-to-stdout', (event, output) ->
|
||||
process.stdout.write(output)
|
||||
|
||||
ipc.on 'write-to-stderr', (event, output) ->
|
||||
ipcMain.on 'write-to-stderr', (event, output) ->
|
||||
process.stderr.write(output)
|
||||
|
||||
ipc.on 'add-recent-document', (event, filename) ->
|
||||
ipcMain.on 'add-recent-document', (event, filename) ->
|
||||
app.addRecentDocument(filename)
|
||||
|
||||
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 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
setupDockMenu: ->
|
||||
if process.platform is 'darwin'
|
||||
dockMenu = Menu.buildFromTemplate [
|
||||
@@ -350,7 +380,7 @@ class AtomApplication
|
||||
_.find @windows, (atomWindow) ->
|
||||
atomWindow.devMode is devMode and atomWindow.containsPaths(pathsToOpen)
|
||||
|
||||
# Returns the {AtomWindow} for the given ipc event.
|
||||
# Returns the {AtomWindow} for the given ipcMain event.
|
||||
windowForEvent: ({sender}) ->
|
||||
window = BrowserWindow.fromWebContents(sender)
|
||||
_.find @windows, ({browserWindow}) -> window is browserWindow
|
||||
@@ -387,8 +417,9 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :profileStartup - Boolean to control creating a profile of the startup time.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window} = {}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window})
|
||||
# :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPath: ({initialPaths, pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState, addToLastWindow} = {}) ->
|
||||
@openPaths({initialPaths, pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState, addToLastWindow})
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
@@ -400,10 +431,12 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :windowDimensions - Object with height and width keys.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window}={}) ->
|
||||
# :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPaths: ({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window, clearWindowState, addToLastWindow}={}) ->
|
||||
devMode = Boolean(devMode)
|
||||
safeMode = Boolean(safeMode)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom) for pathToOpen in pathsToOpen)
|
||||
clearWindowState = Boolean(clearWindowState)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom, addToLastWindow) for pathToOpen in pathsToOpen)
|
||||
pathsToOpen = (locationToOpen.pathToOpen for locationToOpen in locationsToOpen)
|
||||
|
||||
unless pidToKillWhenClosed or newWindow
|
||||
@@ -412,6 +445,7 @@ class AtomApplication
|
||||
unless existingWindow?
|
||||
if currentWindow = window ? @lastFocusedWindow
|
||||
existingWindow = currentWindow if (
|
||||
addToLastWindow or
|
||||
currentWindow.devMode is devMode and
|
||||
(
|
||||
stats.every((stat) -> stat.isFile?()) or
|
||||
@@ -435,7 +469,7 @@ class AtomApplication
|
||||
windowInitializationScript ?= require.resolve('../initialize-application-window')
|
||||
resourcePath ?= @resourcePath
|
||||
windowDimensions ?= @getDimensionsForNewWindow()
|
||||
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
|
||||
openedWindow = new AtomWindow({initialPaths, locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@@ -472,13 +506,14 @@ class AtomApplication
|
||||
if loadSettings = window.getLoadSettings()
|
||||
states.push(initialPaths: loadSettings.initialPaths)
|
||||
if states.length > 0 or allowEmpty
|
||||
@storageFolder.store('application.json', states)
|
||||
@storageFolder.storeSync('application.json', states)
|
||||
|
||||
loadState: (options) ->
|
||||
if (states = @storageFolder.load('application.json'))?.length > 0
|
||||
for state in states
|
||||
@openWithOptions(_.extend(options, {
|
||||
pathsToOpen: state.initialPaths
|
||||
initialPaths: state.initialPaths
|
||||
pathsToOpen: state.initialPaths.filter (directoryPath) -> fs.isDirectorySync(directoryPath)
|
||||
urlsToOpen: []
|
||||
devMode: @devMode
|
||||
safeMode: @safeMode
|
||||
@@ -511,7 +546,7 @@ class AtomApplication
|
||||
if pack.urlMain
|
||||
packagePath = @packages.resolvePackagePath(packageName)
|
||||
windowInitializationScript = path.resolve(packagePath, pack.urlMain)
|
||||
windowDimensions = @focusedWindow()?.getDimensions()
|
||||
windowDimensions = @getDimensionsForNewWindow()
|
||||
new AtomWindow({windowInitializationScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions})
|
||||
else
|
||||
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
|
||||
@@ -580,7 +615,7 @@ class AtomApplication
|
||||
catch error
|
||||
require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner'))
|
||||
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='') ->
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='', forceAddToWindow) ->
|
||||
return {pathToOpen} unless pathToOpen
|
||||
|
||||
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')
|
||||
@@ -596,7 +631,7 @@ class AtomApplication
|
||||
unless url.parse(pathToOpen).protocol?
|
||||
pathToOpen = path.resolve(executedFrom, fs.normalize(pathToOpen))
|
||||
|
||||
{pathToOpen, initialLine, initialColumn}
|
||||
{pathToOpen, initialLine, initialColumn, forceAddToWindow}
|
||||
|
||||
# Opens a native dialog to prompt the user for a path.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
ipc = require 'ipc'
|
||||
{ipcMain} = require 'electron'
|
||||
|
||||
module.exports =
|
||||
class AtomPortable
|
||||
@@ -30,6 +30,6 @@ class AtomPortable
|
||||
catch error
|
||||
message = "Failed to use portable Atom home directory (#{@getPortableAtomHomePath()}). Using the default instead (#{defaultHome}). #{error.message}"
|
||||
|
||||
ipc.on 'check-portable-home-writable', (event) ->
|
||||
ipcMain.on 'check-portable-home-writable', (event) ->
|
||||
event.sender.send 'check-portable-home-writable-response', {writable, message}
|
||||
writable
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
app = require 'app'
|
||||
{app, protocol} = require 'electron'
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
protocol = require 'protocol'
|
||||
|
||||
# Handles requests with 'atom' protocol.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
BrowserWindow = require 'browser-window'
|
||||
app = require 'app'
|
||||
dialog = require 'dialog'
|
||||
{BrowserWindow, app, dialog} = require 'electron'
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
url = require 'url'
|
||||
@@ -19,7 +17,7 @@ class AtomWindow
|
||||
isSpec: null
|
||||
|
||||
constructor: (settings={}) ->
|
||||
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
|
||||
{@resourcePath, initialPaths, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
|
||||
locationsToOpen ?= [{pathToOpen}] if pathToOpen
|
||||
locationsToOpen ?= []
|
||||
|
||||
@@ -43,19 +41,13 @@ class AtomWindow
|
||||
@handleEvents()
|
||||
|
||||
loadSettings = _.extend({}, settings)
|
||||
loadSettings.windowState ?= '{}'
|
||||
loadSettings.appVersion = app.getVersion()
|
||||
loadSettings.resourcePath = @resourcePath
|
||||
loadSettings.devMode ?= false
|
||||
loadSettings.safeMode ?= false
|
||||
loadSettings.atomHome = process.env.ATOM_HOME
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
loadSettings.initialPaths =
|
||||
loadSettings.clearWindowState ?= false
|
||||
loadSettings.initialPaths ?=
|
||||
for {pathToOpen} in locationsToOpen when pathToOpen
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
path.dirname(pathToOpen)
|
||||
@@ -64,24 +56,26 @@ class AtomWindow
|
||||
|
||||
loadSettings.initialPaths.sort()
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
@browserWindow.loadSettings = loadSettings
|
||||
|
||||
@browserWindow.once 'window:loaded', =>
|
||||
@emit 'window:loaded'
|
||||
@loaded = true
|
||||
|
||||
@setLoadSettings(loadSettings)
|
||||
@browserWindow.focusOnWebView() if @isSpec
|
||||
@browserWindow.temporaryState = {windowDimensions} if windowDimensions?
|
||||
|
||||
hasPathToOpen = not (locationsToOpen.length is 1 and not locationsToOpen[0].pathToOpen?)
|
||||
@openLocations(locationsToOpen) if hasPathToOpen and not @isSpecWindow()
|
||||
|
||||
setLoadSettings: (loadSettingsObj) ->
|
||||
# Ignore the windowState when passing loadSettings via URL, since it could
|
||||
# be quite large.
|
||||
loadSettings = _.clone(loadSettingsObj)
|
||||
delete loadSettings['windowState']
|
||||
|
||||
@browserWindow.loadUrl url.format
|
||||
setLoadSettings: (loadSettings) ->
|
||||
@browserWindow.loadURL url.format
|
||||
protocol: 'file'
|
||||
pathname: "#{@resourcePath}/static/index.html"
|
||||
slashes: true
|
||||
@@ -89,7 +83,7 @@ class AtomWindow
|
||||
|
||||
getLoadSettings: ->
|
||||
if @browserWindow.webContents? and not @browserWindow.webContents.isLoading()
|
||||
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
|
||||
hash = url.parse(@browserWindow.webContents.getURL()).hash.substr(1)
|
||||
JSON.parse(decodeURIComponent(hash))
|
||||
|
||||
hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0
|
||||
@@ -121,6 +115,9 @@ class AtomWindow
|
||||
false
|
||||
|
||||
handleEvents: ->
|
||||
@browserWindow.on 'close', ->
|
||||
global.atomApplication.saveState(false)
|
||||
|
||||
@browserWindow.on 'closed', =>
|
||||
global.atomApplication.removeWindow(this)
|
||||
|
||||
@@ -144,10 +141,10 @@ class AtomWindow
|
||||
detail: 'Please report this issue to https://github.com/atom/atom'
|
||||
switch chosen
|
||||
when 0 then @browserWindow.destroy()
|
||||
when 1 then @browserWindow.restart()
|
||||
when 1 then @browserWindow.reload()
|
||||
|
||||
@browserWindow.webContents.on 'will-navigate', (event, url) =>
|
||||
unless url is @browserWindow.webContents.getUrl()
|
||||
unless url is @browserWindow.webContents.getURL()
|
||||
event.preventDefault()
|
||||
|
||||
@setupContextMenu()
|
||||
@@ -220,6 +217,6 @@ class AtomWindow
|
||||
|
||||
isSpecWindow: -> @isSpec
|
||||
|
||||
reload: -> @browserWindow.restart()
|
||||
reload: -> @browserWindow.reload()
|
||||
|
||||
toggleDevTools: -> @browserWindow.toggleDevTools()
|
||||
|
||||
@@ -29,26 +29,34 @@ class AutoUpdateManager
|
||||
if process.platform is 'win32'
|
||||
autoUpdater = require './auto-updater-win32'
|
||||
else
|
||||
autoUpdater = require 'auto-updater'
|
||||
{autoUpdater} = require 'electron'
|
||||
|
||||
autoUpdater.on 'error', (event, message) =>
|
||||
@setState(ErrorState)
|
||||
console.error "Error Downloading Update: #{message}"
|
||||
|
||||
autoUpdater.setFeedUrl @feedUrl
|
||||
autoUpdater.setFeedURL @feedUrl
|
||||
|
||||
autoUpdater.on 'checking-for-update', =>
|
||||
@setState(CheckingState)
|
||||
@emitWindowEvent('checking-for-update')
|
||||
|
||||
autoUpdater.on 'update-not-available', =>
|
||||
@setState(NoUpdateAvailableState)
|
||||
@emitWindowEvent('update-not-available')
|
||||
|
||||
autoUpdater.on 'update-available', =>
|
||||
@setState(DownladingState)
|
||||
# We use sendMessage to send an event called 'update-available' in 'update-downloaded'
|
||||
# once the update download is complete. This mismatch between the electron
|
||||
# autoUpdater events is unfortunate but in the interest of not changing the
|
||||
# one existing event handled by applicationDelegate
|
||||
@emitWindowEvent('did-begin-downloading-update')
|
||||
@emit('did-begin-download')
|
||||
|
||||
autoUpdater.on 'update-downloaded', (event, releaseNotes, @releaseVersion) =>
|
||||
@setState(UpdateAvailableState)
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
@emitUpdateAvailableEvent()
|
||||
|
||||
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
|
||||
if newValue
|
||||
@@ -64,10 +72,14 @@ class AutoUpdateManager
|
||||
when 'linux'
|
||||
@setState(UnsupportedState)
|
||||
|
||||
emitUpdateAvailableEvent: (windows...) ->
|
||||
emitUpdateAvailableEvent: ->
|
||||
return unless @releaseVersion?
|
||||
for atomWindow in windows
|
||||
atomWindow.sendMessage('update-available', {@releaseVersion})
|
||||
@emitWindowEvent('update-available', {@releaseVersion})
|
||||
return
|
||||
|
||||
emitWindowEvent: (eventName, payload) ->
|
||||
for atomWindow in @getWindows()
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
return
|
||||
|
||||
setState: (state) ->
|
||||
@@ -104,7 +116,7 @@ class AutoUpdateManager
|
||||
|
||||
onUpdateNotAvailable: =>
|
||||
autoUpdater.removeListener 'error', @onUpdateError
|
||||
dialog = require 'dialog'
|
||||
{dialog} = require 'electron'
|
||||
dialog.showMessageBox
|
||||
type: 'info'
|
||||
buttons: ['OK']
|
||||
@@ -115,7 +127,7 @@ class AutoUpdateManager
|
||||
|
||||
onUpdateError: (event, message) =>
|
||||
autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable
|
||||
dialog = require 'dialog'
|
||||
{dialog} = require 'electron'
|
||||
dialog.showMessageBox
|
||||
type: 'warning'
|
||||
buttons: ['OK']
|
||||
|
||||
@@ -5,13 +5,13 @@ SquirrelUpdate = require './squirrel-update'
|
||||
class AutoUpdater
|
||||
_.extend @prototype, EventEmitter.prototype
|
||||
|
||||
setFeedUrl: (@updateUrl) ->
|
||||
setFeedURL: (@updateUrl) ->
|
||||
|
||||
quitAndInstall: ->
|
||||
if SquirrelUpdate.existsSync()
|
||||
SquirrelUpdate.restartAtom(require('app'))
|
||||
SquirrelUpdate.restartAtom(require('electron').app)
|
||||
else
|
||||
require('auto-updater').quitAndInstall()
|
||||
require('electron').autoUpdater.quitAndInstall()
|
||||
|
||||
downloadUpdate: (callback) ->
|
||||
SquirrelUpdate.spawn ['--download', @updateUrl], (error, stdout) ->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Menu = require 'menu'
|
||||
{Menu} = require 'electron'
|
||||
|
||||
module.exports =
|
||||
class ContextMenu
|
||||
|
||||
@@ -4,10 +4,10 @@ process.on 'uncaughtException', (error={}) ->
|
||||
console.log(error.message) if error.message?
|
||||
console.log(error.stack) if error.stack?
|
||||
|
||||
crashReporter = require 'crash-reporter'
|
||||
app = require 'app'
|
||||
{crashReporter, app} = require 'electron'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
yargs = require 'yargs'
|
||||
console.log = require 'nslog'
|
||||
|
||||
@@ -32,6 +32,11 @@ start = ->
|
||||
app.on 'open-url', addUrlToOpen
|
||||
app.on 'will-finish-launching', setupCrashReporter
|
||||
|
||||
if args.userDataDir?
|
||||
app.setPath('userData', args.userDataDir)
|
||||
else if args.test
|
||||
app.setPath('userData', temp.mkdirSync('atom-test-data'))
|
||||
|
||||
app.on 'ready', ->
|
||||
app.removeListener 'open-file', addPathToOpen
|
||||
app.removeListener 'open-url', addUrlToOpen
|
||||
@@ -54,12 +59,12 @@ handleStartupEventWithSquirrel = ->
|
||||
SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
|
||||
|
||||
setupCrashReporter = ->
|
||||
crashReporter.start(productName: 'Atom', companyName: 'GitHub')
|
||||
crashReporter.start(productName: 'Atom', companyName: 'GitHub', submitURL: 'http://54.249.141.255:1127/post')
|
||||
|
||||
setupAtomHome = ({setPortable}) ->
|
||||
return if process.env.ATOM_HOME
|
||||
|
||||
atomHome = path.join(app.getHomeDir(), '.atom')
|
||||
atomHome = path.join(app.getPath('home'), '.atom')
|
||||
AtomPortable = require './atom-portable'
|
||||
|
||||
if setPortable and not AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)
|
||||
@@ -81,6 +86,15 @@ setupCompileCache = ->
|
||||
compileCache = require('../compile-cache')
|
||||
compileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
|
||||
|
||||
writeFullVersion = ->
|
||||
process.stdout.write """
|
||||
Atom : #{app.getVersion()}
|
||||
Electron: #{process.versions.electron}
|
||||
Chrome : #{process.versions.chrome}
|
||||
Node : #{process.versions.node}
|
||||
|
||||
"""
|
||||
|
||||
parseCommandLine = ->
|
||||
version = app.getVersion()
|
||||
options = yargs(process.argv[1..]).wrap(100)
|
||||
@@ -116,9 +130,12 @@ parseCommandLine = ->
|
||||
options.boolean('portable').describe('portable', 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.')
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
|
||||
options.string('timeout').describe('timeout', 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).')
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
|
||||
options.string('socket-path')
|
||||
options.string('user-data-dir')
|
||||
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
|
||||
|
||||
args = options.argv
|
||||
|
||||
@@ -127,9 +144,10 @@ parseCommandLine = ->
|
||||
process.exit(0)
|
||||
|
||||
if args.version
|
||||
process.stdout.write("#{version}\n")
|
||||
writeFullVersion()
|
||||
process.exit(0)
|
||||
|
||||
addToLastWindow = args['add']
|
||||
executedFrom = args['executed-from']?.toString() ? process.cwd()
|
||||
devMode = args['dev']
|
||||
safeMode = args['safe']
|
||||
@@ -140,9 +158,11 @@ parseCommandLine = ->
|
||||
pidToKillWhenClosed = args['pid'] if args['wait']
|
||||
logFile = args['log-file']
|
||||
socketPath = args['socket-path']
|
||||
userDataDir = args['user-data-dir']
|
||||
profileStartup = args['profile-startup']
|
||||
clearWindowState = args['clear-window-state']
|
||||
urlsToOpen = []
|
||||
devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom')
|
||||
devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getPath('home'), 'github', 'atom')
|
||||
setPortable = args.portable
|
||||
|
||||
if args['resource-path']
|
||||
@@ -164,6 +184,7 @@ parseCommandLine = ->
|
||||
|
||||
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
|
||||
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
|
||||
logFile, socketPath, profileStartup, timeout, setPortable}
|
||||
logFile, socketPath, userDataDir, profileStartup, timeout, setPortable,
|
||||
clearWindowState, addToLastWindow}
|
||||
|
||||
start()
|
||||
|
||||
@@ -11,15 +11,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
|
||||
@@ -43,11 +46,31 @@ spawn = (command, args, callback) ->
|
||||
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)
|
||||
@@ -64,6 +87,10 @@ installContextMenu = (callback) ->
|
||||
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, ->
|
||||
@@ -74,48 +101,17 @@ installContextMenu = (callback) ->
|
||||
|
||||
installMenu fileKeyPath, '%1', ->
|
||||
installMenu directoryKeyPath, '%1', ->
|
||||
installMenu(backgroundKeyPath, '%V', callback)
|
||||
|
||||
isAscii = (text) ->
|
||||
index = 0
|
||||
while index < text.length
|
||||
return false if text.charCodeAt(index) > 127
|
||||
index++
|
||||
true
|
||||
installMenu backgroundKeyPath, '%V', ->
|
||||
installFileHandler(callback)
|
||||
|
||||
# Get the user's PATH environment variable registry value.
|
||||
getPath = (callback) ->
|
||||
spawnReg ['query', environmentKeyPath, '/v', 'Path'], (error, stdout) ->
|
||||
spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) ->
|
||||
if error?
|
||||
if error.code is 1
|
||||
# FIXME Don't overwrite path when reading value is disabled
|
||||
# https://github.com/atom/atom/issues/5092
|
||||
if stdout.indexOf('ERROR: Registry editing has been disabled by your administrator.') isnt -1
|
||||
return callback(error)
|
||||
return callback(error)
|
||||
|
||||
# The query failed so the Path does not exist yet in the registry
|
||||
return callback(null, '')
|
||||
else
|
||||
return callback(error)
|
||||
|
||||
# Registry query output is in the form:
|
||||
#
|
||||
# HKEY_CURRENT_USER\Environment
|
||||
# Path REG_SZ C:\a\folder\on\the\path;C\another\folder
|
||||
#
|
||||
|
||||
lines = stdout.split(/[\r\n]+/).filter (line) -> line
|
||||
segments = lines[lines.length - 1]?.split(' ')
|
||||
if segments[1] is 'Path' and segments.length >= 3
|
||||
pathEnv = segments?[3..].join(' ')
|
||||
if isAscii(pathEnv)
|
||||
callback(null, pathEnv)
|
||||
else
|
||||
# FIXME Don't corrupt non-ASCII PATH values
|
||||
# https://github.com/atom/atom/issues/5063
|
||||
callback(new Error('PATH contains non-ASCII values'))
|
||||
else
|
||||
callback(new Error('Registry query for PATH failed'))
|
||||
pathOutput = stdout.replace(/^\s+|\s+$/g, '')
|
||||
callback(null, pathOutput)
|
||||
|
||||
# Uninstall the Open with Atom explorer context menu items via the registry.
|
||||
uninstallContextMenu = (callback) ->
|
||||
@@ -124,7 +120,8 @@ uninstallContextMenu = (callback) ->
|
||||
|
||||
deleteFromRegistry fileKeyPath, ->
|
||||
deleteFromRegistry directoryKeyPath, ->
|
||||
deleteFromRegistry(backgroundKeyPath, callback)
|
||||
deleteFromRegistry backgroundKeyPath, ->
|
||||
deleteFromRegistry(applicationsKeyPath, callback)
|
||||
|
||||
# Add atom and apm to the PATH
|
||||
#
|
||||
|
||||
@@ -46,7 +46,7 @@ class BufferedNodeProcess extends BufferedProcess
|
||||
|
||||
options ?= {}
|
||||
options.env ?= Object.create(process.env)
|
||||
options.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = 1
|
||||
options.env['ELECTRON_RUN_AS_NODE'] = 1
|
||||
|
||||
args = args?.slice() ? []
|
||||
args.unshift(command)
|
||||
|
||||
@@ -85,5 +85,5 @@ parseAlpha = (alpha) ->
|
||||
|
||||
numberToHexString = (number) ->
|
||||
hex = number.toString(16)
|
||||
hex = "0#{hex}" if number < 10
|
||||
hex = "0#{hex}" if number < 16
|
||||
hex
|
||||
|
||||
@@ -108,6 +108,10 @@ module.exports =
|
||||
description: 'Automatically update Atom when a new release is available.'
|
||||
type: 'boolean'
|
||||
default: true
|
||||
allowPendingPaneItems:
|
||||
description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.'
|
||||
type: 'boolean'
|
||||
default: true
|
||||
|
||||
editor:
|
||||
type: 'object'
|
||||
@@ -171,7 +175,7 @@ module.exports =
|
||||
tabLength:
|
||||
type: 'integer'
|
||||
default: 2
|
||||
enum: [1, 2, 3, 4, 6, 8]
|
||||
minimum: 1
|
||||
description: 'Number of spaces used to represent a tab.'
|
||||
softWrap:
|
||||
type: 'boolean'
|
||||
|
||||
@@ -319,6 +319,23 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
# * line breaks - `line breaks<br/>`
|
||||
# * ~~strikethrough~~ - `~~strikethrough~~`
|
||||
#
|
||||
# #### order
|
||||
#
|
||||
# The settings view orders your settings alphabetically. You can override this
|
||||
# ordering with the order key.
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# zSetting:
|
||||
# type: 'integer'
|
||||
# default: 4
|
||||
# order: 1
|
||||
# aSetting:
|
||||
# type: 'integer'
|
||||
# default: 4
|
||||
# order: 2
|
||||
# ```
|
||||
#
|
||||
# ## Best practices
|
||||
#
|
||||
# * Don't depend on (or write to) configuration keys outside of your keypath.
|
||||
|
||||
@@ -4,7 +4,7 @@ CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
{calculateSpecificity, validateSelector} = require 'clear-cut'
|
||||
{Disposable} = require 'event-kit'
|
||||
remote = require 'remote'
|
||||
{remote} = require 'electron'
|
||||
MenuHelpers = require './menu-helpers'
|
||||
|
||||
platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
|
||||
|
||||
@@ -393,7 +393,10 @@ class DisplayBuffer extends Model
|
||||
@displayLayer.foldsIntersectingBufferRange(Range(Point(bufferRow, 0), Point(bufferRow, Infinity))).length > 0
|
||||
|
||||
isFoldedAtScreenRow: (screenRow) ->
|
||||
@isFoldedAtBufferRow(@bufferRowForScreenRow(screenRow))?
|
||||
@isFoldedAtBufferRow(@bufferRowForScreenRow(screenRow))
|
||||
|
||||
isFoldableAtBufferRow: (row) ->
|
||||
@tokenizedBuffer.isFoldableAtRow(row)
|
||||
|
||||
# Removes any folds found that contain the given buffer row.
|
||||
#
|
||||
|
||||
@@ -202,32 +202,35 @@ export default class GitRepositoryAsync {
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// Depending on where the paths come from, they may have a '/private/'
|
||||
// prefix. Standardize by stripping that out.
|
||||
_path = _path.replace(/^\/private\//, '/')
|
||||
workingDirectory = workingDirectory.replace(/^\/private\//, '/')
|
||||
|
||||
const originalPath = _path
|
||||
if (_path.indexOf(workingDirectory) === 0) {
|
||||
return originalPath.substring(workingDirectory.length + 1)
|
||||
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()
|
||||
}
|
||||
openedWorkingDirectory = openedWorkingDirectory.replace(/\/$/, '')
|
||||
openedWorkingDirectory = openedWorkingDirectory.replace(/^\/private\//, '/')
|
||||
|
||||
if (_path.indexOf(openedWorkingDirectory) === 0) {
|
||||
return originalPath.substring(openedWorkingDirectory.length + 1)
|
||||
return originalPath.substring(originalOpenedWorkingDirectory.length + 1)
|
||||
} else if (_path === openedWorkingDirectory) {
|
||||
return ''
|
||||
}
|
||||
@@ -456,7 +459,10 @@ export default class GitRepositoryAsync {
|
||||
// information.
|
||||
getDirectoryStatus (directoryPath) {
|
||||
return this.relativizeToWorkingDirectory(directoryPath)
|
||||
.then(relativePath => this._getStatus([relativePath]))
|
||||
.then(relativePath => {
|
||||
const pathspec = relativePath + '/**'
|
||||
return this._getStatus([pathspec])
|
||||
})
|
||||
.then(statuses => {
|
||||
return Promise.all(statuses.map(s => s.statusBit())).then(bits => {
|
||||
return bits
|
||||
@@ -590,7 +596,15 @@ export default class GitRepositoryAsync {
|
||||
.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))
|
||||
@@ -769,12 +783,17 @@ export default class GitRepositoryAsync {
|
||||
|
||||
// Get the current branch and update this.branch.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {String} branch name.
|
||||
// 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 => this.branch = branchName)
|
||||
.then(branchName => {
|
||||
const changed = branchName !== this.branch
|
||||
this.branch = branchName
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh the cached ahead/behind count with the given branch.
|
||||
@@ -782,10 +801,15 @@ export default class GitRepositoryAsync {
|
||||
// * `branchName` The {String} name of the branch whose ahead/behind should be
|
||||
// used for the refresh.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null}.
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// the ahead/behind count changed.
|
||||
_refreshAheadBehindCount (branchName) {
|
||||
return this.getAheadBehindCount(branchName)
|
||||
.then(counts => this.upstream = counts)
|
||||
.then(counts => {
|
||||
const changed = !_.isEqual(counts, this.upstream)
|
||||
this.upstream = counts
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Get the status for this repository.
|
||||
@@ -800,7 +824,7 @@ export default class GitRepositoryAsync {
|
||||
}
|
||||
|
||||
return Promise.all(projectPathsPromises)
|
||||
.then(paths => paths.filter(p => p.length > 0))
|
||||
.then(paths => paths.map(p => p.length > 0 ? p + '/**' : '*'))
|
||||
.then(projectPaths => {
|
||||
return this._getStatus(projectPaths.length > 0 ? projectPaths : null)
|
||||
})
|
||||
@@ -891,15 +915,15 @@ export default class GitRepositoryAsync {
|
||||
|
||||
// Refresh the cached status.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null}.
|
||||
// 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)
|
||||
if (!_.isEqual(this.pathStatusCache, statusesByPath) && this.emitter != null) {
|
||||
this.emitter.emit('did-change-statuses')
|
||||
}
|
||||
const changed = !_.isEqual(this.pathStatusCache, statusesByPath)
|
||||
this.pathStatusCache = statusesByPath
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
@@ -909,11 +933,17 @@ export default class GitRepositoryAsync {
|
||||
refreshStatus () {
|
||||
const status = this._refreshStatus()
|
||||
const branch = this._refreshBranch()
|
||||
const aheadBehind = branch.then(branchName => this._refreshAheadBehindCount(branchName))
|
||||
const aheadBehind = branch.then(() => this._refreshAheadBehindCount(this.branch))
|
||||
|
||||
this._refreshingPromise = this._refreshingPromise.then(_ => {
|
||||
return Promise.all([status, branch, aheadBehind])
|
||||
.then(_ => null)
|
||||
.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.
|
||||
@@ -1032,7 +1062,7 @@ export default class GitRepositoryAsync {
|
||||
return this.getRepo()
|
||||
.then(repo => {
|
||||
const opts = {
|
||||
flags: Git.Status.OPT.INCLUDE_UNTRACKED | Git.Status.OPT.RECURSE_UNTRACKED_DIRS | Git.Status.OPT.DISABLE_PATHSPEC_MATCH
|
||||
flags: Git.Status.OPT.INCLUDE_UNTRACKED | Git.Status.OPT.RECURSE_UNTRACKED_DIRS
|
||||
}
|
||||
|
||||
if (paths) {
|
||||
|
||||
@@ -484,7 +484,7 @@ class GitRepository
|
||||
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.filter (path) -> path.length > 0
|
||||
.map (path) -> if path.length > 0 then path + '/**' else '*'
|
||||
|
||||
@statusTask?.terminate()
|
||||
@statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) =>
|
||||
|
||||
@@ -23,11 +23,10 @@ module.exports = ({blobStore}) ->
|
||||
enablePersistence: true
|
||||
})
|
||||
|
||||
atom.displayWindow()
|
||||
atom.startEditorWindow()
|
||||
atom.startEditorWindow().then ->
|
||||
|
||||
# Workaround for focus getting cleared upon window creation
|
||||
windowFocused = ->
|
||||
window.removeEventListener('focus', windowFocused)
|
||||
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
|
||||
window.addEventListener('focus', windowFocused)
|
||||
# Workaround for focus getting cleared upon window creation
|
||||
windowFocused = ->
|
||||
window.removeEventListener('focus', windowFocused)
|
||||
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
|
||||
window.addEventListener('focus', windowFocused)
|
||||
|
||||
@@ -4,17 +4,17 @@ cloneObject = (object) ->
|
||||
clone
|
||||
|
||||
module.exports = ({blobStore}) ->
|
||||
{crashReporter, remote} = require 'electron'
|
||||
# Start the crash reporter before anything else.
|
||||
require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub')
|
||||
remote = require 'remote'
|
||||
crashReporter.start(productName: 'Atom', companyName: 'GitHub', submitURL: 'http://54.249.141.255:1127/post')
|
||||
|
||||
exitWithStatusCode = (status) ->
|
||||
remote.require('app').emit('will-quit')
|
||||
remote.app.emit('will-quit')
|
||||
remote.process.exit(status)
|
||||
|
||||
try
|
||||
path = require 'path'
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
{getWindowLoadSettings} = require './window-load-settings-helpers'
|
||||
AtomEnvironment = require '../src/atom-environment'
|
||||
ApplicationDelegate = require '../src/application-delegate'
|
||||
@@ -29,15 +29,15 @@ module.exports = ({blobStore}) ->
|
||||
handleKeydown = (event) ->
|
||||
# Reload: cmd-r / ctrl-r
|
||||
if (event.metaKey or event.ctrlKey) and event.keyCode is 82
|
||||
ipc.send('call-window-method', 'restart')
|
||||
ipcRenderer.send('call-window-method', 'reload')
|
||||
|
||||
# Toggle Dev Tools: cmd-alt-i / ctrl-alt-i
|
||||
if (event.metaKey or event.ctrlKey) and event.altKey and event.keyCode is 73
|
||||
ipc.send('call-window-method', 'toggleDevTools')
|
||||
ipcRenderer.send('call-window-method', 'toggleDevTools')
|
||||
|
||||
# Reload: cmd-w / ctrl-w
|
||||
if (event.metaKey or event.ctrlKey) and event.keyCode is 87
|
||||
ipc.send('call-window-method', 'close')
|
||||
ipcRenderer.send('call-window-method', 'close')
|
||||
|
||||
window.addEventListener('keydown', handleKeydown, true)
|
||||
|
||||
@@ -68,7 +68,8 @@ module.exports = ({blobStore}) ->
|
||||
logFile, headless, testPaths, buildAtomEnvironment, buildDefaultApplicationDelegate, legacyTestRunner
|
||||
})
|
||||
|
||||
promise.then(exitWithStatusCode) if getWindowLoadSettings().headless
|
||||
promise.then (statusCode) ->
|
||||
exitWithStatusCode(statusCode) if getWindowLoadSettings().headless
|
||||
catch error
|
||||
if getWindowLoadSettings().headless
|
||||
console.error(error.stack ? error)
|
||||
|
||||
40
src/ipc-helpers.js
Normal file
40
src/ipc-helpers.js
Normal file
@@ -0,0 +1,40 @@
|
||||
var ipcRenderer = null
|
||||
var ipcMain = null
|
||||
var BrowserWindow = null
|
||||
|
||||
exports.call = function (methodName, ...args) {
|
||||
if (!ipcRenderer) {
|
||||
ipcRenderer = require('electron').ipcRenderer
|
||||
}
|
||||
|
||||
var responseChannel = getResponseChannel(methodName)
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
ipcRenderer.on(responseChannel, function (event, result) {
|
||||
ipcRenderer.removeAllListeners(responseChannel)
|
||||
resolve(result)
|
||||
})
|
||||
|
||||
ipcRenderer.send(methodName, ...args)
|
||||
})
|
||||
}
|
||||
|
||||
exports.respondTo = function (methodName, callback) {
|
||||
if (!ipcMain) {
|
||||
var electron = require('electron')
|
||||
ipcMain = electron.ipcMain
|
||||
BrowserWindow = electron.BrowserWindow
|
||||
}
|
||||
|
||||
var responseChannel = getResponseChannel(methodName)
|
||||
|
||||
ipcMain.on(methodName, function (event, ...args) {
|
||||
var browserWindow = BrowserWindow.fromWebContents(event.sender)
|
||||
var result = callback(browserWindow, ...args)
|
||||
event.sender.send(responseChannel, result)
|
||||
})
|
||||
}
|
||||
|
||||
function getResponseChannel (methodName) {
|
||||
return 'ipc-helpers-' + methodName + '-response'
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class LanguageMode
|
||||
# editor - The {TextEditor} to associate with
|
||||
constructor: (@editor, @config) ->
|
||||
{@buffer} = @editor
|
||||
@regexesByPattern = {}
|
||||
|
||||
destroy: ->
|
||||
|
||||
@@ -144,13 +145,11 @@ class LanguageMode
|
||||
|
||||
if bufferRow > 0
|
||||
for currentRow in [bufferRow-1..0] by -1
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
|
||||
if bufferRow < @buffer.getLastRow()
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
@@ -326,7 +325,8 @@ class LanguageMode
|
||||
|
||||
getRegexForProperty: (scopeDescriptor, property) ->
|
||||
if pattern = @config.get(property, scope: scopeDescriptor)
|
||||
new OnigRegExp(pattern)
|
||||
@regexesByPattern[pattern] ?= new OnigRegExp(pattern)
|
||||
@regexesByPattern[pattern]
|
||||
|
||||
increaseIndentRegexForScopeDescriptor: (scopeDescriptor) ->
|
||||
@getRegexForProperty(scopeDescriptor, 'editor.increaseIndentPattern')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
path = require 'path'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
{Disposable} = require 'event-kit'
|
||||
@@ -191,7 +191,7 @@ class MenuManager
|
||||
|
||||
sendToBrowserProcess: (template, keystrokesByCommand) ->
|
||||
keystrokesByCommand = @filterMultipleKeystroke(keystrokesByCommand)
|
||||
ipc.send 'update-application-menu', template, keystrokesByCommand
|
||||
ipcRenderer.send 'update-application-menu', template, keystrokesByCommand
|
||||
|
||||
# Get an {Array} of {String} classes for the given element.
|
||||
classesForElement: (element) ->
|
||||
|
||||
@@ -208,7 +208,7 @@ registerBuiltins = (devMode) ->
|
||||
cache.builtins[builtin] = path.join(commonRoot, "#{builtin}.js")
|
||||
|
||||
rendererRoot = path.join(atomShellRoot, 'renderer', 'api', 'lib')
|
||||
rendererBuiltins = ['ipc', 'remote']
|
||||
rendererBuiltins = ['ipc-renderer', 'remote']
|
||||
for builtin in rendererBuiltins
|
||||
cache.builtins[builtin] = path.join(rendererRoot, "#{builtin}.js")
|
||||
|
||||
|
||||
@@ -357,7 +357,9 @@ class PackageManager
|
||||
packagePaths = @getAvailablePackagePaths()
|
||||
packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath))
|
||||
packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath)
|
||||
@loadPackage(packagePath) for packagePath in packagePaths
|
||||
@config.transact =>
|
||||
@loadPackage(packagePath) for packagePath in packagePaths
|
||||
return
|
||||
@emitter.emit 'did-load-initial-packages'
|
||||
|
||||
loadPackage: (nameOrPath) ->
|
||||
@@ -467,6 +469,14 @@ class PackageManager
|
||||
return unless hook? and _.isString(hook) and hook.length > 0
|
||||
@activationHookEmitter.on(hook, callback)
|
||||
|
||||
serialize: ->
|
||||
for pack in @getActivePackages()
|
||||
@serializePackage(pack)
|
||||
@packageStates
|
||||
|
||||
serializePackage: (pack) ->
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
|
||||
# Deactivate all packages
|
||||
deactivatePackages: ->
|
||||
@config.transact =>
|
||||
@@ -478,8 +488,7 @@ class PackageManager
|
||||
# Deactivate the package with the given name
|
||||
deactivatePackage: (name) ->
|
||||
pack = @getLoadedPackage(name)
|
||||
if @isPackageActive(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
@serializePackage(pack) if @isPackageActive(pack.name)
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
delete @activatingPackages[pack.name]
|
||||
@@ -532,11 +541,12 @@ class PackageManager
|
||||
unless typeof metadata.name is 'string' and metadata.name.length > 0
|
||||
metadata.name = packageName
|
||||
|
||||
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
|
||||
metadata.repository.url = metadata.repository.url.replace(/(^git\+)|(\.git$)/g, '')
|
||||
|
||||
metadata
|
||||
|
||||
normalizePackageMetadata: (metadata) ->
|
||||
unless metadata?._id
|
||||
normalizePackageData ?= require 'normalize-package-data'
|
||||
normalizePackageData(metadata)
|
||||
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
|
||||
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')
|
||||
|
||||
@@ -84,7 +84,7 @@ class Package
|
||||
@loadKeymaps()
|
||||
@loadMenus()
|
||||
@loadStylesheets()
|
||||
@loadDeserializers()
|
||||
@registerDeserializerMethods()
|
||||
@configSchemaRegisteredOnLoad = @registerConfigSchemaFromMetadata()
|
||||
@settingsPromise = @loadSettings()
|
||||
if @shouldRequireMainModuleOnLoad() and not @mainModule?
|
||||
@@ -277,24 +277,24 @@ class Package
|
||||
@stylesheets = @getStylesheetPaths().map (stylesheetPath) =>
|
||||
[stylesheetPath, @themeManager.loadStylesheet(stylesheetPath, true)]
|
||||
|
||||
loadDeserializers: ->
|
||||
registerDeserializerMethods: ->
|
||||
if @metadata.deserializers?
|
||||
for name, implementationPath of @metadata.deserializers
|
||||
do =>
|
||||
deserializePath = path.join(@path, implementationPath)
|
||||
deserializeFunction = null
|
||||
atom.deserializers.add
|
||||
name: name,
|
||||
deserialize: =>
|
||||
@registerViewProviders()
|
||||
deserializeFunction ?= require(deserializePath)
|
||||
deserializeFunction.apply(this, arguments)
|
||||
Object.keys(@metadata.deserializers).forEach (deserializerName) =>
|
||||
methodName = @metadata.deserializers[deserializerName]
|
||||
atom.deserializers.add
|
||||
name: deserializerName,
|
||||
deserialize: (state, atomEnvironment) =>
|
||||
@registerViewProviders()
|
||||
@requireMainModule()
|
||||
@mainModule[methodName](state, atomEnvironment)
|
||||
return
|
||||
|
||||
registerViewProviders: ->
|
||||
if @metadata.viewProviders? and not @registeredViewProviders
|
||||
for implementationPath in @metadata.viewProviders
|
||||
@viewRegistry.addViewProvider(require(path.join(@path, implementationPath)))
|
||||
@requireMainModule()
|
||||
@metadata.viewProviders.forEach (methodName) =>
|
||||
@viewRegistry.addViewProvider (model) =>
|
||||
@mainModule[methodName](model)
|
||||
@registeredViewProviders = true
|
||||
|
||||
getStylesheetsPath: ->
|
||||
|
||||
149
src/pane.coffee
149
src/pane.coffee
@@ -1,5 +1,6 @@
|
||||
Grim = require 'grim'
|
||||
{find, compact, extend, last} = require 'underscore-plus'
|
||||
{Emitter} = require 'event-kit'
|
||||
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||
Model = require './model'
|
||||
PaneAxis = require './pane-axis'
|
||||
TextEditor = require './text-editor'
|
||||
@@ -8,6 +9,11 @@ TextEditor = require './text-editor'
|
||||
# Panes can contain multiple items, one of which is *active* at a given time.
|
||||
# The view corresponding to the active item is displayed in the interface. In
|
||||
# the default configuration, tabs are also displayed for each item.
|
||||
#
|
||||
# Each pane may also contain one *pending* item. When a pending item is added
|
||||
# to a pane, it will replace the currently pending item, if any, instead of
|
||||
# simply being added. In the default configuration, the text in the tab for
|
||||
# pending items is shown in italics.
|
||||
module.exports =
|
||||
class Pane extends Model
|
||||
container: undefined
|
||||
@@ -15,7 +21,7 @@ class Pane extends Model
|
||||
focused: false
|
||||
|
||||
@deserialize: (state, {deserializers, applicationDelegate, config, notifications}) ->
|
||||
{items, activeItemURI, activeItemUri} = state
|
||||
{items, itemStackIndices, activeItemURI, activeItemUri} = state
|
||||
activeItemURI ?= activeItemUri
|
||||
state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState))
|
||||
state.activeItem = find state.items, (item) ->
|
||||
@@ -37,20 +43,25 @@ class Pane extends Model
|
||||
} = params
|
||||
|
||||
@emitter = new Emitter
|
||||
@itemSubscriptions = new WeakMap
|
||||
@subscriptionsPerItem = new WeakMap
|
||||
@items = []
|
||||
@itemStack = []
|
||||
|
||||
@addItems(compact(params?.items ? []))
|
||||
@setActiveItem(@items[0]) unless @getActiveItem()?
|
||||
@addItemsToStack(params?.itemStackIndices ? [])
|
||||
@setFlexScale(params?.flexScale ? 1)
|
||||
|
||||
serialize: ->
|
||||
if typeof @activeItem?.getURI is 'function'
|
||||
activeItemURI = @activeItem.getURI()
|
||||
itemsToBeSerialized = compact(@items.map((item) -> item if typeof item.serialize is 'function'))
|
||||
itemStackIndices = (itemsToBeSerialized.indexOf(item) for item in @itemStack when typeof item.serialize is 'function')
|
||||
|
||||
deserializer: 'Pane'
|
||||
id: @id
|
||||
items: compact(@items.map((item) -> item.serialize?()))
|
||||
items: itemsToBeSerialized.map((item) -> item.serialize())
|
||||
itemStackIndices: itemStackIndices
|
||||
activeItemURI: activeItemURI
|
||||
focused: @focused
|
||||
flexScale: @flexScale
|
||||
@@ -260,8 +271,8 @@ class Pane extends Model
|
||||
getPanes: -> [this]
|
||||
|
||||
unsubscribeFromItem: (item) ->
|
||||
@itemSubscriptions.get(item)?.dispose()
|
||||
@itemSubscriptions.delete(item)
|
||||
@subscriptionsPerItem.get(item)?.dispose()
|
||||
@subscriptionsPerItem.delete(item)
|
||||
|
||||
###
|
||||
Section: Items
|
||||
@@ -278,12 +289,30 @@ class Pane extends Model
|
||||
# Returns a pane item.
|
||||
getActiveItem: -> @activeItem
|
||||
|
||||
setActiveItem: (activeItem) ->
|
||||
setActiveItem: (activeItem, options) ->
|
||||
{modifyStack} = options if options?
|
||||
unless activeItem is @activeItem
|
||||
@addItemToStack(activeItem) unless modifyStack is false
|
||||
@activeItem = activeItem
|
||||
@emitter.emit 'did-change-active-item', @activeItem
|
||||
@activeItem
|
||||
|
||||
# Build the itemStack after deserializing
|
||||
addItemsToStack: (itemStackIndices) ->
|
||||
if @items.length > 0
|
||||
if itemStackIndices.length is 0 or itemStackIndices.length isnt @items.length or itemStackIndices.indexOf(-1) >= 0
|
||||
itemStackIndices = (i for i in [0..@items.length-1])
|
||||
for itemIndex in itemStackIndices
|
||||
@addItemToStack(@items[itemIndex])
|
||||
return
|
||||
|
||||
# Add item (or move item) to the end of the itemStack
|
||||
addItemToStack: (newItem) ->
|
||||
return unless newItem?
|
||||
index = @itemStack.indexOf(newItem)
|
||||
@itemStack.splice(index, 1) unless index is -1
|
||||
@itemStack.push(newItem)
|
||||
|
||||
# Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise.
|
||||
getActiveEditor: ->
|
||||
@activeItem if @activeItem instanceof TextEditor
|
||||
@@ -296,6 +325,29 @@ class Pane extends Model
|
||||
itemAtIndex: (index) ->
|
||||
@items[index]
|
||||
|
||||
# Makes the next item in the itemStack active.
|
||||
activateNextRecentlyUsedItem: ->
|
||||
if @items.length > 1
|
||||
@itemStackIndex = @itemStack.length - 1 unless @itemStackIndex?
|
||||
@itemStackIndex = @itemStack.length if @itemStackIndex is 0
|
||||
@itemStackIndex = @itemStackIndex - 1
|
||||
nextRecentlyUsedItem = @itemStack[@itemStackIndex]
|
||||
@setActiveItem(nextRecentlyUsedItem, modifyStack: false)
|
||||
|
||||
# Makes the previous item in the itemStack active.
|
||||
activatePreviousRecentlyUsedItem: ->
|
||||
if @items.length > 1
|
||||
if @itemStackIndex + 1 is @itemStack.length or not @itemStackIndex?
|
||||
@itemStackIndex = -1
|
||||
@itemStackIndex = @itemStackIndex + 1
|
||||
previousRecentlyUsedItem = @itemStack[@itemStackIndex]
|
||||
@setActiveItem(previousRecentlyUsedItem, modifyStack: false)
|
||||
|
||||
# Moves the active item to the end of the itemStack once the ctrl key is lifted
|
||||
moveActiveItemToTopOfStack: ->
|
||||
delete @itemStackIndex
|
||||
@addItemToStack(@activeItem)
|
||||
|
||||
# Public: Makes the next item active.
|
||||
activateNextItem: ->
|
||||
index = @getActiveItemIndex()
|
||||
@@ -342,43 +394,81 @@ class Pane extends Model
|
||||
|
||||
# Public: Make the given item *active*, causing it to be displayed by
|
||||
# the pane's view.
|
||||
activateItem: (item) ->
|
||||
#
|
||||
# * `options` (optional) {Object}
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be added
|
||||
# in a pending state if it does not yet exist in the pane. Existing pending
|
||||
# items in a pane are replaced with new pending items when they are opened.
|
||||
activateItem: (item, options={}) ->
|
||||
if item?
|
||||
if @activeItem?.isPending?()
|
||||
if @getPendingItem() is @activeItem
|
||||
index = @getActiveItemIndex()
|
||||
else
|
||||
index = @getActiveItemIndex() + 1
|
||||
@addItem(item, index, false)
|
||||
@addItem(item, extend({}, options, {index: index}))
|
||||
@setActiveItem(item)
|
||||
|
||||
# Public: Add the given item to the pane.
|
||||
#
|
||||
# * `item` The item to add. It can be a model with an associated view or a
|
||||
# view.
|
||||
# * `index` (optional) {Number} indicating the index at which to add the item.
|
||||
# If omitted, the item is added after the current active item.
|
||||
# * `options` (optional) {Object}
|
||||
# * `index` (optional) {Number} indicating the index at which to add the item.
|
||||
# If omitted, the item is added after the current active item.
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be
|
||||
# added in a pending state. Existing pending items in a pane are replaced with
|
||||
# new pending items when they are opened.
|
||||
#
|
||||
# Returns the added item.
|
||||
addItem: (item, index=@getActiveItemIndex() + 1, moved=false) ->
|
||||
addItem: (item, options={}) ->
|
||||
# Backward compat with old API:
|
||||
# addItem(item, index=@getActiveItemIndex() + 1)
|
||||
if typeof options is "number"
|
||||
Grim.deprecate("Pane::addItem(item, #{options}) is deprecated in favor of Pane::addItem(item, {index: #{options}})")
|
||||
options = index: options
|
||||
|
||||
index = options.index ? @getActiveItemIndex() + 1
|
||||
moved = options.moved ? false
|
||||
pending = options.pending ? false
|
||||
|
||||
throw new Error("Pane items must be objects. Attempted to add item #{item}.") unless item? and typeof item is 'object'
|
||||
throw new Error("Adding a pane item with URI '#{item.getURI?()}' that has already been destroyed") if item.isDestroyed?()
|
||||
|
||||
return if item in @items
|
||||
|
||||
if item.isPending?()
|
||||
for existingItem, i in @items
|
||||
if existingItem.isPending?()
|
||||
@destroyItem(existingItem)
|
||||
break
|
||||
|
||||
if typeof item.onDidDestroy is 'function'
|
||||
@itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, false)
|
||||
itemSubscriptions = new CompositeDisposable
|
||||
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
|
||||
if typeof item.onDidTerminatePendingState is "function"
|
||||
itemSubscriptions.add item.onDidTerminatePendingState =>
|
||||
@clearPendingItem() if @getPendingItem() is item
|
||||
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
|
||||
@subscriptionsPerItem.set item, itemSubscriptions
|
||||
|
||||
@items.splice(index, 0, item)
|
||||
lastPendingItem = @getPendingItem()
|
||||
@setPendingItem(item) if pending
|
||||
|
||||
@emitter.emit 'did-add-item', {item, index, moved}
|
||||
@destroyItem(lastPendingItem) if lastPendingItem? and not moved
|
||||
@setActiveItem(item) unless @getActiveItem()?
|
||||
item
|
||||
|
||||
setPendingItem: (item) =>
|
||||
if @pendingItem isnt item
|
||||
mostRecentPendingItem = @pendingItem
|
||||
@pendingItem = item
|
||||
@emitter.emit 'item-did-terminate-pending-state', mostRecentPendingItem
|
||||
|
||||
getPendingItem: =>
|
||||
@pendingItem or null
|
||||
|
||||
clearPendingItem: =>
|
||||
@setPendingItem(null)
|
||||
|
||||
onItemDidTerminatePendingState: (callback) =>
|
||||
@emitter.on 'item-did-terminate-pending-state', callback
|
||||
|
||||
# Public: Add the given items to the pane.
|
||||
#
|
||||
# * `items` An {Array} of items to add. Items can be views or models with
|
||||
@@ -390,13 +480,14 @@ class Pane extends Model
|
||||
# Returns an {Array} of added items.
|
||||
addItems: (items, index=@getActiveItemIndex() + 1) ->
|
||||
items = items.filter (item) => not (item in @items)
|
||||
@addItem(item, index + i, false) for item, i in items
|
||||
@addItem(item, {index: index + i}) for item, i in items
|
||||
items
|
||||
|
||||
removeItem: (item, moved) ->
|
||||
index = @items.indexOf(item)
|
||||
return if index is -1
|
||||
|
||||
@pendingItem = null if @getPendingItem() is item
|
||||
@removeItemFromStack(item)
|
||||
@emitter.emit 'will-remove-item', {item, index, destroyed: not moved, moved}
|
||||
@unsubscribeFromItem(item)
|
||||
|
||||
@@ -412,6 +503,14 @@ class Pane extends Model
|
||||
@container?.didDestroyPaneItem({item, index, pane: this}) unless moved
|
||||
@destroy() if @items.length is 0 and @config.get('core.destroyEmptyPanes')
|
||||
|
||||
# Remove the given item from the itemStack.
|
||||
#
|
||||
# * `item` The item to remove.
|
||||
# * `index` {Number} indicating the index to which to remove the item from the itemStack.
|
||||
removeItemFromStack: (item) ->
|
||||
index = @itemStack.indexOf(item)
|
||||
@itemStack.splice(index, 1) unless index is -1
|
||||
|
||||
# Public: Move the given item to the given index.
|
||||
#
|
||||
# * `item` The item to move.
|
||||
@@ -430,7 +529,7 @@ class Pane extends Model
|
||||
# given pane.
|
||||
moveItemToPane: (item, pane, index) ->
|
||||
@removeItem(item, true)
|
||||
pane.addItem(item, index, true)
|
||||
pane.addItem(item, {index: index, moved: true})
|
||||
|
||||
# Public: Destroy the active item and activate the next item.
|
||||
destroyActiveItem: ->
|
||||
@@ -661,7 +760,7 @@ class Pane extends Model
|
||||
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this], @flexScale}))
|
||||
@setFlexScale(1)
|
||||
|
||||
newPane = new Pane(extend({@applicationDelegate, @deserializerManager, @config}, params))
|
||||
newPane = new Pane(extend({@applicationDelegate, @notificationManager, @deserializerManager, @config}, params))
|
||||
switch side
|
||||
when 'before' then @parent.insertChildBefore(this, newPane)
|
||||
when 'after' then @parent.insertChildAfter(this, newPane)
|
||||
@@ -713,7 +812,7 @@ class Pane extends Model
|
||||
if @parent.orientation is 'vertical'
|
||||
bottommostSibling = last(@parent.children)
|
||||
if bottommostSibling instanceof PaneAxis
|
||||
@splitRight()
|
||||
@splitDown()
|
||||
else
|
||||
bottommostSibling
|
||||
else
|
||||
|
||||
@@ -54,8 +54,9 @@ class Project extends Model
|
||||
Section: Serialization
|
||||
###
|
||||
|
||||
deserialize: (state, deserializerManager) ->
|
||||
deserialize: (state) ->
|
||||
state.paths = [state.path] if state.path? # backward compatibility
|
||||
state.paths = state.paths.filter (directoryPath) -> fs.isDirectorySync(directoryPath)
|
||||
|
||||
@buffers = _.compact state.buffers.map (bufferState) ->
|
||||
# Check that buffer's file path is accessible
|
||||
@@ -65,15 +66,15 @@ class Project extends Model
|
||||
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
|
||||
catch error
|
||||
return unless error.code is 'ENOENT'
|
||||
deserializerManager.deserialize(bufferState)
|
||||
TextBuffer.deserialize(bufferState)
|
||||
|
||||
@subscribeToBuffer(buffer) for buffer in @buffers
|
||||
@setPaths(state.paths)
|
||||
|
||||
serialize: ->
|
||||
serialize: (options={}) ->
|
||||
deserializer: 'Project'
|
||||
paths: @getPaths()
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize({markerLayers: options.isUnloading is true}) if buffer.isRetained())
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
commandRegistry.add 'atom-workspace',
|
||||
'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem()
|
||||
'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem()
|
||||
'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack()
|
||||
'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
|
||||
'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
|
||||
'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
|
||||
@@ -18,30 +21,30 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
'window:increase-font-size': -> @getModel().increaseFontSize()
|
||||
'window:decrease-font-size': -> @getModel().decreaseFontSize()
|
||||
'window:reset-font-size': -> @getModel().resetFontSize()
|
||||
'application:about': -> ipc.send('command', 'application:about')
|
||||
'application:show-preferences': -> ipc.send('command', 'application:show-settings')
|
||||
'application:show-settings': -> ipc.send('command', 'application:show-settings')
|
||||
'application:quit': -> ipc.send('command', 'application:quit')
|
||||
'application:hide': -> ipc.send('command', 'application:hide')
|
||||
'application:hide-other-applications': -> ipc.send('command', 'application:hide-other-applications')
|
||||
'application:install-update': -> ipc.send('command', 'application:install-update')
|
||||
'application:unhide-all-applications': -> ipc.send('command', 'application:unhide-all-applications')
|
||||
'application:new-window': -> ipc.send('command', 'application:new-window')
|
||||
'application:new-file': -> ipc.send('command', 'application:new-file')
|
||||
'application:open': -> ipc.send('command', 'application:open')
|
||||
'application:open-file': -> ipc.send('command', 'application:open-file')
|
||||
'application:open-folder': -> ipc.send('command', 'application:open-folder')
|
||||
'application:open-dev': -> ipc.send('command', 'application:open-dev')
|
||||
'application:open-safe': -> ipc.send('command', 'application:open-safe')
|
||||
'application:about': -> ipcRenderer.send('command', 'application:about')
|
||||
'application:show-preferences': -> ipcRenderer.send('command', 'application:show-settings')
|
||||
'application:show-settings': -> ipcRenderer.send('command', 'application:show-settings')
|
||||
'application:quit': -> ipcRenderer.send('command', 'application:quit')
|
||||
'application:hide': -> ipcRenderer.send('command', 'application:hide')
|
||||
'application:hide-other-applications': -> ipcRenderer.send('command', 'application:hide-other-applications')
|
||||
'application:install-update': -> ipcRenderer.send('command', 'application:install-update')
|
||||
'application:unhide-all-applications': -> ipcRenderer.send('command', 'application:unhide-all-applications')
|
||||
'application:new-window': -> ipcRenderer.send('command', 'application:new-window')
|
||||
'application:new-file': -> ipcRenderer.send('command', 'application:new-file')
|
||||
'application:open': -> ipcRenderer.send('command', 'application:open')
|
||||
'application:open-file': -> ipcRenderer.send('command', 'application:open-file')
|
||||
'application:open-folder': -> ipcRenderer.send('command', 'application:open-folder')
|
||||
'application:open-dev': -> ipcRenderer.send('command', 'application:open-dev')
|
||||
'application:open-safe': -> ipcRenderer.send('command', 'application:open-safe')
|
||||
'application:add-project-folder': -> atom.addProjectFolder()
|
||||
'application:minimize': -> ipc.send('command', 'application:minimize')
|
||||
'application:zoom': -> ipc.send('command', 'application:zoom')
|
||||
'application:bring-all-windows-to-front': -> ipc.send('command', 'application:bring-all-windows-to-front')
|
||||
'application:open-your-config': -> ipc.send('command', 'application:open-your-config')
|
||||
'application:open-your-init-script': -> ipc.send('command', 'application:open-your-init-script')
|
||||
'application:open-your-keymap': -> ipc.send('command', 'application:open-your-keymap')
|
||||
'application:open-your-snippets': -> ipc.send('command', 'application:open-your-snippets')
|
||||
'application:open-your-stylesheet': -> ipc.send('command', 'application:open-your-stylesheet')
|
||||
'application:minimize': -> ipcRenderer.send('command', 'application:minimize')
|
||||
'application:zoom': -> ipcRenderer.send('command', 'application:zoom')
|
||||
'application:bring-all-windows-to-front': -> ipcRenderer.send('command', 'application:bring-all-windows-to-front')
|
||||
'application:open-your-config': -> ipcRenderer.send('command', 'application:open-your-config')
|
||||
'application:open-your-init-script': -> ipcRenderer.send('command', 'application:open-your-init-script')
|
||||
'application:open-your-keymap': -> ipcRenderer.send('command', 'application:open-your-keymap')
|
||||
'application:open-your-snippets': -> ipcRenderer.send('command', 'application:open-your-snippets')
|
||||
'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet')
|
||||
'application:open-license': -> @getModel().openLicense()
|
||||
'window:run-package-specs': -> @runPackageSpecs()
|
||||
'window:focus-next-pane': -> @getModel().activateNextPane()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Using clipboard in renderer process is not safe on Linux.
|
||||
module.exports =
|
||||
if process.platform is 'linux' and process.type is 'renderer'
|
||||
require('remote').require('clipboard')
|
||||
require('electron').remote.clipboard
|
||||
else
|
||||
require('clipboard')
|
||||
require('electron').clipboard
|
||||
|
||||
@@ -378,7 +378,7 @@ class Selection extends Model
|
||||
indentAdjustment = @editor.indentLevelForLine(precedingText) - options.indentBasis
|
||||
@adjustIndent(remainingLines, indentAdjustment)
|
||||
|
||||
if options.autoIndent and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
|
||||
if options.autoIndent and NonWhitespaceRegExp.test(text) and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
|
||||
autoIndentFirstLine = true
|
||||
firstLine = precedingText + firstInsertedLine
|
||||
desiredIndentLevel = @editor.languageMode.suggestedIndentForLineAtBufferRow(oldBufferRange.start.row, firstLine)
|
||||
@@ -749,7 +749,7 @@ class Selection extends Model
|
||||
#
|
||||
# * `otherSelection` A {Selection} to compare against
|
||||
compare: (otherSelection) ->
|
||||
@getBufferRange().compare(otherSelection.getBufferRange())
|
||||
@marker.compare(otherSelection.marker)
|
||||
|
||||
###
|
||||
Section: Private Utilities
|
||||
@@ -804,11 +804,11 @@ class Selection extends Model
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
autoscroll: ->
|
||||
autoscroll: (options) ->
|
||||
if @marker.hasTail()
|
||||
@editor.scrollToScreenRange(@getScreenRange(), reversed: @isReversed())
|
||||
@editor.scrollToScreenRange(@getScreenRange(), Object.assign({reversed: @isReversed()}, options))
|
||||
else
|
||||
@cursor.autoscroll()
|
||||
@cursor.autoscroll(options)
|
||||
|
||||
clearAutoscroll: ->
|
||||
|
||||
|
||||
95
src/state-store.js
Normal file
95
src/state-store.js
Normal file
@@ -0,0 +1,95 @@
|
||||
'use strict'
|
||||
|
||||
module.exports =
|
||||
class StateStore {
|
||||
constructor (databaseName, version) {
|
||||
this.dbPromise = new Promise((resolve) => {
|
||||
let dbOpenRequest = indexedDB.open(databaseName, version)
|
||||
dbOpenRequest.onupgradeneeded = (event) => {
|
||||
let db = event.target.result
|
||||
db.createObjectStore('states')
|
||||
}
|
||||
dbOpenRequest.onsuccess = () => {
|
||||
resolve(dbOpenRequest.result)
|
||||
}
|
||||
dbOpenRequest.onerror = (error) => {
|
||||
console.error('Could not connect to indexedDB', error)
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
connect () {
|
||||
return this.dbPromise.then(db => !!db)
|
||||
}
|
||||
|
||||
save (key, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dbPromise.then(db => {
|
||||
if (db == null) resolve()
|
||||
|
||||
var request = db.transaction(['states'], 'readwrite')
|
||||
.objectStore('states')
|
||||
.put({value: value, storedAt: new Date().toString()}, key)
|
||||
|
||||
request.onsuccess = resolve
|
||||
request.onerror = reject
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
load (key) {
|
||||
return this.dbPromise.then(db => {
|
||||
if (!db) return
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = db.transaction(['states'])
|
||||
.objectStore('states')
|
||||
.get(key)
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
let result = event.target.result
|
||||
if (result && !result.isJSON) {
|
||||
resolve(result.value)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = (event) => reject(event)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
clear () {
|
||||
return this.dbPromise.then(db => {
|
||||
if (!db) return
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = db.transaction(['states'], 'readwrite')
|
||||
.objectStore('states')
|
||||
.clear()
|
||||
|
||||
request.onsuccess = resolve
|
||||
request.onerror = reject
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
count () {
|
||||
return this.dbPromise.then(db => {
|
||||
if (!db) return
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = db.transaction(['states'])
|
||||
.objectStore('states')
|
||||
.count()
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result)
|
||||
}
|
||||
request.onerror = reject
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ class StorageFolder
|
||||
constructor: (containingPath) ->
|
||||
@path = path.join(containingPath, "storage") if containingPath?
|
||||
|
||||
store: (name, object) ->
|
||||
storeSync: (name, object) ->
|
||||
return unless @path?
|
||||
|
||||
fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8')
|
||||
|
||||
@@ -2,7 +2,7 @@ _ = require 'underscore-plus'
|
||||
scrollbarStyle = require 'scrollbar-style'
|
||||
{Range, Point} = require 'text-buffer'
|
||||
{CompositeDisposable} = require 'event-kit'
|
||||
ipc = require 'ipc'
|
||||
{ipcRenderer} = require 'electron'
|
||||
|
||||
TextEditorPresenter = require './text-editor-presenter'
|
||||
GutterContainerComponent = require './gutter-container-component'
|
||||
@@ -43,7 +43,7 @@ class TextEditorComponent
|
||||
@assert domNode?, "TextEditorComponent::domNode was set to null."
|
||||
@domNodeValue = domNode
|
||||
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars}) ->
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars, scrollPastEnd}) ->
|
||||
@tileSize = tileSize if tileSize?
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@@ -61,6 +61,7 @@ class TextEditorComponent
|
||||
stoppedScrollingDelay: 200
|
||||
config: @config
|
||||
lineTopIndex: lineTopIndex
|
||||
scrollPastEnd: scrollPastEnd
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
@@ -279,10 +280,10 @@ class TextEditorComponent
|
||||
writeSelectedTextToSelectionClipboard = =>
|
||||
return if @editor.isDestroyed()
|
||||
if selectedText = @editor.getSelectedText()
|
||||
# This uses ipc.send instead of clipboard.writeText because
|
||||
# clipboard.writeText is a sync ipc call on Linux and that
|
||||
# This uses ipcRenderer.send instead of clipboard.writeText because
|
||||
# clipboard.writeText is a sync ipcRenderer call on Linux and that
|
||||
# will slow down selections.
|
||||
ipc.send('write-text-to-selection-clipboard', selectedText)
|
||||
ipcRenderer.send('write-text-to-selection-clipboard', selectedText)
|
||||
@disposables.add @editor.onDidChangeSelectionRange ->
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(writeSelectedTextToSelectionClipboard)
|
||||
|
||||
@@ -17,6 +17,8 @@ class TextEditorElement extends HTMLElement
|
||||
focusOnAttach: false
|
||||
hasTiledRendering: true
|
||||
logicalDisplayBuffer: true
|
||||
scrollPastEnd: true
|
||||
autoHeight: true
|
||||
|
||||
createdCallback: ->
|
||||
# Use globals when the following instance variables aren't set.
|
||||
@@ -38,6 +40,9 @@ class TextEditorElement extends HTMLElement
|
||||
@setAttribute('tabindex', -1)
|
||||
|
||||
initializeContent: (attributes) ->
|
||||
unless @autoHeight
|
||||
@style.height = "100%"
|
||||
|
||||
if @config.get('editor.useShadowDOM')
|
||||
@useShadowDOM = true
|
||||
|
||||
@@ -86,7 +91,7 @@ class TextEditorElement extends HTMLElement
|
||||
@subscriptions.add @component.onDidChangeScrollLeft =>
|
||||
@emitter.emit("did-change-scroll-left", arguments...)
|
||||
|
||||
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}) ->
|
||||
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}, @autoHeight = true, @scrollPastEnd = true) ->
|
||||
throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @views?
|
||||
throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @config?
|
||||
throw new Error("Must pass a themes parameter when initializing TextEditorElements") unless @themes?
|
||||
@@ -143,6 +148,7 @@ class TextEditorElement extends HTMLElement
|
||||
workspace: @workspace
|
||||
assert: @assert
|
||||
grammars: @grammars
|
||||
scrollPastEnd: @scrollPastEnd
|
||||
)
|
||||
@rootElement.appendChild(@component.getDomNode())
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user