Comparar commits
479 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 8acd84c7e2 | |||
| a53d223b6f | |||
| 673b78561a | |||
| 57c56fc46e | |||
| d81fb8cabf | |||
| 1dec9d0ad2 | |||
| a342a18440 | |||
| 047425c564 | |||
| 8374911dab | |||
| 6ca3c90abe | |||
| 96cb266c27 | |||
| 21723b155e | |||
| 14c3feee17 | |||
| 0255f1e223 | |||
| 76a5912126 | |||
| 848dc12f28 | |||
| 7a41fabf60 | |||
| f4b82fe3a4 | |||
| aec32b65a0 | |||
| 01e0970406 | |||
| b08978a431 | |||
| b72028c68d | |||
| 332b918c01 | |||
| bc94dc63e6 | |||
| adb174ef5f | |||
| d3a016bf69 | |||
| 6ef6fb77f9 | |||
| 159adcb00c | |||
| 12bf0f5381 | |||
| 08716fd888 | |||
| 16c86047eb | |||
| 97999f1080 | |||
| a268b3e963 | |||
| 8931bf8f3a | |||
| 918c31905f | |||
| c7ff4f9897 | |||
| 4f604ced3c | |||
| a29c18f8c0 | |||
| 43f517abff | |||
| 8b901f81e0 | |||
| fe0cdbf349 | |||
| c2381ba61c | |||
| 4179d9d268 | |||
| 3fc3d48def | |||
| b438b311f3 | |||
| 561e31c0c5 | |||
| 2a8a5268c6 | |||
| 28b085be1c | |||
| f5bc71e559 | |||
| 2188dd201d | |||
| b21eb6f934 | |||
| 9b0d8ec242 | |||
| 3c32a8e8fb | |||
| 3ab7836ab2 | |||
| ef8b7531b0 | |||
| b04f9f9488 | |||
| 452d86ac0c | |||
| 6fe1bae40d | |||
| e2170ea907 | |||
| 545bf4bd98 | |||
| 1fdc78a157 | |||
| 738bfd7253 | |||
| 8b0b997db6 | |||
| 263ab3b4a6 | |||
| 3afbcbe25f | |||
| 578ca8b197 | |||
| 72fe586101 | |||
| 82d5577bdc | |||
| c703cf2050 | |||
| 993f1ac2d6 | |||
| ddf7c04e66 | |||
| c127237cc6 | |||
| 9694d255f0 | |||
| 81b4803d56 | |||
| 893e9873a1 | |||
| 23e805fe9e | |||
| 43257de7cd | |||
| c7fded0d7f | |||
| 284d823ad5 | |||
| 164a121de9 | |||
| 50ab49aedb | |||
| a127d3c4eb | |||
| cac5c6e3a5 | |||
| 1ee783fdb9 | |||
| 47870a1214 | |||
| 7801d8562f | |||
| 104271861c | |||
| de25b8ea37 | |||
| ba9f353c4a | |||
| 7665cd1a6a | |||
| c39d8d9aa7 | |||
| 4b0d22917b | |||
| 720b2ad47d | |||
| d34327a667 | |||
| d2146f9b2e | |||
| 732d36af28 | |||
| edfc86f153 | |||
| 339e30d973 | |||
| 5e1e092650 | |||
| c69febd44d | |||
| a9d7564f3e | |||
| a0b733b53d | |||
| 8cb565ad2a | |||
| 32158711ce | |||
| e099f00739 | |||
| e707ab5441 | |||
| 3b7f1467e1 | |||
| 1c7eef89c3 | |||
| 4fc1f5b248 | |||
| 6eacfa7077 | |||
| 9aefafb831 | |||
| 5a3353ec28 | |||
| e87b8dc463 | |||
| 60daa483e6 | |||
| cd699d8b9b | |||
| d33a962848 | |||
| 0d2067e1a4 | |||
| 8647137952 | |||
| fbcfad28c3 | |||
| 5e1b2e2696 | |||
| a0346e95cf | |||
| 20ee7b432c | |||
| 4026e6ca5c | |||
| 378901e0d2 | |||
| 964abd3141 | |||
| 5ca7ad3bce | |||
| 4dcba4bb27 | |||
| 073ea84d69 | |||
| 2965d2e974 | |||
| a67f0d4d57 | |||
| 5309d5f24d | |||
| af3ca57094 | |||
| db375cd190 | |||
| d96b63d791 | |||
| e435b48750 | |||
| a1f8a21c7c | |||
| 69f9f10c6b | |||
| d3c6bd2f98 | |||
| 466868e639 | |||
| 2317c6835e | |||
| 4e99d003ee | |||
| 1a5e10c1d2 | |||
| 8efcb1abfa | |||
| f031a9706d | |||
| eb7f3ff5af | |||
| bb595ab08a | |||
| 101326a130 | |||
| ad60594c13 | |||
| ca6da5f9c1 | |||
| c397b3cc60 | |||
| 6cb0f1ff78 | |||
| 371e31c786 | |||
| dce70b35b5 | |||
| 99a67ca1ab | |||
| 9dc1758d76 | |||
| 196942d126 | |||
| d95aa0aac1 | |||
| 27f0d11039 | |||
| a69e6136ea | |||
| 0fd8b6be8d | |||
| 8e51a7f6d6 | |||
| bdb4cd5247 | |||
| 3afe750a66 | |||
| c8f3e056e9 | |||
| 32fdf0b681 | |||
| 82c73c9911 | |||
| fe0184d067 | |||
| 0ef6757e65 | |||
| deb4365d65 | |||
| 750f4ee410 | |||
| 1382bd3b3b | |||
| d908c8b026 | |||
| 6f766acac8 | |||
| 7eba9d3a23 | |||
| cde5861cb8 | |||
| 765c15829d | |||
| 0471619269 | |||
| d8ba8f13d8 | |||
| c089429c14 | |||
| b22f850e03 | |||
| cf47ee5063 | |||
| 9d6a01d11c | |||
| bcf9dfd236 | |||
| c281eb9596 | |||
| 2acde6a727 | |||
| ee9b78afb6 | |||
| a379d47230 | |||
| 57c23e1b5f | |||
| f8d959ae16 | |||
| 13abb28486 | |||
| 717704c8ed | |||
| eab26fb3a6 | |||
| 17947d0f99 | |||
| 626e22e4ae | |||
| 5837b7cfda | |||
| 1a487db29f | |||
| 2938a8e650 | |||
| cd97de76fc | |||
| 25c099f3a2 | |||
| eb39b8505e | |||
| 7a90cc46ad | |||
| c6c1cb233d | |||
| f9e37c9b47 | |||
| dfb6835449 | |||
| bf13b426c5 | |||
| 14175d80ef | |||
| c99e211144 | |||
| 1d04cbf584 | |||
| 92650e079f | |||
| f29ce127db | |||
| b29f1965f0 | |||
| 48a2a1934b | |||
| 8fe60b5838 | |||
| 484a7c95ee | |||
| 44a3365787 | |||
| f3e88b56f3 | |||
| d618472f95 | |||
| a45ffb3aaa | |||
| 1d2b2eec4c | |||
| 5c1f750f94 | |||
| 85a6db253b | |||
| 72caf279a6 | |||
| 05ff058ca5 | |||
| 022ead9228 | |||
| 14fd9aac7e | |||
| 5975884a0e | |||
| e8ce559034 | |||
| 9add438ea4 | |||
| d65a6c3fc4 | |||
| a493359b58 | |||
| d6de973500 | |||
| 25cc37bc86 | |||
| 0e6bc275b7 | |||
| cec731b697 | |||
| 8d4ae33134 | |||
| cbec03c158 | |||
| 1fe0a1ad7b | |||
| 8b9ede8414 | |||
| 194094043e | |||
| 9068f28e83 | |||
| ebb5d38e1a | |||
| aa1ed1dfcc | |||
| 0292c66f93 | |||
| 4ce68fe5d8 | |||
| 289e7d56c3 | |||
| cb7471945c | |||
| 8da9e8ddc1 | |||
| a68694e6e2 | |||
| 98b5a400e6 | |||
| 0bbd9630f5 | |||
| c6770aa83e | |||
| e26d97d5ac | |||
| b2177cbc09 | |||
| c8663541f0 | |||
| 9f32a24e7e | |||
| 914a87290b | |||
| 112b8bfa29 | |||
| 5d99acd8c5 | |||
| 066d8dc944 | |||
| 9af6e99682 | |||
| bc65137911 | |||
| 02dfe074e6 | |||
| 2b4c2f1758 | |||
| a7494cf649 | |||
| 3d494ed9fa | |||
| 47a14bb2e6 | |||
| 7a71f26345 | |||
| 870c4d4214 | |||
| ac5d10fae2 | |||
| 2963fe177f | |||
| 0947947a9d | |||
| 0112e8887f | |||
| 3a7ecccec7 | |||
| f9a9712f54 | |||
| 5e5ba63c59 | |||
| 2c58d1a2b7 | |||
| 5e2e5a4b58 | |||
| 344d237a42 | |||
| 04eef20c84 | |||
| 77dba8d19b | |||
| 704294a2d5 | |||
| 847a8165e0 | |||
| 18f2f5f821 | |||
| 7f2e0e2317 | |||
| 963513e840 | |||
| b1470fc1b5 | |||
| 43482ea78e | |||
| 167e6dc1bf | |||
| 088a627468 | |||
| 4f6c655294 | |||
| e2db58c6ee | |||
| 64aba6ec24 | |||
| 2910170eeb | |||
| 9bc24b8736 | |||
| 401ef87bb2 | |||
| 0c5ed1eee4 | |||
| 790c227924 | |||
| 822f13d6e3 | |||
| 70c14eb4f5 | |||
| 673d2330f0 | |||
| e187604942 | |||
| c2cd1cd13d | |||
| c720a6a029 | |||
| 601466782f | |||
| 92ef8f22e4 | |||
| 690ffab9c0 | |||
| 512f373ca6 | |||
| e8f4da54a6 | |||
| d56137e3c0 | |||
| 9629afb145 | |||
| 821debcb85 | |||
| b9395d2946 | |||
| 44e6e7f45d | |||
| 88ee021b4d | |||
| b5c8e3e1fe | |||
| 9962ce9859 | |||
| 882d766689 | |||
| 959401f5a7 | |||
| fa9aa3691b | |||
| a95fdce85f | |||
| e8edc83e39 | |||
| 3ff702581a | |||
| 88c9275bff | |||
| 826d536c09 | |||
| c99c2af6ae | |||
| ccc6eed3da | |||
| e1aec57ffe | |||
| c1fc09e510 | |||
| 19212f99ee | |||
| 2681dcc63c | |||
| 6f5d85edb9 | |||
| a59c01c6be | |||
| a78613b7e5 | |||
| 33c1353500 | |||
| 22a7c25104 | |||
| 902406c572 | |||
| b0077986b4 | |||
| 686ebf8759 | |||
| 66831ce8b9 | |||
| dd2c6d2f24 | |||
| 30a175230a | |||
| fe3e71cbd7 | |||
| 8d2e1b7e43 | |||
| c37b884007 | |||
| 97aed1f680 | |||
| bac76784e0 | |||
| 8e970b64b8 | |||
| dfe9f5684e | |||
| 470ce7bd22 | |||
| f59080ec74 | |||
| f5ca836e49 | |||
| 319f9a22d8 | |||
| 458d3b3d3c | |||
| 11ec939924 | |||
| e14019e2dd | |||
| 180912db61 | |||
| aba1900d13 | |||
| 5fdb3196a3 | |||
| 8763a49dc6 | |||
| ffbd15eb98 | |||
| 871b7406cd | |||
| 66fa9d6a42 | |||
| 8c7649dd57 | |||
| 38c4fb3884 | |||
| 2d17ffc792 | |||
| 1d70e12594 | |||
| 8fb729d000 | |||
| b9fd05ba1e | |||
| 2ad9fb52ae | |||
| 91778cb566 | |||
| a76cda4564 | |||
| 7cb1ea038c | |||
| fa18a5cb33 | |||
| a012248316 | |||
| 53ccf7cf87 | |||
| fbda0028ca | |||
| 9e3648c22f | |||
| d8374eb251 | |||
| fd6e5e7a13 | |||
| 1da6bca2ae | |||
| 40630114e7 | |||
| 6736b6af3e | |||
| 9c7747efbf | |||
| 42b203d502 | |||
| d7d4a990a5 | |||
| 8597951f0c | |||
| 7d47527b17 | |||
| 197d185ea4 | |||
| 91d1c2914e | |||
| da2487ba84 | |||
| 171c3e018b | |||
| bda8397d3d | |||
| c9aa082e63 | |||
| 6c4eb7439a | |||
| 76922c2d46 | |||
| fd7c3f3980 | |||
| 6b750c82ca | |||
| 4d643242a1 | |||
| e974e61012 | |||
| 1ce4f3c552 | |||
| 3ac2cae355 | |||
| 2ffa989ba4 | |||
| 341454cd81 | |||
| 4507981f2a | |||
| 14f2444883 | |||
| 267b1bdce6 | |||
| 53451e0bcd | |||
| 4223ea25ee | |||
| bb517467eb | |||
| dd16aefbb6 | |||
| 471c323ca5 | |||
| bf021ab7f7 | |||
| 9198f3b809 | |||
| b7c227dbfc | |||
| 0dc031140c | |||
| e853bbfcb6 | |||
| cef7577826 | |||
| 8366887df6 | |||
| e7f63b7c62 | |||
| 81a04769fb | |||
| d4fcfbd034 | |||
| bab92c6d0a | |||
| 72523de046 | |||
| 82fbba4547 | |||
| 8b112cfd65 | |||
| ec83c1061b | |||
| 75c573fe61 | |||
| ed8800d182 | |||
| 0aabfddfcb | |||
| 1bf1785885 | |||
| 20df9f4666 | |||
| e40b7b1412 | |||
| 611559ecd6 | |||
| c74783ebbf | |||
| 9df69f801b | |||
| 105f74e15e | |||
| d7e56c447b | |||
| cecee6a430 | |||
| e3e83918e5 | |||
| 0438565c43 | |||
| cbfa87a3b4 | |||
| 3187013eac | |||
| e7b632eb18 | |||
| cdce91157f | |||
| a566bd469d | |||
| 389586bb41 | |||
| 51b39500fe | |||
| bf0015f6cc | |||
| ca3d1e869c | |||
| a57083a48b | |||
| 2ae46734db | |||
| 3918435c7f | |||
| d8f5ef71cd | |||
| 2e2bab7778 | |||
| fd929364d1 | |||
| 92b829c89b | |||
| 3db9e16637 | |||
| fa34eea27a | |||
| bd3cfda2bb | |||
| ed41cc3cad | |||
| e67e8ff0f5 | |||
| e18a0f045a | |||
| da964a8f15 | |||
| 2c4aee1181 | |||
| 24d3f1daeb | |||
| beb2fb08ea | |||
| 165a417a9d | |||
| 39fe0c418b | |||
| bfcb24f517 | |||
| 208ed09109 | |||
| 0b7f291e17 | |||
| a61b057aea | |||
| 4c817baf4c | |||
| a564cc66f6 | |||
| 14c58c4517 | |||
| ebe77065cc | |||
| 30b0fed60f | |||
| 5f10c48219 | |||
| 23957d7f66 |
@@ -10,3 +10,4 @@ debug.log
|
||||
/atom-shell/
|
||||
docs/output
|
||||
spec/fixtures/evil-files/
|
||||
/apm
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'Atom',
|
||||
'type': 'none',
|
||||
'postbuilds': [
|
||||
{
|
||||
'postbuild_name': 'Create Atom, basically do everything',
|
||||
'action': ['script/constructicon/build'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -5,13 +5,32 @@ os = require 'os'
|
||||
fm = require 'json-front-matter'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
packageJson = require './package.json'
|
||||
packageJson = require '../package.json'
|
||||
|
||||
# OAuth token for atom-bot
|
||||
# TODO Remove once all repositories are public
|
||||
process.env.ATOM_ACCESS_TOKEN ?= '362295be4c5258d3f7b967bbabae662a455ca2a7'
|
||||
|
||||
# Shim harmony collections in case grunt was invoked without harmony
|
||||
# collections enabled
|
||||
_.extend(global, require('harmony-collections')) unless global.WeakMap?
|
||||
|
||||
module.exports = (grunt) ->
|
||||
grunt.loadNpmTasks('grunt-coffeelint')
|
||||
grunt.loadNpmTasks('grunt-lesslint')
|
||||
grunt.loadNpmTasks('grunt-cson')
|
||||
grunt.loadNpmTasks('grunt-contrib-csslint')
|
||||
grunt.loadNpmTasks('grunt-contrib-coffee')
|
||||
grunt.loadNpmTasks('grunt-contrib-less')
|
||||
grunt.loadNpmTasks('grunt-markdown')
|
||||
grunt.loadNpmTasks('grunt-shell')
|
||||
grunt.loadNpmTasks('grunt-download-atom-shell')
|
||||
grunt.loadNpmTasks('grunt-peg')
|
||||
grunt.loadTasks('tasks')
|
||||
|
||||
# This allows all subsequent paths to the relative to the root of the repo
|
||||
grunt.file.setBase(path.resolve('..'))
|
||||
|
||||
if not grunt.option('verbose')
|
||||
grunt.log.writeln = (args...) -> grunt.log
|
||||
grunt.log.write = (args...) -> grunt.log
|
||||
@@ -23,6 +42,7 @@ module.exports = (grunt) ->
|
||||
installRoot = process.env.ProgramFiles
|
||||
buildDir = grunt.option('build-dir') ? path.join(tmpDir, 'atom-build')
|
||||
shellAppDir = path.join(buildDir, appName)
|
||||
contentsDir = shellAppDir
|
||||
appDir = path.join(shellAppDir, 'resources', 'app')
|
||||
atomShellDownloadDir = path.join(os.tmpdir(), 'atom-cached-atom-shells')
|
||||
else
|
||||
@@ -83,6 +103,13 @@ module.exports = (grunt) ->
|
||||
dest: appDir
|
||||
ext: '.json'
|
||||
|
||||
pegConfig =
|
||||
glob_to_multiple:
|
||||
expand: true
|
||||
src: ['src/**/*.pegjs']
|
||||
dest: appDir
|
||||
ext: '.js'
|
||||
|
||||
for child in fs.readdirSync('node_modules') when child isnt '.bin'
|
||||
directory = path.join('node_modules', child)
|
||||
{engines, theme} = grunt.file.readJSON(path.join(directory, 'package.json'))
|
||||
@@ -91,6 +118,7 @@ module.exports = (grunt) ->
|
||||
lessConfig.glob_to_multiple.src.push("#{directory}/**/*.less")
|
||||
prebuildLessConfig.src.push("#{directory}/**/*.less") unless theme
|
||||
csonConfig.glob_to_multiple.src.push("#{directory}/**/*.cson")
|
||||
pegConfig.glob_to_multiple.src.push("#{directory}/**/*.pegjs")
|
||||
|
||||
grunt.initConfig
|
||||
pkg: grunt.file.readJSON('package.json')
|
||||
@@ -105,6 +133,8 @@ module.exports = (grunt) ->
|
||||
|
||||
cson: csonConfig
|
||||
|
||||
peg: pegConfig
|
||||
|
||||
coffeelint:
|
||||
options:
|
||||
no_empty_param_list:
|
||||
@@ -117,8 +147,10 @@ module.exports = (grunt) ->
|
||||
'dot-atom/**/*.coffee'
|
||||
'exports/**/*.coffee'
|
||||
'src/**/*.coffee'
|
||||
'tasks/**/*.coffee'
|
||||
'Gruntfile.coffee'
|
||||
]
|
||||
build: [
|
||||
'build/tasks/**/*.coffee'
|
||||
'build/Gruntfile.coffee'
|
||||
]
|
||||
test: [
|
||||
'spec/*.coffee'
|
||||
@@ -188,21 +220,9 @@ module.exports = (grunt) ->
|
||||
stderr: false
|
||||
failOnError: false
|
||||
|
||||
grunt.loadNpmTasks('grunt-coffeelint')
|
||||
grunt.loadNpmTasks('grunt-lesslint')
|
||||
grunt.loadNpmTasks('grunt-cson')
|
||||
grunt.loadNpmTasks('grunt-contrib-csslint')
|
||||
grunt.loadNpmTasks('grunt-contrib-coffee')
|
||||
grunt.loadNpmTasks('grunt-contrib-less')
|
||||
grunt.loadNpmTasks('grunt-markdown')
|
||||
grunt.loadNpmTasks('grunt-download-atom-shell')
|
||||
grunt.loadNpmTasks('grunt-shell')
|
||||
grunt.loadTasks('tasks')
|
||||
|
||||
grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson'])
|
||||
grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson', 'peg'])
|
||||
grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint'])
|
||||
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
|
||||
grunt.registerTask('ci', ['download-atom-shell', 'build', 'set-development-version', 'lint', 'test'])
|
||||
grunt.registerTask('deploy', ['partial-clean', 'download-atom-shell', 'build', 'codesign'])
|
||||
grunt.registerTask('ci', ['output-disk-space', 'download-atom-shell', 'build', 'set-version', 'lint', 'test', 'codesign', 'publish-build'])
|
||||
grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
|
||||
grunt.registerTask('default', ['download-atom-shell', 'build', 'set-development-version', 'install'])
|
||||
grunt.registerTask('default', ['download-atom-shell', 'build', 'set-version', 'install'])
|
||||
@@ -0,0 +1,10 @@
|
||||
# Atom Build
|
||||
|
||||
This folder contains the grunt configuration and tasks to build Atom.
|
||||
|
||||
It was moved from the root of the repository so that any native modules used
|
||||
would be compiled against node's v8 headers since anything stored in
|
||||
`node_modules` at the root of the repo is compiled against atom's v8 headers.
|
||||
|
||||
New build dependencies should be added to the `package.json` file located in
|
||||
this folder.
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "atom-build",
|
||||
"description": "Atom build",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"biscotto": "0.0.17",
|
||||
"first-mate": "~0.10.0",
|
||||
"formidable": "~1.0.14",
|
||||
"github-releases": "~0.2.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.9",
|
||||
"grunt-coffeelint": "git://github.com/atom/grunt-coffeelint.git",
|
||||
"grunt-contrib-csslint": "~0.1.2",
|
||||
"grunt-contrib-coffee": "~0.7.0",
|
||||
"grunt-contrib-less": "~0.8.0",
|
||||
"grunt-cson": "0.5.0",
|
||||
"grunt-download-atom-shell": "git+https://atom-bot:362295be4c5258d3f7b967bbabae662a455ca2a7@github.com/atom/grunt-download-atom-shell#v0.5.0",
|
||||
"grunt-lesslint": "0.13.0",
|
||||
"grunt-markdown": "~0.4.0",
|
||||
"grunt-shell": "~0.3.1",
|
||||
"harmony-collections": "~0.3.8",
|
||||
"js-yaml": "~2.1.0",
|
||||
"json-front-matter": "~0.1.3",
|
||||
"rcedit": "~0.1.2",
|
||||
"request": "~2.27.0",
|
||||
"rimraf": "~2.2.2",
|
||||
"unzip": "~0.1.9",
|
||||
"walkdir": "0.0.7",
|
||||
"grunt-peg": "~1.1.0"
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ module.exports = (grunt) ->
|
||||
|
||||
cp 'atom.sh', path.join(appDir, 'atom.sh')
|
||||
cp 'package.json', path.join(appDir, 'package.json')
|
||||
cp 'apm', path.join(appDir, 'apm')
|
||||
|
||||
packageDirectories = []
|
||||
nonPackageDirectories = [
|
||||
@@ -0,0 +1,25 @@
|
||||
module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
grunt.registerTask 'codesign', 'Codesign the app', ->
|
||||
done = @async()
|
||||
|
||||
if process.env.XCODE_KEYCHAIN
|
||||
unlockKeychain (error) ->
|
||||
if error?
|
||||
done(error)
|
||||
else
|
||||
signApp(done)
|
||||
else
|
||||
signApp(done)
|
||||
|
||||
unlockKeychain = (callback) ->
|
||||
cmd = 'security'
|
||||
{XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env
|
||||
args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN]
|
||||
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)
|
||||
@@ -3,13 +3,13 @@ fs = require 'fs'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
cmd = path.join('node_modules', '.bin', 'coffee')
|
||||
commonArgs = [path.join('node_modules', '.bin', 'biscotto'), '--']
|
||||
commonArgs = [path.join('build', 'node_modules', '.bin', 'biscotto'), '--']
|
||||
opts =
|
||||
stdio: 'inherit'
|
||||
|
||||
grunt.registerTask 'build-docs', 'Builds the API docs in src/app', ->
|
||||
done = @async()
|
||||
args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/', '../telepath/src/range.coffee', '../telepath/src/point.coffee', '../telepath/src/string-marker.coffee']
|
||||
args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/', '../text-buffer/src/range.coffee', '../text-buffer/src/point.coffee', '../text-buffer/src/marker.coffee']
|
||||
grunt.util.spawn({cmd, args, opts}, done)
|
||||
|
||||
grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', ->
|
||||
@@ -0,0 +1,25 @@
|
||||
module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
grunt.registerTask 'output-disk-space', 'Print diskspace available', ->
|
||||
return unless process.platform is 'darwin'
|
||||
|
||||
done = @async()
|
||||
|
||||
cmd = 'df'
|
||||
args = ['-Hl']
|
||||
spawn {cmd, args}, (error, result, code) ->
|
||||
return done(error) if error?
|
||||
|
||||
lines = result.stdout.split("\n")
|
||||
|
||||
for line in lines[1..]
|
||||
[filesystem, size, used, avail, capacity, extra] = line.split(/\s+/)
|
||||
capacity = parseInt(capacity)
|
||||
|
||||
if capacity > 90
|
||||
grunt.log.error("#{filesystem} is at #{capacity}% capacity!")
|
||||
else if capacity > 80
|
||||
grunt.log.ok("#{filesystem} is at #{capacity}% capacity.")
|
||||
|
||||
done()
|
||||
Arquivo executável → Arquivo normal
+69
-34
@@ -1,7 +1,3 @@
|
||||
#!/usr/bin/env coffee
|
||||
|
||||
return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
|
||||
|
||||
child_process = require 'child_process'
|
||||
path = require 'path'
|
||||
|
||||
@@ -10,6 +6,7 @@ fs = require 'fs-plus'
|
||||
GitHub = require 'github-releases'
|
||||
request = require 'request'
|
||||
|
||||
grunt = null
|
||||
maxReleases = 10
|
||||
assetName = 'atom-mac.zip'
|
||||
assetPath = "/tmp/atom-build/#{assetName}"
|
||||
@@ -19,16 +16,41 @@ defaultHeaders =
|
||||
Authorization: "token #{token}"
|
||||
'User-Agent': 'Atom'
|
||||
|
||||
module.exports = (gruntObject) ->
|
||||
grunt = gruntObject
|
||||
grunt.registerTask 'publish-build', 'Publish the built app', ->
|
||||
return unless process.platform is 'darwin'
|
||||
return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
|
||||
|
||||
done = @async()
|
||||
|
||||
createBuildRelease (error, release) ->
|
||||
return done(error) if error?
|
||||
zipApp (error) ->
|
||||
return done(error) if error?
|
||||
uploadAsset release, (error) ->
|
||||
return done(error) if error?
|
||||
publishRelease release, (error) ->
|
||||
return done(error) if error?
|
||||
getAtomDraftRelease (error, release) ->
|
||||
return done(error) if error?
|
||||
deleteExistingAsset release, (error) ->
|
||||
return done(error) if error?
|
||||
uploadAsset(release, done)
|
||||
|
||||
logError = (message, error, details) ->
|
||||
grunt.log.error(message)
|
||||
grunt.log.error(error.message ? error) if error?
|
||||
grunt.log.error(details) if details
|
||||
|
||||
zipApp = (callback) ->
|
||||
fs.removeSync(assetPath)
|
||||
|
||||
options = {cwd: path.dirname(assetPath), maxBuffer: Infinity}
|
||||
child_process.exec "zip -r --symlinks #{assetName} Atom.app", options, (error, stdout, stderr) ->
|
||||
if error?
|
||||
console.error('Zipping Atom.app failed', error, stderr)
|
||||
process.exit(1)
|
||||
else
|
||||
callback()
|
||||
logError('Zipping Atom.app failed', error, stderr)
|
||||
callback(error)
|
||||
|
||||
getRelease = (callback) ->
|
||||
options =
|
||||
@@ -38,17 +60,29 @@ getRelease = (callback) ->
|
||||
json: true
|
||||
request options, (error, response, releases=[]) ->
|
||||
if error? or response.statusCode isnt 200
|
||||
console.error('Fetching releases failed', error, releases)
|
||||
process.exit(1)
|
||||
logError('Fetching releases failed', error, releases)
|
||||
callback(error ? new Error(response.statusCode))
|
||||
else
|
||||
if releases.length > maxReleases
|
||||
deleteRelease(release) for release in releases[maxReleases..]
|
||||
|
||||
for release in releases when release.name is commitSha
|
||||
callback(release)
|
||||
callback(null, release)
|
||||
return
|
||||
callback()
|
||||
|
||||
getAtomDraftRelease = (callback) ->
|
||||
atomRepo = new GitHub({repo: 'atom/atom', token})
|
||||
atomRepo.getReleases (error, releases=[]) ->
|
||||
if error?
|
||||
logError('Fetching atom/atom releases failed', error, releases)
|
||||
callback(error)
|
||||
else
|
||||
for release in releases when release.draft
|
||||
callback(null, release)
|
||||
return
|
||||
callback(new Error('No draft release in atom/atom repo'))
|
||||
|
||||
deleteRelease = (release) ->
|
||||
options =
|
||||
uri: release.url
|
||||
@@ -57,7 +91,7 @@ deleteRelease = (release) ->
|
||||
json: true
|
||||
request options, (error, response, body='') ->
|
||||
if error? or response.statusCode isnt 204
|
||||
console.error('Deleting release failed', error, body)
|
||||
logError('Deleting release failed', error, body)
|
||||
|
||||
deleteExistingAsset = (release, callback) ->
|
||||
for asset in release.assets when asset.name is assetName
|
||||
@@ -67,8 +101,8 @@ deleteExistingAsset = (release, callback) ->
|
||||
headers: defaultHeaders
|
||||
request options, (error, response, body='') ->
|
||||
if error? or response.statusCode isnt 204
|
||||
console.error('Deleting existing release asset failed', error, body)
|
||||
process.exit(1)
|
||||
logError('Deleting existing release asset failed', error, body)
|
||||
callback(error ? new Error(response.statusCode))
|
||||
else
|
||||
callback()
|
||||
|
||||
@@ -76,11 +110,15 @@ deleteExistingAsset = (release, callback) ->
|
||||
|
||||
callback()
|
||||
|
||||
createRelease = (callback) ->
|
||||
getRelease (release) ->
|
||||
createBuildRelease = (callback) ->
|
||||
getRelease (error, release) ->
|
||||
if error?
|
||||
callback(error)
|
||||
return
|
||||
|
||||
if release?
|
||||
deleteExistingAsset release, ->
|
||||
callback(release)
|
||||
deleteExistingAsset release, (error) ->
|
||||
callback(error, release)
|
||||
return
|
||||
|
||||
options =
|
||||
@@ -91,19 +129,19 @@ createRelease = (callback) ->
|
||||
tag_name: "v#{commitSha}"
|
||||
target_commitish: 'master'
|
||||
name: commitSha
|
||||
body: "Build of [atom@#{commitSha.substring(0, 7)}](https://github.com/atom/atom/commit/#{commitSha})"
|
||||
body: "Build of [atom@#{commitSha.substring(0, 7)}](https://github.com/atom/atom/commits/#{commitSha})"
|
||||
draft: true
|
||||
prerelease: true
|
||||
request options, (error, response, release={}) ->
|
||||
if error? or response.statusCode isnt 201
|
||||
console.error('Creating release failed', error, release)
|
||||
process.exit(1)
|
||||
logError('Creating release failed', error, release)
|
||||
callback(error ? new Error(response.statusCode))
|
||||
else
|
||||
callback(release)
|
||||
callback(null, release)
|
||||
|
||||
uploadAsset = (release, callback) ->
|
||||
options =
|
||||
uri: "https://uploads.github.com/repos/atom/atom-master-builds/releases/#{release.id}/assets?name=#{assetName}"
|
||||
uri: release.upload_url.replace(/\{.*$/, "?name=#{assetName}")
|
||||
method: 'POST'
|
||||
headers: _.extend({
|
||||
'Content-Type': 'application/zip'
|
||||
@@ -112,14 +150,14 @@ uploadAsset = (release, callback) ->
|
||||
|
||||
assetRequest = request options, (error, response, body='') ->
|
||||
if error? or response.statusCode >= 400
|
||||
console.error('Upload release asset failed', error, body)
|
||||
process.exit(1)
|
||||
logError('Upload release asset failed', error, body)
|
||||
callback(error ? new Error(response.statusCode))
|
||||
else
|
||||
callback(release)
|
||||
callback(null, release)
|
||||
|
||||
fs.createReadStream(assetPath).pipe(assetRequest)
|
||||
|
||||
publishRelease = (release) ->
|
||||
publishRelease = (release, callback) ->
|
||||
options =
|
||||
uri: release.url
|
||||
method: 'POST'
|
||||
@@ -128,10 +166,7 @@ publishRelease = (release) ->
|
||||
draft: false
|
||||
request options, (error, response, body={}) ->
|
||||
if error? or response.statusCode isnt 200
|
||||
console.error('Creating release failed', error, body)
|
||||
process.exit(1)
|
||||
|
||||
createRelease (release) ->
|
||||
zipApp ->
|
||||
uploadAsset release, ->
|
||||
publishRelease release
|
||||
logError('Creating release failed', error, body)
|
||||
callback(error ? new Error(response.statusCode))
|
||||
else
|
||||
callback()
|
||||
@@ -6,7 +6,7 @@ module.exports = (grunt) ->
|
||||
|
||||
shellAppDir = grunt.config.get('atom.shellAppDir')
|
||||
shellExePath = path.join(shellAppDir, 'atom.exe')
|
||||
iconPath = path.resolve(__dirname, '..', 'resources', 'win', 'atom.ico')
|
||||
iconPath = path.resolve('resources', 'win', 'atom.ico')
|
||||
|
||||
rcedit = require('rcedit')
|
||||
rcedit(shellExePath, {'icon': iconPath}, done)
|
||||
@@ -4,15 +4,24 @@ path = require 'path'
|
||||
module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
grunt.registerTask 'set-development-version', 'Sets version to current SHA-1', ->
|
||||
getVersion = (callback) ->
|
||||
if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH is 'master'
|
||||
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
|
||||
callback(null, version)
|
||||
else
|
||||
cmd = 'git'
|
||||
args = ['rev-parse', '--short', 'HEAD']
|
||||
spawn {cmd, args}, (error, {stdout}={}, code) ->
|
||||
callback(error, stdout?.trim?())
|
||||
|
||||
grunt.registerTask 'set-version', 'Set the version in the plist and package.json', ->
|
||||
done = @async()
|
||||
|
||||
cmd = 'git'
|
||||
args = ['rev-parse', '--short', 'HEAD']
|
||||
spawn {cmd, args}, (error, result, code) ->
|
||||
return done(error) if error?
|
||||
getVersion (error, version) ->
|
||||
if error?
|
||||
done(error)
|
||||
return
|
||||
|
||||
version = result.stdout.trim()
|
||||
appDir = grunt.config.get('atom.appDir')
|
||||
|
||||
# Replace version field of package.json.
|
||||
@@ -0,0 +1,112 @@
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
async = require 'async'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{isAtomPackage, spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
packageSpecQueue = null
|
||||
|
||||
runPackageSpecs = (callback) ->
|
||||
failedPackages = []
|
||||
rootDir = grunt.config.get('atom.shellAppDir')
|
||||
contentsDir = grunt.config.get('atom.contentsDir')
|
||||
resourcePath = process.cwd()
|
||||
if process.platform is 'darwin'
|
||||
appPath = path.join(contentsDir, 'MacOS', 'Atom')
|
||||
else if process.platform is 'win32'
|
||||
appPath = path.join(contentsDir, 'atom.exe')
|
||||
|
||||
packageSpecQueue = async.queue (packagePath, callback) ->
|
||||
if process.platform is 'darwin'
|
||||
options =
|
||||
cmd: appPath
|
||||
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
|
||||
opts:
|
||||
cwd: packagePath
|
||||
env: _.extend({}, process.env, ATOM_PATH: rootDir)
|
||||
else if process.platform is 'win32'
|
||||
options =
|
||||
cmd: process.env.comspec
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log"]
|
||||
opts:
|
||||
cwd: packagePath
|
||||
env: _.extend({}, process.env, ATOM_PATH: rootDir)
|
||||
|
||||
grunt.verbose.writeln "Launching #{path.basename(packagePath)} specs."
|
||||
spawn options, (error, results, code) ->
|
||||
if process.platform is 'win32'
|
||||
process.stderr.write(fs.readFileSync(path.join(packagePath, 'ci.log')))
|
||||
fs.unlinkSync(path.join(packagePath, 'ci.log'))
|
||||
|
||||
failedPackages.push path.basename(packagePath) if error
|
||||
callback()
|
||||
|
||||
modulesDirectory = path.resolve('node_modules')
|
||||
for packageDirectory in fs.readdirSync(modulesDirectory)
|
||||
packagePath = path.join(modulesDirectory, packageDirectory)
|
||||
continue unless grunt.file.isDir(path.join(packagePath, 'spec'))
|
||||
continue unless isAtomPackage(packagePath)
|
||||
packageSpecQueue.push(packagePath)
|
||||
|
||||
# TODO: Restore concurrency on Windows
|
||||
packageSpecQueue.concurrency = 1 unless process.platform is 'win32'
|
||||
|
||||
packageSpecQueue.drain = -> callback(null, failedPackages)
|
||||
|
||||
runCoreSpecs = (callback) ->
|
||||
contentsDir = grunt.config.get('atom.contentsDir')
|
||||
if process.platform is 'darwin'
|
||||
appPath = path.join(contentsDir, 'MacOS', 'Atom')
|
||||
else if process.platform is 'win32'
|
||||
appPath = path.join(contentsDir, 'atom.exe')
|
||||
resourcePath = process.cwd()
|
||||
coreSpecsPath = path.resolve('spec')
|
||||
|
||||
if process.platform is 'darwin'
|
||||
options =
|
||||
cmd: appPath
|
||||
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
|
||||
else if process.platform is 'win32'
|
||||
options =
|
||||
cmd: process.env.comspec
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}", "--log-file=ci.log"]
|
||||
|
||||
spawn options, (error, results, code) ->
|
||||
if process.platform is 'win32'
|
||||
process.stderr.write(fs.readFileSync('ci.log'))
|
||||
fs.unlinkSync('ci.log')
|
||||
else
|
||||
# TODO: Restore concurrency on Windows
|
||||
packageSpecQueue.concurrency = 2
|
||||
|
||||
callback(null, error)
|
||||
|
||||
grunt.registerTask 'run-specs', 'Run the specs', ->
|
||||
done = @async()
|
||||
startTime = Date.now()
|
||||
|
||||
# TODO: This should really be parallel on both platforms, however our
|
||||
# fixtures step on each others toes currently.
|
||||
if process.platform is 'darwin'
|
||||
method = async.parallel
|
||||
else if process.platform is 'win32'
|
||||
method = async.series
|
||||
|
||||
method [runCoreSpecs, runPackageSpecs], (error, results) ->
|
||||
[coreSpecFailed, failedPackages] = results
|
||||
elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
|
||||
grunt.verbose.writeln("Total spec time: #{elapsedTime}s")
|
||||
failures = failedPackages
|
||||
failures.push "atom core" if coreSpecFailed
|
||||
|
||||
grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0
|
||||
|
||||
# TODO: Mark the build as green on Windows until specs pass.
|
||||
if process.platform is 'darwin'
|
||||
done(!coreSpecFailed and failedPackages.length == 0)
|
||||
else if process.platform is 'win32'
|
||||
done(true)
|
||||
@@ -39,7 +39,7 @@ module.exports = (grunt) ->
|
||||
proc = childProcess.spawn(options.cmd, options.args, options.opts)
|
||||
proc.stdout.on 'data', (data) -> stdout.push(data.toString())
|
||||
proc.stderr.on 'data', (data) -> stderr.push(data.toString())
|
||||
proc.on 'exit', (exitCode, signal) ->
|
||||
proc.on 'close', (exitCode, signal) ->
|
||||
error = new Error(signal) if exitCode != 0
|
||||
results = {stderr: stderr.join(''), stdout: stdout.join(''), code: exitCode}
|
||||
grunt.log.error results.stderr if exitCode != 0
|
||||
@@ -40,25 +40,18 @@ window, and contains every other view. If you open Atom's inspector with
|
||||
|
||||
#### Panes
|
||||
|
||||
The `WorkspaceView` contains a `#horizontal` and a `#vertical` axis surrounding
|
||||
`#panes`. Elements in the horizontal axis will tile across the window
|
||||
horizontally, appearing to have a vertical orientation. Items in the vertical
|
||||
axis will tile across the window vertically, appearing to have a horizontal
|
||||
orientation. You would typically attach tool panels to the root view's primary
|
||||
axes. Tool panels are elements which take up some screen real estate that isn't
|
||||
devoted to direct editing. In the example above, the `TreeView` is present in
|
||||
the `#horizontal` axis to the left of the `#panes`, and the `CommandPanel` is
|
||||
present in the `#vertical` axis below the `#panes`.
|
||||
|
||||
You can attach a tool panel to an axis using the `horizontal` or `vertical`
|
||||
outlets as follows:
|
||||
The `WorkspaceView` contains `prependToBottom/Top/Left/Right` and
|
||||
`appendToBottom/Top/Left/Right` methods, which are used to add Tool Panels. Tool
|
||||
panels are elements that take up screen real estate not devoted to text editing.
|
||||
In the example above, the `TreeView` is appended to the left, and the
|
||||
`CommandPanel` is appended to the top.
|
||||
|
||||
```coffeescript
|
||||
# place a view to the left of the panes (or use .append() to place it to the right)
|
||||
atom.workspaceView.horizontal.prepend(new MyView)
|
||||
# place a view to the left of the panes
|
||||
atom.workspaceView.appendToLeft(new MyView)
|
||||
|
||||
# place a view below the panes (or use .prepend() to place it above)
|
||||
atom.workspaceView.vertical.append(new MyOtherView)
|
||||
# place a view below the panes
|
||||
atom.workspaceView.appendToBottom(new MyOtherView)
|
||||
```
|
||||
|
||||
[spacepen]: http://github.com/nathansobo/space-pen
|
||||
|
||||
@@ -177,32 +177,33 @@ ul.modified-files-list {
|
||||
}
|
||||
```
|
||||
|
||||
We'll add one more line to the end of the `magic` method to make this pane appear:
|
||||
We'll add one more line to the end of the `magic` method to make this pane
|
||||
appear:
|
||||
|
||||
```coffeescript
|
||||
atom.workspaceView.vertical.append(this)
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
If you refresh Atom and hit the key command, you'll see a box appear right underneath
|
||||
the editor:
|
||||
If you refresh Atom and hit the key command, you'll see a box appear right
|
||||
underneath the editor:
|
||||
|
||||
![Changer_Panel_Append]
|
||||
|
||||
As you might have guessed, `atom.workspaceView.vertical.append` tells Atom to append `this`
|
||||
item (_i.e._, whatever is defined by`@content`) _vertically_ to the editor. If
|
||||
we had called `atom.workspaceView.horizontal.append`, the pane would be attached to the
|
||||
right-hand side of the editor.
|
||||
As you might have guessed, `atom.workspaceView.prependToBottom` tells Atom to
|
||||
prepend `this` item (_i.e._, whatever is defined by`@content`). If we had called
|
||||
`atom.workspaceView.appendToBottom`, the pane would be attached below the status
|
||||
bar.
|
||||
|
||||
Before we populate this panel for real, let's apply some logic to toggle the pane
|
||||
off and on, just like we did with the tree view. Replace the `atom.workspaceView.vertical.append`
|
||||
call with this code:
|
||||
Before we populate this panel for real, let's apply some logic to toggle the
|
||||
pane off and on, just like we did with the tree view. Replace the
|
||||
`atom.workspaceView.prependToBottom` call with this code:
|
||||
|
||||
```coffeescript
|
||||
# toggles the pane
|
||||
if @hasParent()
|
||||
atom.workspaceView.vertical.children().last().remove()
|
||||
@remove()
|
||||
else
|
||||
atom.workspaceView.vertical.append(this)
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
There are about a hundred different ways to toggle a pane on and off, and this
|
||||
@@ -261,13 +262,13 @@ appending it to `modifiedFilesList`:
|
||||
```coffeescript
|
||||
# toggles the pane
|
||||
if @hasParent()
|
||||
atom.workspaceView.vertical.children().last().remove()
|
||||
@remove()
|
||||
else
|
||||
for file in modifiedFiles
|
||||
stat = fs.lstatSync(file)
|
||||
mtime = stat.mtime
|
||||
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
|
||||
atom.workspaceView.vertical.append(this)
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
When you toggle the modified files list, your pane is now populated with the
|
||||
@@ -283,13 +284,13 @@ this demonstration, we'll just clear the `modifiedFilesList` each time it's clos
|
||||
# toggles the pane
|
||||
if @hasParent()
|
||||
@modifiedFilesList.empty() # added this to clear the list on close
|
||||
atom.workspaceView.vertical.children().last().remove()
|
||||
@remove()
|
||||
else
|
||||
for file in modifiedFiles
|
||||
stat = fs.lstatSync(file)
|
||||
mtime = stat.mtime
|
||||
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
|
||||
atom.workspaceView.vertical.append(this)
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
## Coloring UI Elements
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
{Document, Model, Point, Range} = require 'telepath'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
|
||||
module.exports =
|
||||
_: require 'underscore-plus'
|
||||
BufferedNodeProcess: require '../src/buffered-node-process'
|
||||
BufferedProcess: require '../src/buffered-process'
|
||||
Directory: require '../src/directory'
|
||||
Document: Document
|
||||
File: require '../src/file'
|
||||
fs: require 'fs-plus'
|
||||
Git: require '../src/git'
|
||||
Model: Model
|
||||
Point: Point
|
||||
Range: Range
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
'alt-shift-left': 'editor:select-to-beginning-of-word'
|
||||
'alt-shift-right': 'editor:select-to-end-of-word'
|
||||
'home': 'editor:move-to-first-character-of-line'
|
||||
'end': 'editor:move-to-end-of-line'
|
||||
'end': 'editor:move-to-end-of-screen-line'
|
||||
'shift-home': 'editor:select-to-first-character-of-line'
|
||||
'shift-end': 'editor:select-to-end-of-line'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Apple specific
|
||||
'cmd-q': 'application:quit'
|
||||
'cmd-h': 'application:hide'
|
||||
'cmd-H': 'application:hide-other-applications'
|
||||
'cmd-alt-h': 'application:hide-other-applications'
|
||||
'cmd-m': 'application:minimize'
|
||||
'alt-cmd-ctrl-m': 'application:zoom'
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
'down': 'core:move-down'
|
||||
'left': 'core:move-left'
|
||||
'right': 'core:move-right'
|
||||
'ctrl-alt-cmd-r': 'window:reload'
|
||||
'ctrl-alt-cmd-l': 'window:reload'
|
||||
'alt-cmd-i': 'window:toggle-dev-tools'
|
||||
'cmd-alt-ctrl-p': 'window:run-package-specs'
|
||||
|
||||
@@ -94,12 +94,12 @@
|
||||
'ctrl-A': 'editor:select-to-first-character-of-line'
|
||||
'ctrl-E': 'editor:select-to-end-of-line'
|
||||
'cmd-left': 'editor:move-to-first-character-of-line'
|
||||
'cmd-right': 'editor:move-to-end-of-line'
|
||||
'cmd-right': 'editor:move-to-end-of-screen-line'
|
||||
'cmd-shift-left': 'editor:select-to-first-character-of-line'
|
||||
'cmd-shift-right': 'editor:select-to-end-of-line'
|
||||
'alt-backspace': 'editor:backspace-to-beginning-of-word'
|
||||
'alt-delete': 'editor:delete-to-end-of-word'
|
||||
'ctrl-a': 'editor:move-to-first-character-of-line'
|
||||
'ctrl-a': 'editor:move-to-beginning-of-line'
|
||||
'ctrl-e': 'editor:move-to-end-of-line'
|
||||
'ctrl-k': 'editor:cut-to-end-of-line'
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
'cmd-alt-p': 'editor:log-cursor-scope'
|
||||
'cmd-k cmd-u': 'editor:upper-case'
|
||||
'cmd-k cmd-l': 'editor:lower-case'
|
||||
'cmd-l': 'editor:select-line'
|
||||
|
||||
'body.platform-darwin .editor:not(.mini)':
|
||||
# Atom specific
|
||||
@@ -129,6 +130,7 @@
|
||||
'cmd-/': 'editor:toggle-line-comments'
|
||||
'cmd-j': 'editor:join-line'
|
||||
'cmd-D': 'editor:duplicate-line'
|
||||
'cmd-L': 'editor:split-selections-into-lines'
|
||||
|
||||
'cmd-alt-[': 'editor:fold-current-row'
|
||||
'cmd-alt-]': 'editor:unfold-current-row'
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
submenu: [
|
||||
{ label: 'Add Selection Above', command: 'editor:add-selection-above' }
|
||||
{ label: 'Add Selection Below', command: 'editor:add-selection-below' }
|
||||
{ label: 'Split into Lines', command: 'editor:split-selections-into-lines'}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
submenu: [
|
||||
{ label: 'Add Selection &Above', command: 'editor:add-selection-above' }
|
||||
{ label: 'Add Selection &Below', command: 'editor:add-selection-below' }
|
||||
{ label: 'S&plit into Lines', command: 'editor:split-selections-into-lines'}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to &Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Botto&m', command: 'core:select-to-bottom' }
|
||||
|
||||
+48
-68
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.44.0",
|
||||
"version": "0.45.0",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,110 +23,91 @@
|
||||
"clear-cut": "0.2.0",
|
||||
"coffee-script": "1.6.3",
|
||||
"coffeestack": "0.6.0",
|
||||
"diff": "git://github.com/benogle/jsdiff.git",
|
||||
"emissary": "0.19.0",
|
||||
"first-mate": "0.5.0",
|
||||
"fs-plus": "0.11.0",
|
||||
"fuzzaldrin": "0.1.0",
|
||||
"emissary": "0.31.0",
|
||||
"first-mate": "0.17.0",
|
||||
"fs-plus": "0.14.0",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "0.6.0",
|
||||
"git-utils": "0.29.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-focused": "~0.15.0",
|
||||
"jasmine-node": "git://github.com/kevinsawicki/jasmine-node.git#short-stacks",
|
||||
"jasmine-tagged": "0.2.0",
|
||||
"mkdirp": "0.3.5",
|
||||
"keytar": "0.13.0",
|
||||
"less-cache": "0.10.0",
|
||||
"serializable": "0.3.0",
|
||||
"nslog": "0.1.0",
|
||||
"oniguruma": "0.24.0",
|
||||
"optimist": "0.4.0",
|
||||
"pathwatcher": "0.11.0",
|
||||
"pegjs": "0.7.0",
|
||||
"pegjs": "0.8.0",
|
||||
"q": "0.9.7",
|
||||
"scandal": "0.8.0",
|
||||
"scandal": "0.11.0",
|
||||
"season": "0.14.0",
|
||||
"semver": "1.1.4",
|
||||
"space-pen": "2.0.2",
|
||||
"telepath": "0.70.0",
|
||||
"space-pen": "3.1.0",
|
||||
"temp": "0.5.0",
|
||||
"underscore-plus": "0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"biscotto": "0.0.17",
|
||||
"formidable": "~1.0.14",
|
||||
"fstream": "0.1.24",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.9",
|
||||
"grunt-coffeelint": "git://github.com/atom/grunt-coffeelint.git",
|
||||
"grunt-lesslint": "0.13.0",
|
||||
"grunt-cson": "0.5.0",
|
||||
"grunt-contrib-csslint": "~0.1.2",
|
||||
"grunt-contrib-coffee": "~0.7.0",
|
||||
"grunt-contrib-less": "~0.8.0",
|
||||
"walkdir": "0.0.7",
|
||||
"js-yaml": "~2.1.0",
|
||||
"grunt-markdown": "~0.4.0",
|
||||
"json-front-matter": "~0.1.3",
|
||||
"grunt-shell": "~0.3.1",
|
||||
"jasmine-node": "git://github.com/kevinsawicki/jasmine-node.git#short-stacks",
|
||||
"jasmine-tagged": "0.2.0",
|
||||
"request": "~2.27.0",
|
||||
"unzip": "~0.1.9",
|
||||
"rcedit": "~0.1.2",
|
||||
"rimraf": "~2.2.2",
|
||||
"github-releases": "~0.2.0"
|
||||
"text-buffer": "0.12.0",
|
||||
"underscore-plus": "0.6.1",
|
||||
"theorist": "~0.13.0",
|
||||
"delegato": "~0.4.0",
|
||||
"mixto": "~0.4.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.10.0",
|
||||
"atom-dark-ui": "0.17.0",
|
||||
"atom-dark-ui": "0.19.0",
|
||||
"atom-light-syntax": "0.10.0",
|
||||
"atom-light-ui": "0.16.0",
|
||||
"atom-light-ui": "0.18.0",
|
||||
"base16-tomorrow-dark-theme": "0.8.0",
|
||||
"solarized-dark-syntax": "0.6.0",
|
||||
"solarized-light-syntax": "0.2.0",
|
||||
"archive-view": "0.16.0",
|
||||
"autocomplete": "0.18.0",
|
||||
"archive-view": "0.19.0",
|
||||
"autocomplete": "0.19.0",
|
||||
"autoflow": "0.11.0",
|
||||
"autosave": "0.10.0",
|
||||
"background-tips": "0.4.0",
|
||||
"bookmarks": "0.15.0",
|
||||
"bracket-matcher": "0.15.0",
|
||||
"bracket-matcher": "0.16.0",
|
||||
"command-logger": "0.8.0",
|
||||
"command-palette": "0.13.0",
|
||||
"dev-live-reload": "0.20.0",
|
||||
"editor-stats": "0.9.0",
|
||||
"exception-reporting": "0.9.0",
|
||||
"feedback": "0.18.0",
|
||||
"find-and-replace": "0.62.0",
|
||||
"fuzzy-finder": "0.28.0",
|
||||
"command-palette": "0.14.0",
|
||||
"dev-live-reload": "0.22.0",
|
||||
"editor-stats": "0.12.0",
|
||||
"exception-reporting": "0.11.0",
|
||||
"feedback": "0.22.0",
|
||||
"find-and-replace": "0.74.0",
|
||||
"fuzzy-finder": "0.30.0",
|
||||
"gists": "0.13.0",
|
||||
"git-diff": "0.21.0",
|
||||
"github-sign-in": "0.15.0",
|
||||
"go-to-line": "0.12.0",
|
||||
"grammar-selector": "0.13.0",
|
||||
"image-view": "0.10.0",
|
||||
"keybinding-resolver": "0.6.0",
|
||||
"link": "0.11.0",
|
||||
"markdown-preview": "0.22.0",
|
||||
"metrics": "0.20.0",
|
||||
"go-to-line": "0.14.0",
|
||||
"grammar-selector": "0.16.0",
|
||||
"image-view": "0.15.0",
|
||||
"keybinding-resolver": "0.8.0",
|
||||
"markdown-preview": "0.24.0",
|
||||
"metrics": "0.21.0",
|
||||
"package-generator": "0.23.0",
|
||||
"release-notes": "0.15.0",
|
||||
"settings-view": "0.51.0",
|
||||
"snippets": "0.17.0",
|
||||
"spell-check": "0.17.0",
|
||||
"status-bar": "0.27.0",
|
||||
"styleguide": "0.18.0",
|
||||
"settings-view": "0.55.0",
|
||||
"snippets": "0.18.0",
|
||||
"spell-check": "0.18.0",
|
||||
"status-bar": "0.31.0",
|
||||
"styleguide": "0.19.0",
|
||||
"symbols-view": "0.27.0",
|
||||
"tabs": "0.16.0",
|
||||
"tabs": "0.17.0",
|
||||
"terminal": "0.23.0",
|
||||
"timecop": "0.11.0",
|
||||
"to-the-hubs": "0.16.0",
|
||||
"tree-view": "0.50.0",
|
||||
"timecop": "0.13.0",
|
||||
"to-the-hubs": "0.17.0",
|
||||
"tree-view": "0.59.0",
|
||||
"visual-bell": "0.6.0",
|
||||
"welcome": "0.4.0",
|
||||
"whitespace": "0.10.0",
|
||||
"wrap-guide": "0.10.0",
|
||||
"wrap-guide": "0.11.0",
|
||||
"language-c": "0.2.0",
|
||||
"language-clojure": "0.1.0",
|
||||
"language-coffee-script": "0.4.0",
|
||||
"language-css": "0.2.0",
|
||||
"language-gfm": "0.10.0",
|
||||
"language-gfm": "0.11.0",
|
||||
"language-git": "0.3.0",
|
||||
"language-go": "0.2.0",
|
||||
"language-html": "0.2.0",
|
||||
@@ -154,8 +135,7 @@
|
||||
"language-todo": "0.2.0",
|
||||
"language-toml": "0.7.0",
|
||||
"language-xml": "0.2.0",
|
||||
"language-yaml": "0.1.0",
|
||||
"grunt-download-atom-shell": "0.4.0"
|
||||
"language-yaml": "0.1.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+16
-5
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --harmony_collections
|
||||
var safeExec = require('./utils/child-process-wrapper.js').safeExec;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// OAuth token for atom-bot
|
||||
@@ -22,16 +23,26 @@ function executeCommands(commands, done, index) {
|
||||
done(null);
|
||||
}
|
||||
|
||||
var apmVendorPath = path.resolve(__dirname, '..', 'vendor', 'apm');
|
||||
var apmInstallPath = path.resolve(__dirname, '..', 'apm');
|
||||
if (!fs.existsSync(apmInstallPath))
|
||||
fs.mkdirSync(apmInstallPath);
|
||||
if (!fs.existsSync(path.join(apmInstallPath, 'node_modules')))
|
||||
fs.mkdirSync(path.join(apmInstallPath, 'node_modules'));
|
||||
|
||||
var apmFlags = process.env.JANKY_SHA1 || process.argv.indexOf('--no-color') !== -1 ? '--no-color' : '';
|
||||
var echoNewLine = process.platform == 'win32' ? 'echo.' : 'echo';
|
||||
var commands = [
|
||||
'git submodule --quiet sync',
|
||||
'git submodule --quiet update --recursive --init',
|
||||
{command: 'npm install --quiet .', options: {cwd: path.resolve(__dirname, '..', 'vendor', 'apm'), ignoreStdout: true}},
|
||||
{command: 'npm install --quiet vendor/apm', options: {ignoreStdout: true}},
|
||||
{command: 'npm install --quiet', options: {cwd: path.resolve(__dirname, '..', 'build'), ignoreStdout: true}},
|
||||
{command: 'npm install --quiet', options: {cwd: apmVendorPath, ignoreStdout: true}},
|
||||
{command: 'npm install --quiet ' + apmVendorPath, options: {cwd: apmInstallPath, ignoreStdout: true}},
|
||||
{command: 'npm install --quiet ' + apmVendorPath, options: {ignoreStdout: true}},
|
||||
{command: 'node ../../apm/node_modules/atom-package-manager/bin/apm rebuild', options: {cwd: path.resolve('node_modules', 'atom-package-manager'), ignoreStdout: true}},
|
||||
echoNewLine,
|
||||
'node node_modules/atom-package-manager/bin/apm clean ' + apmFlags,
|
||||
'node node_modules/atom-package-manager/bin/apm install --quiet ' + apmFlags,
|
||||
'node apm/node_modules/atom-package-manager/bin/apm clean ' + apmFlags,
|
||||
'node apm/node_modules/atom-package-manager/bin/apm install --quiet ' + apmFlags,
|
||||
];
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
+6
-4
@@ -1,11 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --harmony_collections
|
||||
var cp = require('./utils/child-process-wrapper.js');
|
||||
var path = require('path');
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
cp.safeExec('node script/bootstrap', function() {
|
||||
// node_modules/.bin/grunt "$@"
|
||||
var gruntPath = path.join('node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
cp.safeSpawn(gruntPath, process.argv.slice(2), process.exit);
|
||||
// build/node_modules/.bin/grunt "$@"
|
||||
var gruntPath = path.join('build', 'node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
var args = ['--gruntfile', path.resolve('build', 'Gruntfile.coffee')];
|
||||
args = args.concat(process.argv.slice(2));
|
||||
cp.safeSpawn(gruntPath, args, process.exit);
|
||||
});
|
||||
|
||||
+16
-13
@@ -1,20 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --harmony_collections
|
||||
var cp = require('./utils/child-process-wrapper.js');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
if (process.platform != 'darwin')
|
||||
throw new Error('cibuild can not run on ' + process.platform + ' yet!');
|
||||
if (process.platform == 'linux')
|
||||
throw new Error('cibuild can not run on linux yet!');
|
||||
|
||||
var homeDir = process.platform == 'win32' ? process.env.USERPROFILE : process.env.HOME;
|
||||
|
||||
function readEnvironmentVariables() {
|
||||
var credentialsPath = '/var/lib/jenkins/config/atomcredentials';
|
||||
function loadEnvironmentVariables(filePath) {
|
||||
try {
|
||||
var credentials = fs.readFileSync(credentialsPath, 'utf8');
|
||||
var lines = credentials.trim().split('\n');
|
||||
var lines = fs.readFileSync(filePath, 'utf8').trim().split('\n');
|
||||
for (i in lines) {
|
||||
var parts = lines[i].split('=');
|
||||
var key = parts[0].trim();
|
||||
@@ -24,18 +22,23 @@ function readEnvironmentVariables() {
|
||||
} catch(error) { }
|
||||
}
|
||||
|
||||
function readEnvironmentVariables() {
|
||||
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials')
|
||||
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain')
|
||||
}
|
||||
|
||||
readEnvironmentVariables();
|
||||
cp.safeExec.bind(global, 'node script/bootstrap', function(error) {
|
||||
if (error)
|
||||
process.exit(1);
|
||||
require('fs-plus').removeSync.bind(global, path.join(homeDir, '.atom'))
|
||||
var async = require('async');
|
||||
var gruntPath = path.join('node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
async.series([
|
||||
require('rimraf').bind(global, path.join(homeDir, '.atom')),
|
||||
var gruntPath = path.join('build', 'node_modules', '.bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
var tasks = [
|
||||
cp.safeExec.bind(global, 'git clean -dff'),
|
||||
cp.safeExec.bind(global, gruntPath + ' ci --stack --no-color'),
|
||||
cp.safeExec.bind(global, 'node_modules/.bin/coffee script/upload-release')
|
||||
], function(error) {
|
||||
cp.safeExec.bind(global, gruntPath + ' ci --gruntfile build/Gruntfile.coffee --stack --no-color'),
|
||||
]
|
||||
async.series(tasks, function(error) {
|
||||
process.exit(error ? 1 : 0);
|
||||
});
|
||||
})();
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --harmony_collections
|
||||
var cp = require('./utils/child-process-wrapper.js');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
@@ -16,6 +16,9 @@ var killatom = process.platform === 'win32' ? 'START taskkill /F /IM ' + product
|
||||
var commands = [
|
||||
killatom,
|
||||
[__dirname, '..', 'node_modules'],
|
||||
[__dirname, '..', 'build', 'node_modules'],
|
||||
[__dirname, '..', 'apm', 'node_modules'],
|
||||
[__dirname, '..', 'vendor', 'apm', 'node_modules'],
|
||||
[__dirname, '..', 'atom-shell'],
|
||||
[home, '.atom', '.node-gyp'],
|
||||
[home, '.atom', 'storage'],
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
# This entire file is a hack so that constructicon can build Atom via
|
||||
# xcode
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
rm -fr node_modules
|
||||
rm -fr vendor/apm/node_modules
|
||||
./script/bootstrap --no-color
|
||||
./node_modules/.bin/grunt --no-color --build-dir="$BUILT_PRODUCTS_DIR" deploy
|
||||
|
||||
echo "TARGET_BUILD_DIR=$BUILT_PRODUCTS_DIR"
|
||||
echo "FULL_PRODUCT_NAME=Atom.app"
|
||||
echo "PRODUCT_NAME=Atom"
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
cd "$(dirname "$0")/../.."
|
||||
export PATH="atom-shell/Atom.app/Contents/Resources/:${PATH}"
|
||||
|
||||
rm -rf atom.xcodeproj
|
||||
gyp --depth=. atom.gyp
|
||||
Arquivo executável
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env node --harmony_collections
|
||||
var cp = require('./utils/child-process-wrapper.js');
|
||||
var path = require('path');
|
||||
|
||||
// node build/node_modules/grunt-cli/bin/grunt "$@"
|
||||
var gruntPath = path.resolve(__dirname, '..', 'build', 'node_modules', 'grunt-cli', 'bin', 'grunt') + (process.platform === 'win32' ? '.cmd' : '');
|
||||
var args = [gruntPath, '--gruntfile', path.resolve('build', 'Gruntfile.coffee')];
|
||||
args = args.concat(process.argv.slice(2));
|
||||
cp.safeSpawn(process.execPath, args, process.exit);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --harmony_collections
|
||||
var safeExec = require('./utils/child-process-wrapper.js').safeExec;
|
||||
var path = require('path');
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ describe "the `atom` global", ->
|
||||
describe ".isReleasedVersion()", ->
|
||||
it "returns false if the version is a SHA and true otherwise", ->
|
||||
version = '0.1.0'
|
||||
spyOn(atom, 'getVersion').andCallFake -> version
|
||||
spyOn(atom.constructor, 'getVersion').andCallFake -> version
|
||||
expect(atom.isReleasedVersion()).toBe true
|
||||
version = '36b5518'
|
||||
expect(atom.isReleasedVersion()).toBe false
|
||||
|
||||
@@ -68,7 +68,7 @@ describe "Directory", ->
|
||||
|
||||
describe "on #darwin or #linux", ->
|
||||
it "includes symlink information about entries", ->
|
||||
entries = directory.getEntries()
|
||||
entries = directory.getEntriesSync()
|
||||
for entry in entries
|
||||
name = entry.getBaseName()
|
||||
if name is 'symlink-to-dir' or name is 'symlink-to-file'
|
||||
@@ -76,6 +76,20 @@ describe "Directory", ->
|
||||
else
|
||||
expect(entry.symlink).toBeFalsy()
|
||||
|
||||
callback = jasmine.createSpy('getEntries')
|
||||
directory.getEntries(callback)
|
||||
|
||||
waitsFor -> callback.callCount is 1
|
||||
|
||||
runs ->
|
||||
entries = callback.mostRecentCall.args[1]
|
||||
for entry in entries
|
||||
name = entry.getBaseName()
|
||||
if name is 'symlink-to-dir' or name is 'symlink-to-file'
|
||||
expect(entry.symlink).toBeTruthy()
|
||||
else
|
||||
expect(entry.symlink).toBeFalsy()
|
||||
|
||||
describe ".relativize(path)", ->
|
||||
describe "on #darwin or #linux", ->
|
||||
it "returns a relative path based on the directory's path", ->
|
||||
|
||||
@@ -7,7 +7,7 @@ describe "DisplayBuffer", ->
|
||||
tabLength = 2
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
displayBuffer = atom.create(new DisplayBuffer({buffer, tabLength}))
|
||||
displayBuffer = new DisplayBuffer({buffer, tabLength})
|
||||
changeHandler = jasmine.createSpy 'changeHandler'
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
|
||||
@@ -150,7 +150,7 @@ describe "DisplayBuffer", ->
|
||||
describe "when a newline is inserted, deleted, and re-inserted at the end of a wrapped line (regression)", ->
|
||||
it "correctly renders the original wrapped line", ->
|
||||
buffer = atom.project.buildBufferSync(null, '')
|
||||
displayBuffer = atom.create(new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrap: true}))
|
||||
displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrap: true})
|
||||
|
||||
buffer.insert([0, 0], "the quick brown fox jumps over the lazy dog.")
|
||||
buffer.insert([0, Infinity], '\n')
|
||||
@@ -202,7 +202,7 @@ describe "DisplayBuffer", ->
|
||||
displayBuffer.destroy()
|
||||
buffer.release()
|
||||
buffer = atom.project.bufferForPathSync('two-hundred.txt')
|
||||
displayBuffer = atom.create(new DisplayBuffer({buffer, tabLength}))
|
||||
displayBuffer = new DisplayBuffer({buffer, tabLength})
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
|
||||
describe "when folds are created and destroyed", ->
|
||||
@@ -308,7 +308,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when there is another display buffer pointing to the same buffer", ->
|
||||
it "does not create folds in the other display buffer", ->
|
||||
otherDisplayBuffer = atom.create(new DisplayBuffer({buffer, tabLength}))
|
||||
otherDisplayBuffer = new DisplayBuffer({buffer, tabLength})
|
||||
displayBuffer.createFold(2, 4)
|
||||
expect(otherDisplayBuffer.foldsStartingAtBufferRow(2).length).toBe 0
|
||||
|
||||
|
||||
+110
-10
@@ -28,10 +28,7 @@ describe "Editor", ->
|
||||
editor.foldBufferRow(4)
|
||||
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
|
||||
|
||||
# Simulate serialization with replicate
|
||||
editor2 = editor.replicate()
|
||||
# FIXME: The created hook is called manually on deserialization because globals aren't ready otherwise
|
||||
editor2.created()
|
||||
editor2 = editor.testSerialization()
|
||||
|
||||
expect(editor2.id).toBe editor.id
|
||||
expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath()
|
||||
@@ -361,13 +358,13 @@ describe "Editor", ->
|
||||
expect(editor.getCursors().length).toBe 1
|
||||
expect(editor.getCursorBufferPosition()).toEqual [12,2]
|
||||
|
||||
describe ".moveCursorToBeginningOfLine()", ->
|
||||
describe ".moveCursorToBeginningOfScreenLine()", ->
|
||||
describe "when soft wrap is on", ->
|
||||
it "moves cursor to the beginning of the screen line", ->
|
||||
editor.setSoftWrap(true)
|
||||
editor.setEditorWidthInChars(10)
|
||||
editor.setCursorScreenPosition([1, 2])
|
||||
editor.moveCursorToBeginningOfLine()
|
||||
editor.moveCursorToBeginningOfScreenLine()
|
||||
cursor = editor.getCursor()
|
||||
expect(cursor.getScreenPosition()).toEqual [1, 0]
|
||||
|
||||
@@ -375,19 +372,19 @@ describe "Editor", ->
|
||||
it "moves cursor to the beginning of then line", ->
|
||||
editor.setCursorScreenPosition [0,5]
|
||||
editor.addCursorAtScreenPosition [1,7]
|
||||
editor.moveCursorToBeginningOfLine()
|
||||
editor.moveCursorToBeginningOfScreenLine()
|
||||
expect(editor.getCursors().length).toBe 2
|
||||
[cursor1, cursor2] = editor.getCursors()
|
||||
expect(cursor1.getBufferPosition()).toEqual [0,0]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1,0]
|
||||
|
||||
describe ".moveCursorToEndOfLine()", ->
|
||||
describe ".moveCursorToEndOfScreenLine()", ->
|
||||
describe "when soft wrap is on", ->
|
||||
it "moves cursor to the beginning of the screen line", ->
|
||||
editor.setSoftWrap(true)
|
||||
editor.setEditorWidthInChars(10)
|
||||
editor.setCursorScreenPosition([1, 2])
|
||||
editor.moveCursorToEndOfLine()
|
||||
editor.moveCursorToEndOfScreenLine()
|
||||
cursor = editor.getCursor()
|
||||
expect(cursor.getScreenPosition()).toEqual [1, 9]
|
||||
|
||||
@@ -395,12 +392,30 @@ describe "Editor", ->
|
||||
it "moves cursor to the end of line", ->
|
||||
editor.setCursorScreenPosition [0,0]
|
||||
editor.addCursorAtScreenPosition [1,0]
|
||||
editor.moveCursorToEndOfLine()
|
||||
editor.moveCursorToEndOfScreenLine()
|
||||
expect(editor.getCursors().length).toBe 2
|
||||
[cursor1, cursor2] = editor.getCursors()
|
||||
expect(cursor1.getBufferPosition()).toEqual [0,29]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1,30]
|
||||
|
||||
describe ".moveCursorToBeginningOfLine()", ->
|
||||
it "moves cursor to the beginning of the buffer line", ->
|
||||
editor.setSoftWrap(true)
|
||||
editor.setEditorWidthInChars(10)
|
||||
editor.setCursorScreenPosition([1, 2])
|
||||
editor.moveCursorToBeginningOfLine()
|
||||
cursor = editor.getCursor()
|
||||
expect(cursor.getScreenPosition()).toEqual [0, 0]
|
||||
|
||||
describe ".moveCursorToEndOfLine()", ->
|
||||
it "moves cursor to the end of the buffer line", ->
|
||||
editor.setSoftWrap(true)
|
||||
editor.setEditorWidthInChars(10)
|
||||
editor.setCursorScreenPosition([0, 2])
|
||||
editor.moveCursorToEndOfLine()
|
||||
cursor = editor.getCursor()
|
||||
expect(cursor.getScreenPosition()).toEqual [3, 4]
|
||||
|
||||
describe ".moveCursorToFirstCharacterOfLine()", ->
|
||||
describe "when soft wrap is on", ->
|
||||
it "moves to the first character of the current screen line or the beginning of the screen line if it's already on the first character", ->
|
||||
@@ -432,6 +447,13 @@ describe "Editor", ->
|
||||
expect(cursor1.getBufferPosition()).toEqual [0,0]
|
||||
expect(cursor2.getBufferPosition()).toEqual [1,0]
|
||||
|
||||
it "moves to the beginning of the line if it only contains whitespace ", ->
|
||||
editor.setText("first\n \nthird")
|
||||
editor.setCursorScreenPosition [1,2]
|
||||
editor.moveCursorToFirstCharacterOfLine()
|
||||
cursor = editor.getCursor()
|
||||
expect(cursor.getBufferPosition()).toEqual [1,0]
|
||||
|
||||
describe ".moveCursorToBeginningOfWord()", ->
|
||||
it "moves the cursor to the beginning of the word", ->
|
||||
editor.setCursorBufferPosition [0, 8]
|
||||
@@ -797,6 +819,11 @@ describe "Editor", ->
|
||||
editor.selectLine()
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[12,0], [12,2]]
|
||||
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
editor.selectLine()
|
||||
editor.selectLine()
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[0,0], [2,0]]
|
||||
|
||||
describe ".selectToBeginningOfWord()", ->
|
||||
it "selects text from cusor position to beginning of word", ->
|
||||
editor.setCursorScreenPosition [0,13]
|
||||
@@ -1159,6 +1186,27 @@ describe "Editor", ->
|
||||
[[10, 0], [10, 0]]
|
||||
]
|
||||
|
||||
describe ".splitSelectionsIntoLines()", ->
|
||||
it "splits all multi-line selections into one selection per line", ->
|
||||
editor.setSelectedBufferRange([[0, 3], [2, 4]])
|
||||
editor.splitSelectionsIntoLines()
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [
|
||||
[[0, 3], [0, 29]]
|
||||
[[1, 0], [1, 30]]
|
||||
[[2, 0], [2, 4]]
|
||||
]
|
||||
|
||||
editor.setSelectedBufferRange([[0, 3], [1, 10]])
|
||||
editor.splitSelectionsIntoLines()
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [
|
||||
[[0, 3], [0, 29]]
|
||||
[[1, 0], [1, 10]]
|
||||
]
|
||||
|
||||
editor.setSelectedBufferRange([[0, 0], [0, 3]])
|
||||
editor.splitSelectionsIntoLines()
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
|
||||
|
||||
describe ".consolidateSelections()", ->
|
||||
it "destroys all selections but the most recent, returning true if any selections were destroyed", ->
|
||||
editor.setSelectedBufferRange([[3, 16], [3, 21]])
|
||||
@@ -2673,3 +2721,55 @@ describe "Editor", ->
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 2]
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
||||
|
||||
describe "when the editor's grammar has an injection selector", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-text', sync: true)
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
|
||||
it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
|
||||
atom.packages.activatePackage('language-hyperlink', sync: true)
|
||||
grammar = atom.syntax.selectGrammar("text.js")
|
||||
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
|
||||
|
||||
expect(tokens[0].value).toBe "var"
|
||||
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
|
||||
|
||||
expect(tokens[6].value).toBe "http://github.com"
|
||||
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
|
||||
|
||||
describe "when the grammar is added", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
editor = atom.project.openSync('sample.js')
|
||||
editor.setText("// http://github.com")
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " http://github.com"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
|
||||
atom.packages.activatePackage('language-hyperlink', sync: true)
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[2].value).toBe "http://github.com"
|
||||
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
|
||||
|
||||
describe "when the grammar is updated", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
editor = atom.project.openSync('sample.js')
|
||||
editor.setText("// SELECT * FROM OCTOCATS")
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
|
||||
atom.packages.activatePackage('package-with-injection-selector', sync: true)
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
|
||||
atom.packages.activatePackage('language-sql', sync: true)
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[2].value).toBe "SELECT"
|
||||
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
|
||||
|
||||
@@ -123,7 +123,7 @@ describe "EditorView", ->
|
||||
describe ".remove()", ->
|
||||
it "destroys the edit session", ->
|
||||
editorView.remove()
|
||||
expect(editorView.editor.destroyed).toBeTruthy()
|
||||
expect(editorView.editor.isDestroyed()).toBe true
|
||||
|
||||
describe ".edit(editor)", ->
|
||||
[newEditor, newBuffer] = []
|
||||
@@ -2756,7 +2756,7 @@ describe "EditorView", ->
|
||||
editorView = atom.workspaceView.getActiveView()
|
||||
|
||||
view = $$ -> @div id: 'view', tabindex: -1, 'View'
|
||||
editorView.getPane().showItem(view)
|
||||
editorView.getPane().activateItem(view)
|
||||
expect(editorView.isVisible()).toBeFalsy()
|
||||
|
||||
editorView.setText('hidden changes')
|
||||
@@ -2764,7 +2764,7 @@ describe "EditorView", ->
|
||||
|
||||
displayUpdatedHandler = jasmine.createSpy("displayUpdatedHandler")
|
||||
editorView.on 'editor:display-updated', displayUpdatedHandler
|
||||
editorView.getPane().showItem(editorView.getModel())
|
||||
editorView.getPane().activateItem(editorView.getModel())
|
||||
expect(editorView.isVisible()).toBeTruthy()
|
||||
|
||||
waitsFor ->
|
||||
@@ -2809,7 +2809,7 @@ describe "EditorView", ->
|
||||
atom.workspaceView.attachToDom()
|
||||
editorView = atom.workspaceView.getActiveView()
|
||||
|
||||
willBeRemovedHandler = jasmine.createSpy('fileChange')
|
||||
willBeRemovedHandler = jasmine.createSpy('willBeRemovedHandler')
|
||||
editorView.on 'editor:will-be-removed', willBeRemovedHandler
|
||||
editorView.getPane().destroyActiveItem()
|
||||
expect(willBeRemovedHandler).toHaveBeenCalled()
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
'name': 'test'
|
||||
'scopeName': 'source.test'
|
||||
'injectionSelector': 'comment'
|
||||
'patterns': [{'include': 'source.sql'}]
|
||||
@@ -257,8 +257,7 @@ describe "Git", ->
|
||||
|
||||
it "subscribes to all the serialized buffers in the project", ->
|
||||
atom.project.openSync('sample.js')
|
||||
#TODO Replace with atom.replicate().project when Atom is a telepath model
|
||||
project2 = atom.replicate().get('project')
|
||||
project2 = atom.project.testSerialization()
|
||||
buffer = project2.getBuffers()[0]
|
||||
|
||||
waitsFor ->
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports.runSpecSuite = (specSuite, logErrors=true) ->
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
|
||||
{$, $$} = require 'atom'
|
||||
window[key] = value for key, value of require '../vendor/jasmine'
|
||||
|
||||
@@ -8,16 +10,24 @@ module.exports.runSpecSuite = (specSuite, logErrors=true) ->
|
||||
TimeReporter = require './time-reporter'
|
||||
timeReporter = new TimeReporter()
|
||||
|
||||
logStream = fs.openSync(logFile, 'w') if logFile?
|
||||
log = (str) ->
|
||||
if logStream?
|
||||
fs.writeSync(logStream, str)
|
||||
else
|
||||
process.stderr.write(str)
|
||||
|
||||
if atom.getLoadSettings().exitWhenDone
|
||||
{jasmineNode} = require 'jasmine-node/lib/jasmine-node/reporter'
|
||||
reporter = new jasmineNode.TerminalReporter
|
||||
print: (args...) ->
|
||||
process.stderr.write(args...)
|
||||
print: (str) ->
|
||||
log(str)
|
||||
onComplete: (runner) ->
|
||||
process.stdout.write('\n')
|
||||
timeReporter.logLongestSuites 10, (line) -> process.stdout.write("#{line}\n")
|
||||
process.stdout.write('\n')
|
||||
timeReporter.logLongestSpecs 10, (line) -> process.stdout.write("#{line}\n")
|
||||
log('\n')
|
||||
timeReporter.logLongestSuites 10, (line) -> log("#{line}\n")
|
||||
log('\n')
|
||||
timeReporter.logLongestSpecs 10, (line) -> log("#{line}\n")
|
||||
fs.closeSync(logStream) if logStream?
|
||||
atom.exit(runner.results().failedCount > 0 ? 1 : 0)
|
||||
else
|
||||
AtomReporter = require './atom-reporter'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
|
||||
temp = require 'temp'
|
||||
Keymap = require '../src/keymap'
|
||||
{$, $$, WorkspaceView} = require 'atom'
|
||||
|
||||
@@ -7,9 +8,11 @@ describe "Keymap", ->
|
||||
fragment = null
|
||||
keymap = null
|
||||
resourcePath = atom.getLoadSettings().resourcePath
|
||||
configDirPath = null
|
||||
|
||||
beforeEach ->
|
||||
keymap = new Keymap({configDirPath: atom.getConfigDirPath(), resourcePath})
|
||||
configDirPath = temp.mkdirSync('atom')
|
||||
keymap = new Keymap({configDirPath, resourcePath})
|
||||
fragment = $ """
|
||||
<div class="command-mode">
|
||||
<div class="child-node">
|
||||
@@ -18,6 +21,9 @@ describe "Keymap", ->
|
||||
</div>
|
||||
"""
|
||||
|
||||
afterEach ->
|
||||
keymap.destroy()
|
||||
|
||||
describe ".handleKeyEvent(event)", ->
|
||||
deleteCharHandler = null
|
||||
insertCharHandler = null
|
||||
@@ -347,3 +353,19 @@ describe "Keymap", ->
|
||||
el = $$ -> @div class: 'brown'
|
||||
bindings = keymap.keyBindingsForCommandMatchingElement('cultivate', el)
|
||||
expect(bindings).toHaveLength 0
|
||||
|
||||
describe "when the user keymap file is changed", ->
|
||||
it "is reloaded", ->
|
||||
keymapFilePath = path.join(configDirPath, "keymap.cson")
|
||||
fs.writeFileSync(keymapFilePath, '"body": "ctrl-l": "core:move-left"')
|
||||
keymap.loadUserKeymap()
|
||||
|
||||
spyOn(keymap, 'loadUserKeymap').andCallThrough()
|
||||
fs.writeFileSync(keymapFilePath, "'body': 'ctrl-l': 'core:move-right'")
|
||||
|
||||
waitsFor ->
|
||||
keymap.loadUserKeymap.callCount > 0
|
||||
|
||||
runs ->
|
||||
keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0]
|
||||
expect(keyBinding.command).toBe 'core:move-right'
|
||||
|
||||
@@ -1,274 +1,77 @@
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
PaneContainer = require '../src/pane-container'
|
||||
Pane = require '../src/pane'
|
||||
{_, $, View, $$} = require 'atom'
|
||||
|
||||
describe "PaneContainer", ->
|
||||
[TestView, container, pane1, pane2, pane3] = []
|
||||
|
||||
beforeEach ->
|
||||
class TestView extends View
|
||||
atom.deserializers.add(this)
|
||||
@deserialize: ({name}) -> new TestView(name)
|
||||
@content: -> @div tabindex: -1
|
||||
initialize: (@name) -> @text(@name)
|
||||
serialize: -> { deserializer: 'TestView', @name }
|
||||
getUri: -> path.join(temp.dir, @name)
|
||||
save: -> @saved = true
|
||||
isEqual: (other) -> @name is other.name
|
||||
|
||||
container = new PaneContainer
|
||||
pane1 = new Pane(new TestView('1'))
|
||||
container.setRoot(pane1)
|
||||
pane2 = pane1.splitRight(new TestView('2'))
|
||||
pane3 = pane2.splitDown(new TestView('3'))
|
||||
|
||||
afterEach ->
|
||||
atom.deserializers.remove(TestView)
|
||||
|
||||
describe ".focusNextPane()", ->
|
||||
it "focuses the pane following the focused pane or the first pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
container.focusNextPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".focusPreviousPane()", ->
|
||||
it "focuses the pane preceding the focused pane or the last pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
container.focusPreviousPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPane()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".getActivePane()", ->
|
||||
it "returns the most-recently focused pane", ->
|
||||
focusStealer = $$ -> @div tabindex: -1, "focus stealer"
|
||||
focusStealer.attachToDom()
|
||||
container.attachToDom()
|
||||
|
||||
pane2.focus()
|
||||
expect(container.getFocusedPane()).toBe pane2
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
|
||||
focusStealer.focus()
|
||||
expect(container.getFocusedPane()).toBeUndefined()
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
|
||||
pane3.focus()
|
||||
expect(container.getFocusedPane()).toBe pane3
|
||||
expect(container.getActivePane()).toBe pane3
|
||||
|
||||
# returns the first pane if none have been set to active
|
||||
container.find('.pane.active').removeClass('active')
|
||||
expect(container.getActivePane()).toBe pane1
|
||||
|
||||
describe ".eachPane(callback)", ->
|
||||
it "runs the callback with all current and future panes until the subscription is cancelled", ->
|
||||
panes = []
|
||||
subscription = container.eachPane (pane) -> panes.push(pane)
|
||||
expect(panes).toEqual [pane1, pane2, pane3]
|
||||
|
||||
panes = []
|
||||
pane4 = pane3.splitRight(pane3.copyActiveItem())
|
||||
expect(panes).toEqual [pane4]
|
||||
|
||||
panes = []
|
||||
subscription.off()
|
||||
pane4.splitDown()
|
||||
expect(panes).toEqual []
|
||||
|
||||
describe ".saveAll()", ->
|
||||
it "saves all open pane items", ->
|
||||
pane1.showItem(new TestView('4'))
|
||||
|
||||
container.saveAll()
|
||||
|
||||
for pane in container.getPanes()
|
||||
for item in pane.getItems()
|
||||
expect(item.saved).toBeTruthy()
|
||||
|
||||
describe ".confirmClose()", ->
|
||||
it "returns true after modified files are saved", ->
|
||||
pane1.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
pane2.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
spyOn(atom, "confirm").andReturn(0)
|
||||
|
||||
saved = container.confirmClose()
|
||||
|
||||
runs ->
|
||||
expect(saved).toBeTruthy()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "returns false if the user cancels saving", ->
|
||||
pane1.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
pane2.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
spyOn(atom, "confirm").andReturn(1)
|
||||
|
||||
saved = container.confirmClose()
|
||||
|
||||
runs ->
|
||||
expect(saved).toBeFalsy()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe "serialization", ->
|
||||
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
|
||||
newContainer = atom.deserializers.deserialize(container.serialize())
|
||||
expect(newContainer.find('.row > :contains(1)')).toExist()
|
||||
expect(newContainer.find('.row > .column > :contains(2)')).toExist()
|
||||
expect(newContainer.find('.row > .column > :contains(3)')).toExist()
|
||||
[containerA, pane1A, pane2A, pane3A] = []
|
||||
|
||||
newContainer.height(200).width(300).attachToDom()
|
||||
expect(newContainer.find('.row > :contains(1)').width()).toBe 150
|
||||
expect(newContainer.find('.row > .column > :contains(2)').height()).toBe 100
|
||||
|
||||
xit "removes empty panes on deserialization", ->
|
||||
# only deserialize pane 1's view successfully
|
||||
TestView.deserialize = ({name}) -> new TestView(name) if name is '1'
|
||||
newContainer = atom.deserializers.deserialize(container.serialize())
|
||||
expect(newContainer.find('.row, .column')).not.toExist()
|
||||
expect(newContainer.find('> :contains(1)')).toExist()
|
||||
|
||||
describe "pane-container:active-pane-item-changed", ->
|
||||
[pane1, item1a, item1b, item2a, item2b, item3a, container, activeItemChangedHandler] = []
|
||||
beforeEach ->
|
||||
item1a = new TestView('1a')
|
||||
item1b = new TestView('1b')
|
||||
item2a = new TestView('2a')
|
||||
item2b = new TestView('2b')
|
||||
item3a = new TestView('3a')
|
||||
# This is a dummy item to prevent panes from being empty on deserialization
|
||||
class Item
|
||||
atom.deserializers.add(this)
|
||||
@deserialize: -> new this
|
||||
serialize: -> deserializer: 'Item'
|
||||
|
||||
container = new PaneContainer
|
||||
container.attachToDom()
|
||||
pane1 = new Pane(item1a)
|
||||
container.setRoot(pane1)
|
||||
pane1A = new Pane(items: [new Item])
|
||||
containerA = new PaneContainer(root: pane1A)
|
||||
pane2A = pane1A.splitRight(items: [new Item])
|
||||
pane3A = pane2A.splitDown(items: [new Item])
|
||||
|
||||
activeItemChangedHandler = jasmine.createSpy("activeItemChangedHandler")
|
||||
container.on 'pane-container:active-pane-item-changed', activeItemChangedHandler
|
||||
it "preserves the focused pane across serialization", ->
|
||||
expect(pane3A.focused).toBe true
|
||||
|
||||
describe "when there are no panes", ->
|
||||
it "is triggered when a new pane containing a pane item is added", ->
|
||||
container.setRoot()
|
||||
expect(container.getPanes().length).toBe 0
|
||||
activeItemChangedHandler.reset()
|
||||
containerB = containerA.testSerialization()
|
||||
[pane1B, pane2B, pane3B] = containerB.getPanes()
|
||||
expect(pane3B.focused).toBe true
|
||||
|
||||
pane = new Pane(item1a)
|
||||
container.setRoot(pane)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
it "preserves the active pane across serialization, independent of focus", ->
|
||||
pane3A.activate()
|
||||
expect(containerA.activePane).toBe pane3A
|
||||
|
||||
describe "when there is one pane", ->
|
||||
it "is triggered when a new pane item is added", ->
|
||||
pane1.showItem(item1b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1b
|
||||
containerB = containerA.testSerialization()
|
||||
[pane1B, pane2B, pane3B] = containerB.getPanes()
|
||||
expect(containerB.activePane).toBe pane3B
|
||||
|
||||
it "is not triggered when the active pane item is shown again", ->
|
||||
pane1.showItem(item1a)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
describe "::activePane", ->
|
||||
[container, pane1, pane2] = []
|
||||
|
||||
it "is triggered when switching to an existing pane item", ->
|
||||
pane1.showItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
beforeEach ->
|
||||
pane1 = new Pane
|
||||
container = new PaneContainer(root: pane1)
|
||||
|
||||
pane1.showItem(item1a)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
it "references the first pane if no pane has been made active", ->
|
||||
expect(container.activePane).toBe pane1
|
||||
expect(pane1.active).toBe true
|
||||
|
||||
it "is triggered when the active pane item is removed", ->
|
||||
pane1.showItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
it "references the most pane on which ::activate was most recently called", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane2.activate()
|
||||
expect(container.activePane).toBe pane2
|
||||
expect(pane1.active).toBe false
|
||||
expect(pane2.active).toBe true
|
||||
pane1.activate()
|
||||
expect(container.activePane).toBe pane1
|
||||
expect(pane1.active).toBe true
|
||||
expect(pane2.active).toBe false
|
||||
|
||||
pane1.removeItem(item1b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
it "is reassigned to the next pane if the current active pane is destroyed", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane2.activate()
|
||||
pane2.destroy()
|
||||
expect(container.activePane).toBe pane1
|
||||
expect(pane1.active).toBe true
|
||||
pane1.destroy()
|
||||
expect(container.activePane).toBe null
|
||||
|
||||
it "is not triggered when an inactive pane item is removed", ->
|
||||
pane1.showItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
describe "when the last pane is removed", ->
|
||||
[container, pane, surrenderedFocusHandler] = []
|
||||
|
||||
pane1.removeItem(item1a)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
beforeEach ->
|
||||
pane = new Pane
|
||||
container = new PaneContainer(root: pane)
|
||||
container.on 'surrendered-focus', surrenderedFocusHandler = jasmine.createSpy("surrenderedFocusHandler")
|
||||
|
||||
it "is triggered when all pane items are removed", ->
|
||||
pane1.removeItem(item1a)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
|
||||
|
||||
it "is triggered when the pane is removed", ->
|
||||
pane1.remove()
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
|
||||
|
||||
describe "when there are two panes", ->
|
||||
[pane2] = []
|
||||
|
||||
beforeEach ->
|
||||
pane2 = pane1.splitLeft(item2a)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
it "is triggered when a new pane item is added to the active pane", ->
|
||||
pane2.showItem(item2b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2b
|
||||
|
||||
it "is not triggered when a new pane item is added to an inactive pane", ->
|
||||
pane1.showItem(item1b)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when the active pane item removed from the active pane", ->
|
||||
pane2.showItem(item2b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane2.removeItem(item2b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2a
|
||||
|
||||
it "is not triggered when the active pane item removed from an inactive pane", ->
|
||||
pane1.showItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.removeItem(item1b)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when the active pane is removed", ->
|
||||
pane2.remove()
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
it "is not triggered when an inactive pane is removed", ->
|
||||
pane1.remove()
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when the active pane is changed", ->
|
||||
pane1.makeActive()
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
describe "when there are multiple panes", ->
|
||||
beforeEach ->
|
||||
pane2 = pane1.splitRight(item2a)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
it "is triggered when a new pane is added", ->
|
||||
pane2.splitDown(item3a)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item3a
|
||||
|
||||
it "is not triggered when the non active pane is removed", ->
|
||||
pane3 = pane2.splitDown(item3a)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.remove()
|
||||
pane2.remove()
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
it "assigns null to the root and the activePane", ->
|
||||
pane.destroy()
|
||||
expect(container.root).toBe null
|
||||
expect(container.activePane).toBe null
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
PaneContainerView = require '../src/pane-container-view'
|
||||
PaneView = require '../src/pane-view'
|
||||
{_, $, View, $$} = require 'atom'
|
||||
|
||||
describe "PaneContainerView", ->
|
||||
[TestView, container, pane1, pane2, pane3] = []
|
||||
|
||||
beforeEach ->
|
||||
class TestView extends View
|
||||
atom.deserializers.add(this)
|
||||
@deserialize: ({name}) -> new TestView(name)
|
||||
@content: -> @div tabindex: -1
|
||||
initialize: (@name) -> @text(@name)
|
||||
serialize: -> { deserializer: 'TestView', @name }
|
||||
getUri: -> path.join(temp.dir, @name)
|
||||
save: -> @saved = true
|
||||
isEqual: (other) -> @name is other?.name
|
||||
|
||||
container = new PaneContainerView
|
||||
pane1 = new PaneView(new TestView('1'))
|
||||
container.setRoot(pane1)
|
||||
pane2 = pane1.splitRight(new TestView('2'))
|
||||
pane3 = pane2.splitDown(new TestView('3'))
|
||||
|
||||
afterEach ->
|
||||
atom.deserializers.remove(TestView)
|
||||
|
||||
describe ".focusNextPane()", ->
|
||||
it "focuses the pane following the focused pane or the first pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
container.focusNextPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".focusPreviousPane()", ->
|
||||
it "focuses the pane preceding the focused pane or the last pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
$(document.body).focus() # clear focus
|
||||
|
||||
container.focusPreviousPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPane()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".getActivePane()", ->
|
||||
it "returns the most-recently focused pane", ->
|
||||
focusStealer = $$ -> @div tabindex: -1, "focus stealer"
|
||||
focusStealer.attachToDom()
|
||||
container.attachToDom()
|
||||
|
||||
pane2.focus()
|
||||
expect(container.getFocusedPane()).toBe pane2
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
|
||||
focusStealer.focus()
|
||||
expect(container.getFocusedPane()).toBeUndefined()
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
|
||||
pane3.focus()
|
||||
expect(container.getFocusedPane()).toBe pane3
|
||||
expect(container.getActivePane()).toBe pane3
|
||||
|
||||
describe ".eachPane(callback)", ->
|
||||
it "runs the callback with all current and future panes until the subscription is cancelled", ->
|
||||
panes = []
|
||||
subscription = container.eachPane (pane) -> panes.push(pane)
|
||||
expect(panes).toEqual [pane1, pane2, pane3]
|
||||
|
||||
panes = []
|
||||
pane4 = pane3.splitRight(pane3.copyActiveItem())
|
||||
expect(panes).toEqual [pane4]
|
||||
|
||||
panes = []
|
||||
subscription.off()
|
||||
pane4.splitDown()
|
||||
expect(panes).toEqual []
|
||||
|
||||
describe ".saveAll()", ->
|
||||
it "saves all open pane items", ->
|
||||
pane1.activateItem(new TestView('4'))
|
||||
|
||||
container.saveAll()
|
||||
|
||||
for pane in container.getPanes()
|
||||
for item in pane.getItems()
|
||||
expect(item.saved).toBeTruthy()
|
||||
|
||||
describe ".confirmClose()", ->
|
||||
it "returns true after modified files are saved", ->
|
||||
pane1.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
pane2.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
spyOn(atom, "confirm").andReturn(0)
|
||||
|
||||
saved = container.confirmClose()
|
||||
|
||||
runs ->
|
||||
expect(saved).toBeTruthy()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "returns false if the user cancels saving", ->
|
||||
pane1.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
pane2.itemAtIndex(0).shouldPromptToSave = -> true
|
||||
spyOn(atom, "confirm").andReturn(1)
|
||||
|
||||
saved = container.confirmClose()
|
||||
|
||||
runs ->
|
||||
expect(saved).toBeFalsy()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe "serialization", ->
|
||||
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
|
||||
newContainer = atom.deserializers.deserialize(container.serialize())
|
||||
expect(newContainer.find('.pane-row > :contains(1)')).toExist()
|
||||
expect(newContainer.find('.pane-row > .pane-column > :contains(2)')).toExist()
|
||||
expect(newContainer.find('.pane-row > .pane-column > :contains(3)')).toExist()
|
||||
|
||||
newContainer.height(200).width(300).attachToDom()
|
||||
expect(newContainer.find('.pane-row > :contains(1)').width()).toBe 150
|
||||
expect(newContainer.find('.pane-row > .pane-column > :contains(2)').height()).toBe 100
|
||||
|
||||
it "removes empty panes on deserialization", ->
|
||||
# only deserialize pane 1's view successfully
|
||||
TestView.deserialize = ({name}) -> new TestView(name) if name is '1'
|
||||
newContainer = atom.deserializers.deserialize(container.serialize())
|
||||
expect(newContainer.find('.pane-row, .pane-column')).not.toExist()
|
||||
expect(newContainer.find('> :contains(1)')).toExist()
|
||||
|
||||
describe "pane-container:active-pane-item-changed", ->
|
||||
[pane1, item1a, item1b, item2a, item2b, item3a, container, activeItemChangedHandler] = []
|
||||
beforeEach ->
|
||||
item1a = new TestView('1a')
|
||||
item1b = new TestView('1b')
|
||||
item2a = new TestView('2a')
|
||||
item2b = new TestView('2b')
|
||||
item3a = new TestView('3a')
|
||||
|
||||
container = new PaneContainerView
|
||||
container.attachToDom()
|
||||
pane1 = new PaneView(item1a)
|
||||
container.setRoot(pane1)
|
||||
|
||||
activeItemChangedHandler = jasmine.createSpy("activeItemChangedHandler")
|
||||
container.on 'pane-container:active-pane-item-changed', activeItemChangedHandler
|
||||
|
||||
describe "when there are no panes", ->
|
||||
it "is triggered when a new pane containing a pane item is added", ->
|
||||
container.setRoot()
|
||||
expect(container.getPanes().length).toBe 0
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane = new PaneView(item1a)
|
||||
container.setRoot(pane)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
describe "when there is one pane", ->
|
||||
it "is triggered when a new pane item is added", ->
|
||||
pane1.activateItem(item1b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1b
|
||||
|
||||
it "is not triggered when the active pane item is shown again", ->
|
||||
pane1.activateItem(item1a)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when switching to an existing pane item", ->
|
||||
pane1.activateItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.activateItem(item1a)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
it "is triggered when the active pane item is destroyed", ->
|
||||
pane1.activateItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.destroyItem(item1b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
it "is not triggered when an inactive pane item is destroyed", ->
|
||||
pane1.activateItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.destroyItem(item1a)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when all pane items are destroyed", ->
|
||||
pane1.destroyItem(item1a)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
|
||||
|
||||
it "is triggered when the pane is destroyed", ->
|
||||
pane1.remove()
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
|
||||
|
||||
describe "when there are two panes", ->
|
||||
[pane2] = []
|
||||
|
||||
beforeEach ->
|
||||
pane2 = pane1.splitLeft(item2a)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
it "is triggered when a new pane item is added to the active pane", ->
|
||||
pane2.activateItem(item2b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2b
|
||||
|
||||
it "is not triggered when a new pane item is added to an inactive pane", ->
|
||||
pane1.activateItem(item1b)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when the active pane's active item is destroyed", ->
|
||||
pane2.activateItem(item2b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane2.destroyItem(item2b)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2a
|
||||
|
||||
it "is not triggered when an inactive pane's active item is destroyed", ->
|
||||
pane1.activateItem(item1b)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.destroyItem(item1b)
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when the active pane is destroyed", ->
|
||||
pane2.remove()
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
it "is not triggered when an inactive pane is destroyed", ->
|
||||
pane1.remove()
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "is triggered when the active pane is changed", ->
|
||||
pane1.activate()
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
|
||||
|
||||
describe "when there are multiple panes", ->
|
||||
beforeEach ->
|
||||
pane2 = pane1.splitRight(item2a)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
it "is triggered when a new pane is added", ->
|
||||
pane2.splitDown(item3a)
|
||||
expect(activeItemChangedHandler.callCount).toBe 1
|
||||
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item3a
|
||||
|
||||
it "is not triggered when an inactive pane is destroyed", ->
|
||||
pane3 = pane2.splitDown(item3a)
|
||||
activeItemChangedHandler.reset()
|
||||
|
||||
pane1.remove()
|
||||
pane2.remove()
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
+108
-686
@@ -1,712 +1,134 @@
|
||||
PaneContainer = require '../src/pane-container'
|
||||
{Model} = require 'theorist'
|
||||
Pane = require '../src/pane'
|
||||
{fs, $, View} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
PaneAxis = require '../src/pane-axis'
|
||||
PaneContainer = require '../src/pane-container'
|
||||
|
||||
describe "Pane", ->
|
||||
[container, view1, view2, editor1, editor2, pane] = []
|
||||
describe "split methods", ->
|
||||
[pane1, container] = []
|
||||
|
||||
class TestView extends View
|
||||
@deserialize: ({id, text}) -> new TestView({id, text})
|
||||
@content: ({id, text}) -> @div class: 'test-view', id: id, tabindex: -1, text
|
||||
initialize: ({@id, @text}) ->
|
||||
serialize: -> { deserializer: 'TestView', @id, @text }
|
||||
getUri: -> @id
|
||||
isEqual: (other) -> @id == other.id and @text == other.text
|
||||
beforeEach ->
|
||||
pane1 = new Pane(items: ["A"])
|
||||
container = new PaneContainer(root: pane1)
|
||||
|
||||
beforeEach ->
|
||||
atom.deserializers.add(TestView)
|
||||
container = new PaneContainer
|
||||
view1 = new TestView(id: 'view-1', text: 'View 1')
|
||||
view2 = new TestView(id: 'view-2', text: 'View 2')
|
||||
editor1 = atom.project.openSync('sample.js')
|
||||
editor2 = atom.project.openSync('sample.txt')
|
||||
pane = new Pane(view1, editor1, view2, editor2)
|
||||
container.setRoot(pane)
|
||||
describe "::splitLeft(params)", ->
|
||||
describe "when the parent is the container root", ->
|
||||
it "replaces itself with a row and inserts a new pane to the left of itself", ->
|
||||
pane2 = pane1.splitLeft(items: ["B"])
|
||||
pane3 = pane1.splitLeft(items: ["C"])
|
||||
expect(container.root.orientation).toBe 'horizontal'
|
||||
expect(container.root.children).toEqual [pane2, pane3, pane1]
|
||||
|
||||
afterEach ->
|
||||
atom.deserializers.remove(TestView)
|
||||
describe "when the parent is a column", ->
|
||||
it "replaces itself with a row and inserts a new pane to the left of itself", ->
|
||||
pane1.splitDown()
|
||||
pane2 = pane1.splitLeft(items: ["B"])
|
||||
pane3 = pane1.splitLeft(items: ["C"])
|
||||
row = container.root.children[0]
|
||||
expect(row.orientation).toBe 'horizontal'
|
||||
expect(row.children).toEqual [pane2, pane3, pane1]
|
||||
|
||||
describe "::initialize(items...)", ->
|
||||
it "displays the first item in the pane", ->
|
||||
expect(pane.itemViews.find('#view-1')).toExist()
|
||||
describe "::splitRight(params)", ->
|
||||
describe "when the parent is the container root", ->
|
||||
it "replaces itself with a row and inserts a new pane to the right of itself", ->
|
||||
pane2 = pane1.splitRight(items: ["B"])
|
||||
pane3 = pane1.splitRight(items: ["C"])
|
||||
expect(container.root.orientation).toBe 'horizontal'
|
||||
expect(container.root.children).toEqual [pane1, pane3, pane2]
|
||||
|
||||
describe "::showItem(item)", ->
|
||||
it "hides all item views except the one being shown and sets the activeItem", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.showItem(view2)
|
||||
expect(view1.css('display')).toBe 'none'
|
||||
expect(view2.css('display')).not.toBe 'none'
|
||||
expect(pane.activeItem).toBe view2
|
||||
describe "when the parent is a column", ->
|
||||
it "replaces itself with a row and inserts a new pane to the right of itself", ->
|
||||
pane1.splitDown()
|
||||
pane2 = pane1.splitRight(items: ["B"])
|
||||
pane3 = pane1.splitRight(items: ["C"])
|
||||
row = container.root.children[0]
|
||||
expect(row.orientation).toBe 'horizontal'
|
||||
expect(row.children).toEqual [pane1, pane3, pane2]
|
||||
|
||||
it "triggers 'pane:active-item-changed' if the item isn't already the activeItem", ->
|
||||
pane.makeActive()
|
||||
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
|
||||
container.on 'pane:active-item-changed', itemChangedHandler
|
||||
describe "::splitUp(params)", ->
|
||||
describe "when the parent is the container root", ->
|
||||
it "replaces itself with a column and inserts a new pane above itself", ->
|
||||
pane2 = pane1.splitUp(items: ["B"])
|
||||
pane3 = pane1.splitUp(items: ["C"])
|
||||
expect(container.root.orientation).toBe 'vertical'
|
||||
expect(container.root.children).toEqual [pane2, pane3, pane1]
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.showItem(view2)
|
||||
pane.showItem(view2)
|
||||
expect(itemChangedHandler.callCount).toBe 1
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe view2
|
||||
itemChangedHandler.reset()
|
||||
describe "when the parent is a row", ->
|
||||
it "replaces itself with a column and inserts a new pane above itself", ->
|
||||
pane1.splitRight()
|
||||
pane2 = pane1.splitUp(items: ["B"])
|
||||
pane3 = pane1.splitUp(items: ["C"])
|
||||
column = container.root.children[0]
|
||||
expect(column.orientation).toBe 'vertical'
|
||||
expect(column.children).toEqual [pane2, pane3, pane1]
|
||||
|
||||
pane.showItem(editor1)
|
||||
expect(itemChangedHandler).toHaveBeenCalled()
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe editor1
|
||||
itemChangedHandler.reset()
|
||||
describe "::splitDown(params)", ->
|
||||
describe "when the parent is the container root", ->
|
||||
it "replaces itself with a column and inserts a new pane below itself", ->
|
||||
pane2 = pane1.splitDown(items: ["B"])
|
||||
pane3 = pane1.splitDown(items: ["C"])
|
||||
expect(container.root.orientation).toBe 'vertical'
|
||||
expect(container.root.children).toEqual [pane1, pane3, pane2]
|
||||
|
||||
describe "if the pane's active view is focused before calling showItem", ->
|
||||
it "focuses the new active view", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
expect(pane.activeView).not.toBe view2
|
||||
expect(pane.activeView).toMatchSelector ':focus'
|
||||
pane.showItem(view2)
|
||||
expect(view2).toMatchSelector ':focus'
|
||||
describe "when the parent is a row", ->
|
||||
it "replaces itself with a column and inserts a new pane below itself", ->
|
||||
pane1.splitRight()
|
||||
pane2 = pane1.splitDown(items: ["B"])
|
||||
pane3 = pane1.splitDown(items: ["C"])
|
||||
column = container.root.children[0]
|
||||
expect(column.orientation).toBe 'vertical'
|
||||
expect(column.children).toEqual [pane1, pane3, pane2]
|
||||
|
||||
describe "when the given item isn't yet in the items list on the pane", ->
|
||||
view3 = null
|
||||
beforeEach ->
|
||||
view3 = new TestView(id: 'view-3', text: "View 3")
|
||||
pane.showItem(editor1)
|
||||
expect(pane.getActiveItemIndex()).toBe 1
|
||||
|
||||
it "adds it to the items list after the active item", ->
|
||||
pane.showItem(view3)
|
||||
expect(pane.getItems()).toEqual [view1, editor1, view3, view2, editor2]
|
||||
expect(pane.activeItem).toBe view3
|
||||
expect(pane.getActiveItemIndex()).toBe 2
|
||||
|
||||
it "triggers the 'item-added' event with the item and its index before the 'active-item-changed' event", ->
|
||||
events = []
|
||||
container.on 'pane:item-added', (e, item, index) -> events.push(['pane:item-added', item, index])
|
||||
container.on 'pane:active-item-changed', (e, item) -> events.push(['pane:active-item-changed', item])
|
||||
pane.showItem(view3)
|
||||
expect(events).toEqual [['pane:item-added', view3, 2], ['pane:active-item-changed', view3]]
|
||||
|
||||
describe "when showing a model item", ->
|
||||
describe "when no view has yet been appended for that item", ->
|
||||
it "appends and shows a view to display the item based on its `.getViewClass` method", ->
|
||||
pane.showItem(editor1)
|
||||
editorView = pane.activeView
|
||||
expect(editorView.css('display')).not.toBe 'none'
|
||||
expect(editorView.editor).toBe editor1
|
||||
|
||||
describe "when a valid view has already been appended for another item", ->
|
||||
it "multiple views are created for multiple items", ->
|
||||
pane.showItem(editor1)
|
||||
pane.showItem(editor2)
|
||||
expect(pane.itemViews.find('.editor').length).toBe 2
|
||||
editorView = pane.activeView
|
||||
expect(editorView.css('display')).not.toBe 'none'
|
||||
expect(editorView.editor).toBe editor2
|
||||
|
||||
it "creates a new view with the item", ->
|
||||
initialViewCount = pane.itemViews.find('.test-view').length
|
||||
|
||||
model1 =
|
||||
id: 'test-model-1'
|
||||
text: 'Test Model 1'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
model2 =
|
||||
id: 'test-model-2'
|
||||
text: 'Test Model 2'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
pane.showItem(model1)
|
||||
pane.showItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
pane.showPreviousItem()
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
pane.removeItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
|
||||
|
||||
pane.removeItem(model1)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
|
||||
|
||||
describe "when showing a view item", ->
|
||||
it "appends it to the itemViews div if it hasn't already been appended and shows it", ->
|
||||
expect(pane.itemViews.find('#view-2')).not.toExist()
|
||||
pane.showItem(view2)
|
||||
expect(pane.itemViews.find('#view-2')).toExist()
|
||||
expect(pane.activeView).toBe view2
|
||||
it "sets up the new pane to be focused", ->
|
||||
expect(pane1.focused).toBe false
|
||||
pane2 = pane1.splitRight()
|
||||
expect(pane2.focused).toBe true
|
||||
|
||||
describe "::destroyItem(item)", ->
|
||||
describe "if the item is not modified", ->
|
||||
it "removes the item and tries to call destroy on it", ->
|
||||
pane.destroyItem(editor2)
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
describe "when the last item is destroyed", ->
|
||||
it "destroys the pane", ->
|
||||
pane = new Pane(items: ["A", "B"])
|
||||
pane.destroyItem("A")
|
||||
pane.destroyItem("B")
|
||||
expect(pane.isDestroyed()).toBe true
|
||||
|
||||
describe "if the item is modified", ->
|
||||
beforeEach ->
|
||||
jasmine.unspy(editor2, 'shouldPromptToSave')
|
||||
spyOn(editor2, 'save')
|
||||
spyOn(editor2, 'saveAs')
|
||||
describe "when an item emits a destroyed event", ->
|
||||
it "removes it from the list of items", ->
|
||||
pane = new Pane(items: [new Model, new Model, new Model])
|
||||
[item1, item2, item3] = pane.items
|
||||
pane.items[1].destroy()
|
||||
expect(pane.items).toEqual [item1, item3]
|
||||
|
||||
editor2.insertText('a')
|
||||
expect(editor2.isModified()).toBeTruthy()
|
||||
|
||||
describe "if the [Save] option is selected", ->
|
||||
describe "when the item has a uri", ->
|
||||
it "saves the item before removing and destroying it", ->
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(editor2.save).toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
|
||||
describe "when the item has no uri", ->
|
||||
it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
|
||||
editor2.buffer.setPath(undefined)
|
||||
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn("/selected/path")
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
||||
|
||||
expect(editor2.saveAs).toHaveBeenCalledWith("/selected/path")
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
|
||||
describe "if the [Don't Save] option is selected", ->
|
||||
it "removes and destroys the item without saving it", ->
|
||||
spyOn(atom, 'confirm').andReturn(2)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(editor2.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
|
||||
describe "if the [Cancel] option is selected", ->
|
||||
it "does not save, remove, or destroy the item", ->
|
||||
spyOn(atom, 'confirm').andReturn(1)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(editor2.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editor2)).not.toBe -1
|
||||
expect(editor2.destroyed).toBeFalsy()
|
||||
|
||||
describe "::removeItem(item)", ->
|
||||
it "removes the item from the items list and shows the next item if it was showing", ->
|
||||
pane.removeItem(view1)
|
||||
expect(pane.getItems()).toEqual [editor1, view2, editor2]
|
||||
expect(pane.activeItem).toBe editor1
|
||||
|
||||
pane.showItem(editor2)
|
||||
pane.removeItem(editor2)
|
||||
expect(pane.getItems()).toEqual [editor1, view2]
|
||||
expect(pane.activeItem).toBe editor1
|
||||
|
||||
it "triggers 'pane:item-removed' with the item and its former index", ->
|
||||
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
|
||||
pane.on 'pane:item-removed', itemRemovedHandler
|
||||
pane.removeItem(editor1)
|
||||
expect(itemRemovedHandler).toHaveBeenCalled()
|
||||
expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
|
||||
|
||||
describe "when removing the last item", ->
|
||||
it "removes the pane", ->
|
||||
pane.removeItem(item) for item in pane.getItems()
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
it "shifts focus to the next pane", ->
|
||||
expect(container.getRoot()).toBe pane
|
||||
container.attachToDom()
|
||||
pane2 = pane.splitRight(new TestView(id: 'view-3', text: 'View 3'))
|
||||
pane.focus()
|
||||
expect(pane).toMatchSelector(':has(:focus)')
|
||||
pane.removeItem(item) for item in pane.getItems()
|
||||
expect(pane2).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the item is a view", ->
|
||||
it "removes the item from the 'item-views' div", ->
|
||||
expect(view1.parent()).toMatchSelector pane.itemViews
|
||||
pane.removeItem(view1)
|
||||
expect(view1.parent()).not.toMatchSelector pane.itemViews
|
||||
|
||||
describe "when the item is a model", ->
|
||||
it "removes the associated view only when all items that require it have been removed", ->
|
||||
pane.showItem(editor1)
|
||||
pane.showItem(editor2)
|
||||
pane.removeItem(editor2)
|
||||
expect(pane.itemViews.find('.editor')).toExist()
|
||||
pane.removeItem(editor1)
|
||||
expect(pane.itemViews.find('.editor')).not.toExist()
|
||||
|
||||
describe "::moveItem(item, index)", ->
|
||||
it "moves the item to the given index and emits a 'pane:item-moved' event with the item and the new index", ->
|
||||
itemMovedHandler = jasmine.createSpy("itemMovedHandler")
|
||||
pane.on 'pane:item-moved', itemMovedHandler
|
||||
|
||||
pane.moveItem(view1, 2)
|
||||
expect(pane.getItems()).toEqual [editor1, view2, view1, editor2]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [view1, 2]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(editor1, 3)
|
||||
expect(pane.getItems()).toEqual [view2, view1, editor2, editor1]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editor1, 3]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(editor1, 1)
|
||||
expect(pane.getItems()).toEqual [view2, editor1, view1, editor2]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
describe "::moveItemToPane(item, pane, index)", ->
|
||||
[pane2, view3] = []
|
||||
describe "::destroy()", ->
|
||||
[pane1, container] = []
|
||||
|
||||
beforeEach ->
|
||||
view3 = new TestView(id: 'view-3', text: "View 3")
|
||||
pane2 = pane.splitRight(view3)
|
||||
pane1 = new Pane(items: [new Model, new Model])
|
||||
container = new PaneContainer(root: pane1)
|
||||
|
||||
it "moves the item to the given pane at the given index", ->
|
||||
pane.moveItemToPane(view1, pane2, 1)
|
||||
expect(pane.getItems()).toEqual [editor1, view2, editor2]
|
||||
expect(pane2.getItems()).toEqual [view3, view1]
|
||||
it "destroys the pane's destroyable items", ->
|
||||
[item1, item2] = pane1.items
|
||||
pane1.destroy()
|
||||
expect(item1.isDestroyed()).toBe true
|
||||
expect(item2.isDestroyed()).toBe true
|
||||
|
||||
describe "when it is the last item on the source pane", ->
|
||||
it "removes the source pane, but does not destroy the item", ->
|
||||
pane.removeItem(view1)
|
||||
pane.removeItem(view2)
|
||||
pane.removeItem(editor2)
|
||||
|
||||
expect(pane.getItems()).toEqual [editor1]
|
||||
pane.moveItemToPane(editor1, pane2, 1)
|
||||
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
expect(pane2.getItems()).toEqual [view3, editor1]
|
||||
expect(editor1.destroyed).toBeFalsy()
|
||||
|
||||
describe "when the item is a jQuery object", ->
|
||||
it "preserves data by detaching instead of removing", ->
|
||||
view1.data('preservative', 1234)
|
||||
pane.moveItemToPane(view1, pane2, 1)
|
||||
pane2.showItemAtIndex(1)
|
||||
expect(pane2.activeView.data('preservative')).toBe 1234
|
||||
|
||||
describe "pane:close", ->
|
||||
it "destroys all items and removes the pane", ->
|
||||
pane.showItem(editor1)
|
||||
pane.trigger 'pane:close'
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
expect(editor1.destroyed).toBeTruthy()
|
||||
|
||||
describe "pane:close-other-items", ->
|
||||
it "destroys all items except the current", ->
|
||||
pane.showItem(editor1)
|
||||
pane.trigger 'pane:close-other-items'
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
expect(pane.getItems()).toEqual [editor1]
|
||||
|
||||
describe "::saveActiveItem()", ->
|
||||
describe "when the current item has a uri", ->
|
||||
describe "when the current item has a save method", ->
|
||||
it "saves the current item", ->
|
||||
spyOn(editor2, 'save')
|
||||
pane.showItem(editor2)
|
||||
pane.saveActiveItem()
|
||||
expect(editor2.save).toHaveBeenCalled()
|
||||
|
||||
describe "when the current item has no save method", ->
|
||||
it "does nothing", ->
|
||||
pane.activeItem.getUri = -> 'you are eye'
|
||||
expect(pane.activeItem.save).toBeUndefined()
|
||||
pane.saveActiveItem()
|
||||
|
||||
describe "when the current item has no uri", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
|
||||
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens a save dialog and saves the current item as the selected path", ->
|
||||
newEditor = atom.project.openSync()
|
||||
spyOn(newEditor, 'saveAs')
|
||||
pane.showItem(newEditor)
|
||||
|
||||
pane.saveActiveItem()
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
||||
expect(newEditor.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
|
||||
describe "when the current item has no saveAs method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.saveAs).toBeUndefined()
|
||||
pane.saveActiveItem()
|
||||
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
|
||||
|
||||
describe "::saveActiveItemAs()", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
|
||||
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens the save dialog and calls saveAs on the item with the selected path", ->
|
||||
spyOn(editor2, 'saveAs')
|
||||
pane.showItem(editor2)
|
||||
|
||||
pane.saveActiveItemAs()
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(path.dirname(editor2.getPath()))
|
||||
expect(editor2.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
|
||||
describe "when the current item does not have a saveAs method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.saveAs).toBeUndefined()
|
||||
pane.saveActiveItemAs()
|
||||
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
|
||||
|
||||
describe "pane:show-next-item and pane:show-previous-item", ->
|
||||
it "advances forward/backward through the pane's items, looping around at either end", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.trigger 'pane:show-previous-item'
|
||||
expect(pane.activeItem).toBe editor2
|
||||
pane.trigger 'pane:show-previous-item'
|
||||
expect(pane.activeItem).toBe view2
|
||||
pane.trigger 'pane:show-next-item'
|
||||
expect(pane.activeItem).toBe editor2
|
||||
pane.trigger 'pane:show-next-item'
|
||||
expect(pane.activeItem).toBe view1
|
||||
|
||||
describe "pane:show-item-N events", ->
|
||||
it "shows the (n-1)th item if it exists", ->
|
||||
pane.trigger 'pane:show-item-2'
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(1)
|
||||
pane.trigger 'pane:show-item-1'
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(0)
|
||||
pane.trigger 'pane:show-item-9' # don't fail on out-of-bounds indices
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(0)
|
||||
|
||||
describe "when the title of the active item changes", ->
|
||||
it "emits pane:active-item-title-changed", ->
|
||||
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
|
||||
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
|
||||
view2.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
view1.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
|
||||
activeItemTitleChangedHandler.reset()
|
||||
|
||||
pane.showItem(view2)
|
||||
view2.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
|
||||
|
||||
describe "when an unmodifed buffer's path is deleted", ->
|
||||
it "removes the pane item", ->
|
||||
filePath = temp.openSync('atom').path
|
||||
editor = atom.project.openSync(filePath)
|
||||
pane.showItem(editor)
|
||||
expect(pane.items).toHaveLength(5)
|
||||
|
||||
fs.removeSync(filePath)
|
||||
waitsFor ->
|
||||
pane.items.length == 4
|
||||
|
||||
describe "::remove()", ->
|
||||
it "destroys all the pane's items", ->
|
||||
pane.remove()
|
||||
expect(editor1.destroyed).toBeTruthy()
|
||||
expect(editor2.destroyed).toBeTruthy()
|
||||
|
||||
it "triggers a 'pane:removed' event with the pane", ->
|
||||
removedHandler = jasmine.createSpy("removedHandler")
|
||||
container.on 'pane:removed', removedHandler
|
||||
pane.remove()
|
||||
expect(removedHandler).toHaveBeenCalled()
|
||||
expect(removedHandler.argsForCall[0][1]).toBe pane
|
||||
|
||||
describe "when there are other panes", ->
|
||||
[paneToLeft, paneToRight] = []
|
||||
|
||||
beforeEach ->
|
||||
pane.showItem(editor1)
|
||||
paneToLeft = pane.splitLeft(pane.copyActiveItem())
|
||||
paneToRight = pane.splitRight(pane.copyActiveItem())
|
||||
container.attachToDom()
|
||||
|
||||
describe "when the removed pane is focused", ->
|
||||
it "activates and focuses the next pane", ->
|
||||
pane.focus()
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeFalsy()
|
||||
expect(paneToRight.isActive()).toBeTruthy()
|
||||
expect(paneToRight).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the removed pane is active but not focused", ->
|
||||
it "activates the next pane, but does not focus it", ->
|
||||
$(document.activeElement).blur()
|
||||
expect(pane).not.toMatchSelector ':has(:focus)'
|
||||
pane.makeActive()
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeFalsy()
|
||||
expect(paneToRight.isActive()).toBeTruthy()
|
||||
expect(paneToRight).not.toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the removed pane is not active", ->
|
||||
it "does not affect the active pane or the focus", ->
|
||||
paneToLeft.focus()
|
||||
expect(paneToLeft.isActive()).toBeTruthy()
|
||||
expect(paneToRight.isActive()).toBeFalsy()
|
||||
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeTruthy()
|
||||
expect(paneToRight.isActive()).toBeFalsy()
|
||||
expect(paneToLeft).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when it is the last pane", ->
|
||||
beforeEach ->
|
||||
expect(container.getPanes().length).toBe 1
|
||||
atom.workspaceView = focus: jasmine.createSpy("workspaceView.focus")
|
||||
|
||||
describe "when the removed pane is focused", ->
|
||||
it "calls focus on workspaceView so we don't lose focus", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
pane.remove()
|
||||
expect(atom.workspaceView.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when the removed pane is not focused", ->
|
||||
it "does not call focus on root view", ->
|
||||
expect(pane).not.toMatchSelector ':has(:focus)'
|
||||
pane.remove()
|
||||
expect(atom.workspaceView.focus).not.toHaveBeenCalled()
|
||||
|
||||
describe "::getNextPane()", ->
|
||||
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
|
||||
pane.showItem(editor1)
|
||||
expect(pane.getNextPane()).toBeUndefined
|
||||
pane2 = pane.splitRight(pane.copyActiveItem())
|
||||
expect(pane.getNextPane()).toBe pane2
|
||||
expect(pane2.getNextPane()).toBe pane
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
beforeEach ->
|
||||
container.attachToDom()
|
||||
|
||||
it "focuses the active item view", ->
|
||||
focusHandler = jasmine.createSpy("focusHandler")
|
||||
pane.activeItem.on 'focus', focusHandler
|
||||
pane.focus()
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
|
||||
it "triggers 'pane:became-active' if it was not previously active", ->
|
||||
pane2 = pane.splitRight(view2) # Make pane inactive
|
||||
|
||||
becameActiveHandler = jasmine.createSpy("becameActiveHandler")
|
||||
pane.on 'pane:became-active', becameActiveHandler
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
pane.focusin()
|
||||
expect(pane.isActive()).toBeTruthy()
|
||||
pane.focusin()
|
||||
|
||||
expect(becameActiveHandler.callCount).toBe 1
|
||||
|
||||
it "triggers 'pane:became-inactive' when it was previously active", ->
|
||||
pane2 = pane.splitRight(view2) # Make pane inactive
|
||||
|
||||
becameInactiveHandler = jasmine.createSpy("becameInactiveHandler")
|
||||
pane.on 'pane:became-inactive', becameInactiveHandler
|
||||
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
pane.focusin()
|
||||
expect(pane.isActive()).toBeTruthy()
|
||||
pane.splitRight(pane.copyActiveItem())
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
|
||||
expect(becameInactiveHandler.callCount).toBe 1
|
||||
|
||||
describe "split methods", ->
|
||||
[pane1, view3, view4] = []
|
||||
beforeEach ->
|
||||
pane1 = pane
|
||||
pane.showItem(editor1)
|
||||
view3 = new TestView(id: 'view-3', text: 'View 3')
|
||||
view4 = new TestView(id: 'view-4', text: 'View 4')
|
||||
|
||||
describe "splitRight(items...)", ->
|
||||
it "builds a row if needed, then appends a new pane after itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane1.splitRight(pane1.copyActiveItem())
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitRight(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
|
||||
|
||||
it "builds a row if needed, then appends a new pane after itself ", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
describe "if the pane's parent has more than two children", ->
|
||||
it "removes the pane from its parent", ->
|
||||
pane2 = pane1.splitRight()
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(pane2.items).toEqual []
|
||||
expect(pane2.activeItem).toBe null
|
||||
|
||||
pane3 = pane2.splitRight()
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]]
|
||||
expect(pane3.items).toEqual []
|
||||
expect(pane3.activeItem).toBe null
|
||||
|
||||
describe "splitLeft(items...)", ->
|
||||
it "builds a row if needed, then appends a new pane before itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitLeft(pane1.copyActiveItem())
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane2[0], pane[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
expect(container.root.children).toEqual [pane1, pane2, pane3]
|
||||
pane2.destroy()
|
||||
expect(container.root.children).toEqual [pane1, pane3]
|
||||
|
||||
pane3 = pane2.splitLeft(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
|
||||
describe "if the pane's parent has two children", ->
|
||||
it "replaces the parent with its last remaining child", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitDown()
|
||||
|
||||
describe "splitDown(items...)", ->
|
||||
it "builds a column if needed, then appends a new pane after itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitDown(pane1.copyActiveItem())
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitDown(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
|
||||
|
||||
describe "splitUp(items...)", ->
|
||||
it "builds a column if needed, then appends a new pane before itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitUp(pane1.copyActiveItem())
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane2[0], pane[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitUp(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
|
||||
|
||||
it "lays out nested panes by equally dividing their containing row / column", ->
|
||||
container.width(520).height(240).attachToDom()
|
||||
pane1.showItem($("1"))
|
||||
pane1
|
||||
.splitLeft($("2"))
|
||||
.splitUp($("3"))
|
||||
.splitLeft($("4"))
|
||||
.splitDown($("5"))
|
||||
|
||||
row1 = container.children(':eq(0)')
|
||||
expect(row1.children().length).toBe 2
|
||||
column1 = row1.children(':eq(0)').view()
|
||||
pane1 = row1.children(':eq(1)').view()
|
||||
expect(column1.outerWidth()).toBe Math.round(2/3 * container.width())
|
||||
expect(column1.outerHeight()).toBe container.height()
|
||||
expect(pane1.outerWidth()).toBe Math.round(1/3 * container.width())
|
||||
expect(pane1.outerHeight()).toBe container.height()
|
||||
expect(Math.round(pane1.position().left)).toBe column1.outerWidth()
|
||||
|
||||
expect(column1.children().length).toBe 2
|
||||
row2 = column1.children(':eq(0)').view()
|
||||
pane2 = column1.children(':eq(1)').view()
|
||||
expect(row2.outerWidth()).toBe column1.outerWidth()
|
||||
expect(row2.height()).toBe 2/3 * container.height()
|
||||
expect(pane2.outerWidth()).toBe column1.outerWidth()
|
||||
expect(pane2.outerHeight()).toBe 1/3 * container.height()
|
||||
expect(Math.round(pane2.position().top)).toBe row2.height()
|
||||
|
||||
expect(row2.children().length).toBe 2
|
||||
column3 = row2.children(':eq(0)').view()
|
||||
pane3 = row2.children(':eq(1)').view()
|
||||
expect(column3.outerWidth()).toBe Math.round(1/3 * container.width())
|
||||
expect(column3.outerHeight()).toBe row2.outerHeight()
|
||||
# the built in rounding seems to be rounding x.5 down, but we need to go up. this sucks.
|
||||
expect(Math.round(pane3.trueWidth())).toBe Math.round(1/3 * container.width())
|
||||
expect(pane3.height()).toBe row2.outerHeight()
|
||||
expect(Math.round(pane3.position().left)).toBe column3.width()
|
||||
|
||||
expect(column3.children().length).toBe 2
|
||||
pane4 = column3.children(':eq(0)').view()
|
||||
pane5 = column3.children(':eq(1)').view()
|
||||
expect(pane4.outerWidth()).toBe column3.width()
|
||||
expect(pane4.outerHeight()).toBe 1/3 * container.height()
|
||||
expect(pane5.outerWidth()).toBe column3.width()
|
||||
expect(Math.round(pane5.position().top)).toBe pane4.outerHeight()
|
||||
expect(pane5.outerHeight()).toBe 1/3 * container.height()
|
||||
|
||||
pane5.remove()
|
||||
expect(column3.parent()).not.toExist()
|
||||
expect(pane2.outerHeight()).toBe Math.floor(1/2 * container.height())
|
||||
expect(pane3.outerHeight()).toBe Math.floor(1/2 * container.height())
|
||||
expect(pane4.outerHeight()).toBe Math.floor(1/2 * container.height())
|
||||
|
||||
pane4.remove()
|
||||
expect(row2.parent()).not.toExist()
|
||||
expect(pane1.outerWidth()).toBe Math.floor(1/2 * container.width())
|
||||
expect(pane2.outerWidth()).toBe Math.floor(1/2 * container.width())
|
||||
expect(pane3.outerWidth()).toBe Math.floor(1/2 * container.width())
|
||||
|
||||
pane3.remove()
|
||||
expect(column1.parent()).not.toExist()
|
||||
expect(pane2.outerHeight()).toBe container.height()
|
||||
|
||||
pane2.remove()
|
||||
expect(row1.parent()).not.toExist()
|
||||
expect(container.children().length).toBe 1
|
||||
expect(container.children('.pane').length).toBe 1
|
||||
expect(pane1.outerWidth()).toBe container.width()
|
||||
|
||||
describe "::itemForUri(uri)", ->
|
||||
it "returns the item for which a call to .getUri() returns the given uri", ->
|
||||
expect(pane.itemForUri(editor1.getUri())).toBe editor1
|
||||
expect(pane.itemForUri(editor2.getUri())).toBe editor2
|
||||
|
||||
describe "serialization", ->
|
||||
it "can serialize and deserialize the pane and all its items", ->
|
||||
newPane = atom.deserializers.deserialize(pane.serialize())
|
||||
expect(newPane.getItems()).toEqual [view1, editor1, view2, editor2]
|
||||
|
||||
it "restores the active item on deserialization", ->
|
||||
pane.showItem(editor2)
|
||||
newPane = atom.deserializers.deserialize(pane.serialize())
|
||||
expect(newPane.activeItem).toEqual editor2
|
||||
|
||||
it "does not show items that cannot be deserialized", ->
|
||||
spyOn(console, 'warn')
|
||||
|
||||
pane.showItem(view2)
|
||||
paneState = pane.serialize()
|
||||
paneState.get('items').set(pane.items.indexOf(view2), {deserializer: 'Bogus'}) # nuke serialized state of active item
|
||||
|
||||
newPane = atom.deserializers.deserialize(paneState)
|
||||
expect(newPane.activeItem).toEqual pane.items[0]
|
||||
expect(newPane.items.length).toBe pane.items.length - 1
|
||||
|
||||
it "focuses the pane after attach only if had focus when serialized", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
|
||||
container2 = atom.deserializers.deserialize(container.serialize())
|
||||
pane2 = container2.getRoot()
|
||||
container2.attachToDom()
|
||||
expect(pane2).toMatchSelector(':has(:focus)')
|
||||
|
||||
$(document.activeElement).blur()
|
||||
container3 = atom.deserializers.deserialize(container.serialize())
|
||||
pane3 = container3.getRoot()
|
||||
container3.attachToDom()
|
||||
expect(pane3).not.toMatchSelector(':has(:focus)')
|
||||
expect(container.root.children[0]).toBe pane1
|
||||
expect(container.root.children[1].children).toEqual [pane2, pane3]
|
||||
pane3.destroy()
|
||||
expect(container.root.children).toEqual [pane1, pane2]
|
||||
pane2.destroy()
|
||||
expect(container.root).toBe pane1
|
||||
|
||||
@@ -0,0 +1,638 @@
|
||||
PaneContainerView = require '../src/pane-container-view'
|
||||
PaneView = require '../src/pane-view'
|
||||
{fs, $, View} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
|
||||
describe "PaneView", ->
|
||||
[container, view1, view2, editor1, editor2, pane] = []
|
||||
|
||||
class TestView extends View
|
||||
@deserialize: ({id, text}) -> new TestView({id, text})
|
||||
@content: ({id, text}) -> @div class: 'test-view', id: id, tabindex: -1, text
|
||||
initialize: ({@id, @text}) ->
|
||||
serialize: -> { deserializer: 'TestView', @id, @text }
|
||||
getUri: -> @id
|
||||
isEqual: (other) -> other? and @id == other.id and @text == other.text
|
||||
|
||||
beforeEach ->
|
||||
atom.deserializers.add(TestView)
|
||||
container = new PaneContainerView
|
||||
view1 = new TestView(id: 'view-1', text: 'View 1')
|
||||
view2 = new TestView(id: 'view-2', text: 'View 2')
|
||||
editor1 = atom.project.openSync('sample.js')
|
||||
editor2 = atom.project.openSync('sample.txt')
|
||||
pane = new PaneView(view1, editor1, view2, editor2)
|
||||
container.setRoot(pane)
|
||||
|
||||
afterEach ->
|
||||
atom.deserializers.remove(TestView)
|
||||
|
||||
describe "::initialize(items...)", ->
|
||||
it "displays the first item in the pane", ->
|
||||
expect(pane.itemViews.find('#view-1')).toExist()
|
||||
|
||||
describe "::activateItem(item)", ->
|
||||
it "hides all item views except the one being shown and sets the activeItem", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.activateItem(view2)
|
||||
expect(view1.css('display')).toBe 'none'
|
||||
expect(view2.css('display')).not.toBe 'none'
|
||||
expect(pane.activeItem).toBe view2
|
||||
|
||||
it "triggers 'pane:active-item-changed' if the item isn't already the activeItem", ->
|
||||
pane.activate()
|
||||
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
|
||||
container.on 'pane:active-item-changed', itemChangedHandler
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.activateItem(view2)
|
||||
pane.activateItem(view2)
|
||||
expect(itemChangedHandler.callCount).toBe 1
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe view2
|
||||
itemChangedHandler.reset()
|
||||
|
||||
pane.activateItem(editor1)
|
||||
expect(itemChangedHandler).toHaveBeenCalled()
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe editor1
|
||||
itemChangedHandler.reset()
|
||||
|
||||
describe "if the pane's active view is focused before calling activateItem", ->
|
||||
it "focuses the new active view", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
expect(pane.activeView).not.toBe view2
|
||||
expect(pane.activeView).toMatchSelector ':focus'
|
||||
pane.activateItem(view2)
|
||||
expect(view2).toMatchSelector ':focus'
|
||||
|
||||
describe "when the given item isn't yet in the items list on the pane", ->
|
||||
view3 = null
|
||||
beforeEach ->
|
||||
view3 = new TestView(id: 'view-3', text: "View 3")
|
||||
pane.activateItem(editor1)
|
||||
expect(pane.getActiveItemIndex()).toBe 1
|
||||
|
||||
it "adds it to the items list after the active item", ->
|
||||
pane.activateItem(view3)
|
||||
expect(pane.getItems()).toEqual [view1, editor1, view3, view2, editor2]
|
||||
expect(pane.activeItem).toBe view3
|
||||
expect(pane.getActiveItemIndex()).toBe 2
|
||||
|
||||
it "triggers the 'item-added' event with the item and its index before the 'active-item-changed' event", ->
|
||||
events = []
|
||||
container.on 'pane:item-added', (e, item, index) -> events.push(['pane:item-added', item, index])
|
||||
container.on 'pane:active-item-changed', (e, item) -> events.push(['pane:active-item-changed', item])
|
||||
pane.activateItem(view3)
|
||||
expect(events).toEqual [['pane:item-added', view3, 2], ['pane:active-item-changed', view3]]
|
||||
|
||||
describe "when showing a model item", ->
|
||||
describe "when no view has yet been appended for that item", ->
|
||||
it "appends and shows a view to display the item based on its `.getViewClass` method", ->
|
||||
pane.activateItem(editor1)
|
||||
editorView = pane.activeView
|
||||
expect(editorView.css('display')).not.toBe 'none'
|
||||
expect(editorView.editor).toBe editor1
|
||||
|
||||
describe "when a valid view has already been appended for another item", ->
|
||||
it "multiple views are created for multiple items", ->
|
||||
pane.activateItem(editor1)
|
||||
pane.activateItem(editor2)
|
||||
expect(pane.itemViews.find('.editor').length).toBe 2
|
||||
editorView = pane.activeView
|
||||
expect(editorView.css('display')).not.toBe 'none'
|
||||
expect(editorView.editor).toBe editor2
|
||||
|
||||
it "creates a new view with the item", ->
|
||||
initialViewCount = pane.itemViews.find('.test-view').length
|
||||
|
||||
model1 =
|
||||
id: 'test-model-1'
|
||||
text: 'Test Model 1'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
model2 =
|
||||
id: 'test-model-2'
|
||||
text: 'Test Model 2'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
pane.activateItem(model1)
|
||||
pane.activateItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
pane.activatePreviousItem()
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
pane.destroyItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
|
||||
|
||||
pane.destroyItem(model1)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
|
||||
|
||||
describe "when showing a view item", ->
|
||||
it "appends it to the itemViews div if it hasn't already been appended and shows it", ->
|
||||
expect(pane.itemViews.find('#view-2')).not.toExist()
|
||||
pane.activateItem(view2)
|
||||
expect(pane.itemViews.find('#view-2')).toExist()
|
||||
expect(pane.activeView).toBe view2
|
||||
|
||||
describe "::destroyItem(item)", ->
|
||||
describe "if the item is not modified", ->
|
||||
it "removes the item and tries to call destroy on it", ->
|
||||
pane.destroyItem(editor2)
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
|
||||
describe "if the item is modified", ->
|
||||
beforeEach ->
|
||||
jasmine.unspy(editor2, 'shouldPromptToSave')
|
||||
spyOn(editor2, 'save')
|
||||
spyOn(editor2, 'saveAs')
|
||||
|
||||
editor2.insertText('a')
|
||||
expect(editor2.isModified()).toBeTruthy()
|
||||
|
||||
describe "if the [Save] option is selected", ->
|
||||
describe "when the item has a uri", ->
|
||||
it "saves the item before removing and destroying it", ->
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(editor2.save).toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
|
||||
describe "when the item has no uri", ->
|
||||
it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
|
||||
editor2.buffer.setPath(undefined)
|
||||
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn("/selected/path")
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
||||
|
||||
expect(editor2.saveAs).toHaveBeenCalledWith("/selected/path")
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
|
||||
describe "if the [Don't Save] option is selected", ->
|
||||
it "removes and destroys the item without saving it", ->
|
||||
spyOn(atom, 'confirm').andReturn(2)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(editor2.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editor2)).toBe -1
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
|
||||
describe "if the [Cancel] option is selected", ->
|
||||
it "does not save, remove, or destroy the item", ->
|
||||
spyOn(atom, 'confirm').andReturn(1)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(editor2.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editor2)).not.toBe -1
|
||||
expect(editor2.isDestroyed()).toBe false
|
||||
|
||||
it "removes the item's associated view", ->
|
||||
view1.remove = (selector, keepData) -> @wasRemoved = not keepData
|
||||
pane.destroyItem(view1)
|
||||
expect(view1.wasRemoved).toBe true
|
||||
|
||||
it "removes the item from the items list and shows the next item if it was showing", ->
|
||||
pane.destroyItem(view1)
|
||||
expect(pane.getItems()).toEqual [editor1, view2, editor2]
|
||||
expect(pane.activeItem).toBe editor1
|
||||
|
||||
pane.activateItem(editor2)
|
||||
pane.destroyItem(editor2)
|
||||
expect(pane.getItems()).toEqual [editor1, view2]
|
||||
expect(pane.activeItem).toBe editor1
|
||||
|
||||
it "triggers 'pane:item-removed' with the item and its former index", ->
|
||||
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
|
||||
pane.on 'pane:item-removed', itemRemovedHandler
|
||||
pane.destroyItem(editor1)
|
||||
expect(itemRemovedHandler).toHaveBeenCalled()
|
||||
expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
|
||||
|
||||
describe "when removing the last item", ->
|
||||
it "removes the pane", ->
|
||||
pane.destroyItem(item) for item in pane.getItems()
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
it "shifts focus to the next pane", ->
|
||||
expect(container.getRoot()).toBe pane
|
||||
container.attachToDom()
|
||||
pane2 = pane.splitRight(new TestView(id: 'view-3', text: 'View 3'))
|
||||
pane.focus()
|
||||
expect(pane).toMatchSelector(':has(:focus)')
|
||||
pane.destroyItem(item) for item in pane.getItems()
|
||||
expect(pane2).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the item is a view", ->
|
||||
it "removes the item from the 'item-views' div", ->
|
||||
expect(view1.parent()).toMatchSelector pane.itemViews
|
||||
pane.destroyItem(view1)
|
||||
expect(view1.parent()).not.toMatchSelector pane.itemViews
|
||||
|
||||
describe "when the item is a model", ->
|
||||
it "removes the associated view only when all items that require it have been removed", ->
|
||||
pane.activateItem(editor1)
|
||||
pane.activateItem(editor2)
|
||||
pane.destroyItem(editor2)
|
||||
expect(pane.itemViews.find('.editor')).toExist()
|
||||
pane.destroyItem(editor1)
|
||||
expect(pane.itemViews.find('.editor')).not.toExist()
|
||||
|
||||
describe "::moveItem(item, index)", ->
|
||||
it "moves the item to the given index and emits a 'pane:item-moved' event with the item and the new index", ->
|
||||
itemMovedHandler = jasmine.createSpy("itemMovedHandler")
|
||||
pane.on 'pane:item-moved', itemMovedHandler
|
||||
|
||||
pane.moveItem(view1, 2)
|
||||
expect(pane.getItems()).toEqual [editor1, view2, view1, editor2]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [view1, 2]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(editor1, 3)
|
||||
expect(pane.getItems()).toEqual [view2, view1, editor2, editor1]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editor1, 3]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(editor1, 1)
|
||||
expect(pane.getItems()).toEqual [view2, editor1, view1, editor2]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
describe "::moveItemToPane(item, pane, index)", ->
|
||||
[pane2, view3] = []
|
||||
|
||||
beforeEach ->
|
||||
view3 = new TestView(id: 'view-3', text: "View 3")
|
||||
pane2 = pane.splitRight(view3)
|
||||
|
||||
it "moves the item to the given pane at the given index", ->
|
||||
pane.moveItemToPane(view1, pane2, 1)
|
||||
expect(pane.getItems()).toEqual [editor1, view2, editor2]
|
||||
expect(pane2.getItems()).toEqual [view3, view1]
|
||||
|
||||
describe "when it is the last item on the source pane", ->
|
||||
it "removes the source pane, but does not destroy the item", ->
|
||||
pane.destroyItem(view1)
|
||||
pane.destroyItem(view2)
|
||||
pane.destroyItem(editor2)
|
||||
|
||||
expect(pane.getItems()).toEqual [editor1]
|
||||
pane.moveItemToPane(editor1, pane2, 1)
|
||||
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
expect(pane2.getItems()).toEqual [view3, editor1]
|
||||
expect(editor1.isDestroyed()).toBe false
|
||||
|
||||
describe "when the item is a jQuery object", ->
|
||||
it "preserves data by detaching instead of removing", ->
|
||||
view1.data('preservative', 1234)
|
||||
pane.moveItemToPane(view1, pane2, 1)
|
||||
pane2.activateItemAtIndex(1)
|
||||
expect(pane2.activeView.data('preservative')).toBe 1234
|
||||
|
||||
describe "pane:close", ->
|
||||
it "destroys all items and removes the pane", ->
|
||||
pane.activateItem(editor1)
|
||||
pane.trigger 'pane:close'
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
expect(editor1.isDestroyed()).toBe true
|
||||
|
||||
describe "pane:close-other-items", ->
|
||||
it "destroys all items except the current", ->
|
||||
pane.activateItem(editor1)
|
||||
pane.trigger 'pane:close-other-items'
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
expect(pane.getItems()).toEqual [editor1]
|
||||
|
||||
describe "::saveActiveItem()", ->
|
||||
describe "when the current item has a uri", ->
|
||||
describe "when the current item has a save method", ->
|
||||
it "saves the current item", ->
|
||||
spyOn(editor2, 'save')
|
||||
pane.activateItem(editor2)
|
||||
pane.saveActiveItem()
|
||||
expect(editor2.save).toHaveBeenCalled()
|
||||
|
||||
describe "when the current item has no save method", ->
|
||||
it "does nothing", ->
|
||||
pane.activeItem.getUri = -> 'you are eye'
|
||||
expect(pane.activeItem.save).toBeUndefined()
|
||||
pane.saveActiveItem()
|
||||
|
||||
describe "when the current item has no uri", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
|
||||
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens a save dialog and saves the current item as the selected path", ->
|
||||
newEditor = atom.project.openSync()
|
||||
spyOn(newEditor, 'saveAs')
|
||||
pane.activateItem(newEditor)
|
||||
|
||||
pane.saveActiveItem()
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
||||
expect(newEditor.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
|
||||
describe "when the current item has no saveAs method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.saveAs).toBeUndefined()
|
||||
pane.saveActiveItem()
|
||||
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
|
||||
|
||||
describe "::saveActiveItemAs()", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
|
||||
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens the save dialog and calls saveAs on the item with the selected path", ->
|
||||
spyOn(editor2, 'saveAs')
|
||||
pane.activateItem(editor2)
|
||||
|
||||
pane.saveActiveItemAs()
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(path.dirname(editor2.getPath()))
|
||||
expect(editor2.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
|
||||
describe "when the current item does not have a saveAs method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.saveAs).toBeUndefined()
|
||||
pane.saveActiveItemAs()
|
||||
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
|
||||
|
||||
describe "pane:show-next-item and pane:show-previous-item", ->
|
||||
it "advances forward/backward through the pane's items, looping around at either end", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.trigger 'pane:show-previous-item'
|
||||
expect(pane.activeItem).toBe editor2
|
||||
pane.trigger 'pane:show-previous-item'
|
||||
expect(pane.activeItem).toBe view2
|
||||
pane.trigger 'pane:show-next-item'
|
||||
expect(pane.activeItem).toBe editor2
|
||||
pane.trigger 'pane:show-next-item'
|
||||
expect(pane.activeItem).toBe view1
|
||||
|
||||
describe "pane:show-item-N events", ->
|
||||
it "shows the (n-1)th item if it exists", ->
|
||||
pane.trigger 'pane:show-item-2'
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(1)
|
||||
pane.trigger 'pane:show-item-1'
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(0)
|
||||
pane.trigger 'pane:show-item-9' # don't fail on out-of-bounds indices
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(0)
|
||||
|
||||
describe "when the title of the active item changes", ->
|
||||
it "emits pane:active-item-title-changed", ->
|
||||
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
|
||||
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
|
||||
view2.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
view1.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
|
||||
activeItemTitleChangedHandler.reset()
|
||||
|
||||
pane.activateItem(view2)
|
||||
view2.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
|
||||
|
||||
describe "when an unmodifed buffer's path is deleted", ->
|
||||
it "removes the pane item", ->
|
||||
filePath = temp.openSync('atom').path
|
||||
editor = atom.project.openSync(filePath)
|
||||
pane.activateItem(editor)
|
||||
expect(pane.items).toHaveLength(5)
|
||||
|
||||
fs.removeSync(filePath)
|
||||
waitsFor ->
|
||||
pane.items.length == 4
|
||||
|
||||
describe "::remove()", ->
|
||||
it "destroys all the pane's items", ->
|
||||
pane.remove()
|
||||
expect(editor1.isDestroyed()).toBe true
|
||||
expect(editor2.isDestroyed()).toBe true
|
||||
|
||||
it "triggers a 'pane:removed' event with the pane", ->
|
||||
removedHandler = jasmine.createSpy("removedHandler")
|
||||
container.on 'pane:removed', removedHandler
|
||||
pane.remove()
|
||||
expect(removedHandler).toHaveBeenCalled()
|
||||
expect(removedHandler.argsForCall[0][1]).toBe pane
|
||||
|
||||
describe "when there are other panes", ->
|
||||
[paneToLeft, paneToRight] = []
|
||||
|
||||
beforeEach ->
|
||||
pane.activateItem(editor1)
|
||||
paneToLeft = pane.splitLeft(pane.copyActiveItem())
|
||||
paneToRight = pane.splitRight(pane.copyActiveItem())
|
||||
container.attachToDom()
|
||||
|
||||
describe "when the removed pane is active", ->
|
||||
it "makes the next the next pane active and focuses it", ->
|
||||
pane.activate()
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeFalsy()
|
||||
expect(paneToRight.isActive()).toBeTruthy()
|
||||
expect(paneToRight).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the removed pane is not active", ->
|
||||
it "does not affect the active pane or the focus", ->
|
||||
paneToLeft.focus()
|
||||
expect(paneToLeft.isActive()).toBeTruthy()
|
||||
expect(paneToRight.isActive()).toBeFalsy()
|
||||
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeTruthy()
|
||||
expect(paneToRight.isActive()).toBeFalsy()
|
||||
expect(paneToLeft).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when it is the last pane", ->
|
||||
beforeEach ->
|
||||
expect(container.getPanes().length).toBe 1
|
||||
atom.workspaceView = focus: jasmine.createSpy("workspaceView.focus")
|
||||
|
||||
describe "when the removed pane is focused", ->
|
||||
it "calls focus on workspaceView so we don't lose focus", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
pane.remove()
|
||||
expect(atom.workspaceView.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when the removed pane is not focused", ->
|
||||
it "does not call focus on root view", ->
|
||||
expect(pane).not.toMatchSelector ':has(:focus)'
|
||||
pane.remove()
|
||||
expect(atom.workspaceView.focus).not.toHaveBeenCalled()
|
||||
|
||||
describe "::getNextPane()", ->
|
||||
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
|
||||
pane.activateItem(editor1)
|
||||
expect(pane.getNextPane()).toBeUndefined
|
||||
pane2 = pane.splitRight(pane.copyActiveItem())
|
||||
expect(pane.getNextPane()).toBe pane2
|
||||
expect(pane2.getNextPane()).toBe pane
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
beforeEach ->
|
||||
container.attachToDom()
|
||||
|
||||
it "focuses the active item view", ->
|
||||
focusHandler = jasmine.createSpy("focusHandler")
|
||||
pane.activeItem.on 'focus', focusHandler
|
||||
pane.focus()
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
|
||||
it "triggers 'pane:became-active' if it was not previously active", ->
|
||||
pane2 = pane.splitRight(view2) # Make pane inactive
|
||||
|
||||
becameActiveHandler = jasmine.createSpy("becameActiveHandler")
|
||||
pane.on 'pane:became-active', becameActiveHandler
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
pane.focusin()
|
||||
expect(pane.isActive()).toBeTruthy()
|
||||
pane.focusin()
|
||||
|
||||
expect(becameActiveHandler.callCount).toBe 1
|
||||
|
||||
it "triggers 'pane:became-inactive' when it was previously active", ->
|
||||
pane2 = pane.splitRight(view2) # Make pane inactive
|
||||
|
||||
becameInactiveHandler = jasmine.createSpy("becameInactiveHandler")
|
||||
pane.on 'pane:became-inactive', becameInactiveHandler
|
||||
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
pane.focusin()
|
||||
expect(pane.isActive()).toBeTruthy()
|
||||
pane.splitRight(pane.copyActiveItem())
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
|
||||
expect(becameInactiveHandler.callCount).toBe 1
|
||||
|
||||
describe "split methods", ->
|
||||
[pane1, view3, view4] = []
|
||||
beforeEach ->
|
||||
pane1 = pane
|
||||
pane.activateItem(editor1)
|
||||
view3 = new TestView(id: 'view-3', text: 'View 3')
|
||||
view4 = new TestView(id: 'view-4', text: 'View 4')
|
||||
|
||||
describe "splitRight(items...)", ->
|
||||
it "builds a row if needed, then appends a new pane after itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane1.splitRight(pane1.copyActiveItem())
|
||||
expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitRight(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.pane-row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
|
||||
|
||||
it "builds a row if needed, then appends a new pane after itself ", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane1.splitRight()
|
||||
expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(pane2.items).toEqual []
|
||||
expect(pane2.activeItem).toBeUndefined()
|
||||
|
||||
pane3 = pane2.splitRight()
|
||||
expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]]
|
||||
expect(pane3.items).toEqual []
|
||||
expect(pane3.activeItem).toBeUndefined()
|
||||
|
||||
describe "splitLeft(items...)", ->
|
||||
it "builds a row if needed, then appends a new pane before itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitLeft(pane1.copyActiveItem())
|
||||
expect(container.find('.pane-row .pane').toArray()).toEqual [pane2[0], pane[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitLeft(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.pane-row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
|
||||
|
||||
describe "splitDown(items...)", ->
|
||||
it "builds a column if needed, then appends a new pane after itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitDown(pane1.copyActiveItem())
|
||||
expect(container.find('.pane-column .pane').toArray()).toEqual [pane[0], pane2[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitDown(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.pane-column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
|
||||
|
||||
describe "splitUp(items...)", ->
|
||||
it "builds a column if needed, then appends a new pane before itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitUp(pane1.copyActiveItem())
|
||||
expect(container.find('.pane-column .pane').toArray()).toEqual [pane2[0], pane[0]]
|
||||
expect(pane2.items).toEqual [editor1]
|
||||
expect(pane2.activeItem).not.toBe editor1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitUp(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.pane-column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
|
||||
|
||||
describe "::itemForUri(uri)", ->
|
||||
it "returns the item for which a call to .getUri() returns the given uri", ->
|
||||
expect(pane.itemForUri(editor1.getUri())).toBe editor1
|
||||
expect(pane.itemForUri(editor2.getUri())).toBe editor2
|
||||
|
||||
describe "serialization", ->
|
||||
it "can serialize and deserialize the pane and all its items", ->
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.getItems()).toEqual [view1, editor1, view2, editor2]
|
||||
|
||||
it "restores the active item on deserialization", ->
|
||||
pane.activateItem(editor2)
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.activeItem).toEqual editor2
|
||||
|
||||
it "does not show items that cannot be deserialized", ->
|
||||
spyOn(console, 'warn')
|
||||
|
||||
class Unserializable
|
||||
getViewClass: -> TestView
|
||||
|
||||
pane.activateItem(new Unserializable)
|
||||
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.activeItem).toEqual pane.items[0]
|
||||
expect(newPane.items.length).toBe pane.items.length - 1
|
||||
|
||||
it "focuses the pane after attach only if had focus when serialized", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
|
||||
container2 = container.testSerialization()
|
||||
pane2 = container2.getRoot()
|
||||
container2.attachToDom()
|
||||
expect(pane2).toMatchSelector(':has(:focus)')
|
||||
|
||||
$(document.activeElement).blur()
|
||||
container3 = container.testSerialization()
|
||||
pane3 = container3.getRoot()
|
||||
container3.attachToDom()
|
||||
expect(pane3).not.toMatchSelector(':has(:focus)')
|
||||
@@ -16,21 +16,17 @@ describe "Project", ->
|
||||
afterEach ->
|
||||
deserializedProject?.destroy()
|
||||
|
||||
it "destroys unretained buffers and does not include them in the serialized state", ->
|
||||
it "does not include unretained buffers in the serialized state", ->
|
||||
atom.project.bufferForPathSync('a')
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
|
||||
atom.project.getState().serializeForPersistence()
|
||||
deserializedProject = atom.replicate().get('project')
|
||||
|
||||
deserializedProject = atom.project.testSerialization()
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
expect(atom.project.getBuffers().length).toBe 0
|
||||
|
||||
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
|
||||
atom.project.openSync('a')
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
atom.project.getState().serializeForPersistence()
|
||||
deserializedProject = atom.replicate().get('project')
|
||||
deserializedProject = atom.project.testSerialization()
|
||||
|
||||
expect(deserializedProject.getBuffers().length).toBe 1
|
||||
deserializedProject.getBuffers()[0].destroy()
|
||||
@@ -403,6 +399,7 @@ describe "Project", ->
|
||||
range: [[2, 6], [2, 11]]
|
||||
|
||||
it "works on evil filenames", ->
|
||||
platform.generateEvilFiles()
|
||||
atom.project.setPath(path.join(__dirname, 'fixtures', 'evil-files'))
|
||||
paths = []
|
||||
matches = []
|
||||
|
||||
@@ -5,7 +5,7 @@ describe "Selection", ->
|
||||
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
editor = atom.create(new Editor(buffer: buffer, tabLength: 2))
|
||||
editor = new Editor(buffer: buffer, tabLength: 2)
|
||||
selection = editor.getSelection()
|
||||
|
||||
afterEach ->
|
||||
|
||||
@@ -4,12 +4,12 @@ require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub')
|
||||
try
|
||||
require '../src/window'
|
||||
Atom = require '../src/atom'
|
||||
window.atom = new Atom()
|
||||
window.atom = Atom.loadOrCreate('spec')
|
||||
window.atom.show() unless atom.getLoadSettings().exitWhenDone
|
||||
{runSpecSuite} = require './jasmine-helper'
|
||||
|
||||
document.title = "Spec Suite"
|
||||
runSpecSuite './spec-suite'
|
||||
runSpecSuite './spec-suite', atom.getLoadSettings().logFile
|
||||
catch error
|
||||
if atom?.getLoadSettings().exitWhenDone
|
||||
console.error(error.stack ? error)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
{_} = require 'atom'
|
||||
|
||||
## Platform specific helpers
|
||||
module.exports =
|
||||
# Public: Returns true if being run from within Windows
|
||||
@@ -18,20 +16,20 @@ module.exports =
|
||||
fs.removeSync(evilFilesPath) if fs.existsSync(evilFilesPath)
|
||||
fs.mkdirSync(evilFilesPath)
|
||||
|
||||
if (@isWindows())
|
||||
if @isWindows()
|
||||
filenames = [
|
||||
"a_file_with_utf8.txt",
|
||||
"file with spaces.txt",
|
||||
"a_file_with_utf8.txt"
|
||||
"file with spaces.txt"
|
||||
"utfa\u0306.md"
|
||||
]
|
||||
else
|
||||
filenames = [
|
||||
"a_file_with_utf8.txt",
|
||||
"file with spaces.txt",
|
||||
"goddam\nnewlines",
|
||||
"quote\".txt",
|
||||
"a_file_with_utf8.txt"
|
||||
"file with spaces.txt"
|
||||
"goddam\nnewlines"
|
||||
"quote\".txt"
|
||||
"utfa\u0306.md"
|
||||
]
|
||||
|
||||
for filename in filenames
|
||||
fd = fs.writeFileSync(path.join(evilFilesPath, filename), 'evil file!', flag: 'w')
|
||||
fs.writeFileSync(path.join(evilFilesPath, filename), 'evil file!', flag: 'w')
|
||||
|
||||
+14
-15
@@ -1,23 +1,20 @@
|
||||
require '../src/window'
|
||||
atom.setUpEnvironment('spec')
|
||||
atom.restoreDimensions()
|
||||
atom.initialize()
|
||||
atom.restoreWindowDimensions()
|
||||
|
||||
require '../vendor/jasmine-jquery'
|
||||
path = require 'path'
|
||||
{_, $, File, WorkspaceView, fs} = require 'atom'
|
||||
Keymap = require '../src/keymap'
|
||||
Config = require '../src/config'
|
||||
{Point} = require 'telepath'
|
||||
{Point} = require 'text-buffer'
|
||||
Project = require '../src/project'
|
||||
Editor = require '../src/editor'
|
||||
EditorView = require '../src/editor-view'
|
||||
TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
pathwatcher = require 'pathwatcher'
|
||||
platform = require './spec-helper-platform'
|
||||
clipboard = require 'clipboard'
|
||||
|
||||
platform.generateEvilFiles()
|
||||
|
||||
atom.themes.loadBaseStylesheets()
|
||||
atom.themes.requireStylesheet '../static/jasmine'
|
||||
|
||||
@@ -28,9 +25,8 @@ keyBindingsToRestore = atom.keymap.getKeyBindings()
|
||||
|
||||
$(window).on 'core:close', -> window.close()
|
||||
$(window).on 'unload', ->
|
||||
atom.windowMode = 'spec'
|
||||
atom.getWindowState().set('dimensions', atom.getDimensions())
|
||||
atom.saveWindowState()
|
||||
atom.storeWindowDimensions()
|
||||
atom.saveSync()
|
||||
$('html,body').css('overflow', 'auto')
|
||||
|
||||
jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions
|
||||
@@ -51,15 +47,15 @@ if specDirectory
|
||||
beforeEach ->
|
||||
$.fx.off = true
|
||||
projectPath = specProjectPath ? path.join(@specDirectory, 'fixtures')
|
||||
atom.project = atom.getWindowState().set('project', new Project(path: projectPath))
|
||||
atom.project = new Project(path: projectPath)
|
||||
atom.keymap.keyBindings = _.clone(keyBindingsToRestore)
|
||||
|
||||
window.resetTimeouts()
|
||||
atom.packages.packageStates = {}
|
||||
|
||||
serializedWindowState = null
|
||||
spyOn(atom, 'saveWindowState').andCallFake -> serializedWindowState = @getWindowState().serialize()
|
||||
spyOn(atom, 'loadSerializedWindowState').andCallFake -> serializedWindowState
|
||||
|
||||
spyOn(atom, 'saveSync')
|
||||
atom.syntax.clearGrammarOverrides()
|
||||
atom.syntax.clearProperties()
|
||||
|
||||
@@ -111,13 +107,16 @@ afterEach ->
|
||||
|
||||
atom.workspaceView?.remove?()
|
||||
atom.workspaceView = null
|
||||
delete atom.state.workspaceView
|
||||
|
||||
atom.project?.destroy?()
|
||||
atom.project = null
|
||||
|
||||
delete atom.state.packageStates
|
||||
|
||||
$('#jasmine-content').empty() unless window.debugContent
|
||||
delete atom.windowState
|
||||
jasmine.unspy(atom, 'saveWindowState')
|
||||
|
||||
jasmine.unspy(atom, 'saveSync')
|
||||
ensureNoPathSubscriptions()
|
||||
atom.syntax.off()
|
||||
waits(0) # yield to ui thread to make screen update more frequently
|
||||
@@ -136,7 +135,7 @@ jasmine.StringPrettyPrinter.prototype.emitObject = (obj) ->
|
||||
emitObject.call(this, obj)
|
||||
|
||||
jasmine.unspy = (object, methodName) ->
|
||||
throw new Error("Not a spy") unless object[methodName].originalValue?
|
||||
throw new Error("Not a spy") unless object[methodName].hasOwnProperty('originalValue')
|
||||
object[methodName] = object[methodName].originalValue
|
||||
|
||||
addCustomMatchers = (spec) ->
|
||||
|
||||
+12
-10
@@ -1,7 +1,6 @@
|
||||
{fs} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
TextMateGrammar = require '../src/text-mate-grammar'
|
||||
|
||||
describe "the `syntax` global", ->
|
||||
beforeEach ->
|
||||
@@ -62,20 +61,23 @@ describe "the `syntax` global", ->
|
||||
|
||||
describe "when multiple grammars have matching fileTypes", ->
|
||||
it "selects the grammar with the longest fileType match", ->
|
||||
grammar1 = new TextMateGrammar
|
||||
grammarPath1 = temp.path(suffix: '.json')
|
||||
fs.writeFileSync grammarPath1, JSON.stringify(
|
||||
name: 'test1'
|
||||
scopeName: 'source1'
|
||||
fileTypes: ['test', 'more.test']
|
||||
fileTypes: ['test']
|
||||
)
|
||||
grammar1 = atom.syntax.loadGrammarSync(grammarPath1)
|
||||
expect(atom.syntax.selectGrammar('more.test', '')).toBe grammar1
|
||||
|
||||
grammar2 = new TextMateGrammar
|
||||
grammarPath2 = temp.path(suffix: '.json')
|
||||
fs.writeFileSync grammarPath2, JSON.stringify(
|
||||
name: 'test2'
|
||||
scopeName: 'source2'
|
||||
fileTypes: ['test']
|
||||
|
||||
atom.syntax.addGrammar(grammar1)
|
||||
atom.syntax.addGrammar(grammar2)
|
||||
|
||||
expect(atom.syntax.selectGrammar('more.test', '')).toBe grammar1
|
||||
fileTypes: ['test', 'more.test']
|
||||
)
|
||||
grammar2 = atom.syntax.loadGrammarSync(grammarPath2)
|
||||
expect(atom.syntax.selectGrammar('more.test', '')).toBe grammar2
|
||||
|
||||
describe "when there is no file path", ->
|
||||
it "does not throw an exception (regression)", ->
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{_, fs} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
{Site} = require 'telepath'
|
||||
TextBuffer = require '../src/text-buffer'
|
||||
|
||||
describe 'TextBuffer', ->
|
||||
@@ -971,25 +970,22 @@ describe 'TextBuffer', ->
|
||||
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"
|
||||
|
||||
describe "serialization", ->
|
||||
[buffer2, project2] = []
|
||||
buffer2 = null
|
||||
|
||||
beforeEach ->
|
||||
buffer.destroy()
|
||||
|
||||
filePath = temp.openSync('atom').path
|
||||
fs.writeFileSync(filePath, "words")
|
||||
buffer = atom.project.bufferForPathSync(filePath)
|
||||
buffer = atom.project.bufferForPathSync(filePath).retain()
|
||||
|
||||
afterEach ->
|
||||
buffer2?.release()
|
||||
project2?.destroy()
|
||||
buffer2?.destroy()
|
||||
|
||||
describe "when the serialized buffer had no unsaved changes", ->
|
||||
it "loads the current contents of the file at the serialized path", ->
|
||||
expect(buffer.isModified()).toBeFalsy()
|
||||
|
||||
project2 = atom.replicate().get('project')
|
||||
buffer2 = project2.getBuffers()[0]
|
||||
buffer2 = buffer.testSerialization()
|
||||
|
||||
waitsForPromise ->
|
||||
buffer2.load()
|
||||
@@ -1005,8 +1001,7 @@ describe 'TextBuffer', ->
|
||||
buffer.setText("BUFFER CHANGE")
|
||||
fs.writeFileSync(filePath, "DISK CHANGE")
|
||||
|
||||
project2 = atom.replicate().get('project')
|
||||
buffer2 = project2.getBuffers()[0]
|
||||
buffer2 = buffer.testSerialization()
|
||||
|
||||
waitsFor ->
|
||||
buffer2.cachedDiskContents
|
||||
@@ -1022,9 +1017,7 @@ describe 'TextBuffer', ->
|
||||
buffer.setText("abc")
|
||||
buffer.retain()
|
||||
|
||||
buffer.getState().serializeForPersistence()
|
||||
project2 = atom.replicate().get('project')
|
||||
buffer2 = project2.getBuffers()[0]
|
||||
buffer2 = buffer.testSerialization()
|
||||
|
||||
waitsForPromise ->
|
||||
buffer2.load()
|
||||
@@ -1038,15 +1031,11 @@ describe 'TextBuffer', ->
|
||||
|
||||
describe "when the serialized buffer was unsaved and had no path", ->
|
||||
it "restores the previous unsaved state of the buffer", ->
|
||||
buffer.release()
|
||||
buffer.destroy()
|
||||
|
||||
buffer = atom.project.bufferForPathSync()
|
||||
buffer.setText("abc")
|
||||
|
||||
state = buffer.getState().clone()
|
||||
expect(state.get('path')).toBeUndefined()
|
||||
expect(state.getObject('text')).toBe 'abc'
|
||||
|
||||
buffer2 = atom.project.addBuffer(new TextBuffer(state))
|
||||
buffer2 = buffer.testSerialization()
|
||||
expect(buffer2.getPath()).toBeUndefined()
|
||||
expect(buffer2.getText()).toBe("abc")
|
||||
|
||||
@@ -1,704 +0,0 @@
|
||||
TextMateGrammar = require '../src/text-mate-grammar'
|
||||
TextMatePackage = require '../src/text-mate-package'
|
||||
{_, fs} = require 'atom'
|
||||
|
||||
describe "TextMateGrammar", ->
|
||||
grammar = null
|
||||
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-text', sync: true)
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
atom.packages.activatePackage('language-coffee-script', sync: true)
|
||||
atom.packages.activatePackage('language-ruby', sync: true)
|
||||
atom.packages.activatePackage('language-html', sync: true)
|
||||
atom.packages.activatePackage('language-php', sync: true)
|
||||
atom.packages.activatePackage('language-python', sync: true)
|
||||
grammar = atom.syntax.selectGrammar("hello.coffee")
|
||||
|
||||
describe "@loadSync(path)", ->
|
||||
it "loads grammars from plists", ->
|
||||
grammar = TextMateGrammar.loadSync(require.resolve('./fixtures/sample.plist'))
|
||||
expect(grammar.scopeName).toBe "text.plain"
|
||||
{tokens} = grammar.tokenizeLine("this text is so plain. i love it.")
|
||||
expect(tokens[0]).toEqual value: "this text is so plain. i love it.", scopes: ["text.plain", "meta.paragraph.text"]
|
||||
|
||||
it "loads grammars from cson files", ->
|
||||
grammar = TextMateGrammar.loadSync(require.resolve('./fixtures/packages/package-with-grammars/grammars/alot.cson'))
|
||||
expect(grammar.scopeName).toBe "source.alot"
|
||||
{tokens} = grammar.tokenizeLine("this is alot of code")
|
||||
expect(tokens[1]).toEqual value: "alot", scopes: ["source.alot", "keyword.alot"]
|
||||
|
||||
describe ".tokenizeLine(line, ruleStack)", ->
|
||||
describe "when the entire line matches a single pattern with no capture groups", ->
|
||||
it "returns a single token with the correct scope", ->
|
||||
{tokens} = grammar.tokenizeLine("return")
|
||||
|
||||
expect(tokens.length).toBe 1
|
||||
[token] = tokens
|
||||
expect(token.scopes).toEqual ['source.coffee', 'keyword.control.coffee']
|
||||
|
||||
describe "when the entire line matches a single pattern with capture groups", ->
|
||||
it "returns a single token with the correct scope", ->
|
||||
{tokens} = grammar.tokenizeLine("new foo.bar.Baz")
|
||||
|
||||
expect(tokens.length).toBe 3
|
||||
[newOperator, whitespace, className] = tokens
|
||||
expect(newOperator).toEqual value: 'new', scopes: ['source.coffee', 'meta.class.instance.constructor', 'keyword.operator.new.coffee']
|
||||
expect(whitespace).toEqual value: ' ', scopes: ['source.coffee', 'meta.class.instance.constructor']
|
||||
expect(className).toEqual value: 'foo.bar.Baz', scopes: ['source.coffee', 'meta.class.instance.constructor', 'entity.name.type.instance.coffee']
|
||||
|
||||
describe "when the line doesn't match any patterns", ->
|
||||
it "returns the entire line as a single simple token with the grammar's scope", ->
|
||||
textGrammar = atom.syntax.selectGrammar('foo.txt')
|
||||
{tokens} = textGrammar.tokenizeLine("abc def")
|
||||
expect(tokens.length).toBe 1
|
||||
|
||||
describe "when the line matches multiple patterns", ->
|
||||
it "returns multiple tokens, filling in regions that don't match patterns with tokens in the grammar's global scope", ->
|
||||
{tokens} = grammar.tokenizeLine(" return new foo.bar.Baz ")
|
||||
|
||||
expect(tokens.length).toBe 7
|
||||
|
||||
expect(tokens[0]).toEqual value: ' ', scopes: ['source.coffee']
|
||||
expect(tokens[1]).toEqual value: 'return', scopes: ['source.coffee', 'keyword.control.coffee']
|
||||
expect(tokens[2]).toEqual value: ' ', scopes: ['source.coffee']
|
||||
expect(tokens[3]).toEqual value: 'new', scopes: ['source.coffee', 'meta.class.instance.constructor', 'keyword.operator.new.coffee']
|
||||
expect(tokens[4]).toEqual value: ' ', scopes: ['source.coffee', 'meta.class.instance.constructor']
|
||||
expect(tokens[5]).toEqual value: 'foo.bar.Baz', scopes: ['source.coffee', 'meta.class.instance.constructor', 'entity.name.type.instance.coffee']
|
||||
expect(tokens[6]).toEqual value: ' ', scopes: ['source.coffee']
|
||||
|
||||
describe "when the line matches a pattern with optional capture groups", ->
|
||||
it "only returns tokens for capture groups that matched", ->
|
||||
{tokens} = grammar.tokenizeLine("class Quicksort")
|
||||
expect(tokens.length).toBe 3
|
||||
expect(tokens[0].value).toBe "class"
|
||||
expect(tokens[1].value).toBe " "
|
||||
expect(tokens[2].value).toBe "Quicksort"
|
||||
|
||||
describe "when the line matches a rule with nested capture groups and lookahead capture groups beyond the scope of the overall match", ->
|
||||
it "creates distinct tokens for nested captures and does not return tokens beyond the scope of the overall capture", ->
|
||||
{tokens} = grammar.tokenizeLine(" destroy: ->")
|
||||
expect(tokens.length).toBe 6
|
||||
expect(tokens[0]).toEqual(value: ' ', scopes: ["source.coffee"])
|
||||
expect(tokens[1]).toEqual(value: 'destro', scopes: ["source.coffee", "meta.function.coffee", "entity.name.function.coffee"])
|
||||
# this dangling 'y' with a duplicated scope looks wrong, but textmate yields the same behavior. probably a quirk in the coffee grammar.
|
||||
expect(tokens[2]).toEqual(value: 'y', scopes: ["source.coffee", "meta.function.coffee", "entity.name.function.coffee", "entity.name.function.coffee"])
|
||||
expect(tokens[3]).toEqual(value: ':', scopes: ["source.coffee", "keyword.operator.coffee"])
|
||||
expect(tokens[4]).toEqual(value: ' ', scopes: ["source.coffee"])
|
||||
expect(tokens[5]).toEqual(value: '->', scopes: ["source.coffee", "storage.type.function.coffee"])
|
||||
|
||||
describe "when the line matches a pattern that includes a rule", ->
|
||||
it "returns tokens based on the included rule", ->
|
||||
{tokens} = grammar.tokenizeLine("7777777")
|
||||
expect(tokens.length).toBe 1
|
||||
expect(tokens[0]).toEqual value: '7777777', scopes: ['source.coffee', 'constant.numeric.coffee']
|
||||
|
||||
describe "when the line is an interpolated string", ->
|
||||
it "returns the correct tokens", ->
|
||||
{tokens} = grammar.tokenizeLine('"the value is #{@x} my friend"')
|
||||
|
||||
expect(tokens[0]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","punctuation.definition.string.begin.coffee"]
|
||||
expect(tokens[1]).toEqual value: "the value is ", scopes: ["source.coffee","string.quoted.double.coffee"]
|
||||
expect(tokens[2]).toEqual value: '#{', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
|
||||
expect(tokens[3]).toEqual value: "@x", scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","variable.other.readwrite.instance.coffee"]
|
||||
expect(tokens[4]).toEqual value: "}", scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
|
||||
expect(tokens[5]).toEqual value: " my friend", scopes: ["source.coffee","string.quoted.double.coffee"]
|
||||
expect(tokens[6]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","punctuation.definition.string.end.coffee"]
|
||||
|
||||
describe "when the line has an interpolated string inside an interpolated string", ->
|
||||
it "returns the correct tokens", ->
|
||||
{tokens} = grammar.tokenizeLine('"#{"#{@x}"}"')
|
||||
|
||||
expect(tokens[0]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","punctuation.definition.string.begin.coffee"]
|
||||
expect(tokens[1]).toEqual value: '#{', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
|
||||
expect(tokens[2]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","string.quoted.double.coffee","punctuation.definition.string.begin.coffee"]
|
||||
expect(tokens[3]).toEqual value: '#{', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
|
||||
expect(tokens[4]).toEqual value: '@x', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","string.quoted.double.coffee","source.coffee.embedded.source","variable.other.readwrite.instance.coffee"]
|
||||
expect(tokens[5]).toEqual value: '}', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
|
||||
expect(tokens[6]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","string.quoted.double.coffee","punctuation.definition.string.end.coffee"]
|
||||
expect(tokens[7]).toEqual value: '}', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
|
||||
expect(tokens[8]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","punctuation.definition.string.end.coffee"]
|
||||
|
||||
describe "when the line is empty", ->
|
||||
it "returns a single token which has the global scope", ->
|
||||
{tokens} = grammar.tokenizeLine('')
|
||||
expect(tokens[0]).toEqual value: '', scopes: ["source.coffee"]
|
||||
|
||||
describe "when the line matches no patterns", ->
|
||||
it "does not infinitely loop", ->
|
||||
grammar = atom.syntax.selectGrammar("sample.txt")
|
||||
{tokens} = grammar.tokenizeLine('hoo')
|
||||
expect(tokens.length).toBe 1
|
||||
expect(tokens[0]).toEqual value: 'hoo', scopes: ["text.plain", "meta.paragraph.text"]
|
||||
|
||||
describe "when the line matches a pattern with a 'contentName'", ->
|
||||
it "creates tokens using the content of contentName as the token name", ->
|
||||
grammar = atom.syntax.selectGrammar("sample.txt")
|
||||
{tokens} = grammar.tokenizeLine('ok, cool')
|
||||
expect(tokens[0]).toEqual value: 'ok, cool', scopes: ["text.plain", "meta.paragraph.text"]
|
||||
|
||||
describe "when the line matches a pattern with no `name` or `contentName`", ->
|
||||
it "creates tokens without adding a new scope", ->
|
||||
grammar = atom.syntax.selectGrammar('foo.rb')
|
||||
{tokens} = grammar.tokenizeLine('%w|oh \\look|')
|
||||
expect(tokens.length).toBe 5
|
||||
expect(tokens[0]).toEqual value: '%w|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[1]).toEqual value: 'oh ', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby"]
|
||||
expect(tokens[2]).toEqual value: '\\l', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby"]
|
||||
expect(tokens[3]).toEqual value: 'ook', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby"]
|
||||
|
||||
describe "when the line matches a begin/end pattern", ->
|
||||
it "returns tokens based on the beginCaptures, endCaptures and the child scope", ->
|
||||
{tokens} = grammar.tokenizeLine("'''single-quoted heredoc'''")
|
||||
|
||||
expect(tokens.length).toBe 3
|
||||
|
||||
expect(tokens[0]).toEqual value: "'''", scopes: ['source.coffee', 'string.quoted.heredoc.coffee', 'punctuation.definition.string.begin.coffee']
|
||||
expect(tokens[1]).toEqual value: "single-quoted heredoc", scopes: ['source.coffee', 'string.quoted.heredoc.coffee']
|
||||
expect(tokens[2]).toEqual value: "'''", scopes: ['source.coffee', 'string.quoted.heredoc.coffee', 'punctuation.definition.string.end.coffee']
|
||||
|
||||
describe "when the pattern spans multiple lines", ->
|
||||
it "uses the ruleStack returned by the first line to parse the second line", ->
|
||||
{tokens: firstTokens, ruleStack} = grammar.tokenizeLine("'''single-quoted")
|
||||
{tokens: secondTokens, ruleStack} = grammar.tokenizeLine("heredoc'''", ruleStack)
|
||||
|
||||
expect(firstTokens.length).toBe 2
|
||||
expect(secondTokens.length).toBe 2
|
||||
|
||||
expect(firstTokens[0]).toEqual value: "'''", scopes: ['source.coffee', 'string.quoted.heredoc.coffee', 'punctuation.definition.string.begin.coffee']
|
||||
expect(firstTokens[1]).toEqual value: "single-quoted", scopes: ['source.coffee', 'string.quoted.heredoc.coffee']
|
||||
|
||||
expect(secondTokens[0]).toEqual value: "heredoc", scopes: ['source.coffee', 'string.quoted.heredoc.coffee']
|
||||
expect(secondTokens[1]).toEqual value: "'''", scopes: ['source.coffee', 'string.quoted.heredoc.coffee', 'punctuation.definition.string.end.coffee']
|
||||
|
||||
describe "when the pattern contains sub-patterns", ->
|
||||
it "returns tokens within the begin/end scope based on the sub-patterns", ->
|
||||
{tokens} = grammar.tokenizeLine('"""heredoc with character escape \\t"""')
|
||||
|
||||
expect(tokens.length).toBe 4
|
||||
|
||||
expect(tokens[0]).toEqual value: '"""', scopes: ['source.coffee', 'string.quoted.double.heredoc.coffee', 'punctuation.definition.string.begin.coffee']
|
||||
expect(tokens[1]).toEqual value: "heredoc with character escape ", scopes: ['source.coffee', 'string.quoted.double.heredoc.coffee']
|
||||
expect(tokens[2]).toEqual value: "\\t", scopes: ['source.coffee', 'string.quoted.double.heredoc.coffee', 'constant.character.escape.coffee']
|
||||
expect(tokens[3]).toEqual value: '"""', scopes: ['source.coffee', 'string.quoted.double.heredoc.coffee', 'punctuation.definition.string.end.coffee']
|
||||
|
||||
describe "when the end pattern contains a back reference", ->
|
||||
it "constructs the end rule based on its back-references to captures in the begin rule", ->
|
||||
grammar = atom.syntax.selectGrammar('foo.rb')
|
||||
{tokens} = grammar.tokenizeLine('%w|oh|,')
|
||||
expect(tokens.length).toBe 4
|
||||
expect(tokens[0]).toEqual value: '%w|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[1]).toEqual value: 'oh', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby"]
|
||||
expect(tokens[2]).toEqual value: '|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.end.ruby"]
|
||||
expect(tokens[3]).toEqual value: ',', scopes: ["source.ruby", "punctuation.separator.object.ruby"]
|
||||
|
||||
it "allows the rule containing that end pattern to be pushed to the stack multiple times", ->
|
||||
grammar = atom.syntax.selectGrammar('foo.rb')
|
||||
{tokens} = grammar.tokenizeLine('%Q+matz had some #{%Q-crazy ideas-} for ruby syntax+ # damn.')
|
||||
expect(tokens[0]).toEqual value: '%Q+', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[1]).toEqual value: 'matz had some ', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[2]).toEqual value: '#{', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","punctuation.section.embedded.begin.ruby"]
|
||||
expect(tokens[3]).toEqual value: '%Q-', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[4]).toEqual value: 'crazy ideas', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[5]).toEqual value: '-', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.end.ruby"]
|
||||
expect(tokens[6]).toEqual value: '}', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","meta.embedded.line.ruby","punctuation.section.embedded.end.ruby", "source.ruby"]
|
||||
expect(tokens[7]).toEqual value: ' for ruby syntax', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby"]
|
||||
expect(tokens[8]).toEqual value: '+', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.end.ruby"]
|
||||
expect(tokens[9]).toEqual value: ' ', scopes: ["source.ruby"]
|
||||
expect(tokens[10]).toEqual value: '#', scopes: ["source.ruby","comment.line.number-sign.ruby","punctuation.definition.comment.ruby"]
|
||||
expect(tokens[11]).toEqual value: ' damn.', scopes: ["source.ruby","comment.line.number-sign.ruby"]
|
||||
|
||||
describe "when the pattern includes rules from another grammar", ->
|
||||
describe "when a grammar matching the desired scope is available", ->
|
||||
it "parses tokens inside the begin/end patterns based on the included grammar's rules", ->
|
||||
atom.packages.activatePackage('language-html', sync: true)
|
||||
atom.packages.activatePackage('language-ruby-on-rails', sync: true)
|
||||
|
||||
grammar = atom.syntax.grammarForScopeName('text.html.ruby')
|
||||
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
|
||||
|
||||
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
|
||||
expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
|
||||
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","meta.tag.block.any.html"]
|
||||
expect(tokens[3]).toEqual value: 'class', scopes: ["text.html.ruby","meta.tag.block.any.html", "entity.other.attribute-name.html"]
|
||||
expect(tokens[4]).toEqual value: '=', scopes: ["text.html.ruby","meta.tag.block.any.html"]
|
||||
expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"]
|
||||
expect(tokens[6]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"]
|
||||
expect(tokens[7]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"]
|
||||
expect(tokens[8]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
|
||||
expect(tokens[9]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
|
||||
expect(tokens[10]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
|
||||
expect(tokens[11]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"]
|
||||
expect(tokens[12]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"]
|
||||
expect(tokens[13]).toEqual value: 'find', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
|
||||
expect(tokens[14]).toEqual value: '(', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"]
|
||||
expect(tokens[15]).toEqual value: '2', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","constant.numeric.ruby"]
|
||||
expect(tokens[16]).toEqual value: ')', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"]
|
||||
expect(tokens[17]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"]
|
||||
expect(tokens[18]).toEqual value: 'full_name ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
|
||||
expect(tokens[19]).toEqual value: '%>', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
|
||||
expect(tokens[20]).toEqual value: '</', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
|
||||
expect(tokens[21]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
|
||||
expect(tokens[22]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
|
||||
|
||||
it "updates the grammar if the included grammar is updated later", ->
|
||||
atom.packages.activatePackage('language-html', sync: true)
|
||||
atom.packages.activatePackage('language-ruby-on-rails', sync: true)
|
||||
|
||||
grammar = atom.syntax.selectGrammar('foo.html.erb')
|
||||
grammarUpdatedHandler = jasmine.createSpy("grammarUpdatedHandler")
|
||||
grammar.on 'grammar-updated', grammarUpdatedHandler
|
||||
|
||||
{tokens} = grammar.tokenizeLine("<div class='name'><% <<-SQL select * from users;")
|
||||
expect(tokens[12].value).toBe " select * from users;"
|
||||
|
||||
atom.packages.activatePackage('language-sql', sync: true)
|
||||
expect(grammarUpdatedHandler).toHaveBeenCalled()
|
||||
{tokens} = grammar.tokenizeLine("<div class='name'><% <<-SQL select * from users;")
|
||||
expect(tokens[12].value).toBe " "
|
||||
expect(tokens[13].value).toBe "select"
|
||||
|
||||
describe "when a grammar matching the desired scope is unavailable", ->
|
||||
it "updates the grammar if a matching grammar is added later", ->
|
||||
atom.packages.deactivatePackage('language-html')
|
||||
atom.packages.activatePackage('language-ruby-on-rails', sync: true)
|
||||
|
||||
grammar = atom.syntax.grammarForScopeName('text.html.ruby')
|
||||
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
|
||||
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
|
||||
expect(tokens[1]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
|
||||
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
|
||||
expect(tokens[3]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"]
|
||||
|
||||
atom.packages.activatePackage('language-html', sync: true)
|
||||
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
|
||||
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
|
||||
expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
|
||||
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","meta.tag.block.any.html"]
|
||||
expect(tokens[3]).toEqual value: 'class', scopes: ["text.html.ruby","meta.tag.block.any.html", "entity.other.attribute-name.html"]
|
||||
expect(tokens[4]).toEqual value: '=', scopes: ["text.html.ruby","meta.tag.block.any.html"]
|
||||
expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"]
|
||||
expect(tokens[6]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"]
|
||||
expect(tokens[7]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"]
|
||||
expect(tokens[8]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
|
||||
expect(tokens[9]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
|
||||
expect(tokens[10]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
|
||||
|
||||
it "can parse a grammar with newline characters in its regular expressions (regression)", ->
|
||||
grammar = new TextMateGrammar
|
||||
name: "test"
|
||||
scopeName: "source.imaginaryLanguage"
|
||||
repository: {}
|
||||
patterns: [
|
||||
{
|
||||
name: "comment-body"
|
||||
begin: "//"
|
||||
end: "\\n"
|
||||
beginCaptures:
|
||||
"0": { name: "comment-start" }
|
||||
}
|
||||
]
|
||||
|
||||
{tokens, ruleStack} = grammar.tokenizeLine("// a singleLineComment")
|
||||
expect(ruleStack.length).toBe 1
|
||||
expect(ruleStack[0].scopeName).toBe "source.imaginaryLanguage"
|
||||
|
||||
expect(tokens.length).toBe 2
|
||||
expect(tokens[0].value).toBe "//"
|
||||
expect(tokens[1].value).toBe " a singleLineComment"
|
||||
|
||||
it "does not loop infinitely (regression)", ->
|
||||
grammar = atom.syntax.selectGrammar("hello.js")
|
||||
{tokens, ruleStack} = grammar.tokenizeLine("// line comment")
|
||||
{tokens, ruleStack} = grammar.tokenizeLine(" // second line comment with a single leading space", ruleStack)
|
||||
|
||||
describe "when inside a C block", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-c', sync: true)
|
||||
|
||||
it "correctly parses a method. (regression)", ->
|
||||
grammar = atom.syntax.selectGrammar("hello.c")
|
||||
{tokens, ruleStack} = grammar.tokenizeLine("if(1){m()}")
|
||||
expect(tokens[5]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]
|
||||
|
||||
it "correctly parses nested blocks. (regression)", ->
|
||||
grammar = atom.syntax.selectGrammar("hello.c")
|
||||
{tokens, ruleStack} = grammar.tokenizeLine("if(1){if(1){m()}}")
|
||||
expect(tokens[5]).toEqual value: "if", scopes: ["source.c", "meta.block.c", "keyword.control.c"]
|
||||
expect(tokens[10]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]
|
||||
|
||||
describe "when the grammar can infinitely loop over a line", ->
|
||||
it "aborts tokenization", ->
|
||||
spyOn(console, 'error')
|
||||
atom.packages.activatePackage("package-with-infinite-loop-grammar")
|
||||
grammar = atom.syntax.selectGrammar("something.package-with-infinite-loop-grammar")
|
||||
{tokens} = grammar.tokenizeLine("abc")
|
||||
expect(tokens[0].value).toBe "a"
|
||||
expect(tokens[1].value).toBe "bc"
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
|
||||
describe "when a grammar has a pattern that has back references in the match value", ->
|
||||
it "does not special handle the back references and instead allows oniguruma to resolve them", ->
|
||||
atom.packages.activatePackage('language-sass', sync: true)
|
||||
grammar = atom.syntax.selectGrammar("style.scss")
|
||||
{tokens} = grammar.tokenizeLine("@mixin x() { -moz-selector: whatever; }")
|
||||
expect(tokens[9]).toEqual value: "-moz-selector", scopes: ["source.css.scss", "meta.property-list.scss", "meta.property-name.scss"]
|
||||
|
||||
describe "when a line has more tokens than `maxTokensPerLine`", ->
|
||||
it "creates a final token with the remaining text and resets the ruleStack to match the begining of the line", ->
|
||||
grammar = atom.syntax.selectGrammar("hello.js")
|
||||
spyOn(grammar, 'getMaxTokensPerLine').andCallFake -> 5
|
||||
originalRuleStack = [grammar.initialRule, grammar.initialRule, grammar.initialRule]
|
||||
{tokens, ruleStack} = grammar.tokenizeLine("one(two(three(four(five(_param_)))))", originalRuleStack)
|
||||
expect(tokens.length).toBe 5
|
||||
expect(tokens[4].value).toBe "three(four(five(_param_)))))"
|
||||
expect(ruleStack).toEqual originalRuleStack
|
||||
|
||||
describe "when a grammar has a capture with patterns", ->
|
||||
it "matches the patterns and includes the scope specified as the pattern's match name", ->
|
||||
grammar = atom.syntax.selectGrammar("hello.php")
|
||||
{tokens} = grammar.tokenizeLine("<?php public final function meth() {} ?>")
|
||||
|
||||
expect(tokens[2].value).toBe "public"
|
||||
expect(tokens[2].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.modifier.php"]
|
||||
|
||||
expect(tokens[3].value).toBe " "
|
||||
expect(tokens[3].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php"]
|
||||
|
||||
expect(tokens[4].value).toBe "final"
|
||||
expect(tokens[4].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.modifier.php"]
|
||||
|
||||
expect(tokens[5].value).toBe " "
|
||||
expect(tokens[5].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php"]
|
||||
|
||||
expect(tokens[6].value).toBe "function"
|
||||
expect(tokens[6].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.type.function.php"]
|
||||
|
||||
it "ignores child captures of a capture with patterns", ->
|
||||
grammar = new TextMateGrammar
|
||||
name: "test"
|
||||
scopeName: "source"
|
||||
repository: {}
|
||||
patterns: [
|
||||
{
|
||||
name: "text"
|
||||
match: "(a(b))"
|
||||
captures:
|
||||
"1":
|
||||
patterns: [
|
||||
{
|
||||
match: "ab"
|
||||
name: "a"
|
||||
}
|
||||
]
|
||||
"2":
|
||||
name: "b"
|
||||
}
|
||||
]
|
||||
{tokens} = grammar.tokenizeLine("ab")
|
||||
|
||||
expect(tokens[0].value).toBe "ab"
|
||||
expect(tokens[0].scopes).toEqual ["source", "text", "a"]
|
||||
|
||||
describe "when the grammar has injections", ->
|
||||
it "correctly includes the injected patterns when tokenizing", ->
|
||||
grammar = atom.syntax.selectGrammar("hello.php")
|
||||
{tokens} = grammar.tokenizeLine("<div><?php function hello() {} ?></div>")
|
||||
|
||||
expect(tokens[3].value).toBe "<?php"
|
||||
expect(tokens[3].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "punctuation.section.embedded.begin.php"]
|
||||
|
||||
expect(tokens[5].value).toBe "function"
|
||||
expect(tokens[5].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.type.function.php"]
|
||||
|
||||
expect(tokens[7].value).toBe "hello"
|
||||
expect(tokens[7].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "entity.name.function.php"]
|
||||
|
||||
expect(tokens[14].value).toBe "?"
|
||||
expect(tokens[14].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "punctuation.section.embedded.end.php", "source.php"]
|
||||
|
||||
expect(tokens[15].value).toBe ">"
|
||||
expect(tokens[15].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "punctuation.section.embedded.end.php"]
|
||||
|
||||
expect(tokens[16].value).toBe "</"
|
||||
expect(tokens[16].scopes).toEqual ["text.html.php", "meta.tag.block.any.html", "punctuation.definition.tag.begin.html"]
|
||||
|
||||
expect(tokens[17].value).toBe "div"
|
||||
expect(tokens[17].scopes).toEqual ["text.html.php", "meta.tag.block.any.html", "entity.name.tag.block.any.html"]
|
||||
|
||||
describe "when the grammar's pattern name has a group number in it", ->
|
||||
it "replaces the group number with the matched captured text", ->
|
||||
atom.packages.activatePackage('language-hyperlink', sync: true)
|
||||
grammar = atom.syntax.grammarForScopeName("text.hyperlink")
|
||||
{tokens} = grammar.tokenizeLine("https://github.com")
|
||||
expect(tokens[0].scopes).toEqual ["text.hyperlink", "markup.underline.link.https.hyperlink"]
|
||||
|
||||
describe "when the grammar has an injection selector", ->
|
||||
it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
|
||||
atom.packages.activatePackage('language-hyperlink', sync: true)
|
||||
grammar = atom.syntax.selectGrammar("text.js")
|
||||
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
|
||||
|
||||
expect(tokens[0].value).toBe "var"
|
||||
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
|
||||
|
||||
expect(tokens[6].value).toBe "http://github.com"
|
||||
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
|
||||
|
||||
describe "when the grammar is added", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
editor = atom.project.openSync('sample.js')
|
||||
editor.setText("// http://github.com")
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " http://github.com"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
|
||||
atom.packages.activatePackage('language-hyperlink', sync: true)
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[2].value).toBe "http://github.com"
|
||||
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
|
||||
|
||||
describe "when the grammar is updated", ->
|
||||
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
|
||||
editor = atom.project.openSync('sample.js')
|
||||
editor.setText("// SELECT * FROM OCTOCATS")
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
|
||||
atom.syntax.addGrammar(new TextMateGrammar(
|
||||
name: "test"
|
||||
scopeName: "source.test"
|
||||
repository: {}
|
||||
injectionSelector: "comment"
|
||||
patterns: [ { include: "source.sql" } ]
|
||||
))
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
|
||||
atom.packages.activatePackage('language-sql', sync: true)
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[2].value).toBe "SELECT"
|
||||
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
|
||||
|
||||
describe "when the position doesn't advance and rule includes $self and matches itself", ->
|
||||
it "tokenizes the entire line using the rule", ->
|
||||
grammar = new TextMateGrammar
|
||||
name: "test"
|
||||
scopeName: "source"
|
||||
repository: {}
|
||||
patterns: [
|
||||
{
|
||||
name: "text"
|
||||
begin: "(?=forever)"
|
||||
end: "whatevs"
|
||||
patterns: [
|
||||
include: "$self"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
{tokens} = grammar.tokenizeLine("forever and ever")
|
||||
|
||||
expect(tokens.length).toBe 1
|
||||
expect(tokens[0].value).toBe "forever and ever"
|
||||
expect(tokens[0].scopes).toEqual ["source", "text"]
|
||||
|
||||
describe "${capture:/command} style pattern names", ->
|
||||
lines = null
|
||||
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-todo', sync: true)
|
||||
grammar = atom.syntax.selectGrammar('main.rb')
|
||||
lines = grammar.tokenizeLines "# TODO be nicer"
|
||||
|
||||
it "replaces the number with the capture group and translates the text", ->
|
||||
tokens = lines[0]
|
||||
expect(tokens[2].value).toEqual "TODO"
|
||||
expect(tokens[2].scopes).toEqual ["source.ruby", "comment.line.number-sign.ruby", "storage.type.class.todo"]
|
||||
|
||||
describe "language-specific integration tests", ->
|
||||
lines = null
|
||||
|
||||
describe "Git commit messages", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-git', sync: true)
|
||||
grammar = atom.syntax.selectGrammar('COMMIT_EDITMSG')
|
||||
lines = grammar.tokenizeLines """
|
||||
longggggggggggggggggggggggggggggggggggggggggggggggg
|
||||
# Please enter the commit message for your changes. Lines starting
|
||||
"""
|
||||
|
||||
it "correctly parses a long line", ->
|
||||
tokens = lines[0]
|
||||
expect(tokens[0].value).toBe "longggggggggggggggggggggggggggggggggggggggggggggggg"
|
||||
expect(tokens[0].scopes).toEqual ["text.git-commit", "meta.scope.message.git-commit", "invalid.deprecated.line-too-long.git-commit"]
|
||||
|
||||
it "correctly parses the number sign of the first comment line", ->
|
||||
tokens = lines[1]
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[0].scopes).toEqual ["text.git-commit", "meta.scope.metadata.git-commit", "comment.line.number-sign.git-commit", "punctuation.definition.comment.git-commit"]
|
||||
|
||||
describe "C++", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-c', sync: true)
|
||||
grammar = atom.syntax.selectGrammar('includes.cc')
|
||||
lines = grammar.tokenizeLines """
|
||||
#include "a.h"
|
||||
#include "b.h"
|
||||
"""
|
||||
|
||||
it "correctly parses the first include line", ->
|
||||
tokens = lines[0]
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[0].scopes).toEqual ["source.c++", "meta.preprocessor.c.include"]
|
||||
expect(tokens[1].value).toBe 'include'
|
||||
expect(tokens[1].scopes).toEqual ["source.c++", "meta.preprocessor.c.include", "keyword.control.import.include.c"]
|
||||
|
||||
it "correctly parses the second include line", ->
|
||||
tokens = lines[1]
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[0].scopes).toEqual ["source.c++", "meta.preprocessor.c.include"]
|
||||
expect(tokens[1].value).toBe 'include'
|
||||
expect(tokens[1].scopes).toEqual ["source.c++", "meta.preprocessor.c.include", "keyword.control.import.include.c"]
|
||||
|
||||
describe "Ruby", ->
|
||||
beforeEach ->
|
||||
grammar = atom.syntax.selectGrammar('hello.rb')
|
||||
lines = grammar.tokenizeLines """
|
||||
a = {
|
||||
"b" => "c",
|
||||
}
|
||||
"""
|
||||
|
||||
it "doesn't loop infinitely (regression)", ->
|
||||
expect(_.pluck(lines[0], 'value').join('')).toBe 'a = {'
|
||||
expect(_.pluck(lines[1], 'value').join('')).toBe ' "b" => "c",'
|
||||
expect(_.pluck(lines[2], 'value').join('')).toBe '}'
|
||||
expect(_.pluck(lines[3], 'value').join('')).toBe ''
|
||||
|
||||
describe "Objective-C", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-c', sync: true)
|
||||
atom.packages.activatePackage('language-objective-c', sync: true)
|
||||
grammar = atom.syntax.selectGrammar('function.mm')
|
||||
lines = grammar.tokenizeLines """
|
||||
void test() {
|
||||
NSString *a = @"a\\nb";
|
||||
}
|
||||
"""
|
||||
|
||||
it "correctly parses variable type when it is a built-in Cocoa class", ->
|
||||
tokens = lines[1]
|
||||
expect(tokens[0].value).toBe "NSString"
|
||||
expect(tokens[0].scopes).toEqual ["source.objc++", "meta.function.c", "meta.block.c", "support.class.cocoa"]
|
||||
|
||||
it "correctly parses the semicolon at the end of the line", ->
|
||||
tokens = lines[1]
|
||||
lastToken = _.last(tokens)
|
||||
expect(lastToken.value).toBe ";"
|
||||
expect(lastToken.scopes).toEqual ["source.objc++", "meta.function.c", "meta.block.c"]
|
||||
|
||||
it "correctly parses the string characters before the escaped character", ->
|
||||
tokens = lines[1]
|
||||
expect(tokens[2].value).toBe '@"'
|
||||
expect(tokens[2].scopes).toEqual ["source.objc++", "meta.function.c", "meta.block.c", "string.quoted.double.objc", "punctuation.definition.string.begin.objc"]
|
||||
|
||||
describe "Java", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-java', sync: true)
|
||||
grammar = atom.syntax.selectGrammar('Function.java')
|
||||
|
||||
it "correctly parses single line comments", ->
|
||||
lines = grammar.tokenizeLines """
|
||||
public void test() {
|
||||
//comment
|
||||
}
|
||||
"""
|
||||
|
||||
tokens = lines[1]
|
||||
expect(tokens[0].scopes).toEqual ["source.java", "comment.line.double-slash.java", "punctuation.definition.comment.java"]
|
||||
expect(tokens[0].value).toEqual '//'
|
||||
expect(tokens[1].scopes).toEqual ["source.java", "comment.line.double-slash.java"]
|
||||
expect(tokens[1].value).toEqual 'comment'
|
||||
|
||||
it "correctly parses nested method calls", ->
|
||||
tokens = grammar.tokenizeLines('a(b(new Object[0]));')[0]
|
||||
lastToken = _.last(tokens)
|
||||
expect(lastToken.scopes).toEqual ['source.java', 'punctuation.terminator.java']
|
||||
expect(lastToken.value).toEqual ';'
|
||||
|
||||
describe "HTML (Ruby - ERB)", ->
|
||||
it "correctly parses strings inside tags", ->
|
||||
grammar = atom.syntax.selectGrammar('page.erb')
|
||||
lines = grammar.tokenizeLines '<% page_title "My Page" %>'
|
||||
tokens = lines[0]
|
||||
|
||||
expect(tokens[2].value).toEqual '"'
|
||||
expect(tokens[2].scopes).toEqual ["text.html.erb", "meta.embedded.line.erb", "string.quoted.double.ruby", "punctuation.definition.string.begin.ruby"]
|
||||
expect(tokens[3].value).toEqual 'My Page'
|
||||
expect(tokens[3].scopes).toEqual ["text.html.erb", "meta.embedded.line.erb", "string.quoted.double.ruby"]
|
||||
expect(tokens[4].value).toEqual '"'
|
||||
expect(tokens[4].scopes).toEqual ["text.html.erb", "meta.embedded.line.erb", "string.quoted.double.ruby", "punctuation.definition.string.end.ruby"]
|
||||
|
||||
it "does not loop infinitely on <%>", ->
|
||||
atom.packages.activatePackage('language-html', sync: true)
|
||||
atom.packages.activatePackage('language-ruby-on-rails', sync: true)
|
||||
|
||||
grammar = atom.syntax.selectGrammar('foo.html.erb')
|
||||
[tokens] = grammar.tokenizeLines '<%>'
|
||||
expect(tokens.length).toBe 1
|
||||
expect(tokens[0].value).toEqual '<%>'
|
||||
expect(tokens[0].scopes).toEqual ["text.html.erb"]
|
||||
|
||||
describe "Unicode support", ->
|
||||
describe "Surrogate pair characters", ->
|
||||
beforeEach ->
|
||||
grammar = atom.syntax.selectGrammar('main.js')
|
||||
lines = grammar.tokenizeLines "'\uD835\uDF97'"
|
||||
|
||||
it "correctly parses JavaScript strings containing surrogate pair characters", ->
|
||||
tokens = lines[0]
|
||||
expect(tokens.length).toBe 3
|
||||
expect(tokens[0].value).toBe "'"
|
||||
expect(tokens[1].value).toBe "\uD835\uDF97"
|
||||
expect(tokens[2].value).toBe "'"
|
||||
|
||||
describe "when the line contains unicode characters", ->
|
||||
it "correctly parses tokens starting after them", ->
|
||||
atom.packages.activatePackage('language-json', sync: true)
|
||||
grammar = atom.syntax.selectGrammar('package.json')
|
||||
{tokens} = grammar.tokenizeLine '{"\u2026": 1}'
|
||||
|
||||
expect(tokens.length).toBe 8
|
||||
expect(tokens[6].value).toBe '1'
|
||||
expect(tokens[6].scopes).toEqual ["source.json", "meta.structure.dictionary.json", "meta.structure.dictionary.value.json", "constant.numeric.json"]
|
||||
|
||||
describe "python", ->
|
||||
it "parses import blocks correctly", ->
|
||||
grammar = atom.syntax.selectGrammar("file.py")
|
||||
lines = grammar.tokenizeLines "import a\nimport b"
|
||||
|
||||
line1 = lines[0]
|
||||
expect(line1.length).toBe 3
|
||||
expect(line1[0].value).toEqual "import"
|
||||
expect(line1[0].scopes).toEqual ["source.python", "keyword.control.import.python"]
|
||||
expect(line1[1].value).toEqual " "
|
||||
expect(line1[1].scopes).toEqual ["source.python"]
|
||||
expect(line1[2].value).toEqual "a"
|
||||
expect(line1[2].scopes).toEqual ["source.python"]
|
||||
|
||||
line2 = lines[1]
|
||||
expect(line2.length).toBe 3
|
||||
expect(line2[0].value).toEqual "import"
|
||||
expect(line2[0].scopes).toEqual ["source.python", "keyword.control.import.python"]
|
||||
expect(line2[1].value).toEqual " "
|
||||
expect(line2[1].scopes).toEqual ["source.python"]
|
||||
expect(line2[2].value).toEqual "b"
|
||||
expect(line2[2].scopes).toEqual ["source.python"]
|
||||
@@ -21,11 +21,11 @@ describe "TokenizedBuffer", ->
|
||||
describe "when the buffer is destroyed", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
startTokenizing(tokenizedBuffer)
|
||||
|
||||
it "stops tokenization", ->
|
||||
tokenizedBuffer.state.destroy()
|
||||
tokenizedBuffer.destroy()
|
||||
spyOn(tokenizedBuffer, 'tokenizeNextChunk')
|
||||
advanceClock()
|
||||
expect(tokenizedBuffer.tokenizeNextChunk).not.toHaveBeenCalled()
|
||||
@@ -33,7 +33,7 @@ describe "TokenizedBuffer", ->
|
||||
describe "when the buffer contains soft-tabs", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
startTokenizing(tokenizedBuffer)
|
||||
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
|
||||
|
||||
@@ -313,7 +313,7 @@ describe "TokenizedBuffer", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-coffee-script', sync: true)
|
||||
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
startTokenizing(tokenizedBuffer)
|
||||
|
||||
afterEach ->
|
||||
@@ -347,7 +347,7 @@ describe "TokenizedBuffer", ->
|
||||
'abc\uD835\uDF97def'
|
||||
//\uD835\uDF97xyz
|
||||
"""
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
afterEach ->
|
||||
@@ -384,7 +384,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
buffer = atom.project.bufferForPathSync()
|
||||
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
@@ -403,7 +403,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
it "returns the correct token (regression)", ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedBuffer.tokenForPosition([1,0]).scopes).toEqual ["source.js"]
|
||||
expect(tokenizedBuffer.tokenForPosition([1,1]).scopes).toEqual ["source.js"]
|
||||
@@ -412,7 +412,7 @@ describe "TokenizedBuffer", ->
|
||||
describe ".bufferRangeForScopeAtPosition(selector, position)", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
describe "when the selector does not match the token at the position", ->
|
||||
@@ -431,7 +431,7 @@ describe "TokenizedBuffer", ->
|
||||
it "updates the tab length of the tokenized lines", ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
buffer.setText('\ttest')
|
||||
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedBuffer.tokenForPosition([0,0]).value).toBe ' '
|
||||
atom.config.set('editor.tabLength', 6)
|
||||
|
||||
@@ -8,8 +8,11 @@ describe "Window", ->
|
||||
|
||||
beforeEach ->
|
||||
spyOn(atom, 'hide')
|
||||
atom.getLoadSettings() # Causes atom.loadSettings to be initialized
|
||||
atom.loadSettings.initialPath = atom.project.getPath()
|
||||
initialPath = atom.project.getPath()
|
||||
spyOn(atom, 'getLoadSettings').andCallFake ->
|
||||
loadSettings = atom.getLoadSettings.originalValue.call(atom)
|
||||
loadSettings.initialPath = initialPath
|
||||
loadSettings
|
||||
atom.project.destroy()
|
||||
windowEventHandler = new WindowEventHandler()
|
||||
atom.deserializeEditorWindow()
|
||||
@@ -90,9 +93,9 @@ describe "Window", ->
|
||||
|
||||
atom.unloadEditorWindow()
|
||||
|
||||
expect(atom.getWindowState().getObject('workspaceView')).toEqual workspaceViewState.toObject()
|
||||
expect(atom.getWindowState().getObject('syntax')).toEqual syntaxState
|
||||
expect(atom.saveWindowState).toHaveBeenCalled()
|
||||
expect(atom.state.workspaceView).toEqual workspaceViewState
|
||||
expect(atom.state.syntax).toEqual syntaxState
|
||||
expect(atom.saveSync).toHaveBeenCalled()
|
||||
|
||||
it "unsubscribes from all buffers", ->
|
||||
atom.workspaceView.openSync('sample.js')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Q = require 'q'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
Pane = require '../src/pane'
|
||||
PaneView = require '../src/pane-view'
|
||||
|
||||
describe "WorkspaceView", ->
|
||||
pathToOpen = null
|
||||
@@ -19,8 +19,12 @@ describe "WorkspaceView", ->
|
||||
viewState = null
|
||||
|
||||
simulateReload = ->
|
||||
atom.unloadEditorWindow()
|
||||
atom.deserializeEditorWindow()
|
||||
workspaceState = atom.workspaceView.serialize()
|
||||
projectState = atom.project.serialize()
|
||||
atom.workspaceView.remove()
|
||||
atom.project = atom.deserializers.deserialize(projectState)
|
||||
atom.workspaceView = WorkspaceView.deserialize(workspaceState)
|
||||
atom.workspaceView.attachToDom()
|
||||
|
||||
describe "when the serialized WorkspaceView has an unsaved buffer", ->
|
||||
it "constructs the view with the same panes", ->
|
||||
@@ -29,12 +33,12 @@ describe "WorkspaceView", ->
|
||||
editor1 = atom.workspaceView.getActiveView()
|
||||
buffer = editor1.getBuffer()
|
||||
editor1.splitRight()
|
||||
expect(atom.workspaceView.getActiveView()).toBe atom.workspaceView.getEditorViews()[2]
|
||||
expect(atom.workspaceView.getActivePane()).toBe atom.workspaceView.getPanes()[1]
|
||||
|
||||
simulateReload()
|
||||
|
||||
expect(atom.workspaceView.getEditorViews().length).toBe 2
|
||||
expect(atom.workspaceView.getActiveView()).toBe atom.workspaceView.getEditorViews()[1]
|
||||
expect(atom.workspaceView.getActivePane()).toBe atom.workspaceView.getPanes()[1]
|
||||
expect(atom.workspaceView.title).toBe "untitled - #{atom.project.getPath()}"
|
||||
|
||||
describe "when there are open editors", ->
|
||||
@@ -44,20 +48,20 @@ describe "WorkspaceView", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitRight()
|
||||
pane4 = pane2.splitDown()
|
||||
pane2.showItem(atom.project.openSync('b'))
|
||||
pane3.showItem(atom.project.openSync('../sample.js'))
|
||||
pane2.activateItem(atom.project.openSync('b'))
|
||||
pane3.activateItem(atom.project.openSync('../sample.js'))
|
||||
pane3.activeItem.setCursorScreenPosition([2, 4])
|
||||
pane4.showItem(atom.project.openSync('../sample.txt'))
|
||||
pane4.activateItem(atom.project.openSync('../sample.txt'))
|
||||
pane4.activeItem.setCursorScreenPosition([0, 2])
|
||||
pane2.focus()
|
||||
|
||||
simulateReload()
|
||||
|
||||
expect(atom.workspaceView.getEditorViews().length).toBe 4
|
||||
editor1 = atom.workspaceView.panes.find('.row > .pane .editor:eq(0)').view()
|
||||
editor3 = atom.workspaceView.panes.find('.row > .pane .editor:eq(1)').view()
|
||||
editor2 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(0)').view()
|
||||
editor4 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(1)').view()
|
||||
editor1 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(0)').view()
|
||||
editor3 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(1)').view()
|
||||
editor2 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(0)').view()
|
||||
editor4 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(1)').view()
|
||||
|
||||
expect(editor1.getPath()).toBe atom.project.resolve('a')
|
||||
expect(editor2.getPath()).toBe atom.project.resolve('b')
|
||||
@@ -109,7 +113,7 @@ describe "WorkspaceView", ->
|
||||
it "passes focus to the first focusable element", ->
|
||||
focusable1 = $$ -> @div "One", id: 'one', tabindex: -1
|
||||
focusable2 = $$ -> @div "Two", id: 'two', tabindex: -1
|
||||
atom.workspaceView.horizontal.append(focusable1, focusable2)
|
||||
atom.workspaceView.appendToLeft(focusable1, focusable2)
|
||||
expect(document.activeElement).toBe document.body
|
||||
|
||||
atom.workspaceView.focus()
|
||||
@@ -118,7 +122,7 @@ describe "WorkspaceView", ->
|
||||
describe "when there are no visible focusable elements", ->
|
||||
it "surrenders focus to the body", ->
|
||||
focusable = $$ -> @div "One", id: 'one', tabindex: -1
|
||||
atom.workspaceView.horizontal.append(focusable)
|
||||
atom.workspaceView.appendToLeft(focusable)
|
||||
focusable.hide()
|
||||
expect(document.activeElement).toBe document.body
|
||||
|
||||
@@ -208,7 +212,7 @@ describe "WorkspaceView", ->
|
||||
describe ".openSync(filePath, options)", ->
|
||||
describe "when there is no active pane", ->
|
||||
beforeEach ->
|
||||
spyOn(Pane.prototype, 'focus')
|
||||
spyOn(PaneView.prototype, 'focus')
|
||||
atom.workspaceView.getActivePane().remove()
|
||||
expect(atom.workspaceView.getActivePane()).toBeUndefined()
|
||||
|
||||
@@ -294,20 +298,19 @@ describe "WorkspaceView", ->
|
||||
expect(pane2[0]).not.toBe pane1[0]
|
||||
expect(editor.getPath()).toBe require.resolve('./fixtures/dir/b')
|
||||
|
||||
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
|
||||
editor = atom.workspaceView.openSync('file1', split: 'right')
|
||||
pane3 = atom.workspaceView.getActivePane()
|
||||
expect(pane3[0]).toBe pane2[0]
|
||||
expect(editor.getPath()).toBe require.resolve('./fixtures/dir/file1')
|
||||
|
||||
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
|
||||
describe ".openSingletonSync(filePath, options)", ->
|
||||
describe "when there is an active pane", ->
|
||||
[pane1] = []
|
||||
beforeEach ->
|
||||
spyOn(Pane.prototype, 'focus').andCallFake -> @makeActive()
|
||||
pane1 = atom.workspaceView.getActivePane()
|
||||
|
||||
it "creates a new pane and reuses the file when already open", ->
|
||||
@@ -316,9 +319,9 @@ describe "WorkspaceView", ->
|
||||
expect(pane2[0]).not.toBe pane1[0]
|
||||
expect(pane1.itemForUri('b')).toBeFalsy()
|
||||
expect(pane2.itemForUri('b')).not.toBeFalsy()
|
||||
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
|
||||
pane1.focus()
|
||||
pane1.activate()
|
||||
expect(atom.workspaceView.getActivePane()[0]).toBe pane1[0]
|
||||
|
||||
atom.workspaceView.openSingletonSync('b', split: 'right')
|
||||
@@ -326,7 +329,7 @@ describe "WorkspaceView", ->
|
||||
expect(pane3[0]).toBe pane2[0]
|
||||
expect(pane1.itemForUri('b')).toBeFalsy()
|
||||
expect(pane2.itemForUri('b')).not.toBeFalsy()
|
||||
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
|
||||
it "handles split: left by opening to the left pane when necessary", ->
|
||||
atom.workspaceView.openSingletonSync('b', split: 'right')
|
||||
@@ -340,15 +343,15 @@ describe "WorkspaceView", ->
|
||||
|
||||
expect(pane1.itemForUri('file1')).toBeTruthy()
|
||||
expect(pane2.itemForUri('file1')).toBeFalsy()
|
||||
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
|
||||
pane2.focus()
|
||||
pane2.activate()
|
||||
expect(atom.workspaceView.getActivePane()[0]).toBe pane2[0]
|
||||
|
||||
atom.workspaceView.openSingletonSync('file1', split: 'left')
|
||||
activePane = atom.workspaceView.getActivePane()
|
||||
expect(activePane[0]).toBe pane1[0]
|
||||
expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
|
||||
it "reuses the file when already open", ->
|
||||
atom.workspaceView.openSync('b')
|
||||
@@ -357,7 +360,7 @@ describe "WorkspaceView", ->
|
||||
|
||||
describe ".open(filePath)", ->
|
||||
beforeEach ->
|
||||
spyOn(Pane.prototype, 'focus')
|
||||
spyOn(PaneView.prototype, 'focus')
|
||||
|
||||
describe "when there is no active pane", ->
|
||||
beforeEach ->
|
||||
@@ -567,7 +570,7 @@ describe "WorkspaceView", ->
|
||||
it "saves active editor until there are none", ->
|
||||
editor = atom.project.openSync('../sample.txt')
|
||||
spyOn(editor, 'save')
|
||||
atom.workspaceView.getActivePane().showItem(editor)
|
||||
atom.workspaceView.getActivePane().activateItem(editor)
|
||||
atom.workspaceView.trigger('core:save')
|
||||
expect(editor.save).toHaveBeenCalled()
|
||||
|
||||
@@ -578,6 +581,6 @@ describe "WorkspaceView", ->
|
||||
it "saves active editor until there are none", ->
|
||||
editor = atom.project.openSync('../sample.txt')
|
||||
spyOn(editor, 'saveAs')
|
||||
atom.workspaceView.getActivePane().showItem(editor)
|
||||
atom.workspaceView.getActivePane().activateItem(editor)
|
||||
atom.workspaceView.trigger('core:save-as')
|
||||
expect(editor.saveAs).toHaveBeenCalled()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
TextMateGrammar = require './text-mate-grammar'
|
||||
Package = require './package'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
@@ -53,12 +52,6 @@ class AtomPackage extends Package
|
||||
console.warn "Failed to load package named '#{@name}'", e.stack ? e
|
||||
this
|
||||
|
||||
enable: ->
|
||||
atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name)
|
||||
|
||||
disable: ->
|
||||
atom.config.pushAtKeyPath('core.disabledPackages', @metadata.name)
|
||||
|
||||
reset: ->
|
||||
@stylesheets = []
|
||||
@keymaps = []
|
||||
@@ -105,7 +98,7 @@ class AtomPackage extends Package
|
||||
atom.keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
|
||||
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
|
||||
atom.syntax.addGrammar(grammar) for grammar in @grammars
|
||||
grammar.activate() for grammar in @grammars
|
||||
for [scopedPropertiesPath, selector, properties] in @scopedProperties
|
||||
atom.syntax.addProperties(scopedPropertiesPath, selector, properties)
|
||||
|
||||
@@ -152,7 +145,7 @@ class AtomPackage extends Package
|
||||
@grammars = []
|
||||
grammarsDirPath = path.join(@path, 'grammars')
|
||||
for grammarPath in fs.listSync(grammarsDirPath, ['.json', '.cson'])
|
||||
@grammars.push(TextMateGrammar.loadSync(grammarPath))
|
||||
@grammars.push(atom.syntax.readGrammarSync(grammarPath))
|
||||
|
||||
loadScopedProperties: ->
|
||||
@scopedProperties = []
|
||||
@@ -180,7 +173,7 @@ class AtomPackage extends Package
|
||||
@configActivated = false
|
||||
|
||||
deactivateResources: ->
|
||||
atom.syntax.removeGrammar(grammar) for grammar in @grammars
|
||||
grammar.deactivate() for grammar in @grammars
|
||||
atom.syntax.removeProperties(scopedPropertiesPath) for [scopedPropertiesPath] in @scopedProperties
|
||||
atom.keymap.remove(keymapPath) for [keymapPath] in @keymaps
|
||||
atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets
|
||||
|
||||
+154
-168
@@ -9,15 +9,10 @@ dialog = remote.require 'dialog'
|
||||
app = remote.require 'app'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
telepath = require 'telepath'
|
||||
{Document} = telepath
|
||||
{Model} = require 'theorist'
|
||||
fs = require 'fs-plus'
|
||||
{Subscriber} = require 'emissary'
|
||||
|
||||
{$} = require './space-pen-extensions'
|
||||
DeserializerManager = require './deserializer-manager'
|
||||
Package = require './package'
|
||||
SiteShim = require './site-shim'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
|
||||
# Public: Atom global for dealing with packages, themes, menus, and the window.
|
||||
@@ -37,24 +32,105 @@ WindowEventHandler = require './window-event-handler'
|
||||
# * `atom.syntax` - A {Syntax} instance
|
||||
# * `atom.themes` - A {ThemeManager} instance
|
||||
module.exports =
|
||||
class Atom
|
||||
Subscriber.includeInto(this)
|
||||
class Atom extends Model
|
||||
@version: 1
|
||||
|
||||
# Public: Load or create the Atom environment in the given mode
|
||||
#
|
||||
# - mode: Pass 'editor' or 'spec' depending on the kind of environment you
|
||||
# want to build.
|
||||
#
|
||||
# Returns an Atom instance, fully initialized
|
||||
@loadOrCreate: (mode) ->
|
||||
@deserialize(@loadState(mode)) ? new this({mode, @version})
|
||||
|
||||
# Private: Deserializes the Atom environment from a state object
|
||||
@deserialize: (state) ->
|
||||
new this(state) if state?.version is @version
|
||||
|
||||
# Private: Loads and returns the serialized state corresponding to this window
|
||||
# if it exists; otherwise returns undefined.
|
||||
@loadState: (mode) ->
|
||||
statePath = @getStatePath(mode)
|
||||
|
||||
if fs.existsSync(statePath)
|
||||
try
|
||||
stateString = fs.readFileSync(statePath, 'utf8')
|
||||
catch error
|
||||
console.warn "Error reading window state: #{statePath}", error.stack, error
|
||||
else
|
||||
stateString = @getLoadSettings().windowState
|
||||
|
||||
try
|
||||
JSON.parse(stateString) if stateString?
|
||||
catch error
|
||||
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
|
||||
|
||||
# Private: Returns the path where the state for the current window will be
|
||||
# located if it exists.
|
||||
@getStatePath: (mode) ->
|
||||
switch mode
|
||||
when 'spec'
|
||||
filename = 'spec'
|
||||
when 'editor'
|
||||
{initialPath} = @getLoadSettings()
|
||||
if initialPath
|
||||
sha1 = crypto.createHash('sha1').update(initialPath).digest('hex')
|
||||
filename = "editor-#{sha1}"
|
||||
|
||||
if filename
|
||||
path.join(@getStorageDirPath(), filename)
|
||||
else
|
||||
null
|
||||
|
||||
# Private: Get the directory path to Atom's configuration area.
|
||||
#
|
||||
# Returns the absolute path to ~/.atom
|
||||
@getConfigDirPath: ->
|
||||
@configDirPath ?= fs.absolute('~/.atom')
|
||||
|
||||
# Private: Get the path to Atom's storage directory.
|
||||
#
|
||||
# Returns the absolute path to ~/.atom/storage
|
||||
@getStorageDirPath: ->
|
||||
@storageDirPath ?= path.join(@getConfigDirPath(), 'storage')
|
||||
|
||||
# Private: Returns the load settings hash associated with the current window.
|
||||
@getLoadSettings: ->
|
||||
_.deepClone(@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings))
|
||||
|
||||
# Private:
|
||||
constructor: ->
|
||||
@loadTime = null
|
||||
@workspaceViewParentSelector = 'body'
|
||||
@deserializers = new DeserializerManager()
|
||||
@getCurrentWindow: ->
|
||||
remote.getCurrentWindow()
|
||||
|
||||
# Private: Initialize all the properties in this object.
|
||||
# Private: Get the version of the Atom application.
|
||||
@getVersion: ->
|
||||
@version ?= app.getVersion()
|
||||
|
||||
# Private: Determine whether the current version is an official release.
|
||||
@isReleasedVersion: ->
|
||||
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
|
||||
|
||||
workspaceViewParentSelector: 'body'
|
||||
|
||||
# Private: Call .loadOrCreate instead
|
||||
constructor: (@state) ->
|
||||
{@mode} = @state
|
||||
DeserializerManager = require './deserializer-manager'
|
||||
@deserializers = new DeserializerManager(this)
|
||||
|
||||
# Public: Sets up the basic services that should be available in all modes
|
||||
# (both spec and application). Call after this instance has been assigned to
|
||||
# the `atom` global.
|
||||
initialize: ->
|
||||
window.onerror = =>
|
||||
@openDevTools()
|
||||
@emit 'uncaught-error', arguments...
|
||||
|
||||
@unsubscribe()
|
||||
@setBodyPlatformClass()
|
||||
|
||||
{devMode, resourcePath} = atom.getLoadSettings()
|
||||
configDirPath = @getConfigDirPath()
|
||||
|
||||
telepath.devMode = not @isReleasedVersion()
|
||||
@loadTime = null
|
||||
|
||||
Config = require './config'
|
||||
Keymap = require './keymap'
|
||||
@@ -64,40 +140,46 @@ class Atom
|
||||
ThemeManager = require './theme-manager'
|
||||
ContextMenuManager = require './context-menu-manager'
|
||||
MenuManager = require './menu-manager'
|
||||
{devMode, resourcePath} = @getLoadSettings()
|
||||
configDirPath = @getConfigDirPath()
|
||||
|
||||
@config = new Config({configDirPath, resourcePath})
|
||||
@keymap = new Keymap({configDirPath, resourcePath})
|
||||
@packages = new PackageManager({devMode, configDirPath, resourcePath})
|
||||
|
||||
@subscribe @packages, 'activated', => @watchThemes()
|
||||
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath})
|
||||
@contextMenu = new ContextMenuManager(devMode)
|
||||
@menu = new MenuManager({resourcePath})
|
||||
@pasteboard = new Pasteboard()
|
||||
@syntax = @deserializers.deserialize(@getWindowState('syntax')) ? new Syntax()
|
||||
@syntax = @deserializers.deserialize(@state.syntax) ? new Syntax()
|
||||
|
||||
# Private: This method is called in any window needing a general environment, including specs
|
||||
setUpEnvironment: (@windowMode) ->
|
||||
@initialize()
|
||||
@subscribe @packages, 'activated', => @watchThemes()
|
||||
|
||||
Project = require './project'
|
||||
TextBuffer = require './text-buffer'
|
||||
TokenizedBuffer = require './tokenized-buffer'
|
||||
DisplayBuffer = require './display-buffer'
|
||||
Editor = require './editor'
|
||||
|
||||
@windowEventHandler = new WindowEventHandler
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClass: ->
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClasses: ->
|
||||
|
||||
# Private:
|
||||
setBodyPlatformClass: ->
|
||||
document.body.classList.add("platform-#{process.platform}")
|
||||
|
||||
# Public: Create a new telepath model. We won't need to define this method when
|
||||
# the atom global is a telepath model itself because all model subclasses inherit
|
||||
# a create method.
|
||||
create: (model) ->
|
||||
@site.createDocument(model)
|
||||
|
||||
# Public: Get the current window
|
||||
getCurrentWindow: ->
|
||||
remote.getCurrentWindow()
|
||||
@constructor.getCurrentWindow()
|
||||
|
||||
# Public: Get the dimensions of this window.
|
||||
#
|
||||
# Returns an object with x, y, width, and height keys.
|
||||
getDimensions: ->
|
||||
getWindowDimensions: ->
|
||||
browserWindow = @getCurrentWindow()
|
||||
[x, y] = browserWindow.getPosition()
|
||||
[width, height] = browserWindow.getSize()
|
||||
@@ -106,65 +188,56 @@ class Atom
|
||||
# Public: Set the dimensions of the window.
|
||||
#
|
||||
# The window will be centered if either the x or y coordinate is not set
|
||||
# in the dimensions parameter.
|
||||
# in the dimensions parameter. If x or y are omitted the window will be
|
||||
# centered. If height or width are omitted only the position will be changed.
|
||||
#
|
||||
# * dimensions:
|
||||
# + x:
|
||||
# The new x coordinate.
|
||||
# + y:
|
||||
# The new y coordinate.
|
||||
# + width:
|
||||
# The new width.
|
||||
# + height:
|
||||
# The new height.
|
||||
setDimensions: ({x, y, width, height}) ->
|
||||
# + x: The new x coordinate.
|
||||
# + y: The new y coordinate.
|
||||
# + width: The new width.
|
||||
# + height: The new height.
|
||||
setWindowDimensions: ({x, y, width, height}) ->
|
||||
browserWindow = @getCurrentWindow()
|
||||
browserWindow.setSize(width, height)
|
||||
if width? and height?
|
||||
browserWindow.setSize(width, height)
|
||||
if x? and y?
|
||||
browserWindow.setPosition(x, y)
|
||||
else
|
||||
browserWindow.center()
|
||||
|
||||
# Private:
|
||||
restoreDimensions: ->
|
||||
dimensions = @getWindowState().getObject('dimensions')
|
||||
unless dimensions?.width and dimensions?.height
|
||||
{height, width} = @getLoadSettings().initialSize ? {}
|
||||
height ?= screen.availHeight
|
||||
width ?= Math.min(screen.availWidth, 1024)
|
||||
dimensions = {width, height}
|
||||
@setDimensions(dimensions)
|
||||
restoreWindowDimensions: ->
|
||||
windowDimensions = @state.windowDimensions ? {}
|
||||
{initialSize} = @getLoadSettings()
|
||||
windowDimensions.height ?= initialSize?.height ? global.screen.availHeight
|
||||
windowDimensions.width ?= initialSize?.width ? Math.min(global.screen.availWidth, 1024)
|
||||
@setWindowDimensions(windowDimensions)
|
||||
|
||||
# Private:
|
||||
storeWindowDimensions: ->
|
||||
@state.windowDimensions = @getWindowDimensions()
|
||||
|
||||
# Public: Get the load settings for the current window.
|
||||
#
|
||||
# Returns an object containing all the load setting key/value pairs.
|
||||
getLoadSettings: ->
|
||||
@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings)
|
||||
_.deepClone(@loadSettings)
|
||||
@constructor.getLoadSettings()
|
||||
|
||||
# Private:
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
@project = @getWindowState('project')
|
||||
unless @project instanceof Project
|
||||
@project = new Project(path: @getLoadSettings().initialPath)
|
||||
@setWindowState('project', @project)
|
||||
@project ?= @deserializers.deserialize(@project) ? new Project(path: @getLoadSettings().initialPath)
|
||||
|
||||
# Private:
|
||||
deserializeWorkspaceView: ->
|
||||
WorkspaceView = require './workspace-view'
|
||||
state = @getWindowState()
|
||||
@workspaceView = @deserializers.deserialize(state.get('workspaceView'))
|
||||
unless @workspaceView?
|
||||
@workspaceView = new WorkspaceView()
|
||||
state.set('workspaceView', @workspaceView.getState())
|
||||
@workspaceView = @deserializers.deserialize(@state.workspaceView) ? new WorkspaceView
|
||||
$(@workspaceViewParentSelector).append(@workspaceView)
|
||||
|
||||
# Private:
|
||||
deserializePackageStates: ->
|
||||
state = @getWindowState()
|
||||
@packages.packageStates = state.getObject('packageStates') ? {}
|
||||
state.remove('packageStates')
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
delete @state.packageStates
|
||||
|
||||
# Private:
|
||||
deserializeEditorWindow: ->
|
||||
@@ -172,15 +245,14 @@ class Atom
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
|
||||
# Private: This method is only called when opening a real application window
|
||||
# Private: Call this method when establishing a real application window.
|
||||
startEditorWindow: ->
|
||||
if process.platform is 'darwin'
|
||||
CommandInstaller = require './command-installer'
|
||||
CommandInstaller.installAtomCommand()
|
||||
CommandInstaller.installApmCommand()
|
||||
|
||||
@windowEventHandler = new WindowEventHandler
|
||||
@restoreDimensions()
|
||||
@restoreWindowDimensions()
|
||||
@config.load()
|
||||
@config.setDefaults('core', require('./workspace-view').configDefaults)
|
||||
@config.setDefaults('editor', require('./editor-view').configDefaults)
|
||||
@@ -204,29 +276,18 @@ class Atom
|
||||
unloadEditorWindow: ->
|
||||
return if not @project and not @workspaceView
|
||||
|
||||
windowState = @getWindowState()
|
||||
windowState.set('project', @project)
|
||||
windowState.set('syntax', @syntax.serialize())
|
||||
windowState.set('workspaceView', @workspaceView.serialize())
|
||||
@state.syntax = @syntax.serialize()
|
||||
@state.workspaceView = @workspaceView.serialize()
|
||||
@packages.deactivatePackages()
|
||||
windowState.set('packageStates', @packages.packageStates)
|
||||
@saveWindowState()
|
||||
@state.packageStates = @packages.packageStates
|
||||
@saveSync()
|
||||
@workspaceView.remove()
|
||||
@workspaceView = null
|
||||
@project.destroy()
|
||||
@windowEventHandler?.unsubscribe()
|
||||
@keymap.destroy()
|
||||
@windowState = null
|
||||
|
||||
# Set up the default event handlers and menus for a non-editor window.
|
||||
#
|
||||
# This can be used by packages to have a minimum level of keybindings and
|
||||
# menus available when not using the standard editor window.
|
||||
#
|
||||
# This should only be called after setUpEnvironment() has been called.
|
||||
setUpDefaultEvents: ->
|
||||
@windowEventHandler = new WindowEventHandler
|
||||
@keymap.loadBundledKeymaps()
|
||||
@menu.update()
|
||||
|
||||
# Private:
|
||||
loadThemes: ->
|
||||
@themes.load()
|
||||
@@ -331,7 +392,7 @@ class Atom
|
||||
setImmediate =>
|
||||
@show()
|
||||
@focus()
|
||||
@setFullScreen(true) if @workspaceView.getState().get('fullScreen')
|
||||
@setFullScreen(true) if @workspaceView.fullScreen
|
||||
|
||||
# Public: Close the current window.
|
||||
close: ->
|
||||
@@ -362,11 +423,11 @@ class Atom
|
||||
|
||||
# Public: Get the version of the Atom application.
|
||||
getVersion: ->
|
||||
@version ?= app.getVersion()
|
||||
@constructor.getVersion()
|
||||
|
||||
# Public: Determine whether the current version is an official release.
|
||||
isReleasedVersion: ->
|
||||
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
|
||||
@constructor.isReleasedVersion()
|
||||
|
||||
getGitHubAuthTokenName: ->
|
||||
'Atom GitHub API Token'
|
||||
@@ -383,86 +444,15 @@ class Atom
|
||||
#
|
||||
# Returns the absolute path to ~/.atom
|
||||
getConfigDirPath: ->
|
||||
@configDirPath ?= fs.absolute('~/.atom')
|
||||
|
||||
# Public: Get the directory path to Atom's storage area.
|
||||
#
|
||||
# Returns the absoluste path to ~/.atom/storage
|
||||
getStorageDirPath: ->
|
||||
@storageDirPath ?= path.join(@getConfigDirPath(), 'storage')
|
||||
@constructor.getConfigDirPath()
|
||||
|
||||
# Private:
|
||||
getWindowStatePath: ->
|
||||
switch @windowMode
|
||||
when 'spec'
|
||||
filename = @windowMode
|
||||
when 'editor'
|
||||
{initialPath} = @getLoadSettings()
|
||||
if initialPath
|
||||
sha1 = crypto.createHash('sha1').update(initialPath).digest('hex')
|
||||
filename = "editor-#{sha1}"
|
||||
|
||||
if filename
|
||||
path.join(@getStorageDirPath(), filename)
|
||||
saveSync: ->
|
||||
stateString = JSON.stringify(@state)
|
||||
if statePath = @constructor.getStatePath(@mode)
|
||||
fs.writeFileSync(statePath, stateString, 'utf8')
|
||||
else
|
||||
null
|
||||
|
||||
# Public: Set the window state of the given keypath to the value.
|
||||
setWindowState: (keyPath, value) ->
|
||||
windowState = @getWindowState()
|
||||
windowState.set(keyPath, value)
|
||||
windowState
|
||||
|
||||
# Private
|
||||
loadSerializedWindowState: ->
|
||||
if windowStatePath = @getWindowStatePath()
|
||||
if fs.existsSync(windowStatePath)
|
||||
try
|
||||
documentStateJson = fs.readFileSync(windowStatePath, 'utf8')
|
||||
catch error
|
||||
console.warn "Error reading window state: #{windowStatePath}", error.stack, error
|
||||
else
|
||||
documentStateJson = @getLoadSettings().windowState
|
||||
|
||||
try
|
||||
documentState = JSON.parse(documentStateJson) if documentStateJson
|
||||
catch error
|
||||
console.warn "Error parsing window state: #{windowStatePath}", error.stack, error
|
||||
|
||||
# Private:
|
||||
loadWindowState: ->
|
||||
serializedWindowState = @loadSerializedWindowState()
|
||||
doc = Document.deserialize(serializedWindowState) if serializedWindowState?
|
||||
doc ?= Document.create()
|
||||
doc.registerModelClasses(
|
||||
require('./text-buffer'),
|
||||
require('./project'),
|
||||
require('./tokenized-buffer'),
|
||||
require('./display-buffer'),
|
||||
require('./editor')
|
||||
)
|
||||
# TODO: Remove this when everything is using telepath models
|
||||
if @site?
|
||||
@site.setRootDocument(doc)
|
||||
else
|
||||
@site = new SiteShim(doc)
|
||||
doc
|
||||
|
||||
# Private:
|
||||
saveWindowState: ->
|
||||
windowState = @getWindowState()
|
||||
if windowStatePath = @getWindowStatePath()
|
||||
windowState.saveSync(windowStatePath)
|
||||
else
|
||||
@getCurrentWindow().loadSettings.windowState = JSON.stringify(windowState.serializeForPersistence())
|
||||
|
||||
# Public: Get the window state for the key path.
|
||||
getWindowState: (keyPath) ->
|
||||
@windowState ?= @loadWindowState()
|
||||
if keyPath
|
||||
@windowState.get(keyPath)
|
||||
else
|
||||
@windowState
|
||||
@getCurrentWindow().loadSettings.windowState = stateString
|
||||
|
||||
# Public: Get the time taken to completely load the current window.
|
||||
#
|
||||
@@ -474,10 +464,6 @@ class Atom
|
||||
getWindowLoadTime: ->
|
||||
@loadTime
|
||||
|
||||
# Private: Returns a replicated copy of the current state.
|
||||
replicate: ->
|
||||
@getWindowState().replicate()
|
||||
|
||||
# Private:
|
||||
crashMainProcess: ->
|
||||
remote.process.crash()
|
||||
|
||||
@@ -52,6 +52,8 @@ class AtomApplication
|
||||
resourcePath: null
|
||||
version: null
|
||||
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
constructor: (options) ->
|
||||
{@resourcePath, @version, @devMode} = options
|
||||
global.atomApplication = this
|
||||
@@ -71,9 +73,9 @@ class AtomApplication
|
||||
@openWithOptions(options)
|
||||
|
||||
# Private: Opens a new window based on the options provided.
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, newWindow, specDirectory}) ->
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, newWindow, specDirectory, logFile}) ->
|
||||
if test
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory})
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode})
|
||||
else if urlsToOpen.length > 0
|
||||
@@ -107,7 +109,7 @@ class AtomApplication
|
||||
|
||||
# Private: Configures required javascript environment flags.
|
||||
setupJavaScriptArguments: ->
|
||||
app.commandLine.appendSwitch 'js-flags', '--harmony_collections'
|
||||
app.commandLine.appendSwitch 'js-flags', '--harmony_collections --harmony-proxies'
|
||||
|
||||
# Private: Enable updates unless running from a local build of Atom.
|
||||
checkForUpdates: ->
|
||||
@@ -326,7 +328,7 @@ class AtomApplication
|
||||
# The path to include specs from.
|
||||
# + specPath:
|
||||
# The directory to load specs from.
|
||||
runSpecs: ({exitWhenDone, resourcePath, specDirectory}) ->
|
||||
runSpecs: ({exitWhenDone, resourcePath, specDirectory, logFile}) ->
|
||||
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
|
||||
resourcePath = @resourcePath
|
||||
|
||||
@@ -337,7 +339,7 @@ class AtomApplication
|
||||
|
||||
isSpec = true
|
||||
devMode = true
|
||||
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory})
|
||||
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile})
|
||||
|
||||
runBenchmarks: ->
|
||||
try
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
app = require 'app'
|
||||
fs = require 'fs'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
protocol = require 'protocol'
|
||||
|
||||
@@ -11,8 +11,9 @@ module.exports =
|
||||
class AtomProtocolHandler
|
||||
constructor: (@resourcePath) ->
|
||||
@loadPaths = [
|
||||
path.join(@resourcePath, 'node_modules')
|
||||
path.join(app.getHomeDir(), '.atom', 'dev', 'packages')
|
||||
path.join(app.getHomeDir(), '.atom', 'packages')
|
||||
path.join(@resourcePath, 'node_modules')
|
||||
]
|
||||
|
||||
@registerAtomProtocol()
|
||||
@@ -23,5 +24,5 @@ class AtomProtocolHandler
|
||||
relativePath = path.normalize(request.url.substr(7))
|
||||
for loadPath in @loadPaths
|
||||
filePath = path.join(loadPath, relativePath)
|
||||
break if fs.statSyncNoException(filePath)?
|
||||
break if fs.isFileSync(filePath)
|
||||
return new protocol.RequestFileJob(filePath)
|
||||
|
||||
@@ -18,7 +18,7 @@ class AtomWindow
|
||||
isSpec: null
|
||||
|
||||
constructor: (settings={}) ->
|
||||
{@resourcePath, pathToOpen, initialLine, @isSpec} = settings
|
||||
{@resourcePath, pathToOpen, initialLine, @isSpec, @exitWhenDone} = settings
|
||||
global.atomApplication.addWindow(this)
|
||||
|
||||
@setupNodePath(@resourcePath)
|
||||
@@ -30,7 +30,7 @@ class AtomWindow
|
||||
@handleEvents()
|
||||
|
||||
loadSettings = _.extend({}, settings)
|
||||
loadSettings.windowState ?= ''
|
||||
loadSettings.windowState ?= '{}'
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@@ -82,6 +82,8 @@ class AtomWindow
|
||||
@browserWindow.destroy() if chosen is 0
|
||||
|
||||
@browserWindow.on 'crashed', =>
|
||||
global.atomApplication.exit(100) if @exitWhenDone
|
||||
|
||||
chosen = dialog.showMessageBox @browserWindow,
|
||||
type: 'warning'
|
||||
buttons: ['Close Window', 'Reload', 'Keep It Open']
|
||||
|
||||
@@ -83,6 +83,7 @@ parseCommandLine = ->
|
||||
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
|
||||
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the browser process in the foreground.')
|
||||
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')
|
||||
options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.')
|
||||
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
|
||||
options.alias('s', 'spec-directory').string('s').describe('s', 'Set the directory from which specs are loaded (default: Atom\'s spec directory).')
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
|
||||
@@ -106,6 +107,7 @@ parseCommandLine = ->
|
||||
specDirectory = args['spec-directory']
|
||||
newWindow = args['new-window']
|
||||
pidToKillWhenClosed = args['pid'] if args['wait']
|
||||
logFile = args['log-file']
|
||||
|
||||
if args['resource-path']
|
||||
devMode = true
|
||||
@@ -119,4 +121,4 @@ parseCommandLine = ->
|
||||
devMode = false
|
||||
resourcePath = path.dirname(path.dirname(__dirname))
|
||||
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, newWindow, specDirectory}
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, newWindow, specDirectory, logFile}
|
||||
|
||||
@@ -64,5 +64,5 @@ module.exports =
|
||||
resourcePath = null
|
||||
|
||||
resourcePath ?= atom.getLoadSettings().resourcePath
|
||||
commandPath = path.join(resourcePath, 'node_modules', '.bin', 'apm')
|
||||
commandPath = path.join(resourcePath, 'apm', 'node_modules', '.bin', 'apm')
|
||||
@install(commandPath, callback)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{View} = require './space-pen-extensions'
|
||||
{Point, Range} = require 'telepath'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
### Internal ###
|
||||
@@ -30,9 +30,6 @@ class CursorView extends View
|
||||
@cursor.on 'destroyed.cursor-view', =>
|
||||
@needsRemoval = true
|
||||
|
||||
if @cursor.marker.isRemote()
|
||||
@addClass("site-#{@cursor.marker.getOriginSiteId()}")
|
||||
|
||||
beforeRemove: ->
|
||||
@editorView.removeCursorView(this)
|
||||
@cursor.off('.cursor-view')
|
||||
|
||||
+14
-8
@@ -1,4 +1,4 @@
|
||||
{Point, Range} = require 'telepath'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
{Emitter} = require 'emissary'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
@@ -6,7 +6,7 @@ _ = require 'underscore-plus'
|
||||
# where text can be inserted.
|
||||
#
|
||||
# Cursors belong to {Editor}s and have some metadata attached in the form
|
||||
# of a {StringMarker}.
|
||||
# of a {Marker}.
|
||||
module.exports =
|
||||
class Cursor
|
||||
Emitter.includeInto(this)
|
||||
@@ -250,10 +250,14 @@ class Cursor
|
||||
moveToBottom: ->
|
||||
@setBufferPosition(@editor.getEofBufferPosition())
|
||||
|
||||
# Public: Moves the cursor to the beginning of the screen line.
|
||||
moveToBeginningOfLine: ->
|
||||
# Public: Moves the cursor to the beginning of the line.
|
||||
moveToBeginningOfScreenLine: ->
|
||||
@setScreenPosition([@getScreenRow(), 0])
|
||||
|
||||
# Public: Moves the cursor to the beginning of the buffer line.
|
||||
moveToBeginningOfLine: ->
|
||||
@setBufferPosition([@getBufferRow(), 0])
|
||||
|
||||
# Public: Moves the cursor to the beginning of the first character in the
|
||||
# line.
|
||||
moveToFirstCharacterOfLine: ->
|
||||
@@ -261,9 +265,7 @@ class Cursor
|
||||
screenline = @editor.lineForScreenRow(row)
|
||||
|
||||
goalColumn = screenline.text.search(/\S/)
|
||||
return if goalColumn == -1
|
||||
|
||||
goalColumn = 0 if goalColumn == column
|
||||
goalColumn = 0 if goalColumn == column or goalColumn == -1
|
||||
@setScreenPosition([row, goalColumn])
|
||||
|
||||
# Public: Moves the cursor to the beginning of the buffer line, skipping all
|
||||
@@ -277,9 +279,13 @@ class Cursor
|
||||
|
||||
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
|
||||
|
||||
# Public: Moves the cursor to the end of the line.
|
||||
moveToEndOfScreenLine: ->
|
||||
@setScreenPosition([@getScreenRow(), Infinity])
|
||||
|
||||
# Public: Moves the cursor to the end of the buffer line.
|
||||
moveToEndOfLine: ->
|
||||
@setScreenPosition([@getScreenRow(), Infinity])
|
||||
@setBufferPosition([@getBufferRow(), Infinity])
|
||||
|
||||
# Public: Moves the cursor to the beginning of the word.
|
||||
moveToBeginningOfWord: ->
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
{Document, Model} = require 'telepath'
|
||||
|
||||
# Public: Manages the deserializers used for serialized state
|
||||
#
|
||||
# Should be accessed via `atom.deserializers`
|
||||
module.exports =
|
||||
class DeserializerManager
|
||||
constructor: ->
|
||||
constructor: (@environment) ->
|
||||
@deserializers = {}
|
||||
@deferredDeserializers = {}
|
||||
|
||||
@@ -25,13 +23,9 @@ class DeserializerManager
|
||||
deserialize: (state, params) ->
|
||||
return unless state?
|
||||
|
||||
return state if state instanceof Model
|
||||
|
||||
if deserializer = @get(state)
|
||||
stateVersion = state.get?('version') ? state.version
|
||||
return if deserializer.version? and deserializer.version isnt stateVersion
|
||||
if (state instanceof Document) and not deserializer.acceptsDocuments
|
||||
state = state.toObject()
|
||||
deserializer.deserialize(state, params)
|
||||
else
|
||||
console.warn "No deserializer found for", state
|
||||
|
||||
+48
-17
@@ -1,8 +1,11 @@
|
||||
path = require 'path'
|
||||
|
||||
async = require 'async'
|
||||
{Emitter} = require 'emissary'
|
||||
fs = require 'fs-plus'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
File = require './file'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
# Public: Represents a directory using {File}s
|
||||
module.exports =
|
||||
@@ -41,7 +44,7 @@ class Directory
|
||||
#
|
||||
# All relative directory entries are removed and symlinks are resolved to
|
||||
# their final destination.
|
||||
getRealPath: ->
|
||||
getRealPathSync: ->
|
||||
unless @realPath?
|
||||
try
|
||||
@realPath = fs.realpathSync(@path)
|
||||
@@ -56,7 +59,7 @@ class Directory
|
||||
|
||||
if pathToCheck.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
true
|
||||
else if pathToCheck.indexOf(path.join(@getRealPath(), path.sep)) is 0
|
||||
else if pathToCheck.indexOf(path.join(@getRealPathSync(), path.sep)) is 0
|
||||
true
|
||||
else
|
||||
false
|
||||
@@ -70,30 +73,26 @@ class Directory
|
||||
|
||||
if fullPath is @getPath()
|
||||
''
|
||||
else if fullPath.indexOf(path.join(@getPath(), path.sep)) is 0
|
||||
else if @isPathPrefixOf(@getPath(), fullPath)
|
||||
fullPath.substring(@getPath().length + 1)
|
||||
else if fullPath is @getRealPath()
|
||||
else if fullPath is @getRealPathSync()
|
||||
''
|
||||
else if fullPath.indexOf(path.join(@getRealPath(), path.sep)) is 0
|
||||
fullPath.substring(@getRealPath().length + 1)
|
||||
else if @isPathPrefixOf(@getRealPathSync(), fullPath)
|
||||
fullPath.substring(@getRealPathSync().length + 1)
|
||||
else
|
||||
fullPath
|
||||
|
||||
# Public: Reads file entries in this directory from disk.
|
||||
# Public: Reads file entries in this directory from disk synchronously.
|
||||
#
|
||||
# Note: It follows symlinks.
|
||||
#
|
||||
# Returns an Array of {Files}.
|
||||
getEntries: ->
|
||||
# Returns an Array of {File} and {Directory} objects.
|
||||
getEntriesSync: ->
|
||||
directories = []
|
||||
files = []
|
||||
for entryPath in fs.listSync(@path)
|
||||
try
|
||||
stat = fs.lstatSync(entryPath)
|
||||
if stat = fs.lstatSyncNoException(entryPath)
|
||||
symlink = stat.isSymbolicLink()
|
||||
stat = fs.statSync(entryPath) if symlink
|
||||
catch e
|
||||
continue
|
||||
stat = fs.statSyncNoException(entryPath) if symlink
|
||||
continue unless stat
|
||||
if stat.isDirectory()
|
||||
directories.push(new Directory(entryPath, symlink))
|
||||
else if stat.isFile()
|
||||
@@ -101,6 +100,34 @@ class Directory
|
||||
|
||||
directories.concat(files)
|
||||
|
||||
# Public: Reads file entries in this directory from disk asynchronously.
|
||||
#
|
||||
# * callback: A function to call with an Error as the first argument and
|
||||
# an Array of {File} and {Directory} objects as the second argument.
|
||||
getEntries: (callback) ->
|
||||
fs.list @path, (error, entries) ->
|
||||
return callback(error) if error?
|
||||
|
||||
directories = []
|
||||
files = []
|
||||
addEntry = (entryPath, stat, symlink, callback) ->
|
||||
if stat?.isDirectory()
|
||||
directories.push(new Directory(entryPath, symlink))
|
||||
else if stat?.isFile()
|
||||
files.push(new File(entryPath, symlink))
|
||||
callback()
|
||||
|
||||
statEntry = (entryPath, callback) ->
|
||||
fs.lstat entryPath, (error, stat) ->
|
||||
if stat?.isSymbolicLink()
|
||||
fs.stat entryPath, (error, stat) ->
|
||||
addEntry(entryPath, stat, true, callback)
|
||||
else
|
||||
addEntry(entryPath, stat, false, callback)
|
||||
|
||||
async.eachLimit entries, 1, statEntry, ->
|
||||
callback(null, directories.concat(files))
|
||||
|
||||
# Private:
|
||||
subscribeToNativeChangeEvents: ->
|
||||
unless @watchSubscription?
|
||||
@@ -112,3 +139,7 @@ class Directory
|
||||
if @watchSubscription?
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
||||
|
||||
# Private: Does given full path start with the given prefix?
|
||||
isPathPrefixOf: (prefix, fullPath) ->
|
||||
fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{Range} = require 'telepath'
|
||||
{Range} = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
|
||||
@@ -42,7 +42,7 @@ class DisplayBufferMarker
|
||||
# Modifies the screen range of the display marker.
|
||||
#
|
||||
# screenRange - The new {Range} to use
|
||||
# options - A hash of options matching those found in {StringMarker.setRange}
|
||||
# options - A hash of options matching those found in {Marker.setRange}
|
||||
setScreenRange: (screenRange, options) ->
|
||||
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
|
||||
|
||||
@@ -55,7 +55,7 @@ class DisplayBufferMarker
|
||||
# Modifies the buffer range of the display marker.
|
||||
#
|
||||
# screenRange - The new {Range} to use
|
||||
# options - A hash of options matching those found in {StringMarker.setRange}
|
||||
# options - A hash of options matching those found in {Marker.setRange}
|
||||
setBufferRange: (bufferRange, options) ->
|
||||
@bufferMarker.setRange(bufferRange, options)
|
||||
|
||||
@@ -140,19 +140,10 @@ class DisplayBufferMarker
|
||||
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
|
||||
# can be invalid without being destroyed, in which case undoing the invalidating
|
||||
# operation would restore the marker. Once a marker is destroyed by calling
|
||||
# {StringMarker.destroy}, no undo/redo operation can ever bring it back.
|
||||
# {Marker.destroy}, no undo/redo operation can ever bring it back.
|
||||
isDestroyed: ->
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
getOriginSiteId: ->
|
||||
@bufferMarker.getOriginSiteId()
|
||||
|
||||
isLocal: ->
|
||||
@bufferMarker.isLocal()
|
||||
|
||||
isRemote: ->
|
||||
@bufferMarker.isRemote()
|
||||
|
||||
getAttributes: ->
|
||||
@bufferMarker.getAttributes()
|
||||
|
||||
@@ -160,7 +151,7 @@ class DisplayBufferMarker
|
||||
@bufferMarker.setAttributes(attributes)
|
||||
|
||||
matchesAttributes: (attributes) ->
|
||||
attributes = @displayBuffer.translateToStringMarkerAttributes(attributes)
|
||||
attributes = @displayBuffer.translateToBufferMarkerAttributes(attributes)
|
||||
@bufferMarker.matchesAttributes(attributes)
|
||||
|
||||
# Destroys the marker
|
||||
@@ -177,7 +168,7 @@ class DisplayBufferMarker
|
||||
|
||||
# Returns a {String} representation of the marker
|
||||
inspect: ->
|
||||
"DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange().inspect()})"
|
||||
"DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange()})"
|
||||
|
||||
### Internal ###
|
||||
|
||||
@@ -194,13 +185,11 @@ class DisplayBufferMarker
|
||||
newTailScreenPosition = @getTailScreenPosition()
|
||||
isValid = @isValid()
|
||||
|
||||
changed = false
|
||||
changed = true unless _.isEqual(newHeadBufferPosition, @oldHeadBufferPosition)
|
||||
changed = true unless _.isEqual(newHeadScreenPosition, @oldHeadScreenPosition)
|
||||
changed = true unless _.isEqual(newTailBufferPosition, @oldTailBufferPosition)
|
||||
changed = true unless _.isEqual(newTailScreenPosition, @oldTailScreenPosition)
|
||||
changed = true unless _.isEqual(isValid, @wasValid)
|
||||
return unless changed
|
||||
return if _.isEqual(isValid, @wasValid) and
|
||||
_.isEqual(newHeadBufferPosition, @oldHeadBufferPosition) and
|
||||
_.isEqual(newHeadScreenPosition, @oldHeadScreenPosition) and
|
||||
_.isEqual(newTailBufferPosition, @oldTailBufferPosition) and
|
||||
_.isEqual(newTailScreenPosition, @oldTailScreenPosition)
|
||||
|
||||
@emit 'changed', {
|
||||
@oldHeadScreenPosition, newHeadScreenPosition,
|
||||
|
||||
+29
-28
@@ -1,7 +1,9 @@
|
||||
_ = require 'underscore-plus'
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
guid = require 'guid'
|
||||
{Model, Point, Range} = require 'telepath'
|
||||
Serializable = require 'serializable'
|
||||
{Model} = require 'theorist'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
TokenizedBuffer = require './tokenized-buffer'
|
||||
RowMap = require './row-map'
|
||||
Fold = require './fold'
|
||||
@@ -12,28 +14,18 @@ ConfigObserver = require './config-observer'
|
||||
# Private:
|
||||
module.exports =
|
||||
class DisplayBuffer extends Model
|
||||
Serializable.includeInto(this)
|
||||
_.extend @prototype, ConfigObserver
|
||||
|
||||
@properties
|
||||
tokenizedBuffer: null
|
||||
softWrap: -> atom.config.get('editor.softWrap') ? false
|
||||
softWrap: null
|
||||
editorWidthInChars: null
|
||||
|
||||
constructor: ->
|
||||
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer}={}) ->
|
||||
super
|
||||
@deserializing = @state?
|
||||
|
||||
created: ->
|
||||
if @deserializing
|
||||
@deserializing = false
|
||||
return
|
||||
|
||||
if @tokenizedBuffer?
|
||||
@tokenizedBuffer?.created()
|
||||
else
|
||||
@tokenizedBuffer = new TokenizedBuffer({@tabLength, @buffer, project: atom.project})
|
||||
@softWrap ?= atom.config.get('editor.softWrap') ? false
|
||||
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer})
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@updateAllScreenLines()
|
||||
@@ -43,10 +35,9 @@ class DisplayBuffer extends Model
|
||||
@subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated
|
||||
@subscribe @buffer, 'marker-created', @handleBufferMarkerCreated
|
||||
|
||||
@subscribe @state, 'changed', ({newValues}) =>
|
||||
if newValues.softWrap?
|
||||
@emit 'soft-wrap-changed', newValues.softWrap
|
||||
@updateWrappedScreenLines()
|
||||
@subscribe @$softWrap, (softWrap) =>
|
||||
@emit 'soft-wrap-changed', softWrap
|
||||
@updateWrappedScreenLines()
|
||||
|
||||
@observeConfig 'editor.preferredLineLength', callNow: false, =>
|
||||
@updateWrappedScreenLines() if @softWrap and atom.config.get('editor.softWrapAtPreferredLineLength')
|
||||
@@ -54,8 +45,18 @@ class DisplayBuffer extends Model
|
||||
@observeConfig 'editor.softWrapAtPreferredLineLength', callNow: false, =>
|
||||
@updateWrappedScreenLines() if @softWrap
|
||||
|
||||
serializeParams: ->
|
||||
id: @id
|
||||
softWrap: @softWrap
|
||||
editorWidthInChars: @editorWidthInChars
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer)
|
||||
params
|
||||
|
||||
copy: ->
|
||||
newDisplayBuffer = atom.create(new DisplayBuffer({@buffer, tabLength: @getTabLength()}))
|
||||
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()})
|
||||
for marker in @findMarkers(displayBufferId: @id)
|
||||
marker.copy(displayBufferId: newDisplayBuffer.id)
|
||||
newDisplayBuffer
|
||||
@@ -358,7 +359,7 @@ class DisplayBuffer extends Model
|
||||
|
||||
# Get the grammar for this buffer.
|
||||
#
|
||||
# Returns the current {TextMateGrammar} or the {NullGrammar}.
|
||||
# Returns the current {Grammar} or the {NullGrammar}.
|
||||
getGrammar: ->
|
||||
@tokenizedBuffer.grammar
|
||||
|
||||
@@ -468,7 +469,7 @@ class DisplayBuffer extends Model
|
||||
# Constructs a new marker at the given screen range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {StringMarker} constructor
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenRange: (args...) ->
|
||||
@@ -478,7 +479,7 @@ class DisplayBuffer extends Model
|
||||
# Constructs a new marker at the given buffer range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {StringMarker} constructor
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferRange: (args...) ->
|
||||
@@ -487,7 +488,7 @@ class DisplayBuffer extends Model
|
||||
# Constructs a new marker at the given screen position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {StringMarker} constructor
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
@@ -496,7 +497,7 @@ class DisplayBuffer extends Model
|
||||
# Constructs a new marker at the given buffer position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {StringMarker} constructor
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@@ -526,10 +527,10 @@ class DisplayBuffer extends Model
|
||||
#
|
||||
# Returns an {Array} of {DisplayBufferMarker}s
|
||||
findMarkers: (attributes) ->
|
||||
attributes = @translateToStringMarkerAttributes(attributes)
|
||||
attributes = @translateToBufferMarkerAttributes(attributes)
|
||||
@buffer.findMarkers(attributes).map (stringMarker) => @getMarker(stringMarker.id)
|
||||
|
||||
translateToStringMarkerAttributes: (attributes) ->
|
||||
translateToBufferMarkerAttributes: (attributes) ->
|
||||
stringMarkerAttributes = {}
|
||||
for key, value of attributes
|
||||
switch key
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{View, $, $$$} = require './space-pen-extensions'
|
||||
TextBuffer = require './text-buffer'
|
||||
Gutter = require './gutter'
|
||||
{Point, Range} = require 'telepath'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
Editor = require './editor'
|
||||
CursorView = require './cursor-view'
|
||||
SelectionView = require './selection-view'
|
||||
@@ -105,12 +105,12 @@ class EditorView extends View
|
||||
if editor?
|
||||
@edit(editor)
|
||||
else if @mini
|
||||
@edit(atom.create(new Editor
|
||||
buffer: atom.create(new TextBuffer)
|
||||
@edit(new Editor
|
||||
buffer: new TextBuffer
|
||||
softWrap: false
|
||||
tabLength: 2
|
||||
softTabs: true
|
||||
))
|
||||
)
|
||||
else
|
||||
throw new Error("Must supply an Editor or mini: true")
|
||||
|
||||
@@ -139,7 +139,9 @@ class EditorView extends View
|
||||
'editor:delete-to-end-of-word': @deleteToEndOfWord
|
||||
'editor:delete-line': @deleteLine
|
||||
'editor:cut-to-end-of-line': @cutToEndOfLine
|
||||
'editor:move-to-beginning-of-screen-line': => @editor.moveCursorToBeginningOfScreenLine()
|
||||
'editor:move-to-beginning-of-line': @moveCursorToBeginningOfLine
|
||||
'editor:move-to-end-of-screen-line': => @editor.moveCursorToEndOfScreenLine()
|
||||
'editor:move-to-end-of-line': @moveCursorToEndOfLine
|
||||
'editor:move-to-first-character-of-line': @moveCursorToFirstCharacterOfLine
|
||||
'editor:move-to-beginning-of-word': @moveCursorToBeginningOfWord
|
||||
@@ -181,6 +183,7 @@ class EditorView extends View
|
||||
'editor:newline-above': @insertNewlineAbove
|
||||
'editor:add-selection-below': @addSelectionBelow
|
||||
'editor:add-selection-above': @addSelectionAbove
|
||||
'editor:split-selections-into-lines': => @editor.splitSelectionsIntoLines()
|
||||
'editor:toggle-soft-tabs': @toggleSoftTabs
|
||||
'editor:toggle-soft-wrap': @toggleSoftWrap
|
||||
'editor:fold-all': @foldAll
|
||||
@@ -824,6 +827,9 @@ class EditorView extends View
|
||||
|
||||
@editor.setVisible(true)
|
||||
|
||||
@editor.on "destroyed", =>
|
||||
@remove()
|
||||
|
||||
@editor.on "contents-conflicted.editor", =>
|
||||
@showBufferConflictAlert(@editor)
|
||||
|
||||
@@ -1067,7 +1073,7 @@ class EditorView extends View
|
||||
#
|
||||
# Returns a {Pane}.
|
||||
getPane: ->
|
||||
@parent('.item-views').parent('.pane').view()
|
||||
@parent('.item-views').parents('.pane').view()
|
||||
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData or @removed
|
||||
@@ -1215,8 +1221,8 @@ class EditorView extends View
|
||||
@scrollTop(editorScrollTop)
|
||||
@scrollLeft(editorScrollLeft)
|
||||
@setSoftWrap(@editor.getSoftWrap())
|
||||
@newCursors = @editor.getAllCursors()
|
||||
@newSelections = @editor.getAllSelections()
|
||||
@newCursors = @editor.getCursors()
|
||||
@newSelections = @editor.getSelections()
|
||||
@updateDisplay(suppressAutoScroll: true)
|
||||
|
||||
requestDisplayUpdate: ->
|
||||
@@ -1229,7 +1235,7 @@ class EditorView extends View
|
||||
|
||||
updateDisplay: (options={}) ->
|
||||
return unless @attached and @editor
|
||||
return if @editor.destroyed
|
||||
return if @editor.isDestroyed()
|
||||
unless @isOnDom() and @isVisible()
|
||||
@redrawOnReattach = true
|
||||
return
|
||||
|
||||
+65
-84
@@ -1,6 +1,8 @@
|
||||
_ = require 'underscore-plus'
|
||||
path = require 'path'
|
||||
{Model, Point, Range} = require 'telepath'
|
||||
Serializable = require 'serializable'
|
||||
{Model} = require 'theorist'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
LanguageMode = require './language-mode'
|
||||
DisplayBuffer = require './display-buffer'
|
||||
Cursor = require './cursor'
|
||||
@@ -27,17 +29,12 @@ TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
# atom.workspaceView.eachEditorView (editorView) ->
|
||||
# editorView.insertText('Hello World')
|
||||
# ```
|
||||
#
|
||||
# ## Collaboration builtin
|
||||
#
|
||||
# FIXME: Describe how there are both local and remote cursors and selections and
|
||||
# why that is.
|
||||
module.exports =
|
||||
class Editor extends Model
|
||||
Serializable.includeInto(this)
|
||||
atom.deserializers.add(this)
|
||||
|
||||
@properties
|
||||
displayBuffer: null
|
||||
softTabs: null
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
|
||||
@@ -47,33 +44,18 @@ class Editor extends Model
|
||||
buffer: null
|
||||
languageMode: null
|
||||
cursors: null
|
||||
remoteCursors: null
|
||||
selections: null
|
||||
remoteSelections: null
|
||||
suppressSelectionMerging: false
|
||||
|
||||
constructor: ->
|
||||
constructor: ({@softTabs, initialLine, tabLength, softWrap, @displayBuffer, buffer, registerEditor, suppressCursorCreation}) ->
|
||||
super
|
||||
@deserializing = @state?
|
||||
|
||||
created: ->
|
||||
if @deserializing
|
||||
@deserializing = false
|
||||
@callDisplayBufferCreatedHook = true
|
||||
@registerEditor = true
|
||||
return
|
||||
|
||||
@cursors = []
|
||||
@remoteCursors = []
|
||||
@selections = []
|
||||
@remoteSelections = []
|
||||
|
||||
unless @displayBuffer?
|
||||
@displayBuffer = new DisplayBuffer({@buffer, @tabLength, @softWrap})
|
||||
@softTabs = @buffer.usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true
|
||||
|
||||
@displayBuffer.created() if @callDisplayBufferCreatedHook
|
||||
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrap})
|
||||
@buffer = @displayBuffer.buffer
|
||||
@softTabs = @buffer.usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true
|
||||
|
||||
for marker in @findMarkers(@getSelectionMarkerAttributes())
|
||||
marker.setAttributes(preserveFolds: true)
|
||||
@@ -82,22 +64,31 @@ class Editor extends Model
|
||||
@subscribeToBuffer()
|
||||
@subscribeToDisplayBuffer()
|
||||
|
||||
if @getCursors().length is 0 and not @suppressCursorCreation
|
||||
if @initialLine
|
||||
position = [@initialLine, 0]
|
||||
if @getCursors().length is 0 and not suppressCursorCreation
|
||||
if initialLine
|
||||
position = [initialLine, 0]
|
||||
else
|
||||
position = _.last(@getRemoteCursors())?.getBufferPosition() ? [0, 0]
|
||||
position = [0, 0]
|
||||
@addCursorAtBufferPosition(position)
|
||||
|
||||
@languageMode = new LanguageMode(this, @buffer.getExtension())
|
||||
|
||||
@subscribe @$scrollTop, 'value', (scrollTop) => @emit 'scroll-top-changed', scrollTop
|
||||
@subscribe @$scrollLeft, 'value', (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
|
||||
@subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop
|
||||
@subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
|
||||
|
||||
atom.project.addEditor(this) if @registerEditor
|
||||
atom.project.addEditor(this) if registerEditor
|
||||
|
||||
# Deprecated: The goal is a world where we don't call serialize explicitly
|
||||
serialize: -> this
|
||||
serializeParams: ->
|
||||
id: @id
|
||||
softTabs: @softTabs
|
||||
scrollTop: @scrollTop
|
||||
scrollLeft: @scrollLeft
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
|
||||
params.registerEditor = true
|
||||
params
|
||||
|
||||
# Private:
|
||||
subscribeToBuffer: ->
|
||||
@@ -126,24 +117,20 @@ class Editor extends Model
|
||||
require './editor-view'
|
||||
|
||||
# Private:
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@destroyed = true
|
||||
destroyed: ->
|
||||
@unsubscribe()
|
||||
selection.destroy() for selection in @getSelections()
|
||||
@buffer.release()
|
||||
@displayBuffer.destroy()
|
||||
@languageMode.destroy()
|
||||
atom.project?.removeEditor(this)
|
||||
@emit 'destroyed'
|
||||
@off()
|
||||
|
||||
# Private: Creates an {Editor} with the same initial state
|
||||
copy: ->
|
||||
tabLength = @getTabLength()
|
||||
displayBuffer = @displayBuffer.copy()
|
||||
softTabs = @getSoftTabs()
|
||||
newEditor = @create(new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true}))
|
||||
newEditor = new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true})
|
||||
newEditor.setScrollTop(@getScrollTop())
|
||||
newEditor.setScrollLeft(@getScrollLeft())
|
||||
for marker in @findMarkers(editorId: @id)
|
||||
@@ -187,7 +174,8 @@ class Editor extends Model
|
||||
# Returns a {Boolean}.
|
||||
isEqual: (other) ->
|
||||
return false unless other instanceof Editor
|
||||
@buffer == other.buffer and
|
||||
@isAlive() == other.isAlive() and
|
||||
@buffer.getPath() == other.buffer.getPath() and
|
||||
@getScrollTop() == other.getScrollTop() and
|
||||
@getScrollLeft() == other.getScrollLeft() and
|
||||
@getCursorScreenPosition().isEqual(other.getCursorScreenPosition())
|
||||
@@ -326,6 +314,12 @@ class Editor extends Model
|
||||
# {Delegates to: TextBuffer.setText}
|
||||
setText: (text) -> @buffer.setText(text)
|
||||
|
||||
# {Delegates to: TextBuffer.getTextInRange}
|
||||
getTextInRange: (range) -> @buffer.getTextInRange(range)
|
||||
|
||||
# {Delegates to: TextBuffer.getLineCount}
|
||||
getLineCount: -> @buffer.getLineCount()
|
||||
|
||||
# Private: Retrieves the current {TextBuffer}.
|
||||
getBuffer: -> @buffer
|
||||
|
||||
@@ -825,11 +819,6 @@ class Editor extends Model
|
||||
hasMultipleCursors: ->
|
||||
@getCursors().length > 1
|
||||
|
||||
# Public: Returns an Array of all {Cursor}s, including cursors representing
|
||||
# remote users.
|
||||
getAllCursors: ->
|
||||
@getCursors().concat(@getRemoteCursors())
|
||||
|
||||
# Public: Returns an Array of all local {Cursor}s.
|
||||
getCursors: -> new Array(@cursors...)
|
||||
|
||||
@@ -837,9 +826,6 @@ class Editor extends Model
|
||||
getCursor: ->
|
||||
_.last(@cursors)
|
||||
|
||||
# Public: Returns an Array of all remove {Cursor}s.
|
||||
getRemoteCursors: -> new Array(@remoteCursors...)
|
||||
|
||||
# Public: Adds and returns a cursor at the given screen position.
|
||||
addCursorAtScreenPosition: (screenPosition) ->
|
||||
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
||||
@@ -854,10 +840,7 @@ class Editor extends Model
|
||||
# position.
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker)
|
||||
if marker.isLocal()
|
||||
@cursors.push(cursor)
|
||||
else
|
||||
@remoteCursors.push(cursor)
|
||||
@cursors.push(cursor)
|
||||
@emit 'cursor-added', cursor
|
||||
cursor
|
||||
|
||||
@@ -878,12 +861,7 @@ class Editor extends Model
|
||||
@destroyFoldsIntersectingBufferRange(marker.getBufferRange())
|
||||
cursor = @addCursor(marker)
|
||||
selection = new Selection(_.extend({editor: this, marker, cursor}, options))
|
||||
|
||||
if marker.isLocal()
|
||||
@selections.push(selection)
|
||||
else
|
||||
@remoteSelections.push(selection)
|
||||
|
||||
@selections.push(selection)
|
||||
selectionBufferRange = selection.getBufferRange()
|
||||
@mergeIntersectingSelections()
|
||||
if selection.destroyed
|
||||
@@ -941,10 +919,7 @@ class Editor extends Model
|
||||
#
|
||||
# * selection - The {Selection} to remove.
|
||||
removeSelection: (selection) ->
|
||||
if selection.isLocal()
|
||||
_.remove(@selections, selection)
|
||||
else
|
||||
_.remove(@remoteSelections, selection)
|
||||
_.remove(@selections, selection)
|
||||
|
||||
# Public: Clears every selection.
|
||||
#
|
||||
@@ -964,10 +939,6 @@ class Editor extends Model
|
||||
else
|
||||
false
|
||||
|
||||
# Public: Returns all selections, including remote selections.
|
||||
getAllSelections: ->
|
||||
@getSelections().concat(@getRemoteSelections())
|
||||
|
||||
# Public: Gets all local selections.
|
||||
#
|
||||
# Returns an {Array} of {Selection}s.
|
||||
@@ -982,21 +953,12 @@ class Editor extends Model
|
||||
getLastSelection: ->
|
||||
_.last(@selections)
|
||||
|
||||
# Public: Returns all remote selections.
|
||||
getRemoteSelections: -> new Array(@remoteSelections...)
|
||||
|
||||
# Public: Gets all local selections, ordered by their position in the buffer.
|
||||
#
|
||||
# Returns an {Array} of {Selection}s.
|
||||
getSelectionsOrderedByBufferPosition: ->
|
||||
@getSelections().sort (a, b) -> a.compare(b)
|
||||
|
||||
# Public: Gets all remote selections, ordered by their position in the buffer.
|
||||
#
|
||||
# Returns an {Array} of {Selection}s.
|
||||
getRemoteSelectionsOrderedByBufferPosition: ->
|
||||
@getRemoteSelections().sort (a, b) -> a.compare(b)
|
||||
|
||||
# Public: Gets the very last local selection in the buffer.
|
||||
#
|
||||
# Returns a {Selection}.
|
||||
@@ -1066,12 +1028,6 @@ class Editor extends Model
|
||||
getSelectedBufferRanges: ->
|
||||
selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition()
|
||||
|
||||
# Public: Gets an Array of buffer {Range}s of all the remote {Selection}s.
|
||||
#
|
||||
# Sorted by their position in the file itself.
|
||||
getRemoteSelectedBufferRanges: ->
|
||||
selection.getBufferRange() for selection in @getRemoteSelectionsOrderedByBufferPosition()
|
||||
|
||||
# Public: Returns the selected text of the most recently added local {Selection}.
|
||||
getSelectedText: ->
|
||||
@getLastSelection().getText()
|
||||
@@ -1120,6 +1076,10 @@ class Editor extends Model
|
||||
@moveCursors (cursor) -> cursor.moveToBottom()
|
||||
|
||||
# Public: Moves every local cursor to the beginning of the line.
|
||||
moveCursorToBeginningOfScreenLine: ->
|
||||
@moveCursors (cursor) -> cursor.moveToBeginningOfScreenLine()
|
||||
|
||||
# Public: Moves every local cursor to the beginning of the buffer line.
|
||||
moveCursorToBeginningOfLine: ->
|
||||
@moveCursors (cursor) -> cursor.moveToBeginningOfLine()
|
||||
|
||||
@@ -1128,6 +1088,10 @@ class Editor extends Model
|
||||
@moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine()
|
||||
|
||||
# Public: Moves every local cursor to the end of the line.
|
||||
moveCursorToEndOfScreenLine: ->
|
||||
@moveCursors (cursor) -> cursor.moveToEndOfScreenLine()
|
||||
|
||||
# Public: Moves every local cursor to the end of the buffer line.
|
||||
moveCursorToEndOfLine: ->
|
||||
@moveCursors (cursor) -> cursor.moveToEndOfLine()
|
||||
|
||||
@@ -1233,6 +1197,23 @@ class Editor extends Model
|
||||
addSelectionAbove: ->
|
||||
@expandSelectionsBackward (selection) => selection.addSelectionAbove()
|
||||
|
||||
# Public: Split any multi-line selections into one selection per line.
|
||||
#
|
||||
# This methods break apart all multi-line selections to create multiple
|
||||
# single-line selections that cumulatively cover the same original area.
|
||||
splitSelectionsIntoLines: ->
|
||||
for selection in @getSelections()
|
||||
range = selection.getBufferRange()
|
||||
continue if range.isSingleLine()
|
||||
|
||||
selection.destroy()
|
||||
{start, end} = range
|
||||
@addSelectionForBufferRange([start, [start.row, Infinity]])
|
||||
{row} = start
|
||||
while ++row < end.row
|
||||
@addSelectionForBufferRange([[row, 0], [row, Infinity]])
|
||||
@addSelectionForBufferRange([[end.row, 0], [end.row, end.column]])
|
||||
|
||||
# Public: Transposes the current text selections.
|
||||
#
|
||||
# The text in each selection is reversed so `abcd` would become `dcba`. The
|
||||
@@ -1401,7 +1382,7 @@ class Editor extends Model
|
||||
|
||||
# Private:
|
||||
inspect: ->
|
||||
JSON.stringify @state.toObject()
|
||||
"<Editor #{@id}>"
|
||||
|
||||
# Private:
|
||||
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
{Point, Range} = require 'telepath'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
|
||||
# Private: Represents a fold that collapses multiple buffer lines into a single
|
||||
# line on the screen.
|
||||
@@ -38,6 +38,7 @@ class Fold
|
||||
getBufferRange: ({includeNewline}={}) ->
|
||||
range = @marker.getRange()
|
||||
if includeNewline
|
||||
range = range.copy()
|
||||
range.end.row++
|
||||
range.end.column = 0
|
||||
range
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ class Git
|
||||
@refreshStatus()
|
||||
|
||||
if @project?
|
||||
@subscribe @project.buffers.onEach (buffer) => @subscribeToBuffer(buffer)
|
||||
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
|
||||
|
||||
# Private: Subscribes to buffer events.
|
||||
subscribeToBuffer: (buffer) ->
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
{View, $, $$, $$$} = require './space-pen-extensions'
|
||||
{Range} = require 'telepath'
|
||||
{Range} = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
# Private: Represents the portion of the {EditorView} containing row numbers.
|
||||
@@ -230,6 +230,8 @@ class Gutter extends View
|
||||
@highlightedLineNumbers.push(highlightedLineNumber)
|
||||
|
||||
highlightLines: ->
|
||||
return unless @getEditorView().editor?.isAlive()
|
||||
|
||||
if @getEditorView().getSelection().isEmpty()
|
||||
row = @getEditorView().getCursorScreenPosition().row
|
||||
rowRange = new Range([row, 0], [row, 0])
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{$} = require './space-pen-extensions'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
{specificity} = require 'clear-cut'
|
||||
PEG = require 'pegjs'
|
||||
|
||||
### Internal ###
|
||||
|
||||
@@ -10,28 +8,41 @@ module.exports =
|
||||
class KeyBinding
|
||||
@parser: null
|
||||
@currentIndex: 1
|
||||
@specificities: null
|
||||
|
||||
@calculateSpecificity: (selector) ->
|
||||
@specificities ?= {}
|
||||
value = @specificities[selector]
|
||||
unless value?
|
||||
value = specificity(selector)
|
||||
@specificities[selector] = value
|
||||
value
|
||||
|
||||
@normalizeKeystroke: (keystroke) ->
|
||||
normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) =>
|
||||
keys = @getParser().parse(keystroke)
|
||||
keys = @parseKeystroke(keystroke)
|
||||
modifiers = keys[0...-1]
|
||||
modifiers.sort()
|
||||
[modifiers..., _.last(keys)].join('-')
|
||||
normalizedKeystroke.join(' ')
|
||||
|
||||
@getParser: ->
|
||||
if not KeyBinding.parser
|
||||
keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8')
|
||||
KeyBinding.parser = PEG.buildParser(keystrokePattern)
|
||||
@parseKeystroke: (keystroke) ->
|
||||
unless @parser?
|
||||
try
|
||||
@parser = require './keystroke-pattern'
|
||||
catch
|
||||
keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8')
|
||||
PEG = require 'pegjs'
|
||||
@parser = PEG.buildParser(keystrokePattern)
|
||||
|
||||
KeyBinding.parser
|
||||
@parser.parse(keystroke)
|
||||
|
||||
constructor: (source, command, keystroke, selector) ->
|
||||
@source = source
|
||||
@command = command
|
||||
@keystroke = KeyBinding.normalizeKeystroke(keystroke)
|
||||
@selector = selector.replace(/!important/g, '')
|
||||
@specificity = specificity(selector)
|
||||
@specificity = KeyBinding.calculateSpecificity(selector)
|
||||
@index = KeyBinding.currentIndex++
|
||||
|
||||
matches: (keystroke) ->
|
||||
|
||||
+18
-2
@@ -4,6 +4,7 @@ fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
CSON = require 'season'
|
||||
KeyBinding = require './key-binding'
|
||||
File = require './file'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd']
|
||||
@@ -28,6 +29,9 @@ class Keymap
|
||||
constructor: ({@resourcePath, @configDirPath})->
|
||||
@keyBindings = []
|
||||
|
||||
destroy: ->
|
||||
@unwatchUserKeymap()
|
||||
|
||||
# Public: Returns an array of all {KeyBinding}s.
|
||||
getKeyBindings: ->
|
||||
_.clone(@keyBindings)
|
||||
@@ -118,9 +122,21 @@ class Keymap
|
||||
@loadDirectory(path.join(@resourcePath, 'keymaps'))
|
||||
@emit('bundled-keymaps-loaded')
|
||||
|
||||
userKeymapPath: ->
|
||||
CSON.resolve(path.join(@configDirPath, 'keymap'))
|
||||
|
||||
unwatchUserKeymap: ->
|
||||
keymapPath = @userKeymapPath()
|
||||
@userKeymapFile?.off()
|
||||
@remove(keymapPath) if keymapPath
|
||||
|
||||
loadUserKeymap: ->
|
||||
userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap'))
|
||||
@load(userKeymapPath) if userKeymapPath
|
||||
keymapPath = @userKeymapPath()
|
||||
@unwatchUserKeymap()
|
||||
if keymapPath
|
||||
@load(keymapPath)
|
||||
@userKeymapFile = new File(keymapPath)
|
||||
@userKeymapFile.on 'contents-changed', => @loadUserKeymap()
|
||||
|
||||
loadDirectory: (directoryPath) ->
|
||||
@load(filePath) for filePath in fs.listSync(directoryPath, ['.cson', '.json'])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{Range} = require 'telepath'
|
||||
{Range} = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
{OnigRegExp} = require 'oniguruma'
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
@@ -289,9 +289,6 @@ class LanguageMode
|
||||
if desiredIndentLevel >= 0 and desiredIndentLevel < currentIndentLevel
|
||||
@editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
|
||||
|
||||
tokenizeLine: (line, stack, firstLine) ->
|
||||
{tokens, stack} = @grammar.tokenizeLine(line, stack, firstLine)
|
||||
|
||||
getRegexForProperty: (scopes, property) ->
|
||||
if pattern = atom.syntax.getProperty(scopes, property)
|
||||
new OnigRegExp(pattern)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
Token = require './token'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
### Internal ###
|
||||
module.exports =
|
||||
class NullGrammar
|
||||
Emitter.includeInto(this)
|
||||
|
||||
name: 'Null Grammar'
|
||||
scopeName: 'text.plain.null-grammar'
|
||||
|
||||
getScore: -> 0
|
||||
|
||||
tokenizeLine: (line) ->
|
||||
{ tokens: [new Token(value: line, scopes: ['null-grammar.text.plain'])] }
|
||||
|
||||
tokenizeLines: (text) ->
|
||||
lines = text.split('\n')
|
||||
for line, i in lines
|
||||
{tokens} = @tokenizeLine(line)
|
||||
tokens
|
||||
|
||||
grammarUpdated: -> # noop
|
||||
@@ -226,10 +226,6 @@ class PackageManager
|
||||
{@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
|
||||
@packageDependencies ?= {}
|
||||
|
||||
# Temporarily ignore 'grunt-download-atom-shell' here, should remove this
|
||||
# when it became a public npm module.
|
||||
delete @packageDependencies['grunt-download-atom-shell']
|
||||
|
||||
@packageDependencies
|
||||
|
||||
# Public: Get an array of all the available package paths.
|
||||
|
||||
@@ -47,6 +47,12 @@ class Package
|
||||
isActive: ->
|
||||
atom.packages.isPackageActive(@name)
|
||||
|
||||
enable: ->
|
||||
atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name)
|
||||
|
||||
disable: ->
|
||||
atom.config.pushAtKeyPath('core.disabledPackages', @metadata.name)
|
||||
|
||||
isTheme: ->
|
||||
!!@metadata?.theme
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{View} = require './space-pen-extensions'
|
||||
PaneView = null
|
||||
|
||||
### Internal ###
|
||||
module.exports =
|
||||
class PaneAxisView extends View
|
||||
initialize: (@model) ->
|
||||
@onChildAdded(child) for child in @model.children
|
||||
@subscribe @model.children, 'changed', @onChildrenChanged
|
||||
|
||||
viewForModel: (model) ->
|
||||
viewClass = model.getViewClass()
|
||||
model._view ?= new viewClass(model)
|
||||
|
||||
onChildrenChanged: ({index, removedValues, insertedValues}) =>
|
||||
focusedElement = document.activeElement if @hasFocus()
|
||||
@onChildRemoved(child, index) for child in removedValues
|
||||
@onChildAdded(child, index + i) for child, i in insertedValues
|
||||
focusedElement?.focus() if document.activeElement is document.body
|
||||
|
||||
onChildAdded: (child, index) =>
|
||||
view = @viewForModel(child)
|
||||
@insertAt(index, view)
|
||||
|
||||
onChildRemoved: (child) =>
|
||||
view = @viewForModel(child)
|
||||
view.detach()
|
||||
PaneView ?= require './pane-view'
|
||||
|
||||
if view instanceof PaneView and view.model.isDestroyed()
|
||||
@getContainer()?.trigger 'pane:removed', [view]
|
||||
|
||||
getContainer: ->
|
||||
@closest('.panes').view()
|
||||
+54
-93
@@ -1,105 +1,66 @@
|
||||
{$, View} = require './space-pen-extensions'
|
||||
telepath = require 'telepath'
|
||||
{Model, Sequence} = require 'theorist'
|
||||
{flatten} = require 'underscore-plus'
|
||||
Serializable = require 'serializable'
|
||||
|
||||
PaneRowView = null
|
||||
PaneColumnView = null
|
||||
|
||||
### Internal ###
|
||||
module.exports =
|
||||
class PaneAxis extends View
|
||||
@acceptsDocuments: true
|
||||
class PaneAxis extends Model
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
@deserialize: (state) ->
|
||||
new this(state)
|
||||
constructor: ({@container, @orientation, children}) ->
|
||||
@children = Sequence.fromArray(children ? [])
|
||||
|
||||
initialize: (args...) ->
|
||||
if args[0] instanceof telepath.Document
|
||||
@state = args[0]
|
||||
@state.get('children').each (child, index) =>
|
||||
@addChild(atom.deserializers.deserialize(child), index, updateState: false)
|
||||
@subscribe @children.onEach (child) =>
|
||||
child.parent = this
|
||||
child.container = @container
|
||||
@subscribe child, 'destroyed', => @removeChild(child)
|
||||
|
||||
@subscribe @children.onRemoval (child) => @unsubscribe(child)
|
||||
|
||||
@when @children.$length.becomesLessThan(2), 'reparentLastChild'
|
||||
@when @children.$length.becomesLessThan(1), 'destroy'
|
||||
|
||||
deserializeParams: (params) ->
|
||||
{container} = params
|
||||
params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {container})
|
||||
params
|
||||
|
||||
serializeParams: ->
|
||||
children: @children.map (child) -> child.serialize()
|
||||
orientation: @orientation
|
||||
|
||||
getViewClass: ->
|
||||
if @orientation is 'vertical'
|
||||
PaneColumnView ?= require './pane-column-view'
|
||||
else
|
||||
@state = atom.site.createDocument(deserializer: @className(), children: [])
|
||||
@addChild(child) for child in args
|
||||
PaneRowView ?= require './pane-row-view'
|
||||
|
||||
@state.get('children').on 'changed', ({index, insertedValues, removedValues, siteId}) =>
|
||||
return if siteId is @state.siteId
|
||||
for childState in removedValues
|
||||
@removeChild(@children(":eq(#{index})").view(), updateState: false)
|
||||
for childState, i in insertedValues
|
||||
@addChild(atom.deserializers.deserialize(childState), index + i, updateState: false)
|
||||
getPanes: ->
|
||||
flatten(@children.map (child) -> child.getPanes())
|
||||
|
||||
addChild: (child, index=@children().length, options={}) ->
|
||||
@insertAt(index, child)
|
||||
state = child.getState()
|
||||
@state.get('children').insert(index, state) if options.updateState ? true
|
||||
@getContainer()?.adjustPaneDimensions()
|
||||
addChild: (child, index=@children.length) ->
|
||||
@children.splice(index, 0, child)
|
||||
|
||||
removeChild: (child, options={}) ->
|
||||
options.updateState ?= true
|
||||
removeChild: (child) ->
|
||||
index = @children.indexOf(child)
|
||||
throw new Error("Removing non-existent child") if index is -1
|
||||
@children.splice(index, 1)
|
||||
|
||||
parent = @parent().view()
|
||||
container = @getContainer()
|
||||
childWasInactive = not child.isActive?()
|
||||
replaceChild: (oldChild, newChild) ->
|
||||
index = @children.indexOf(oldChild)
|
||||
throw new Error("Replacing non-existent child") if index is -1
|
||||
@children.splice(index, 1, newChild)
|
||||
|
||||
primitiveRemove = (child) =>
|
||||
node = child[0]
|
||||
$.cleanData(node.getElementsByTagName('*'))
|
||||
$.cleanData([node])
|
||||
this[0].removeChild(node)
|
||||
insertChildBefore: (currentChild, newChild) ->
|
||||
index = @children.indexOf(currentChild)
|
||||
@children.splice(index, 0, newChild)
|
||||
|
||||
# use primitive .removeChild() dom method instead of .remove() to avoid recursive loop
|
||||
if @children().length == 2
|
||||
primitiveRemove(child)
|
||||
sibling = @children().view()
|
||||
siblingFocused = sibling.is(':has(:focus)')
|
||||
sibling.detach()
|
||||
insertChildAfter: (currentChild, newChild) ->
|
||||
index = @children.indexOf(currentChild)
|
||||
@children.splice(index + 1, 0, newChild)
|
||||
|
||||
if parent.setRoot?
|
||||
parent.setRoot(sibling, suppressPaneItemChangeEvents: childWasInactive)
|
||||
else
|
||||
parent.insertChildBefore(this, sibling, options)
|
||||
parent.removeChild(this, options)
|
||||
sibling.focus() if siblingFocused
|
||||
else
|
||||
@state.get('children').remove(@indexOf(child)) if options.updateState
|
||||
primitiveRemove(child)
|
||||
|
||||
container.adjustPaneDimensions()
|
||||
Pane = require './pane'
|
||||
container.trigger 'pane:removed', [child] if child instanceof Pane
|
||||
|
||||
detachChild: (child) ->
|
||||
@state.get('children').remove(@indexOf(child))
|
||||
child.detach()
|
||||
|
||||
getContainer: ->
|
||||
@closest('.panes').view()
|
||||
|
||||
getActivePaneItem: ->
|
||||
@getActivePane()?.activeItem
|
||||
|
||||
getActivePane: ->
|
||||
@find('.pane.active').view() ? @find('.pane:first').view()
|
||||
|
||||
insertChildBefore: (child, newChild, options={}) ->
|
||||
newChild.insertBefore(child)
|
||||
if options.updateState ? true
|
||||
children = @state.get('children')
|
||||
childIndex = children.indexOf(child.getState())
|
||||
children.insert(childIndex, newChild.getState())
|
||||
|
||||
insertChildAfter: (child, newChild) ->
|
||||
newChild.insertAfter(child)
|
||||
children = @state.get('children')
|
||||
childIndex = children.indexOf(child.getState())
|
||||
children.insert(childIndex + 1, newChild.getState())
|
||||
|
||||
serialize: ->
|
||||
state = @state.clone()
|
||||
state.set('children', child.serialize() for child in @children().views())
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
horizontalChildUnits: ->
|
||||
$(child).view().horizontalGridUnits() for child in @children()
|
||||
|
||||
verticalChildUnits: ->
|
||||
$(child).view().verticalGridUnits() for child in @children()
|
||||
reparentLastChild: ->
|
||||
@parent.replaceChild(this, @children[0])
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{$} = require './space-pen-extensions'
|
||||
_ = require 'underscore-plus'
|
||||
PaneAxisView = require './pane-axis-view'
|
||||
|
||||
# Internal:
|
||||
module.exports =
|
||||
class PaneColumnView extends PaneAxisView
|
||||
|
||||
@content: ->
|
||||
@div class: 'pane-column'
|
||||
|
||||
className: ->
|
||||
"PaneColumn"
|
||||
@@ -1,34 +0,0 @@
|
||||
{$} = require './space-pen-extensions'
|
||||
_ = require 'underscore-plus'
|
||||
PaneAxis = require './pane-axis'
|
||||
|
||||
# Internal:
|
||||
module.exports =
|
||||
class PaneColumn extends PaneAxis
|
||||
|
||||
@content: ->
|
||||
@div class: 'column'
|
||||
|
||||
className: ->
|
||||
"PaneColumn"
|
||||
|
||||
adjustDimensions: ->
|
||||
totalUnits = @verticalGridUnits()
|
||||
unitsSoFar = 0
|
||||
for child in @children()
|
||||
child = $(child).view()
|
||||
childUnits = child.verticalGridUnits()
|
||||
child.css
|
||||
width: '100%'
|
||||
height: "#{childUnits / totalUnits * 100}%"
|
||||
top: "#{unitsSoFar / totalUnits * 100}%"
|
||||
left: 0
|
||||
|
||||
child.adjustDimensions()
|
||||
unitsSoFar += childUnits
|
||||
|
||||
horizontalGridUnits: ->
|
||||
Math.max(@horizontalChildUnits()...)
|
||||
|
||||
verticalGridUnits: ->
|
||||
_.sum(@verticalChildUnits())
|
||||
@@ -0,0 +1,132 @@
|
||||
Serializable = require 'serializable'
|
||||
{$, View} = require './space-pen-extensions'
|
||||
PaneView = require './pane-view'
|
||||
PaneContainer = require './pane-container'
|
||||
|
||||
# Private: Manages the list of panes within a {WorkspaceView}
|
||||
module.exports =
|
||||
class PaneContainerView extends View
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
@deserialize: (state) ->
|
||||
new this(PaneContainer.deserialize(state.model))
|
||||
|
||||
@content: ->
|
||||
@div class: 'panes'
|
||||
|
||||
initialize: (params) ->
|
||||
if params instanceof PaneContainer
|
||||
@model = params
|
||||
else
|
||||
@model = new PaneContainer({root: params?.root?.model})
|
||||
|
||||
@subscribe @model.$root, @onRootChanged
|
||||
@subscribe @model.$activePaneItem.changes, @onActivePaneItemChanged
|
||||
|
||||
viewForModel: (model) ->
|
||||
if model?
|
||||
viewClass = model.getViewClass()
|
||||
model._view ?= new viewClass(model)
|
||||
|
||||
serializeParams: ->
|
||||
model: @model.serialize()
|
||||
|
||||
### Public ###
|
||||
|
||||
itemDestroyed: (item) ->
|
||||
@trigger 'item-destroyed', [item]
|
||||
|
||||
getRoot: ->
|
||||
@children().first().view()
|
||||
|
||||
setRoot: (root) ->
|
||||
@model.root = root?.model
|
||||
|
||||
onRootChanged: (root) =>
|
||||
focusedElement = document.activeElement if @hasFocus()
|
||||
|
||||
oldRoot = @getRoot()
|
||||
if oldRoot instanceof PaneView and oldRoot.model.isDestroyed()
|
||||
@trigger 'pane:removed', [oldRoot]
|
||||
oldRoot?.detach()
|
||||
if root?
|
||||
view = @viewForModel(root)
|
||||
@append(view)
|
||||
focusedElement?.focus()
|
||||
else
|
||||
atom.workspaceView?.focus() if focusedElement?
|
||||
|
||||
onActivePaneItemChanged: (activeItem) =>
|
||||
@trigger 'pane-container:active-pane-item-changed', [activeItem]
|
||||
|
||||
removeChild: (child) ->
|
||||
throw new Error("Removing non-existant child") unless @getRoot() is child
|
||||
@setRoot(null)
|
||||
@trigger 'pane:removed', [child] if child instanceof PaneView
|
||||
|
||||
saveAll: ->
|
||||
pane.saveItems() for pane in @getPanes()
|
||||
|
||||
confirmClose: ->
|
||||
saved = true
|
||||
for pane in @getPanes()
|
||||
for item in pane.getItems()
|
||||
if not pane.promptToSaveItem(item)
|
||||
saved = false
|
||||
break
|
||||
saved
|
||||
|
||||
getPanes: ->
|
||||
@find('.pane').views()
|
||||
|
||||
indexOfPane: (pane) ->
|
||||
@getPanes().indexOf(pane.view())
|
||||
|
||||
paneAtIndex: (index) ->
|
||||
@getPanes()[index]
|
||||
|
||||
eachPane: (callback) ->
|
||||
callback(pane) for pane in @getPanes()
|
||||
paneAttached = (e) -> callback($(e.target).view())
|
||||
@on 'pane:attached', paneAttached
|
||||
off: => @off 'pane:attached', paneAttached
|
||||
|
||||
getFocusedPane: ->
|
||||
@find('.pane:has(:focus)').view()
|
||||
|
||||
getActivePane: ->
|
||||
@viewForModel(@model.activePane)
|
||||
|
||||
getActivePaneItem: ->
|
||||
@model.activePaneItem
|
||||
|
||||
getActiveView: ->
|
||||
@getActivePane()?.activeView
|
||||
|
||||
paneForUri: (uri) ->
|
||||
for pane in @getPanes()
|
||||
view = pane.itemForUri(uri)
|
||||
return pane if view?
|
||||
null
|
||||
|
||||
focusNextPane: ->
|
||||
panes = @getPanes()
|
||||
if panes.length > 1
|
||||
currentIndex = panes.indexOf(@getFocusedPane())
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes[nextIndex].focus()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
focusPreviousPane: ->
|
||||
panes = @getPanes()
|
||||
if panes.length > 1
|
||||
currentIndex = panes.indexOf(@getFocusedPane())
|
||||
previousIndex = currentIndex - 1
|
||||
previousIndex = panes.length - 1 if previousIndex < 0
|
||||
panes[previousIndex].focus()
|
||||
true
|
||||
else
|
||||
false
|
||||
+47
-141
@@ -1,160 +1,66 @@
|
||||
{$, View} = require './space-pen-extensions'
|
||||
{Model} = require 'theorist'
|
||||
Serializable = require 'serializable'
|
||||
Pane = require './pane'
|
||||
telepath = require 'telepath'
|
||||
|
||||
# Private: Manages the list of panes within a {WorkspaceView}
|
||||
module.exports =
|
||||
class PaneContainer extends View
|
||||
class PaneContainer extends Model
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
### Internal ###
|
||||
@acceptsDocuments: true
|
||||
@properties
|
||||
root: null
|
||||
activePane: null
|
||||
|
||||
@deserialize: (state) ->
|
||||
container = new PaneContainer(state)
|
||||
container.removeEmptyPanes()
|
||||
container
|
||||
previousRoot: null
|
||||
|
||||
@content: ->
|
||||
@div class: 'panes'
|
||||
@behavior 'activePaneItem', ->
|
||||
@$activePane.switch (activePane) -> activePane?.$activeItem
|
||||
|
||||
initialize: (state) ->
|
||||
if state instanceof telepath.Document
|
||||
@state = state
|
||||
@setRoot(atom.deserializers.deserialize(@state.get('root')))
|
||||
else
|
||||
@state = atom.site.createDocument(deserializer: 'PaneContainer')
|
||||
constructor: (params) ->
|
||||
super
|
||||
@subscribe @$root, @onRootChanged
|
||||
@destroyEmptyPanes() if params?.destroyEmptyPanes
|
||||
|
||||
@subscribe @state, 'changed', ({newValues, siteId}) =>
|
||||
return if siteId is @state.siteId
|
||||
if newValues.hasOwnProperty('root')
|
||||
if rootState = newValues.root
|
||||
@setRoot(deserialize(rootState))
|
||||
else
|
||||
@setRoot(null)
|
||||
deserializeParams: (params) ->
|
||||
params.root = atom.deserializers.deserialize(params.root, container: this)
|
||||
params.destroyEmptyPanes = true
|
||||
params
|
||||
|
||||
@subscribe this, 'pane:attached', (event, pane) =>
|
||||
@triggerActiveItemChange() if @getActivePane() is pane
|
||||
serializeParams: (params) ->
|
||||
root: @root?.serialize()
|
||||
|
||||
@subscribe this, 'pane:removed', (event, pane) =>
|
||||
@triggerActiveItemChange() unless @getActivePane()?
|
||||
|
||||
@subscribe this, 'pane:became-active', =>
|
||||
@triggerActiveItemChange()
|
||||
|
||||
@subscribe this, 'pane:active-item-changed', (event, item) =>
|
||||
@triggerActiveItemChange() if @getActivePaneItem() is item
|
||||
|
||||
triggerActiveItemChange: ->
|
||||
@trigger 'pane-container:active-pane-item-changed', [@getActivePaneItem()]
|
||||
|
||||
serialize: ->
|
||||
state = @state.clone()
|
||||
state.set('root', @getRoot()?.serialize())
|
||||
state
|
||||
|
||||
getState: -> @state
|
||||
|
||||
### Public ###
|
||||
|
||||
focusNextPane: ->
|
||||
panes = @getPanes()
|
||||
if panes.length > 1
|
||||
currentIndex = panes.indexOf(@getFocusedPane())
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes[nextIndex].focus()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
focusPreviousPane: ->
|
||||
panes = @getPanes()
|
||||
if panes.length > 1
|
||||
currentIndex = panes.indexOf(@getFocusedPane())
|
||||
previousIndex = currentIndex - 1
|
||||
previousIndex = panes.length - 1 if previousIndex < 0
|
||||
panes[previousIndex].focus()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
makeNextPaneActive: ->
|
||||
panes = @getPanes()
|
||||
currentIndex = panes.indexOf(@getActivePane())
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes[nextIndex].makeActive()
|
||||
|
||||
itemDestroyed: (item) ->
|
||||
@trigger 'item-destroyed', item
|
||||
|
||||
getRoot: ->
|
||||
@children().first().view()
|
||||
|
||||
setRoot: (root, {suppressPaneItemChangeEvents}={}) ->
|
||||
@empty()
|
||||
if root?
|
||||
@append(root)
|
||||
root.makeActive?()
|
||||
@state.set(root: root?.getState())
|
||||
|
||||
removeChild: (child) ->
|
||||
throw new Error("Removing non-existant child") unless @getRoot() is child
|
||||
@setRoot(null)
|
||||
@trigger 'pane:removed', [child] if child instanceof Pane
|
||||
|
||||
saveAll: ->
|
||||
pane.saveItems() for pane in @getPanes()
|
||||
|
||||
confirmClose: ->
|
||||
saved = true
|
||||
for pane in @getPanes()
|
||||
for item in pane.getItems()
|
||||
if not pane.promptToSaveItem(item)
|
||||
saved = false
|
||||
break
|
||||
saved
|
||||
replaceChild: (oldChild, newChild) ->
|
||||
throw new Error("Replacing non-existent child") if oldChild isnt @root
|
||||
@root = newChild
|
||||
|
||||
getPanes: ->
|
||||
@find('.pane').views()
|
||||
@root?.getPanes() ? []
|
||||
|
||||
indexOfPane: (pane) ->
|
||||
@getPanes().indexOf(pane.view())
|
||||
activateNextPane: ->
|
||||
panes = @getPanes()
|
||||
if panes.length > 1
|
||||
currentIndex = panes.indexOf(@activePane)
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes[nextIndex].activate()
|
||||
else
|
||||
@activePane = null
|
||||
|
||||
paneAtIndex: (index) ->
|
||||
@getPanes()[index]
|
||||
onRootChanged: (root) =>
|
||||
@unsubscribe(@previousRoot) if @previousRoot?
|
||||
@previousRoot = root
|
||||
|
||||
eachPane: (callback) ->
|
||||
callback(pane) for pane in @getPanes()
|
||||
paneAttached = (e) -> callback($(e.target).view())
|
||||
@on 'pane:attached', paneAttached
|
||||
off: => @off 'pane:attached', paneAttached
|
||||
unless root?
|
||||
@activePane = null
|
||||
return
|
||||
|
||||
getFocusedPane: ->
|
||||
@find('.pane:has(:focus)').view()
|
||||
root.parent = this
|
||||
root.container = this
|
||||
|
||||
getActivePane: ->
|
||||
@find('.pane.active').view() ? @find('.pane:first').view()
|
||||
if root instanceof Pane
|
||||
@activePane ?= root
|
||||
@subscribe root, 'destroyed', =>
|
||||
@activePane = null
|
||||
@root = null
|
||||
|
||||
getActivePaneItem: ->
|
||||
@getActivePane()?.activeItem
|
||||
|
||||
getActiveView: ->
|
||||
@getActivePane()?.activeView
|
||||
|
||||
paneForUri: (uri) ->
|
||||
for pane in @getPanes()
|
||||
view = pane.itemForUri(uri)
|
||||
return pane if view?
|
||||
null
|
||||
|
||||
adjustPaneDimensions: ->
|
||||
if root = @getRoot()
|
||||
root.css(width: '100%', height: '100%', top: 0, left: 0)
|
||||
root.adjustDimensions()
|
||||
|
||||
removeEmptyPanes: ->
|
||||
for pane in @getPanes() when pane.getItems().length == 0
|
||||
pane.remove()
|
||||
|
||||
afterAttach: ->
|
||||
@adjustPaneDimensions()
|
||||
destroyEmptyPanes: ->
|
||||
pane.destroy() for pane in @getPanes() when pane.items.length is 0
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{$} = require './space-pen-extensions'
|
||||
_ = require 'underscore-plus'
|
||||
PaneAxisView = require './pane-axis-view'
|
||||
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class PaneRowView extends PaneAxisView
|
||||
@content: ->
|
||||
@div class: 'pane-row'
|
||||
|
||||
className: ->
|
||||
"PaneRow"
|
||||
@@ -1,34 +0,0 @@
|
||||
{$} = require './space-pen-extensions'
|
||||
_ = require 'underscore-plus'
|
||||
PaneAxis = require './pane-axis'
|
||||
|
||||
### Internal ###
|
||||
|
||||
module.exports =
|
||||
class PaneRow extends PaneAxis
|
||||
@content: ->
|
||||
@div class: 'row'
|
||||
|
||||
className: ->
|
||||
"PaneRow"
|
||||
|
||||
adjustDimensions: ->
|
||||
totalUnits = @horizontalGridUnits()
|
||||
unitsSoFar = 0
|
||||
for child in @children()
|
||||
child = $(child).view()
|
||||
childUnits = child.horizontalGridUnits()
|
||||
child.css
|
||||
width: "#{childUnits / totalUnits * 100}%"
|
||||
height: '100%'
|
||||
top: 0
|
||||
left: "#{unitsSoFar / totalUnits * 100}%"
|
||||
|
||||
child.adjustDimensions()
|
||||
unitsSoFar += childUnits
|
||||
|
||||
horizontalGridUnits: ->
|
||||
_.sum(@horizontalChildUnits())
|
||||
|
||||
verticalGridUnits: ->
|
||||
Math.max(@verticalChildUnits()...)
|
||||
@@ -0,0 +1,226 @@
|
||||
{$, View} = require './space-pen-extensions'
|
||||
Serializable = require 'serializable'
|
||||
Delegator = require 'delegato'
|
||||
|
||||
Pane = require './pane'
|
||||
|
||||
# Public: A container which can contains multiple items to be switched between.
|
||||
#
|
||||
# Items can be almost anything however most commonly they're {EditorView}s.
|
||||
#
|
||||
# Most packages won't need to use this class, unless you're interested in
|
||||
# building a package that deals with switching between panes or tiems.
|
||||
module.exports =
|
||||
class PaneView extends View
|
||||
Serializable.includeInto(this)
|
||||
Delegator.includeInto(this)
|
||||
|
||||
@version: 1
|
||||
|
||||
@deserialize: (state) ->
|
||||
new this(Pane.deserialize(state.model))
|
||||
|
||||
@content: (wrappedView) ->
|
||||
@div class: 'pane', tabindex: -1, =>
|
||||
@div class: 'item-views', outlet: 'itemViews'
|
||||
|
||||
@delegatesProperties 'items', 'activeItem', toProperty: 'model'
|
||||
@delegatesMethods 'getItems', 'activateNextItem', 'activatePreviousItem', 'getActiveItemIndex',
|
||||
'activateItemAtIndex', 'activateItem', 'addItem', 'itemAtIndex', 'moveItem', 'moveItemToPane',
|
||||
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
|
||||
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
|
||||
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
|
||||
'activate', toProperty: 'model'
|
||||
|
||||
previousActiveItem: null
|
||||
|
||||
# Private:
|
||||
initialize: (args...) ->
|
||||
if args[0] instanceof Pane
|
||||
@model = args[0]
|
||||
else
|
||||
@model = new Pane(items: args)
|
||||
@model._view = this
|
||||
|
||||
@onItemAdded(item) for item in @items
|
||||
@viewsByItem = new WeakMap()
|
||||
@handleEvents()
|
||||
|
||||
handleEvents: ->
|
||||
@subscribe @model, 'destroyed', => @remove()
|
||||
|
||||
@subscribe @model.$activeItem, @onActiveItemChanged
|
||||
@subscribe @model, 'item-added', @onItemAdded
|
||||
@subscribe @model, 'item-removed', @onItemRemoved
|
||||
@subscribe @model, 'item-moved', @onItemMoved
|
||||
@subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed
|
||||
@subscribe @model, 'item-destroyed', @onItemDestroyed
|
||||
@subscribe @model, 'activated', @onActivated
|
||||
@subscribe @model.$active, @onActiveStatusChanged
|
||||
|
||||
@subscribe this, 'focusin', => @model.focus()
|
||||
@subscribe this, 'focusout', => @model.blur()
|
||||
@subscribe this, 'focus', =>
|
||||
@activeView?.focus()
|
||||
false
|
||||
|
||||
@command 'pane:save-items', => @saveItems()
|
||||
@command 'pane:show-next-item', => @activateNextItem()
|
||||
@command 'pane:show-previous-item', => @activatePreviousItem()
|
||||
|
||||
@command 'pane:show-item-1', => @activateItemAtIndex(0)
|
||||
@command 'pane:show-item-2', => @activateItemAtIndex(1)
|
||||
@command 'pane:show-item-3', => @activateItemAtIndex(2)
|
||||
@command 'pane:show-item-4', => @activateItemAtIndex(3)
|
||||
@command 'pane:show-item-5', => @activateItemAtIndex(4)
|
||||
@command 'pane:show-item-6', => @activateItemAtIndex(5)
|
||||
@command 'pane:show-item-7', => @activateItemAtIndex(6)
|
||||
@command 'pane:show-item-8', => @activateItemAtIndex(7)
|
||||
@command 'pane:show-item-9', => @activateItemAtIndex(8)
|
||||
|
||||
@command 'pane:split-left', => @splitLeft(@copyActiveItem())
|
||||
@command 'pane:split-right', => @splitRight(@copyActiveItem())
|
||||
@command 'pane:split-up', => @splitUp(@copyActiveItem())
|
||||
@command 'pane:split-down', => @splitDown(@copyActiveItem())
|
||||
@command 'pane:close', => @destroyItems()
|
||||
@command 'pane:close-other-items', => @destroyInactiveItems()
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.model = Pane.deserialize(params.model)
|
||||
params
|
||||
|
||||
serializeParams: ->
|
||||
model: @model.serialize()
|
||||
|
||||
# Deprecated: Use ::destroyItem
|
||||
removeItem: (item) -> @destroyItem(item)
|
||||
|
||||
# Deprecated: Use ::activateItem
|
||||
showItem: (item) -> @activateItem(item)
|
||||
|
||||
# Deprecated: Use ::activateItemForUri
|
||||
showItemForUri: (item) -> @activateItemForUri(item)
|
||||
|
||||
# Deprecated: Use ::activateItemAtIndex
|
||||
showItemAtIndex: (index) -> @activateItemAtIndex(index)
|
||||
|
||||
# Deprecated: Use ::activateNextItem
|
||||
showNextItem: -> @activateNextItem()
|
||||
|
||||
# Deprecated: Use ::activatePreviousItem
|
||||
showPreviousItem: -> @activatePreviousItem()
|
||||
|
||||
# Private:
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if @model.focused and onDom
|
||||
|
||||
return if @attached
|
||||
@attached = true
|
||||
@trigger 'pane:attached', [this]
|
||||
|
||||
onActivated: =>
|
||||
@focus() unless @hasFocus()
|
||||
|
||||
onActiveStatusChanged: (active) =>
|
||||
if active
|
||||
@addClass('active')
|
||||
@trigger 'pane:became-active'
|
||||
else
|
||||
@removeClass('active')
|
||||
@trigger 'pane:became-inactive'
|
||||
|
||||
# Public: Returns the next pane, ordered by creation.
|
||||
getNextPane: ->
|
||||
panes = @getContainer()?.getPanes()
|
||||
return unless panes.length > 1
|
||||
nextIndex = (panes.indexOf(this) + 1) % panes.length
|
||||
panes[nextIndex]
|
||||
|
||||
getActivePaneItem: ->
|
||||
@activeItem
|
||||
|
||||
onActiveItemChanged: (item) =>
|
||||
@previousActiveItem?.off? 'title-changed', @activeItemTitleChanged
|
||||
@previousActiveItem = item
|
||||
|
||||
return unless item?
|
||||
|
||||
hasFocus = @hasFocus()
|
||||
item.on? 'title-changed', @activeItemTitleChanged
|
||||
view = @viewForItem(item)
|
||||
@itemViews.children().not(view).hide()
|
||||
@itemViews.append(view) unless view.parent().is(@itemViews)
|
||||
view.show() if @attached
|
||||
view.focus() if hasFocus
|
||||
|
||||
@activeView = view
|
||||
@trigger 'pane:active-item-changed', [item]
|
||||
|
||||
onItemAdded: (item, index) =>
|
||||
@trigger 'pane:item-added', [item, index]
|
||||
|
||||
onItemRemoved: (item, index, destroyed) =>
|
||||
if item instanceof $
|
||||
viewToRemove = item
|
||||
else if viewToRemove = @viewsByItem.get(item)
|
||||
@viewsByItem.delete(item)
|
||||
|
||||
if viewToRemove?
|
||||
viewToRemove.setModel?(null)
|
||||
if destroyed
|
||||
viewToRemove.remove()
|
||||
else
|
||||
viewToRemove.detach()
|
||||
|
||||
@trigger 'pane:item-removed', [item, index]
|
||||
|
||||
onItemMoved: (item, newIndex) =>
|
||||
@trigger 'pane:item-moved', [item, newIndex]
|
||||
|
||||
onBeforeItemDestroyed: (item) =>
|
||||
@unsubscribe(item) if typeof item.off is 'function'
|
||||
@trigger 'pane:before-item-destroyed', [item]
|
||||
|
||||
onItemDestroyed: (item) =>
|
||||
@getContainer()?.itemDestroyed(item)
|
||||
|
||||
# Private:
|
||||
activeItemTitleChanged: =>
|
||||
@trigger 'pane:active-item-title-changed'
|
||||
|
||||
# Private:
|
||||
viewForItem: (item) ->
|
||||
if item instanceof $
|
||||
item
|
||||
else if view = @viewsByItem.get(item)
|
||||
view
|
||||
else
|
||||
viewClass = item.getViewClass()
|
||||
view = new viewClass(item)
|
||||
@viewsByItem.set(item, view)
|
||||
view
|
||||
|
||||
# Private:
|
||||
viewForActiveItem: ->
|
||||
@viewForItem(@activeItem)
|
||||
|
||||
splitLeft: (items...) -> @model.splitLeft({items})._view
|
||||
|
||||
splitRight: (items...) -> @model.splitRight({items})._view
|
||||
|
||||
splitUp: (items...) -> @model.splitUp({items})._view
|
||||
|
||||
splitDown: (items...) -> @model.splitDown({items})._view
|
||||
|
||||
# Private:
|
||||
getContainer: ->
|
||||
@closest('.panes').view()
|
||||
|
||||
beforeRemove: ->
|
||||
@model.destroy() unless @model.isDestroyed()
|
||||
|
||||
# Private:
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData
|
||||
@unsubscribe()
|
||||
super
|
||||
+207
-340
@@ -1,228 +1,193 @@
|
||||
{find, compact, extend} = require 'underscore-plus'
|
||||
{dirname} = require 'path'
|
||||
{$, View} = require './space-pen-extensions'
|
||||
_ = require 'underscore-plus'
|
||||
telepath = require 'telepath'
|
||||
PaneRow = require './pane-row'
|
||||
PaneColumn = require './pane-column'
|
||||
{Model, Sequence} = require 'theorist'
|
||||
Serializable = require 'serializable'
|
||||
PaneAxis = require './pane-axis'
|
||||
PaneView = null
|
||||
|
||||
# Public: A container which can contains multiple items to be switched between.
|
||||
#
|
||||
# Items can be almost anything however most commonly they're {EditorView}s.
|
||||
#
|
||||
# Most packages won't need to use this class, unless you're interested in
|
||||
# building a package that deals with switching between panes or tiems.
|
||||
# Public: A container for multiple items, one of which is *active* at a given
|
||||
# time. With the default packages, a tab is displayed for each item and the
|
||||
# active item's view is displayed.
|
||||
module.exports =
|
||||
class Pane extends View
|
||||
class Pane extends Model
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
@acceptsDocuments: true
|
||||
@properties
|
||||
container: null
|
||||
activeItem: null
|
||||
focused: false
|
||||
|
||||
@content: (wrappedView) ->
|
||||
@div class: 'pane', tabindex: -1, =>
|
||||
@div class: 'item-views', outlet: 'itemViews'
|
||||
|
||||
@deserialize: (state) ->
|
||||
pane = new Pane(state)
|
||||
pane.focusOnAttach = true if state.get('focused')
|
||||
pane
|
||||
|
||||
activeItem: null
|
||||
items: null
|
||||
viewsByItem: null # Views without a setModel() method are stored here
|
||||
# Public: Only one pane is considered *active* at a time. A pane is activated
|
||||
# when it is focused, and when focus returns to the pane container after
|
||||
# moving to another element such as a panel, it returns to the active pane.
|
||||
@behavior 'active', ->
|
||||
@$container
|
||||
.switch((container) -> container?.$activePane)
|
||||
.map((activePane) => activePane is this)
|
||||
.distinctUntilChanged()
|
||||
|
||||
# Private:
|
||||
initialize: (args...) ->
|
||||
@items = []
|
||||
if args[0] instanceof telepath.Document
|
||||
@state = args[0]
|
||||
@items = _.compact @state.get('items').map (item) ->
|
||||
item = atom.deserializers.deserialize(item)
|
||||
item?.created?()
|
||||
item
|
||||
else
|
||||
@items = args
|
||||
@state = atom.site.createDocument
|
||||
deserializer: 'Pane'
|
||||
items: @items.map (item) -> item.getState?() ? item.serialize()
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
||||
@handleItemEvents(item) for item in @items
|
||||
@items = Sequence.fromArray(params?.items ? [])
|
||||
@activeItem ?= @items[0]
|
||||
|
||||
@subscribe @state.get('items'), 'changed', ({index, removedValues, insertedValues, siteId}) =>
|
||||
return if siteId is @state.siteId
|
||||
for itemState in removedValues
|
||||
@removeItemAtIndex(index, updateState: false)
|
||||
for itemState, i in insertedValues
|
||||
@addItem(atom.deserializers.deserialize(itemState), index + i, updateState: false)
|
||||
@subscribe @items.onEach (item) =>
|
||||
if typeof item.on is 'function'
|
||||
@subscribe item, 'destroyed', => @removeItem(item)
|
||||
|
||||
@subscribe @state, 'changed', ({newValues, siteId}) =>
|
||||
return if siteId is @state.siteId
|
||||
if newValues.activeItemUri
|
||||
@showItemForUri(newValues.activeItemUri)
|
||||
@subscribe @items.onRemoval (item, index) =>
|
||||
@unsubscribe item if typeof item.on is 'function'
|
||||
|
||||
@viewsByItem = new WeakMap()
|
||||
activeItemUri = @state.get('activeItemUri')
|
||||
unless activeItemUri? and @showItemForUri(activeItemUri)
|
||||
@showItem(@items[0]) if @items.length > 0
|
||||
@activate() if params?.active
|
||||
|
||||
@command 'pane:save-items', @saveItems
|
||||
@command 'pane:show-next-item', @showNextItem
|
||||
@command 'pane:show-previous-item', @showPreviousItem
|
||||
# Private: Called by the Serializable mixin during serialization.
|
||||
serializeParams: ->
|
||||
items: compact(@items.map((item) -> item.serialize?()))
|
||||
activeItemUri: @activeItem?.getUri?()
|
||||
focused: @focused
|
||||
active: @active
|
||||
|
||||
@command 'pane:show-item-1', => @showItemAtIndex(0)
|
||||
@command 'pane:show-item-2', => @showItemAtIndex(1)
|
||||
@command 'pane:show-item-3', => @showItemAtIndex(2)
|
||||
@command 'pane:show-item-4', => @showItemAtIndex(3)
|
||||
@command 'pane:show-item-5', => @showItemAtIndex(4)
|
||||
@command 'pane:show-item-6', => @showItemAtIndex(5)
|
||||
@command 'pane:show-item-7', => @showItemAtIndex(6)
|
||||
@command 'pane:show-item-8', => @showItemAtIndex(7)
|
||||
@command 'pane:show-item-9', => @showItemAtIndex(8)
|
||||
# Private: Called by the Serializable mixin during deserialization.
|
||||
deserializeParams: (params) ->
|
||||
{items, activeItemUri} = params
|
||||
params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
|
||||
params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri
|
||||
params
|
||||
|
||||
@command 'pane:split-left', => @splitLeft(@copyActiveItem())
|
||||
@command 'pane:split-right', => @splitRight(@copyActiveItem())
|
||||
@command 'pane:split-up', => @splitUp(@copyActiveItem())
|
||||
@command 'pane:split-down', => @splitDown(@copyActiveItem())
|
||||
@command 'pane:close', => @destroyItems()
|
||||
@command 'pane:close-other-items', => @destroyInactiveItems()
|
||||
@on 'focus', => @activeView?.focus(); false
|
||||
@on 'focusin', => @makeActive()
|
||||
# Private: Called by the view layer to construct a view for this model.
|
||||
getViewClass: -> PaneView ?= require './pane-view'
|
||||
|
||||
isActive: -> @active
|
||||
|
||||
# Private: Called by the view layer to indicate that the pane has gained focus.
|
||||
focus: ->
|
||||
@focused = true
|
||||
@activate() unless @isActive()
|
||||
|
||||
# Private: Called by the view layer to indicate that the pane has lost focus.
|
||||
blur: ->
|
||||
@focused = false
|
||||
true # if this is called from an event handler, don't cancel it
|
||||
|
||||
# Public: Makes this pane the *active* pane, causing it to gain focus
|
||||
# immediately.
|
||||
activate: ->
|
||||
@container?.activePane = this
|
||||
@emit 'activated'
|
||||
|
||||
# Private:
|
||||
afterAttach: (onDom) ->
|
||||
if @focusOnAttach and onDom
|
||||
@focusOnAttach = null
|
||||
@focus()
|
||||
getPanes: -> [this]
|
||||
|
||||
return if @attached
|
||||
@attached = true
|
||||
@trigger 'pane:attached', [this]
|
||||
|
||||
# Public: Focus this pane.
|
||||
makeActive: ->
|
||||
wasActive = @isActive()
|
||||
for pane in @getContainer().getPanes() when pane isnt this
|
||||
pane.makeInactive()
|
||||
@addClass('active')
|
||||
@trigger 'pane:became-active' unless wasActive
|
||||
|
||||
# Public: Unfocus this pane.
|
||||
makeInactive: ->
|
||||
wasActive = @isActive()
|
||||
@removeClass('active')
|
||||
@trigger 'pane:became-inactive' if wasActive
|
||||
|
||||
# Public: Returns whether this pane is currently focused.
|
||||
isActive: ->
|
||||
@getContainer()?.getActivePane() == this
|
||||
|
||||
# Public: Returns the next pane, ordered by creation.
|
||||
getNextPane: ->
|
||||
panes = @getContainer()?.getPanes()
|
||||
return unless panes.length > 1
|
||||
nextIndex = (panes.indexOf(this) + 1) % panes.length
|
||||
panes[nextIndex]
|
||||
|
||||
# Public: Returns all contained views.
|
||||
# Public:
|
||||
getItems: ->
|
||||
new Array(@items...)
|
||||
|
||||
# Public: Switches to the next contained item.
|
||||
showNextItem: =>
|
||||
index = @getActiveItemIndex()
|
||||
if index < @items.length - 1
|
||||
@showItemAtIndex(index + 1)
|
||||
else
|
||||
@showItemAtIndex(0)
|
||||
|
||||
# Public: Switches to the previous contained item.
|
||||
showPreviousItem: =>
|
||||
index = @getActiveItemIndex()
|
||||
if index > 0
|
||||
@showItemAtIndex(index - 1)
|
||||
else
|
||||
@showItemAtIndex(@items.length - 1)
|
||||
|
||||
getActivePaneItem: ->
|
||||
@activeItem
|
||||
|
||||
# Public: Returns the index of the currently active item.
|
||||
getActiveItemIndex: ->
|
||||
@items.indexOf(@activeItem)
|
||||
|
||||
# Public: Switch to the item associated with the given index.
|
||||
showItemAtIndex: (index) ->
|
||||
@showItem(@itemAtIndex(index))
|
||||
@items.slice()
|
||||
|
||||
# Public: Returns the item at the specified index.
|
||||
itemAtIndex: (index) ->
|
||||
@items[index]
|
||||
|
||||
# Public: Focuses the given item.
|
||||
showItem: (item) ->
|
||||
return if !item? or item is @activeItem
|
||||
# Public: Makes the next item active.
|
||||
activateNextItem: ->
|
||||
index = @getActiveItemIndex()
|
||||
if index < @items.length - 1
|
||||
@activateItemAtIndex(index + 1)
|
||||
else
|
||||
@activateItemAtIndex(0)
|
||||
|
||||
if @activeItem
|
||||
@activeItem.off? 'title-changed', @activeItemTitleChanged
|
||||
# Public: Makes the previous item active.
|
||||
activatePreviousItem: ->
|
||||
index = @getActiveItemIndex()
|
||||
if index > 0
|
||||
@activateItemAtIndex(index - 1)
|
||||
else
|
||||
@activateItemAtIndex(@items.length - 1)
|
||||
|
||||
isFocused = @is(':has(:focus)')
|
||||
@addItem(item)
|
||||
item.on? 'title-changed', @activeItemTitleChanged
|
||||
view = @viewForItem(item)
|
||||
@itemViews.children().not(view).hide()
|
||||
@itemViews.append(view) unless view.parent().is(@itemViews)
|
||||
view.show() if @attached
|
||||
view.focus() if isFocused
|
||||
@activeItem = item
|
||||
@activeView = view
|
||||
@trigger 'pane:active-item-changed', [item]
|
||||
# Public: Returns the index of the current active item.
|
||||
getActiveItemIndex: ->
|
||||
@items.indexOf(@activeItem)
|
||||
|
||||
@state.set('activeItemUri', item.getUri?())
|
||||
# Public: Makes the item at the given index active.
|
||||
activateItemAtIndex: (index) ->
|
||||
@activateItem(@itemAtIndex(index))
|
||||
|
||||
# Private:
|
||||
activeItemTitleChanged: =>
|
||||
@trigger 'pane:active-item-title-changed'
|
||||
# Public: Makes the given item active, adding the item if necessary.
|
||||
activateItem: (item) ->
|
||||
if item?
|
||||
@addItem(item)
|
||||
@activeItem = item
|
||||
|
||||
# Public: Add an additional item at the specified index.
|
||||
addItem: (item, index=@getActiveItemIndex()+1, options={}) ->
|
||||
return if _.include(@items, item)
|
||||
# Public: Adds the item to the pane.
|
||||
#
|
||||
# * item:
|
||||
# The item to add. It can be a model with an associated view or a view.
|
||||
# * index:
|
||||
# An optional index at which to add the item. If omitted, the item is
|
||||
# added to the end.
|
||||
#
|
||||
# Returns the added item
|
||||
addItem: (item, index=@getActiveItemIndex() + 1) ->
|
||||
return if item in @items
|
||||
|
||||
@state.get('items').splice(index, 0, item.getState?() ? item.serialize()) if options.updateState ? true
|
||||
@items.splice(index, 0, item)
|
||||
@trigger 'pane:item-added', [item, index]
|
||||
@handleItemEvents(item)
|
||||
@emit 'item-added', item, index
|
||||
item
|
||||
|
||||
handleItemEvents: (item) ->
|
||||
if _.isFunction(item.on)
|
||||
@subscribe item, 'destroyed', =>
|
||||
@destroyItem(item) if @state.isAlive()
|
||||
# Private:
|
||||
removeItem: (item, destroying) ->
|
||||
index = @items.indexOf(item)
|
||||
return if index is -1
|
||||
@activateNextItem() if item is @activeItem and @items.length > 1
|
||||
@items.splice(index, 1)
|
||||
@emit 'item-removed', item, index, destroying
|
||||
@destroy() if @items.length is 0
|
||||
|
||||
# Public: Remove the currently active item.
|
||||
destroyActiveItem: =>
|
||||
# Public: Moves the given item to the specified index.
|
||||
moveItem: (item, newIndex) ->
|
||||
oldIndex = @items.indexOf(item)
|
||||
@items.splice(oldIndex, 1)
|
||||
@items.splice(newIndex, 0, item)
|
||||
@emit 'item-moved', item, newIndex
|
||||
|
||||
# Public: Moves the given item to the given index at another pane.
|
||||
moveItemToPane: (item, pane, index) ->
|
||||
pane.addItem(item, index)
|
||||
@removeItem(item)
|
||||
|
||||
# Public: Destroys the currently active item and make the next item active.
|
||||
destroyActiveItem: ->
|
||||
@destroyItem(@activeItem)
|
||||
false
|
||||
|
||||
# Public: Remove the specified item.
|
||||
# Public: Destroys the given item. If it is the active item, activate the next
|
||||
# one. If this is the last item, also destroys the pane.
|
||||
destroyItem: (item) ->
|
||||
@unsubscribe(item) if _.isFunction(item.off)
|
||||
@trigger 'pane:before-item-destroyed', [item]
|
||||
|
||||
@emit 'before-item-destroyed', item
|
||||
if @promptToSaveItem(item)
|
||||
@getContainer()?.itemDestroyed(item)
|
||||
@removeItem(item)
|
||||
@emit 'item-destroyed', item
|
||||
@removeItem(item, true)
|
||||
item.destroy?()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
# Public: Remove and delete all items.
|
||||
# Public: Destroys all items and destroys the pane.
|
||||
destroyItems: ->
|
||||
@destroyItem(item) for item in @getItems()
|
||||
|
||||
# Public: Remove and delete all but the currently focused item.
|
||||
# Public: Destroys all items but the active one.
|
||||
destroyInactiveItems: ->
|
||||
@destroyItem(item) for item in @getItems() when item isnt @activeItem
|
||||
|
||||
# Public: Prompt the user to save the given item.
|
||||
# Private: Called by model superclass.
|
||||
destroyed: ->
|
||||
@container.activateNextPane() if @isActive()
|
||||
item.destroy?() for item in @items.slice()
|
||||
|
||||
# Public: Prompts the user to save the given item if it can be saved and is
|
||||
# currently unsaved.
|
||||
promptToSaveItem: (item) ->
|
||||
return true unless item.shouldPromptToSave?()
|
||||
|
||||
@@ -237,15 +202,18 @@ class Pane extends View
|
||||
when 1 then false
|
||||
when 2 then true
|
||||
|
||||
# Public: Saves the currently focused item.
|
||||
saveActiveItem: =>
|
||||
# Public: Saves the active item.
|
||||
saveActiveItem: ->
|
||||
@saveItem(@activeItem)
|
||||
|
||||
# Public: Save and prompt for path for the currently focused item.
|
||||
saveActiveItemAs: =>
|
||||
# Public: Saves the active item at a prompted-for location.
|
||||
saveActiveItemAs: ->
|
||||
@saveItemAs(@activeItem)
|
||||
|
||||
# Public: Saves the specified item and call the next action when complete.
|
||||
# Public: Saves the specified item.
|
||||
#
|
||||
# * item: The item to save.
|
||||
# * nextAction: An optional function which will be called after the item is saved.
|
||||
saveItem: (item, nextAction) ->
|
||||
if item.getUri?()
|
||||
item.save?()
|
||||
@@ -253,8 +221,10 @@ class Pane extends View
|
||||
else
|
||||
@saveItemAs(item, nextAction)
|
||||
|
||||
# Public: Prompts for path and then saves the specified item. Upon completion
|
||||
# it also calls the next action.
|
||||
# Public: Saves the given item at a prompted-for location.
|
||||
#
|
||||
# * item: The item to save.
|
||||
# * nextAction: An optional function which will be called after the item is saved.
|
||||
saveItemAs: (item, nextAction) ->
|
||||
return unless item.saveAs?
|
||||
|
||||
@@ -265,176 +235,73 @@ class Pane extends View
|
||||
item.saveAs(path)
|
||||
nextAction?()
|
||||
|
||||
# Public: Saves all items in this pane.
|
||||
saveItems: =>
|
||||
# Public: Saves all items.
|
||||
saveItems: ->
|
||||
@saveItem(item) for item in @getItems()
|
||||
|
||||
# Public:
|
||||
removeItem: (item, options) ->
|
||||
index = @items.indexOf(item)
|
||||
@removeItemAtIndex(index, options) if index >= 0
|
||||
|
||||
# Public: Just remove the item at the given index.
|
||||
removeItemAtIndex: (index, options={}) ->
|
||||
item = @items[index]
|
||||
@activeItem.off? 'title-changed', @activeItemTitleChanged if item is @activeItem
|
||||
@showNextItem() if item is @activeItem and @items.length > 1
|
||||
_.remove(@items, item)
|
||||
@state.get('items').splice(index, 1) if options.updateState ? true
|
||||
@cleanupItemView(item)
|
||||
@trigger 'pane:item-removed', [item, index]
|
||||
|
||||
# Public: Moves the given item to a the new index.
|
||||
moveItem: (item, newIndex) ->
|
||||
oldIndex = @items.indexOf(item)
|
||||
@items.splice(oldIndex, 1)
|
||||
@items.splice(newIndex, 0, item)
|
||||
@state.get('items').insert(newIndex, item.getState?() ? item.serialize())
|
||||
@trigger 'pane:item-moved', [item, newIndex]
|
||||
|
||||
# Public: Moves the given item to another pane.
|
||||
moveItemToPane: (item, pane, index) ->
|
||||
@isMovingItem = true
|
||||
pane.addItem(item, index)
|
||||
@removeItem(item, updateState: false)
|
||||
@isMovingItem = false
|
||||
|
||||
# Public: Finds the first item that matches the given uri.
|
||||
# Public: Returns the first item that matches the given URI or undefined if
|
||||
# none exists.
|
||||
itemForUri: (uri) ->
|
||||
_.detect @items, (item) -> item.getUri?() is uri
|
||||
find @items, (item) -> item.getUri?() is uri
|
||||
|
||||
# Public: Focuses the first item that matches the given uri.
|
||||
showItemForUri: (uri) ->
|
||||
# Public: Activates the first item that matches the given URI. Returns a
|
||||
# boolean indicating whether a matching item was found.
|
||||
activateItemForUri: (uri) ->
|
||||
if item = @itemForUri(uri)
|
||||
@showItem(item)
|
||||
@activateItem(item)
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
# Private:
|
||||
cleanupItemView: (item) ->
|
||||
if item instanceof $
|
||||
viewToRemove = item
|
||||
else if viewToRemove = @viewsByItem.get(item)
|
||||
@viewsByItem.delete(item)
|
||||
|
||||
if @items.length > 0
|
||||
if @isMovingItem and item is viewToRemove
|
||||
viewToRemove?.detach()
|
||||
else if @isMovingItem and viewToRemove?.setModel
|
||||
viewToRemove.setModel(null) # dont want to destroy the model, so set to null
|
||||
viewToRemove.remove()
|
||||
else
|
||||
viewToRemove?.remove()
|
||||
else
|
||||
if @isMovingItem and item is viewToRemove
|
||||
viewToRemove?.detach()
|
||||
else if @isMovingItem and viewToRemove?.setModel
|
||||
viewToRemove.setModel(null) # dont want to destroy the model, so set to null
|
||||
|
||||
@parent().view().removeChild(this, updateState: false)
|
||||
|
||||
# Private:
|
||||
viewForItem: (item) ->
|
||||
if item instanceof $
|
||||
item
|
||||
else if view = @viewsByItem.get(item)
|
||||
view
|
||||
else
|
||||
viewClass = item.getViewClass()
|
||||
view = new viewClass(item)
|
||||
@viewsByItem.set(item, view)
|
||||
view
|
||||
|
||||
# Private:
|
||||
viewForActiveItem: ->
|
||||
@viewForItem(@activeItem)
|
||||
|
||||
# Private:
|
||||
serialize: ->
|
||||
state = @state.clone()
|
||||
state.set('items', item.serialize() for item, index in @items)
|
||||
state.set('focused', @is(':has(:focus)'))
|
||||
state
|
||||
|
||||
# Private:
|
||||
getState: -> @state
|
||||
|
||||
# Private:
|
||||
adjustDimensions: -> # do nothing
|
||||
|
||||
# Private:
|
||||
horizontalGridUnits: -> 1
|
||||
|
||||
# Private:
|
||||
verticalGridUnits: -> 1
|
||||
|
||||
# Public: Creates a new pane above with a copy of the currently focused item.
|
||||
splitUp: (items...) ->
|
||||
@split(items, 'column', 'before')
|
||||
|
||||
# Public: Creates a new pane below with a copy of the currently focused item.
|
||||
splitDown: (items...) ->
|
||||
@split(items, 'column', 'after')
|
||||
|
||||
# Public: Creates a new pane left with a copy of the currently focused item.
|
||||
splitLeft: (items...) ->
|
||||
@split(items, 'row', 'before')
|
||||
|
||||
# Public: Creates a new pane right with a copy of the currently focused item.
|
||||
splitRight: (items...) ->
|
||||
@split(items, 'row', 'after')
|
||||
|
||||
# Private:
|
||||
split: (items, axis, side) ->
|
||||
PaneContainer = require './pane-container'
|
||||
|
||||
parent = @parent().view()
|
||||
unless parent.hasClass(axis)
|
||||
axis = @buildPaneAxis(axis)
|
||||
if parent instanceof PaneContainer
|
||||
@detach()
|
||||
axis.addChild(this)
|
||||
parent.setRoot(axis)
|
||||
else
|
||||
parent.insertChildBefore(this, axis)
|
||||
axis.addChild(this)
|
||||
parent = axis
|
||||
|
||||
newPane = new Pane(items...)
|
||||
|
||||
switch side
|
||||
when 'before' then parent.insertChildBefore(this, newPane)
|
||||
when 'after' then parent.insertChildAfter(this, newPane)
|
||||
@getContainer().adjustPaneDimensions()
|
||||
newPane.makeActive()
|
||||
newPane.focus()
|
||||
newPane
|
||||
|
||||
# Private:
|
||||
buildPaneAxis: (axis) ->
|
||||
switch axis
|
||||
when 'row' then new PaneRow()
|
||||
when 'column' then new PaneColumn()
|
||||
|
||||
# Private:
|
||||
getContainer: ->
|
||||
@closest('.panes').view()
|
||||
|
||||
# Private:
|
||||
copyActiveItem: ->
|
||||
@activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
|
||||
|
||||
# Private:
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData
|
||||
@parent().view().removeChild(this)
|
||||
# Public: Creates a new pane to the left of the receiver.
|
||||
#
|
||||
# * params:
|
||||
# + items: An optional array of items with which to construct the new pane.
|
||||
#
|
||||
# Returns the new {Pane}.
|
||||
splitLeft: (params) ->
|
||||
@split('horizontal', 'before', params)
|
||||
|
||||
# Public: Creates a new pane to the right of the receiver.
|
||||
#
|
||||
# * params:
|
||||
# + items: An optional array of items with which to construct the new pane.
|
||||
#
|
||||
# Returns the new {Pane}.
|
||||
splitRight: (params) ->
|
||||
@split('horizontal', 'after', params)
|
||||
|
||||
# Public: Creates a new pane above the receiver.
|
||||
#
|
||||
# * params:
|
||||
# + items: An optional array of items with which to construct the new pane.
|
||||
#
|
||||
# Returns the new {Pane}.
|
||||
splitUp: (params) ->
|
||||
@split('vertical', 'before', params)
|
||||
|
||||
# Public: Creates a new pane below the receiver.
|
||||
#
|
||||
# * params:
|
||||
# + items: An optional array of items with which to construct the new pane.
|
||||
#
|
||||
# Returns the new {Pane}.
|
||||
splitDown: (params) ->
|
||||
@split('vertical', 'after', params)
|
||||
|
||||
# Private:
|
||||
beforeRemove: ->
|
||||
if @is(':has(:focus)')
|
||||
@getContainer().focusNextPane() or atom.workspaceView?.focus()
|
||||
else if @isActive()
|
||||
@getContainer().makeNextPaneActive()
|
||||
split: (orientation, side, params) ->
|
||||
if @parent.orientation isnt orientation
|
||||
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
|
||||
|
||||
item.destroy?() for item in @getItems()
|
||||
newPane = new @constructor(extend({focused: true}, params))
|
||||
switch side
|
||||
when 'before' then @parent.insertChildBefore(this, newPane)
|
||||
when 'after' then @parent.insertChildAfter(this, newPane)
|
||||
|
||||
newPane.activate()
|
||||
newPane
|
||||
|
||||
+32
-22
@@ -4,7 +4,9 @@ url = require 'url'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
Q = require 'q'
|
||||
{Model} = require 'telepath'
|
||||
{Model} = require 'theorist'
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
Serializable = require 'serializable'
|
||||
|
||||
TextBuffer = require './text-buffer'
|
||||
Editor = require './editor'
|
||||
@@ -18,10 +20,8 @@ Git = require './git'
|
||||
# of directories and files that you can operate on.
|
||||
module.exports =
|
||||
class Project extends Model
|
||||
|
||||
@properties
|
||||
buffers: []
|
||||
path: null
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
# Public: Find the local path for the given repository URL.
|
||||
@pathForRepositoryUrl: (repoUrl) ->
|
||||
@@ -29,18 +29,23 @@ class Project extends Model
|
||||
repoName = repoName.replace(/\.git$/, '')
|
||||
path.join(atom.config.get('core.projectHome'), repoName)
|
||||
|
||||
# Private: Called by telepath.
|
||||
created: ->
|
||||
for buffer in @buffers.getValues()
|
||||
buffer.once 'destroyed', (buffer) => @removeBuffer(buffer) if @isAlive()
|
||||
constructor: ({path, @buffers}={}) ->
|
||||
@buffers ?= []
|
||||
for buffer in @buffers
|
||||
do (buffer) =>
|
||||
buffer.once 'destroyed', => @removeBuffer(buffer)
|
||||
|
||||
@openers = []
|
||||
@editors = []
|
||||
@setPath(@path)
|
||||
@setPath(path)
|
||||
|
||||
# Private: Called by telepath.
|
||||
beforePersistence: ->
|
||||
@destroyUnretainedBuffers()
|
||||
serializeParams: ->
|
||||
path: @path
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
|
||||
params
|
||||
|
||||
# Public: Register an opener for project files.
|
||||
#
|
||||
@@ -177,7 +182,7 @@ class Project extends Model
|
||||
#
|
||||
# Returns an {Array} of {TextBuffer}s.
|
||||
getBuffers: ->
|
||||
new Array(@buffers.getValues()...)
|
||||
@buffers.slice()
|
||||
|
||||
# Private: Is the buffer for the given path modified?
|
||||
isPathModified: (filePath) ->
|
||||
@@ -185,7 +190,7 @@ class Project extends Model
|
||||
|
||||
# Private:
|
||||
findBufferForPath: (filePath) ->
|
||||
_.find @buffers.getValues(), (buffer) -> buffer.getPath() == filePath
|
||||
_.find @buffers, (buffer) -> buffer.getPath() == filePath
|
||||
|
||||
# Private: Only to be used in specs
|
||||
bufferForPathSync: (filePath) ->
|
||||
@@ -233,11 +238,12 @@ class Project extends Model
|
||||
# Private:
|
||||
addBuffer: (buffer, options={}) ->
|
||||
@addBufferAtIndex(buffer, @buffers.length, options)
|
||||
buffer.once 'destroyed', => @removeBuffer(buffer)
|
||||
|
||||
# Private:
|
||||
addBufferAtIndex: (buffer, index, options={}) ->
|
||||
buffer = @buffers.insert(index, buffer)
|
||||
buffer.once 'destroyed', => @removeBuffer(buffer) if @isAlive()
|
||||
@buffers.splice(index, 0, buffer)
|
||||
buffer.once 'destroyed', => @removeBuffer(buffer)
|
||||
@emit 'buffer-created', buffer
|
||||
buffer
|
||||
|
||||
@@ -285,13 +291,17 @@ class Project extends Model
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
|
||||
for buffer in @buffers.getValues() when buffer.isModified()
|
||||
for buffer in @getBuffers() when buffer.isModified()
|
||||
filePath = buffer.getPath()
|
||||
matches = []
|
||||
buffer.scan regex, (match) -> matches.push match
|
||||
iterator {filePath, matches} if matches.length > 0
|
||||
|
||||
deferred.promise
|
||||
promise = deferred.promise
|
||||
promise.cancel = ->
|
||||
task.terminate()
|
||||
deferred.resolve('cancelled')
|
||||
promise
|
||||
|
||||
# Public: Performs a replace across all the specified files in the project.
|
||||
#
|
||||
@@ -302,7 +312,7 @@ class Project extends Model
|
||||
replace: (regex, replacementText, filePaths, iterator) ->
|
||||
deferred = Q.defer()
|
||||
|
||||
openPaths = (buffer.getPath() for buffer in @buffers.getValues())
|
||||
openPaths = (buffer.getPath() for buffer in @getBuffers())
|
||||
outOfProcessPaths = _.difference(filePaths, openPaths)
|
||||
|
||||
inProcessFinished = !openPaths.length
|
||||
@@ -320,7 +330,7 @@ class Project extends Model
|
||||
|
||||
task.on 'replace:path-replaced', iterator
|
||||
|
||||
for buffer in @buffers.getValues()
|
||||
for buffer in @getBuffers()
|
||||
continue unless buffer.getPath() in filePaths
|
||||
replacements = buffer.replace(regex, replacementText, iterator)
|
||||
iterator({filePath: buffer.getPath(), replacements}) if replacements
|
||||
@@ -332,7 +342,7 @@ class Project extends Model
|
||||
|
||||
# Private:
|
||||
buildEditorForBuffer: (buffer, editorOptions) ->
|
||||
editor = @create(new Editor(_.extend({buffer}, editorOptions)))
|
||||
editor = new Editor(_.extend({buffer}, editorOptions))
|
||||
@addEditor(editor)
|
||||
editor
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{Point, Range} = require 'telepath'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
{View, $$} = require './space-pen-extensions'
|
||||
|
||||
# Internal:
|
||||
@@ -18,9 +18,6 @@ class SelectionView extends View
|
||||
@needsRemoval = true
|
||||
@editorView.requestDisplayUpdate()
|
||||
|
||||
if @selection.marker.isRemote()
|
||||
@addClass("site-#{@selection.marker.getOriginSiteId()}")
|
||||
|
||||
updateDisplay: ->
|
||||
@clearRegions()
|
||||
range = @getScreenRange()
|
||||
|
||||
+4
-12
@@ -1,4 +1,4 @@
|
||||
{Range} = require 'telepath'
|
||||
{Range} = require 'text-buffer'
|
||||
{Emitter} = require 'emissary'
|
||||
{pick} = require 'underscore-plus'
|
||||
|
||||
@@ -21,7 +21,7 @@ class Selection
|
||||
@marker.on 'destroyed', =>
|
||||
@destroyed = true
|
||||
@editor.removeSelection(this)
|
||||
@emit 'destroyed' unless @editor.destroyed
|
||||
@emit 'destroyed' unless @editor.isDestroyed()
|
||||
|
||||
# Private:
|
||||
destroy: ->
|
||||
@@ -132,7 +132,7 @@ class Selection
|
||||
# The line Number to select (default: the row of the cursor)
|
||||
selectLine: (row=@cursor.getBufferPosition().row) ->
|
||||
range = @editor.bufferRangeForBufferRow(row, includeNewline: true)
|
||||
@setBufferRange(range)
|
||||
@setBufferRange(@getBufferRange().union(range))
|
||||
@linewise = true
|
||||
@wordwise = false
|
||||
@initialScreenRange = @getScreenRange()
|
||||
@@ -216,7 +216,7 @@ class Selection
|
||||
# Public: Selects all the text from the current cursor position to the end of
|
||||
# the line.
|
||||
selectToEndOfLine: ->
|
||||
@modifySelection => @cursor.moveToEndOfLine()
|
||||
@modifySelection => @cursor.moveToEndOfScreenLine()
|
||||
|
||||
# Public: Selects all the text from the current cursor position to the
|
||||
# beginning of the word.
|
||||
@@ -601,14 +601,6 @@ class Selection
|
||||
compare: (otherSelection) ->
|
||||
@getBufferRange().compare(otherSelection.getBufferRange())
|
||||
|
||||
# Public: Returns true if it was locally created.
|
||||
isLocal: ->
|
||||
@marker.isLocal()
|
||||
|
||||
# Public: Returns true if it was created remotely.
|
||||
isRemote: ->
|
||||
@marker.isRemote()
|
||||
|
||||
# Private:
|
||||
screenRangeChanged: ->
|
||||
screenRange = @getScreenRange()
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Private: TODO remove once telepath upgrades are complete.
|
||||
module.exports =
|
||||
class SiteShim
|
||||
constructor: (document) ->
|
||||
@setRootDocument(document)
|
||||
|
||||
setRootDocument: (@document) ->
|
||||
@id = @document.siteId
|
||||
|
||||
createDocument: (values) ->
|
||||
@document.create({values})
|
||||
+13
-51
@@ -1,15 +1,15 @@
|
||||
_ = require 'underscore-plus'
|
||||
{specificity} = require 'clear-cut'
|
||||
{Subscriber} = require 'emissary'
|
||||
{GrammarRegistry, ScopeSelector} = require 'first-mate'
|
||||
|
||||
{$, $$} = require './space-pen-extensions'
|
||||
{Emitter} = require 'emissary'
|
||||
NullGrammar = require './null-grammar'
|
||||
TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
|
||||
### Internal ###
|
||||
Token = require './token'
|
||||
|
||||
### Public ###
|
||||
module.exports =
|
||||
class Syntax
|
||||
Emitter.includeInto(this)
|
||||
class Syntax extends GrammarRegistry
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
atom.deserializers.add(this)
|
||||
|
||||
@@ -19,53 +19,15 @@ class Syntax
|
||||
syntax
|
||||
|
||||
constructor: ->
|
||||
@nullGrammar = new NullGrammar
|
||||
@grammars = [@nullGrammar]
|
||||
@grammarsByScopeName = {}
|
||||
@injectionGrammars = []
|
||||
@grammarOverridesByPath = {}
|
||||
super
|
||||
|
||||
@scopedPropertiesIndex = 0
|
||||
@scopedProperties = []
|
||||
|
||||
serialize: ->
|
||||
{ deserializer: @constructor.name, @grammarOverridesByPath }
|
||||
{deserializer: @constructor.name, @grammarOverridesByPath}
|
||||
|
||||
addGrammar: (grammar) ->
|
||||
previousGrammars = new Array(@grammars...)
|
||||
@grammars.push(grammar)
|
||||
@grammarsByScopeName[grammar.scopeName] = grammar
|
||||
@injectionGrammars.push(grammar) if grammar.injectionSelector?
|
||||
@grammarUpdated(grammar.scopeName)
|
||||
@emit 'grammar-added', grammar
|
||||
|
||||
removeGrammar: (grammar) ->
|
||||
_.remove(@grammars, grammar)
|
||||
delete @grammarsByScopeName[grammar.scopeName]
|
||||
_.remove(@injectionGrammars, grammar)
|
||||
@grammarUpdated(grammar.scopeName)
|
||||
|
||||
grammarUpdated: (scopeName) ->
|
||||
for grammar in @grammars when grammar.scopeName isnt scopeName
|
||||
@emit 'grammar-updated', grammar if grammar.grammarUpdated(scopeName)
|
||||
|
||||
setGrammarOverrideForPath: (path, scopeName) ->
|
||||
@grammarOverridesByPath[path] = scopeName
|
||||
|
||||
clearGrammarOverrideForPath: (path) ->
|
||||
delete @grammarOverridesByPath[path]
|
||||
|
||||
clearGrammarOverrides: ->
|
||||
@grammarOverridesByPath = {}
|
||||
|
||||
selectGrammar: (filePath, fileContents) ->
|
||||
grammar = _.max @grammars, (grammar) -> grammar.getScore(filePath, fileContents)
|
||||
grammar
|
||||
|
||||
grammarOverrideForPath: (path) ->
|
||||
@grammarOverridesByPath[path]
|
||||
|
||||
grammarForScopeName: (scopeName) ->
|
||||
@grammarsByScopeName[scopeName]
|
||||
createToken: (value, scopes) -> new Token({value, scopes})
|
||||
|
||||
addProperties: (args...) ->
|
||||
name = args.shift() if args.length > 2
|
||||
@@ -114,7 +76,7 @@ class Syntax
|
||||
_.pluck matchingScopedProperties, 'properties'
|
||||
|
||||
buildScopeElement: (scope) ->
|
||||
scope = new Array(scope...)
|
||||
scope = scope.slice()
|
||||
element = $$ ->
|
||||
elementsForRemainingScopes = =>
|
||||
classString = scope.shift()
|
||||
@@ -132,4 +94,4 @@ class Syntax
|
||||
element[0]
|
||||
|
||||
cssSelectorFromScopeSelector: (scopeSelector) ->
|
||||
new TextMateScopeSelector(scopeSelector).toCssSelector()
|
||||
new ScopeSelector(scopeSelector).toCssSelector()
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário