Comparar commits
255 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 34b93554c3 | |||
| 3de926a1af | |||
| bc85bd2e74 | |||
| 3601aac136 | |||
| de8b498402 | |||
| 1bd00f1d0a | |||
| 65e80cd65e | |||
| 14bed8a3c8 | |||
| d0513cb95d | |||
| 6a08618821 | |||
| 4e73fe3f24 | |||
| 5788e30269 | |||
| de6fb2802b | |||
| 2bfd7d093a | |||
| 416266b815 | |||
| bcc888bfb4 | |||
| e9bcb851c3 | |||
| a66f81d70a | |||
| 72d1eb24a3 | |||
| ba18c65dac | |||
| 0c94298ea4 | |||
| 9bf374d4e5 | |||
| 9344e3fc98 | |||
| ede6a081a4 | |||
| d83547b9f4 | |||
| ef2337359b | |||
| bc8a5f4b23 | |||
| 261421b609 | |||
| 07eb2585d6 | |||
| bec3d4effc | |||
| 422c4d41d1 | |||
| 7aa233d9d6 | |||
| 6c9df6d91a | |||
| 520e478447 | |||
| 69b34b7377 | |||
| 3180bd2e3c | |||
| 286c7e8f18 | |||
| 1c6e2f06ed | |||
| 527a18fedc | |||
| 46e1a9254d | |||
| ae46f1a3e1 | |||
| 573c7f9621 | |||
| dbed45c68e | |||
| 95818ab21f | |||
| 9cf50960a2 | |||
| f555af0b88 | |||
| 853ee3eec5 | |||
| bb9e052c90 | |||
| 4bade1c976 | |||
| 058d55c829 | |||
| dc323348b7 | |||
| 858e7b62a3 | |||
| 28015339d9 | |||
| ca86e0258f | |||
| 87fcc39045 | |||
| b78ff8c3a9 | |||
| 01fdf016a9 | |||
| 655cce9fd4 | |||
| f3ea2cd9e0 | |||
| ebcae85f1a | |||
| 0b79c31d45 | |||
| d5ec8551e1 | |||
| cd376c2d1d | |||
| 0104d7e264 | |||
| 019cccc978 | |||
| 7f1d88a05d | |||
| ec754e5cd9 | |||
| e046bb52d7 | |||
| 34be92b5b0 | |||
| fb0a15b1b3 | |||
| e26ab5513f | |||
| f623a4f2b5 | |||
| 4186a4943b | |||
| e179860519 | |||
| 43f8596fb1 | |||
| 2c7d01e398 | |||
| 1bdf45f7d6 | |||
| 7e6e61905b | |||
| 01b4ff24b9 | |||
| 47e2cb4645 | |||
| 236f4c5342 | |||
| 911390aba8 | |||
| 4569b76dd5 | |||
| 1c410cbf5a | |||
| 5f602ad86e | |||
| 9da9c77e61 | |||
| 8ed05a4201 | |||
| a8533c1010 | |||
| 87915f70e2 | |||
| ec07cb9524 | |||
| d4ae3ac548 | |||
| 1b4426e3ba | |||
| 32245b1d92 | |||
| 030bcd6d4f | |||
| f1628fb1e0 | |||
| 7c1a4a9e08 | |||
| 3b1c70911f | |||
| 70fc084864 | |||
| d9152e856b | |||
| 10dba4b9a0 | |||
| 5e51445118 | |||
| f4b7f86eb0 | |||
| 07308a4e04 | |||
| 8cca198af1 | |||
| efea4c6d05 | |||
| 588d9b97eb | |||
| 4f77a371f4 | |||
| 94dd1eed54 | |||
| 09b91974a7 | |||
| 99611fad34 | |||
| 1b026cc805 | |||
| 4c0d87080a | |||
| a09eb96982 | |||
| 5531d54554 | |||
| b73d267a5f | |||
| 5add0e4f70 | |||
| 7040906473 | |||
| 131aad24c0 | |||
| 64870c733b | |||
| 68ca6a0a0d | |||
| 376010722e | |||
| 31cf907b48 | |||
| 28af52dfab | |||
| 9e7e77bdf8 | |||
| d0385a90ea | |||
| f9c4af301d | |||
| 3cf4b7b3e4 | |||
| 8523df58b0 | |||
| bd2c2bfe46 | |||
| f5a70da6f0 | |||
| a8186b15cf | |||
| fe088ba16a | |||
| 562a047b9e | |||
| 634c995a71 | |||
| 153faefaf9 | |||
| 636a8a8286 | |||
| faada2de30 | |||
| e762efa97e | |||
| a2c9c21cfb | |||
| ea4f99e5ac | |||
| b46ac9080c | |||
| 24a463b18d | |||
| 3d28a04ffa | |||
| 326542644d | |||
| 45f70bb783 | |||
| a227ed7586 | |||
| 5288666bfc | |||
| 0396aac11f | |||
| 5db163a328 | |||
| 4d12e025e4 | |||
| be20148518 | |||
| 756347a716 | |||
| 7379627fcb | |||
| 4b02d3ff61 | |||
| dc6836dc2d | |||
| a2c4caf7ad | |||
| e8b6fb919e | |||
| 2087426afc | |||
| b703ca0ebf | |||
| 601c877136 | |||
| 6c609cb7d2 | |||
| 31d7110bba | |||
| ad522e6ab1 | |||
| e59f242f19 | |||
| bae625a894 | |||
| b5532ee4a3 | |||
| 31b4b7a372 | |||
| 1a1ed56419 | |||
| 13be8d5139 | |||
| 312901ff68 | |||
| 346b6007ca | |||
| a8df77243c | |||
| f30641da44 | |||
| 049531e495 | |||
| e8594ccec4 | |||
| 65ab436da2 | |||
| e7bd8026d2 | |||
| d9e731c84a | |||
| 8e1e5a3760 | |||
| 02594e3f7a | |||
| da5bf6c74c | |||
| 5cd8f5952f | |||
| 1b8be75a76 | |||
| 235180cf03 | |||
| a13990155f | |||
| a72f11594d | |||
| 86d7a45a78 | |||
| a229d696d5 | |||
| ef6ca3853d | |||
| 9e86d5f5f1 | |||
| 3ef91c61d9 | |||
| 5bae58eeb1 | |||
| 6ce859774a | |||
| fa4a6e2d71 | |||
| 9ee54801a2 | |||
| 79578e08ab | |||
| 732e23b8ea | |||
| d51894103d | |||
| eb59196c02 | |||
| 142eedd705 | |||
| 77d269c6d9 | |||
| b23009a8f3 | |||
| 84de8c1bfd | |||
| 6a9faee109 | |||
| 4d6737230a | |||
| 362f696e3e | |||
| 677d3abe0e | |||
| c78f9137cc | |||
| d8d378e083 | |||
| 8cf9e1990b | |||
| e9f116a14e | |||
| b2253ab7ce | |||
| fc5dc46e73 | |||
| cb9d488a1e | |||
| 389b5c7891 | |||
| 6bf97f7a1a | |||
| d0639393ca | |||
| 8521114c08 | |||
| 43b5a9cfd0 | |||
| 4387e16496 | |||
| 6f3ebe8d0c | |||
| c972d9b022 | |||
| b1563fdfc0 | |||
| a2c41a2ae3 | |||
| 21623b9c37 | |||
| 2f3d74c047 | |||
| 2f45685a06 | |||
| 8577bf9a4c | |||
| 2234ff78c7 | |||
| fca6a7758b | |||
| 7dae04b8d4 | |||
| d7ee81418d | |||
| 04e4b917b0 | |||
| 2ee2efeb84 | |||
| 367e429162 | |||
| 8417dfd10e | |||
| 15d1c5f992 | |||
| e0b1a0cfa7 | |||
| cfd9baa0d6 | |||
| 1d80c35708 | |||
| ad88ace448 | |||
| a6d870bf4c | |||
| fa66689c07 | |||
| 905b28975e | |||
| 7e5f667b31 | |||
| f103e72c01 | |||
| 6cba6af743 | |||
| eeb44407e3 | |||
| 68df603512 | |||
| 087b52c9b4 | |||
| 18f7297968 | |||
| 3cd34e0b4d | |||
| 25e3e46166 | |||
| 2c84d69ff2 | |||
| 3fb28803b5 |
@@ -6,3 +6,9 @@ spec/fixtures/**/*.less text eol=lf
|
||||
spec/fixtures/**/*.css text eol=lf
|
||||
spec/fixtures/**/*.txt text eol=lf
|
||||
spec/fixtures/dir/**/* text eol=lf
|
||||
|
||||
# Git 1.7 does not support **/* patterns
|
||||
spec/fixtures/css.css text eol=lf
|
||||
spec/fixtures/sample.js text eol=lf
|
||||
spec/fixtures/sample.less text eol=lf
|
||||
spec/fixtures/sample.txt text eol=lf
|
||||
|
||||
@@ -11,6 +11,9 @@ propose changes to this document in a pull request.
|
||||
|
||||
## Submitting Issues
|
||||
|
||||
* Check the [debugging guide](https://atom.io/docs/latest/debugging) for tips
|
||||
on debugging. You might be able to find the cause of the problem and fix
|
||||
things yourself.
|
||||
* Include the version of Atom you are using and the OS.
|
||||
* Include screenshots and animated GIFs whenever possible; they are immensely
|
||||
helpful.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"harmony-collections": "~0.3.8",
|
||||
"json-front-matter": "~0.1.3",
|
||||
"legal-eagle": "~0.4.0",
|
||||
"minidump": "~0.6",
|
||||
"minidump": "~0.7",
|
||||
"read-package-json": "1.1.8",
|
||||
"normalize-package-data": "0.2.12",
|
||||
"rcedit": "~0.1.2",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{cp, isAtomPackage, mkdir, rm} = require('./task-helpers')(grunt)
|
||||
@@ -48,16 +49,22 @@ module.exports = (grunt) ->
|
||||
path.join('bootstrap', 'docs')
|
||||
path.join('bootstrap', 'examples')
|
||||
path.join('pegjs', 'examples')
|
||||
# Add .* to avoid matching hunspell_dictionaries.
|
||||
path.join('spellchecker', 'vendor', 'hunspell', '.*')
|
||||
path.join('plist', 'tests')
|
||||
path.join('xmldom', 'test')
|
||||
path.join('jasmine-reporters', 'ext')
|
||||
path.join('build', 'Release', 'obj.target')
|
||||
path.join('build', 'Release', 'obj')
|
||||
path.join('build', 'Release', '.deps')
|
||||
path.join('vendor', 'apm')
|
||||
path.join('resources', 'mac')
|
||||
path.join('resources', 'win')
|
||||
]
|
||||
ignoredPaths = ignoredPaths.map (ignoredPath) -> _.escapeRegExp(ignoredPath)
|
||||
|
||||
# Add .* to avoid matching hunspell_dictionaries.
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('spellchecker', 'vendor', 'hunspell') + path.sep)}.*"
|
||||
ignoredPaths.push "#{_.escapeRegExp(path.join('build', 'Release') + path.sep)}.*\\.pdb"
|
||||
|
||||
# Hunspell dictionaries are only not needed on OS X.
|
||||
if process.platform is 'darwin'
|
||||
ignoredPaths.push path.join('spellchecker', 'vendor', 'hunspell_dictionaries')
|
||||
|
||||
@@ -2,6 +2,8 @@ module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
grunt.registerTask 'codesign', 'Codesign the app', ->
|
||||
return unless process.platform is 'darwin'
|
||||
|
||||
done = @async()
|
||||
|
||||
if process.env.XCODE_KEYCHAIN
|
||||
|
||||
@@ -8,11 +8,17 @@ GitHub = require 'github-releases'
|
||||
request = require 'request'
|
||||
|
||||
grunt = null
|
||||
maxReleases = 10
|
||||
assets = [
|
||||
{assetName: 'atom-mac.zip', sourceName: 'Atom.app'}
|
||||
{assetName: 'atom-mac-symbols.zip', sourceName: 'Atom.breakpad.syms'}
|
||||
]
|
||||
|
||||
if process.platform is 'darwin'
|
||||
assets = [
|
||||
{assetName: 'atom-mac.zip', sourceName: 'Atom.app'}
|
||||
{assetName: 'atom-mac-symbols.zip', sourceName: 'Atom.breakpad.syms'}
|
||||
]
|
||||
else
|
||||
assets = [
|
||||
{assetName: 'atom-windows.zip', sourceName: 'Atom'}
|
||||
]
|
||||
|
||||
commitSha = process.env.JANKY_SHA1
|
||||
token = process.env.ATOM_ACCESS_TOKEN
|
||||
defaultHeaders =
|
||||
@@ -23,7 +29,6 @@ module.exports = (gruntObject) ->
|
||||
grunt = gruntObject
|
||||
|
||||
grunt.registerTask 'publish-build', 'Publish the built app', ->
|
||||
return unless process.platform is 'darwin'
|
||||
return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
|
||||
|
||||
done = @async()
|
||||
@@ -45,10 +50,13 @@ logError = (message, error, details) ->
|
||||
|
||||
zipApps = (buildDir, assets, callback) ->
|
||||
zip = (directory, sourceName, assetName, callback) ->
|
||||
if process.platform is 'win32'
|
||||
zipCommand = "C:/psmodules/7z.exe a -r #{assetName} #{sourceName}"
|
||||
else
|
||||
zipCommand = "zip -r --symlinks #{assetName} #{sourceName}"
|
||||
options = {cwd: directory, maxBuffer: Infinity}
|
||||
child_process.exec "zip -r --symlinks #{assetName} #{sourceName}", options, (error, stdout, stderr) ->
|
||||
if error?
|
||||
logError("Zipping #{sourceName} failed", error, stderr)
|
||||
child_process.exec zipCommand, options, (error, stdout, stderr) ->
|
||||
logError("Zipping #{sourceName} failed", error, stderr) if error?
|
||||
callback(error)
|
||||
|
||||
tasks = []
|
||||
|
||||
@@ -39,7 +39,8 @@ module.exports = (grunt) ->
|
||||
grunt.verbose.writeln "Launching #{path.basename(packagePath)} specs."
|
||||
spawn options, (error, results, code) ->
|
||||
if process.platform is 'win32'
|
||||
process.stderr.write(fs.readFileSync(path.join(packagePath, 'ci.log')))
|
||||
if error
|
||||
process.stderr.write(fs.readFileSync(path.join(packagePath, 'ci.log')))
|
||||
fs.unlinkSync(path.join(packagePath, 'ci.log'))
|
||||
|
||||
failedPackages.push path.basename(packagePath) if error
|
||||
@@ -77,7 +78,7 @@ module.exports = (grunt) ->
|
||||
|
||||
spawn options, (error, results, code) ->
|
||||
if process.platform is 'win32'
|
||||
process.stderr.write(fs.readFileSync('ci.log'))
|
||||
process.stderr.write(fs.readFileSync('ci.log')) if error
|
||||
fs.unlinkSync('ci.log')
|
||||
else
|
||||
# TODO: Restore concurrency on Windows
|
||||
@@ -104,9 +105,4 @@ module.exports = (grunt) ->
|
||||
failures.push "atom core" if coreSpecFailed
|
||||
|
||||
grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0
|
||||
|
||||
# TODO: Mark the build as green on Windows until specs pass.
|
||||
if process.platform is 'darwin'
|
||||
done(!coreSpecFailed and failedPackages.length == 0)
|
||||
else if process.platform is 'win32'
|
||||
done(true)
|
||||
done(!coreSpecFailed and failedPackages.length == 0)
|
||||
|
||||
@@ -5,8 +5,15 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
## Requirements
|
||||
|
||||
* OS with 64-bit or 32-bit architecture
|
||||
* C++ toolchain
|
||||
* on Ubuntu/Debian: `sudo apt-get install build-essential`
|
||||
* on Fedora: `sudo yum --assumeyes install make gcc gcc-c++ glibc-devel`
|
||||
* [node.js](http://nodejs.org/download/) v0.10.x
|
||||
* [npm](http://www.npmjs.org/) v1.4.x
|
||||
* [Ubuntu/Debian/Mint instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os)
|
||||
* [Fedora instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora)
|
||||
* [npm](http://www.npmjs.org/) v1.4.x
|
||||
* `npm` comes with node.js so no explicit installation is needed here.
|
||||
* You can check `npm` 1.4 or above is installed by running `npm -v`.
|
||||
* libgnome-keyring-dev
|
||||
* on Ubuntu/Debian: `sudo apt-get install libgnome-keyring-dev`
|
||||
* on Fedora: `sudo yum --assumeyes install libgnome-keyring-devel`
|
||||
@@ -14,7 +21,9 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses Python 2
|
||||
* This command may require `sudo` depending on how you have
|
||||
[configured npm](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
|
||||
|
||||
* Git
|
||||
* on Ubuntu/Debian: `sudo apt-get install git`
|
||||
* on Fedora: `sudo yum install git-core`
|
||||
|
||||
## Instructions
|
||||
|
||||
@@ -53,4 +62,5 @@ and restart Atom. If Atom now works fine, you can make this setting permanent:
|
||||
See also https://github.com/atom/atom/issues/2082.
|
||||
|
||||
### Linux build error reports in atom/atom
|
||||
* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues) to get a list of reports about build errors on Linux.
|
||||
* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues)
|
||||
to get a list of reports about build errors on Linux.
|
||||
|
||||
@@ -51,5 +51,13 @@ fix this, you probably need to fiddle with your system PATH.
|
||||
* 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.`
|
||||
|
||||
* These can occur because your home directory (`%USERPROFILE%`) has non-ASCII
|
||||
characters in it. This is a bug in [gyp](https://code.google.com/p/gyp/)
|
||||
which is used to build native node modules and there is no known workaround.
|
||||
* https://github.com/TooTallNate/node-gyp/issues/297
|
||||
* https://code.google.com/p/gyp/issues/detail?id=393
|
||||
|
||||
### Windows build error reports in atom/atom
|
||||
* Use [this search](https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues) to get a list of reports about build errors on Windows.
|
||||
|
||||
+8
-1
@@ -1,6 +1,13 @@
|
||||
# Debugging
|
||||
|
||||
Atom provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when [submitting issues].
|
||||
Atom provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when [submitting issues]:
|
||||
|
||||
* [Update to the latest version](#update-to-the-latest-version)
|
||||
* [Check Atom and package settings](#check-atom-and-package-settings)
|
||||
* [Check the keybindings](#check-the-keybindings)
|
||||
* [Check if the problem shows up in safe mode](#check-if-the-problem-shows-up-in-safe-mode)
|
||||
* [Check your config files](#check-your-config-files)
|
||||
* [Check for errors in the developer tools](#check-for-errors-in-the-developer-tools)
|
||||
|
||||
## Update to the latest version
|
||||
|
||||
|
||||
+27
-27
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.102.0",
|
||||
"version": "0.104.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -17,10 +17,10 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.13.0",
|
||||
"atomShellVersion": "0.13.1",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^0.26.0",
|
||||
"atom-keymap": "^0.27.0",
|
||||
"bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
|
||||
"clear-cut": "0.4.0",
|
||||
"coffee-script": "1.7.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^1.1",
|
||||
"git-utils": "^1.3",
|
||||
"grim": "0.10.0",
|
||||
"grim": "0.11.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-tagged": "^1.1.2",
|
||||
"less-cache": "0.12.0",
|
||||
@@ -48,15 +48,15 @@
|
||||
"react-atom-fork": "^0.10.0",
|
||||
"reactionary-atom-fork": "^0.9.0",
|
||||
"runas": "^0.5",
|
||||
"scandal": "0.15.2",
|
||||
"scandal": "0.15.3",
|
||||
"scoped-property-store": "^0.9.0",
|
||||
"scrollbar-style": "^0.4.0",
|
||||
"season": "^1.0.2",
|
||||
"semver": "1.1.4",
|
||||
"serializable": "^1",
|
||||
"space-pen": "3.3.0",
|
||||
"temp": "0.5.0",
|
||||
"text-buffer": "^2.2.2",
|
||||
"space-pen": "3.2.0",
|
||||
"temp": "0.7.0",
|
||||
"text-buffer": "^2.3.0",
|
||||
"theorist": "^1",
|
||||
"underscore-plus": "^1.4.1",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
@@ -69,45 +69,45 @@
|
||||
"base16-tomorrow-dark-theme": "0.16.0",
|
||||
"solarized-dark-syntax": "0.17.0",
|
||||
"solarized-light-syntax": "0.8.0",
|
||||
"archive-view": "0.31.0",
|
||||
"archive-view": "0.32.0",
|
||||
"autocomplete": "0.28.0",
|
||||
"autoflow": "0.17.0",
|
||||
"autosave": "0.13.0",
|
||||
"autosave": "0.14.0",
|
||||
"background-tips": "0.14.0",
|
||||
"bookmarks": "0.22.0",
|
||||
"bracket-matcher": "0.43.0",
|
||||
"command-palette": "0.21.0",
|
||||
"deprecation-cop": "0.6.0",
|
||||
"bookmarks": "0.24.0",
|
||||
"bracket-matcher": "0.46.0",
|
||||
"command-palette": "0.22.0",
|
||||
"deprecation-cop": "0.7.0",
|
||||
"dev-live-reload": "0.31.0",
|
||||
"exception-reporting": "0.18.0",
|
||||
"feedback": "0.33.0",
|
||||
"find-and-replace": "0.113.0",
|
||||
"find-and-replace": "0.117.0",
|
||||
"fuzzy-finder": "0.54.0",
|
||||
"git-diff": "0.28.0",
|
||||
"git-diff": "0.31.0",
|
||||
"go-to-line": "0.22.0",
|
||||
"grammar-selector": "0.27.0",
|
||||
"image-view": "0.34.0",
|
||||
"image-view": "0.35.0",
|
||||
"keybinding-resolver": "0.18.0",
|
||||
"link": "0.22.0",
|
||||
"markdown-preview": "0.74.0",
|
||||
"markdown-preview": "0.80.0",
|
||||
"metrics": "0.32.0",
|
||||
"open-on-github": "0.28.0",
|
||||
"package-generator": "0.30.0",
|
||||
"package-generator": "0.31.0",
|
||||
"release-notes": "0.32.0",
|
||||
"settings-view": "0.119.0",
|
||||
"settings-view": "0.127.0",
|
||||
"snippets": "0.45.0",
|
||||
"spell-check": "0.36.0",
|
||||
"spell-check": "0.37.0",
|
||||
"status-bar": "0.40.0",
|
||||
"styleguide": "0.29.0",
|
||||
"symbols-view": "0.55.0",
|
||||
"symbols-view": "0.56.0",
|
||||
"tabs": "0.41.0",
|
||||
"timecop": "0.19.0",
|
||||
"tree-view": "0.98.0",
|
||||
"tree-view": "0.103.0",
|
||||
"update-package-dependencies": "0.6.0",
|
||||
"welcome": "0.16.0",
|
||||
"whitespace": "0.22.0",
|
||||
"wrap-guide": "0.18.0",
|
||||
"language-c": "0.16.0",
|
||||
"language-c": "0.18.0",
|
||||
"language-coffee-script": "0.22.0",
|
||||
"language-css": "0.17.0",
|
||||
"language-gfm": "0.39.0",
|
||||
@@ -116,7 +116,7 @@
|
||||
"language-html": "0.22.0",
|
||||
"language-hyperlink": "0.10.0",
|
||||
"language-java": "0.10.0",
|
||||
"language-javascript": "0.26.0",
|
||||
"language-javascript": "0.27.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.9.0",
|
||||
"language-make": "0.10.0",
|
||||
@@ -125,7 +125,7 @@
|
||||
"language-php": "0.15.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.18.0",
|
||||
"language-ruby": "0.27.0",
|
||||
"language-ruby": "0.29.0",
|
||||
"language-ruby-on-rails": "0.14.0",
|
||||
"language-sass": "0.13.0",
|
||||
"language-shellscript": "0.8.0",
|
||||
@@ -135,7 +135,7 @@
|
||||
"language-todo": "0.10.0",
|
||||
"language-toml": "0.12.0",
|
||||
"language-xml": "0.14.0",
|
||||
"language-yaml": "0.6.0"
|
||||
"language-yaml": "0.7.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+10
-10
@@ -10,6 +10,8 @@ function executeCommands(commands, done, index) {
|
||||
index = (index == undefined ? 0 : index);
|
||||
if (index < commands.length) {
|
||||
var command = commands[index];
|
||||
if (command.message)
|
||||
console.log(command.message);
|
||||
var options = null;
|
||||
if (typeof command !== 'string') {
|
||||
options = command.options;
|
||||
@@ -24,9 +26,9 @@ function executeCommands(commands, done, index) {
|
||||
function bootstrap() {
|
||||
var apmInstallPath = path.resolve(__dirname, '..', 'apm');
|
||||
if (!fs.existsSync(apmInstallPath))
|
||||
fs.mkdirSync(apmInstallPath);
|
||||
fs.mkdirSync(apmInstallPath);
|
||||
if (!fs.existsSync(path.join(apmInstallPath, 'node_modules')))
|
||||
fs.mkdirSync(path.join(apmInstallPath, 'node_modules'));
|
||||
fs.mkdirSync(path.join(apmInstallPath, 'node_modules'));
|
||||
|
||||
var apmPath = path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager', 'bin', 'apm')
|
||||
var apmFlags = process.env.JANKY_SHA1 || process.argv.indexOf('--no-color') !== -1 ? '--no-color' : '';
|
||||
@@ -35,16 +37,14 @@ function bootstrap() {
|
||||
var initialNpmCommand = fs.existsSync(npmPath) ? npmPath : 'npm';
|
||||
var npmFlags = ' --userconfig=' + path.resolve('.npmrc') + ' ';
|
||||
|
||||
var packagesToDedupe = ['fs-plus', 'humanize-plus', 'oniguruma', 'roaster', 'season'];
|
||||
var echoNewLine = process.platform == 'win32' ? 'echo.' : 'echo';
|
||||
var packagesToDedupe = ['fs-plus', 'humanize-plus', 'oniguruma', 'roaster', 'season', 'grim'];
|
||||
|
||||
var commands = [
|
||||
{command: initialNpmCommand + npmFlags + 'install --quiet', options: {cwd: path.resolve(__dirname, '..', 'build'), ignoreStdout: true}},
|
||||
{command: npmPath + npmFlags + 'install --quiet', options: {cwd: apmInstallPath, ignoreStdout: true}},
|
||||
echoNewLine,
|
||||
apmPath + ' clean ' + apmFlags,
|
||||
apmPath + ' install --quiet ' + apmFlags,
|
||||
apmPath + ' dedupe --quiet ' + apmFlags + ' ' + packagesToDedupe.join(' '),
|
||||
{command: initialNpmCommand + npmFlags + 'install --quiet', message: 'Installing build modules...', options: {cwd: path.resolve(__dirname, '..', 'build'), ignoreStdout: true}},
|
||||
{command: npmPath + npmFlags + 'install --quiet', message: 'Installing apm...', options: {cwd: apmInstallPath, ignoreStdout: true}},
|
||||
apmPath + ' clean ' + apmFlags,
|
||||
apmPath + ' install --quiet ' + apmFlags,
|
||||
apmPath + ' dedupe --quiet ' + apmFlags + ' ' + packagesToDedupe.join(' '),
|
||||
];
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
+9
-3
@@ -19,12 +19,18 @@ function loadEnvironmentVariables(filePath) {
|
||||
var value = parts[1].trim().substr(1, parts[1].length - 2);
|
||||
process.env[key] = value;
|
||||
}
|
||||
} catch(error) { }
|
||||
} catch(error) {
|
||||
console.error("Failed to load environment variables: " + filePath, error.code);
|
||||
}
|
||||
}
|
||||
|
||||
function readEnvironmentVariables() {
|
||||
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials')
|
||||
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain')
|
||||
if (process.platform === 'win32')
|
||||
loadEnvironmentVariables(path.resolve('/jenkins/config/atomcredentials'));
|
||||
else {
|
||||
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials');
|
||||
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain');
|
||||
}
|
||||
}
|
||||
|
||||
readEnvironmentVariables();
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env coffee
|
||||
|
||||
path = require 'path'
|
||||
CommandInstaller = require '../src/command-installer'
|
||||
|
||||
callback = (error) ->
|
||||
console.warn error.message if error?
|
||||
|
||||
CommandInstaller.installAtomCommand(path.resolve(__dirname, '..'), callback)
|
||||
CommandInstaller.installApmCommand(path.resolve(__dirname, '..'), callback)
|
||||
@@ -11,8 +11,15 @@ module.exports = function(cb) {
|
||||
return;
|
||||
}
|
||||
|
||||
verifyPython27(function(error, pythonSuccessMessage) {
|
||||
cb(error, (nodeSuccessMessage + "\n" + pythonSuccessMessage).trim());
|
||||
verifyNpm(function(error, npmSuccessMessage) {
|
||||
if (error) {
|
||||
cb(error);
|
||||
return;
|
||||
}
|
||||
|
||||
verifyPython27(function(error, pythonSuccessMessage) {
|
||||
cb(error, (nodeSuccessMessage + "\n" + npmSuccessMessage + "\n" + pythonSuccessMessage).trim());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +39,31 @@ function verifyNode(cb) {
|
||||
}
|
||||
}
|
||||
|
||||
function verifyNpm(cb) {
|
||||
var localNpmPath = path.resolve(__dirname, '..', '..', 'build', 'node_modules', '.bin', 'npm');
|
||||
if (process.platform === 'win32')
|
||||
localNpmPath += ".cmd";
|
||||
|
||||
var npmCommand = fs.existsSync(localNpmPath) ? localNpmPath : 'npm';
|
||||
if (npmCommand === 'npm' && process.platform === 'win32')
|
||||
npmCommand += ".cmd";
|
||||
|
||||
childProcess.execFile(npmCommand, ['-v'], { env: process.env }, function(err, stdout) {
|
||||
if (err)
|
||||
return cb("npm 1.4 is required to build Atom. An error (" + err + ") occured when checking the version.");
|
||||
|
||||
var npmVersion = stdout ? stdout.trim() : '';
|
||||
var versionArray = npmVersion.split('.');
|
||||
var npmMajorVersion = +versionArray[0] || 0;
|
||||
var npmMinorVersion = +versionArray[1] || 0;
|
||||
if (npmMajorVersion === 1 && npmMinorVersion < 4)
|
||||
cb("npm v1.4+ is required to build Atom.");
|
||||
else
|
||||
cb(null, "npm: v" + npmVersion);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function verifyPython27(cb) {
|
||||
if (process.platform == 'win32') {
|
||||
if (!pythonExecutable) {
|
||||
|
||||
@@ -2,6 +2,7 @@ path = require 'path'
|
||||
_ = require 'underscore-plus'
|
||||
{convertStackTrace} = require 'coffeestack'
|
||||
{View, $, $$} = require '../src/space-pen-extensions'
|
||||
grim = require 'grim'
|
||||
|
||||
sourceMaps = {}
|
||||
formatStackTrace = (spec, message='', stackTrace) ->
|
||||
@@ -52,6 +53,11 @@ class AtomReporter extends View
|
||||
@div outlet: "message", class: 'message'
|
||||
@div outlet: "results", class: 'results'
|
||||
|
||||
@div outlet: "deprecations", class: 'status alert alert-warning', style: 'display: none', =>
|
||||
@span outlet: 'deprecationStatus', '0 deprecations'
|
||||
@div class: 'deprecation-toggle'
|
||||
@div outlet: 'deprecationList', class: 'deprecation-list'
|
||||
|
||||
startedAt: null
|
||||
runningSpecCount: 0
|
||||
completeSpecCount: 0
|
||||
@@ -59,6 +65,7 @@ class AtomReporter extends View
|
||||
failedCount: 0
|
||||
skippedCount: 0
|
||||
totalSpecCount: 0
|
||||
deprecationCount: 0
|
||||
@timeoutId: 0
|
||||
|
||||
reportRunnerStarting: (runner) ->
|
||||
@@ -88,6 +95,29 @@ class AtomReporter extends View
|
||||
reportSpecStarting: (spec) ->
|
||||
@specStarted(spec)
|
||||
|
||||
addDeprecations: (spec) ->
|
||||
deprecations = grim.getDeprecations()
|
||||
@deprecationCount += deprecations.length
|
||||
@deprecations.show() if @deprecationCount > 0
|
||||
if @deprecationCount is 1
|
||||
@deprecationStatus.text("1 deprecation")
|
||||
else
|
||||
@deprecationStatus.text("#{@deprecationCount} deprecations")
|
||||
|
||||
for deprecation in deprecations
|
||||
@deprecationList.append $$ ->
|
||||
@div class: 'padded', =>
|
||||
@div class: 'result-message fail deprecation-message', deprecation.message
|
||||
|
||||
for stack in deprecation.stacks
|
||||
fullStack = stack.map ({functionName, location}) ->
|
||||
if functionName is '<unknown>'
|
||||
" at #{location}"
|
||||
else
|
||||
" at #{functionName} (#{location})"
|
||||
@pre class: 'stack-trace padded', formatStackTrace(spec, deprecation.message, fullStack.join('\n'))
|
||||
grim.clearDeprecations()
|
||||
|
||||
handleEvents: ->
|
||||
$(document).on "click", ".spec-toggle", ({currentTarget}) =>
|
||||
element = $(currentTarget)
|
||||
@@ -96,6 +126,13 @@ class AtomReporter extends View
|
||||
element.toggleClass('folded')
|
||||
false
|
||||
|
||||
$(document).on "click", ".deprecation-toggle", ({currentTarget}) =>
|
||||
element = $(currentTarget)
|
||||
deprecationList = $(document).find('.deprecation-list')
|
||||
deprecationList.toggle()
|
||||
element.toggleClass('folded')
|
||||
false
|
||||
|
||||
updateSpecCounts: ->
|
||||
if @skippedCount
|
||||
specCount = "#{@completeSpecCount - @skippedCount}/#{@totalSpecCount - @skippedCount} (#{@skippedCount} skipped)"
|
||||
@@ -175,6 +212,7 @@ class AtomReporter extends View
|
||||
specView = new SpecResultView(spec)
|
||||
specView.attach()
|
||||
@failedCount++
|
||||
@addDeprecations(spec)
|
||||
|
||||
class SuiteResultView extends View
|
||||
@content: ->
|
||||
|
||||
@@ -165,7 +165,7 @@ describe "EditorComponent", ->
|
||||
beforeEach ->
|
||||
editor.setText "a line that wraps "
|
||||
editor.setSoftWrap(true)
|
||||
node.style.width = 15 * charWidth + 'px'
|
||||
node.style.width = 16 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
it "doesn't show end of line invisibles at the end of wrapped lines", ->
|
||||
@@ -227,7 +227,19 @@ describe "EditorComponent", ->
|
||||
else
|
||||
[node]
|
||||
|
||||
describe "when the buffer contains null bytes", ->
|
||||
it "excludes the null byte from character measurement", ->
|
||||
editor.setText("a\0b")
|
||||
expect(editor.pixelPositionForScreenPosition([0, Infinity]).left).toEqual 2 * charWidth
|
||||
|
||||
describe "gutter rendering", ->
|
||||
[lineNumberHasClass, gutter] = []
|
||||
|
||||
beforeEach ->
|
||||
{gutter} = component.refs
|
||||
lineNumberHasClass = (screenRow, klass) ->
|
||||
component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
|
||||
it "renders the currently-visible line numbers", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
@@ -302,6 +314,267 @@ describe "EditorComponent", ->
|
||||
expect(component.lineNumberNodeForScreenRow(9).textContent).toBe "10"
|
||||
expect(gutterNode.offsetWidth).toBe initialGutterWidth
|
||||
|
||||
describe "fold decorations", ->
|
||||
describe "rendering fold decorations", ->
|
||||
it "adds the foldable class to line numbers when the line is foldable", ->
|
||||
expect(lineNumberHasClass(0, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(1, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(2, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(3, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(4, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(5, 'foldable')).toBe false
|
||||
|
||||
it "updates the foldable class on the correct line numbers when the foldable positions change", ->
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(lineNumberHasClass(0, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(1, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(2, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(3, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(4, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(5, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(6, 'foldable')).toBe false
|
||||
|
||||
it "updates the foldable class on a line number that becomes foldable", ->
|
||||
expect(lineNumberHasClass(11, 'foldable')).toBe false
|
||||
|
||||
editor.getBuffer().insert([11, 44], '\n fold me')
|
||||
expect(lineNumberHasClass(11, 'foldable')).toBe true
|
||||
|
||||
editor.undo()
|
||||
expect(lineNumberHasClass(11, 'foldable')).toBe false
|
||||
|
||||
it "adds, updates and removes the folded class on the correct line number nodes", ->
|
||||
editor.foldBufferRow(4)
|
||||
expect(lineNumberHasClass(4, 'folded')).toBe true
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(lineNumberHasClass(4, 'folded')).toBe false
|
||||
expect(lineNumberHasClass(5, 'folded')).toBe true
|
||||
|
||||
editor.unfoldBufferRow(5)
|
||||
expect(lineNumberHasClass(5, 'folded')).toBe false
|
||||
|
||||
describe "mouse interactions with fold indicators", ->
|
||||
[gutterNode] = []
|
||||
|
||||
buildClickEvent = (target) ->
|
||||
buildMouseEvent('click', {target})
|
||||
|
||||
beforeEach ->
|
||||
gutterNode = node.querySelector('.gutter')
|
||||
|
||||
it "folds and unfolds the block represented by the fold indicator when clicked", ->
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
target = lineNumber.querySelector('.icon-right')
|
||||
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe true
|
||||
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
target = lineNumber.querySelector('.icon-right')
|
||||
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||
|
||||
it "does not fold when the line number node is clicked", ->
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
lineNumber.dispatchEvent(buildClickEvent(lineNumber))
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||
|
||||
describe "cursor-line decorations", ->
|
||||
cursor = null
|
||||
beforeEach ->
|
||||
cursor = editor.getCursor()
|
||||
|
||||
it "modifies the cursor-line decoration when the cursor moves", ->
|
||||
cursor.setScreenPosition([0, 0])
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe true
|
||||
|
||||
cursor.setScreenPosition([1, 0])
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(1, 'cursor-line')).toBe true
|
||||
|
||||
it "updates cursor-line decorations for multiple cursors", ->
|
||||
cursor.setScreenPosition([2, 0])
|
||||
cursor2 = editor.addCursorAtScreenPosition([8, 0])
|
||||
cursor3 = editor.addCursorAtScreenPosition([10, 0])
|
||||
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(8, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe true
|
||||
|
||||
cursor2.destroy()
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(8, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe true
|
||||
|
||||
cursor3.destroy()
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(8, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe false
|
||||
|
||||
it "adds cursor-line decorations to multiple lines when a selection is performed", ->
|
||||
cursor.setScreenPosition([1, 0])
|
||||
editor.selectDown(2)
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(1, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(3, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(4, 'cursor-line')).toBe false
|
||||
|
||||
describe "when decorations are used", ->
|
||||
describe "when decorations are applied to buffer rows", ->
|
||||
it "renders line number classes based on the decorations on their buffer row", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
expect(component.lineNumberNodeForScreenRow(9)).not.toBeDefined()
|
||||
|
||||
editor.addDecorationToBufferRow(9, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationToBufferRow(9, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
expect(lineNumberHasClass(9, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(9, 'nope-class')).toBe false
|
||||
|
||||
it "renders updates to gutter decorations", ->
|
||||
editor.addDecorationToBufferRow(2, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationToBufferRow(2, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(2, 'nope-class')).toBe false
|
||||
|
||||
editor.removeDecorationFromBufferRow(2, type: 'gutter', class: 'fancy-class')
|
||||
editor.removeDecorationFromBufferRow(2, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'fancy-class')).toBe false
|
||||
expect(lineNumberHasClass(2, 'nope-class')).toBe false
|
||||
|
||||
it "renders decorations on soft-wrapped line numbers when softWrap is true", ->
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'wrap-me', softWrap: true)
|
||||
|
||||
editor.setSoftWrap(true)
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 30 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe true
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe true
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe true
|
||||
|
||||
# should remove the wrapped decorations
|
||||
editor.removeDecorationFromBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.removeDecorationFromBufferRow(1, type: 'gutter', class: 'wrap-me')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe false
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe false
|
||||
|
||||
# should add them back when the nodes are not recreated
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'wrap-me', softWrap: true)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe true
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe true
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe true
|
||||
|
||||
describe "when decorations are applied to markers", ->
|
||||
{marker, decoration} = {}
|
||||
beforeEach ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], class: 'my-marker', invalidate: 'inside')
|
||||
decoration = {type: 'gutter', class: 'someclass'}
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
|
||||
it "updates line number classes when the marker moves", ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(5, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().deleteRows(0, 1)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(0, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes line number classes when a decoration's marker is invalidated", ->
|
||||
editor.getBuffer().insert([3, 2], 'n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
|
||||
expect(marker.isValid()).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().undo()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe true
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
it "removes the classes and unsubscribes from the marker when decoration is removed", ->
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes the line number classes when the decoration's marker is destroyed", ->
|
||||
marker.destroy()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
describe "cursor rendering", ->
|
||||
it "renders the currently visible cursors, translated relative to the scroll position", ->
|
||||
cursor1 = editor.getCursor()
|
||||
@@ -357,6 +630,16 @@ describe "EditorComponent", ->
|
||||
expect(cursorRect.left).toBe rangeRect.left
|
||||
expect(cursorRect.width).toBe rangeRect.width
|
||||
|
||||
it "sets the cursor to the default character width at the end of a line", ->
|
||||
editor.setCursorScreenPosition([0, Infinity])
|
||||
cursorNode = node.querySelector('.cursor')
|
||||
expect(cursorNode.offsetWidth).toBe charWidth
|
||||
|
||||
it "gives the cursor a non-zero width even if it's inside atomic tokens", ->
|
||||
editor.setCursorScreenPosition([1, 0])
|
||||
cursorNode = node.querySelector('.cursor')
|
||||
expect(cursorNode.offsetWidth).toBe charWidth
|
||||
|
||||
it "blinks cursors when they aren't moving", ->
|
||||
spyOn(_._, 'now').andCallFake -> window.now # Ensure _.debounce is based on our fake spec timeline
|
||||
cursorsNode = node.querySelector('.cursors')
|
||||
@@ -630,12 +913,6 @@ describe "EditorComponent", ->
|
||||
clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop()
|
||||
{clientX, clientY}
|
||||
|
||||
buildMouseEvent = (type, properties...) ->
|
||||
properties = extend({bubbles: true, cancelable: true}, properties...)
|
||||
event = new MouseEvent(type, properties)
|
||||
Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
|
||||
event
|
||||
|
||||
describe "focus handling", ->
|
||||
inputNode = null
|
||||
|
||||
@@ -651,8 +928,26 @@ describe "EditorComponent", ->
|
||||
expect(document.activeElement).toBe document.body
|
||||
inputNode.focus()
|
||||
expect(node.classList.contains('is-focused')).toBe true
|
||||
expect(wrapperView.hasClass('is-focused')).toBe true
|
||||
inputNode.blur()
|
||||
expect(node.classList.contains('is-focused')).toBe false
|
||||
expect(wrapperView.hasClass('is-focused')).toBe false
|
||||
|
||||
describe "selection handling", ->
|
||||
cursor = null
|
||||
|
||||
beforeEach ->
|
||||
cursor = editor.getCursor()
|
||||
cursor.setScreenPosition([0, 0])
|
||||
|
||||
it "adds the 'has-selection' class to the editor when there is a selection", ->
|
||||
expect(node.classList.contains('has-selection')).toBe false
|
||||
|
||||
editor.selectDown()
|
||||
expect(node.classList.contains('has-selection')).toBe true
|
||||
|
||||
cursor.moveDown()
|
||||
expect(node.classList.contains('has-selection')).toBe false
|
||||
|
||||
describe "scrolling", ->
|
||||
it "updates the vertical scrollbar when the scrollTop is changed in the model", ->
|
||||
@@ -787,24 +1082,47 @@ describe "EditorComponent", ->
|
||||
|
||||
expect(horizontalScrollbarNode.scrollWidth).toBe gutterNode.offsetWidth + editor.getScrollWidth()
|
||||
|
||||
describe "when a mousewheel event occurs on the editor", ->
|
||||
|
||||
describe "mousewheel events", ->
|
||||
it "updates the scrollLeft or scrollTop on mousewheel events depending on which delta is greater (x or y)", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 20 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
beforeEach ->
|
||||
atom.config.set('editor.scrollSensitivity', 100)
|
||||
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 0
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
describe "updating scrollTop and scrollLeft", ->
|
||||
beforeEach ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 20 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
it "updates the scrollLeft or scrollTop on mousewheel events depending on which delta is greater (x or y)", ->
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 0
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 15
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 15
|
||||
|
||||
it "updates the scrollLeft or scrollTop according to the scroll sensitivity", ->
|
||||
atom.config.set('editor.scrollSensitivity', 50)
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 5
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 5
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 7
|
||||
|
||||
it "uses the previous scrollSensitivity when the value is not an int", ->
|
||||
atom.config.set('editor.scrollSensitivity', 'nope')
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -10))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
|
||||
it "parses negative scrollSensitivity values as positive", ->
|
||||
atom.config.set('editor.scrollSensitivity', -50)
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -10))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 5
|
||||
|
||||
describe "when the mousewheel event's target is a line", ->
|
||||
it "keeps the line on the DOM if it is scrolled off-screen", ->
|
||||
@@ -881,24 +1199,33 @@ describe "EditorComponent", ->
|
||||
beforeEach ->
|
||||
inputNode = node.querySelector('.hidden-input')
|
||||
|
||||
buildTextInputEvent = ({data, target}) ->
|
||||
event = new Event('textInput')
|
||||
event.data = data
|
||||
Object.defineProperty(event, 'target', get: -> target)
|
||||
event
|
||||
|
||||
it "inserts the newest character in the input's value into the buffer", ->
|
||||
inputNode.value = 'x'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
node.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'xvar quicksort = function () {'
|
||||
|
||||
inputNode.value = 'xy'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
node.dispatchEvent(buildTextInputEvent(data: 'y', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'xyvar quicksort = function () {'
|
||||
|
||||
it "replaces the last character if the length of the input's value doesn't increase, as occurs with the accented character menu", ->
|
||||
inputNode.value = 'u'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
node.dispatchEvent(buildTextInputEvent(data: 'u', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'uvar quicksort = function () {'
|
||||
|
||||
inputNode.value = 'ü'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
# simulate the accented character suggestion's selection of the previous character
|
||||
inputNode.setSelectionRange(0, 1)
|
||||
node.dispatchEvent(buildTextInputEvent(data: 'ü', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'üvar quicksort = function () {'
|
||||
|
||||
it "does not handle input events when input is disabled", ->
|
||||
component.setInputEnabled(false)
|
||||
node.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
|
||||
|
||||
describe "commands", ->
|
||||
describe "editor:consolidate-selections", ->
|
||||
it "consolidates selections on the editor model, aborting the key binding if there is only one selection", ->
|
||||
@@ -940,3 +1267,12 @@ describe "EditorComponent", ->
|
||||
editor.setCursorBufferPosition([0, Infinity])
|
||||
wrapperView.show()
|
||||
expect(node.querySelector('.cursor').style['-webkit-transform']).toBe "translate3d(#{9 * charWidth}px, 0px, 0px)"
|
||||
|
||||
buildMouseEvent = (type, properties...) ->
|
||||
properties = extend({bubbles: true, cancelable: true}, properties...)
|
||||
event = new MouseEvent(type, properties)
|
||||
Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
|
||||
if properties.target?
|
||||
Object.defineProperty(event, 'target', get: -> properties.target)
|
||||
Object.defineProperty(event, 'srcObject', get: -> properties.target)
|
||||
event
|
||||
|
||||
+151
-2
@@ -863,6 +863,30 @@ describe "Editor", ->
|
||||
expect(selection1.getScreenRange()).toEqual [[3, 0], [4, 5]]
|
||||
expect(selection2.getScreenRange()).toEqual [[5, 6], [6, 2]]
|
||||
|
||||
describe ".selectToBeginningOfNextParagraph()", ->
|
||||
it "selects from the cursor to first line of the next paragraph", ->
|
||||
editor.setSelectedBufferRange([[3, 0], [4, 5]])
|
||||
editor.addCursorAtScreenPosition([5, 6])
|
||||
editor.selectToScreenPosition([6, 2])
|
||||
|
||||
editor.selectToBeginningOfNextParagraph()
|
||||
|
||||
selections = editor.getSelections()
|
||||
expect(selections.length).toBe 1
|
||||
expect(selections[0].getScreenRange()).toEqual [[3, 0], [10, 0]]
|
||||
|
||||
describe ".selectToBeginningOfPreviousParagraph()", ->
|
||||
it "selects from the cursor to the first line of the pevious paragraph", ->
|
||||
editor.setSelectedBufferRange([[3, 0], [4, 5]])
|
||||
editor.addCursorAtScreenPosition([5, 6])
|
||||
editor.selectToScreenPosition([6, 2])
|
||||
|
||||
editor.selectToBeginningOfPreviousParagraph()
|
||||
|
||||
selections = editor.getSelections()
|
||||
expect(selections.length).toBe 1
|
||||
expect(selections[0].getScreenRange()).toEqual [[0, 0], [5, 6]]
|
||||
|
||||
it "merges selections if they intersect, maintaining the directionality of the last selection", ->
|
||||
editor.setCursorScreenPosition([4, 10])
|
||||
editor.selectToScreenPosition([5, 27])
|
||||
@@ -1622,7 +1646,7 @@ describe "Editor", ->
|
||||
editor.setCursorBufferPosition([9,2])
|
||||
editor.insertNewline()
|
||||
expect(editor.lineForBufferRow(10)).toBe ' };'
|
||||
|
||||
|
||||
describe ".backspace()", ->
|
||||
describe "when there is a single cursor", ->
|
||||
changeScreenRangeHandler = null
|
||||
@@ -2234,6 +2258,15 @@ describe "Editor", ->
|
||||
editor.outdentSelectedRows()
|
||||
expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
|
||||
|
||||
it "outdents only up to the first non-space non-tab character", ->
|
||||
editor.insertText(' \tfoo\t ')
|
||||
editor.outdentSelectedRows()
|
||||
expect(buffer.lineForRow(0)).toBe "\tfoo\t var quicksort = function () {"
|
||||
editor.outdentSelectedRows()
|
||||
expect(buffer.lineForRow(0)).toBe "foo\t var quicksort = function () {"
|
||||
editor.outdentSelectedRows()
|
||||
expect(buffer.lineForRow(0)).toBe "foo\t var quicksort = function () {"
|
||||
|
||||
describe "when one line is selected", ->
|
||||
it "outdents line and retains editor", ->
|
||||
editor.setSelectedBufferRange([[1,4], [1,14]])
|
||||
@@ -3187,21 +3220,137 @@ describe "Editor", ->
|
||||
expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
|
||||
|
||||
describe ".pageUp/Down()", ->
|
||||
it "scrolls one screen height up or down", ->
|
||||
it "scrolls one screen height up or down and moves the cursor one page length", ->
|
||||
editor.manageScrollPosition = true
|
||||
|
||||
editor.setLineHeightInPixels(10)
|
||||
editor.setHeight(50)
|
||||
expect(editor.getScrollHeight()).toBe 130
|
||||
expect(editor.getCursorBufferPosition().row).toBe 0
|
||||
|
||||
editor.pageDown()
|
||||
expect(editor.getScrollTop()).toBe 50
|
||||
expect(editor.getCursorBufferPosition().row).toBe 5
|
||||
|
||||
editor.pageDown()
|
||||
expect(editor.getScrollTop()).toBe 80
|
||||
expect(editor.getCursorBufferPosition().row).toBe 10
|
||||
|
||||
editor.pageUp()
|
||||
expect(editor.getScrollTop()).toBe 30
|
||||
expect(editor.getCursorBufferPosition().row).toBe 5
|
||||
|
||||
editor.pageUp()
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getCursorBufferPosition().row).toBe 0
|
||||
|
||||
describe "decorations", ->
|
||||
decoration = null
|
||||
beforeEach ->
|
||||
decoration = {type: 'gutter', class: 'one'}
|
||||
|
||||
it "can add decorations to buffer rows and remove them", ->
|
||||
editor.addDecorationToBufferRow(2, decoration)
|
||||
editor.addDecorationToBufferRow(2, decoration)
|
||||
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toHaveLength 1
|
||||
expect(decorations).toContain decoration
|
||||
|
||||
editor.removeDecorationFromBufferRow(2, decoration)
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toHaveLength 0
|
||||
|
||||
it "can add decorations to buffer row ranges and remove them", ->
|
||||
editor.addDecorationToBufferRowRange(2, 4, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
|
||||
editor.removeDecorationFromBufferRowRange(3, 5, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], class: 'my-marker', invalidate: 'inside')
|
||||
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
expect(editor.decorationsForBufferRow 1).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.getBuffer().insert([4, 2], 'n')
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.getBuffer().undo()
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
describe "decorationsForBufferRow", ->
|
||||
one = {type: 'one', class: 'one'}
|
||||
two = {type: 'two', class: 'two'}
|
||||
typeless = {class: 'typeless'}
|
||||
|
||||
beforeEach ->
|
||||
editor.addDecorationToBufferRow(2, one)
|
||||
editor.addDecorationToBufferRow(2, two)
|
||||
editor.addDecorationToBufferRow(2, typeless)
|
||||
|
||||
it "returns all decorations with no decorationType specified", ->
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toContain one
|
||||
expect(decorations).toContain two
|
||||
expect(decorations).toContain typeless
|
||||
|
||||
it "returns typeless decorations with all decorationTypes", ->
|
||||
decorations = editor.decorationsForBufferRow(2, 'one')
|
||||
expect(decorations).toContain one
|
||||
expect(decorations).not.toContain two
|
||||
expect(decorations).toContain typeless
|
||||
|
||||
describe "decorationsForBufferRowRange", ->
|
||||
one = {type: 'one', class: 'one'}
|
||||
two = {type: 'two', class: 'two'}
|
||||
typeless = {class: 'typeless'}
|
||||
|
||||
it "returns an object of decorations based on the decorationType", ->
|
||||
editor.addDecorationToBufferRow(2, one)
|
||||
editor.addDecorationToBufferRow(3, one)
|
||||
editor.addDecorationToBufferRow(5, one)
|
||||
|
||||
editor.addDecorationToBufferRow(3, two)
|
||||
editor.addDecorationToBufferRow(4, two)
|
||||
|
||||
editor.addDecorationToBufferRow(3, typeless)
|
||||
editor.addDecorationToBufferRow(5, typeless)
|
||||
|
||||
decorations = editor.decorationsForBufferRowRange(2, 5, 'one')
|
||||
expect(decorations[2]).toContain one
|
||||
|
||||
expect(decorations[3]).toContain one
|
||||
expect(decorations[3]).not.toContain two
|
||||
expect(decorations[3]).toContain typeless
|
||||
|
||||
expect(decorations[4]).toHaveLength 0
|
||||
|
||||
expect(decorations[5]).toContain one
|
||||
expect(decorations[5]).toContain typeless
|
||||
|
||||
@@ -10,6 +10,8 @@ describe "EditorView", ->
|
||||
[buffer, editorView, editor, cachedEditor, cachedLineHeight, cachedCharWidth, fart] = []
|
||||
|
||||
beforeEach ->
|
||||
atom.config.set 'core.useReactEditor', false
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js').then (o) -> editor = o
|
||||
|
||||
|
||||
@@ -52,12 +52,17 @@ describe "LanguageMode", ->
|
||||
languageMode.toggleLineCommentsForBufferRows(0, 0)
|
||||
expect(buffer.lineForRow(0)).toBe " // "
|
||||
|
||||
buffer.setText (' a\n \n b')
|
||||
buffer.setText(' a\n \n b')
|
||||
languageMode.toggleLineCommentsForBufferRows(0, 2)
|
||||
expect(buffer.lineForRow(0)).toBe " // a"
|
||||
expect(buffer.lineForRow(1)).toBe " // "
|
||||
expect(buffer.lineForRow(2)).toBe " // b"
|
||||
|
||||
buffer.setText(' \n // var i;')
|
||||
languageMode.toggleLineCommentsForBufferRows(0, 1)
|
||||
expect(buffer.lineForRow(0)).toBe ' '
|
||||
expect(buffer.lineForRow(1)).toBe ' var i;'
|
||||
|
||||
describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
|
||||
it "returns the start/end rows of the foldable region starting at the given row", ->
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 12]
|
||||
|
||||
@@ -16,6 +16,7 @@ describe "PaneContainer", ->
|
||||
containerA = new PaneContainer(root: pane1A)
|
||||
pane2A = pane1A.splitRight(items: [new Item])
|
||||
pane3A = pane2A.splitDown(items: [new Item])
|
||||
pane3A.focus()
|
||||
|
||||
it "preserves the focused pane across serialization", ->
|
||||
expect(pane3A.focused).toBe true
|
||||
|
||||
@@ -439,10 +439,11 @@ describe "Pane", ->
|
||||
expect(column.orientation).toBe 'vertical'
|
||||
expect(column.children).toEqual [pane1, pane3, pane2]
|
||||
|
||||
it "sets up the new pane to be focused", ->
|
||||
expect(pane1.focused).toBe false
|
||||
it "activates the new pane", ->
|
||||
expect(pane1.isActive()).toBe true
|
||||
pane2 = pane1.splitRight()
|
||||
expect(pane2.focused).toBe true
|
||||
expect(pane1.isActive()).toBe false
|
||||
expect(pane2.isActive()).toBe true
|
||||
|
||||
describe "::destroy()", ->
|
||||
[container, pane1, pane2] = []
|
||||
|
||||
@@ -199,6 +199,7 @@ describe "PaneView", ->
|
||||
|
||||
it "focuses the next pane", ->
|
||||
container.attachToDom()
|
||||
pane2.activate()
|
||||
expect(pane.hasFocus()).toBe false
|
||||
expect(pane2.hasFocus()).toBe true
|
||||
pane2Model.destroy()
|
||||
|
||||
@@ -24,19 +24,6 @@ describe "SpacePen extensions", ->
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
describe "tooltips", ->
|
||||
describe "humanizeKeystrokes", ->
|
||||
humanizeKeystrokes = $.fn.setTooltip.humanizeKeystrokes
|
||||
|
||||
it "replaces single keystroke", ->
|
||||
expect(humanizeKeystrokes('cmd-O')).toEqual '⌘⇧O'
|
||||
expect(humanizeKeystrokes('cmd-shift-up')).toEqual '⌘⇧↑'
|
||||
expect(humanizeKeystrokes('cmd-option-down')).toEqual '⌘⌥↓'
|
||||
expect(humanizeKeystrokes('cmd-option-left')).toEqual '⌘⌥←'
|
||||
expect(humanizeKeystrokes('cmd-option-right')).toEqual '⌘⌥→'
|
||||
|
||||
it "replaces multiple keystroke", ->
|
||||
expect(humanizeKeystrokes('cmd-o ctrl-2')).toEqual '⌘O ⌃2'
|
||||
|
||||
describe "when the window is resized", ->
|
||||
it "hides the tooltips", ->
|
||||
class TooltipView extends View
|
||||
|
||||
@@ -89,7 +89,7 @@ beforeEach ->
|
||||
config.set "editor.autoIndent", false
|
||||
config.set "core.disabledPackages", ["package-that-throws-an-exception",
|
||||
"package-with-broken-package-json", "package-with-broken-keymap"]
|
||||
config.set "core.useReactEditor", false
|
||||
config.set "core.useReactEditor", true
|
||||
config.save.reset()
|
||||
atom.config = config
|
||||
|
||||
|
||||
@@ -201,7 +201,9 @@ describe "WorkspaceView", ->
|
||||
expect(rightEditorView.find(".line:first").text()).toBe " "
|
||||
expect(leftEditorView.find(".line:first").text()).toBe " "
|
||||
|
||||
withInvisiblesShowing = "#{rightEditorView.invisibles.space}#{rightEditorView.invisibles.tab} #{rightEditorView.invisibles.space}#{rightEditorView.invisibles.eol}"
|
||||
{invisibles} = rightEditorView.component.state
|
||||
{space, tab, eol} = invisibles
|
||||
withInvisiblesShowing = "#{space}#{tab} #{space}#{eol}"
|
||||
|
||||
atom.workspaceView.trigger "window:toggle-invisibles"
|
||||
expect(rightEditorView.find(".line:first").text()).toBe withInvisiblesShowing
|
||||
|
||||
@@ -6,10 +6,11 @@ CursorComponent = React.createClass
|
||||
displayName: 'CursorComponent'
|
||||
|
||||
render: ->
|
||||
{editor, screenRange, scrollTop, scrollLeft} = @props
|
||||
{editor, screenRange, scrollTop, scrollLeft, defaultCharWidth} = @props
|
||||
{top, left, height, width} = editor.pixelRectForScreenRange(screenRange)
|
||||
top -= scrollTop
|
||||
left -= scrollLeft
|
||||
width = defaultCharWidth if width is 0
|
||||
WebkitTransform = "translate3d(#{left}px, #{top}px, 0px)"
|
||||
|
||||
div className: 'cursor', style: {height, width, WebkitTransform}
|
||||
|
||||
+2
-1
@@ -114,9 +114,10 @@ class Cursor extends Model
|
||||
|
||||
# Public: Get the RegExp used by the cursor to determine what a "word" is.
|
||||
#
|
||||
# options: An {Object} with the following keys:
|
||||
# options: An optional {Object} with the following keys:
|
||||
# :includeNonWordCharacters - A {Boolean} indicating whether to include
|
||||
# non-word characters in the regex.
|
||||
# (default: true)
|
||||
#
|
||||
# Returns a {RegExp}.
|
||||
wordRegExp: ({includeNonWordCharacters}={})->
|
||||
|
||||
@@ -12,7 +12,7 @@ CursorsComponent = React.createClass
|
||||
cursorBlinkIntervalHandle: null
|
||||
|
||||
render: ->
|
||||
{editor, cursorScreenRanges, scrollTop, scrollLeft} = @props
|
||||
{editor, cursorScreenRanges, scrollTop, scrollLeft, defaultCharWidth} = @props
|
||||
{blinkOff} = @state
|
||||
|
||||
className = 'cursors'
|
||||
@@ -21,7 +21,7 @@ CursorsComponent = React.createClass
|
||||
div {className},
|
||||
if @isMounted()
|
||||
for key, screenRange of cursorScreenRanges
|
||||
CursorComponent({key, editor, screenRange, scrollTop, scrollLeft})
|
||||
CursorComponent({key, editor, screenRange, scrollTop, scrollLeft, defaultCharWidth})
|
||||
|
||||
getInitialState: ->
|
||||
blinkOff: false
|
||||
|
||||
@@ -111,6 +111,34 @@ class DisplayBufferMarker
|
||||
setTailBufferPosition: (bufferPosition) ->
|
||||
@bufferMarker.setTailPosition(bufferPosition)
|
||||
|
||||
# Retrieves the screen position of the marker's start. This will always be
|
||||
# less than or equal to the result of {DisplayBufferMarker::getEndScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Retrieves the buffer position of the marker's start. This will always be
|
||||
# less than or equal to the result of {DisplayBufferMarker::getEndBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartBufferPosition: ->
|
||||
@bufferMarker.getStartPosition()
|
||||
|
||||
# Retrieves the screen position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {DisplayBufferMarker::getStartScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getEndBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Retrieves the buffer position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {DisplayBufferMarker::getStartBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndBufferPosition: ->
|
||||
@bufferMarker.getEndPosition()
|
||||
|
||||
# Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
|
||||
+100
-7
@@ -43,6 +43,8 @@ class DisplayBuffer extends Model
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@decorations = {}
|
||||
@decorationMarkerSubscriptions = {}
|
||||
@updateAllScreenLines()
|
||||
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
||||
@subscribe @tokenizedBuffer, 'grammar-changed', (grammar) => @emit 'grammar-changed', grammar
|
||||
@@ -228,8 +230,7 @@ class DisplayBuffer extends Model
|
||||
@charWidthsByScope = {}
|
||||
|
||||
getScrollHeight: ->
|
||||
unless @getLineHeightInPixels() > 0
|
||||
throw new Error("You must assign lineHeightInPixels before calling ::getScrollHeight()")
|
||||
return 0 unless @getLineHeightInPixels() > 0
|
||||
|
||||
@getLineCount() * @getLineHeightInPixels()
|
||||
|
||||
@@ -237,8 +238,7 @@ class DisplayBuffer extends Model
|
||||
(@getMaxLineLength() * @getDefaultCharWidth()) + @getCursorWidth()
|
||||
|
||||
getVisibleRowRange: ->
|
||||
unless @getLineHeightInPixels() > 0
|
||||
throw new Error("You must assign a non-zero lineHeightInPixels before calling ::getVisibleRowRange()")
|
||||
return [0, 0] unless @getLineHeightInPixels() > 0
|
||||
|
||||
heightInLines = Math.ceil(@getHeight() / @getLineHeightInPixels()) + 1
|
||||
startRow = Math.floor(@getScrollTop() / @getLineHeightInPixels())
|
||||
@@ -289,9 +289,9 @@ class DisplayBuffer extends Model
|
||||
height = (screenRange.end.row - screenRange.start.row + 1) * @getLineHeightInPixels()
|
||||
width = @getScrollWidth()
|
||||
else
|
||||
{top, left} = @pixelPositionForScreenPosition(screenRange.start)
|
||||
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
|
||||
height = @getLineHeightInPixels()
|
||||
width = @pixelPositionForScreenPosition(screenRange.end).left - left
|
||||
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
|
||||
|
||||
{top, left, width, height}
|
||||
|
||||
@@ -520,7 +520,7 @@ class DisplayBuffer extends Model
|
||||
charWidths = @getScopedCharWidths(token.scopes)
|
||||
for char in token.value
|
||||
return {top, left} if column is targetColumn
|
||||
left += charWidths[char] ? defaultCharWidth
|
||||
left += charWidths[char] ? defaultCharWidth unless char is '\0'
|
||||
column++
|
||||
{top, left}
|
||||
|
||||
@@ -718,6 +718,97 @@ class DisplayBuffer extends Model
|
||||
rangeForAllLines: ->
|
||||
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
|
||||
|
||||
decorationsForBufferRow: (bufferRow, decorationType) ->
|
||||
decorations = @decorations[bufferRow] ? []
|
||||
decorations = (dec for dec in decorations when not dec.type? or dec.type is decorationType) if decorationType?
|
||||
decorations
|
||||
|
||||
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
|
||||
decorations = {}
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
decorations[bufferRow] = @decorationsForBufferRow(bufferRow, decorationType)
|
||||
decorations
|
||||
|
||||
addDecorationToBufferRow: (bufferRow, decoration) ->
|
||||
@decorations[bufferRow] ?= []
|
||||
for current in @decorations[bufferRow]
|
||||
return if _.isEqual(current, decoration)
|
||||
@decorations[bufferRow].push(decoration)
|
||||
@emit 'decoration-changed', {bufferRow, decoration, action: 'add'}
|
||||
|
||||
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
|
||||
return unless decorations = @decorations[bufferRow]
|
||||
|
||||
removed = []
|
||||
i = decorations.length - 1
|
||||
while i >= 0
|
||||
if @decorationMatchesPattern(decorations[i], decorationPattern)
|
||||
removed.push decorations[i]
|
||||
decorations.splice(i, 1)
|
||||
i--
|
||||
|
||||
delete @decorations[bufferRow] unless @decorations[bufferRow]?
|
||||
|
||||
for decoration in removed
|
||||
@emit 'decoration-changed', {bufferRow, decoration, action: 'remove'}
|
||||
|
||||
removed
|
||||
|
||||
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
@addDecorationToBufferRow(bufferRow, decoration)
|
||||
return
|
||||
|
||||
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
@removeDecorationFromBufferRow(bufferRow, decoration)
|
||||
return
|
||||
|
||||
decorationMatchesPattern: (decoration, decorationPattern) ->
|
||||
return false unless decoration? and decorationPattern?
|
||||
for key, value of decorationPattern
|
||||
return false if decoration[key] != value
|
||||
true
|
||||
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
startRow = marker.getStartBufferPosition().row
|
||||
endRow = marker.getEndBufferPosition().row
|
||||
@addDecorationToBufferRowRange(startRow, endRow, decoration)
|
||||
|
||||
changedSubscription = @subscribe marker, 'changed', (e) =>
|
||||
oldStartRow = e.oldHeadBufferPosition.row
|
||||
oldEndRow = e.oldTailBufferPosition.row
|
||||
newStartRow = e.newHeadBufferPosition.row
|
||||
newEndRow = e.newTailBufferPosition.row
|
||||
|
||||
# swap so head is always <= than tail
|
||||
[oldEndRow, oldStartRow] = [oldStartRow, oldEndRow] if oldStartRow > oldEndRow
|
||||
[newEndRow, newStartRow] = [newStartRow, newEndRow] if newStartRow > newEndRow
|
||||
|
||||
@removeDecorationFromBufferRowRange(oldStartRow, oldEndRow, decoration)
|
||||
@addDecorationToBufferRowRange(newStartRow, newEndRow, decoration) if e.isValid
|
||||
|
||||
destroyedSubscription = @subscribe marker, 'destroyed', (e) =>
|
||||
@removeDecorationForMarker(marker, decoration)
|
||||
|
||||
@decorationMarkerSubscriptions[marker.id] ?= []
|
||||
@decorationMarkerSubscriptions[marker.id].push {decoration, changedSubscription, destroyedSubscription}
|
||||
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
return unless @decorationMarkerSubscriptions[marker.id]?
|
||||
|
||||
startRow = marker.getStartBufferPosition().row
|
||||
endRow = marker.getEndBufferPosition().row
|
||||
@removeDecorationFromBufferRowRange(startRow, endRow, decorationPattern)
|
||||
|
||||
for subscription in _.clone(@decorationMarkerSubscriptions[marker.id])
|
||||
if @decorationMatchesPattern(subscription.decoration, decorationPattern)
|
||||
subscription.changedSubscription.off()
|
||||
subscription.destroyedSubscription.off()
|
||||
@decorationMarkerSubscriptions[marker.id] = _.without(@decorationMarkerSubscriptions[marker.id], subscription)
|
||||
|
||||
return
|
||||
|
||||
# Retrieves a {DisplayBufferMarker} based on its id.
|
||||
#
|
||||
# id - A {Number} representing a marker id
|
||||
@@ -961,6 +1052,8 @@ class DisplayBuffer extends Model
|
||||
@emit 'marker-created', @getMarker(marker.id)
|
||||
|
||||
createFoldForMarker: (marker) ->
|
||||
bufferMarker = new DisplayBufferMarker({bufferMarker: marker, displayBuffer: this})
|
||||
@addDecorationForMarker(bufferMarker, type: 'gutter', class: 'folded')
|
||||
new Fold(this, marker)
|
||||
|
||||
foldForMarker: (marker) ->
|
||||
|
||||
+128
-79
@@ -33,22 +33,24 @@ EditorComponent = React.createClass
|
||||
pendingHorizontalScrollDelta: 0
|
||||
mouseWheelScreenRow: null
|
||||
mouseWheelScreenRowClearDelay: 150
|
||||
scrollSensitivity: 0.4
|
||||
scrollViewMeasurementRequested: false
|
||||
overflowChangedEventsPaused: false
|
||||
overflowChangedWhilePaused: false
|
||||
measureLineHeightAndDefaultCharWidthWhenShown: false
|
||||
inputEnabled: true
|
||||
|
||||
render: ->
|
||||
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state
|
||||
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||
maxLineNumberDigits = editor.getScreenLineCount().toString().length
|
||||
maxLineNumberDigits = editor.getLineCount().toString().length
|
||||
invisibles = if showInvisibles then @state.invisibles else {}
|
||||
hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty()
|
||||
|
||||
if @isMounted()
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
cursorScreenRanges = @getCursorScreenRanges(renderedRowRange)
|
||||
selectionScreenRanges = @getSelectionScreenRanges(renderedRowRange)
|
||||
decorations = @getGutterDecorations(renderedRowRange)
|
||||
scrollHeight = editor.getScrollHeight()
|
||||
scrollWidth = editor.getScrollWidth()
|
||||
scrollTop = editor.getScrollTop()
|
||||
@@ -67,11 +69,13 @@ EditorComponent = React.createClass
|
||||
|
||||
className = 'editor-contents editor-colors'
|
||||
className += ' is-focused' if focused
|
||||
className += ' has-selection' if hasSelection
|
||||
|
||||
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
|
||||
GutterComponent {
|
||||
ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, scrollTop,
|
||||
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow
|
||||
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
|
||||
decorations
|
||||
}
|
||||
|
||||
div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown,
|
||||
@@ -79,7 +83,6 @@ EditorComponent = React.createClass
|
||||
ref: 'input'
|
||||
className: 'hidden-input'
|
||||
style: hiddenInputStyle
|
||||
onInput: @onInput
|
||||
onFocus: @onInputFocused
|
||||
onBlur: @onInputBlurred
|
||||
|
||||
@@ -138,6 +141,7 @@ EditorComponent = React.createClass
|
||||
@pendingChanges = []
|
||||
@props.editor.manageScrollPosition = true
|
||||
@observeConfig()
|
||||
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
|
||||
|
||||
componentDidMount: ->
|
||||
{editor} = @props
|
||||
@@ -161,14 +165,16 @@ EditorComponent = React.createClass
|
||||
window.removeEventListener('resize', @onWindowResize)
|
||||
|
||||
componentWillUpdate: ->
|
||||
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
|
||||
if @props.editor.isAlive()
|
||||
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
|
||||
@props.parentView.trigger 'selection:changed' if @selectionChanged
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
@pendingChanges.length = 0
|
||||
@refreshingScrollbars = false
|
||||
@updateParentViewFocusedClassIfNeeded(prevState)
|
||||
@measureScrollbars() if @measuringScrollbars
|
||||
@measureLineHeightAndCharWidthsIfNeeded(prevState)
|
||||
@pauseOverflowChangedEvents()
|
||||
@props.parentView.trigger 'editor:display-updated'
|
||||
|
||||
requestUpdate: ->
|
||||
@@ -225,6 +231,18 @@ EditorComponent = React.createClass
|
||||
|
||||
selectionScreenRanges
|
||||
|
||||
getGutterDecorations: (renderedRowRange) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
|
||||
bufferRows = editor.bufferRowsForScreenRows(renderedStartRow, renderedEndRow - 1)
|
||||
|
||||
decorations = {}
|
||||
for bufferRow in bufferRows
|
||||
decorations[bufferRow] = editor.decorationsForBufferRow(bufferRow, 'gutter')
|
||||
decorations[bufferRow].push {class: 'foldable'} if editor.isFoldableAtBufferRow(bufferRow)
|
||||
decorations
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@subscribe editor, 'batched-updates-started', @onBatchedUpdatesStarted
|
||||
@@ -233,6 +251,7 @@ EditorComponent = React.createClass
|
||||
@subscribe editor, 'cursors-moved', @onCursorsMoved
|
||||
@subscribe editor, 'selection-removed selection-screen-range-changed', @onSelectionChanged
|
||||
@subscribe editor, 'selection-added', @onSelectionAdded
|
||||
@subscribe editor, 'decoration-changed', @onDecorationChanged
|
||||
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
||||
@subscribe editor.$height.changes, @requestUpdate
|
||||
@@ -244,6 +263,7 @@ EditorComponent = React.createClass
|
||||
node = @getDOMNode()
|
||||
node.addEventListener 'mousewheel', @onMouseWheel
|
||||
node.addEventListener 'focus', @onFocus # For some reason, React's built in focus events seem to bubble
|
||||
node.addEventListener 'textInput', @onTextInput
|
||||
|
||||
scrollViewNode = @refs.scrollView.getDOMNode()
|
||||
scrollViewNode.addEventListener 'overflowchanged', @onScrollViewOverflowChanged
|
||||
@@ -286,6 +306,8 @@ EditorComponent = React.createClass
|
||||
'editor:move-to-beginning-of-next-word': => editor.moveCursorToBeginningOfNextWord()
|
||||
'editor:move-to-previous-word-boundary': => editor.moveCursorToPreviousWordBoundary()
|
||||
'editor:move-to-next-word-boundary': => editor.moveCursorToNextWordBoundary()
|
||||
'editor:select-to-beginning-of-next-paragraph': => editor.selectToBeginningOfNextParagraph()
|
||||
'editor:select-to-beginning-of-previous-paragraph': => editor.selectToBeginningOfPreviousParagraph()
|
||||
'editor:select-to-end-of-line': => editor.selectToEndOfLine()
|
||||
'editor:select-to-beginning-of-line': => editor.selectToBeginningOfLine()
|
||||
'editor:select-to-end-of-word': => editor.selectToEndOfWord()
|
||||
@@ -363,10 +385,31 @@ EditorComponent = React.createClass
|
||||
@subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
|
||||
@subscribe atom.config.observe 'editor.invisibles', @setInvisibles
|
||||
@subscribe atom.config.observe 'editor.showInvisibles', @setShowInvisibles
|
||||
@subscribe atom.config.observe 'editor.scrollSensitivity', @setScrollSensitivity
|
||||
|
||||
onFocus: ->
|
||||
@refs.input.focus()
|
||||
|
||||
onTextInput: (event) ->
|
||||
return unless @isInputEnabled()
|
||||
|
||||
{editor} = @props
|
||||
inputNode = event.target
|
||||
|
||||
# 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
|
||||
|
||||
editor.insertText(event.data)
|
||||
inputNode.value = event.data
|
||||
|
||||
# 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 ' '
|
||||
|
||||
onInputFocused: ->
|
||||
@setState(focused: true)
|
||||
|
||||
@@ -405,10 +448,10 @@ EditorComponent = React.createClass
|
||||
{wheelDeltaX, wheelDeltaY} = event
|
||||
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
|
||||
# Scrolling horizontally
|
||||
@pendingHorizontalScrollDelta -= wheelDeltaX
|
||||
@pendingHorizontalScrollDelta -= Math.round(wheelDeltaX * @scrollSensitivity)
|
||||
else
|
||||
# Scrolling vertically
|
||||
@pendingVerticalScrollDelta -= wheelDeltaY
|
||||
@pendingVerticalScrollDelta -= Math.round(wheelDeltaY * @scrollSensitivity)
|
||||
@mouseWheelScreenRow = @screenRowForNode(event.target)
|
||||
@clearMouseWheelScreenRowAfterDelay ?= debounce(@clearMouseWheelScreenRow, @mouseWheelScreenRowClearDelay)
|
||||
@clearMouseWheelScreenRowAfterDelay()
|
||||
@@ -422,10 +465,7 @@ EditorComponent = React.createClass
|
||||
@pendingHorizontalScrollDelta = 0
|
||||
|
||||
onScrollViewOverflowChanged: ->
|
||||
if @overflowChangedEventsPaused
|
||||
@overflowChangedWhilePaused = true
|
||||
else
|
||||
@requestScrollViewMeasurement()
|
||||
@requestScrollViewMeasurement()
|
||||
|
||||
onWindowResize: ->
|
||||
@requestScrollViewMeasurement()
|
||||
@@ -436,17 +476,9 @@ EditorComponent = React.createClass
|
||||
scrollViewNode.scrollTop = 0
|
||||
scrollViewNode.scrollLeft = 0
|
||||
|
||||
onInput: (char, replaceLastCharacter) ->
|
||||
{editor} = @props
|
||||
|
||||
if replaceLastCharacter
|
||||
editor.transact ->
|
||||
editor.selectLeft()
|
||||
editor.insertText(char)
|
||||
else
|
||||
editor.insertText(char)
|
||||
|
||||
onMouseDown: (event) ->
|
||||
return unless event.button is 0 # only handle the left mouse button
|
||||
|
||||
{editor} = @props
|
||||
{detail, shiftKey, metaKey} = event
|
||||
screenPosition = @screenPositionForMouseEvent(event)
|
||||
@@ -501,6 +533,8 @@ EditorComponent = React.createClass
|
||||
@onStoppedScrollingAfterDelay()
|
||||
|
||||
onStoppedScrolling: ->
|
||||
return unless @isMounted()
|
||||
|
||||
@scrollingVertically = false
|
||||
@mouseWheelScreenRow = null
|
||||
@requestUpdate()
|
||||
@@ -509,6 +543,12 @@ EditorComponent = React.createClass
|
||||
|
||||
onCursorsMoved: ->
|
||||
@cursorsMoved = true
|
||||
@requestUpdate()
|
||||
|
||||
onDecorationChanged: ->
|
||||
@decorationChangedImmediate ?= setImmediate =>
|
||||
@requestUpdate() if @isMounted()
|
||||
@decorationChangedImmediate = null
|
||||
|
||||
selectToMousePositionUntilMouseUp: (event) ->
|
||||
{editor} = @props
|
||||
@@ -641,18 +681,6 @@ EditorComponent = React.createClass
|
||||
# if the editor's content and dimensions require them to be visible.
|
||||
@requestUpdate()
|
||||
|
||||
pauseOverflowChangedEvents: ->
|
||||
@overflowChangedEventsPaused = true
|
||||
@resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500)
|
||||
@resumeOverflowChangedEventsAfterDelay()
|
||||
|
||||
resumeOverflowChangedEvents: ->
|
||||
if @overflowChangedWhilePaused
|
||||
@overflowChangedWhilePaused = false
|
||||
@requestScrollViewMeasurement()
|
||||
|
||||
resumeOverflowChangedEventsAfterDelay: null
|
||||
|
||||
clearMouseWheelScreenRow: ->
|
||||
if @mouseWheelScreenRow?
|
||||
@mouseWheelScreenRow = null
|
||||
@@ -680,6 +708,71 @@ EditorComponent = React.createClass
|
||||
show: ->
|
||||
@setState(visible: true)
|
||||
|
||||
getFontSize: ->
|
||||
@state.fontSize
|
||||
|
||||
setFontSize: (fontSize) ->
|
||||
@setState({fontSize})
|
||||
|
||||
getFontFamily: ->
|
||||
@state.fontFamily
|
||||
|
||||
setFontFamily: (fontFamily) ->
|
||||
@setState({fontFamily})
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@setState({lineHeight})
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@setState({showIndentGuide})
|
||||
|
||||
# Public: Defines which characters are invisible.
|
||||
#
|
||||
# invisibles - An {Object} defining the invisible characters:
|
||||
# :eol - The end of line invisible {String} (default: `\u00ac`).
|
||||
# :space - The space invisible {String} (default: `\u00b7`).
|
||||
# :tab - The tab invisible {String} (default: `\u00bb`).
|
||||
# :cr - The carriage return invisible {String} (default: `\u00a4`).
|
||||
setInvisibles: (invisibles={}) ->
|
||||
defaults invisibles,
|
||||
eol: '\u00ac'
|
||||
space: '\u00b7'
|
||||
tab: '\u00bb'
|
||||
cr: '\u00a4'
|
||||
|
||||
@setState({invisibles})
|
||||
|
||||
setShowInvisibles: (showInvisibles) ->
|
||||
@setState({showInvisibles})
|
||||
|
||||
setScrollSensitivity: (scrollSensitivity) ->
|
||||
if scrollSensitivity = parseInt(scrollSensitivity)
|
||||
@scrollSensitivity = Math.abs(scrollSensitivity) / 100
|
||||
|
||||
screenPositionForMouseEvent: (event) ->
|
||||
pixelPosition = @pixelPositionForMouseEvent(event)
|
||||
@props.editor.screenPositionForPixelPosition(pixelPosition)
|
||||
|
||||
pixelPositionForMouseEvent: (event) ->
|
||||
{editor} = @props
|
||||
{clientX, clientY} = event
|
||||
|
||||
scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect()
|
||||
top = clientY - scrollViewClientRect.top + editor.getScrollTop()
|
||||
left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
|
||||
{top, left}
|
||||
|
||||
getModel: ->
|
||||
@props.editor
|
||||
|
||||
isInputEnabled: -> @inputEnabled
|
||||
|
||||
setInputEnabled: (@inputEnabled) -> @inputEnabled
|
||||
|
||||
updateParentViewFocusedClassIfNeeded: (prevState) ->
|
||||
if prevState.focused isnt @state.focused
|
||||
@props.parentView.toggleClass('is-focused', @props.focused)
|
||||
|
||||
runScrollBenchmark: ->
|
||||
unless process.env.NODE_ENV is 'production'
|
||||
ReactPerf = require 'react-atom-fork/lib/ReactDefaultPerf'
|
||||
@@ -714,47 +807,3 @@ EditorComponent = React.createClass
|
||||
ReactPerf.printExclusive()
|
||||
console.log "Wasted"
|
||||
ReactPerf.printWasted()
|
||||
|
||||
setFontSize: (fontSize) ->
|
||||
@setState({fontSize})
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@setState({lineHeight})
|
||||
|
||||
setFontFamily: (fontFamily) ->
|
||||
@setState({fontFamily})
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@setState({showIndentGuide})
|
||||
|
||||
# Public: Defines which characters are invisible.
|
||||
#
|
||||
# invisibles - An {Object} defining the invisible characters:
|
||||
# :eol - The end of line invisible {String} (default: `\u00ac`).
|
||||
# :space - The space invisible {String} (default: `\u00b7`).
|
||||
# :tab - The tab invisible {String} (default: `\u00bb`).
|
||||
# :cr - The carriage return invisible {String} (default: `\u00a4`).
|
||||
setInvisibles: (invisibles={}) ->
|
||||
defaults invisibles,
|
||||
eol: '\u00ac'
|
||||
space: '\u00b7'
|
||||
tab: '\u00bb'
|
||||
cr: '\u00a4'
|
||||
|
||||
@setState({invisibles})
|
||||
|
||||
setShowInvisibles: (showInvisibles) ->
|
||||
@setState({showInvisibles})
|
||||
|
||||
screenPositionForMouseEvent: (event) ->
|
||||
pixelPosition = @pixelPositionForMouseEvent(event)
|
||||
@props.editor.screenPositionForPixelPosition(pixelPosition)
|
||||
|
||||
pixelPositionForMouseEvent: (event) ->
|
||||
{editor} = @props
|
||||
{clientX, clientY} = event
|
||||
|
||||
scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect()
|
||||
top = clientY - scrollViewClientRect.top + editor.getScrollTop()
|
||||
left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
|
||||
{top, left}
|
||||
|
||||
@@ -56,6 +56,7 @@ class EditorView extends View
|
||||
softWrap: false
|
||||
softTabs: true
|
||||
softWrapAtPreferredLineLength: false
|
||||
scrollSensitivity: 40
|
||||
|
||||
@nextEditorId: 1
|
||||
|
||||
@@ -171,6 +172,8 @@ class EditorView extends View
|
||||
'editor:move-to-beginning-of-next-word': => @editor.moveCursorToBeginningOfNextWord()
|
||||
'editor:move-to-previous-word-boundary': => @editor.moveCursorToPreviousWordBoundary()
|
||||
'editor:move-to-next-word-boundary': => @editor.moveCursorToNextWordBoundary()
|
||||
'editor:select-to-beginning-of-next-paragraph': => @editor.selectToBeginningOfNextParagraph()
|
||||
'editor:select-to-beginning-of-previous-paragraph': => @editor.selectToBeginningOfPreviousParagraph()
|
||||
'editor:select-to-end-of-line': => @editor.selectToEndOfLine()
|
||||
'editor:select-to-beginning-of-line': => @editor.selectToBeginningOfLine()
|
||||
'editor:select-to-end-of-word': => @editor.selectToEndOfWord()
|
||||
@@ -280,7 +283,7 @@ class EditorView extends View
|
||||
@editor.moveCursorUp(@getPageRows())
|
||||
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
|
||||
|
||||
# Public: Gets the number of actual page rows existing in an editor.
|
||||
# Gets the number of actual page rows existing in an editor.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getPageRows: ->
|
||||
@@ -365,6 +368,8 @@ class EditorView extends View
|
||||
false if @isFocused
|
||||
|
||||
@overlayer.on 'mousedown', (e) =>
|
||||
return unless e.which is 1 # only handle the left mouse button
|
||||
|
||||
@overlayer.hide()
|
||||
clickedElement = document.elementFromPoint(e.pageX, e.pageY)
|
||||
@overlayer.show()
|
||||
@@ -564,7 +569,7 @@ class EditorView extends View
|
||||
@scrollTop(scrollTop)
|
||||
|
||||
@subscribe @editor, 'scroll-left-changed', (scrollLeft) =>
|
||||
@scrollLeft(scrollLeft)
|
||||
@scrollView.scrollLeft(scrollLeft)
|
||||
|
||||
@subscribe @editor, 'soft-wrap-changed', (softWrap) =>
|
||||
@setSoftWrap(softWrap)
|
||||
|
||||
+126
-2
@@ -214,6 +214,7 @@ class Editor extends Model
|
||||
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
|
||||
@subscribe @displayBuffer, 'tokenized', => @handleTokenization()
|
||||
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
|
||||
@subscribe @displayBuffer, "decoration-changed", (e) => @emit 'decoration-changed', e
|
||||
|
||||
getViewClass: ->
|
||||
if atom.config.get('core.useReactEditor')
|
||||
@@ -1057,6 +1058,106 @@ class Editor extends Model
|
||||
selection.insertText(fn(text))
|
||||
selection.setBufferRange(range)
|
||||
|
||||
# Public: Get all the decorations for a buffer row.
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decorationType - the {String} decoration type to filter by eg. 'gutter'
|
||||
#
|
||||
# Returns an {Array} of decorations in the form `[{type: 'gutter', class: 'someclass'}, ...]`
|
||||
# Returns an empty array when no decorations are found
|
||||
decorationsForBufferRow: (bufferRow, decorationType) ->
|
||||
@displayBuffer.decorationsForBufferRow(bufferRow, decorationType)
|
||||
|
||||
# Public: Get all the decorations for a range of buffer rows (inclusive)
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decorationType - the {String} decoration type to filter by eg. 'gutter'
|
||||
#
|
||||
# Returns an {Object} of decorations in the form `{23: [{type: 'gutter', class: 'someclass'}, ...], 24: [...]}`
|
||||
# Returns an {Object} with keyed with all buffer rows in the range containing empty {Array}s when no decorations are found
|
||||
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
|
||||
@displayBuffer.decorationsForBufferRowRange(startBufferRow, endBufferRow, decorationType)
|
||||
|
||||
# Public: Adds a decoration to a buffer row. For example, use to mark a gutter
|
||||
# line number with a class by using the form `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationToBufferRow: (bufferRow, decoration) ->
|
||||
@displayBuffer.addDecorationToBufferRow(bufferRow, decoration)
|
||||
|
||||
# Public: Removes a decoration from a buffer row.
|
||||
#
|
||||
# ```coffee
|
||||
# editor.removeDecorationFromBufferRow(2, {type: 'gutter', class: 'linter-error'})
|
||||
# ```
|
||||
#
|
||||
# All decorations matching a pattern will be removed. For example, you might
|
||||
# have decorations with a namespace like this attached to a row:
|
||||
#
|
||||
# ```coffee
|
||||
# [
|
||||
# {type: 'gutter', namespace: 'myns', class: 'something'},
|
||||
# {type: 'gutter', namespace: 'myns', class: 'something-else'}
|
||||
# ]
|
||||
# ```
|
||||
#
|
||||
# You can remove both with:
|
||||
#
|
||||
# ```coffee
|
||||
# editor.removeDecorationFromBufferRow(2, {namespace: 'myns'})
|
||||
# ```
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns an {Array} of the removed decorations
|
||||
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
|
||||
@displayBuffer.removeDecorationFromBufferRow(bufferRow, decorationPattern)
|
||||
|
||||
# Public: Adds a decoration to line numbers in a buffer row range
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
@displayBuffer.addDecorationToBufferRowRange(startBufferRow, endBufferRow, decoration)
|
||||
|
||||
# Public: Removes a decoration from line numbers in a buffer row range
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
@displayBuffer.removeDecorationFromBufferRowRange(startBufferRow, endBufferRow, decoration)
|
||||
|
||||
# Public: Adds a decoration that tracks a {Marker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect the marker's state.
|
||||
#
|
||||
# marker - the {Marker} you want this decoration to follow
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
@displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
|
||||
# Public: Removes all decorations associated with a {Marker} that match a
|
||||
# `decorationPattern` and stop tracking the {Marker}.
|
||||
#
|
||||
# marker - the {Marker} to detach from
|
||||
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
@displayBuffer.removeDecorationForMarker(marker, decorationPattern)
|
||||
|
||||
# Public: Get the {DisplayBufferMarker} for the given marker id.
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
@@ -1162,6 +1263,7 @@ class Editor extends Model
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker)
|
||||
@cursors.push(cursor)
|
||||
@addDecorationForMarker(marker, {class: 'cursor-line'})
|
||||
@emit 'cursor-added', cursor
|
||||
cursor
|
||||
|
||||
@@ -1503,10 +1605,18 @@ class Editor extends Model
|
||||
@getCursor().autoscroll()
|
||||
|
||||
pageUp: ->
|
||||
@setScrollTop(@getScrollTop() - @getHeight())
|
||||
newScrollTop = @getScrollTop() - @getHeight()
|
||||
@moveCursorUp(@getRowsPerPage())
|
||||
@setScrollTop(newScrollTop)
|
||||
|
||||
pageDown: ->
|
||||
@setScrollTop(@getScrollTop() + @getHeight())
|
||||
newScrollTop = @getScrollTop() + @getHeight()
|
||||
@moveCursorDown(@getRowsPerPage())
|
||||
@setScrollTop(newScrollTop)
|
||||
|
||||
# Returns the number of rows per page
|
||||
getRowsPerPage: ->
|
||||
Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels()))
|
||||
|
||||
moveCursors: (fn) ->
|
||||
@movingCursors = true
|
||||
@@ -1727,6 +1837,20 @@ class Editor extends Model
|
||||
selectWord: ->
|
||||
@expandSelectionsForward (selection) => selection.selectWord()
|
||||
|
||||
# Public: Expand selections to the beginning of the next paragraph.
|
||||
#
|
||||
# Operates on all selections. Moves the cursor to the beginning of the next
|
||||
# paragraph while preserving the selection's tail position.
|
||||
selectToBeginningOfNextParagraph: ->
|
||||
@expandSelectionsForward (selection) => selection.selectToBeginningOfNextParagraph()
|
||||
|
||||
# Public: Expand selections to the beginning of the next paragraph.
|
||||
#
|
||||
# Operates on all selections. Moves the cursor to the beginning of the next
|
||||
# paragraph while preserving the selection's tail position.
|
||||
selectToBeginningOfPreviousParagraph: ->
|
||||
@expandSelectionsBackward (selection) => selection.selectToBeginningOfPreviousParagraph()
|
||||
|
||||
# Public: Select the range of the given marker if it is valid.
|
||||
#
|
||||
# marker - A {DisplayBufferMarker}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
_ = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
@@ -15,8 +16,8 @@ GutterComponent = React.createClass
|
||||
render: ->
|
||||
{scrollHeight, scrollTop} = @props
|
||||
|
||||
div className: 'gutter',
|
||||
div className: 'line-numbers', ref: 'lineNumbers', style:
|
||||
div className: 'gutter', onClick: @onClick,
|
||||
div className: 'line-numbers editor-colors', ref: 'lineNumbers', style:
|
||||
height: scrollHeight
|
||||
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
|
||||
@@ -24,6 +25,7 @@ GutterComponent = React.createClass
|
||||
@lineNumberNodesById = {}
|
||||
@lineNumberIdsByScreenRow = {}
|
||||
@screenRowsByLineNumberId = {}
|
||||
@previousDecorations = {}
|
||||
|
||||
componentDidMount: ->
|
||||
@appendDummyLineNumber()
|
||||
@@ -36,10 +38,12 @@ GutterComponent = React.createClass
|
||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
{renderedRowRange, pendingChanges, decorations} = newProps
|
||||
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
|
||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
||||
|
||||
return true unless _.isEqual(@previousDecorations, decorations)
|
||||
|
||||
false
|
||||
|
||||
componentDidUpdate: (oldProps) ->
|
||||
@@ -58,7 +62,7 @@ GutterComponent = React.createClass
|
||||
# since the real line numbers are absolutely positioned for performance reasons.
|
||||
appendDummyLineNumber: ->
|
||||
{maxLineNumberDigits} = @props
|
||||
WrapperDiv.innerHTML = @buildLineNumberHTML(0, false, maxLineNumberDigits)
|
||||
WrapperDiv.innerHTML = @buildLineNumberHTML(-1, false, maxLineNumberDigits)
|
||||
@dummyLineNumberNode = WrapperDiv.children[0]
|
||||
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
|
||||
|
||||
@@ -70,7 +74,7 @@ GutterComponent = React.createClass
|
||||
@removeLineNumberNodes(lineNumberIdsToPreserve)
|
||||
|
||||
appendOrUpdateVisibleLineNumberNodes: ->
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits} = @props
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, decorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
newLineNumberIds = null
|
||||
@@ -91,12 +95,12 @@ GutterComponent = React.createClass
|
||||
visibleLineNumberIds.add(id)
|
||||
|
||||
if @hasLineNumberNode(id)
|
||||
@updateLineNumberNode(id, screenRow)
|
||||
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, decorations[bufferRow])
|
||||
else
|
||||
newLineNumberIds ?= []
|
||||
newLineNumbersHTML ?= ""
|
||||
newLineNumberIds.push(id)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, decorations[bufferRow])
|
||||
@screenRowsByLineNumberId[id] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = id
|
||||
|
||||
@@ -110,6 +114,7 @@ GutterComponent = React.createClass
|
||||
@lineNumberNodesById[lineNumberId] = lineNumberNode
|
||||
node.appendChild(lineNumberNode)
|
||||
|
||||
@previousDecorations = decorations
|
||||
visibleLineNumberIds
|
||||
|
||||
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
|
||||
@@ -123,7 +128,7 @@ GutterComponent = React.createClass
|
||||
delete @screenRowsByLineNumberId[lineNumberId]
|
||||
node.removeChild(lineNumberNode)
|
||||
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow, decorations) ->
|
||||
if screenRow?
|
||||
{lineHeightInPixels} = @props
|
||||
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
|
||||
@@ -131,7 +136,13 @@ GutterComponent = React.createClass
|
||||
style = "visibility: hidden;"
|
||||
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
|
||||
|
||||
"<div class=\"line-number\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
classes = ''
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
classes += decoration.class + ' ' if not softWrapped or softWrapped and decoration.softWrap
|
||||
classes += "line-number line-number-#{bufferRow}"
|
||||
|
||||
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
|
||||
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
|
||||
if softWrapped
|
||||
@@ -143,11 +154,23 @@ GutterComponent = React.createClass
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
padding + lineNumber + iconHTML
|
||||
|
||||
updateLineNumberNode: (lineNumberId, screenRow) ->
|
||||
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped, decorations) ->
|
||||
node = @lineNumberNodesById[lineNumberId]
|
||||
previousDecorations = @previousDecorations[bufferRow]
|
||||
|
||||
if previousDecorations?
|
||||
for decoration in previousDecorations
|
||||
node.classList.remove(decoration.class) if not contains(decorations, decoration)
|
||||
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
if not contains(previousDecorations, decoration) and (not softWrapped or softWrapped and decoration.softWrap)
|
||||
node.classList.add(decoration.class)
|
||||
|
||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
||||
{lineHeightInPixels} = @props
|
||||
@lineNumberNodesById[lineNumberId].style.top = screenRow * lineHeightInPixels + 'px'
|
||||
@lineNumberNodesById[lineNumberId].dataset.screenRow = screenRow
|
||||
node.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
node.dataset.screenRow = screenRow
|
||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
||||
|
||||
@@ -156,3 +179,22 @@ GutterComponent = React.createClass
|
||||
|
||||
lineNumberNodeForScreenRow: (screenRow) ->
|
||||
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
|
||||
|
||||
onClick: (event) ->
|
||||
{editor} = @props
|
||||
{target} = event
|
||||
lineNumber = target.parentNode
|
||||
|
||||
if target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
|
||||
bufferRow = parseInt(lineNumber.getAttribute('data-buffer-row'))
|
||||
if lineNumber.classList.contains('folded')
|
||||
editor.unfoldBufferRow(bufferRow)
|
||||
else
|
||||
editor.foldBufferRow(bufferRow)
|
||||
|
||||
# Created because underscore uses === not _.isEqual, which we need
|
||||
contains = (array, target) ->
|
||||
return false unless array?
|
||||
for object in array
|
||||
return true if _.isEqual(object, target)
|
||||
false
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
punycode = require 'punycode'
|
||||
{last, isEqual} = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{input} = require 'reactionary-atom-fork'
|
||||
@@ -17,7 +16,6 @@ InputComponent = React.createClass
|
||||
|
||||
componentDidMount: ->
|
||||
@getDOMNode().addEventListener 'paste', @onPaste
|
||||
@getDOMNode().addEventListener 'input', @onInput
|
||||
@getDOMNode().addEventListener 'compositionupdate', @onCompositionUpdate
|
||||
|
||||
# Don't let text accumulate in the input forever, but avoid excessive reflows
|
||||
@@ -36,15 +34,6 @@ InputComponent = React.createClass
|
||||
onPaste: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
onInput: (e) ->
|
||||
e.stopPropagation()
|
||||
valueCharCodes = punycode.ucs2.decode(@getDOMNode().value)
|
||||
valueLength = valueCharCodes.length
|
||||
replaceLastChar = valueLength is @lastValueLength
|
||||
@lastValueLength = valueLength
|
||||
lastChar = String.fromCharCode(last(valueCharCodes))
|
||||
@props.onInput?(lastChar, replaceLastChar)
|
||||
|
||||
onFocus: ->
|
||||
@props.onFocus?()
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ class LanguageMode
|
||||
buffer = @editor.buffer
|
||||
commentStartRegexString = _.escapeRegExp(commentStartString).replace(/(\s+)$/, '(?:$1)?')
|
||||
commentStartRegex = new OnigRegExp("^(\\s*)(#{commentStartRegexString})")
|
||||
shouldUncomment = commentStartRegex.test(buffer.lineForRow(start))
|
||||
|
||||
if commentEndString
|
||||
shouldUncomment = commentStartRegex.test(buffer.lineForRow(start))
|
||||
if shouldUncomment
|
||||
commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '(?:$1)?')
|
||||
commentEndRegex = new OnigRegExp("(#{commentEndRegexString})(\\s*)$")
|
||||
@@ -64,10 +64,18 @@ class LanguageMode
|
||||
buffer.insert([start, indentLength], commentStartString)
|
||||
buffer.insert([end, buffer.lineLengthForRow(end)], commentEndString)
|
||||
else
|
||||
if shouldUncomment and start isnt end
|
||||
shouldUncomment = [start+1..end].every (row) ->
|
||||
line = buffer.lineForRow(row)
|
||||
not line or commentStartRegex.test(line)
|
||||
allBlank = true
|
||||
allBlankOrCommented = true
|
||||
|
||||
for row in [start..end]
|
||||
line = buffer.lineForRow(row)
|
||||
blank = line?.match(/^\s*$/)
|
||||
|
||||
allBlank = false unless blank
|
||||
allBlankOrCommented = false unless blank or commentStartRegex.test(line)
|
||||
|
||||
shouldUncomment = allBlankOrCommented and not allBlank
|
||||
|
||||
if shouldUncomment
|
||||
for row in [start..end]
|
||||
if match = commentStartRegex.search(buffer.lineForRow(row))
|
||||
|
||||
@@ -21,7 +21,7 @@ LinesComponent = React.createClass
|
||||
width: scrollWidth
|
||||
WebkitTransform: "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||
|
||||
div {className: 'lines', style},
|
||||
div {className: 'lines editor-colors', style},
|
||||
SelectionsComponent({editor, selectionScreenRanges, lineHeightInPixels, defaultCharWidth}) if @isMounted()
|
||||
|
||||
componentWillMount: ->
|
||||
@@ -232,6 +232,8 @@ LinesComponent = React.createClass
|
||||
charWidths = editor.getScopedCharWidths(scopes)
|
||||
|
||||
for char in value
|
||||
continue if char is '\0'
|
||||
|
||||
unless charWidths[char]?
|
||||
unless textNode?
|
||||
rangeForMeasurement ?= document.createRange()
|
||||
|
||||
+5
-2
@@ -212,7 +212,10 @@ class Pane extends Model
|
||||
@destroyItem(item) for item in @getItems() when item isnt @activeItem
|
||||
|
||||
destroy: ->
|
||||
super unless @container?.isAlive() and @container?.getPanes().length is 1
|
||||
if @container?.isAlive() and @container.getPanes().length is 1
|
||||
@destroyItems()
|
||||
else
|
||||
super
|
||||
|
||||
# Called by model superclass.
|
||||
destroyed: ->
|
||||
@@ -331,7 +334,7 @@ class Pane extends Model
|
||||
if @parent.orientation isnt orientation
|
||||
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
|
||||
|
||||
newPane = new @constructor(extend({focused: true}, params))
|
||||
newPane = new @constructor(params)
|
||||
switch side
|
||||
when 'before' then @parent.insertChildBefore(this, newPane)
|
||||
when 'after' then @parent.insertChildAfter(this, newPane)
|
||||
|
||||
+132
-23
@@ -16,10 +16,45 @@ class ReactEditorView extends View
|
||||
|
||||
getEditor: -> @editor
|
||||
|
||||
getModel: -> @editor
|
||||
|
||||
Object.defineProperty @::, 'lineHeight', get: -> @editor.getLineHeightInPixels()
|
||||
Object.defineProperty @::, 'charWidth', get: -> @editor.getDefaultCharWidth()
|
||||
Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
|
||||
Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
|
||||
Object.defineProperty @::, 'active', get: -> @is(@getPane().activeView)
|
||||
Object.defineProperty @::, 'isFocused', get: -> @component?.state.focused
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return unless onDom
|
||||
return if @attached
|
||||
|
||||
@attached = true
|
||||
props = defaults({@editor, parentView: this}, @props)
|
||||
@component = React.renderComponent(EditorComponent(props), @element)
|
||||
|
||||
node = @component.getDOMNode()
|
||||
|
||||
@scrollView = $(node).find('.scroll-view')
|
||||
@underlayer = $(node).find('.selections').addClass('underlayer')
|
||||
@overlayer = $(node).find('.lines').addClass('overlayer')
|
||||
@hiddenInput = $(node).find('.hidden-input')
|
||||
|
||||
@gutter = $(node).find('.gutter')
|
||||
@gutter.removeClassFromAllLines = (klass) =>
|
||||
@gutter.find('.line-number').removeClass(klass)
|
||||
|
||||
@gutter.getLineNumberElement = (bufferRow) =>
|
||||
@gutter.find("[data-buffer-row='#{bufferRow}']")
|
||||
|
||||
@gutter.addClassToLine = (bufferRow, klass) =>
|
||||
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
|
||||
lines.addClass(klass)
|
||||
lines.length > 0
|
||||
|
||||
@focus() if @focusOnAttach
|
||||
|
||||
@trigger 'editor:attached', [this]
|
||||
|
||||
scrollTop: (scrollTop) ->
|
||||
if scrollTop?
|
||||
@@ -33,34 +68,21 @@ class ReactEditorView extends View
|
||||
else
|
||||
@editor.getScrollLeft()
|
||||
|
||||
scrollToBottom: ->
|
||||
@editor.setScrollBottom(Infinity)
|
||||
|
||||
scrollToScreenPosition: (screenPosition) ->
|
||||
@editor.scrollToScreenPosition(screenPosition)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) ->
|
||||
@editor.scrollToBufferPosition(bufferPosition)
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return unless onDom
|
||||
@attached = true
|
||||
props = defaults({@editor, parentView: this}, @props)
|
||||
@component = React.renderComponent(EditorComponent(props), @element)
|
||||
scrollToCursorPosition: ->
|
||||
@editor.scrollToCursorPosition()
|
||||
|
||||
node = @component.getDOMNode()
|
||||
|
||||
@underlayer = $(node).find('.selections')
|
||||
|
||||
@gutter = $(node).find('.gutter')
|
||||
@gutter.removeClassFromAllLines = (klass) =>
|
||||
@gutter.find('.line-number').removeClass(klass)
|
||||
|
||||
@gutter.addClassToLine = (bufferRow, klass) =>
|
||||
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
|
||||
lines.addClass(klass)
|
||||
lines.length > 0
|
||||
|
||||
@focus() if @focusOnAttach
|
||||
|
||||
@trigger 'editor:attached', [this]
|
||||
scrollToPixelPosition: (pixelPosition) ->
|
||||
screenPosition = screenPositionForPixelPosition(pixelPosition)
|
||||
@editor.scrollToScreenPosition(screenPosition)
|
||||
|
||||
pixelPositionForBufferPosition: (bufferPosition) ->
|
||||
@editor.pixelPositionForBufferPosition(bufferPosition)
|
||||
@@ -78,6 +100,26 @@ class ReactEditorView extends View
|
||||
@attached = false
|
||||
@trigger 'editor:detached', this
|
||||
|
||||
# Public: Split the editor view left.
|
||||
splitLeft: ->
|
||||
pane = @getPane()
|
||||
pane?.splitLeft(pane?.copyActiveItem()).activeView
|
||||
|
||||
# Public: Split the editor view right.
|
||||
splitRight: ->
|
||||
pane = @getPane()
|
||||
pane?.splitRight(pane?.copyActiveItem()).activeView
|
||||
|
||||
# Public: Split the editor view up.
|
||||
splitUp: ->
|
||||
pane = @getPane()
|
||||
pane?.splitUp(pane?.copyActiveItem()).activeView
|
||||
|
||||
# Public: Split the editor view down.
|
||||
splitDown: ->
|
||||
pane = @getPane()
|
||||
pane?.splitDown(pane?.copyActiveItem()).activeView
|
||||
|
||||
getPane: ->
|
||||
@closest('.pane').view()
|
||||
|
||||
@@ -89,10 +131,77 @@ class ReactEditorView extends View
|
||||
|
||||
hide: ->
|
||||
super
|
||||
@component.hide()
|
||||
@component?.hide()
|
||||
|
||||
show: ->
|
||||
super
|
||||
@component.show()
|
||||
@component?.show()
|
||||
|
||||
pageDown: ->
|
||||
@editor.pageDown()
|
||||
|
||||
pageUp: ->
|
||||
@editor.pageUp()
|
||||
|
||||
getModel: ->
|
||||
@component?.getModel()
|
||||
|
||||
getFirstVisibleScreenRow: ->
|
||||
@editor.getVisibleRowRange()[0]
|
||||
|
||||
getLastVisibleScreenRow: ->
|
||||
@editor.getVisibleRowRange()[1]
|
||||
|
||||
getFontFamily: ->
|
||||
@component?.getFontFamily()
|
||||
|
||||
setFontFamily: (fontFamily)->
|
||||
@component?.setFontFamily(fontFamily)
|
||||
|
||||
getFontSize: ->
|
||||
@component?.getFontSize()
|
||||
|
||||
setFontSize: (fontSize)->
|
||||
@component?.setFontSize(fontSize)
|
||||
|
||||
setWidthInChars: (widthInChars) ->
|
||||
@component.getDOMNode().style.width = (@editor.getDefaultCharWidth() * widthInChars) + 'px'
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@component.setLineHeight(lineHeight)
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@component.setShowIndentGuide(showIndentGuide)
|
||||
|
||||
setSoftWrap: (softWrap) ->
|
||||
@editor.setSoftWrap(softWrap)
|
||||
|
||||
setShowInvisibles: (showInvisibles) ->
|
||||
@component.setShowInvisibles(showInvisibles)
|
||||
|
||||
toggleSoftWrap: ->
|
||||
@editor.toggleSoftWrap()
|
||||
|
||||
toggleSoftTabs: ->
|
||||
@editor.toggleSoftTabs()
|
||||
|
||||
getText: ->
|
||||
@editor.getText()
|
||||
|
||||
setText: (text) ->
|
||||
@editor.setText(text)
|
||||
|
||||
insertText: (text) ->
|
||||
@editor.insertText(text)
|
||||
|
||||
isInputEnabled: ->
|
||||
@component.isInputEnabled()
|
||||
|
||||
setInputEnabled: (inputEnabled) ->
|
||||
@component.setInputEnabled(inputEnabled)
|
||||
|
||||
requestDisplayUpdate: -> # No-op shim for find-and-replace
|
||||
|
||||
updateDisplay: -> # No-op shim for package specs
|
||||
|
||||
redraw: -> # No-op shim
|
||||
|
||||
+11
-1
@@ -236,6 +236,16 @@ class Selection extends Model
|
||||
selectToNextWordBoundary: ->
|
||||
@modifySelection => @cursor.moveToNextWordBoundary()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the
|
||||
# beginning of the next paragraph.
|
||||
selectToBeginningOfNextParagraph: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfNextParagraph()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the
|
||||
# beginning of the previous paragraph.
|
||||
selectToBeginningOfPreviousParagraph: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfPreviousParagraph()
|
||||
|
||||
# Public: Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
@@ -485,7 +495,7 @@ class Selection extends Model
|
||||
outdentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
buffer = @editor.buffer
|
||||
leadingTabRegex = new RegExp("^ {1,#{@editor.getTabLength()}}|\t")
|
||||
leadingTabRegex = new RegExp("^( {1,#{@editor.getTabLength()}}|\t)")
|
||||
for row in [start..end]
|
||||
if matchLength = buffer.lineForRow(row).match(leadingTabRegex)?[0].length
|
||||
buffer.delete [[row, 0], [row, matchLength]]
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
.cursor {
|
||||
z-index: 2;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -69,11 +69,13 @@
|
||||
.gutter {
|
||||
.line-number {
|
||||
white-space: nowrap;
|
||||
padding: 0 .5em;
|
||||
padding-left: .5em;
|
||||
|
||||
.icon-right {
|
||||
padding: 0;
|
||||
padding-left: .1em;
|
||||
padding: 0 .4em;
|
||||
&:before {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +123,7 @@
|
||||
visibility: hidden;
|
||||
padding-left: .1em;
|
||||
padding-right: .5em;
|
||||
opacity: .7;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.editor .gutter:hover .line-number.foldable .icon-right {
|
||||
|
||||
@@ -135,6 +135,10 @@ body {
|
||||
padding: 5px 0 5px 0;
|
||||
}
|
||||
|
||||
.result-message.deprecation-message {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.stack-trace {
|
||||
font-size: 12px;
|
||||
margin: 5px 0 0 0;
|
||||
@@ -174,4 +178,24 @@ body {
|
||||
// overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.deprecation-toggle {
|
||||
.octicon(fold);
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
color: #999;
|
||||
|
||||
&.folded {
|
||||
.octicon(unfold);
|
||||
}
|
||||
}
|
||||
|
||||
.deprecation-toggle:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&:hover .deprecation-toggle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário