Comparar commits
384 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 92d16a9d0a | |||
| f224a6d5f0 | |||
| 16f95a1420 | |||
| 0231d02877 | |||
| 6d2b70b3d9 | |||
| 49b825aeb3 | |||
| 357299a700 | |||
| 31afa0abd5 | |||
| b11accec6d | |||
| defa869d5c | |||
| 8d4be6ab57 | |||
| 9976166902 | |||
| 8ea277ef77 | |||
| d85b8bfaf3 | |||
| d9e15d937e | |||
| 908ff5d3cd | |||
| 35b4ba3066 | |||
| f90e47daef | |||
| 5440dd68a7 | |||
| 11205d9eaa | |||
| 34e37fce7c | |||
| 3402c8dcd1 | |||
| 3921a63f67 | |||
| 5ecaf5dfc4 | |||
| 475f92351f | |||
| 0a5b378425 | |||
| 157774f552 | |||
| 320c12134a | |||
| 280b451835 | |||
| 4ee03fe590 | |||
| db1652f1ed | |||
| 8013ff7775 | |||
| c24475c2c8 | |||
| 580c639265 | |||
| 4c33549371 | |||
| 7be5553ba1 | |||
| 43936a1faf | |||
| e3c44bf551 | |||
| 6b0636d331 | |||
| cb0ee735be | |||
| 036dc06bac | |||
| 7155ec4b73 | |||
| 86ea4d94bb | |||
| 6a2021ac98 | |||
| 6fe05064eb | |||
| dd5c9ff6d4 | |||
| 29179d0bbc | |||
| f6400a4097 | |||
| e170b9f56b | |||
| 2ea8418c66 | |||
| c20a4a3084 | |||
| 0be64f9902 | |||
| 5300fefef4 | |||
| 2602e6ec0a | |||
| 1a22fc3c68 | |||
| 6d02861f11 | |||
| 57ed190ea3 | |||
| 436d7de817 | |||
| a9feed2e4a | |||
| 8b04e94d09 | |||
| 5c5576c39d | |||
| 2b957beeda | |||
| 832aeffd4f | |||
| 5d08ecdcb2 | |||
| 997529774c | |||
| 48d20ff1ec | |||
| 0793f291d1 | |||
| 8d479328ec | |||
| 569326e76a | |||
| 8372b84e9f | |||
| eda55156e5 | |||
| c40a526302 | |||
| 540b038ced | |||
| ce1ebec253 | |||
| 44e121c997 | |||
| 3579404bed | |||
| 1e19860409 | |||
| eab4b578a3 | |||
| 2878196e0a | |||
| 1c037411e9 | |||
| 68144681b2 | |||
| 484a516bd6 | |||
| fa6e84415b | |||
| 7117634ba8 | |||
| a108c283cf | |||
| 3dc61f4a7a | |||
| 9685e3f1db | |||
| b2c70f9e69 | |||
| d7a3ffa9de | |||
| 18ed91a402 | |||
| e991b3d10c | |||
| f1b7f9ca30 | |||
| e1e510e473 | |||
| d042d15a50 | |||
| fe9fec733d | |||
| 06095e57d7 | |||
| b83f908e28 | |||
| fad2a63a14 | |||
| 2de42303d4 | |||
| a7aed07d70 | |||
| 673b62f547 | |||
| 19835f2f66 | |||
| 7474de8f7e | |||
| 84c30ef6c5 | |||
| 0aa5fa9eeb | |||
| b5f8b159fc | |||
| 79357be899 | |||
| 12d6a90ddc | |||
| 4eb3be6f17 | |||
| af622c6b74 | |||
| df8e0a8464 | |||
| a1c1879ba6 | |||
| d1e52d4105 | |||
| 0f01840e3e | |||
| e1945fce14 | |||
| e6ecf10616 | |||
| b85ebbad2a | |||
| 38ba96a54f | |||
| 334e2ef7ab | |||
| 58efeb8a20 | |||
| 3ae6540c70 | |||
| 497ac5e5ce | |||
| 8735780473 | |||
| 888b5ab098 | |||
| c897f42d51 | |||
| 3c5312e834 | |||
| c59cbb3b25 | |||
| b3da11edfb | |||
| f56b487935 | |||
| 840abd6780 | |||
| 2e554ac819 | |||
| 35c7bc0eef | |||
| d99a9b0f3f | |||
| 7ac8b80172 | |||
| 68f2bd56f0 | |||
| e709b986cd | |||
| 74bdd5f0e8 | |||
| 48b6c24882 | |||
| e5f800ef35 | |||
| 492022fdd8 | |||
| cf7b87842e | |||
| 1838ff2502 | |||
| 505bfc28db | |||
| abbe8d2eec | |||
| bf33d96899 | |||
| a56b5eef2f | |||
| 80eb31679f | |||
| 54039e9d3b | |||
| 92c28fc44f | |||
| bc67efb72b | |||
| 0ee4d864be | |||
| 1d724339d6 | |||
| ce90b72807 | |||
| 4f356121d7 | |||
| 7b19152a58 | |||
| 15da69287e | |||
| 8d8db5142f | |||
| 6d7881bcfe | |||
| c2b7955ec6 | |||
| cc073ae462 | |||
| 3348c0e75a | |||
| 376a850507 | |||
| 168c6cdbca | |||
| 9ece33dbfe | |||
| 942041f214 | |||
| 260be2e096 | |||
| 6c8b4de986 | |||
| 9b267728d0 | |||
| 3360d8500f | |||
| a4ed02c3c5 | |||
| 5658f9ab07 | |||
| c890e56cef | |||
| 5d22ce7128 | |||
| 758abf8c8a | |||
| 9e90d09428 | |||
| 092dac7625 | |||
| c129842037 | |||
| 4bb7e0f323 | |||
| 7b09fc04da | |||
| 1090746cb1 | |||
| 294a7ce6d3 | |||
| 26c1bf2075 | |||
| ec1d97a7c9 | |||
| a92eed5107 | |||
| 66319ca9c0 | |||
| 17fa580ecd | |||
| 2585c10688 | |||
| 7202f497db | |||
| 9508909a9f | |||
| e5ab2c6507 | |||
| 8add5ccd7e | |||
| d3829e2fc6 | |||
| c26f6cc7f7 | |||
| 3250888e05 | |||
| 2e6ef6848a | |||
| 74bcbf6291 | |||
| ab947082dc | |||
| 9e31bc5179 | |||
| be1a2f9e30 | |||
| 9fe887191d | |||
| b0031e493e | |||
| 1fa6661efd | |||
| 8b57bb3309 | |||
| 8b2e1b088a | |||
| dcb0d9b039 | |||
| 4efabd2b5e | |||
| 18d17b55b5 | |||
| 718e1228bd | |||
| 22b0a35dba | |||
| e395b1416a | |||
| 0edfbaebeb | |||
| 3e57fb8e9a | |||
| 512d4da587 | |||
| de0e945c5c | |||
| 4642d96f39 | |||
| de2930fe99 | |||
| 65c2f26656 | |||
| 8ca709eb6a | |||
| 43e88f6515 | |||
| d97eacc2f9 | |||
| a70367c70b | |||
| d60c718fe2 | |||
| fe838375a1 | |||
| 06d06d10e0 | |||
| de9f070f53 | |||
| a6e437b9d3 | |||
| 44b95fc637 | |||
| e93e4e9333 | |||
| d4a7bff525 | |||
| 5e78bbf471 | |||
| 873abd68af | |||
| d42a064a44 | |||
| 32c8a3ce99 | |||
| a99bde4e2d | |||
| 5753680b58 | |||
| 68ddbd9e75 | |||
| e6725c9fcb | |||
| b14317cf50 | |||
| bb6a44a757 | |||
| 431fc725ff | |||
| 72d5a4cdae | |||
| f5517bad66 | |||
| a63797019d | |||
| 324f1392a3 | |||
| 21ae33f749 | |||
| 15ca3e2a40 | |||
| ea9f5eb1c7 | |||
| 7f70b41696 | |||
| 9a6510c938 | |||
| 825910dc1d | |||
| bd238d6885 | |||
| 8435e0176b | |||
| 5bdaf3cbe4 | |||
| f5e54b0ba6 | |||
| fa7e388352 | |||
| 70f352251e | |||
| 1b05708d7a | |||
| a2b90cd501 | |||
| 61c7e797b9 | |||
| b689ffd402 | |||
| 71fb063a67 | |||
| b376bd8688 | |||
| e068d3d293 | |||
| a49221e359 | |||
| e7aef25f1f | |||
| 071b391994 | |||
| 7b11343ea8 | |||
| d25f432400 | |||
| 2e16f2dc3f | |||
| f828b91686 | |||
| 3f4210c293 | |||
| 0f7b97821b | |||
| efa457bade | |||
| 61188cb05f | |||
| 69c3001836 | |||
| 81afb89d27 | |||
| 2107b10778 | |||
| bc31f94833 | |||
| 58c2cec787 | |||
| 2c95af63e6 | |||
| 8a377c9339 | |||
| e21f561af5 | |||
| ccb01c23d2 | |||
| 3bc50c9b2c | |||
| 18c5f9c8de | |||
| 7179bc5af1 | |||
| 3bf0c73170 | |||
| 95242c8c82 | |||
| eb84737109 | |||
| 2b54c9bebe | |||
| 63e8099088 | |||
| f739dce210 | |||
| 77389b0518 | |||
| 809804d0cc | |||
| 8054b769d6 | |||
| e3c3779a73 | |||
| 196f64d846 | |||
| 9cc1244f32 | |||
| 1c69995bb6 | |||
| 7c4cbd8bf9 | |||
| 570f59a7e2 | |||
| 8d71e3f69a | |||
| 0263d72551 | |||
| 9299ff3404 | |||
| 7c79643b89 | |||
| 6a4b4d2efd | |||
| d6f217f80a | |||
| 1f94e89e56 | |||
| 27a959ec16 | |||
| 0369d77c85 | |||
| 866bc02d68 | |||
| ae96d9aeaa | |||
| 5481b37d01 | |||
| b127155805 | |||
| 74dbfbc956 | |||
| f41a9739ac | |||
| b75e748ec5 | |||
| 3f9fdad478 | |||
| 0150b40376 | |||
| d3e6bd038f | |||
| b7d8e581ee | |||
| daaf9be4bf | |||
| 396b21bc04 | |||
| d4617f1036 | |||
| 6cb5af0bfc | |||
| 1c2e997415 | |||
| 148b691734 | |||
| 9b577ecbf8 | |||
| d57119793b | |||
| ccada33d57 | |||
| 1d9514ca81 | |||
| 5ebb17c2e8 | |||
| f741b1d6a6 | |||
| 36280bb3a7 | |||
| 15ccfac75a | |||
| 2e424baf2a | |||
| 0547d5a78b | |||
| 5529f13cc6 | |||
| 058ab7d486 | |||
| 0a54233ef0 | |||
| b4f4ef8ec4 | |||
| e11785ce98 | |||
| b1d7de8d43 | |||
| becdca0858 | |||
| c9e3ca3d69 | |||
| 6336ffbf44 | |||
| 6aeca79d1b | |||
| e76b24f8ad | |||
| 0304f187dd | |||
| 968ab0e41d | |||
| ea6feddd7b | |||
| f381abcbad | |||
| bb5d628397 | |||
| f3d87b1653 | |||
| 76a911f17a | |||
| 7139fd9f98 | |||
| 64e2b53baa | |||
| 78630a14fd | |||
| 38fbcbdf89 | |||
| 6e102c7e1e | |||
| f40c8a97c0 | |||
| b9a3eca091 | |||
| f9fcb1767b | |||
| 433843ba23 | |||
| 598e752a9d | |||
| c1142f6c6a | |||
| 937ff194b6 | |||
| 9a5da3c27e | |||
| 43cb9af4a4 | |||
| 843c075779 | |||
| e13cf19daa | |||
| 0eecf49e61 | |||
| 748d189079 | |||
| 9d4c4a190f | |||
| ac2b84f21e | |||
| 610f515f68 | |||
| 0dadce1047 | |||
| 8c3bfaa3a2 | |||
| f8b8b22b75 | |||
| 67a1ed92db | |||
| 87aae13c80 | |||
| c735432163 | |||
| 624c0bf9f1 | |||
| 2fdcf7a124 |
@@ -13,3 +13,5 @@ debug.log
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
resources/linux/Atom.desktop
|
||||
resources/linux/debian/control
|
||||
|
||||
@@ -11,6 +11,7 @@ pairs:
|
||||
bo: Ben Ogle; benogle
|
||||
jr: Jason Rudolph; jasonrudolph
|
||||
jl: Jessica Lord; jlord
|
||||
dh: Daniel Hengeveld; danielh
|
||||
email:
|
||||
domain: github.com
|
||||
#global: true
|
||||
|
||||
+12
@@ -6,10 +6,21 @@ Visit [atom.io](https://atom.io) to learn more.
|
||||
|
||||
## Installing
|
||||
|
||||
### Mac OS X
|
||||
|
||||
Download the latest [Atom release](https://github.com/atom/atom/releases/latest).
|
||||
|
||||
Atom will automatically update when a new release is available.
|
||||
|
||||
### Windows
|
||||
|
||||
Install the [Atom chocolatey package](https://chocolatey.org/packages/Atom).
|
||||
|
||||
1. Install [chocolatey](https://chocolatey.org).
|
||||
2. Close and reopen your command prompt or PowerShell window.
|
||||
3. Run `cinst Atom`
|
||||
4. In the future run `cup Atom` to upgrade to the latest release.
|
||||
|
||||
## Building
|
||||
|
||||
* [Linux](docs/build-instructions/linux.md)
|
||||
@@ -18,4 +29,5 @@ Atom will automatically update when a new release is available.
|
||||
* [Windows](docs/build-instructions/windows.md)
|
||||
|
||||
## Developing
|
||||
|
||||
Check out the [guides](https://atom.io/docs/latest) and the [API reference](https://atom.io/docs/api).
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.69.0"
|
||||
"atom-package-manager": "0.77.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,21 +39,25 @@ module.exports = (grunt) ->
|
||||
tmpDir = os.tmpdir()
|
||||
appName = if process.platform is 'darwin' then 'Atom.app' else 'Atom'
|
||||
buildDir = grunt.option('build-dir') ? path.join(tmpDir, 'atom-build')
|
||||
installDir = grunt.option('install-dir')
|
||||
atomShellDownloadDir = path.join(os.tmpdir(), 'atom-cached-atom-shells')
|
||||
symbolsDir = path.join(buildDir, 'Atom.breakpad.syms')
|
||||
shellAppDir = path.join(buildDir, appName)
|
||||
if process.platform is 'win32'
|
||||
contentsDir = shellAppDir
|
||||
appDir = path.join(shellAppDir, 'resources', 'app')
|
||||
installDir = path.join(process.env.ProgramFiles, appName)
|
||||
installDir ?= path.join(process.env.ProgramFiles, appName)
|
||||
killCommand = 'taskkill /F /IM atom.exe'
|
||||
else if process.platform is 'darwin'
|
||||
contentsDir = path.join(shellAppDir, 'Contents')
|
||||
appDir = path.join(contentsDir, 'Resources', 'app')
|
||||
installDir = path.join('/Applications', appName)
|
||||
installDir ?= path.join('/Applications', appName)
|
||||
killCommand = 'pkill -9 Atom'
|
||||
else
|
||||
contentsDir = shellAppDir
|
||||
appDir = path.join(shellAppDir, 'resources', 'app')
|
||||
installDir = process.env.INSTALL_PREFIX ? '/usr/local'
|
||||
installDir ?= process.env.INSTALL_PREFIX ? '/usr/local'
|
||||
killCommand ='pkill -9 Atom'
|
||||
|
||||
coffeeConfig =
|
||||
glob_to_multiple:
|
||||
@@ -121,6 +125,8 @@ module.exports = (grunt) ->
|
||||
|
||||
atom: {appDir, appName, symbolsDir, buildDir, contentsDir, installDir, shellAppDir}
|
||||
|
||||
docsOutputDir: 'docs/output/api'
|
||||
|
||||
coffee: coffeeConfig
|
||||
|
||||
less: lessConfig
|
||||
@@ -210,7 +216,7 @@ module.exports = (grunt) ->
|
||||
|
||||
shell:
|
||||
'kill-atom':
|
||||
command: 'pkill -9 Atom'
|
||||
command: killCommand
|
||||
options:
|
||||
stdout: false
|
||||
stderr: false
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"github-releases": "~0.2.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.9",
|
||||
"grunt-coffeelint": "git://github.com/atom/grunt-coffeelint.git",
|
||||
"grunt-coffeelint": "git+https://github.com/atom/grunt-coffeelint.git",
|
||||
"grunt-contrib-csslint": "~0.1.2",
|
||||
"grunt-contrib-coffee": "~0.9.0",
|
||||
"grunt-contrib-less": "~0.8.0",
|
||||
|
||||
@@ -51,7 +51,17 @@ module.exports = (grunt) ->
|
||||
path.join('pegjs', 'examples')
|
||||
path.join('plist', 'tests')
|
||||
path.join('xmldom', 'test')
|
||||
path.join('combined-stream', 'test')
|
||||
path.join('delayed-stream', 'test')
|
||||
path.join('domhandler', 'test')
|
||||
path.join('fstream-ignore', 'test')
|
||||
path.join('harmony-collections', 'test')
|
||||
path.join('lru-cache', 'test')
|
||||
path.join('minimatch', 'test')
|
||||
path.join('normalize-package-data', 'test')
|
||||
path.join('npm', 'test')
|
||||
path.join('jasmine-reporters', 'ext')
|
||||
path.join('jasmine-node', 'node_modules', 'gaze')
|
||||
path.join('build', 'Release', 'obj.target')
|
||||
path.join('build', 'Release', 'obj')
|
||||
path.join('build', 'Release', '.deps')
|
||||
@@ -90,6 +100,11 @@ module.exports = (grunt) ->
|
||||
cp path.join('resources', 'win', 'msvcp100.dll'), path.join(shellAppDir, 'msvcp100.dll')
|
||||
cp path.join('resources', 'win', 'msvcr100.dll'), path.join(shellAppDir, 'msvcr100.dll')
|
||||
|
||||
# Set up chocolatey ignore and gui files
|
||||
fs.writeFileSync path.join(appDir, 'apm', 'node_modules', 'atom-package-manager', 'bin', 'node.exe.ignore'), ''
|
||||
fs.writeFileSync path.join(appDir, 'node_modules', 'symbols-view', 'vendor', 'ctags-win32.exe.ignore'), ''
|
||||
fs.writeFileSync path.join(shellAppDir, 'atom.exe.gui'), ''
|
||||
|
||||
dependencies = ['compile', "generate-license:save"]
|
||||
dependencies.push('copy-info-plist') if process.platform is 'darwin'
|
||||
dependencies.push('set-exe-icon') if process.platform is 'win32'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
path = require 'path'
|
||||
|
||||
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
|
||||
if process.platform is 'darwin' and process.env.XCODE_KEYCHAIN
|
||||
unlockKeychain (error) ->
|
||||
if error?
|
||||
done(error)
|
||||
@@ -22,6 +22,15 @@ module.exports = (grunt) ->
|
||||
spawn {cmd, args}, (error) -> callback(error)
|
||||
|
||||
signApp = (callback) ->
|
||||
cmd = 'codesign'
|
||||
args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
|
||||
spawn {cmd, args}, (error) -> callback(error)
|
||||
switch process.platform
|
||||
when 'darwin'
|
||||
cmd = 'codesign'
|
||||
args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
|
||||
spawn {cmd, args}, (error) -> callback(error)
|
||||
when 'win32'
|
||||
spawn {cmd: 'taskkill', args: ['/F', '/IM', 'atom.exe']}, ->
|
||||
cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
|
||||
args = [path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')]
|
||||
spawn {cmd, args}, (error) -> callback(error)
|
||||
else
|
||||
callback()
|
||||
|
||||
@@ -15,15 +15,17 @@ module.exports = (grunt) ->
|
||||
grunt.registerTask 'build-docs', 'Builds the API docs in src', ->
|
||||
done = @async()
|
||||
|
||||
docsOutputDir = grunt.config.get('docsOutputDir')
|
||||
|
||||
downloadIncludes (error, includePaths) ->
|
||||
if error?
|
||||
done(error)
|
||||
else
|
||||
rm('docs/output/api')
|
||||
rm(docsOutputDir)
|
||||
args = [
|
||||
commonArgs...
|
||||
'--title', 'Atom API Documentation'
|
||||
'-o', 'docs/output/api'
|
||||
'-o', docsOutputDir
|
||||
'-r', 'docs/README.md'
|
||||
'--stability', '1'
|
||||
'src/'
|
||||
|
||||
@@ -40,13 +40,15 @@ module.exports = (grunt) ->
|
||||
mkdir path.dirname(shareDir)
|
||||
cp shellAppDir, shareDir
|
||||
|
||||
# Create Atom.desktop if installation in '/usr/local'
|
||||
applicationsDir = path.join('/usr','share','applications')
|
||||
# Create Atom.desktop if installation not in temporary folder
|
||||
tmpDir = if process.env.TMPDIR? then process.env.TMPDIR else '/tmp'
|
||||
if installDir.indexOf(tmpDir) isnt 0 and fs.isDirectorySync(applicationsDir)
|
||||
desktopInstallFile = path.join(installDir,'share','applications','Atom.desktop')
|
||||
if installDir.indexOf(tmpDir) isnt 0
|
||||
mkdir path.dirname(desktopInstallFile)
|
||||
{description} = grunt.file.readJSON('package.json')
|
||||
installDir = path.join(installDir,'.') # To prevent "Exec=/usr/local//share/atom/atom"
|
||||
fillTemplate(desktopFile, {description, installDir, iconName})
|
||||
cp desktopFile, path.join(applicationsDir,'Atom.desktop')
|
||||
cp desktopFile, desktopInstallFile
|
||||
|
||||
# Create relative symbol link for apm.
|
||||
process.chdir(binDir)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
path = require 'path'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
grunt.registerTask 'output-long-paths', 'Log long paths in the built application', ->
|
||||
shellAppDir = grunt.config.get('atom.shellAppDir')
|
||||
|
||||
longPaths = []
|
||||
grunt.file.recurse shellAppDir, (absolutePath, rootPath, relativePath, fileName) ->
|
||||
if relativePath
|
||||
fullPath = path.join(relativePath, fileName)
|
||||
else
|
||||
fullPath = fileName
|
||||
longPaths.push(fullPath) if fullPath.length >= 200
|
||||
|
||||
longPaths.sort (longPath1, longPath2) -> longPath2.length - longPath1.length
|
||||
|
||||
longPaths.forEach (longPath) ->
|
||||
grunt.log.error "#{longPath.length} character path: #{longPath}"
|
||||
@@ -9,16 +9,6 @@ request = require 'request'
|
||||
|
||||
grunt = null
|
||||
|
||||
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 =
|
||||
@@ -30,11 +20,19 @@ module.exports = (gruntObject) ->
|
||||
|
||||
grunt.registerTask 'publish-build', 'Publish the built app', ->
|
||||
return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
|
||||
tasks = ['upload-assets']
|
||||
tasks.unshift('build-docs', 'prepare-docs') if process.platform is 'darwin'
|
||||
grunt.task.run(tasks)
|
||||
|
||||
grunt.registerTask 'prepare-docs', 'Move the build docs to the build dir', ->
|
||||
fs.copySync(grunt.config.get('docsOutputDir'), path.join(grunt.config.get('atom.buildDir'), 'atom-docs'))
|
||||
|
||||
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
|
||||
done = @async()
|
||||
buildDir = grunt.config.get('atom.buildDir')
|
||||
assets = getAssets()
|
||||
|
||||
zipApps buildDir, assets, (error) ->
|
||||
zipAssets buildDir, assets, (error) ->
|
||||
return done(error) if error?
|
||||
getAtomDraftRelease (error, release) ->
|
||||
return done(error) if error?
|
||||
@@ -43,26 +41,38 @@ module.exports = (gruntObject) ->
|
||||
return done(error) if error?
|
||||
uploadAssets(release, buildDir, assets, done)
|
||||
|
||||
getAssets = ->
|
||||
if process.platform is 'darwin'
|
||||
[
|
||||
{assetName: 'atom-mac.zip', sourcePath: 'Atom.app'}
|
||||
{assetName: 'atom-mac-symbols.zip', sourcePath: 'Atom.breakpad.syms'}
|
||||
{assetName: 'atom-docs.zip', sourcePath: 'atom-docs'}
|
||||
]
|
||||
else
|
||||
[
|
||||
{assetName: 'atom-windows.zip', sourcePath: 'Atom'}
|
||||
]
|
||||
|
||||
logError = (message, error, details) ->
|
||||
grunt.log.error(message)
|
||||
grunt.log.error(error.message ? error) if error?
|
||||
grunt.log.error(details) if details
|
||||
|
||||
zipApps = (buildDir, assets, callback) ->
|
||||
zip = (directory, sourceName, assetName, callback) ->
|
||||
zipAssets = (buildDir, assets, callback) ->
|
||||
zip = (directory, sourcePath, assetName, callback) ->
|
||||
if process.platform is 'win32'
|
||||
zipCommand = "C:/psmodules/7z.exe a -r #{assetName} #{sourceName}"
|
||||
zipCommand = "C:/psmodules/7z.exe a -r #{assetName} #{sourcePath}"
|
||||
else
|
||||
zipCommand = "zip -r --symlinks #{assetName} #{sourceName}"
|
||||
zipCommand = "zip -r --symlinks #{assetName} #{sourcePath}"
|
||||
options = {cwd: directory, maxBuffer: Infinity}
|
||||
child_process.exec zipCommand, options, (error, stdout, stderr) ->
|
||||
logError("Zipping #{sourceName} failed", error, stderr) if error?
|
||||
logError("Zipping #{sourcePath} failed", error, stderr) if error?
|
||||
callback(error)
|
||||
|
||||
tasks = []
|
||||
for {assetName, sourceName} in assets
|
||||
for {assetName, sourcePath} in assets
|
||||
fs.removeSync(path.join(buildDir, assetName))
|
||||
tasks.push(zip.bind(this, buildDir, sourceName, assetName))
|
||||
tasks.push(zip.bind(this, buildDir, sourcePath, assetName))
|
||||
async.parallel(tasks, callback)
|
||||
|
||||
getAtomDraftRelease = (callback) ->
|
||||
@@ -127,7 +137,7 @@ uploadAssets = (release, buildDir, assets, callback) ->
|
||||
fs.createReadStream(assetPath).pipe(assetRequest)
|
||||
|
||||
tasks = []
|
||||
for {assetName, sourceName} in assets
|
||||
for {assetName} in assets
|
||||
assetPath = path.join(buildDir, assetName)
|
||||
tasks.push(upload.bind(this, release, assetName, assetPath))
|
||||
async.parallel(tasks, callback)
|
||||
|
||||
@@ -53,9 +53,7 @@ module.exports = (grunt) ->
|
||||
continue unless isAtomPackage(packagePath)
|
||||
packageSpecQueue.push(packagePath)
|
||||
|
||||
# TODO: Restore concurrency on Windows
|
||||
packageSpecQueue.concurrency = 1 unless process.platform is 'win32'
|
||||
|
||||
packageSpecQueue.concurrency = 1
|
||||
packageSpecQueue.drain = -> callback(null, failedPackages)
|
||||
|
||||
runCoreSpecs = (callback) ->
|
||||
@@ -105,4 +103,9 @@ module.exports = (grunt) ->
|
||||
failures.push "atom core" if coreSpecFailed
|
||||
|
||||
grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0
|
||||
done(!coreSpecFailed and failedPackages.length == 0)
|
||||
|
||||
if process.platform is 'win32' and process.env.JANKY_SHA1
|
||||
# Package specs are still flaky on Windows CI
|
||||
done(!coreSpecFailed)
|
||||
else
|
||||
done(!coreSpecFailed and failedPackages.length == 0)
|
||||
|
||||
@@ -11,41 +11,31 @@ value of a namespaced config key with `atom.config.get`:
|
||||
@showInvisibles() if atom.config.get "editor.showInvisibles"
|
||||
```
|
||||
|
||||
Or you can use the `::observeConfig` to track changes from any view object.
|
||||
Or you can use the `::subscribe` with `atom.config.observe` to track changes
|
||||
from any view object.
|
||||
|
||||
```coffeescript
|
||||
class MyView extends View
|
||||
initialize: ->
|
||||
@observeConfig 'editor.fontSize', () =>
|
||||
@subscribe atom.config.observe 'editor.fontSize', (newValue, {previous}) =>
|
||||
@adjustFontSize()
|
||||
```
|
||||
|
||||
The `::observeConfig` method will call the given callback immediately with the
|
||||
current value for the specified key path, and it will also call it in the future
|
||||
whenever the value of that key path changes.
|
||||
The `atom.config.observe` method will call the given callback immediately with
|
||||
the current value for the specified key path, and it will also call it in the
|
||||
future whenever the value of that key path changes.
|
||||
|
||||
Subscriptions made with `observeConfig` are automatically canceled when the
|
||||
Subscriptions made with `::subscribe` are automatically canceled when the
|
||||
view is removed. You can cancel config subscriptions manually via the
|
||||
`unobserveConfig` method.
|
||||
`off` method on the subscription object that `atom.config.observe` returns.
|
||||
|
||||
```coffeescript
|
||||
view1.unobserveConfig() # unobserve all properties
|
||||
```
|
||||
fontSizeSubscription = atom.config.observe 'editor.fontSize', (newValue, {previous}) =>
|
||||
@adjustFontSize()
|
||||
|
||||
You can add the ability to observe config values to non-view classes by
|
||||
extending their prototype with the `ConfigObserver` mixin:
|
||||
# ... later on
|
||||
|
||||
```coffeescript
|
||||
{ConfigObserver} = require 'atom'
|
||||
|
||||
class MyClass
|
||||
ConfigObserver.includeInto(this)
|
||||
|
||||
constructor: ->
|
||||
@observeConfig 'editor.showInvisibles', -> # ...
|
||||
|
||||
destroy: ->
|
||||
@unobserveConfig()
|
||||
fontSizeSubscription.off() # Stop observing
|
||||
```
|
||||
|
||||
### Writing Config Settings
|
||||
|
||||
+50
-10
@@ -1,4 +1,4 @@
|
||||
## Atom.io package and update API
|
||||
# Atom.io package and update API
|
||||
|
||||
This guide describes the web API used by [apm](https://github.com/atom/apm) and
|
||||
Atom. The vast majority of use cases are met by the `apm` command-line tool,
|
||||
@@ -17,9 +17,11 @@ For calls to the API that require authentication, provide a valid token from you
|
||||
|
||||
All requests that take parameters require `application/json`.
|
||||
|
||||
## Resources
|
||||
# API Resources
|
||||
|
||||
### Packages
|
||||
## Packages
|
||||
|
||||
### Listing packages
|
||||
|
||||
#### GET /api/packages
|
||||
|
||||
@@ -40,6 +42,8 @@ Returns a list of all packages in the following format:
|
||||
]
|
||||
```
|
||||
|
||||
### Showing package details
|
||||
|
||||
#### GET /api/packages/:package_name
|
||||
|
||||
Returns package details and versions for a single package
|
||||
@@ -68,6 +72,8 @@ Returns:
|
||||
}
|
||||
```
|
||||
|
||||
### Creating a package
|
||||
|
||||
#### POST /api/packages
|
||||
|
||||
Create a new package; requires authentication.
|
||||
@@ -92,6 +98,7 @@ Returns:
|
||||
- The package.json at owner/repo isn't valid
|
||||
- **409** - A package by that name already exists
|
||||
|
||||
### Deleting a package
|
||||
|
||||
#### DELETE /api/packages/:package_name
|
||||
|
||||
@@ -103,6 +110,13 @@ Returns:
|
||||
- **400** - Repository is inaccessible
|
||||
- **401** - Unauthorized
|
||||
|
||||
### Renaming a package
|
||||
|
||||
Packages are renamed by publishing a new version with the name changed in `package.json`
|
||||
See [Creating a new package version](#creating-a-new-package-version) for details.
|
||||
|
||||
Requests made to the previous name will forward to the new name.
|
||||
|
||||
### Package Versions
|
||||
|
||||
#### GET /api/packages/:package_name/versions/:version_name
|
||||
@@ -144,7 +158,9 @@ Returns `package.json` with `dist` key added for e.g. tarball download:
|
||||
|
||||
#### POST /api/packages/:package_name/versions
|
||||
|
||||
Creates a new package version from a git tag; requires authentication.
|
||||
Creates a new package version from a git tag; requires authentication. If `rename`
|
||||
is not `true`, the `name` field in `package.json` *must* match the current package
|
||||
name.
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -152,14 +168,15 @@ Creates a new package version from a git tag; requires authentication.
|
||||
that the version name will not be taken from the tag, but from the `version`
|
||||
key in the `package.json` file at that ref. The authenticating user *must* have
|
||||
access to the package repository.
|
||||
- **rename** - Boolean indicating whether this version contains a new name for the package.
|
||||
|
||||
#### Returns
|
||||
|
||||
- **201** - Successfully created. Returns created version.
|
||||
- **400** - Git tag not found / Repository inaccessible
|
||||
- **400** - Git tag not found / Repository inaccessible / package.json invalid
|
||||
- **409** - Version exists
|
||||
|
||||
### Delete a version
|
||||
### Deleting a version
|
||||
|
||||
#### DELETE /api/packages/:package_name/versions/:version_name
|
||||
|
||||
@@ -172,7 +189,9 @@ you'll need to increment the version when republishing.
|
||||
Returns 204 No Content
|
||||
|
||||
|
||||
### Stars
|
||||
## Stars
|
||||
|
||||
### Listing user stars
|
||||
|
||||
#### GET /api/users/:login/stars
|
||||
|
||||
@@ -186,19 +205,40 @@ List the authenticated user's starred packages; requires authentication.
|
||||
|
||||
Return value is similar to **GET /api/packages**
|
||||
|
||||
### Starring a package
|
||||
|
||||
#### POST /api/packages/:name/star
|
||||
|
||||
Star a package; requires authentication.
|
||||
|
||||
Returns a package.
|
||||
|
||||
### Unstarring a package
|
||||
|
||||
#### DELETE /api/packages/:name/star
|
||||
|
||||
Unstar a package; requires authentication.
|
||||
|
||||
Returns 204 No Content.
|
||||
|
||||
### Atom updates
|
||||
### Listing a package's stargazers
|
||||
|
||||
#### GET /api/packages/:name/stargazers
|
||||
|
||||
List the users that have starred a package.
|
||||
|
||||
Returns a list of user objects:
|
||||
|
||||
```json
|
||||
[
|
||||
{"login":"aperson"},
|
||||
{"login":"anotherperson"},
|
||||
]
|
||||
```
|
||||
|
||||
## Atom updates
|
||||
|
||||
### Listing Atom updates
|
||||
|
||||
#### GET /api/updates
|
||||
|
||||
@@ -206,10 +246,10 @@ Atom update feed, following the format expected by [Squirrel](https://github.com
|
||||
|
||||
Returns:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "0.96.0",
|
||||
"notes": "[HTML release notes]"
|
||||
"notes": "[HTML release notes]",
|
||||
"pub_date": "2014-05-19T15:52:06.000Z",
|
||||
"url": "https://www.atom.io/api/updates/download"
|
||||
}
|
||||
|
||||
@@ -61,6 +61,13 @@ and restart Atom. If Atom now works fine, you can make this setting permanent:
|
||||
|
||||
See also https://github.com/atom/atom/issues/2082.
|
||||
|
||||
### /usr/bin/env: node: No such file or directory
|
||||
|
||||
If you get this notice when attempting to `script/build`, you either do not
|
||||
have nodejs installed, or node isn't identified as nodejs on your machine.
|
||||
If it's the latter, entering `sudo ln -s /usr/bin/nodejs /usr/bin/node` into
|
||||
your terminal may fix the issue.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -169,7 +169,7 @@ For example, to change the color of the cursor, you could add the following
|
||||
rule to your _~/.atom/styles.less_ file:
|
||||
|
||||
```less
|
||||
.editor .cursor {
|
||||
.editor.is-focused .cursor {
|
||||
border-color: pink;
|
||||
}
|
||||
```
|
||||
|
||||
+69
-1
@@ -38,7 +38,7 @@ Atom uses [Jasmine](http://jasmine.github.io/2.0/introduction.html) as its spec
|
||||
|
||||
0. Add one or more expectations
|
||||
|
||||
The best way to learn about expectations is to read the [jasmine documentation](http://jasmine.github.io/2.0/introduction.html#section-Expectations) about them. Below is a simple example.
|
||||
The best way to learn about expectations is to read the [jasmine documentation](http://jasmine.github.io/1.3/introduction.html#section-Expectations) about them. Below is a simple example.
|
||||
|
||||
```coffee
|
||||
describe "when a test is written", ->
|
||||
@@ -47,6 +47,74 @@ Atom uses [Jasmine](http://jasmine.github.io/2.0/introduction.html) as its spec
|
||||
expect("oranges").not.toEqual("apples")
|
||||
```
|
||||
|
||||
## Asynchronous specs
|
||||
|
||||
Writing Asynchronous specs can be tricky at first. Some examples.
|
||||
|
||||
0. Promises
|
||||
|
||||
Working with promises is rather easy in Atom. You can use our `waitsForPromise` function.
|
||||
|
||||
```coffee
|
||||
describe "when we open a file", ->
|
||||
it "should be opened in an editor", ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('c.coffee').then (editor) ->
|
||||
expect(editor.getPath()).toContain 'c.coffee'
|
||||
```
|
||||
|
||||
This method can be used in the `describe`, `it`, `beforeEach` and `afterEach` functions.
|
||||
|
||||
```coffee
|
||||
describe "when we open a file", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open 'c.coffee'
|
||||
|
||||
it "should be opened in an editor", ->
|
||||
expect(atom.workspace.getActiveEditor().getPath()).toContain 'c.coffee'
|
||||
|
||||
```
|
||||
|
||||
If you need to wait for multiple promises use a new `waitsForPromise` function for each promise. (Caution: Without `beforeEach` this example will fail!)
|
||||
|
||||
```coffee
|
||||
describe "waiting for the packages to load", ->
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js')
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('tabs')
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('tree-view')
|
||||
|
||||
it 'should have waited long enough', ->
|
||||
expect(atom.packages.isPackageActive('tabs')).toBe true
|
||||
expect(atom.packages.isPackageActive('tree-view')).toBe true
|
||||
```
|
||||
|
||||
0. Asynchronous functions with callbacks
|
||||
|
||||
Specs for asynchronous functions can be done using the `waitsFor` and `runs` functions. A simple example.
|
||||
|
||||
```coffee
|
||||
describe "fs.readdir(path, cb)", ->
|
||||
it "is async", ->
|
||||
spy = jasmine.createSpy('fs.readdirSpy')
|
||||
|
||||
fs.readdir('/tmp/example', spy)
|
||||
waitsFor ->
|
||||
spy.callCount > 0
|
||||
runs ->
|
||||
exp = [null, ['example.coffee']]
|
||||
expect(spy.mostRecentCall.args).toEqual exp
|
||||
expect(spy).toHaveBeenCalledWith(null, ['example.coffee'])
|
||||
```
|
||||
|
||||
For a more detailed documentation on asynchronous tests please visit the [jasmine documentation](http://jasmine.github.io/1.3/introduction.html#section-Asynchronous_Support).
|
||||
|
||||
|
||||
## Running specs
|
||||
|
||||
Most of the time you'll want to run specs by triggering the `window:run-package-specs` command. This command is not only to run package specs, it is also for Atom core specs. This will run all the specs in the current project's spec directory. If you want to run the Atom core specs and **all** the default package specs trigger the `window:run-all-specs` command.
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
'cmd-}': 'pane:show-next-item'
|
||||
'cmd-alt-left': 'pane:show-previous-item'
|
||||
'cmd-alt-right': 'pane:show-next-item'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'cmd-=': 'window:increase-font-size'
|
||||
|
||||
@@ -38,13 +38,15 @@
|
||||
'shift-pageup': 'core:select-page-up'
|
||||
'shift-pagedown': 'core:select-page-down'
|
||||
'delete': 'core:delete'
|
||||
'shift-delete': 'core:delete'
|
||||
'shift-delete': 'core:cut'
|
||||
'pageup': 'core:page-up'
|
||||
'pagedown': 'core:page-down'
|
||||
'backspace': 'core:backspace'
|
||||
'shift-backspace': 'core:backspace'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-shift-up': 'core:move-up'
|
||||
'ctrl-shift-down': 'core:move-down'
|
||||
'ctrl-=': 'window:increase-font-size'
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'ctrl-s': 'core:save'
|
||||
'ctrl-S': 'core:save-as'
|
||||
'ctrl-w': 'core:close'
|
||||
'ctrl-f4': 'core:close'
|
||||
'ctrl-z': 'core:undo'
|
||||
'ctrl-y': 'core:redo'
|
||||
'ctrl-x': 'core:cut'
|
||||
@@ -40,13 +41,15 @@
|
||||
'shift-pageup': 'core:select-page-up'
|
||||
'shift-pagedown': 'core:select-page-down'
|
||||
'delete': 'core:delete'
|
||||
'shift-delete': 'core:delete'
|
||||
'shift-delete': 'core:cut'
|
||||
'pageup': 'core:page-up'
|
||||
'pagedown': 'core:page-down'
|
||||
'backspace': 'core:backspace'
|
||||
'shift-backspace': 'core:backspace'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-shift-up': 'core:move-up'
|
||||
'ctrl-shift-down': 'core:move-down'
|
||||
'ctrl-alt-up': 'editor:add-selection-above'
|
||||
|
||||
+44
-43
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.107.0",
|
||||
"version": "0.114.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -17,7 +17,7 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.13.2",
|
||||
"atomShellVersion": "0.13.3",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^0.27.0",
|
||||
@@ -27,7 +27,7 @@
|
||||
"coffeestack": "0.7.0",
|
||||
"delegato": "^1",
|
||||
"emissary": "^1.2.1",
|
||||
"first-mate": "^1.7",
|
||||
"first-mate": "^1.7.1",
|
||||
"fs-plus": "^2.2.3",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^1.1",
|
||||
@@ -47,8 +47,8 @@
|
||||
"random-words": "0.0.1",
|
||||
"react-atom-fork": "^0.10.0",
|
||||
"reactionary-atom-fork": "^0.9.0",
|
||||
"runas": "^0.5",
|
||||
"scandal": "0.15.3",
|
||||
"runas": "0.5.4",
|
||||
"scandal": "0.16.0",
|
||||
"scoped-property-store": "^0.9.0",
|
||||
"scrollbar-style": "^0.4.0",
|
||||
"season": "^1.0.2",
|
||||
@@ -62,80 +62,81 @@
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.17.0",
|
||||
"atom-dark-ui": "0.29.0",
|
||||
"atom-light-syntax": "0.18.0",
|
||||
"atom-light-ui": "0.25.0",
|
||||
"base16-tomorrow-dark-theme": "0.17.0",
|
||||
"solarized-dark-syntax": "0.18.0",
|
||||
"solarized-light-syntax": "0.9.0",
|
||||
"atom-dark-syntax": "0.19.0",
|
||||
"atom-dark-ui": "0.32.0",
|
||||
"atom-light-syntax": "0.20.0",
|
||||
"atom-light-ui": "0.28.0",
|
||||
"base16-tomorrow-dark-theme": "0.19.0",
|
||||
"solarized-dark-syntax": "0.20.0",
|
||||
"solarized-light-syntax": "0.11.0",
|
||||
"archive-view": "0.33.0",
|
||||
"autocomplete": "0.28.0",
|
||||
"autoflow": "0.17.0",
|
||||
"autosave": "0.14.0",
|
||||
"background-tips": "0.14.0",
|
||||
"bookmarks": "0.25.0",
|
||||
"bracket-matcher": "0.47.0",
|
||||
"command-palette": "0.23.0",
|
||||
"background-tips": "0.15.0",
|
||||
"bookmarks": "0.27.0",
|
||||
"bracket-matcher": "0.48.0",
|
||||
"command-palette": "0.24.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.120.0",
|
||||
"fuzzy-finder": "0.55.0",
|
||||
"git-diff": "0.34.0",
|
||||
"find-and-replace": "0.126.0",
|
||||
"fuzzy-finder": "0.57.0",
|
||||
"git-diff": "0.35.0",
|
||||
"go-to-line": "0.23.0",
|
||||
"grammar-selector": "0.27.0",
|
||||
"image-view": "0.35.0",
|
||||
"image-view": "0.36.0",
|
||||
"keybinding-resolver": "0.18.0",
|
||||
"link": "0.24.0",
|
||||
"markdown-preview": "0.83.0",
|
||||
"metrics": "0.32.0",
|
||||
"open-on-github": "0.28.0",
|
||||
"markdown-preview": "0.90.0",
|
||||
"metrics": "0.33.0",
|
||||
"open-on-github": "0.29.0",
|
||||
"package-generator": "0.31.0",
|
||||
"release-notes": "0.32.0",
|
||||
"settings-view": "0.128.0",
|
||||
"snippets": "0.46.0",
|
||||
"release-notes": "0.35.0",
|
||||
"settings-view": "0.133.0",
|
||||
"snippets": "0.47.0",
|
||||
"spell-check": "0.38.0",
|
||||
"status-bar": "0.41.0",
|
||||
"styleguide": "0.29.0",
|
||||
"symbols-view": "0.56.0",
|
||||
"tabs": "0.42.0",
|
||||
"timecop": "0.19.0",
|
||||
"tree-view": "0.104.0",
|
||||
"symbols-view": "0.59.0",
|
||||
"tabs": "0.44.0",
|
||||
"timecop": "0.21.0",
|
||||
"tree-view": "0.108.0",
|
||||
"update-package-dependencies": "0.6.0",
|
||||
"welcome": "0.16.0",
|
||||
"whitespace": "0.22.0",
|
||||
"wrap-guide": "0.19.0",
|
||||
"welcome": "0.17.0",
|
||||
"whitespace": "0.24.0",
|
||||
"wrap-guide": "0.21.0",
|
||||
|
||||
"language-c": "0.21.0",
|
||||
"language-coffee-script": "0.22.0",
|
||||
"language-coffee-script": "0.25.0",
|
||||
"language-css": "0.17.0",
|
||||
"language-gfm": "0.40.0",
|
||||
"language-gfm": "0.42.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.12.0",
|
||||
"language-go": "0.13.0",
|
||||
"language-html": "0.22.0",
|
||||
"language-hyperlink": "0.10.0",
|
||||
"language-java": "0.10.0",
|
||||
"language-javascript": "0.28.0",
|
||||
"language-java": "0.11.0",
|
||||
"language-javascript": "0.35.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.9.0",
|
||||
"language-less": "0.12.0",
|
||||
"language-make": "0.10.0",
|
||||
"language-objective-c": "0.11.0",
|
||||
"language-perl": "0.9.0",
|
||||
"language-php": "0.15.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.18.0",
|
||||
"language-ruby": "0.30.0",
|
||||
"language-ruby-on-rails": "0.14.0",
|
||||
"language-ruby": "0.32.0",
|
||||
"language-ruby-on-rails": "0.15.0",
|
||||
"language-sass": "0.13.0",
|
||||
"language-shellscript": "0.8.0",
|
||||
"language-source": "0.7.0",
|
||||
"language-sql": "0.8.0",
|
||||
"language-sql": "0.9.0",
|
||||
"language-text": "0.6.0",
|
||||
"language-todo": "0.10.0",
|
||||
"language-toml": "0.12.0",
|
||||
"language-xml": "0.15.0",
|
||||
"language-yaml": "0.7.0"
|
||||
"language-yaml": "0.11.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+43
-6
@@ -31,7 +31,7 @@ function bootstrap() {
|
||||
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' : '';
|
||||
var apmFlags = process.env.JANKY_SHA1 || process.argv.indexOf('--no-color') !== -1 ? ' --no-color' : '';
|
||||
|
||||
var npmPath = path.resolve(__dirname, '..', 'build', 'node_modules', '.bin', 'npm');
|
||||
var initialNpmCommand = fs.existsSync(npmPath) ? npmPath : 'npm';
|
||||
@@ -39,12 +39,49 @@ function bootstrap() {
|
||||
|
||||
var packagesToDedupe = ['fs-plus', 'humanize-plus', 'oniguruma', 'roaster', 'season', 'grim'];
|
||||
|
||||
var buildInstallCommand = initialNpmCommand + npmFlags + 'install';
|
||||
var buildInstallOptions = {cwd: path.resolve(__dirname, '..', 'build')};
|
||||
var apmInstallCommand = npmPath + npmFlags + 'install';
|
||||
var apmInstallOptions = {cwd: apmInstallPath};
|
||||
var moduleInstallCommand = apmPath + ' install' + apmFlags;
|
||||
var dedupeApmCommand = apmPath + ' dedupe' + apmFlags;
|
||||
var dedupeNpmCommand = npmPath + npmFlags + 'dedupe';
|
||||
|
||||
if (process.argv.indexOf('--no-quiet') === -1) {
|
||||
buildInstallCommand += ' --quiet';
|
||||
apmInstallCommand += ' --quiet';
|
||||
moduleInstallCommand += ' --quiet';
|
||||
dedupeApmCommand += ' --quiet';
|
||||
dedupeNpmCommand += ' --quiet';
|
||||
buildInstallOptions.ignoreStdout = true;
|
||||
apmInstallOptions.ignoreStdout = true;
|
||||
}
|
||||
|
||||
// apm ships with 32-bit node so make sure its native modules are compiled
|
||||
// for a 32-bit target architecture
|
||||
if (process.env.JANKY_SHA1 && process.platform === 'win32')
|
||||
apmInstallCommand += ' --arch=ia32';
|
||||
|
||||
var commands = [
|
||||
{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(' '),
|
||||
{
|
||||
command: buildInstallCommand,
|
||||
message: 'Installing build modules...',
|
||||
options: buildInstallOptions
|
||||
},
|
||||
{
|
||||
command: apmInstallCommand,
|
||||
message: 'Installing apm...',
|
||||
options: apmInstallOptions
|
||||
},
|
||||
apmPath + ' clean' + apmFlags,
|
||||
moduleInstallCommand,
|
||||
dedupeApmCommand + ' ' + packagesToDedupe.join(' '),
|
||||
{
|
||||
command: dedupeNpmCommand + ' request',
|
||||
options: {
|
||||
cwd: path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager')
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
@@ -535,3 +535,18 @@ describe "the `atom` global", ->
|
||||
expect(atom.isReleasedVersion()).toBe true
|
||||
version = '36b5518'
|
||||
expect(atom.isReleasedVersion()).toBe false
|
||||
|
||||
describe "window:update-available", ->
|
||||
it "is triggered when the auto-updater sends the update-downloaded event", ->
|
||||
updateAvailableHandler = jasmine.createSpy("update-available-handler")
|
||||
atom.workspaceView.on 'window:update-available', updateAvailableHandler
|
||||
autoUpdater = require('remote').require('auto-updater')
|
||||
autoUpdater.emit 'update-downloaded', null, "notes", "version"
|
||||
|
||||
waitsFor ->
|
||||
updateAvailableHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
[event, version, notes] = updateAvailableHandler.mostRecentCall.args
|
||||
expect(notes).toBe 'notes'
|
||||
expect(version).toBe 'version'
|
||||
|
||||
@@ -17,6 +17,33 @@ describe "ContextMenuManager", ->
|
||||
expect(contextMenu.definitions['.selector'][0].label).toEqual 'label'
|
||||
expect(contextMenu.definitions['.selector'][0].command).toEqual 'command'
|
||||
|
||||
it 'does not add duplicate menu items', ->
|
||||
contextMenu.add 'file-path',
|
||||
'.selector':
|
||||
'label': 'command'
|
||||
|
||||
contextMenu.add 'file-path',
|
||||
'.selector':
|
||||
'label': 'command'
|
||||
|
||||
expect(contextMenu.definitions['.selector'][0].label).toEqual 'label'
|
||||
expect(contextMenu.definitions['.selector'][0].command).toEqual 'command'
|
||||
expect(contextMenu.definitions['.selector'].length).toBe 1
|
||||
|
||||
it 'allows duplicate commands with different labels', ->
|
||||
contextMenu.add 'file-path',
|
||||
'.selector':
|
||||
'label': 'command'
|
||||
|
||||
contextMenu.add 'file-path',
|
||||
'.selector':
|
||||
'another label': 'command'
|
||||
|
||||
expect(contextMenu.definitions['.selector'][0].label).toEqual 'label'
|
||||
expect(contextMenu.definitions['.selector'][0].command).toEqual 'command'
|
||||
expect(contextMenu.definitions['.selector'][1].label).toEqual 'another label'
|
||||
expect(contextMenu.definitions['.selector'][1].command).toEqual 'command'
|
||||
|
||||
it "loads submenus", ->
|
||||
contextMenu.add 'file-path',
|
||||
'.selector':
|
||||
|
||||
@@ -47,6 +47,15 @@ describe "DisplayBuffer", ->
|
||||
buffer.insert([0,0], oneHundredLines)
|
||||
expect(displayBuffer.getLineCount()).toBe 100 + originalLineCount
|
||||
|
||||
it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", ->
|
||||
displayBuffer.manageScrollPosition = true
|
||||
displayBuffer.setHeight(50)
|
||||
displayBuffer.setLineHeightInPixels(10)
|
||||
displayBuffer.setScrollTop(80)
|
||||
|
||||
buffer.delete([[8, 0], [10, 0]])
|
||||
expect(displayBuffer.getScrollTop()).toBe 60
|
||||
|
||||
describe "soft wrapping", ->
|
||||
beforeEach ->
|
||||
displayBuffer.setSoftWrap(true)
|
||||
@@ -206,6 +215,19 @@ describe "DisplayBuffer", ->
|
||||
displayBuffer.setEditorWidthInChars(-1)
|
||||
expect(displayBuffer.editorWidthInChars).not.toBe -1
|
||||
|
||||
it "sets ::scrollLeft to 0 and keeps it there when soft wrapping is enabled", ->
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
displayBuffer.setWidth(50)
|
||||
displayBuffer.manageScrollPosition = true
|
||||
|
||||
displayBuffer.setSoftWrap(false)
|
||||
displayBuffer.setScrollLeft(Infinity)
|
||||
expect(displayBuffer.getScrollLeft()).toBeGreaterThan 0
|
||||
displayBuffer.setSoftWrap(true)
|
||||
expect(displayBuffer.getScrollLeft()).toBe 0
|
||||
displayBuffer.setScrollLeft(10)
|
||||
expect(displayBuffer.getScrollLeft()).toBe 0
|
||||
|
||||
describe "primitive folding", ->
|
||||
beforeEach ->
|
||||
displayBuffer.destroy()
|
||||
@@ -315,6 +337,14 @@ describe "DisplayBuffer", ->
|
||||
expect(line0.fold).toBe outerFold
|
||||
expect(line1.fold).toBeUndefined()
|
||||
|
||||
describe "when a fold ends where another fold begins", ->
|
||||
it "continues to hide the lines inside the second fold", ->
|
||||
fold2 = displayBuffer.createFold(4, 9)
|
||||
fold1 = displayBuffer.createFold(0, 4)
|
||||
|
||||
expect(displayBuffer.lineForRow(0).text).toMatch /^0/
|
||||
expect(displayBuffer.lineForRow(1).text).toMatch /^10/
|
||||
|
||||
describe "when there is another display buffer pointing to the same buffer", ->
|
||||
it "does not create folds in the other display buffer", ->
|
||||
otherDisplayBuffer = new DisplayBuffer({buffer, tabLength})
|
||||
@@ -624,6 +654,19 @@ describe "DisplayBuffer", ->
|
||||
buffer.delete([[6, 0], [6, 65]])
|
||||
expect(displayBuffer.getMaxLineLength()).toBe 62
|
||||
|
||||
it "correctly updates the location of the longest screen line when changes occur", ->
|
||||
expect(displayBuffer.getLongestScreenRow()).toBe 6
|
||||
buffer.delete([[0, 0], [2, 0]])
|
||||
expect(displayBuffer.getLongestScreenRow()).toBe 4
|
||||
buffer.delete([[4, 0], [5, 0]])
|
||||
|
||||
expect(displayBuffer.getLongestScreenRow()).toBe 1
|
||||
expect(displayBuffer.getMaxLineLength()).toBe 62
|
||||
|
||||
buffer.delete([[2, 0], [4, 0]])
|
||||
expect(displayBuffer.getLongestScreenRow()).toBe 1
|
||||
expect(displayBuffer.getMaxLineLength()).toBe 62
|
||||
|
||||
describe "::destroy()", ->
|
||||
it "unsubscribes all display buffer markers from their underlying buffer marker (regression)", ->
|
||||
marker = displayBuffer.markBufferPosition([12, 2])
|
||||
@@ -986,22 +1029,44 @@ describe "DisplayBuffer", ->
|
||||
|
||||
displayBuffer.setLineHeightInPixels(20)
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
displayBuffer.setScopedCharWidths(["source.js", "keyword.control.js"], r: 11, e: 11, t: 11, u: 11, n: 11)
|
||||
|
||||
for char in ['r', 'e', 't', 'u', 'r', 'n']
|
||||
displayBuffer.setScopedCharWidth(["source.js", "keyword.control.js"], char, 11)
|
||||
|
||||
{start, end} = marker.getPixelRange()
|
||||
expect(start.top).toBe 5 * 20
|
||||
expect(start.left).toBe (4 * 10) + (6 * 11)
|
||||
|
||||
describe "decorations", ->
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
decoration = {type: 'gutter', class: 'one'}
|
||||
[marker, decoration, decorationParams] = []
|
||||
beforeEach ->
|
||||
marker = displayBuffer.markBufferRange([[2, 13], [3, 15]])
|
||||
decorationParams = {type: 'gutter', class: 'one'}
|
||||
decoration = displayBuffer.decorateMarker(marker, decorationParams)
|
||||
|
||||
displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
expect(decoration).toBeDefined()
|
||||
expect(decoration.getParams()).toBe decorationParams
|
||||
expect(displayBuffer.decorationForId(decoration.id)).toBe decoration
|
||||
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
|
||||
|
||||
displayBuffer.removeDecorationForMarker(marker, decoration)
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
|
||||
expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "will not fail if the decoration is removed twice", ->
|
||||
decoration.destroy()
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
describe "when a decoration is updated via Decoration::update()", ->
|
||||
it "emits an 'updated' event containing the new and old params", ->
|
||||
decoration.on 'updated', updatedSpy = jasmine.createSpy()
|
||||
decoration.update type: 'gutter', class: 'two'
|
||||
|
||||
{oldParams, newParams} = updatedSpy.mostRecentCall.args[0]
|
||||
expect(oldParams).toEqual decorationParams
|
||||
expect(newParams).toEqual type: 'gutter', class: 'two', id: decoration.id
|
||||
|
||||
describe "::setScrollTop", ->
|
||||
beforeEach ->
|
||||
@@ -1067,3 +1132,38 @@ describe "DisplayBuffer", ->
|
||||
it "does not scroll vertically if the position is already in view", ->
|
||||
displayBuffer.scrollToScreenPosition([4, 20], center: true)
|
||||
expect(displayBuffer.getScrollTop()).toBe 0
|
||||
|
||||
describe "scroll width", ->
|
||||
cursorWidth = 1
|
||||
beforeEach ->
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
|
||||
it "recomputes the scroll width when the default character width changes", ->
|
||||
expect(displayBuffer.getScrollWidth()).toBe 10 * 65 + cursorWidth
|
||||
|
||||
displayBuffer.setDefaultCharWidth(12)
|
||||
expect(displayBuffer.getScrollWidth()).toBe 12 * 65 + cursorWidth
|
||||
|
||||
it "recomputes the scroll width when the max line length changes", ->
|
||||
buffer.insert([6, 12], ' ')
|
||||
expect(displayBuffer.getScrollWidth()).toBe 10 * 66 + cursorWidth
|
||||
|
||||
buffer.delete([[6, 10], [6, 12]], ' ')
|
||||
expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + cursorWidth
|
||||
|
||||
it "recomputes the scroll width when the scoped character widths change", ->
|
||||
operatorWidth = 20
|
||||
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
|
||||
expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + operatorWidth + cursorWidth
|
||||
|
||||
it "recomputes the scroll width when the scoped character widths change in a batch", ->
|
||||
operatorWidth = 20
|
||||
|
||||
displayBuffer.on 'character-widths-changed', changedSpy = jasmine.createSpy()
|
||||
|
||||
displayBuffer.batchCharacterMeasurement ->
|
||||
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
|
||||
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '?', operatorWidth)
|
||||
|
||||
expect(displayBuffer.getScrollWidth()).toBe 10 * 63 + operatorWidth * 2 + cursorWidth
|
||||
expect(changedSpy.callCount).toBe 1
|
||||
|
||||
+674
-453
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -785,6 +785,19 @@ describe "Editor", ->
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getScrollLeft()).toBe 58 * 10
|
||||
|
||||
it "scrolls down when inserting lines makes the document longer than the editor's height", ->
|
||||
editor.setCursorScreenPosition([13, Infinity])
|
||||
editor.insertNewline()
|
||||
expect(editor.getScrollBottom()).toBe 14 * 10
|
||||
editor.insertNewline()
|
||||
expect(editor.getScrollBottom()).toBe 15 * 10
|
||||
|
||||
it "autoscrolls to the cursor when it moves due to undo", ->
|
||||
editor.insertText('abc')
|
||||
editor.setScrollTop(Infinity)
|
||||
editor.undo()
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
|
||||
describe "selection", ->
|
||||
selection = null
|
||||
|
||||
@@ -1196,6 +1209,7 @@ describe "Editor", ->
|
||||
editor.setHeight(50)
|
||||
editor.setWidth(50)
|
||||
editor.setHorizontalScrollbarHeight(0)
|
||||
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
|
||||
editor.setSelectedBufferRange([[5, 6], [6, 8]], autoscroll: true)
|
||||
@@ -1226,6 +1240,8 @@ describe "Editor", ->
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 0]], [[3, 4], [5, 6]]]
|
||||
|
||||
it "autoscrolls to the added selection if needed", ->
|
||||
editor.manageScrollPosition = true
|
||||
|
||||
editor.setLineHeightInPixels(10)
|
||||
editor.setDefaultCharWidth(10)
|
||||
editor.setHeight(50)
|
||||
@@ -1482,6 +1498,17 @@ describe "Editor", ->
|
||||
expect(cursor1.getBufferPosition()).toEqual [1, 5]
|
||||
expect(cursor2.getBufferPosition()).toEqual [2, 7]
|
||||
|
||||
it "autoscrolls to the last cursor", ->
|
||||
editor.manageScrollPosition = true
|
||||
editor.setCursorScreenPosition([1, 2])
|
||||
editor.addCursorAtScreenPosition([10, 4])
|
||||
editor.setLineHeightInPixels(10)
|
||||
editor.setHeight(50)
|
||||
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
editor.insertText('a')
|
||||
expect(editor.getScrollTop()).toBe 80
|
||||
|
||||
describe "when there are multiple non-empty selections", ->
|
||||
describe "when the selections are on the same line", ->
|
||||
it "replaces each selection range with the inserted characters", ->
|
||||
|
||||
@@ -205,6 +205,20 @@ describe "Project", ->
|
||||
fs.writeFileSync(filePath, sampleContent)
|
||||
fs.writeFileSync(commentFilePath, sampleCommentContent)
|
||||
|
||||
describe "when a file doesn't exist", ->
|
||||
it "calls back with an error", ->
|
||||
errors = []
|
||||
missingPath = path.resolve('/not-a-file.js')
|
||||
expect(fs.existsSync(missingPath)).toBeFalsy()
|
||||
|
||||
waitsForPromise ->
|
||||
atom.project.replace /items/gi, 'items', [missingPath], (result, error) ->
|
||||
errors.push(error)
|
||||
|
||||
runs ->
|
||||
expect(errors).toHaveLength 1
|
||||
expect(errors[0].path).toBe missingPath
|
||||
|
||||
describe "when called with unopened files", ->
|
||||
it "replaces properly", ->
|
||||
results = []
|
||||
|
||||
@@ -37,7 +37,7 @@ jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of e
|
||||
|
||||
if process.platform is 'win32' and process.env.JANKY_SHA1
|
||||
# Use longer timeout on Windows CI
|
||||
jasmine.getEnv().defaultTimeoutInterval = 30000
|
||||
jasmine.getEnv().defaultTimeoutInterval = 60000
|
||||
else
|
||||
jasmine.getEnv().defaultTimeoutInterval = 5000
|
||||
|
||||
|
||||
@@ -335,12 +335,13 @@ describe "TokenizedBuffer", ->
|
||||
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
|
||||
{ tokens } = screenLine0
|
||||
|
||||
expect(tokens.length).toBe 3
|
||||
expect(tokens.length).toBe 4
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[1].value).toBe " Econ 101"
|
||||
expect(tokens[2].value).toBe tabAsSpaces
|
||||
expect(tokens[2].scopes).toEqual tokens[1].scopes
|
||||
expect(tokens[2].isAtomic).toBeTruthy()
|
||||
expect(tokens[3].value).toBe ""
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
|
||||
|
||||
@@ -411,7 +412,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
describe "when the buffer contains surrogate pairs", ->
|
||||
describe "when the buffer contains UTF-8 surrogate pairs", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
@@ -429,7 +430,7 @@ describe "TokenizedBuffer", ->
|
||||
tokenizedBuffer.destroy()
|
||||
buffer.release()
|
||||
|
||||
it "renders each surrogate pair as its own atomic token", ->
|
||||
it "renders each UTF-8 surrogate pair as its own atomic token", ->
|
||||
screenLine0 = tokenizedBuffer.lineForScreenRow(0)
|
||||
expect(screenLine0.text).toBe "'abc\uD835\uDF97def'"
|
||||
{ tokens } = screenLine0
|
||||
@@ -446,11 +447,12 @@ describe "TokenizedBuffer", ->
|
||||
expect(screenLine1.text).toBe "//\uD835\uDF97xyz"
|
||||
{ tokens } = screenLine1
|
||||
|
||||
expect(tokens.length).toBe 3
|
||||
expect(tokens.length).toBe 4
|
||||
expect(tokens[0].value).toBe '//'
|
||||
expect(tokens[1].value).toBe '\uD835\uDF97'
|
||||
expect(tokens[1].value).toBeTruthy()
|
||||
expect(tokens[2].value).toBe 'xyz'
|
||||
expect(tokens[3].value).toBe ''
|
||||
|
||||
describe "when the grammar is tokenized", ->
|
||||
it "emits the `tokenized` event", ->
|
||||
@@ -655,3 +657,65 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
buffer.setText('\n\n\n')
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).indentLevel).toBe 0
|
||||
|
||||
describe "when the changed lines are surrounded by whitespace-only lines", ->
|
||||
it "updates the indentLevel of empty lines that precede the change", ->
|
||||
expect(tokenizedBuffer.lineForScreenRow(12).indentLevel).toBe 0
|
||||
|
||||
buffer.insert([12, 0], '\n')
|
||||
buffer.insert([13, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(12).indentLevel).toBe 1
|
||||
|
||||
it "updates empty line indent guides when the empty line is the last line", ->
|
||||
buffer.insert([12, 2], '\n')
|
||||
|
||||
# The newline and he tab need to be in two different operations to surface the bug
|
||||
buffer.insert([12, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 1
|
||||
|
||||
buffer.insert([12, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(14)).not.toBeDefined()
|
||||
|
||||
it "updates the indentLevel of empty lines surrounding a change that inserts lines", ->
|
||||
# create some new lines
|
||||
buffer.insert([7, 0], '\n\n')
|
||||
buffer.insert([5, 0], '\n\n')
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.lineForScreenRow(6).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.lineForScreenRow(9).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.lineForScreenRow(10).indentLevel).toBe 3
|
||||
expect(tokenizedBuffer.lineForScreenRow(11).indentLevel).toBe 2
|
||||
|
||||
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
|
||||
|
||||
buffer.setTextInRange([[7, 0], [8, 65]], ' one\n two\n three\n four')
|
||||
|
||||
delete changeHandler.argsForCall[0][0].bufferChange
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: 2)
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.lineForScreenRow(6).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.lineForScreenRow(11).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.lineForScreenRow(12).indentLevel).toBe 4
|
||||
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 2
|
||||
|
||||
it "updates the indentLevel of empty lines surrounding a change that removes lines", ->
|
||||
# create some new lines
|
||||
buffer.insert([7, 0], '\n\n')
|
||||
buffer.insert([5, 0], '\n\n')
|
||||
|
||||
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
|
||||
|
||||
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
|
||||
|
||||
delete changeHandler.argsForCall[0][0].bufferChange
|
||||
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(6).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(7).indentLevel).toBe 2 # new text
|
||||
expect(tokenizedBuffer.lineForScreenRow(8).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(9).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(10).indentLevel).toBe 2 # }
|
||||
|
||||
@@ -4,6 +4,20 @@ describe "TokenizedLine", ->
|
||||
beforeEach ->
|
||||
waitsForPromise -> atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
describe "::isOnlyWhitespace()", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.project.open('coffee.coffee').then (o) -> editor = o
|
||||
|
||||
it "returns true when the line is only whitespace", ->
|
||||
expect(editor.lineForScreenRow(3).isOnlyWhitespace()).toBe true
|
||||
expect(editor.lineForScreenRow(7).isOnlyWhitespace()).toBe true
|
||||
expect(editor.lineForScreenRow(23).isOnlyWhitespace()).toBe true
|
||||
|
||||
it "returns false when the line is not only whitespace", ->
|
||||
expect(editor.lineForScreenRow(0).isOnlyWhitespace()).toBe false
|
||||
expect(editor.lineForScreenRow(2).isOnlyWhitespace()).toBe false
|
||||
|
||||
describe "::getScopeTree()", ->
|
||||
it "returns a tree whose inner nodes are scopes and whose leaf nodes are tokens in those scopes", ->
|
||||
[tokens, tokenIndex] = []
|
||||
|
||||
@@ -309,3 +309,28 @@ describe "Workspace", ->
|
||||
expect(handler.callCount).toBe 1
|
||||
editorCopy = editor.copy()
|
||||
expect(handler.callCount).toBe 2
|
||||
|
||||
it "stores the active grammars used by all the open editors", ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.coffee')
|
||||
|
||||
runs ->
|
||||
atom.workspace.getActiveEditor().setText('i = /test/;')
|
||||
|
||||
state = atom.workspace.serialize()
|
||||
expect(state.packagesWithActiveGrammars).toEqual ['language-coffee-script', 'language-javascript']
|
||||
|
||||
jsPackage = atom.packages.getLoadedPackage('language-javascript')
|
||||
coffeePackage = atom.packages.getLoadedPackage('language-coffee-script')
|
||||
spyOn(jsPackage, 'loadGrammarsSync')
|
||||
spyOn(coffeePackage, 'loadGrammarsSync')
|
||||
|
||||
workspace2 = Workspace.deserialize(state)
|
||||
expect(jsPackage.loadGrammarsSync.callCount).toBe 1
|
||||
expect(coffeePackage.loadGrammarsSync.callCount).toBe 1
|
||||
|
||||
+27
-7
@@ -225,9 +225,15 @@ class Atom extends Model
|
||||
else
|
||||
@center()
|
||||
|
||||
# Returns true if the dimensions are useable, false if they should be ignored.
|
||||
# Work around for https://github.com/atom/atom-shell/issues/473
|
||||
isValidDimensions: ({x, y, width, height}={}) ->
|
||||
width > 0 and height > 0 and x + width > 0 and y + height > 0
|
||||
|
||||
storeDefaultWindowDimensions: ->
|
||||
dimensions = JSON.stringify(atom.getWindowDimensions())
|
||||
localStorage.setItem("defaultWindowDimensions", dimensions)
|
||||
dimensions = @getWindowDimensions()
|
||||
if @isValidDimensions(dimensions)
|
||||
localStorage.setItem("defaultWindowDimensions", JSON.stringify(dimensions))
|
||||
|
||||
getDefaultWindowDimensions: ->
|
||||
{windowDimensions} = @getLoadSettings()
|
||||
@@ -240,15 +246,21 @@ class Atom extends Model
|
||||
console.warn "Error parsing default window dimensions", error
|
||||
localStorage.removeItem("defaultWindowDimensions")
|
||||
|
||||
{width, height} = screen.getPrimaryDisplay().workAreaSize
|
||||
dimensions ? {x: 0, y: 0, width: Math.min(1024, width), height: height}
|
||||
if @isValidDimensions(dimensions)
|
||||
dimensions
|
||||
else
|
||||
{width, height} = screen.getPrimaryDisplay().workAreaSize
|
||||
{x: 0, y: 0, width: Math.min(1024, width), height}
|
||||
|
||||
restoreWindowDimensions: ->
|
||||
windowDimensions = @state.windowDimensions ? @getDefaultWindowDimensions()
|
||||
@setWindowDimensions(windowDimensions)
|
||||
dimensions = @state.windowDimensions
|
||||
unless @isValidDimensions(dimensions)
|
||||
dimensions = @getDefaultWindowDimensions()
|
||||
@setWindowDimensions(dimensions)
|
||||
|
||||
storeWindowDimensions: ->
|
||||
@state.windowDimensions = @getWindowDimensions()
|
||||
dimensions = @getWindowDimensions()
|
||||
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
|
||||
|
||||
# Public: Get the load settings for the current window.
|
||||
#
|
||||
@@ -258,13 +270,20 @@ class Atom extends Model
|
||||
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
|
||||
startTime = Date.now()
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath)
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
deserializeWorkspaceView: ->
|
||||
Workspace = require './workspace'
|
||||
WorkspaceView = require './workspace-view'
|
||||
|
||||
startTime = Date.now()
|
||||
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
|
||||
@workspaceView = new WorkspaceView(@workspace)
|
||||
@deserializeTimings.workspace = Date.now() - startTime
|
||||
|
||||
@keymaps.defaultTarget = @workspaceView[0]
|
||||
$(@workspaceViewParentSelector).append(@workspaceView)
|
||||
|
||||
@@ -273,6 +292,7 @@ class Atom extends Model
|
||||
delete @state.packageStates
|
||||
|
||||
deserializeEditorWindow: ->
|
||||
@deserializeTimings = {}
|
||||
@deserializePackageStates()
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
|
||||
@@ -138,6 +138,7 @@ class AtomWindow
|
||||
when 'window:reload' then @reload()
|
||||
when 'window:toggle-dev-tools' then @toggleDevTools()
|
||||
when 'window:close' then @close()
|
||||
when 'window:update-available' then @sendCommandToBrowserWindow(command, args...) # For spec testing
|
||||
else if @isWebViewFocused()
|
||||
@sendCommandToBrowserWindow(command, args...)
|
||||
else
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
https = require 'https'
|
||||
autoUpdater = require 'auto-updater'
|
||||
dialog = require 'dialog'
|
||||
_ = require 'underscore-plus'
|
||||
@@ -16,11 +17,12 @@ class AutoUpdateManager
|
||||
|
||||
constructor: (@version) ->
|
||||
@state = IDLE_STATE
|
||||
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
|
||||
|
||||
# Only released versions should check for updates.
|
||||
return if /\w{7}/.test(@version)
|
||||
if process.platform is 'win32'
|
||||
autoUpdater.checkForUpdates = => @checkForUpdatesShim()
|
||||
|
||||
autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}"
|
||||
autoUpdater.setFeedUrl @feedUrl
|
||||
|
||||
autoUpdater.on 'checking-for-update', =>
|
||||
@setState(CHECKING_STATE)
|
||||
@@ -39,7 +41,25 @@ class AutoUpdateManager
|
||||
@setState(UPDATE_AVAILABLE_STATE)
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
|
||||
@check(hidePopups: true)
|
||||
# Only released versions should check for updates.
|
||||
unless /\w{7}/.test(@version)
|
||||
@check(hidePopups: true)
|
||||
|
||||
# Windows doesn't have an auto-updater, so use this method to shim the events.
|
||||
checkForUpdatesShim: ->
|
||||
autoUpdater.emit 'checking-for-update'
|
||||
request = https.get @feedUrl, (response) ->
|
||||
if response.statusCode == 200
|
||||
body = ""
|
||||
response.on 'data', (chunk) -> body += chunk
|
||||
response.on 'end', ->
|
||||
{notes, name} = JSON.parse(body)
|
||||
autoUpdater.emit 'update-downloaded', null, notes, name
|
||||
else
|
||||
autoUpdater.emit 'update-not-available'
|
||||
|
||||
request.on 'error', (error) ->
|
||||
autoUpdater.emit 'error', null, error.message
|
||||
|
||||
emitUpdateAvailableEvent: (windows...) ->
|
||||
return unless @releaseVersion? and @releaseNotes
|
||||
|
||||
@@ -45,7 +45,10 @@ class BufferedProcess
|
||||
# Related to joyent/node#2318
|
||||
if process.platform is "win32"
|
||||
# Quote all arguments and escapes inner quotes
|
||||
cmdArgs = args.map (arg) -> "\"#{arg.replace(/"/g, '\\"')}\""
|
||||
if args?
|
||||
cmdArgs = args.map (arg) -> "\"#{arg.replace(/"/g, '\\"')}\""
|
||||
else
|
||||
cmdArgs = []
|
||||
cmdArgs.unshift("\"#{command}\"")
|
||||
cmdArgs = ['/s', '/c', "\"#{cmdArgs.join(' ')}\""]
|
||||
cmdOptions = _.clone(options)
|
||||
|
||||
@@ -60,7 +60,8 @@ class ContextMenuManager
|
||||
# editor is in dev mode.
|
||||
addBySelector: (selector, definition, {devMode}={}) ->
|
||||
definitions = if devMode then @devModeDefinitions else @definitions
|
||||
(definitions[selector] ?= []).push(definition)
|
||||
unless _.findWhere(definitions[selector], definition)
|
||||
(definitions[selector] ?= []).push(definition)
|
||||
|
||||
# Returns definitions which match the element and devMode.
|
||||
definitionsForElement: (element, {devMode}={}) ->
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
CursorComponent = React.createClass
|
||||
displayName: 'CursorComponent'
|
||||
|
||||
render: ->
|
||||
{editor, screenRange, scrollTop, scrollLeft, defaultCharWidth} = @props
|
||||
{top, left, height, width} = editor.pixelRectForScreenRange(screenRange)
|
||||
top -= scrollTop
|
||||
left -= scrollLeft
|
||||
{pixelRect, defaultCharWidth} = @props
|
||||
{height, width} = pixelRect
|
||||
width = defaultCharWidth if width is 0
|
||||
WebkitTransform = "translate3d(#{left}px, #{top}px, 0px)"
|
||||
WebkitTransform = @getTransform()
|
||||
|
||||
div className: 'cursor', style: {height, width, WebkitTransform}
|
||||
|
||||
getTransform: ->
|
||||
{pixelRect, scrollTop, scrollLeft, useHardwareAcceleration} = @props
|
||||
{top, left} = pixelRect
|
||||
top -= scrollTop
|
||||
left -= scrollLeft
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(#{left}px, #{top}px, 0px)"
|
||||
else
|
||||
"translate(#{left}px, #{top}px)"
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'pixelRect', 'scrollTop', 'scrollLeft', 'defaultCharWidth')
|
||||
|
||||
+5
-5
@@ -28,9 +28,7 @@ class Cursor extends Model
|
||||
|
||||
# Supports old editor view
|
||||
@needsAutoscroll ?= @isLastCursor() and !textChanged
|
||||
|
||||
# Supports react editor view
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition
|
||||
@autoscroll() if @editor.manageScrollPosition and @isLastCursor() and textChanged
|
||||
|
||||
@goalColumn = null
|
||||
|
||||
@@ -55,8 +53,10 @@ class Cursor extends Model
|
||||
changePosition: (options, fn) ->
|
||||
@clearSelection()
|
||||
@needsAutoscroll = options.autoscroll ? @isLastCursor()
|
||||
unless fn()
|
||||
@emit 'autoscrolled' if @needsAutoscroll
|
||||
fn()
|
||||
if @needsAutoscroll
|
||||
@emit 'autoscrolled' # Support legacy editor
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
|
||||
|
||||
getPixelRect: ->
|
||||
@editor.pixelRectForScreenRange(@getScreenRange())
|
||||
|
||||
@@ -12,7 +12,7 @@ CursorsComponent = React.createClass
|
||||
cursorBlinkIntervalHandle: null
|
||||
|
||||
render: ->
|
||||
{editor, cursorScreenRanges, scrollTop, scrollLeft, defaultCharWidth} = @props
|
||||
{cursorPixelRects, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration} = @props
|
||||
{blinkOff} = @state
|
||||
|
||||
className = 'cursors'
|
||||
@@ -20,8 +20,8 @@ CursorsComponent = React.createClass
|
||||
|
||||
div {className},
|
||||
if @isMounted()
|
||||
for key, screenRange of cursorScreenRanges
|
||||
CursorComponent({key, editor, screenRange, scrollTop, scrollLeft, defaultCharWidth})
|
||||
for key, pixelRect of cursorPixelRects
|
||||
CursorComponent({key, pixelRect, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration})
|
||||
|
||||
getInitialState: ->
|
||||
blinkOff: false
|
||||
@@ -34,13 +34,14 @@ CursorsComponent = React.createClass
|
||||
|
||||
shouldComponentUpdate: (newProps, newState) ->
|
||||
not newState.blinkOff is @state.blinkOff or
|
||||
not isEqualForProperties(newProps, @props,
|
||||
'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels',
|
||||
'defaultCharWidth', 'scopedCharacterWidthsChangeCount'
|
||||
)
|
||||
not isEqualForProperties(newProps, @props, 'cursorPixelRects', 'scrollTop', 'scrollLeft', 'defaultCharWidth', 'useHardwareAcceleration')
|
||||
|
||||
componentWillUpdate: (newProps) ->
|
||||
@pauseCursorBlinking() if @props.cursorScreenRanges and not isEqual(newProps.cursorScreenRanges, @props.cursorScreenRanges)
|
||||
cursorsMoved = @props.cursorPixelRects? and
|
||||
isEqualForProperties(newProps, @props, 'defaultCharWidth', 'scopedCharacterWidthsChangeCount') and
|
||||
not isEqual(newProps.cursorPixelRects, @props.cursorPixelRects)
|
||||
|
||||
@pauseCursorBlinking() if cursorsMoved
|
||||
|
||||
startBlinkingCursors: ->
|
||||
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2) if @isMounted()
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
_ = require 'underscore-plus'
|
||||
{Subscriber, Emitter} = require 'emissary'
|
||||
|
||||
idCounter = 0
|
||||
nextId = -> idCounter++
|
||||
|
||||
# Public: Represents a decoration that follows a {Marker}. A decoration is
|
||||
# basically a visual representation of a marker. It allows you to add CSS
|
||||
# classes to line numbers in the gutter, lines, and add selection-line regions
|
||||
# around marked ranges of text.
|
||||
#
|
||||
# {Decoration} objects are not meant to be created directly, but created with
|
||||
# {Editor::decorateMarker}. eg.
|
||||
#
|
||||
# ```coffee
|
||||
# range = editor.getSelectedBufferRange() # any range you like
|
||||
# marker = editor.markBufferRange(range)
|
||||
# decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
|
||||
# ```
|
||||
#
|
||||
# Best practice for destorying the decoration is by destroying the {Marker}.
|
||||
#
|
||||
# ```
|
||||
# marker.destroy()
|
||||
# ```
|
||||
#
|
||||
# You should only use {Decoration::destroy} when you still need or do not own
|
||||
# the marker.
|
||||
#
|
||||
# ### IDs
|
||||
# Each {Decoration} has a unique ID available via `decoration.id`.
|
||||
#
|
||||
# ### Events
|
||||
# A couple of events are emitted:
|
||||
#
|
||||
# * `destroyed`: When the {Decoration} is destroyed
|
||||
# * `updated`: When the {Decoration} is updated via {Decoration::update}.
|
||||
# Event object has properties `oldParams` and `newParams`
|
||||
#
|
||||
module.exports =
|
||||
class Decoration
|
||||
Emitter.includeInto(this)
|
||||
|
||||
@isType: (decorationParams, type) ->
|
||||
if _.isArray(decorationParams.type)
|
||||
type in decorationParams.type
|
||||
else
|
||||
type is decorationParams.type
|
||||
|
||||
constructor: (@marker, @displayBuffer, @params) ->
|
||||
@id = nextId()
|
||||
@params.id = @id
|
||||
@flashQueue = null
|
||||
@isDestroyed = false
|
||||
|
||||
# Public: Destroy this marker.
|
||||
#
|
||||
# If you own the marker, you should use {Marker:destroy} which will destroy
|
||||
# this decoration.
|
||||
destroy: ->
|
||||
return if @isDestroyed
|
||||
@isDestroyed = true
|
||||
@displayBuffer.removeDecoration(this)
|
||||
@emit 'destoryed'
|
||||
|
||||
# Public: Update the marker with new params. Allows you to change the decoration's class.
|
||||
#
|
||||
# ```
|
||||
# decoration.update({type: 'gutter', class: 'my-new-class'})
|
||||
# ```
|
||||
update: (newParams) ->
|
||||
return if @isDestroyed
|
||||
oldParams = @params
|
||||
@params = newParams
|
||||
@params.id = @id
|
||||
@displayBuffer.decorationUpdated(this)
|
||||
@emit 'updated', {oldParams, newParams}
|
||||
|
||||
# Public: Returns the marker associated with this {Decoration}
|
||||
getMarker: -> @marker
|
||||
|
||||
# Public: Returns the {Decoration}'s params.
|
||||
getParams: -> @params
|
||||
|
||||
# Public: Check if this decoration is of type `type`
|
||||
#
|
||||
# type - A {String} type like `'gutter'`
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
isType: (type) ->
|
||||
Decoration.isType(@params, type)
|
||||
|
||||
matchesPattern: (decorationPattern) ->
|
||||
return false unless decorationPattern?
|
||||
for key, value of decorationPattern
|
||||
return false if @params[key] != value
|
||||
true
|
||||
|
||||
flash: (klass, duration=500) ->
|
||||
flashObject = {class: klass, duration}
|
||||
@flashQueue ?= []
|
||||
@flashQueue.push(flashObject)
|
||||
@emit 'flash'
|
||||
|
||||
consumeNextFlash: ->
|
||||
return @flashQueue.shift() if @flashQueue?.length > 0
|
||||
null
|
||||
+96
-54
@@ -8,6 +8,7 @@ TokenizedBuffer = require './tokenized-buffer'
|
||||
RowMap = require './row-map'
|
||||
Fold = require './fold'
|
||||
Token = require './token'
|
||||
Decoration = require './decoration'
|
||||
DisplayBufferMarker = require './display-buffer-marker'
|
||||
|
||||
class BufferToScreenConversionError extends Error
|
||||
@@ -29,6 +30,7 @@ class DisplayBuffer extends Model
|
||||
width: null
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
scrollWidth: 0
|
||||
|
||||
verticalScrollMargin: 2
|
||||
horizontalScrollMargin: 6
|
||||
@@ -44,8 +46,10 @@ class DisplayBuffer extends Model
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@decorationsById = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@decorationMarkerSubscriptions = {}
|
||||
@decorationMarkerChangedSubscriptions = {}
|
||||
@decorationMarkerDestroyedSubscriptions = {}
|
||||
@updateAllScreenLines()
|
||||
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
||||
@subscribe @tokenizedBuffer, 'grammar-changed', (grammar) => @emit 'grammar-changed', grammar
|
||||
@@ -180,9 +184,12 @@ class DisplayBuffer extends Model
|
||||
getScrollTop: -> @scrollTop
|
||||
setScrollTop: (scrollTop) ->
|
||||
if @manageScrollPosition
|
||||
@scrollTop = Math.max(0, Math.min(@getScrollHeight() - @getClientHeight(), scrollTop))
|
||||
@scrollTop = Math.round(Math.max(0, Math.min(@getMaxScrollTop(), scrollTop)))
|
||||
else
|
||||
@scrollTop = scrollTop
|
||||
@scrollTop = Math.round(scrollTop)
|
||||
|
||||
getMaxScrollTop: ->
|
||||
@getScrollHeight() - @getClientHeight()
|
||||
|
||||
getScrollBottom: -> @scrollTop + @height
|
||||
setScrollBottom: (scrollBottom) ->
|
||||
@@ -192,10 +199,13 @@ class DisplayBuffer extends Model
|
||||
getScrollLeft: -> @scrollLeft
|
||||
setScrollLeft: (scrollLeft) ->
|
||||
if @manageScrollPosition
|
||||
@scrollLeft = Math.max(0, Math.min(@getScrollWidth() - @getClientWidth(), scrollLeft))
|
||||
@scrollLeft = Math.round(Math.max(0, Math.min(@getScrollWidth() - @getClientWidth(), scrollLeft)))
|
||||
@scrollLeft
|
||||
else
|
||||
@scrollLeft = scrollLeft
|
||||
@scrollLeft = Math.round(scrollLeft)
|
||||
|
||||
getMaxScrollLeft: ->
|
||||
@getScrollWidth() - @getClientWidth()
|
||||
|
||||
getScrollRight: -> @scrollLeft + @width
|
||||
setScrollRight: (scrollRight) ->
|
||||
@@ -206,7 +216,11 @@ class DisplayBuffer extends Model
|
||||
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
|
||||
|
||||
getDefaultCharWidth: -> @defaultCharWidth
|
||||
setDefaultCharWidth: (@defaultCharWidth) -> @defaultCharWidth
|
||||
setDefaultCharWidth: (defaultCharWidth) ->
|
||||
if defaultCharWidth isnt @defaultCharWidth
|
||||
@defaultCharWidth = defaultCharWidth
|
||||
@computeScrollWidth()
|
||||
defaultCharWidth
|
||||
|
||||
getCursorWidth: -> 1
|
||||
|
||||
@@ -221,13 +235,21 @@ class DisplayBuffer extends Model
|
||||
scope.charWidths ?= {}
|
||||
scope.charWidths
|
||||
|
||||
batchCharacterMeasurement: (fn) ->
|
||||
oldChangeCount = @scopedCharacterWidthsChangeCount
|
||||
@batchingCharacterMeasurement = true
|
||||
fn()
|
||||
@batchingCharacterMeasurement = false
|
||||
@characterWidthsChanged() if oldChangeCount isnt @scopedCharacterWidthsChangeCount
|
||||
|
||||
setScopedCharWidth: (scopeNames, char, width) ->
|
||||
@getScopedCharWidths(scopeNames)[char] = width
|
||||
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount++
|
||||
@scopedCharacterWidthsChangeCount++
|
||||
@characterWidthsChanged() unless @batchingCharacterMeasurement
|
||||
|
||||
setScopedCharWidths: (scopeNames, charWidths) ->
|
||||
_.extend(@getScopedCharWidths(scopeNames), charWidths)
|
||||
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount++
|
||||
characterWidthsChanged: ->
|
||||
@computeScrollWidth()
|
||||
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount
|
||||
|
||||
clearScopedCharWidths: ->
|
||||
@charWidthsByScope = {}
|
||||
@@ -238,7 +260,7 @@ class DisplayBuffer extends Model
|
||||
@getLineCount() * @getLineHeightInPixels()
|
||||
|
||||
getScrollWidth: ->
|
||||
(@getMaxLineLength() * @getDefaultCharWidth()) + @getCursorWidth()
|
||||
@scrollWidth
|
||||
|
||||
getVisibleRowRange: ->
|
||||
return [0, 0] unless @getLineHeightInPixels() > 0
|
||||
@@ -576,6 +598,12 @@ class DisplayBuffer extends Model
|
||||
getMaxLineLength: ->
|
||||
@maxLineLength
|
||||
|
||||
# Gets the row number of the longest screen line.
|
||||
#
|
||||
# Return a {}
|
||||
getLongestScreenRow: ->
|
||||
@longestScreenRow
|
||||
|
||||
# Given a buffer position, this converts it into a screen position.
|
||||
#
|
||||
# bufferPosition - An object that represents a buffer position. It can be either
|
||||
@@ -729,53 +757,48 @@ class DisplayBuffer extends Model
|
||||
rangeForAllLines: ->
|
||||
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
|
||||
|
||||
decorationForId: (id) ->
|
||||
@decorationsById[id]
|
||||
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsByMarkerId = {}
|
||||
|
||||
for marker in @findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
decorationMatchesType: (decoration, type) ->
|
||||
if _.isArray(decoration.type)
|
||||
type in decoration.type
|
||||
else
|
||||
type is decoration.type
|
||||
|
||||
decorationMatchesPattern: (decoration, decorationPattern) ->
|
||||
return false unless decoration? and decorationPattern?
|
||||
for key, value of decorationPattern
|
||||
return false if decoration[key] != value
|
||||
true
|
||||
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
unless marker?
|
||||
console.warn 'A null marker cannot be decorated'
|
||||
return
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
marker = @getMarker(marker.id)
|
||||
@decorationMarkerSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', => @removeAllDecorationsForMarker(marker)
|
||||
|
||||
@decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', =>
|
||||
@removeAllDecorationsForMarker(marker)
|
||||
|
||||
@decorationMarkerChangedSubscriptions[marker.id] ?= @subscribe marker, 'changed', (event) =>
|
||||
decorations = @decorationsByMarkerId[marker.id]
|
||||
|
||||
# Why check existence? Markers may get destroyed or decorations removed
|
||||
# in the change handler. Bookmarks does this.
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
@emit 'decoration-changed', marker, decoration, event
|
||||
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@decorationsById[decoration.id] = decoration
|
||||
@emit 'decoration-added', marker, decoration
|
||||
decoration
|
||||
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
unless marker?
|
||||
console.warn 'A decoration cannot be removed from a null marker'
|
||||
return
|
||||
removeDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
return unless @decorationMarkerSubscriptions[marker.id]?
|
||||
|
||||
decorations = @decorationsByMarkerId[marker.id]
|
||||
for i in [decorations.length - 1..0]
|
||||
decoration = decorations[i]
|
||||
if @decorationMatchesPattern(decoration, decorationPattern)
|
||||
decorations.splice(i, 1)
|
||||
@emit 'decoration-removed', marker, decoration
|
||||
|
||||
@removedAllMarkerDecorations(marker) if decorations.length is 0
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emit 'decoration-removed', marker, decoration
|
||||
@removedAllMarkerDecorations(marker) if decorations.length is 0
|
||||
|
||||
removeAllDecorationsForMarker: (marker) ->
|
||||
decorations = @decorationsByMarkerId[marker.id].slice()
|
||||
@@ -784,9 +807,15 @@ class DisplayBuffer extends Model
|
||||
@removedAllMarkerDecorations(marker)
|
||||
|
||||
removedAllMarkerDecorations: (marker) ->
|
||||
@decorationMarkerSubscriptions[marker.id].off()
|
||||
@decorationMarkerChangedSubscriptions[marker.id].off()
|
||||
@decorationMarkerDestroyedSubscriptions[marker.id].off()
|
||||
|
||||
delete @decorationsByMarkerId[marker.id]
|
||||
delete @decorationMarkerSubscriptions[marker.id]
|
||||
delete @decorationMarkerChangedSubscriptions[marker.id]
|
||||
delete @decorationMarkerDestroyedSubscriptions[marker.id]
|
||||
|
||||
decorationUpdated: (decoration) ->
|
||||
@emit 'decoration-updated', decoration
|
||||
|
||||
# Retrieves a {DisplayBufferMarker} based on its id.
|
||||
#
|
||||
@@ -941,7 +970,7 @@ class DisplayBuffer extends Model
|
||||
for marker in @getMarkers()
|
||||
marker.notifyObservers(textChanged: false)
|
||||
|
||||
destroy: ->
|
||||
destroyed: ->
|
||||
marker.unsubscribe() for marker in @getMarkers()
|
||||
@tokenizedBuffer.destroy()
|
||||
@unsubscribe()
|
||||
@@ -954,24 +983,26 @@ class DisplayBuffer extends Model
|
||||
handleTokenizedBufferChange: (tokenizedBufferChange) =>
|
||||
{start, end, delta, bufferChange} = tokenizedBufferChange
|
||||
@updateScreenLines(start, end + 1, delta, delayChangeEvent: bufferChange?)
|
||||
@setScrollTop(Math.min(@getScrollTop(), @getMaxScrollTop())) if @manageScrollPosition and delta < 0
|
||||
|
||||
updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) ->
|
||||
startBufferRow = @rowMap.bufferRowRangeForBufferRow(startBufferRow)[0]
|
||||
endBufferRow = @rowMap.bufferRowRangeForBufferRow(endBufferRow - 1)[1]
|
||||
startScreenRow = @rowMap.screenRowRangeForBufferRow(startBufferRow)[0]
|
||||
endScreenRow = @rowMap.screenRowRangeForBufferRow(endBufferRow - 1)[1]
|
||||
|
||||
{screenLines, regions} = @buildScreenLines(startBufferRow, endBufferRow + bufferDelta)
|
||||
screenDelta = screenLines.length - (endScreenRow - startScreenRow)
|
||||
|
||||
@screenLines[startScreenRow...endScreenRow] = screenLines
|
||||
@rowMap.spliceRegions(startBufferRow, endBufferRow - startBufferRow, regions)
|
||||
@findMaxLineLength(startScreenRow, endScreenRow, screenLines)
|
||||
@findMaxLineLength(startScreenRow, endScreenRow, screenLines, screenDelta)
|
||||
|
||||
return if options.suppressChangeEvent
|
||||
|
||||
changeEvent =
|
||||
start: startScreenRow
|
||||
end: endScreenRow - 1
|
||||
screenDelta: screenLines.length - (endScreenRow - startScreenRow)
|
||||
screenDelta: screenDelta
|
||||
bufferDelta: bufferDelta
|
||||
|
||||
if options.delayChangeEvent
|
||||
@@ -1026,22 +1057,33 @@ class DisplayBuffer extends Model
|
||||
|
||||
{screenLines, regions}
|
||||
|
||||
findMaxLineLength: (startScreenRow, endScreenRow, newScreenLines) ->
|
||||
findMaxLineLength: (startScreenRow, endScreenRow, newScreenLines, screenDelta) ->
|
||||
oldMaxLineLength = @maxLineLength
|
||||
|
||||
if startScreenRow <= @longestScreenRow < endScreenRow
|
||||
@longestScreenRow = 0
|
||||
@maxLineLength = 0
|
||||
maxLengthCandidatesStartRow = 0
|
||||
maxLengthCandidates = @screenLines
|
||||
else
|
||||
@longestScreenRow += screenDelta if endScreenRow < @longestScreenRow
|
||||
maxLengthCandidatesStartRow = startScreenRow
|
||||
maxLengthCandidates = newScreenLines
|
||||
|
||||
for screenLine, screenRow in maxLengthCandidates
|
||||
for screenLine, i in maxLengthCandidates
|
||||
screenRow = maxLengthCandidatesStartRow + i
|
||||
length = screenLine.text.length
|
||||
if length > @maxLineLength
|
||||
@longestScreenRow = maxLengthCandidatesStartRow + screenRow
|
||||
@longestScreenRow = screenRow
|
||||
@maxLineLength = length
|
||||
|
||||
@computeScrollWidth() if oldMaxLineLength isnt @maxLineLength
|
||||
|
||||
computeScrollWidth: ->
|
||||
@scrollWidth = @pixelPositionForScreenPosition([@longestScreenRow, @maxLineLength]).left
|
||||
@scrollWidth += 1 unless @getSoftWrap()
|
||||
@setScrollLeft(Math.min(@getScrollLeft(), @getMaxScrollLeft()))
|
||||
|
||||
handleBufferMarkersUpdated: =>
|
||||
if event = @pendingChangeEvent
|
||||
@pendingChangeEvent = null
|
||||
@@ -1052,7 +1094,7 @@ class DisplayBuffer extends Model
|
||||
@emit 'marker-created', @getMarker(marker.id)
|
||||
|
||||
createFoldForMarker: (marker) ->
|
||||
@addDecorationForMarker(marker, type: 'gutter', class: 'folded')
|
||||
@decorateMarker(marker, type: 'gutter', class: 'folded')
|
||||
new Fold(this, marker)
|
||||
|
||||
foldForMarker: (marker) ->
|
||||
|
||||
+130
-53
@@ -12,7 +12,7 @@ ScrollbarComponent = require './scrollbar-component'
|
||||
ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
DummyHighlightDecoration = {id: 'dummy', screenRange: new Range(new Point(0, 0), new Point(0, 0)), decorations: [{class: 'dummy'}]}
|
||||
DummyHighlightDecoration = {id: 'dummy', startPixelPosition: {top: 0, left: 0}, endPixelPosition: {top: 0, left: 0}, decorations: [{class: 'dummy'}]}
|
||||
|
||||
module.exports =
|
||||
EditorComponent = React.createClass
|
||||
@@ -26,6 +26,8 @@ EditorComponent = React.createClass
|
||||
pendingScrollLeft: null
|
||||
selectOnMouseMove: false
|
||||
updateRequested: false
|
||||
updatesPaused: false
|
||||
updateRequestedWhilePaused: false
|
||||
cursorsMoved: false
|
||||
selectionChanged: false
|
||||
selectionAdded: false
|
||||
@@ -40,13 +42,14 @@ EditorComponent = React.createClass
|
||||
scrollSensitivity: 0.4
|
||||
scrollViewMeasurementRequested: false
|
||||
measureLineHeightAndDefaultCharWidthWhenShown: false
|
||||
remeasureCharacterWidthsWhenShown: false
|
||||
remeasureCharacterWidthsIfVisibleAfterNextUpdate: false
|
||||
inputEnabled: true
|
||||
scrollViewMeasurementInterval: 100
|
||||
scopedCharacterWidthsChangeCount: null
|
||||
scrollViewMeasurementPaused: false
|
||||
|
||||
render: ->
|
||||
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state
|
||||
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state
|
||||
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||
maxLineNumberDigits = editor.getLineCount().toString().length
|
||||
invisibles = if showInvisibles then @state.invisibles else {}
|
||||
@@ -55,7 +58,7 @@ EditorComponent = React.createClass
|
||||
if @isMounted()
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
cursorScreenRanges = @getCursorScreenRanges(renderedRowRange)
|
||||
cursorPixelRects = @getCursorPixelRects(renderedRowRange)
|
||||
|
||||
decorations = editor.decorationsForScreenRowRange(renderedStartRow, renderedEndRow)
|
||||
highlightDecorations = @getHighlightDecorations(decorations)
|
||||
@@ -68,12 +71,13 @@ EditorComponent = React.createClass
|
||||
lineHeightInPixels = editor.getLineHeightInPixels()
|
||||
defaultCharWidth = editor.getDefaultCharWidth()
|
||||
scrollViewHeight = editor.getHeight()
|
||||
lineWidth = Math.max(scrollWidth, editor.getWidth())
|
||||
horizontalScrollbarHeight = editor.getHorizontalScrollbarHeight()
|
||||
verticalScrollbarWidth = editor.getVerticalScrollbarWidth()
|
||||
verticallyScrollable = editor.verticallyScrollable()
|
||||
horizontallyScrollable = editor.horizontallyScrollable()
|
||||
hiddenInputStyle = @getHiddenInputPosition()
|
||||
hiddenInputStyle.WebkitTransform = 'translateZ(0)'
|
||||
hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @useHardwareAcceleration
|
||||
if @mouseWheelScreenRow? and not (renderedStartRow <= @mouseWheelScreenRow < renderedEndRow)
|
||||
mouseWheelScreenRow = @mouseWheelScreenRow
|
||||
|
||||
@@ -82,11 +86,12 @@ EditorComponent = React.createClass
|
||||
className += ' has-selection' if hasSelection
|
||||
|
||||
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
|
||||
GutterComponent {
|
||||
ref: 'gutter', onMouseDown: @onGutterMouseDown, onWidthChanged: @onGutterWidthChanged,
|
||||
lineDecorations, defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
|
||||
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow
|
||||
}
|
||||
if showLineNumbers
|
||||
GutterComponent {
|
||||
ref: 'gutter', onMouseDown: @onGutterMouseDown, onWidthChanged: @onGutterWidthChanged,
|
||||
lineDecorations, defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
|
||||
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow, @useHardwareAcceleration
|
||||
}
|
||||
|
||||
div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown,
|
||||
InputComponent
|
||||
@@ -97,15 +102,15 @@ EditorComponent = React.createClass
|
||||
onBlur: @onInputBlurred
|
||||
|
||||
CursorsComponent {
|
||||
editor, scrollTop, scrollLeft, cursorScreenRanges, cursorBlinkPeriod, cursorBlinkResumeDelay,
|
||||
lineHeightInPixels, defaultCharWidth, @scopedCharacterWidthsChangeCount
|
||||
scrollTop, scrollLeft, cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay,
|
||||
lineHeightInPixels, defaultCharWidth, @scopedCharacterWidthsChangeCount, @useHardwareAcceleration
|
||||
}
|
||||
LinesComponent {
|
||||
ref: 'lines',
|
||||
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
|
||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
|
||||
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
visible, scrollViewHeight, @scopedCharacterWidthsChangeCount
|
||||
visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration
|
||||
}
|
||||
|
||||
ScrollbarComponent
|
||||
@@ -161,7 +166,7 @@ EditorComponent = React.createClass
|
||||
componentDidMount: ->
|
||||
{editor} = @props
|
||||
|
||||
@scrollViewMeasurementIntervalId = setInterval(@requestScrollViewMeasurement, @scrollViewMeasurementInterval)
|
||||
@scrollViewMeasurementIntervalId = setInterval(@measureScrollView, @scrollViewMeasurementInterval)
|
||||
|
||||
@observeEditor()
|
||||
@listenForDOMEvents()
|
||||
@@ -177,12 +182,11 @@ EditorComponent = React.createClass
|
||||
@measureScrollbars()
|
||||
|
||||
componentWillUnmount: ->
|
||||
@props.parentView.trigger 'editor:will-be-removed', [@props.parentView]
|
||||
@unsubscribe()
|
||||
clearInterval(@scrollViewMeasurementIntervalId)
|
||||
@scrollViewMeasurementIntervalId = null
|
||||
|
||||
componentWillUpdate: ->
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
cursorsMoved = @cursorsMoved
|
||||
selectionChanged = @selectionChanged
|
||||
@@ -202,14 +206,28 @@ EditorComponent = React.createClass
|
||||
@remeasureCharacterWidthsIfNeeded(prevState)
|
||||
|
||||
requestUpdate: ->
|
||||
if @updatesPaused
|
||||
@updateRequestedWhilePaused = true
|
||||
return
|
||||
|
||||
if @performSyncUpdates ? EditorComponent.performSyncUpdates
|
||||
@forceUpdate()
|
||||
else unless @updateRequested
|
||||
@updateRequested = true
|
||||
process.nextTick =>
|
||||
setImmediate =>
|
||||
@updateRequested = false
|
||||
@forceUpdate() if @isMounted()
|
||||
|
||||
requestAnimationFrame: (fn) ->
|
||||
@updatesPaused = true
|
||||
@pauseScrollViewMeasurement()
|
||||
requestAnimationFrame =>
|
||||
fn()
|
||||
@updatesPaused = false
|
||||
if @updateRequestedWhilePaused and @isMounted()
|
||||
@updateRequestedWhilePaused = false
|
||||
@forceUpdate()
|
||||
|
||||
getRenderedRowRange: ->
|
||||
{editor, lineOverdrawMargin} = @props
|
||||
[visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
|
||||
@@ -242,22 +260,43 @@ EditorComponent = React.createClass
|
||||
cursorScreenRanges[cursor.id] = screenRange
|
||||
cursorScreenRanges
|
||||
|
||||
getCursorPixelRects: (renderedRowRange) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
|
||||
cursorPixelRects = {}
|
||||
for selection in editor.getSelections() when selection.isEmpty()
|
||||
{cursor} = selection
|
||||
screenRange = cursor.getScreenRange()
|
||||
if renderedStartRow <= screenRange.start.row < renderedEndRow
|
||||
cursorPixelRects[cursor.id] = editor.pixelRectForScreenRange(screenRange)
|
||||
cursorPixelRects
|
||||
|
||||
getLineDecorations: (decorationsByMarkerId) ->
|
||||
{editor} = @props
|
||||
decorationsByScreenRow = {}
|
||||
for markerId, decorations of decorationsByMarkerId
|
||||
marker = editor.getMarker(markerId)
|
||||
screenRange = null
|
||||
headScreenRow = null
|
||||
if marker.isValid()
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'gutter') or editor.decorationMatchesType(decoration, 'line')
|
||||
if decoration.isType('gutter') or decoration.isType('line')
|
||||
decorationParams = decoration.getParams()
|
||||
screenRange ?= marker.getScreenRange()
|
||||
headScreenRow ?= marker.getHeadScreenPosition().row
|
||||
startRow = screenRange.start.row
|
||||
endRow = screenRange.end.row
|
||||
endRow-- if not screenRange.isEmpty() and screenRange.end.column == 0
|
||||
for screenRow in [startRow..endRow]
|
||||
decorationsByScreenRow[screenRow] ?= []
|
||||
decorationsByScreenRow[screenRow].push decoration
|
||||
continue if decorationParams.onlyHead and screenRow isnt headScreenRow
|
||||
if screenRange.isEmpty()
|
||||
continue if decorationParams.onlyNonEmpty
|
||||
else
|
||||
continue if decorationParams.onlyEmpty
|
||||
|
||||
decorationsByScreenRow[screenRow] ?= {}
|
||||
decorationsByScreenRow[screenRow][decoration.id] = decorationParams
|
||||
|
||||
decorationsByScreenRow
|
||||
|
||||
@@ -266,11 +305,17 @@ EditorComponent = React.createClass
|
||||
filteredDecorations = {}
|
||||
for markerId, decorations of decorationsByMarkerId
|
||||
marker = editor.getMarker(markerId)
|
||||
if marker.isValid() and not marker.getScreenRange().isEmpty()
|
||||
screenRange = marker.getScreenRange()
|
||||
if marker.isValid() and not screenRange.isEmpty()
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'highlight')
|
||||
filteredDecorations[markerId] ?= {id: markerId, screenRange: marker.getScreenRange(), decorations: []}
|
||||
filteredDecorations[markerId].decorations.push decoration
|
||||
if decoration.isType('highlight')
|
||||
decorationParams = decoration.getParams()
|
||||
filteredDecorations[markerId] ?=
|
||||
id: markerId
|
||||
startPixelPosition: editor.pixelPositionForScreenPosition(screenRange.start)
|
||||
endPixelPosition: editor.pixelPositionForScreenPosition(screenRange.end)
|
||||
decorations: []
|
||||
filteredDecorations[markerId].decorations.push decorationParams
|
||||
|
||||
# At least in Chromium 31, removing the last highlight causes a rendering
|
||||
# artifact where chunks of the lines disappear, so we always leave this
|
||||
@@ -287,6 +332,8 @@ EditorComponent = React.createClass
|
||||
@subscribe editor, 'selection-added', @onSelectionAdded
|
||||
@subscribe editor, 'decoration-added', @onDecorationChanged
|
||||
@subscribe editor, 'decoration-removed', @onDecorationChanged
|
||||
@subscribe editor, 'decoration-changed', @onDecorationChanged
|
||||
@subscribe editor, 'decoration-updated', @onDecorationChanged
|
||||
@subscribe editor, 'character-widths-changed', @onCharacterWidthsChanged
|
||||
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
||||
@@ -325,12 +372,13 @@ EditorComponent = React.createClass
|
||||
# 5. textInput fired; event.data == the completion string
|
||||
|
||||
selectedText = null
|
||||
node.addEventListener 'compositionstart', =>
|
||||
node.addEventListener 'compositionstart', ->
|
||||
selectedText = editor.getSelectedText()
|
||||
node.addEventListener 'compositionupdate', (event) =>
|
||||
node.addEventListener 'compositionupdate', (event) ->
|
||||
editor.insertText(event.data, select: true, undo: 'skip')
|
||||
node.addEventListener 'compositionend', =>
|
||||
node.addEventListener 'compositionend', (event) ->
|
||||
editor.insertText(selectedText, select: true, undo: 'skip')
|
||||
event.target.value = ''
|
||||
|
||||
listenForCommands: ->
|
||||
{parentView, editor, mini} = @props
|
||||
@@ -414,7 +462,7 @@ EditorComponent = React.createClass
|
||||
'editor:unfold-all': => editor.unfoldAll()
|
||||
'editor:fold-current-row': => editor.foldCurrentRow()
|
||||
'editor:unfold-current-row': => editor.unfoldCurrentRow()
|
||||
'editor:fold-selection': => neditor.foldSelectedLines()
|
||||
'editor:fold-selection': => editor.foldSelectedLines()
|
||||
'editor:fold-at-indent-level-1': => editor.foldAllAtIndentLevel(0)
|
||||
'editor:fold-at-indent-level-2': => editor.foldAllAtIndentLevel(1)
|
||||
'editor:fold-at-indent-level-3': => editor.foldAllAtIndentLevel(2)
|
||||
@@ -450,7 +498,9 @@ 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.showLineNumbers', @setShowLineNumbers
|
||||
@subscribe atom.config.observe 'editor.scrollSensitivity', @setScrollSensitivity
|
||||
@subscribe atom.config.observe 'editor.useHardwareAcceleration', @setUseHardwareAcceleration
|
||||
|
||||
onFocus: ->
|
||||
@refs.input.focus()
|
||||
@@ -484,24 +534,25 @@ EditorComponent = React.createClass
|
||||
onVerticalScroll: (scrollTop) ->
|
||||
{editor} = @props
|
||||
|
||||
return if scrollTop is editor.getScrollTop()
|
||||
return if @updateRequested or scrollTop is editor.getScrollTop()
|
||||
|
||||
animationFramePending = @pendingScrollTop?
|
||||
@pendingScrollTop = scrollTop
|
||||
unless animationFramePending
|
||||
requestAnimationFrame =>
|
||||
@props.editor.setScrollTop(@pendingScrollTop)
|
||||
@requestAnimationFrame =>
|
||||
pendingScrollTop = @pendingScrollTop
|
||||
@pendingScrollTop = null
|
||||
@props.editor.setScrollTop(pendingScrollTop)
|
||||
|
||||
onHorizontalScroll: (scrollLeft) ->
|
||||
{editor} = @props
|
||||
|
||||
return if scrollLeft is editor.getScrollLeft()
|
||||
return if @updateRequested or scrollLeft is editor.getScrollLeft()
|
||||
|
||||
animationFramePending = @pendingScrollLeft?
|
||||
@pendingScrollLeft = scrollLeft
|
||||
unless animationFramePending
|
||||
requestAnimationFrame =>
|
||||
@requestAnimationFrame =>
|
||||
@props.editor.setScrollLeft(@pendingScrollLeft)
|
||||
@pendingScrollLeft = null
|
||||
|
||||
@@ -522,7 +573,7 @@ EditorComponent = React.createClass
|
||||
@clearMouseWheelScreenRowAfterDelay()
|
||||
|
||||
unless animationFramePending
|
||||
requestAnimationFrame =>
|
||||
@requestAnimationFrame =>
|
||||
{editor} = @props
|
||||
editor.setScrollTop(editor.getScrollTop() + @pendingVerticalScrollDelta)
|
||||
editor.setScrollLeft(editor.getScrollLeft() + @pendingHorizontalScrollDelta)
|
||||
@@ -540,7 +591,7 @@ EditorComponent = React.createClass
|
||||
return unless event.button is 0 # only handle the left mouse button
|
||||
|
||||
{editor} = @props
|
||||
{detail, shiftKey, metaKey} = event
|
||||
{detail, shiftKey, metaKey, ctrlKey} = event
|
||||
screenPosition = @screenPositionForMouseEvent(event)
|
||||
|
||||
if event.target?.classList.contains('fold-marker')
|
||||
@@ -548,15 +599,18 @@ EditorComponent = React.createClass
|
||||
editor.unfoldBufferRow(bufferRow)
|
||||
return
|
||||
|
||||
if shiftKey
|
||||
editor.selectToScreenPosition(screenPosition)
|
||||
else if metaKey
|
||||
editor.addCursorAtScreenPosition(screenPosition)
|
||||
else
|
||||
editor.setCursorScreenPosition(screenPosition)
|
||||
switch detail
|
||||
when 2 then editor.selectWord()
|
||||
when 3 then editor.selectLine()
|
||||
switch detail
|
||||
when 1
|
||||
if shiftKey
|
||||
editor.selectToScreenPosition(screenPosition)
|
||||
else if metaKey or (ctrlKey and process.platform isnt 'darwin')
|
||||
editor.addCursorAtScreenPosition(screenPosition)
|
||||
else
|
||||
editor.setCursorScreenPosition(screenPosition)
|
||||
when 2
|
||||
editor.getLastSelection().selectWord()
|
||||
when 3
|
||||
editor.getLastSelection().selectLine()
|
||||
|
||||
@handleDragUntilMouseUp event, (screenPosition) ->
|
||||
editor.selectToScreenPosition(screenPosition)
|
||||
@@ -601,6 +655,8 @@ EditorComponent = React.createClass
|
||||
|
||||
onStylesheetsChanged: (stylesheet) ->
|
||||
@refreshScrollbars() if @containsScrollbarSelector(stylesheet)
|
||||
@remeasureCharacterWidthsIfVisibleAfterNextUpdate = true
|
||||
@requestUpdate() if @state.visible
|
||||
|
||||
onScreenLinesChanged: (change) ->
|
||||
{editor} = @props
|
||||
@@ -623,7 +679,7 @@ EditorComponent = React.createClass
|
||||
onScrollTopChanged: ->
|
||||
@scrollingVertically = true
|
||||
@requestUpdate()
|
||||
@onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100)
|
||||
@onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 200)
|
||||
@onStoppedScrollingAfterDelay()
|
||||
|
||||
onStoppedScrolling: ->
|
||||
@@ -650,7 +706,7 @@ EditorComponent = React.createClass
|
||||
dragging = false
|
||||
lastMousePosition = {}
|
||||
animationLoop = =>
|
||||
requestAnimationFrame =>
|
||||
@requestAnimationFrame =>
|
||||
if dragging
|
||||
screenPosition = @screenPositionForMouseEvent(lastMousePosition)
|
||||
dragHandler(screenPosition)
|
||||
@@ -677,8 +733,18 @@ EditorComponent = React.createClass
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('mouseup', onMouseUp)
|
||||
|
||||
pauseScrollViewMeasurement: ->
|
||||
@scrollViewMeasurementPaused = true
|
||||
@resumeScrollViewMeasurementAfterDelay ?= debounce(@resumeScrollViewMeasurement, 100)
|
||||
@resumeScrollViewMeasurementAfterDelay()
|
||||
|
||||
resumeScrollViewMeasurement: ->
|
||||
@scrollViewMeasurementPaused = false
|
||||
|
||||
resumeScrollViewMeasurementAfterDelay: null # created lazily
|
||||
|
||||
requestScrollViewMeasurement: ->
|
||||
return if @measurementPending
|
||||
return if @scrollViewMeasurementRequested
|
||||
|
||||
@scrollViewMeasurementRequested = true
|
||||
requestAnimationFrame =>
|
||||
@@ -690,6 +756,7 @@ EditorComponent = React.createClass
|
||||
# and use the scrollHeight / scrollWidth as its height and width in
|
||||
# calculations.
|
||||
measureScrollView: ->
|
||||
return if @scrollViewMeasurementPaused
|
||||
return unless @isMounted()
|
||||
|
||||
{editor} = @props
|
||||
@@ -704,6 +771,8 @@ EditorComponent = React.createClass
|
||||
|
||||
if position is 'absolute' or width
|
||||
clientWidth = scrollViewNode.clientWidth
|
||||
paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft)
|
||||
clientWidth -= paddingLeft
|
||||
editor.setWidth(clientWidth) if clientWidth > 0
|
||||
|
||||
measureLineHeightAndCharWidthsIfNeeded: (prevState) ->
|
||||
@@ -724,12 +793,12 @@ EditorComponent = React.createClass
|
||||
if @state.visible
|
||||
@remeasureCharacterWidths()
|
||||
else
|
||||
@remeasureCharacterWidthsWhenShown = true
|
||||
else if @remeasureCharacterWidthsWhenShown and @state.visible and not prevState.visible
|
||||
@remeasureCharacterWidthsIfVisibleAfterNextUpdate = true
|
||||
else if @remeasureCharacterWidthsIfVisibleAfterNextUpdate and @state.visible
|
||||
@remeasureCharacterWidthsIfVisibleAfterNextUpdate = false
|
||||
@remeasureCharacterWidths()
|
||||
|
||||
remeasureCharacterWidths: ->
|
||||
@remeasureCharacterWidthsWhenShown = false
|
||||
@refs.lines.remeasureCharacterWidths()
|
||||
|
||||
onGutterWidthChanged: (@gutterWidth) ->
|
||||
@@ -835,10 +904,18 @@ EditorComponent = React.createClass
|
||||
setShowInvisibles: (showInvisibles) ->
|
||||
@setState({showInvisibles})
|
||||
|
||||
setShowLineNumbers: (showLineNumbers) ->
|
||||
@setState({showLineNumbers})
|
||||
|
||||
setScrollSensitivity: (scrollSensitivity) ->
|
||||
if scrollSensitivity = parseInt(scrollSensitivity)
|
||||
@scrollSensitivity = Math.abs(scrollSensitivity) / 100
|
||||
|
||||
setUseHardwareAcceleration: (useHardwareAcceleration=true) ->
|
||||
unless @useHardwareAcceleration is useHardwareAcceleration
|
||||
@useHardwareAcceleration = useHardwareAcceleration
|
||||
@requestUpdate()
|
||||
|
||||
screenPositionForMouseEvent: (event) ->
|
||||
pixelPosition = @pixelPositionForMouseEvent(event)
|
||||
@props.editor.screenPositionForPixelPosition(pixelPosition)
|
||||
@@ -847,9 +924,9 @@ EditorComponent = React.createClass
|
||||
{editor} = @props
|
||||
{clientX, clientY} = event
|
||||
|
||||
scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect()
|
||||
top = clientY - scrollViewClientRect.top + editor.getScrollTop()
|
||||
left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
|
||||
linesClientRect = @refs.lines.getDOMNode().getBoundingClientRect()
|
||||
top = clientY - linesClientRect.top
|
||||
left = clientX - linesClientRect.left
|
||||
{top, left}
|
||||
|
||||
getModel: ->
|
||||
|
||||
@@ -57,6 +57,7 @@ class EditorView extends View
|
||||
softTabs: true
|
||||
softWrapAtPreferredLineLength: false
|
||||
scrollSensitivity: 40
|
||||
useHardwareAcceleration: true
|
||||
|
||||
@nextEditorId: 1
|
||||
|
||||
|
||||
+56
-49
@@ -131,6 +131,10 @@ TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
# - {::markScreenRange}
|
||||
# - {::getMarker}
|
||||
# - {::findMarkers}
|
||||
#
|
||||
# ### Decorations
|
||||
# - {::decorateMarker}
|
||||
# - {::decorationsForScreenRowRange}
|
||||
module.exports =
|
||||
class Editor extends Model
|
||||
Serializable.includeInto(this)
|
||||
@@ -146,6 +150,7 @@ class Editor extends Model
|
||||
selections: null
|
||||
suppressSelectionMerging: false
|
||||
updateBatchDepth: 0
|
||||
selectionFlashDuration: 500
|
||||
|
||||
@delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
|
||||
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
|
||||
@@ -217,6 +222,8 @@ class Editor extends Model
|
||||
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
|
||||
@subscribe @displayBuffer, "decoration-added", (args...) => @emit 'decoration-added', args...
|
||||
@subscribe @displayBuffer, "decoration-removed", (args...) => @emit 'decoration-removed', args...
|
||||
@subscribe @displayBuffer, "decoration-changed", (args...) => @emit 'decoration-changed', args...
|
||||
@subscribe @displayBuffer, "decoration-updated", (args...) => @emit 'decoration-updated', args...
|
||||
@subscribe @displayBuffer, "character-widths-changed", (changeCount) => @emit 'character-widths-changed', changeCount
|
||||
|
||||
getViewClass: ->
|
||||
@@ -1077,60 +1084,57 @@ class Editor extends Model
|
||||
# startScreenRow - the {Number} beginning screen row
|
||||
# endScreenRow - the {Number} end screen row (inclusive)
|
||||
#
|
||||
# Returns an {Object} of decorations in the form `{1: [{type: 'gutter', class: 'someclass'}], 2: ...}`
|
||||
# where the keys are markerIds, and the values are an array of decoration objects attached to the marker.
|
||||
# Returns an {Object} of decorations in the form
|
||||
# `{1: [{id: 10, type: 'gutter', class: 'someclass'}], 2: ...}`
|
||||
# where the keys are {Marker} IDs, and the values are an array of decoration
|
||||
# params objects attached to the marker.
|
||||
# Returns an empty object when no decorations are found
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
# 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.
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect
|
||||
# the marker's state.
|
||||
#
|
||||
# There are a few supported decoration types:
|
||||
# * `gutter`: `{type: 'gutter', class: 'linter-error'}` Will add a class to the gutter rows associated with the marker.
|
||||
# * `line`: `{type: 'line', class: 'linter-error'}` Will add a class to the editor lines associated with the marker.
|
||||
# * `highlight`: `{type: 'highlight', class: 'linter-error'}` Will highlight the region of the buffer associated with the marker. Your specified class will be added to the highlight.
|
||||
# There are three types of supported decorations:
|
||||
# * `line`: Adds your CSS `class` to the line nodes within the range
|
||||
# marked by the marker
|
||||
# * `gutter`: Adds your CSS `class` to the line number nodes within the
|
||||
# range marked by the marker
|
||||
# * `highlight`: Adds a new highlight div to the editor surrounding the
|
||||
# range marked by the marker. When the user selects text, the selection is
|
||||
# visualized with a highlight decoration internally. The structure of this
|
||||
# highlight will be:
|
||||
# ```html
|
||||
# <div class="highlight <your-class>">
|
||||
# <!-- Will be one region for each row in the range. Spans 2 lines? There will be 2 regions. -->
|
||||
# <div class="region"></div>
|
||||
# </div>
|
||||
# ```
|
||||
#
|
||||
# marker - the {Marker} you want this decoration to follow
|
||||
# decoration - the {Object} decoration eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
# marker - A {Marker} you want this decoration to follow.
|
||||
# decorationParams - An {Object} representing the decoration eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
# :type - There are a few supported decoration types:
|
||||
# * `gutter`: Applies the decoration to the line numbers spanned by the marker.
|
||||
# * `line`: Applies the decoration to the lines spanned by the marker.
|
||||
# * `highlight`: Applies the decoration to a "highlight" behind the marked range.
|
||||
# :class - This CSS class will be applied to the decorated line number,
|
||||
# line, or highlight.
|
||||
# :onlyHead - If `true`, the decoration will only be applied to the head
|
||||
# of the marker. Only applicable to the `line` and `gutter` types.
|
||||
# :onlyEmpty - If `true`, the decoration will only be applied if the
|
||||
# associated marker is empty. Only applicable to the `line` and
|
||||
# `gutter` types.
|
||||
# :onlyNonEmpty - If `true`, the decoration will only be applied if the
|
||||
# associated marker is non-empty. Only applicable to the `line` and
|
||||
# gutter types.
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
@displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
@displayBuffer.decorateMarker(marker, decorationParams)
|
||||
|
||||
# Public: Removes all decorations associated with a {Marker} that match a
|
||||
# `decorationPattern` and stop tracking the {Marker}.
|
||||
#
|
||||
# ```coffee
|
||||
# marker = editor.markBufferRange([[4, 13], [5, 17]])
|
||||
# editor.removeDecorationForMarker(marker, {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.removeDecorationForMarker(marker, {namespace: 'myns'})
|
||||
# ```
|
||||
#
|
||||
# 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)
|
||||
|
||||
decorationMatchesType: (decoration, type) ->
|
||||
@displayBuffer.decorationMatchesType(decoration, type)
|
||||
decorationForId: (id) ->
|
||||
@displayBuffer.decorationForId(id)
|
||||
|
||||
# Public: Get the {DisplayBufferMarker} for the given marker id.
|
||||
getMarker: (id) ->
|
||||
@@ -1237,7 +1241,9 @@ class Editor extends Model
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker)
|
||||
@cursors.push(cursor)
|
||||
@addDecorationForMarker(marker, type: ['gutter', 'line'], class: 'cursor-line')
|
||||
@decorateMarker(marker, type: 'gutter', class: 'cursor-line')
|
||||
@decorateMarker(marker, type: 'gutter', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
|
||||
@decorateMarker(marker, type: 'line', class: 'cursor-line', onlyEmpty: true)
|
||||
@emit 'cursor-added', cursor
|
||||
cursor
|
||||
|
||||
@@ -1264,7 +1270,6 @@ class Editor extends Model
|
||||
if selection.intersectsBufferRange(selectionBufferRange)
|
||||
return selection
|
||||
else
|
||||
@addDecorationForMarker(marker, type: 'highlight', class: 'selection')
|
||||
@emit 'selection-added', selection
|
||||
selection
|
||||
|
||||
@@ -1279,7 +1284,7 @@ class Editor extends Model
|
||||
addSelectionForBufferRange: (bufferRange, options={}) ->
|
||||
@markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
selection = @getLastSelection()
|
||||
selection.autoscroll()
|
||||
selection.autoscroll() if @manageScrollPosition
|
||||
selection
|
||||
|
||||
# Public: Set the selected range in buffer coordinates. If there are multiple
|
||||
@@ -1487,7 +1492,7 @@ class Editor extends Model
|
||||
# text - A {String}
|
||||
#
|
||||
# Returns the {Range} of the newly-inserted text.
|
||||
setTextInBufferRange: (range, text) -> @getBuffer().setTextInRange(range, text)
|
||||
setTextInBufferRange: (range, text, normalizeLineEndings) -> @getBuffer().setTextInRange(range, text, normalizeLineEndings)
|
||||
|
||||
# Public: Get the {Range} of the paragraph surrounding the most recently added
|
||||
# cursor.
|
||||
@@ -1974,6 +1979,8 @@ class Editor extends Model
|
||||
getLineHeightInPixels: -> @displayBuffer.getLineHeightInPixels()
|
||||
setLineHeightInPixels: (lineHeightInPixels) -> @displayBuffer.setLineHeightInPixels(lineHeightInPixels)
|
||||
|
||||
batchCharacterMeasurement: (fn) -> @displayBuffer.batchCharacterMeasurement(fn)
|
||||
|
||||
getScopedCharWidth: (scopeNames, char) -> @displayBuffer.getScopedCharWidth(scopeNames, char)
|
||||
setScopedCharWidth: (scopeNames, char, width) -> @displayBuffer.setScopedCharWidth(scopeNames, char, width)
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ class Fold
|
||||
# Returns a {Range}.
|
||||
getBufferRange: ({includeNewline}={}) ->
|
||||
range = @marker.getRange()
|
||||
|
||||
if range.end.row > range.start.row and nextFold = @displayBuffer.largestFoldStartingAtBufferRow(range.end.row)
|
||||
nextRange = nextFold.getBufferRange()
|
||||
range = new Range(range.start, nextRange.end)
|
||||
|
||||
if includeNewline
|
||||
range = range.copy()
|
||||
range.end.row++
|
||||
|
||||
@@ -2,6 +2,7 @@ _ = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
Decoration = require './decoration'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
WrapperDiv = document.createElement('div')
|
||||
@@ -15,14 +16,22 @@ GutterComponent = React.createClass
|
||||
measuredWidth: null
|
||||
|
||||
render: ->
|
||||
{scrollHeight, scrollViewHeight, scrollTop, onMouseDown} = @props
|
||||
{scrollHeight, scrollViewHeight, onMouseDown} = @props
|
||||
|
||||
div className: 'gutter', onClick: @onClick, onMouseDown: onMouseDown,
|
||||
# The line-numbers div must have the 'editor-colors' class so it has an
|
||||
# opaque background to avoid sub-pixel anti-aliasing problems on the GPU
|
||||
div className: 'gutter line-numbers editor-colors', ref: 'lineNumbers', style:
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
WebkitTransform: @getTransform()
|
||||
|
||||
getTransform: ->
|
||||
{scrollTop, useHardwareAcceleration} = @props
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
else
|
||||
"translate(0px, #{-scrollTop}px)"
|
||||
|
||||
componentWillMount: ->
|
||||
@lineNumberNodesById = {}
|
||||
@@ -32,6 +41,7 @@ GutterComponent = React.createClass
|
||||
|
||||
componentDidMount: ->
|
||||
@appendDummyLineNumber()
|
||||
@updateLineNumbers() if @props.renderedRowRange?
|
||||
|
||||
# Only update the gutter if the visible row range has changed or if a
|
||||
# non-zero-delta change to the screen lines has occurred within the current
|
||||
@@ -39,7 +49,7 @@ GutterComponent = React.createClass
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow', 'lineDecorations',
|
||||
'scrollViewHeight'
|
||||
'scrollViewHeight', 'useHardwareAcceleration'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges, lineDecorations} = newProps
|
||||
@@ -145,8 +155,8 @@ GutterComponent = React.createClass
|
||||
|
||||
classes = ''
|
||||
if lineDecorations? and decorations = lineDecorations[screenRow]
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'gutter')
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'gutter')
|
||||
classes += decoration.class + ' '
|
||||
|
||||
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
|
||||
@@ -177,12 +187,13 @@ GutterComponent = React.createClass
|
||||
previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]
|
||||
|
||||
if previousDecorations?
|
||||
for decoration in previousDecorations
|
||||
node.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(decorations, decoration)
|
||||
for id, decoration of previousDecorations
|
||||
if Decoration.isType(decoration, 'gutter') and not @hasDecoration(decorations, decoration)
|
||||
node.classList.remove(decoration.class)
|
||||
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(previousDecorations, decoration)
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'gutter') and not @hasDecoration(previousDecorations, decoration)
|
||||
node.classList.add(decoration.class)
|
||||
|
||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
||||
@@ -192,6 +203,9 @@ GutterComponent = React.createClass
|
||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
||||
|
||||
hasDecoration: (decorations, decoration) ->
|
||||
decorations? and decorations[decoration.id] is decoration
|
||||
|
||||
hasLineNumberNode: (lineNumberId) ->
|
||||
@lineNumberNodesById.hasOwnProperty(lineNumberId)
|
||||
|
||||
|
||||
@@ -1,27 +1,47 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
HighlightComponent = React.createClass
|
||||
displayName: 'HighlightComponent'
|
||||
|
||||
render: ->
|
||||
{editor, screenRange, decoration} = @props
|
||||
{start, end} = screenRange
|
||||
rowCount = end.row - start.row + 1
|
||||
startPixelPosition = editor.pixelPositionForScreenPosition(start)
|
||||
endPixelPosition = editor.pixelPositionForScreenPosition(end)
|
||||
{startPixelPosition, endPixelPosition, decoration} = @props
|
||||
|
||||
className = 'highlight'
|
||||
className += " #{decoration.class}" if decoration.class?
|
||||
div {className},
|
||||
if rowCount is 1
|
||||
@renderSingleLineRegions(startPixelPosition, endPixelPosition)
|
||||
else
|
||||
@renderMultiLineRegions(startPixelPosition, endPixelPosition, rowCount)
|
||||
|
||||
renderSingleLineRegions: (startPixelPosition, endPixelPosition) ->
|
||||
{lineHeightInPixels} = @props
|
||||
div {className},
|
||||
if endPixelPosition.top is startPixelPosition.top
|
||||
@renderSingleLineRegions()
|
||||
else
|
||||
@renderMultiLineRegions()
|
||||
|
||||
componentDidMount: ->
|
||||
{editor, decoration} = @props
|
||||
if decoration.id?
|
||||
@decoration = editor.decorationForId(decoration.id)
|
||||
@decoration.on 'flash', @startFlashAnimation
|
||||
@startFlashAnimation()
|
||||
|
||||
componentWillUnmount: ->
|
||||
@decoration?.off 'flash', @startFlashAnimation
|
||||
|
||||
startFlashAnimation: ->
|
||||
return unless flash = @decoration.consumeNextFlash()
|
||||
|
||||
node = @getDOMNode()
|
||||
node.classList.remove(flash.class)
|
||||
|
||||
requestAnimationFrame =>
|
||||
node.classList.add(flash.class)
|
||||
clearTimeout(@flashTimeoutId)
|
||||
removeFlashClass = -> node.classList.remove(flash.class)
|
||||
@flashTimeoutId = setTimeout(removeFlashClass, flash.duration)
|
||||
|
||||
renderSingleLineRegions: ->
|
||||
{startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
|
||||
|
||||
[
|
||||
div className: 'region', key: 0, style:
|
||||
@@ -31,8 +51,8 @@ HighlightComponent = React.createClass
|
||||
width: endPixelPosition.left - startPixelPosition.left
|
||||
]
|
||||
|
||||
renderMultiLineRegions: (startPixelPosition, endPixelPosition, rowCount) ->
|
||||
{lineHeightInPixels} = @props
|
||||
renderMultiLineRegions: ->
|
||||
{startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
|
||||
regions = []
|
||||
index = 0
|
||||
|
||||
@@ -46,11 +66,11 @@ HighlightComponent = React.createClass
|
||||
)
|
||||
|
||||
# Middle rows, extending from left side to right side of screen
|
||||
if rowCount > 2
|
||||
if endPixelPosition.top - startPixelPosition.top > lineHeightInPixels
|
||||
regions.push(
|
||||
div className: 'region', key: index++, style:
|
||||
top: startPixelPosition.top + lineHeightInPixels
|
||||
height: (rowCount - 2) * lineHeightInPixels
|
||||
height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels
|
||||
left: 0
|
||||
right: 0
|
||||
)
|
||||
@@ -65,3 +85,6 @@ HighlightComponent = React.createClass
|
||||
)
|
||||
|
||||
regions
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'startPixelPosition', 'endPixelPosition', 'lineHeightInPixels')
|
||||
|
||||
@@ -8,15 +8,16 @@ HighlightsComponent = React.createClass
|
||||
displayName: 'HighlightsComponent'
|
||||
|
||||
render: ->
|
||||
div className: 'highlights', @renderHighlights()
|
||||
div className: 'highlights',
|
||||
@renderHighlights() if @isMounted()
|
||||
|
||||
renderHighlights: ->
|
||||
{editor, highlightDecorations, lineHeightInPixels} = @props
|
||||
|
||||
highlightComponents = []
|
||||
for markerId, {screenRange, decorations} of highlightDecorations
|
||||
for markerId, {startPixelPosition, endPixelPosition, decorations} of highlightDecorations
|
||||
for decoration in decorations
|
||||
highlightComponents.push(HighlightComponent({key: "#{markerId}-#{decoration.class}", screenRange, decoration, editor, lineHeightInPixels}))
|
||||
highlightComponents.push(HighlightComponent({editor, key: "#{markerId}-#{decoration.class}", startPixelPosition, endPixelPosition, decoration, lineHeightInPixels}))
|
||||
|
||||
highlightComponents
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ React = require 'react-atom-fork'
|
||||
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
Decoration = require './decoration'
|
||||
HighlightsComponent = require './highlights-component'
|
||||
|
||||
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
|
||||
@@ -16,17 +17,25 @@ LinesComponent = React.createClass
|
||||
|
||||
render: ->
|
||||
if @isMounted()
|
||||
{editor, highlightDecorations, scrollTop, scrollLeft, scrollHeight, scrollWidth} = @props
|
||||
{editor, highlightDecorations, scrollHeight, scrollWidth} = @props
|
||||
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
|
||||
style =
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
width: scrollWidth
|
||||
WebkitTransform: "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||
WebkitTransform: @getTransform()
|
||||
|
||||
# The lines div must have the 'editor-colors' class so it has an opaque
|
||||
# background to avoid sub-pixel anti-aliasing problems on the GPU
|
||||
div {className: 'lines editor-colors', style},
|
||||
HighlightsComponent({editor, highlightDecorations, lineHeightInPixels, defaultCharWidth, scopedCharacterWidthsChangeCount}) if @isMounted()
|
||||
HighlightsComponent({editor, highlightDecorations, lineHeightInPixels, defaultCharWidth, scopedCharacterWidthsChangeCount})
|
||||
|
||||
getTransform: ->
|
||||
{scrollTop, scrollLeft, useHardwareAcceleration} = @props
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||
else
|
||||
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
|
||||
|
||||
componentWillMount: ->
|
||||
@measuredLines = new WeakSet
|
||||
@@ -39,13 +48,16 @@ LinesComponent = React.createClass
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
|
||||
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
|
||||
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount'
|
||||
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
for change in pendingChanges
|
||||
return true unless change.end < renderedStartRow or renderedEndRow <= change.start
|
||||
if change.screenDelta is 0
|
||||
return true unless change.end < renderedStartRow or renderedEndRow <= change.start
|
||||
else
|
||||
return true unless renderedEndRow <= change.start
|
||||
|
||||
false
|
||||
|
||||
@@ -54,20 +66,20 @@ LinesComponent = React.createClass
|
||||
|
||||
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
|
||||
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
|
||||
@updateLines()
|
||||
@updateLines(@props.lineWidth isnt prevProps.lineWidth)
|
||||
@measureCharactersInNewLines() if visible and not scrollingVertically
|
||||
|
||||
clearScreenRowCaches: ->
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
|
||||
updateLines: ->
|
||||
updateLines: (updateWidth) ->
|
||||
{editor, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
|
||||
@removeLineNodes(visibleLines)
|
||||
@appendOrUpdateVisibleLineNodes(visibleLines, startRow)
|
||||
@appendOrUpdateVisibleLineNodes(visibleLines, startRow, updateWidth)
|
||||
|
||||
removeLineNodes: (visibleLines=[]) ->
|
||||
{mouseWheelScreenRow} = @props
|
||||
@@ -83,7 +95,7 @@ LinesComponent = React.createClass
|
||||
delete @renderedDecorationsByLineId[lineId]
|
||||
node.removeChild(lineNode)
|
||||
|
||||
appendOrUpdateVisibleLineNodes: (visibleLines, startRow) ->
|
||||
appendOrUpdateVisibleLineNodes: (visibleLines, startRow, updateWidth) ->
|
||||
{lineDecorations} = @props
|
||||
|
||||
newLines = null
|
||||
@@ -93,7 +105,7 @@ LinesComponent = React.createClass
|
||||
screenRow = startRow + index
|
||||
|
||||
if @hasLineNode(line.id)
|
||||
@updateLineNode(line, screenRow)
|
||||
@updateLineNode(line, screenRow, updateWidth)
|
||||
else
|
||||
newLines ?= []
|
||||
newLinesHTML ?= ""
|
||||
@@ -118,18 +130,18 @@ LinesComponent = React.createClass
|
||||
@lineNodesByLineId.hasOwnProperty(lineId)
|
||||
|
||||
buildLineHTML: (line, screenRow) ->
|
||||
{editor, mini, showIndentGuide, lineHeightInPixels, lineDecorations} = @props
|
||||
{editor, mini, showIndentGuide, lineHeightInPixels, lineDecorations, lineWidth} = @props
|
||||
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
|
||||
|
||||
classes = ''
|
||||
if decorations = lineDecorations[screenRow]
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'line')
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'line')
|
||||
classes += decoration.class + ' '
|
||||
classes += 'line'
|
||||
|
||||
top = screenRow * lineHeightInPixels
|
||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{lineWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
|
||||
if text is ""
|
||||
lineHTML += @buildEmptyLineInnerHTML(line)
|
||||
@@ -141,14 +153,31 @@ LinesComponent = React.createClass
|
||||
lineHTML
|
||||
|
||||
buildEmptyLineInnerHTML: (line) ->
|
||||
{showIndentGuide} = @props
|
||||
{showIndentGuide, invisibles} = @props
|
||||
{cr, eol} = invisibles
|
||||
{indentLevel, tabLength} = line
|
||||
|
||||
if showIndentGuide and indentLevel > 0
|
||||
indentSpan = "<span class='indent-guide'>#{multiplyString(' ', tabLength)}</span>"
|
||||
multiplyString(indentSpan, indentLevel + 1)
|
||||
invisiblesToRender = []
|
||||
invisiblesToRender.push(cr) if cr? and line.lineEnding is '\r\n'
|
||||
invisiblesToRender.push(eol) if eol?
|
||||
|
||||
lineHTML = ''
|
||||
for i in [0...indentLevel]
|
||||
lineHTML += "<span class='indent-guide'>"
|
||||
for j in [0...tabLength]
|
||||
if invisible = invisiblesToRender.shift()
|
||||
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
lineHTML += ' '
|
||||
lineHTML += "</span>"
|
||||
|
||||
while invisiblesToRender.length
|
||||
lineHTML += "<span class='invisible-character'>#{invisiblesToRender.shift()}</span>"
|
||||
|
||||
lineHTML
|
||||
else
|
||||
" "
|
||||
@buildEndOfLineHTML(line, @props.invisibles)
|
||||
|
||||
buildLineInnerHTML: (line) ->
|
||||
{invisibles, mini, showIndentGuide, invisibles} = @props
|
||||
@@ -203,28 +232,34 @@ LinesComponent = React.createClass
|
||||
scopeStack.push(scope)
|
||||
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
updateLineNode: (line, screenRow) ->
|
||||
{editor, lineHeightInPixels, lineDecorations} = @props
|
||||
updateLineNode: (line, screenRow, updateWidth) ->
|
||||
{editor, lineHeightInPixels, lineDecorations, lineWidth} = @props
|
||||
lineNode = @lineNodesByLineId[line.id]
|
||||
|
||||
decorations = lineDecorations[screenRow]
|
||||
previousDecorations = @renderedDecorationsByLineId[line.id]
|
||||
|
||||
if previousDecorations?
|
||||
for decoration in previousDecorations
|
||||
lineNode.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(decorations, decoration)
|
||||
for id, decoration of previousDecorations
|
||||
if Decoration.isType(decoration, 'line') and not @hasDecoration(decorations, decoration)
|
||||
lineNode.classList.remove(decoration.class)
|
||||
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(previousDecorations, decoration)
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration)
|
||||
lineNode.classList.add(decoration.class)
|
||||
|
||||
lineNode.style.width = lineWidth + 'px' if updateWidth
|
||||
|
||||
unless @screenRowsByLineId[line.id] is screenRow
|
||||
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
lineNode.dataset.screenRow = screenRow
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
hasDecoration: (decorations, decoration) ->
|
||||
decorations? and decorations[decoration.id] is decoration
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
@@ -248,10 +283,12 @@ LinesComponent = React.createClass
|
||||
[visibleStartRow, visibleEndRow] = @props.renderedRowRange
|
||||
node = @getDOMNode()
|
||||
|
||||
for tokenizedLine in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
unless @measuredLines.has(tokenizedLine)
|
||||
lineNode = @lineNodesByLineId[tokenizedLine.id]
|
||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
||||
editor.batchCharacterMeasurement =>
|
||||
for tokenizedLine in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
unless @measuredLines.has(tokenizedLine)
|
||||
lineNode = @lineNodesByLineId[tokenizedLine.id]
|
||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
||||
return
|
||||
|
||||
measureCharactersInLine: (tokenizedLine, lineNode) ->
|
||||
{editor} = @props
|
||||
|
||||
+24
-4
@@ -67,7 +67,6 @@ class Package
|
||||
@loadKeymaps()
|
||||
@loadMenus()
|
||||
@loadStylesheets()
|
||||
@grammarsPromise = @loadGrammars()
|
||||
@scopedPropertiesPromise = @loadScopedProperties()
|
||||
@requireMainModule() unless @hasActivationEvents()
|
||||
|
||||
@@ -83,6 +82,8 @@ class Package
|
||||
@scopedProperties = []
|
||||
|
||||
activate: ->
|
||||
@grammarsPromise ?= @loadGrammars()
|
||||
|
||||
unless @activationDeferred?
|
||||
@activationDeferred = Q.defer()
|
||||
@measure 'activateTime', =>
|
||||
@@ -128,8 +129,9 @@ class Package
|
||||
atom.contextMenu.add(menuPath, map['context-menu']) for [menuPath, map] in @menus
|
||||
atom.menu.add(map.menu) for [menuPath, map] in @menus when map.menu
|
||||
|
||||
grammar.activate() for grammar in @grammars
|
||||
@grammarsActivated = true
|
||||
unless @grammarsActivated
|
||||
grammar.activate() for grammar in @grammars
|
||||
@grammarsActivated = true
|
||||
|
||||
scopedProperties.activate() for scopedProperties in @scopedProperties
|
||||
@scopedPropertiesActivated = true
|
||||
@@ -173,14 +175,32 @@ class Package
|
||||
else
|
||||
fs.listSync(stylesheetDirPath, ['css', 'less'])
|
||||
|
||||
loadGrammarsSync: ->
|
||||
return if @grammarsLoaded
|
||||
|
||||
grammarsDirPath = path.join(@path, 'grammars')
|
||||
grammarPaths = fs.listSync(grammarsDirPath, ['json', 'cson'])
|
||||
for grammarPath in grammarPaths
|
||||
try
|
||||
grammar = atom.syntax.readGrammarSync(grammarPath)
|
||||
grammar.packageName = @name
|
||||
@grammars.push(grammar)
|
||||
grammar.activate()
|
||||
catch error
|
||||
console.warn("Failed to load grammar: #{grammarPath}", error.stack ? error)
|
||||
|
||||
@grammarsLoaded = true
|
||||
@grammarsActivated = true
|
||||
|
||||
loadGrammars: ->
|
||||
@grammars = []
|
||||
return Q() if @grammarsLoaded
|
||||
|
||||
loadGrammar = (grammarPath, callback) =>
|
||||
atom.syntax.readGrammar grammarPath, (error, grammar) =>
|
||||
if error?
|
||||
console.warn("Failed to load grammar: #{grammarPath}", error.stack ? error)
|
||||
else
|
||||
grammar.packageName = @name
|
||||
@grammars.push(grammar)
|
||||
grammar.activate() if @grammarsActivated
|
||||
callback()
|
||||
|
||||
@@ -8,6 +8,8 @@ class PaneContainer extends Model
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
@version: 1
|
||||
|
||||
@properties
|
||||
root: -> new Pane
|
||||
activePane: null
|
||||
@@ -27,10 +29,12 @@ class PaneContainer extends Model
|
||||
deserializeParams: (params) ->
|
||||
params.root = atom.deserializers.deserialize(params.root, container: this)
|
||||
params.destroyEmptyPanes = atom.config.get('core.destroyEmptyPanes')
|
||||
params.activePane = params.root.getPanes().find (pane) -> pane.id is params.activePaneId
|
||||
params
|
||||
|
||||
serializeParams: (params) ->
|
||||
root: @root?.serialize()
|
||||
activePaneId: @activePane.id
|
||||
|
||||
replaceChild: (oldChild, newChild) ->
|
||||
throw new Error("Replacing non-existent child") if oldChild isnt @root
|
||||
|
||||
+1
-3
@@ -40,14 +40,12 @@ class Pane extends Model
|
||||
@subscribe @items.onRemoval (item, index) =>
|
||||
@unsubscribe item if typeof item.on is 'function'
|
||||
|
||||
@activate() if params?.active
|
||||
|
||||
# Called by the Serializable mixin during serialization.
|
||||
serializeParams: ->
|
||||
id: @id
|
||||
items: compact(@items.map((item) -> item.serialize?()))
|
||||
activeItemUri: @activeItem?.getUri?()
|
||||
focused: @focused
|
||||
active: @active
|
||||
|
||||
# Called by the Serializable mixin during deserialization.
|
||||
deserializeParams: (params) ->
|
||||
|
||||
@@ -239,6 +239,9 @@ class Project extends Model
|
||||
task.on 'scan:result-found', (result) =>
|
||||
iterator(result) unless @isPathModified(result.filePath)
|
||||
|
||||
task.on 'scan:file-error', (error) ->
|
||||
iterator(null, error)
|
||||
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
@@ -283,6 +286,7 @@ class Project extends Model
|
||||
checkFinished()
|
||||
|
||||
task.on 'replace:path-replaced', iterator
|
||||
task.on 'replace:file-error', (error) -> iterator(null, error)
|
||||
|
||||
for buffer in @getBuffers()
|
||||
continue unless buffer.getPath() in filePaths
|
||||
|
||||
@@ -202,4 +202,6 @@ class ReactEditorView extends View
|
||||
|
||||
updateDisplay: -> # No-op shim for package specs
|
||||
|
||||
resetDisplay: -> # No-op shim for package specs
|
||||
|
||||
redraw: -> # No-op shim
|
||||
|
||||
@@ -6,8 +6,10 @@ module.exports = (filePaths, regexSource, regexFlags, replacementText) ->
|
||||
replacer = new PathReplacer()
|
||||
regex = new RegExp(regexSource, regexFlags)
|
||||
|
||||
replacer.on 'file-error', ({code, path, message}) ->
|
||||
emit('replace:file-error', {code, path, message})
|
||||
|
||||
replacer.on 'path-replaced', (result) ->
|
||||
emit('replace:path-replaced', result)
|
||||
|
||||
replacer.replacePaths regex, replacementText, filePaths, ->
|
||||
callback()
|
||||
replacer.replacePaths(regex, replacementText, filePaths, -> callback())
|
||||
|
||||
@@ -9,6 +9,9 @@ module.exports = (rootPath, regexSource, options) ->
|
||||
searcher = new PathSearcher()
|
||||
scanner = new PathScanner(rootPath, options)
|
||||
|
||||
searcher.on 'file-error', ({code, path, message}) ->
|
||||
emit('scan:file-error', {code, path, message})
|
||||
|
||||
searcher.on 'results-found', (result) ->
|
||||
emit('scan:result-found', result)
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ class Selection extends Model
|
||||
constructor: ({@cursor, @marker, @editor, id}) ->
|
||||
@assignId(id)
|
||||
@cursor.selection = this
|
||||
@decoration = @editor.decorateMarker(@marker, type: 'highlight', class: 'selection')
|
||||
|
||||
@marker.on 'changed', => @screenRangeChanged()
|
||||
@marker.on 'destroyed', =>
|
||||
@destroyed = true
|
||||
@@ -76,9 +78,12 @@ class Selection extends Model
|
||||
options.reversed ?= @isReversed()
|
||||
@editor.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
|
||||
@modifySelection =>
|
||||
needsFlash = options.flash
|
||||
delete options.flash if options.flash?
|
||||
@cursor.needsAutoscroll = false if @needsAutoscroll?
|
||||
@marker.setBufferRange(bufferRange, options)
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition
|
||||
@decoration.flash('flash', @editor.selectionFlashDuration) if needsFlash
|
||||
|
||||
# Public: Returns the starting and ending buffer rows the selection is
|
||||
# highlighting.
|
||||
@@ -320,6 +325,7 @@ class Selection extends Model
|
||||
text = @normalizeIndents(text, options.indentBasis)
|
||||
|
||||
newBufferRange = @editor.buffer.setTextInRange(oldBufferRange, text, pick(options, 'undo'))
|
||||
|
||||
if options.select
|
||||
@setBufferRange(newBufferRange, reversed: wasReversed)
|
||||
else
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@ _ = require 'underscore-plus'
|
||||
textUtils = require './text-utils'
|
||||
|
||||
WhitespaceRegexesByTabLength = {}
|
||||
LeadingWhitespaceRegex = /^[ ]+/
|
||||
TrailingWhitespaceRegex = /[ ]+$/
|
||||
LeadingSpaceRegex = /^[ ]+/
|
||||
TrailingSpaceRegex = /[ ]+$/
|
||||
EscapeRegex = /[&"'<>]/g
|
||||
CharacterRegex = /./g
|
||||
StartCharacterRegex = /^./
|
||||
@@ -151,7 +151,7 @@ class Token
|
||||
leadingHtml = ''
|
||||
trailingHtml = ''
|
||||
|
||||
if @hasLeadingWhitespace and match = LeadingWhitespaceRegex.exec(@value)
|
||||
if @hasLeadingWhitespace and match = LeadingSpaceRegex.exec(@value)
|
||||
classes = 'leading-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
@@ -161,7 +161,7 @@ class Token
|
||||
|
||||
startIndex = match[0].length
|
||||
|
||||
if @hasTrailingWhitespace and match = TrailingWhitespaceRegex.exec(@value)
|
||||
if @hasTrailingWhitespace and match = TrailingSpaceRegex.exec(@value)
|
||||
tokenIsOnlyWhitespace = match[0].length is @value.length
|
||||
classes = 'trailing-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide and not @hasLeadingWhitespace and tokenIsOnlyWhitespace
|
||||
|
||||
@@ -161,13 +161,26 @@ class TokenizedBuffer extends Model
|
||||
previousEndStack = @stackForRow(end) # used in spill detection below
|
||||
newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1))
|
||||
_.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines)
|
||||
newEndStack = @stackForRow(end + delta)
|
||||
|
||||
start = @retokenizeWhitespaceRowsIfIndentLevelChanged(start - 1, -1)
|
||||
end = @retokenizeWhitespaceRowsIfIndentLevelChanged(newRange.end.row + 1, 1) - delta
|
||||
|
||||
newEndStack = @stackForRow(end + delta)
|
||||
if newEndStack and not _.isEqual(newEndStack, previousEndStack)
|
||||
@invalidateRow(end + delta + 1)
|
||||
|
||||
@emit "changed", { start, end, delta, bufferChange: e }
|
||||
|
||||
retokenizeWhitespaceRowsIfIndentLevelChanged: (row, increment) ->
|
||||
line = @tokenizedLines[row]
|
||||
if line?.isOnlyWhitespace() and @indentLevelForRow(row) isnt line.indentLevel
|
||||
while line?.isOnlyWhitespace()
|
||||
@tokenizedLines[row] = @buildTokenizedTokenizedLineForRow(row, @stackForRow(row - 1))
|
||||
row += increment
|
||||
line = @tokenizedLines[row]
|
||||
|
||||
row - increment
|
||||
|
||||
buildTokenizedLinesForRows: (startRow, endRow, startingStack) ->
|
||||
ruleStack = startingStack
|
||||
stopTokenizingAt = startRow + @chunkSize
|
||||
@@ -218,22 +231,27 @@ class TokenizedBuffer extends Model
|
||||
|
||||
indentLevelForRow: (row) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
indentLevel = 0
|
||||
|
||||
if line is ''
|
||||
nextRow = row + 1
|
||||
lineCount = @getLineCount()
|
||||
while nextRow < lineCount
|
||||
nextLine = @buffer.lineForRow(nextRow)
|
||||
return @indentLevelForLine(nextLine) unless nextLine is ''
|
||||
unless nextLine is ''
|
||||
indentLevel = Math.ceil(@indentLevelForLine(nextLine))
|
||||
break
|
||||
nextRow++
|
||||
|
||||
previousRow = row - 1
|
||||
while previousRow >= 0
|
||||
previousLine = @buffer.lineForRow(previousRow)
|
||||
return @indentLevelForLine(previousLine) unless previousLine is ''
|
||||
unless previousLine is ''
|
||||
indentLevel = Math.max(Math.ceil(@indentLevelForLine(previousLine)), indentLevel)
|
||||
break
|
||||
previousRow--
|
||||
|
||||
0
|
||||
indentLevel
|
||||
else
|
||||
@indentLevelForLine(line)
|
||||
|
||||
|
||||
@@ -152,6 +152,14 @@ class TokenizedLine
|
||||
break
|
||||
false
|
||||
|
||||
isOnlyWhitespace: ->
|
||||
if @text == ''
|
||||
true
|
||||
else
|
||||
for token in @tokens
|
||||
return false unless token.isOnlyWhitespace()
|
||||
true
|
||||
|
||||
tokenAtIndex: (index) ->
|
||||
@tokens[index]
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ class Workspace extends Model
|
||||
|
||||
# Called by the Serializable mixin during deserialization
|
||||
deserializeParams: (params) ->
|
||||
for packageName in params.packagesWithActiveGrammars ? []
|
||||
atom.packages.getLoadedPackage(packageName)?.loadGrammarsSync()
|
||||
|
||||
params.paneContainer = PaneContainer.deserialize(params.paneContainer)
|
||||
params
|
||||
|
||||
@@ -53,6 +56,21 @@ class Workspace extends Model
|
||||
serializeParams: ->
|
||||
paneContainer: @paneContainer.serialize()
|
||||
fullScreen: atom.isFullScreen()
|
||||
packagesWithActiveGrammars: @getPackageNamesWithActiveGrammars()
|
||||
|
||||
getPackageNamesWithActiveGrammars: ->
|
||||
packageNames = []
|
||||
addGrammar = ({includedGrammarScopes, packageName}={}) ->
|
||||
return unless packageName
|
||||
# Prevent cycles
|
||||
return if packageNames.indexOf(packageName) isnt -1
|
||||
|
||||
packageNames.push(packageName)
|
||||
for scopeName in includedGrammarScopes ? []
|
||||
addGrammar(atom.syntax.grammarForScopeName(scopeName))
|
||||
|
||||
addGrammar(editor.getGrammar()) for editor in @getEditors()
|
||||
_.uniq(packageNames)
|
||||
|
||||
editorAdded: (editor) ->
|
||||
@emit 'editor-created', editor
|
||||
|
||||
@@ -64,15 +64,15 @@
|
||||
}
|
||||
|
||||
.btn-toolbar {
|
||||
> .btn-group {
|
||||
> .btn-group + .btn-group, > .btn-group + .btn, > .btn + .btn {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
}
|
||||
> * {
|
||||
margin-right: 0;
|
||||
margin-left: @component-padding/2;
|
||||
}
|
||||
> *:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
> * {
|
||||
margin-right: @component-padding / 2;
|
||||
}
|
||||
> *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,11 @@
|
||||
box-shadow: inset 1px 0;
|
||||
}
|
||||
|
||||
.editor .vertical-scrollbar,
|
||||
.editor .horizontal-scrollbar {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.editor .vertical-scrollbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@@ -19,3 +19,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.define-selection-flash-color-if-not-defined() { @syntax-selection-flash-color: rgba(100, 255, 100, 0.7); }
|
||||
.define-selection-flash-color-if-not-defined();
|
||||
|
||||
@-webkit-keyframes flash {
|
||||
from { background-color: @syntax-selection-flash-color; }
|
||||
to { background-color: null; }
|
||||
}
|
||||
|
||||
.editor .flash.selection .region {
|
||||
-webkit-animation-name: flash;
|
||||
-webkit-animation-duration: .5s;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@syntax-text-color: #333;
|
||||
@syntax-cursor-color: #333;
|
||||
@syntax-selection-color: #69c;
|
||||
@syntax-selection-flash-color: #00f; // Color the selection is 'flashed' when you run find next
|
||||
@syntax-background-color: #fff;
|
||||
|
||||
// Guide colors
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário