Merge remote-tracking branch 'refs/remotes/origin/master' into wl-update-language-javascript

This commit is contained in:
Wliu
2016-04-18 16:35:12 -04:00
57 changed files with 1063 additions and 349 deletions

View File

@@ -50,7 +50,7 @@ To get a sense for the packages that are bundled with Atom, you can go to Settin
Here's a list of the big ones:
* [atom/atom](https://github.com/atom/atom) - Atom Core! The core editor component is responsible for basic text editing (e.g. cursors, selections, scrolling), text indentation, wrapping, and folding, text rendering, editor rendering, file system operations (e.g. saving), and installation and auto-updating. You should also use this repository for feedback related to the [core API](https://atom.io/docs/api/latest/Notification) and for large, overarching design proposals.
* [atom/atom](https://github.com/atom/atom) - Atom Core! The core editor component is responsible for basic text editing (e.g. cursors, selections, scrolling), text indentation, wrapping, and folding, text rendering, editor rendering, file system operations (e.g. saving), and installation and auto-updating. You should also use this repository for feedback related to the [core API](https://atom.io/docs/api/latest) and for large, overarching design proposals.
* [tree-view](https://github.com/atom/tree-view) - file and directory listing on the left of the UI.
* [fuzzy-finder](https://github.com/atom/fuzzy-finder) - the quick file opener.
* [find-and-replace](https://github.com/atom/find-and-replace) - all search and replace functionality.
@@ -82,7 +82,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r
#### Before Submitting A Bug Report
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings).
* **Check the [FAQs on the forum](https://discuss.atom.io/c/faq)** for a list of common questions and problems.
* **Determine [which repository the problem should be reported in](#atom-and-packages)**.
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one.
@@ -100,13 +100,13 @@ Explain the problem and include additional details to help maintainers reproduce
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On OSX, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](https://atom.io/docs/latest/hacking-atom-debugging#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
* **If the Chrome's developer tools pane is shown without you triggering it**, that normally means that an exception was thrown. The Console tab will include an entry for the exception. Expand the exception so that the stack trace is visible, and provide the full exception and stack trace in a [code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines) and as a screenshot.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
Provide more context by answering these questions:
* **Can you reproduce the problem in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode)?**
* **Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance-problems-with-the-dev-tools-cpu-profiler)?**
* **Did the problem start happening recently** (e.g. after updating to a new version of Atom) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Atom?** What's the most recent version in which the problem doesn't happen? You can download older versions of Atom from [the releases page](https://github.com/atom/atom/releases).
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
@@ -118,7 +118,7 @@ Include details about your configuration and environment:
* **What's the name and version of the OS you're using**?
* **Are you running Atom in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest?
* **Which [packages](#atom-and-packages) do you have installed?** You can get that list by running `apm list --installed`.
* **Are you using [local configuration files](https://atom.io/docs/latest/using-atom-basic-customization)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
* **Are you using [local configuration files](http://flight-manual.atom.io/using-atom/sections/basic-customization/)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
* **Are you using Atom with multiple monitors?** If so, can you reproduce the problem when you use a single monitor?
* **Which keyboard layout are you using?** Are you using a US layout or some other layout?
@@ -166,7 +166,7 @@ Before creating enhancement suggestions, please check [this list](#before-submit
#### Before Submitting An Enhancement Suggestion
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
* **Check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using [the latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version) and if you can get the desired behavior by changing [Atom's or packages' config settings](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-atom-and-package-settings).
* **Check if there's already [a package](https://atom.io/packages) which provides that enhancement.**
* **Determine [which repository the enhancement should be suggested in](#atom-and-packages).**
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
@@ -376,7 +376,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
| `windows` | [search][search-atom-repo-label-windows] | [search][search-atom-org-label-windows] | Related to Atom running on Windows. |
| `linux` | [search][search-atom-repo-label-linux] | [search][search-atom-org-label-linux] | Related to Atom running on Linux. |
| `mac` | [search][search-atom-repo-label-mac] | [search][search-atom-org-label-mac] | Related to Atom running on OSX. |
| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/Atom) and the [flight manual](https://atom.io/docs/latest/)). |
| `documentation` | [search][search-atom-repo-label-documentation] | [search][search-atom-org-label-documentation] | Related to any type of documentation (e.g. [API documentation](https://atom.io/docs/api/latest/) and the [flight manual](http://flight-manual.atom.io/)). |
| `performance` | [search][search-atom-repo-label-performance] | [search][search-atom-org-label-performance] | Related to performance. |
| `security` | [search][search-atom-repo-label-security] | [search][search-atom-org-label-security] | Related to security. |
| `ui` | [search][search-atom-repo-label-ui] | [search][search-atom-org-label-ui] | Related to visual design. |

View File

@@ -2,7 +2,7 @@
* [ ] Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode)?
* [ ] Are you running the [latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version)?
* [ ] Did you check the [debugging guide](flight-manual.atom.io/hacking-atom/sections/debugging/)?
* [ ] Did you check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)?
* [ ] Did you check the [FAQs on Discuss](https://discuss.atom.io/c/faq)?
* [ ] Are you reporting to the [correct repository](https://github.com/atom/atom/blob/master/CONTRIBUTING.md#atom-and-packages)?
* [ ] Did you [perform a cursory search](https://github.com/issues?q=is%3Aissue+user%3Aatom+-repo%3Aatom%2Felectron) to see if your bug or enhancement is already reported?
@@ -25,4 +25,4 @@ For more information on how to write a good [bug report](https://github.com/atom
### Versions
You can get this information from executing `atom --version` and `apm --version` at the command line.
You can get this information from executing `atom --version` and `apm --version` at the command line. Also, please include the OS and what version of the OS you're running.

View File

@@ -76,6 +76,22 @@ Currently only a 64-bit version is available.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
### Archive extraction
An archive is available for people who don't want to install `atom` as root.
This version enables you to install multiple Atom versions in parallel. It has been built on Ubuntu 64-bit,
but should be compatible with other Linux distributions.
1. Install dependencies (on Ubuntu): `sudo apt install git gconf2 gconf-service libgtk2.0-0 libudev1 libgcrypt20
libnotify4 libxtst6 libnss3 python gvfs-bin xdg-utils libcap2`
2. Download `atom-amd64.tar.gz` from the [Atom releases page](https://github.com/atom/atom/releases/latest).
3. Run `tar xf atom-amd64.tar.gz` in the directory where you want to extract the Atom folder.
4. Launch Atom using the installed `atom` command from the newly extracted directory.
The Linux version does not currently automatically update so you will need to
repeat these steps to upgrade to future releases.
## Building
* [Linux](docs/build-instructions/linux.md)

View File

@@ -285,6 +285,7 @@ module.exports = (grunt) ->
ciTasks.push('dump-symbols') if process.platform is 'darwin'
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
ciTasks.push('mkdeb') if process.platform is 'linux'
ciTasks.push('mktar') if process.platform is 'linux'
ciTasks.push('codesign:exe') if process.platform is 'win32' and not process.env.CI
ciTasks.push('create-windows-installer:installer') if process.platform is 'win32'
ciTasks.push('test') if process.platform is 'darwin'

Binary file not shown.

View File

@@ -1,24 +1,46 @@
path = require 'path'
fs = require 'fs'
request = require 'request'
module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
signUsingWindowsSDK = (exeToSign, callback) ->
{WIN_P12KEY_PASSWORD, WIN_P12KEY_URL} = process.env
if WIN_P12KEY_URL?
grunt.log.ok("Obtaining signing key")
downloadedKeyFile = path.resolve(__dirname, 'DownloadedSignKey.p12')
downloadFile WIN_P12KEY_URL, downloadedKeyFile, (done) ->
signUsingWindowsSDKTool exeToSign, downloadedKeyFile, WIN_P12KEY_PASSWORD, (done) ->
fs.unlinkSync(downloadedKeyFile)
callback()
else
signUsingWindowsSDKTool exeToSign, path.resolve(__dirname, '..', 'certs', 'AtomDevTestSignKey.p12'), 'password', callback
signUsingWindowsSDKTool = (exeToSign, keyFilePath, password, callback) ->
grunt.log.ok("Signing #{exeToSign}")
args = ['sign', '/v', '/p', password, '/f', keyFilePath, exeToSign]
spawn {cmd: 'C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.1A\\bin\\signtool.exe', args: args}, callback
signUsingJanky = (exeToSign, callback) ->
spawn {cmd: process.env.JANKY_SIGNTOOL, args: [exeToSign]}, callback
signWindowsExecutable = if process.env.JANKY_SIGNTOOL then signUsingJanky else signUsingWindowsSDK
grunt.registerTask 'codesign:exe', 'CodeSign Atom.exe and Update.exe', ->
done = @async()
spawn {cmd: 'taskkill', args: ['/F', '/IM', 'atom.exe']}, ->
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
atomExePath = path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')
spawn {cmd, args: [atomExePath]}, (error) ->
signWindowsExecutable atomExePath, (error) ->
return done(error) if error?
updateExePath = path.resolve(__dirname, '..', 'node_modules', 'grunt-electron-installer', 'vendor', 'Update.exe')
spawn {cmd, args: [updateExePath]}, (error) -> done(error)
signWindowsExecutable updateExePath, (error) -> done(error)
grunt.registerTask 'codesign:installer', 'CodeSign AtomSetup.exe', ->
done = @async()
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
atomSetupExePath = path.resolve(grunt.config.get('atom.buildDir'), 'installer', 'AtomSetup.exe')
spawn {cmd, args: [atomSetupExePath]}, (error) -> done(error)
signWindowsExecutable atomSetupExePath, (error) -> done(error)
grunt.registerTask 'codesign:app', 'CodeSign Atom.app', ->
done = @async()
@@ -26,14 +48,24 @@ module.exports = (grunt) ->
unlockKeychain (error) ->
return done(error) if error?
cmd = 'codesign'
args = ['--deep', '--force', '--verbose', '--sign', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
spawn {cmd, args}, (error) -> done(error)
spawn {cmd: 'codesign', args: args}, (error) -> done(error)
unlockKeychain = (callback) ->
return callback() unless process.env.XCODE_KEYCHAIN
cmd = 'security'
{XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env
args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN]
spawn {cmd, args}, (error) -> callback(error)
spawn {cmd: 'security', args: args}, (error) -> callback(error)
downloadFile = (sourceUrl, targetPath, callback) ->
options = {
url: sourceUrl
headers: {
'User-Agent': 'Atom Signing Key build task',
'Accept': 'application/vnd.github.VERSION.raw'
}
}
request(options)
.pipe(fs.createWriteStream(targetPath))
.on('finish', callback)

View File

@@ -123,10 +123,10 @@ module.exports =
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
'tweetnacl@0.13.2':
'tweetnacl@0.14.3':
repository: 'https://github.com/dchest/tweetnacl-js'
license: 'Public Domain'
source: 'https://github.com/dchest/tweetnacl-js/blob/2f328394f74d83564634fb89ea2798caa3a4edb9/README.md says public domain.'
source: 'https://github.com/dchest/tweetnacl-js/blob/1042c9c65dc8f1dcb9e981d962d7dbbcf58f1fdc/COPYING.txt says public domain.'
'json-schema@0.2.2':
repository: 'https://github.com/kriszyp/json-schema'
license: 'BSD'

View File

@@ -0,0 +1,30 @@
path = require 'path'
module.exports = (grunt) ->
{spawn, fillTemplate} = require('./task-helpers')(grunt)
grunt.registerTask 'mktar', 'Create an archive', ->
done = @async()
appFileName = grunt.config.get('atom.appFileName')
buildDir = grunt.config.get('atom.buildDir')
shellAppDir = grunt.config.get('atom.shellAppDir')
{version, description} = grunt.config.get('atom.metadata')
if process.arch is 'ia32'
arch = 'i386'
else if process.arch is 'x64'
arch = 'amd64'
else
return done("Unsupported arch #{process.arch}")
iconPath = path.join(shellAppDir, 'resources', 'app.asar.unpacked', 'resources', 'atom.png')
cmd = path.join('script', 'mktar')
args = [appFileName, version, arch, iconPath, buildDir]
spawn {cmd, args}, (error) ->
if error?
done(error)
else
grunt.log.ok "Created " + path.join(buildDir, "#{appFileName}-#{version}-#{arch}.tar.gz")
done()

View File

@@ -85,13 +85,13 @@ getAssets = ->
arch = 'amd64'
# Check for a Debian build
sourcePath = "#{buildDir}/#{appFileName}-#{version}-#{arch}.deb"
sourcePath = path.join(buildDir, "#{appFileName}-#{version}-#{arch}.deb")
assetName = "atom-#{arch}.deb"
# Check for a Fedora build
unless fs.isFileSync(sourcePath)
rpmName = fs.readdirSync("#{buildDir}/rpm")[0]
sourcePath = "#{buildDir}/rpm/#{rpmName}"
sourcePath = path.join(buildDir, "rpm", rpmName)
if process.arch is 'ia32'
arch = 'i386'
else
@@ -99,10 +99,17 @@ getAssets = ->
assetName = "atom.#{arch}.rpm"
cp sourcePath, path.join(buildDir, assetName)
assets = [{assetName, sourcePath}]
[
{assetName, sourcePath}
]
# Check for an archive build on a debian build machine.
# We could provide a Fedora version if some libraries are not compatible
sourcePath = path.join(buildDir, "#{appFileName}-#{version}-#{arch}.tar.gz")
if fs.isFileSync(sourcePath)
assetName = "atom-#{arch}.tar.gz"
cp sourcePath, path.join(buildDir, assetName)
assets.push({assetName, sourcePath})
assets
logError = (message, error, details) ->
grunt.log.error(message)

View File

@@ -17,20 +17,6 @@ module.exports = (grunt) ->
packageSpecQueue = null
logDeprecations = (label, {stderr}={}) ->
return unless process.env.JANKY_SHA1 or process.env.CI
stderr ?= ''
deprecatedStart = stderr.indexOf('Calls to deprecated functions')
return if deprecatedStart is -1
grunt.log.error(label)
stderr = stderr.substring(deprecatedStart)
stderr = stderr.replace(/^\s*\[[^\]]+\]\s+/gm, '')
stderr = stderr.replace(/source: .*$/gm, '')
stderr = stderr.replace(/^"/gm, '')
stderr = stderr.replace(/",\s*$/gm, '')
grunt.log.error(stderr)
getAppPath = ->
contentsDir = grunt.config.get('atom.contentsDir')
switch process.platform
@@ -57,14 +43,14 @@ module.exports = (grunt) ->
args: ['--test', "--resource-path=#{resourcePath}", path.join(packagePath, 'spec')]
opts:
cwd: packagePath
env: _.extend({}, process.env, ATOM_PATH: rootDir)
env: _.extend({}, process.env, ELECTRON_ENABLE_LOGGING: true, ATOM_PATH: rootDir)
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--log-file=ci.log", path.join(packagePath, 'spec')]
opts:
cwd: packagePath
env: _.extend({}, process.env, ATOM_PATH: rootDir)
env: _.extend({}, process.env, ELECTRON_ENABLE_LOGGING: true, ATOM_PATH: rootDir)
grunt.log.ok "Launching #{path.basename(packagePath)} specs."
spawn options, (error, results, code) ->
@@ -74,7 +60,6 @@ module.exports = (grunt) ->
fs.unlinkSync(path.join(packagePath, 'ci.log'))
failedPackages.push path.basename(packagePath) if error
logDeprecations("#{path.basename(packagePath)} Specs", results)
callback()
modulesDirectory = path.resolve('node_modules')
@@ -87,7 +72,7 @@ module.exports = (grunt) ->
packageSpecQueue.concurrency = Math.max(1, concurrency - 1)
packageSpecQueue.drain = -> callback(null, failedPackages)
runCoreSpecs = (callback, logOutput = false) ->
runCoreSpecs = (callback) ->
appPath = getAppPath()
resourcePath = process.cwd()
coreSpecsPath = path.resolve('spec')
@@ -97,21 +82,16 @@ module.exports = (grunt) ->
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath, "--user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
)
env: _.extend({}, process.env, {ELECTRON_ENABLE_LOGGING: true, ATOM_INTEGRATION_TESTS_ENABLED: true})
stdio: 'inherit'
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", '--log-file=ci.log', coreSpecsPath]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
)
if logOutput
options.opts.stdio = 'inherit'
env: _.extend({}, process.env, {ELECTRON_ENABLE_LOGGING: true, ATOM_INTEGRATION_TESTS_ENABLED: true})
stdio: 'inherit'
grunt.log.ok "Launching core specs."
spawn options, (error, results, code) ->
@@ -121,7 +101,6 @@ module.exports = (grunt) ->
else
# TODO: Restore concurrency on Windows
packageSpecQueue?.concurrency = concurrency
logDeprecations('Core Specs', results)
callback(null, error)
@@ -134,17 +113,11 @@ module.exports = (grunt) ->
else
async.parallel
# If we're just running the core specs then we won't have any output to
# indicate the tests actually *are* running. This upsets Travis:
# https://github.com/atom/atom/issues/10837. So pass the test output
# through.
runCoreSpecsWithLogging = (callback) -> runCoreSpecs(callback, true)
specs =
if process.env.ATOM_SPECS_TASK is 'packages'
[runPackageSpecs]
else if process.env.ATOM_SPECS_TASK is 'core'
[runCoreSpecsWithLogging]
[runCoreSpecs]
else
[runCoreSpecs, runPackageSpecs]

View File

@@ -6,8 +6,8 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++ toolchain
* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* [Git](https://git-scm.com/)
* [Node.js](https://nodejs.org/en/download/) (0.10.x or above)
* [npm](https://www.npmjs.com/) v1.4.x or above (automatically bundled with Node.js)
* `npm -v` to check the version.
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
@@ -74,18 +74,24 @@ If you have problems with permissions don't forget to prefix with `sudo`
To use the newly installed Atom, quit and restart all running Atom instances.
5. *Optionally*, you may generate distributable packages of Atom at `out`. Currently, `.deb` and `.rpm` package types are supported. To create a `.deb` package run:
5. *Optionally*, you may generate distributable packages of Atom at `out`. Currently, `.deb` and `.rpm` package types are supported, as well as a `.tar.gz` archive. To create a `.deb` package run:
```sh
script/grunt mkdeb
```
To create an `.rpm` package run
To create a `.rpm` package run
```sh
script/grunt mkrpm
```
To create a `.tar.gz` archive run
```sh
script/grunt mktar
```
## Advanced Options
### Custom build directory

View File

@@ -3,7 +3,7 @@
## Requirements
* OS X 10.8 or later
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* [Node.js](https://nodejs.org/en/download/) (0.10.x or above)
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
## Instructions

View File

@@ -3,7 +3,7 @@
## Requirements
### General
* [Node.js](http://nodejs.org/en/download/) v4.x
* [Node.js](https://nodejs.org/en/download/) v4.x
* [Python](https://www.python.org/downloads/) v2.7.x
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`.
If it is installed elsewhere, you can create a symbolic link to the
@@ -14,29 +14,27 @@
You can use either:
* [Visual Studio 2013 Update 5](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express or better) on Windows 7, 8 or 10
* [Visual Studio 2015](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community or better) with Windows 8 or 10
* [Visual Studio 2013 Update 5](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Express or better) on Windows 7, 8 or 10
* [Visual Studio 2015](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs) (Community or better) with Windows 8 or 10
Whichever version you use, ensure that:
* The default installation folder is chosen so the build tools can find it
* Visual C++ support is installed
* You set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (`2013` or `2015`), e.g. , e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell or set it in Windows advanced system settings control panel.
* The git command is in your path
* A `git` command is in your path
* If you have both VS2013 and VS2015 installed set the `GYP_MSVS_VERSION` environment variable to the Visual Studio version (`2013` or `2015`) you wish to use, e.g. ``[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")`` in PowerShell or set it in Windows advanced system settings control panel.
## Instructions
You can run these commands using Command Prompt, PowerShell or Git Shell via [GitHub Desktop](https://desktop.github.com/). These instructions will assume the use of Bash from Git Shell - if you are using Command Prompt use a backslash instead: i.e. `script\build`.
**VS2015 + Git Shell users** should note that the default path supplied with Git Shell includes reference to an older version of msbuild that will fail. It is recommended you use a PowerShell window that has git in the path at this time.
```bash
cd C:\
git clone https://github.com/atom/atom/
cd atom
script/build
```
This will create the Atom application in the `Program Files` folder.
This will create the Atom application in the `out\Atom` folder as well as copy it to a folder named `Atom` within `Program Files`.
### `script/build` Options
* `--install-dir` - Creates the final built application in this directory. Example (trailing slash is optional):
@@ -47,6 +45,7 @@ This will create the Atom application in the `Program Files` folder.
```bash
./script/build --build-dir Z:\Some\Temporary\Directory\
```
* `--no-install` - Skips the installation task after building.
* `--verbose` - Verbose mode. A lot more information output.
## Do I have to use GitHub Desktop?
@@ -63,37 +62,34 @@ If none of this works, do install Github Desktop and use its Git Shell as it mak
### Common Errors
* `node is not recognized`
* If you just installed Node.js, you'll need to restart your PowerShell/Command Prompt/Git Shell before the node
command is available on your Path.
* `script/build` outputs only the Node.js and Python versions before returning
* `msbuild.exe failed with exit code: 1`
* Ensure you have Visual C++ support installed. Go into Add/Remove Programs, select Visual Studio and press Modify and then check the Visual C++ box.
* `script/build` stops with no error or warning shortly after displaying the versions of node, npm and Python
* Make sure that the path where you have checked out Atom does not include a space. e.g. use `c:\atom` and not `c:\my stuff\atom`
* `script/build` outputs only the Node.js and Python versions before returning
* Try moving the repository to `C:\atom`. Most likely, the path is too long.
See [issue #2200](https://github.com/atom/atom/issues/2200).
* `error MSB4025: The project file could not be loaded. Invalid character in the given encoding.`
* This can occur because your home directory (`%USERPROFILE%`) has non-ASCII
characters in it. This is a bug in [gyp](https://code.google.com/p/gyp/)
which is used to build native Node.js modules and there is no known workaround.
* https://github.com/TooTallNate/node-gyp/issues/297
* https://code.google.com/p/gyp/issues/detail?id=393
* `script/build` stops at installing runas with `Failed at the runas@x.y.z install script.`
* `'node_modules\.bin\npm' is not recognized as an internal or external command, operable program or batch file.`
* This occurs if the previous build left things in a bad state. Run `script\clean` and then `script\build` again.
* `script/build` stops at installing runas with `Failed at the runas@x.y.z install script.`
* See the next item.
* `error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.`
* If you're building Atom with Visual Studio 2013 or above make sure the `GYP_MSVS_VERSION` environment variable is set, and then re-run `script/build` after a clean:
```bash
$env:GYP_MSVS_VERSION='2013' # '2015' if using Visual Studio 2015, and so on
script/clean
script/build
```
* If you are using Visual Studio 2013 or above and the build fails with some other error message this environment variable might still be required and ensure you have Visual C++ language support installed.
* If you're building Atom with Visual Studio 2013 try setting the `GYP_MSVS_VERSION` environment variable to 2013 and then `script/clean` followed by `script/build` (re-open your command prompt or Powershell window if you set it using the GUI)
* Other `node-gyp` errors on first build attempt, even though the right Node.js and Python versions are installed.
* Do try the build command one more time, as experience shows it often works on second try in many of these cases.

18
docs/native-profiling.md Normal file
View File

@@ -0,0 +1,18 @@
# Profiling the Atom Render Process on OS X with Instruments
![Instruments](https://cloud.githubusercontent.com/assets/1789/14193295/d503db7a-f760-11e5-88bf-fe417c0cd913.png)
* Determine the version of Electron for your version of Atom.
* Open the dev tools with `alt-cmd-i`
* Evaluate `process.versions.electron` in the console.
* Based on this version, download the appropriate Electron symbols from the [releases](https://github.com/atom/electron/releases) page.
* The file name should look like `electron-v0.X.Y-darwin-x64-dsym.zip`.
* Decompress these symbols in your `~/Downloads` directory.
* Now create a time profile in Instruments.
* Open `Instruments.app`.
* Select `Time Profiler`
* In Atom, determine the pid to attach to by evaluating `process.pid` in the dev tools console.
* Attach to this pid via the menu at the upper left corner of the Instruments profiler.
* Click record, do your thing.
* Click stop.
* The symbols should have been automatically located by Instruments (via Spotlight or something?), giving you a readable profile.

View File

@@ -18,15 +18,15 @@
# 'ctrl-p': 'core:move-down'
#
# You can find more information about keymaps in these guides:
# * https://atom.io/docs/latest/using-atom-basic-customization#customizing-key-bindings
# * https://atom.io/docs/latest/behind-atom-keymaps-in-depth
# * http://flight-manual.atom.io/using-atom/sections/basic-customization/#_customizing_keybindings
# * http://flight-manual.atom.io/behind-atom/sections/keymaps-in-depth/
#
# If you're having trouble with your keybindings not working, try the
# Keybinding Resolver: `Cmd+.` on OS X and `Ctrl+.` on other platforms. See the
# Debugging Guide for more information:
# * https://atom.io/docs/latest/hacking-atom-debugging#check-the-keybindings
# * http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-the-keybindings
#
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it in the
# Atom Flight Manual:
# https://atom.io/docs/latest/using-atom-basic-customization#cson
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson

View File

@@ -130,6 +130,8 @@
# Atom Specific
'ctrl-W': 'editor:select-word'
'cmd-ctrl-left': 'editor:move-selection-left'
'cmd-ctrl-right': 'editor:move-selection-right'
# Sublime Parity
'cmd-a': 'core:select-all'

View File

@@ -14,6 +14,8 @@
'ctrl-shift-pageup': 'pane:move-item-left'
'ctrl-shift-pagedown': 'pane:move-item-right'
'f11': 'window:toggle-full-screen'
'alt-shift-left': 'editor:move-selection-left'
'alt-shift-right': 'editor:move-selection-right'
# Sublime Parity
'ctrl-,': 'application:show-settings'

View File

@@ -20,6 +20,8 @@
'ctrl-shift-left': 'pane:move-item-left'
'ctrl-shift-right': 'pane:move-item-right'
'f11': 'window:toggle-full-screen'
'alt-shift-left': 'editor:move-selection-left'
'alt-shift-right': 'editor:move-selection-right'
# Sublime Parity
'ctrl-,': 'application:show-settings'

View File

@@ -75,6 +75,13 @@
{ label: 'Join Lines', command: 'editor:join-lines' }
]
}
{
label: 'Columns',
submenu: [
{ label: 'Move Selection Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection Right', command: 'editor:move-selection-right' }
]
}
{
label: 'Text',
submenu: [
@@ -221,10 +228,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -48,6 +48,13 @@
{ label: '&Join Lines', command: 'editor:join-lines' }
]
}
{
label: 'Columns',
submenu: [
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
]
}
{
label: 'Text',
submenu: [
@@ -197,10 +204,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -56,6 +56,13 @@
{ label: '&Join Lines', command: 'editor:join-lines' }
]
}
{
label: 'Columns',
submenu: [
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
]
}
{
label: 'Text',
submenu: [
@@ -200,10 +207,10 @@
{label: 'Delete', command: 'core:delete'}
{label: 'Select All', command: 'core:select-all'}
{type: 'separator'}
{label: 'Split Up', command: 'pane:split-up'}
{label: 'Split Down', command: 'pane:split-down'}
{label: 'Split Left', command: 'pane:split-left'}
{label: 'Split Right', command: 'pane:split-right'}
{label: 'Split Up', command: 'pane:split-up-and-copy-active-item'}
{label: 'Split Down', command: 'pane:split-down-and-copy-active-item'}
{label: 'Split Left', command: 'pane:split-left-and-copy-active-item'}
{label: 'Split Right', command: 'pane:split-right-and-copy-active-item'}
{label: 'Close Pane', command: 'pane:close'}
{type: 'separator'}
]

View File

@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "1.8.0-dev",
"version": "1.9.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@@ -15,7 +15,7 @@
"electronVersion": "0.36.8",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^6.3.1",
"atom-keymap": "6.3.2",
"babel-core": "^5.8.21",
"bootstrap": "^3.3.4",
"cached-run-in-this-context": "0.4.1",
@@ -37,7 +37,7 @@
"less-cache": "0.23",
"line-top-index": "0.2.0",
"marked": "^0.3.4",
"nodegit": "0.12.0",
"nodegit": "0.12.2",
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"oniguruma": "^5",
@@ -54,7 +54,7 @@
"service-hub": "^0.7.0",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "8.4.3",
"text-buffer": "8.4.6",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"yargs": "^3.23.0"
@@ -66,32 +66,32 @@
"atom-light-ui": "0.43.0",
"base16-tomorrow-dark-theme": "1.1.0",
"base16-tomorrow-light-theme": "1.1.1",
"one-dark-ui": "1.3.0",
"one-light-ui": "1.3.0",
"one-dark-ui": "1.3.1",
"one-light-ui": "1.3.1",
"one-dark-syntax": "1.2.0",
"one-light-syntax": "1.2.0",
"solarized-dark-syntax": "1.0.0",
"solarized-light-syntax": "1.0.0",
"about": "1.4.2",
"solarized-dark-syntax": "1.0.2",
"solarized-light-syntax": "1.0.2",
"about": "1.5.0",
"archive-view": "0.61.1",
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.11.0",
"autocomplete-css": "0.11.1",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.29.1",
"autocomplete-plus": "2.29.2",
"autocomplete-snippets": "1.10.0",
"autoflow": "0.27.0",
"autosave": "0.23.1",
"background-tips": "0.26.0",
"bookmarks": "0.38.2",
"bracket-matcher": "0.81.0",
"bookmarks": "0.39.0",
"bracket-matcher": "0.82.0",
"command-palette": "0.38.0",
"deprecation-cop": "0.54.1",
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.37.0",
"find-and-replace": "0.197.4",
"fuzzy-finder": "1.0.3",
"exception-reporting": "0.38.1",
"fuzzy-finder": "1.0.4",
"git-diff": "1.0.1",
"find-and-replace": "0.198.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.48.1",
"image-view": "0.57.0",
@@ -101,28 +101,28 @@
"link": "0.31.1",
"markdown-preview": "0.158.0",
"metrics": "0.53.1",
"notifications": "0.63.1",
"open-on-github": "1.0.1",
"notifications": "0.63.2",
"open-on-github": "1.1.0",
"package-generator": "1.0.0",
"settings-view": "0.235.1",
"snippets": "1.0.2",
"spell-check": "0.67.0",
"status-bar": "1.2.0",
"spell-check": "0.67.1",
"status-bar": "1.2.3",
"styleguide": "0.45.2",
"symbols-view": "0.112.0",
"tabs": "0.92.0",
"tabs": "0.92.2",
"timecop": "0.33.1",
"tree-view": "0.203.3",
"tree-view": "0.206.0",
"update-package-dependencies": "0.10.0",
"welcome": "0.34.0",
"whitespace": "0.32.2",
"wrap-guide": "0.38.1",
"language-c": "0.51.2",
"language-c": "0.51.3",
"language-clojure": "0.20.0",
"language-coffee-script": "0.46.1",
"language-csharp": "0.12.0",
"language-css": "0.36.0",
"language-gfm": "0.85.0",
"language-csharp": "0.12.1",
"language-css": "0.36.1",
"language-gfm": "0.86.0",
"language-git": "0.12.1",
"language-go": "0.42.0",
"language-html": "0.44.1",
@@ -130,24 +130,24 @@
"language-java": "0.17.0",
"language-javascript": "0.116.0",
"language-json": "0.18.0",
"language-less": "0.29.1",
"language-make": "0.21.0",
"language-less": "0.29.3",
"language-make": "0.21.1",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.1",
"language-perl": "0.32.0",
"language-perl": "0.34.0",
"language-php": "0.37.0",
"language-property-list": "0.8.0",
"language-python": "0.43.1",
"language-ruby": "0.68.4",
"language-ruby": "0.68.5",
"language-ruby-on-rails": "0.25.0",
"language-sass": "0.46.0",
"language-shellscript": "0.21.1",
"language-source": "0.9.0",
"language-sql": "0.20.0",
"language-sql": "0.21.0",
"language-text": "0.7.1",
"language-todo": "0.27.0",
"language-toml": "0.18.0",
"language-xml": "0.34.4",
"language-xml": "0.34.5",
"language-yaml": "0.25.2"
},
"private": true,

39
script/mktar Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# mktar name version arch icon-path build-root-path
set -e
SCRIPT=`readlink -f "$0"`
ROOT=`readlink -f $(dirname $SCRIPT)/..`
cd $ROOT
NAME="$1"
VERSION="$2"
ARCH="$3"
ICON_FILE="$4"
BUILD_ROOT_PATH="$5"
FILE_MODE=755
TAR_PATH=$BUILD_ROOT_PATH
ATOM_PATH="$BUILD_ROOT_PATH/Atom"
TARGET_ROOT="`mktemp -d`"
chmod $FILE_MODE "$TARGET_ROOT"
NAME_IN_TAR="$NAME-$VERSION-$ARCH"
TARGET="$TARGET_ROOT/$NAME_IN_TAR"
# Copy executable and resources
cp -a "$ATOM_PATH" "$TARGET"
# Copy icon file
cp "$ICON_FILE" "$TARGET/$NAME.png"
# Remove executable bit from .node files
find "$TARGET" -type f -name "*.node" -exec chmod a-x {} \;
# Create the archive
pushd "$TARGET_ROOT"
tar caf "$TAR_PATH/$NAME_IN_TAR.tar.gz" "$NAME_IN_TAR"
popd
rm -rf "$TARGET_ROOT"

View File

@@ -17,7 +17,7 @@ exports.safeExec = function(command, options, callback) {
var child = childProcess.exec(command, options, function(error, stdout, stderr) {
if (error)
process.exit(error.code || 1);
else
else if (callback)
callback(null);
});
child.stderr.pipe(process.stderr);

View File

@@ -64,6 +64,16 @@ describe('AutoUpdateManager (renderer)', () => {
})
})
describe('::onUpdateError', () => {
it('subscribes to "update-error" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onUpdateError(spy)
electronAutoUpdater.emit('error', {}, 'an error message')
waitsFor(() => spy.callCount === 1)
runs(() => expect(autoUpdateManager.getErrorMessage()).toBe('an error message'))
})
})
describe('::platformSupportsUpdates', () => {
let state, releaseChannel
it('returns true on OS X and Windows when in stable', () => {

View File

@@ -88,14 +88,15 @@ describe "BufferedProcess", ->
describe "when the explorer command is spawned on Windows", ->
it "doesn't quote arguments of the form /root,C...", ->
new BufferedProcess({command: 'explorer.exe', args: ['/root,C:\\foo']})
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"explorer.exe /root,C:\\foo"'
expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"explorer.exe /root,C:\\foo"'
it "spawns the command using a cmd.exe wrapper", ->
new BufferedProcess({command: 'dir'})
expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe 'cmd.exe'
expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe '/s'
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/c'
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '"dir"'
expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe '/d'
expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe '/c'
expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe '"dir"'
it "calls the specified stdout, stderr, and exit callbacks", ->
stdout = ''

View File

@@ -230,9 +230,7 @@ describe('GitRepositoryAsync', () => {
})
})
// @joshaber: Disabling for now. There seems to be some race with path
// subscriptions leading to intermittent test failures, e.g.: https://travis-ci.org/atom/atom/jobs/102702554
xdescribe('.checkoutHeadForEditor(editor)', () => {
describe('.checkoutHeadForEditor(editor)', () => {
let filePath
let editor

View File

@@ -268,6 +268,36 @@ describe "Starting Atom", ->
[otherTempDirPath]
].sort()
it "doesn't reopen any previously opened windows if restorePreviousWindowsOnStart is disabled", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
configPath = path.join(atomHome, 'config.cson')
config = CSON.readFileSync(configPath)
config['*'].core = {restorePreviousWindowsOnStart: false}
CSON.writeFileSync(configPath, config)
runAtom [], {ATOM_HOME: atomHome}, (client) ->
windowProjectPaths = []
client
.waitForWindowCount(1, 10000)
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
.call ->
expect(windowProjectPaths).toEqual [
[]
]
describe "opening a remote directory", ->
it "opens the parent directory and creates an empty text editor", ->
remoteDirectory = 'remote://server:3437/some/directory/path'

View File

@@ -1,3 +1,4 @@
Grim = require 'grim'
_ = require 'underscore-plus'
fs = require 'fs-plus'
path = require 'path'
@@ -96,13 +97,10 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
log(str)
onComplete: (runner) ->
fs.closeSync(logStream) if logStream?
if process.env.JANKY_SHA1 or process.env.CI
grim = require 'grim'
if grim.getDeprecationsLength() > 0
grim.logDeprecations()
resolveWithExitCode(1)
return
if Grim.getDeprecationsLength() > 0
Grim.logDeprecations()
resolveWithExitCode(1)
return
if runner.results().failedCount > 0
resolveWithExitCode(1)

View File

@@ -66,6 +66,9 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-with-broken-package-json package")
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-with-broken-package-json"
it "returns null if the package name or path starts with a dot", ->
expect(atom.packages.loadPackage("/Users/user/.atom/packages/.git")).toBeNull()
it "normalizes short repository urls in package.json", ->
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
expect(metadata.repository.type).toBe "git"

View File

@@ -262,6 +262,26 @@ describe "Pane", ->
pane.setPendingItem("fake item two")
expect(callbackCalled).toBeTruthy()
it "isn't called when a pending item is replaced with a new one", ->
pane = null
pendingSpy = jasmine.createSpy("onItemDidTerminatePendingState")
destroySpy = jasmine.createSpy("onWillDestroyItem")
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then ->
pane = atom.workspace.getActivePane()
runs ->
pane.onItemDidTerminatePendingState pendingSpy
pane.onWillDestroyItem destroySpy
waitsForPromise ->
atom.workspace.open('sample.js', pending: true)
runs ->
expect(destroySpy).toHaveBeenCalled()
expect(pendingSpy).not.toHaveBeenCalled()
describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))

View File

@@ -0,0 +1,66 @@
/** @babel */
import ResourcePool from '../src/resource-pool'
import {it} from './async-spec-helpers'
describe('ResourcePool', () => {
let queue
beforeEach(() => {
queue = new ResourcePool([{}])
})
describe('.enqueue', () => {
it('calls the enqueued function', async () => {
let called = false
await queue.enqueue(() => {
called = true
return Promise.resolve()
})
expect(called).toBe(true)
})
it('forwards values from the inner promise', async () => {
const result = await queue.enqueue(() => Promise.resolve(42))
expect(result).toBe(42)
})
it('forwards errors from the inner promise', async () => {
let threw = false
try {
await queue.enqueue(() => Promise.reject(new Error('down with the sickness')))
} catch (e) {
threw = true
}
expect(threw).toBe(true)
})
it('continues to dequeue work after a promise has been rejected', async () => {
try {
await queue.enqueue(() => Promise.reject(new Error('down with the sickness')))
} catch (e) {}
const result = await queue.enqueue(() => Promise.resolve(42))
expect(result).toBe(42)
})
it('queues up work', async () => {
let resolve = null
queue.enqueue(() => {
return new Promise((resolve_, reject) => {
resolve = resolve_
})
})
expect(queue.getQueueDepth()).toBe(0)
queue.enqueue(() => new Promise((resolve, reject) => {}))
expect(queue.getQueueDepth()).toBe(1)
resolve()
waitsFor(() => queue.getQueueDepth() === 0)
})
})
})

View File

@@ -91,3 +91,13 @@ describe "Selection", ->
expect(buffer.lineForRow(0)).toBe " "
expect(buffer.lineForRow(1)).toBe " "
expect(buffer.lineForRow(2)).toBe ""
it "auto-indents if only a newline is inserted", ->
selection.setBufferRange [[2, 0], [3, 0]]
selection.insertText("\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "
it "auto-indents if only a carriage return + newline is inserted", ->
selection.setBufferRange [[2, 0], [3, 0]]
selection.insertText("\r\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "

View File

@@ -112,14 +112,14 @@ afterEach ->
document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent
ensureNoPathSubscriptions()
warnIfLeakingPathSubscriptions()
waits(0) # yield to ui thread to make screen update more frequently
ensureNoPathSubscriptions = ->
warnIfLeakingPathSubscriptions = ->
watchedPaths = pathwatcher.getWatchedPaths()
pathwatcher.closeAllWatchers()
if watchedPaths.length > 0
throw new Error("Leaking subscriptions for paths: " + watchedPaths.join(", "))
console.error("WARNING: Leaking subscriptions for paths: " + watchedPaths.join(", "))
pathwatcher.closeAllWatchers()
ensureNoDeprecatedFunctionsCalled = ->
deprecations = Grim.getDeprecations()

View File

@@ -3745,6 +3745,21 @@ describe('TextEditorComponent', function () {
return event
}
function buildKeydownEvent ({keyCode, target}) {
let event = new KeyboardEvent('keydown')
Object.defineProperty(event, 'keyCode', {
get: function () {
return keyCode
}
})
Object.defineProperty(event, 'target', {
get: function () {
return target
}
})
return event
}
let inputNode
beforeEach(function () {
@@ -3767,11 +3782,12 @@ describe('TextEditorComponent', function () {
expect(editor.lineTextForBufferRow(0)).toBe('xyvar quicksort = function () {')
})
it('replaces the last character if the length of the input\'s value does not increase, as occurs with the accented character menu', async function () {
componentNode.dispatchEvent(buildTextInputEvent({
data: 'u',
target: inputNode
}))
it('replaces the last character if a keypress event is bracketed by keydown events with matching keyCodes, which occurs when the accented character menu is shown', async function () {
componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode}))
componentNode.dispatchEvent(buildTextInputEvent({data: 'u', target: inputNode}))
componentNode.dispatchEvent(new KeyboardEvent('keypress'))
componentNode.dispatchEvent(buildKeydownEvent({keyCode: 85, target: inputNode}))
componentNode.dispatchEvent(new KeyboardEvent('keyup'))
await nextViewUpdatePromise()
expect(editor.lineTextForBufferRow(0)).toBe('uvar quicksort = function () {')

View File

@@ -4561,6 +4561,136 @@ describe "TextEditor", ->
expect(cursor1.getBufferPosition()).toEqual [0, 0]
expect(cursor3.getBufferPosition()).toEqual [1, 2]
describe ".moveSelectionLeft()", ->
it "moves one active selection on one line one column to the left", ->
editor.setSelectedBufferRange [[0, 4], [0, 13]]
expect(editor.getSelectedText()).toBe 'quicksort'
editor.moveSelectionLeft()
expect(editor.getSelectedText()).toBe 'quicksort'
expect(editor.getSelectedBufferRange()).toEqual [[0, 3], [0, 12]]
it "moves multiple active selections on one line one column to the left", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 3], [0, 12]], [[0, 15], [0, 23]]]
it "moves multiple active selections on multiple lines one column to the left", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 3], [0, 12]], [[1, 5], [1, 9]]]
describe "when a selection is at the first column of a line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[1, 0], [1, 3]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe ' v'
editor.moveSelectionLeft()
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe ' v'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]], [[1, 0], [1, 3]]]
describe "when multiple selections are active on one line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[0, 0], [0, 3]], [[0, 4], [0, 13]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe 'quicksort'
editor.moveSelectionLeft()
expect(selections[0].getText()).toBe 'var'
expect(selections[1].getText()).toBe 'quicksort'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]], [[0, 4], [0, 13]]]
describe ".moveSelectionRight()", ->
it "moves one active selection on one line one column to the right", ->
editor.setSelectedBufferRange [[0, 4], [0, 13]]
expect(editor.getSelectedText()).toBe 'quicksort'
editor.moveSelectionRight()
expect(editor.getSelectedText()).toBe 'quicksort'
expect(editor.getSelectedBufferRange()).toEqual [[0, 5], [0, 14]]
it "moves multiple active selections on one line one column to the right", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[0, 16], [0, 24]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'function'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 5], [0, 14]], [[0, 17], [0, 25]]]
it "moves multiple active selections on multiple lines one column to the right", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'quicksort'
expect(selections[1].getText()).toBe 'sort'
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 5], [0, 14]], [[1, 7], [1, 11]]]
describe "when a selection is at the last column of a line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[2, 34], [2, 40]], [[5, 22], [5, 30]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'items;'
expect(selections[1].getText()).toBe 'shift();'
editor.moveSelectionRight()
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'items;'
expect(selections[1].getText()).toBe 'shift();'
expect(editor.getSelectedBufferRanges()).toEqual [[[2, 34], [2, 40]], [[5, 22], [5, 30]]]
describe "when multiple selections are active on one line", ->
it "does not change the selection", ->
editor.setSelectedBufferRanges([[[2, 27], [2, 33]], [[2, 34], [2, 40]]])
selections = editor.getSelections()
expect(selections[0].getText()).toBe 'return'
expect(selections[1].getText()).toBe 'items;'
editor.moveSelectionRight()
expect(selections[0].getText()).toBe 'return'
expect(selections[1].getText()).toBe 'items;'
expect(editor.getSelectedBufferRanges()).toEqual [[[2, 27], [2, 33]], [[2, 34], [2, 40]]]
describe 'reading text', ->
it '.lineTextForScreenRow(row)', ->
editor.foldBufferRow(4)

View File

@@ -47,9 +47,14 @@ describe "WorkspaceElement", ->
it "updates the font-family based on the 'editor.fontFamily' config value", ->
initialCharWidth = editor.getDefaultCharWidth()
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
fontFamily = atom.config.get('editor.fontFamily')
fontFamily += ", 'Apple Color Emoji'" if process.platform is 'darwin'
expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily
atom.config.set('editor.fontFamily', 'sans-serif')
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
fontFamily = atom.config.get('editor.fontFamily')
fontFamily += ", 'Apple Color Emoji'" if process.platform is 'darwin'
expect(getComputedStyle(editorElement).fontFamily).toBe fontFamily
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
it "updates the line-height based on the 'editor.lineHeight' config value", ->

View File

@@ -1,5 +1,5 @@
_ = require 'underscore-plus'
{ipcRenderer, remote, shell, webFrame} = require 'electron'
{screen, ipcRenderer, remote, shell, webFrame} = require 'electron'
ipcHelpers = require './ipc-helpers'
{Disposable} = require 'event-kit'
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
@@ -211,6 +211,14 @@ class ApplicationDelegate
new Disposable ->
ipcRenderer.removeListener('message', outerCallback)
onUpdateError: (callback) ->
outerCallback = (event, message, detail) ->
callback(detail) if message is 'update-error'
ipcRenderer.on('message', outerCallback)
new Disposable ->
ipcRenderer.removeListener('message', outerCallback)
onApplicationMenuCommand: (callback) ->
outerCallback = (event, args...) ->
callback(args...)
@@ -233,14 +241,28 @@ class ApplicationDelegate
openExternal: (url) ->
shell.openExternal(url)
disablePinchToZoom: ->
webFrame.setZoomLevelLimits(1, 1)
disableZoom: ->
outerCallback = ->
webFrame.setZoomLevelLimits(1, 1)
outerCallback()
# Set the limits every time a display is added or removed, otherwise the
# configuration gets reset to the default, which allows zooming the
# webframe.
screen.on('display-added', outerCallback)
screen.on('display-removed', outerCallback)
new Disposable ->
screen.removeListener('display-added', outerCallback)
screen.removeListener('display-removed', outerCallback)
checkForUpdate: ->
ipcRenderer.send('check-for-update')
ipcRenderer.send('command', 'application:check-for-update')
restartAndInstallUpdate: ->
ipcRenderer.send('install-update')
ipcRenderer.send('command', 'application:install-update')
getAutoUpdateManagerState: ->
ipcRenderer.sendSync('get-auto-update-manager-state')
getAutoUpdateManagerErrorMessage: ->
ipcRenderer.sendSync('get-auto-update-manager-error')

View File

@@ -208,7 +208,7 @@ class AtomEnvironment extends Model
@stylesElement = @styles.buildStylesElement()
@document.head.appendChild(@stylesElement)
@applicationDelegate.disablePinchToZoom()
@disposables.add(@applicationDelegate.disableZoom())
@keymaps.subscribeToFileReadFailure()
@keymaps.loadBundledKeymaps()

View File

@@ -20,6 +20,9 @@ export default class AutoUpdateManager {
}),
applicationDelegate.onUpdateNotAvailable(() => {
this.emitter.emit('update-not-available')
}),
applicationDelegate.onUpdateError(() => {
this.emitter.emit('update-error')
})
)
}
@@ -41,6 +44,10 @@ export default class AutoUpdateManager {
return this.applicationDelegate.getAutoUpdateManagerState()
}
getErrorMessage () {
return this.applicationDelegate.getAutoUpdateManagerErrorMessage()
}
platformSupportsUpdates () {
return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported'
}
@@ -67,6 +74,10 @@ export default class AutoUpdateManager {
return this.emitter.on('update-not-available', callback)
}
onUpdateError (callback) {
return this.emitter.on('update-error', callback)
}
getPlatform () {
return process.platform
}

View File

@@ -3,6 +3,7 @@ ApplicationMenu = require './application-menu'
AtomProtocolHandler = require './atom-protocol-handler'
AutoUpdateManager = require './auto-update-manager'
StorageFolder = require '../storage-folder'
Config = require '../config'
ipcHelpers = require '../ipc-helpers'
{BrowserWindow, Menu, app, dialog, ipcMain, shell} = require 'electron'
fs = require 'fs-plus'
@@ -70,7 +71,11 @@ class AtomApplication
@pidsToOpenWindows = {}
@windows = []
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath)
@config = new Config({configDirPath: process.env.ATOM_HOME, @resourcePath, enablePersistence: true})
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
@config.load()
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config)
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
@@ -305,12 +310,12 @@ class AtomApplication
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
event.sender.devToolsWebContents?.executeJavaScript(code)
ipcMain.on 'check-for-update', =>
@autoUpdateManager.check()
ipcMain.on 'get-auto-update-manager-state', (event) =>
event.returnValue = @autoUpdateManager.getState()
ipcMain.on 'get-auto-update-manager-error', (event) =>
event.returnValue = @autoUpdateManager.getErrorMessage()
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
event.sender.devToolsWebContents?.executeJavaScript(code)
@@ -461,6 +466,7 @@ class AtomApplication
openedWindow.restore()
else
openedWindow.focus()
openedWindow.replaceEnvironment(env)
else
if devMode
try
@@ -510,7 +516,8 @@ class AtomApplication
@storageFolder.storeSync('application.json', states)
loadState: (options) ->
if (states = @storageFolder.load('application.json'))?.length > 0
restorePreviousState = @config.get('core.restorePreviousWindowsOnStart') ? true
if restorePreviousState and (states = @storageFolder.load('application.json'))?.length > 0
for state in states
@openWithOptions(_.extend(options, {
initialPaths: state.initialPaths

View File

@@ -68,6 +68,7 @@ class AtomWindow
@loaded = true
@setLoadSettings(loadSettings)
@env = loadSettings.env if loadSettings.env?
@browserWindow.focusOnWebView() if @isSpec
@browserWindow.temporaryState = {windowDimensions} if windowDimensions?
@@ -169,6 +170,9 @@ class AtomWindow
else
@browserWindow.once 'window:loaded', => @openLocations(locationsToOpen)
replaceEnvironment: (env) ->
@browserWindow.webContents.send 'environment', env
sendMessage: (message, detail) ->
@browserWindow.webContents.send 'message', message, detail

View File

@@ -1,6 +1,5 @@
autoUpdater = null
_ = require 'underscore-plus'
Config = require '../config'
{EventEmitter} = require 'events'
path = require 'path'
@@ -16,13 +15,10 @@ module.exports =
class AutoUpdateManager
_.extend @prototype, EventEmitter.prototype
constructor: (@version, @testMode, resourcePath) ->
constructor: (@version, @testMode, resourcePath, @config) ->
@state = IdleState
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
@config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true})
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
@config.load()
process.nextTick => @setupAutoUpdater()
setupAutoUpdater: ->
@@ -32,7 +28,8 @@ class AutoUpdateManager
{autoUpdater} = require 'electron'
autoUpdater.on 'error', (event, message) =>
@setState(ErrorState)
@setState(ErrorState, message)
@emitWindowEvent('update-error')
console.error "Error Downloading Update: #{message}"
autoUpdater.setFeedURL @feedUrl
@@ -82,14 +79,18 @@ class AutoUpdateManager
atomWindow.sendMessage(eventName, payload)
return
setState: (state) ->
setState: (state, errorMessage) ->
return if @state is state
@state = state
@errorMessage = errorMessage
@emit 'state-changed', @state
getState: ->
@state
getErrorMessage: ->
@errorMessage
scheduleUpdateCheck: ->
# Only schedule update check periodically if running in release version and
# and there is no existing scheduled update check.

View File

@@ -47,6 +47,7 @@ class BufferedNodeProcess extends BufferedProcess
options ?= {}
options.env ?= Object.create(process.env)
options.env['ELECTRON_RUN_AS_NODE'] = 1
options.env['ELECTRON_NO_ATTACH_CONSOLE'] = 1
args = args?.slice() ? []
args.unshift(command)

View File

@@ -67,7 +67,7 @@ class BufferedProcess
cmdArgs.unshift("\"#{command}\"")
else
cmdArgs.unshift(command)
cmdArgs = ['/s', '/c', "\"#{cmdArgs.join(' ')}\""]
cmdArgs = ['/s', '/d', '/c', "\"#{cmdArgs.join(' ')}\""]
cmdOptions = _.clone(options)
cmdOptions.windowsVerbatimArguments = true
@spawn(@getCmdPath(), cmdArgs, cmdOptions)

View File

@@ -58,7 +58,7 @@ function getFromShell () {
function needsPatching (options = { platform: process.platform, env: process.env }) {
if (options.platform === 'darwin' && !options.env.PWD) {
let shell = getUserShell()
if (shell.endsWith('csh') || shell.endsWith('tcsh')) {
if (shell.endsWith('csh') || shell.endsWith('tcsh') || shell.endsWith('fish')) {
return false
}
return true
@@ -67,9 +67,20 @@ function needsPatching (options = { platform: process.platform, env: process.env
return false
}
// Fix for #11302 because `process.env` on Windows is a magic object that offers case-insensitive
// environment variable matching. By always cloning to `process.env` we prevent breaking the
// underlying functionality.
function clone (to, from) {
for (var key in to) {
delete to[key]
}
Object.assign(to, from)
}
function normalize (options = {}) {
if (options && options.env) {
process.env = options.env
clone(process.env, options.env)
}
if (!options.env) {
@@ -85,10 +96,18 @@ function normalize (options = {}) {
// in #4126. Retain the original in case someone needs it.
let shellEnv = getFromShell()
if (shellEnv && shellEnv.PATH) {
process._originalEnv = process.env
process.env = shellEnv
process._originalEnv = Object.assign({}, process.env)
clone(process.env, shellEnv)
}
}
}
export default { getFromShell, needsPatching, normalize }
function replace (env) {
if (!env || !env.PATH) {
return
}
clone(process.env, env)
}
export default { getFromShell, needsPatching, normalize, replace }

View File

@@ -3,6 +3,7 @@
import fs from 'fs-plus'
import path from 'path'
import Git from 'nodegit'
import ResourcePool from './resource-pool'
import {Emitter, CompositeDisposable, Disposable} from 'event-kit'
const modifiedStatusFlags = Git.Status.STATUS.WT_MODIFIED | Git.Status.STATUS.INDEX_MODIFIED | Git.Status.STATUS.WT_DELETED | Git.Status.STATUS.INDEX_DELETED | Git.Status.STATUS.WT_TYPECHANGE | Git.Status.STATUS.INDEX_TYPECHANGE
@@ -38,7 +39,8 @@ export default class GitRepositoryAsync {
}
constructor (_path, options = {}) {
Git.enableThreadSafety()
// We'll serialize our access manually.
Git.setThreadSafetyStatus(Git.THREAD_SAFETY.DISABLED)
this.emitter = new Emitter()
this.subscriptions = new CompositeDisposable()
@@ -50,6 +52,11 @@ export default class GitRepositoryAsync {
this._openExactPath = options.openExactPath || false
this.repoPromise = this.openRepository()
// NB: We don't currently _use_ the pooled object. But by giving it one
// thing, we're really just serializing all the work. Down the road, we
// could open multiple connections to the repository.
this.repoPool = new ResourcePool([this.repoPromise])
this.isCaseInsensitive = fs.isCaseInsensitive()
this.upstream = {}
this.submodules = {}
@@ -81,6 +88,7 @@ export default class GitRepositoryAsync {
this.emitter.dispose()
this.emitter = null
}
if (this.subscriptions) {
this.subscriptions.dispose()
this.subscriptions = null
@@ -260,10 +268,12 @@ export default class GitRepositoryAsync {
// Public: Returns a {Promise} which resolves to whether the given branch
// exists.
hasBranch (branch) {
return this.getRepo()
.then(repo => repo.getBranch(branch))
.then(branch => branch != null)
.catch(_ => false)
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.getBranch(branch))
.then(branch => branch != null)
.catch(_ => false)
})
}
// Public: Retrieves a shortened version of the HEAD reference value.
@@ -277,9 +287,11 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} which resolves to a {String}.
getShortHead (_path) {
return this.getRepo(_path)
.then(repo => repo.getCurrentBranch())
.then(branch => branch.shorthand())
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.getCurrentBranch())
.then(branch => branch.shorthand())
})
}
// Public: Is the given path a submodule in the repository?
@@ -289,14 +301,18 @@ export default class GitRepositoryAsync {
// Returns a {Promise} that resolves true if the given path is a submodule in
// the repository.
isSubmodule (_path) {
return this.getRepo()
.then(repo => repo.openIndex())
.then(index => Promise.all([index, this.relativizeToWorkingDirectory(_path)]))
.then(([index, relativePath]) => {
const entry = index.getByPath(relativePath)
if (!entry) return false
return this.relativizeToWorkingDirectory(_path)
.then(relativePath => {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.index())
.then(index => {
const entry = index.getByPath(relativePath)
if (!entry) return false
return entry.mode === submoduleMode
return entry.mode === submoduleMode
})
})
})
}
@@ -311,16 +327,18 @@ export default class GitRepositoryAsync {
// * `ahead` The {Number} of commits ahead.
// * `behind` The {Number} of commits behind.
getAheadBehindCount (reference, _path) {
return this.getRepo(_path)
.then(repo => Promise.all([repo, repo.getBranch(reference)]))
.then(([repo, local]) => {
const upstream = Git.Branch.upstream(local)
return Promise.all([repo, local, upstream])
})
.then(([repo, local, upstream]) => {
return Git.Graph.aheadBehind(repo, local.target(), upstream.target())
})
.catch(_ => ({ahead: 0, behind: 0}))
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => Promise.all([repo, repo.getBranch(reference)]))
.then(([repo, local]) => {
const upstream = Git.Branch.upstream(local)
return Promise.all([repo, local, upstream])
})
.then(([repo, local, upstream]) => {
return Git.Graph.aheadBehind(repo, local.target(), upstream.target())
})
.catch(_ => ({ahead: 0, behind: 0}))
})
}
// Public: Get the cached ahead/behind commit counts for the current branch's
@@ -352,10 +370,12 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to the {String} git configuration value
// specified by the key.
getConfigValue (key, _path) {
return this.getRepo(_path)
.then(repo => repo.configSnapshot())
.then(config => config.getStringBuf(key))
.catch(_ => null)
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.configSnapshot())
.then(config => config.getStringBuf(key))
.catch(_ => null)
})
}
// Public: Get the URL for the 'origin' remote.
@@ -378,9 +398,11 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {String} branch name such as
// `refs/remotes/origin/master`.
getUpstreamBranch (_path) {
return this.getRepo(_path)
.then(repo => repo.getCurrentBranch())
.then(branch => Git.Branch.upstream(branch))
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.getCurrentBranch())
.then(branch => Git.Branch.upstream(branch))
})
}
// Public: Gets all the local and remote references.
@@ -393,23 +415,25 @@ export default class GitRepositoryAsync {
// * `remotes` An {Array} of remote reference names.
// * `tags` An {Array} of tag reference names.
getReferences (_path) {
return this.getRepo(_path)
.then(repo => repo.getReferences(Git.Reference.TYPE.LISTALL))
.then(refs => {
const heads = []
const remotes = []
const tags = []
for (const ref of refs) {
if (ref.isTag()) {
tags.push(ref.name())
} else if (ref.isRemote()) {
remotes.push(ref.name())
} else if (ref.isBranch()) {
heads.push(ref.name())
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => repo.getReferences(Git.Reference.TYPE.LISTALL))
.then(refs => {
const heads = []
const remotes = []
const tags = []
for (const ref of refs) {
if (ref.isTag()) {
tags.push(ref.name())
} else if (ref.isRemote()) {
remotes.push(ref.name())
} else if (ref.isBranch()) {
heads.push(ref.name())
}
}
}
return {heads, remotes, tags}
})
return {heads, remotes, tags}
})
})
}
// Public: Get the SHA for the given reference.
@@ -421,9 +445,11 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to the current {String} SHA for the
// given reference.
getReferenceTarget (reference, _path) {
return this.getRepo(_path)
.then(repo => Git.Reference.nameToId(repo, reference))
.then(oid => oid.tostrS())
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => Git.Reference.nameToId(repo, reference))
.then(oid => oid.tostrS())
})
}
// Reading Status
@@ -460,12 +486,17 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is ignored.
isPathIgnored (_path) {
return Promise.all([this.getRepo(), this.getWorkingDirectory()])
.then(([repo, wd]) => {
const relativePath = this.relativize(_path, wd)
return Git.Ignore.pathIsIgnored(repo, relativePath)
return this.getWorkingDirectory()
.then(wd => {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => {
const relativePath = this.relativize(_path, wd)
return Git.Ignore.pathIsIgnored(repo, relativePath)
})
.then(ignored => Boolean(ignored))
})
})
.then(ignored => Boolean(ignored))
}
// Get the status of a directory in the repository's working directory.
@@ -501,8 +532,8 @@ export default class GitRepositoryAsync {
// status bit for the path.
refreshStatusForPath (_path) {
let relativePath
return Promise.all([this.getRepo(), this.getWorkingDirectory()])
.then(([repo, wd]) => {
return this.getWorkingDirectory()
.then(wd => {
relativePath = this.relativize(_path, wd)
return this._getStatus([relativePath])
})
@@ -609,34 +640,39 @@ export default class GitRepositoryAsync {
// * `added` The {Number} of added lines.
// * `deleted` The {Number} of deleted lines.
getDiffStats (_path) {
return this.getRepo(_path)
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
.then(([repo, headCommit]) => Promise.all([repo, headCommit.getTree(), this.getWorkingDirectory(_path)]))
.then(([repo, tree, wd]) => {
const options = new Git.DiffOptions()
options.contextLines = 0
options.flags = Git.Diff.OPTION.DISABLE_PATHSPEC_MATCH
options.pathspec = this.relativize(_path, wd)
if (process.platform === 'win32') {
// Ignore eol of line differences on windows so that files checked in
// as LF don't report every line modified when the text contains CRLF
// endings.
options.flags |= Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
}
return Git.Diff.treeToWorkdir(repo, tree, options)
})
.then(diff => this._getDiffLines(diff))
.then(lines => {
const stats = {added: 0, deleted: 0}
for (const line of lines) {
const origin = line.origin()
if (origin === Git.Diff.LINE.ADDITION) {
stats.added++
} else if (origin === Git.Diff.LINE.DELETION) {
stats.deleted++
}
}
return stats
return this.getWorkingDirectory(_path)
.then(wd => {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
.then(([repo, headCommit]) => Promise.all([repo, headCommit.getTree()]))
.then(([repo, tree]) => {
const options = new Git.DiffOptions()
options.contextLines = 0
options.flags = Git.Diff.OPTION.DISABLE_PATHSPEC_MATCH
options.pathspec = this.relativize(_path, wd)
if (process.platform === 'win32') {
// Ignore eol of line differences on windows so that files checked in
// as LF don't report every line modified when the text contains CRLF
// endings.
options.flags |= Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
}
return Git.Diff.treeToWorkdir(repo, tree, options)
})
.then(diff => this._getDiffLines(diff))
.then(lines => {
const stats = {added: 0, deleted: 0}
for (const line of lines) {
const origin = line.origin()
if (origin === Git.Diff.LINE.ADDITION) {
stats.added++
} else if (origin === Git.Diff.LINE.DELETION) {
stats.deleted++
}
}
return stats
})
})
})
}
@@ -652,24 +688,29 @@ export default class GitRepositoryAsync {
// * `oldLines` The {Number} of lines in the old hunk.
// * `newLines` The {Number} of lines in the new hunk
getLineDiffs (_path, text) {
let relativePath = null
return Promise.all([this.getRepo(_path), this.getWorkingDirectory(_path)])
.then(([repo, wd]) => {
relativePath = this.relativize(_path, wd)
return repo.getHeadCommit()
})
.then(commit => commit.getEntry(relativePath))
.then(entry => entry.getBlob())
.then(blob => {
const options = new Git.DiffOptions()
options.contextLines = 0
if (process.platform === 'win32') {
// Ignore eol of line differences on windows so that files checked in
// as LF don't report every line modified when the text contains CRLF
// endings.
options.flags = Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
}
return this._diffBlobToBuffer(blob, text, options)
return this.getWorkingDirectory(_path)
.then(wd => {
let relativePath = null
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => {
relativePath = this.relativize(_path, wd)
return repo.getHeadCommit()
})
.then(commit => commit.getEntry(relativePath))
.then(entry => entry.getBlob())
.then(blob => {
const options = new Git.DiffOptions()
options.contextLines = 0
if (process.platform === 'win32') {
// Ignore eol of line differences on windows so that files checked in
// as LF don't report every line modified when the text contains CRLF
// endings.
options.flags = Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
}
return this._diffBlobToBuffer(blob, text, options)
})
})
})
}
@@ -691,12 +732,17 @@ export default class GitRepositoryAsync {
// Returns a {Promise} that resolves or rejects depending on whether the
// method was successful.
checkoutHead (_path) {
return Promise.all([this.getRepo(_path), this.getWorkingDirectory(_path)])
.then(([repo, wd]) => {
const checkoutOptions = new Git.CheckoutOptions()
checkoutOptions.paths = [this.relativize(_path, wd)]
checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH
return Git.Checkout.head(repo, checkoutOptions)
return this.getWorkingDirectory(_path)
.then(wd => {
return this.repoPool.enqueue(() => {
return this.getRepo(_path)
.then(repo => {
const checkoutOptions = new Git.CheckoutOptions()
checkoutOptions.paths = [this.relativize(_path, wd)]
checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH
return Git.Checkout.head(repo, checkoutOptions)
})
})
})
.then(() => this.refreshStatusForPath(_path))
}
@@ -709,17 +755,19 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} that resolves if the method was successful.
checkoutReference (reference, create) {
return this.getRepo()
.then(repo => repo.checkoutBranch(reference))
.catch(error => {
if (create) {
return this._createBranch(reference)
.then(_ => this.checkoutReference(reference, false))
} else {
throw error
}
})
.then(_ => null)
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.checkoutBranch(reference))
})
.catch(error => {
if (create) {
return this._createBranch(reference)
.then(_ => this.checkoutReference(reference, false))
} else {
throw error
}
})
.then(_ => null)
}
// Private
@@ -745,9 +793,11 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {NodeGit.Ref} reference to the
// created branch.
_createBranch (name) {
return this.getRepo()
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
.then(([repo, commit]) => repo.createBranch(name, commit))
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => Promise.all([repo, repo.getHeadCommit()]))
.then(([repo, commit]) => repo.createBranch(name, commit))
})
}
// Get all the hunks in the diff.
@@ -804,14 +854,16 @@ export default class GitRepositoryAsync {
// Returns a {Promise} which resolves to a {boolean} indicating whether the
// branch name changed.
_refreshBranch () {
return this.getRepo()
.then(repo => repo.getCurrentBranch())
.then(ref => ref.name())
.then(branchName => {
const changed = branchName !== this.branch
this.branch = branchName
return changed
})
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => repo.getCurrentBranch())
.then(ref => ref.name())
.then(branchName => {
const changed = branchName !== this.branch
this.branch = branchName
return changed
})
})
}
// Refresh the cached ahead/behind count with the given branch.
@@ -1077,18 +1129,20 @@ export default class GitRepositoryAsync {
//
// Returns a {Promise} which resolves to an {Array} of {NodeGit.StatusFile}
// statuses for the paths.
_getStatus (paths, repo) {
return this.getRepo()
.then(repo => {
const opts = {
flags: Git.Status.OPT.INCLUDE_UNTRACKED | Git.Status.OPT.RECURSE_UNTRACKED_DIRS
}
_getStatus (paths) {
return this.repoPool.enqueue(() => {
return this.getRepo()
.then(repo => {
const opts = {
flags: Git.Status.OPT.INCLUDE_UNTRACKED | Git.Status.OPT.RECURSE_UNTRACKED_DIRS
}
if (paths) {
opts.pathspec = paths
}
if (paths) {
opts.pathspec = paths
}
return repo.getStatusExt(opts)
})
return repo.getStatusExt(opts)
})
})
}
}

View File

@@ -166,7 +166,12 @@ class GitRepository
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatuses: (callback) ->
@async.onDidChangeStatuses callback
@async.onDidChangeStatuses ->
# Defer the callback to the next tick so that we've reset
# `@statusesByPath` by the time it's called. Otherwise reads from within
# the callback could be inconsistent.
# See https://github.com/atom/atom/issues/11396
process.nextTick callback
###
Section: Repository Details

View File

@@ -4,11 +4,12 @@ module.exports = ({blobStore}) ->
path = require 'path'
require './window'
{getWindowLoadSettings} = require './window-load-settings-helpers'
{ipcRenderer} = require 'electron'
{resourcePath, isSpec, devMode, env} = getWindowLoadSettings()
# Set baseline environment
environmentHelpers.normalize({env: env})
env = process.env
# Add application-specific exports to module search path.
exportsPath = path.join(resourcePath, 'exports')
@@ -25,7 +26,7 @@ module.exports = ({blobStore}) ->
applicationDelegate: new ApplicationDelegate,
configDirPath: process.env.ATOM_HOME
enablePersistence: true
env: env
env: process.env
})
atom.startEditorWindow().then ->
@@ -35,3 +36,6 @@ module.exports = ({blobStore}) ->
window.removeEventListener('focus', windowFocused)
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
window.addEventListener('focus', windowFocused)
ipcRenderer.on('environment', (event, env) ->
environmentHelpers.replace(env)
)

View File

@@ -367,6 +367,8 @@ class PackageManager
@emitter.emit 'did-load-initial-packages'
loadPackage: (nameOrPath) ->
return null if path.basename(nameOrPath)[0].match /^\./ # primarily to skip .git folder
return pack if pack = @getLoadedPackage(nameOrPath)
if packagePath = @resolvePackagePath(nameOrPath)

View File

@@ -442,15 +442,16 @@ class Pane extends Model
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()
replacingPendingItem = lastPendingItem? and not moved
@pendingItem = null if replacingPendingItem
@setPendingItem(item) if pending
@emitter.emit 'did-add-item', {item, index, moved}
@destroyItem(lastPendingItem) if lastPendingItem? and not moved
@destroyItem(lastPendingItem) if replacingPendingItem
@setActiveItem(item) unless @getActiveItem()?
item
@@ -458,7 +459,8 @@ class Pane extends Model
if @pendingItem isnt item
mostRecentPendingItem = @pendingItem
@pendingItem = item
@emitter.emit 'item-did-terminate-pending-state', mostRecentPendingItem
if mostRecentPendingItem?
@emitter.emit 'item-did-terminate-pending-state', mostRecentPendingItem
getPendingItem: =>
@pendingItem or null

View File

@@ -207,6 +207,8 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'editor:checkout-head-revision': -> @checkoutHeadRevision()
'editor:move-line-up': -> @moveLineUp()
'editor:move-line-down': -> @moveLineDown()
'editor:move-selection-left': -> @moveSelectionLeft()
'editor:move-selection-right': -> @moveSelectionRight()
'editor:duplicate-lines': -> @duplicateLines()
'editor:join-lines': -> @joinLines()
)

57
src/resource-pool.js Normal file
View File

@@ -0,0 +1,57 @@
/** @babel */
// Manages a pool of some resource.
export default class ResourcePool {
constructor (pool) {
this.pool = pool
this.queue = []
}
// Enqueue the given function. The function will be given an object from the
// pool. The function must return a {Promise}.
enqueue (fn) {
let resolve = null
let reject = null
const wrapperPromise = new Promise((resolve_, reject_) => {
resolve = resolve_
reject = reject_
})
this.queue.push(this.wrapFunction(fn, resolve, reject))
this.dequeueIfAble()
return wrapperPromise
}
wrapFunction (fn, resolve, reject) {
return (resource) => {
const promise = fn(resource)
promise
.then(result => {
resolve(result)
this.taskDidComplete(resource)
}, error => {
reject(error)
this.taskDidComplete(resource)
})
}
}
taskDidComplete (resource) {
this.pool.push(resource)
this.dequeueIfAble()
}
dequeueIfAble () {
if (!this.pool.length || !this.queue.length) return
const fn = this.queue.shift()
const resource = this.pool.shift()
fn(resource)
}
getQueueDepth () { return this.queue.length }
}

View File

@@ -378,7 +378,8 @@ class Selection extends Model
indentAdjustment = @editor.indentLevelForLine(precedingText) - options.indentBasis
@adjustIndent(remainingLines, indentAdjustment)
if options.autoIndent and NonWhitespaceRegExp.test(text) and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
textIsAutoIndentable = text is '\n' or text is '\r\n' or NonWhitespaceRegExp.test(text)
if options.autoIndent and textIsAutoIndentable and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
autoIndentFirstLine = true
firstLine = precedingText + firstInsertedLine
desiredIndentLevel = @editor.languageMode.suggestedIndentForLineAtBufferRow(oldBufferRange.start.row, firstLine)

View File

@@ -247,9 +247,50 @@ class TextEditorComponent
@scrollViewNode.addEventListener 'mousedown', @onMouseDown
@scrollViewNode.addEventListener 'scroll', @onScrollViewScroll
@detectAccentedCharacterMenu()
@listenForIMEEvents()
@trackSelectionClipboard() if process.platform is 'linux'
detectAccentedCharacterMenu: ->
# We need to get clever to detect when the accented character menu is
# opened on OS X. Usually, every keydown event that could cause input is
# followed by a corresponding keypress. However, pressing and holding
# long enough to open the accented character menu causes additional keydown
# events to fire that aren't followed by their own keypress and textInput
# events.
#
# Therefore, we assume the accented character menu has been deployed if,
# before observing any keyup event, we observe events in the following
# sequence:
#
# keydown(keyCode: X), keypress, keydown(keyCode: X)
#
# The keyCode X must be the same in the keydown events that bracket the
# keypress, meaning we're *holding* the _same_ key we intially pressed.
# Got that?
lastKeydown = null
lastKeydownBeforeKeypress = null
@domNode.addEventListener 'keydown', (event) =>
if lastKeydownBeforeKeypress
if lastKeydownBeforeKeypress.keyCode is event.keyCode
@openedAccentedCharacterMenu = true
lastKeydownBeforeKeypress = null
else
lastKeydown = event
@domNode.addEventListener 'keypress', =>
lastKeydownBeforeKeypress = lastKeydown
lastKeydown = null
# This cancels the accented character behavior if we type a key normally
# with the menu open.
@openedAccentedCharacterMenu = false
@domNode.addEventListener 'keyup', ->
lastKeydownBeforeKeypress = null
lastKeydown = null
listenForIMEEvents: ->
# The IME composition events work like this:
#
@@ -266,6 +307,9 @@ class TextEditorComponent
checkpoint = null
@domNode.addEventListener 'compositionstart', =>
if @openedAccentedCharacterMenu
@editor.selectLeft()
@openedAccentedCharacterMenu = false
checkpoint = @editor.createCheckpoint()
@domNode.addEventListener 'compositionupdate', (event) =>
@editor.insertText(event.data, select: true)
@@ -321,24 +365,21 @@ class TextEditorComponent
onTextInput: (event) =>
event.stopPropagation()
# If we prevent the insertion of a space character, then the browser
# interprets the spacebar keypress as a page-down command.
event.preventDefault() unless event.data is ' '
event.preventDefault()
return unless @isInputEnabled()
inputNode = event.target
# Workaround of the accented character suggestion feature in OS X.
# This will only occur when the user is not composing in IME mode.
# When the user selects a modified character from the OSX menu, `textInput`
# will occur twice, once for the initial character, and once for the
# modified character. However, only a single keypress will have fired. If
# this is the case, select backward to replace the original character.
if @openedAccentedCharacterMenu
@editor.selectLeft()
@openedAccentedCharacterMenu = false
# Work around of the accented character suggestion feature in OS X.
# Text input fires before a character is inserted, and if the browser is
# replacing the previous un-accented character with an accented variant, it
# will select backward over it.
selectedLength = inputNode.selectionEnd - inputNode.selectionStart
@editor.selectLeft() if selectedLength is 1
insertedRange = @editor.insertText(event.data, groupUndo: true)
inputNode.value = event.data if insertedRange
@editor.insertText(event.data, groupUndo: true)
onVerticalScroll: (scrollTop) =>
return if @updateRequested or scrollTop is @presenter.getScrollTop()

View File

@@ -1074,6 +1074,50 @@ class TextEditor extends Model
@autoIndentSelectedRows() if @shouldAutoIndent()
@scrollToBufferPosition([newSelectionRanges[0].start.row - 1, 0])
# Move any active selections one column to the left.
moveSelectionLeft: ->
selections = @getSelectedBufferRanges()
noSelectionAtStartOfLine = selections.every((selection) ->
selection.start.column isnt 0
)
translationDelta = [0, -1]
translatedRanges = []
if noSelectionAtStartOfLine
@transact =>
for selection in selections
charToLeftOfSelection = new Range(selection.start.translate(translationDelta), selection.start)
charTextToLeftOfSelection = @buffer.getTextInRange(charToLeftOfSelection)
@buffer.insert(selection.end, charTextToLeftOfSelection)
@buffer.delete(charToLeftOfSelection)
translatedRanges.push(selection.translate(translationDelta))
@setSelectedBufferRanges(translatedRanges)
# Move any active selections one column to the right.
moveSelectionRight: ->
selections = @getSelectedBufferRanges()
noSelectionAtEndOfLine = selections.every((selection) =>
selection.end.column isnt @buffer.lineLengthForRow(selection.end.row)
)
translationDelta = [0, 1]
translatedRanges = []
if noSelectionAtEndOfLine
@transact =>
for selection in selections
charToRightOfSelection = new Range(selection.end, selection.end.translate(translationDelta))
charTextToRightOfSelection = @buffer.getTextInRange(charToRightOfSelection)
@buffer.delete(charToRightOfSelection)
@buffer.insert(selection.start, charTextToRightOfSelection)
translatedRanges.push(selection.translate(translationDelta))
@setSelectedBufferRanges(translatedRanges)
# Duplicate the most recent cursor's current line.
duplicateLines: ->
@transact =>
@@ -1507,7 +1551,7 @@ class TextEditor extends Model
#
# * `markerLayer` A {TextEditorMarkerLayer} or {MarkerLayer} to decorate.
# * `decorationParams` The same parameters that are passed to
# {decorateMarker}, except the `type` cannot be `overlay` or `gutter`.
# {TextEditor::decorateMarker}, except the `type` cannot be `overlay` or `gutter`.
#
# This API is experimental and subject to change on any release.
#

View File

@@ -44,10 +44,15 @@ class WorkspaceElement extends HTMLElement
@subscriptions.add @config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
updateGlobalTextEditorStyleSheet: ->
fontFamily = @config.get('editor.fontFamily')
# TODO: There is a bug in how some emojis (e.g. ❤️) are rendered on OSX.
# This workaround should be removed once we update to Chromium 51, where the
# problem was fixed.
fontFamily += ', "Apple Color Emoji"' if process.platform is 'darwin'
styleSheetSource = """
atom-text-editor {
font-size: #{@config.get('editor.fontSize')}px;
font-family: #{@config.get('editor.fontFamily')};
font-family: #{fontFamily};
line-height: #{@config.get('editor.lineHeight')};
}
"""