Comparar commits
1357 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| d471d60bfa | |||
| 75d6aab811 | |||
| a80fcff81c | |||
| 42bce0d438 | |||
| 168e1d7c58 | |||
| bdf0f7d10a | |||
| d21060fd87 | |||
| ddc7ebb576 | |||
| 4ad45a1955 | |||
| 6c5b7cbd1e | |||
| b1492037d5 | |||
| 7a5537d01e | |||
| 91fbb10fd3 | |||
| 684687e412 | |||
| 4612d42fb3 | |||
| 026a0672df | |||
| fe4b60d6b0 | |||
| 040d76e3bf | |||
| 1885d50374 | |||
| f9e508e958 | |||
| de695e31d2 | |||
| 4eabb5bec6 | |||
| f3ec868af6 | |||
| 4e96d24d78 | |||
| b5c3c01cc1 | |||
| 13aa29b485 | |||
| be5342fb51 | |||
| 50dc82d074 | |||
| 4358ff9183 | |||
| 336f0382b5 | |||
| 0307c0e6c9 | |||
| fc1e62471f | |||
| 137e9aacab | |||
| 4ddc6c1c1d | |||
| ef12913422 | |||
| 29c36836fe | |||
| 5c2614544d | |||
| 4c1d85e2bf | |||
| 2dd04c449c | |||
| 55f4a7b63e | |||
| a651254e38 | |||
| 1131952a54 | |||
| 189ebc4476 | |||
| 0688ddf083 | |||
| de2e59ddcb | |||
| 41632f2d95 | |||
| d21e8f0aa8 | |||
| 5521d3765a | |||
| d8b4e00fa8 | |||
| e1906ec9ba | |||
| 35fce94025 | |||
| 4708ffa8f6 | |||
| 8a858a1836 | |||
| f8ce0be095 | |||
| cb17078c48 | |||
| 28ed117db8 | |||
| 67d505fc7e | |||
| dba2ab55e0 | |||
| 119bf1a879 | |||
| 30dd202f33 | |||
| 4a4285b18f | |||
| 37b5a00df3 | |||
| 45fff99967 | |||
| c319329b23 | |||
| e280aa7f40 | |||
| f06a976052 | |||
| 77b8b51c16 | |||
| 3b86bb528c | |||
| d55acdd67b | |||
| de8d0197ff | |||
| 1fb9697229 | |||
| 5f8fe698ba | |||
| ed41ba49f9 | |||
| b500b23a69 | |||
| 27099392f4 | |||
| 19e63996ac | |||
| 87a4f3fc20 | |||
| 1dfed85f37 | |||
| cac2599b55 | |||
| d765e7f115 | |||
| 552af032f4 | |||
| 9bd82b201c | |||
| 662e8b8a11 | |||
| eda4e53b2c | |||
| b8cbc3b5af | |||
| f6309ec489 | |||
| be30cce41a | |||
| a6efeb7a19 | |||
| bc3791f738 | |||
| a8cc73c5e6 | |||
| 2a8dce75cd | |||
| e7200fb938 | |||
| 8c60207fde | |||
| 1ca86d36fd | |||
| 6f971901d7 | |||
| 6f353b6c54 | |||
| a7ebdbb361 | |||
| d84a0337b4 | |||
| 0d1f980940 | |||
| cf382ea2f8 | |||
| a674950ce1 | |||
| 2b7bd8a9d7 | |||
| d98fef52f2 | |||
| c2d1a10684 | |||
| 2580c3e29f | |||
| d2a340c8d0 | |||
| 8ec01b1583 | |||
| 1e3dc14729 | |||
| 253593431d | |||
| 730fbec9ce | |||
| ec70b71511 | |||
| 0b33709c69 | |||
| 7db3f7fefb | |||
| 9bb81b3db9 | |||
| dc1ee10aa4 | |||
| 18214be4f7 | |||
| 1dd4e5bb87 | |||
| 5c17e20a23 | |||
| b040aed702 | |||
| b68b075b1e | |||
| df5f2c4f81 | |||
| 281eb99d88 | |||
| b03694efff | |||
| 2d9a805d8f | |||
| ee110dfd91 | |||
| 6a0fee406e | |||
| b8d2473c6a | |||
| ec4469aebb | |||
| 209adda958 | |||
| 1f74ae1206 | |||
| 04b0d6d930 | |||
| 5b6fcca58a | |||
| cfb785705b | |||
| 80460850d4 | |||
| 116b920703 | |||
| 89b683d9d3 | |||
| 5ac8611a2c | |||
| 52051837c9 | |||
| 1ed3767f0c | |||
| 7f0e6fd86c | |||
| 03610f01d4 | |||
| 5f548627b3 | |||
| 80497be71b | |||
| 952b5e84f7 | |||
| 25eb38ff60 | |||
| b4517c5393 | |||
| 67a1112ced | |||
| 6f38a40b94 | |||
| 5a20066d2e | |||
| 37be8fab30 | |||
| 91fffc08b9 | |||
| 458dea9780 | |||
| ea43384244 | |||
| 65a8c8d946 | |||
| 0e93a11850 | |||
| ce34f758bd | |||
| 0eaf3b6336 | |||
| 4700eeb7e2 | |||
| d7d2a6f1f0 | |||
| 878831bfc7 | |||
| 324ae3fe3a | |||
| bc22130938 | |||
| 461b95b1ac | |||
| d14396e2d3 | |||
| 1e0fd948a5 | |||
| 9d9e33f245 | |||
| f612a85599 | |||
| 136e7d3491 | |||
| 00b913d363 | |||
| 3fd11b7aaf | |||
| c1cf4efb31 | |||
| 4c65d711b0 | |||
| 090f737647 | |||
| 224294d00f | |||
| 8c145168ef | |||
| 4660ed5745 | |||
| c515cf880c | |||
| eb58ce7736 | |||
| 08ede33b66 | |||
| ff519d52b2 | |||
| 6c5a5c49da | |||
| b5a799c997 | |||
| ca7d2695ea | |||
| 81c2374c87 | |||
| e5200d5414 | |||
| f4fdbbc307 | |||
| c6a7e84640 | |||
| fac70d080d | |||
| 5e2a641861 | |||
| 9cf79730ea | |||
| 737d41ad16 | |||
| a49ae766ae | |||
| 239e6ce156 | |||
| fb42086a6f | |||
| 3f53b8d2cb | |||
| 53fb36d0e2 | |||
| 545b375bb5 | |||
| 383d8f6940 | |||
| 280fc9af85 | |||
| 029e4336bc | |||
| aebca38ae6 | |||
| 01f6ca3cae | |||
| 2275a6d2d1 | |||
| 893493960f | |||
| 26121489be | |||
| f0dc88e33c | |||
| 99f6b89b6e | |||
| ff42526199 | |||
| dea7fafee7 | |||
| 357f158f8a | |||
| c18fa805f9 | |||
| 5fd0850f5b | |||
| 66225736fe | |||
| a87036b538 | |||
| 62b8ef7bb3 | |||
| 546351dd46 | |||
| 69f0572f2a | |||
| a6ffaff2a2 | |||
| 4e62540a9b | |||
| bb03d6b83d | |||
| 8aaa34b8fa | |||
| a8f4019693 | |||
| 03a60561b7 | |||
| 761e96429d | |||
| 3e685d91a5 | |||
| 668d3d3672 | |||
| ad1fe5f5f0 | |||
| 23308625f6 | |||
| abc6386ad6 | |||
| 91a1ce84d0 | |||
| 5695a209da | |||
| 97782de8d3 | |||
| a056f44a66 | |||
| a8f57ed276 | |||
| dbd20bcd31 | |||
| e6d63656b7 | |||
| 49fdd24af3 | |||
| db6018d847 | |||
| d87393f21e | |||
| 857c467497 | |||
| 33a207d57c | |||
| 02e3754dd9 | |||
| 70ab01d376 | |||
| 6a34d58ff4 | |||
| 2c5ae6e553 | |||
| 1988ed9e92 | |||
| 7a04a414f7 | |||
| c16435a604 | |||
| a9b2661d3c | |||
| 43220fb189 | |||
| 43f272d1e7 | |||
| 8900d9efdd | |||
| 4cc18cd936 | |||
| ae060fb450 | |||
| affaf9007a | |||
| 54e35a5014 | |||
| 9fa72c0841 | |||
| cfaa5ac3cb | |||
| 76903a0b91 | |||
| d316b14c4d | |||
| 523f965791 | |||
| 9f2d6fd8b5 | |||
| b176a246b3 | |||
| fac844c255 | |||
| 59bd424e89 | |||
| 12bc2a0a39 | |||
| dca5fadeec | |||
| 0872db3780 | |||
| 6134d95bfd | |||
| 1bf98f5b8d | |||
| f468420a29 | |||
| 459c11b602 | |||
| 6f8cb24e5a | |||
| b8d8ae68a3 | |||
| 63eb032472 | |||
| 69396922db | |||
| 99774d7417 | |||
| e67fb86084 | |||
| 98a25ca5e3 | |||
| 245ad7a356 | |||
| b042dffc2c | |||
| ef263580df | |||
| c0010789bc | |||
| 77453ff989 | |||
| da945de5f5 | |||
| 7a7521d810 | |||
| 31811ea4ae | |||
| 512a14d2ed | |||
| cdeb5d1a3d | |||
| 72b120cfe1 | |||
| 14424abe08 | |||
| 254730dc9f | |||
| ba3ae00b76 | |||
| ea2793a746 | |||
| e5fe583a3c | |||
| fe19798af9 | |||
| ce6751859c | |||
| b7aa88f9a1 | |||
| 0c01df5d29 | |||
| 24403ef0b0 | |||
| 56cb847633 | |||
| a7a0ecd019 | |||
| 0dce31e02d | |||
| ad36db61c6 | |||
| 69fe67d6e1 | |||
| 57ed5d7d57 | |||
| 320acab126 | |||
| a91285c754 | |||
| 313fb79fdc | |||
| 3571d1e60e | |||
| 3d89f76abf | |||
| 9e19f62b47 | |||
| e439847b59 | |||
| ce6d5b3b2a | |||
| b4e8d5480c | |||
| 04290ebbbb | |||
| 366684f3d7 | |||
| 5f9549e279 | |||
| a149056408 | |||
| fb60a3189a | |||
| 94f26b99cc | |||
| 3f8d9a4c47 | |||
| 50a27e99bb | |||
| eb885357e6 | |||
| 9b3463ef64 | |||
| 39762081b1 | |||
| 0dd96a0732 | |||
| 70d8777db6 | |||
| bb0a31228d | |||
| cec35ed7c8 | |||
| 95e68c8c2b | |||
| 3f5cd19ddc | |||
| d0dd0a9882 | |||
| 2d52f3deac | |||
| fa376d9543 | |||
| 31480f92ce | |||
| 7d725d5597 | |||
| dbfcd0c4d1 | |||
| 55eff7cd69 | |||
| 97951e38ad | |||
| 68f190ab9a | |||
| a7edf10f1f | |||
| bd050a817d | |||
| 0c267f35ef | |||
| 4437255f65 | |||
| 85af8a2225 | |||
| db64dd5426 | |||
| 34732d059e | |||
| 8888de0256 | |||
| 87b7d9d5e3 | |||
| 67de3ceb5e | |||
| 60b4554cb3 | |||
| f7ae280760 | |||
| 42320c5227 | |||
| 7f090fdac8 | |||
| 4c69abc7db | |||
| 3406b5ee1f | |||
| 2af12a7b7a | |||
| b3c3b55131 | |||
| 74aceb02fc | |||
| 4a36d2ee89 | |||
| de24128009 | |||
| 415c871b83 | |||
| 7f826dd005 | |||
| 408901360f | |||
| ec6accbc8b | |||
| 523460166f | |||
| df996695c8 | |||
| 86a9279743 | |||
| 53b423496f | |||
| ead21bfc33 | |||
| ee14214c7a | |||
| 8f26feadb5 | |||
| 760d8c60ad | |||
| bec5c9c9c9 | |||
| 02a8a41427 | |||
| d5f7e2a6e2 | |||
| 3e6e530b44 | |||
| c277a19781 | |||
| 3191697a38 | |||
| 0cead13df7 | |||
| 59872889db | |||
| da14629826 | |||
| 3bf551cefd | |||
| 85543888b4 | |||
| 37cd047c46 | |||
| e2dc48456d | |||
| bf2a3d3ca4 | |||
| 63f7f01169 | |||
| 6d68c4eeea | |||
| d91dd2f6ea | |||
| c91443825b | |||
| c31e981479 | |||
| 2cefd4f4d6 | |||
| 2a9b1260c2 | |||
| e53feba1fd | |||
| 24752ff006 | |||
| 29ef6366ca | |||
| 06e782e920 | |||
| 6e29f08469 | |||
| 52c1747aaf | |||
| b2973556d6 | |||
| d0c2dfffb1 | |||
| f3af21c5d2 | |||
| e5b4302407 | |||
| fc6aa2aa12 | |||
| 883f6dd50b | |||
| 8ac197074d | |||
| d045bc9836 | |||
| d31c0f81d2 | |||
| 5bad4c95c1 | |||
| dc11573862 | |||
| 4d42cb908d | |||
| db2bb9274d | |||
| 0e3b6628f3 | |||
| 65dafd58c1 | |||
| 0c0b082604 | |||
| 906fcf0929 | |||
| 36458331f8 | |||
| 4b1e44a967 | |||
| c7855dbcd3 | |||
| 950a0ce153 | |||
| d64fefd96c | |||
| 7f903bc6e7 | |||
| d4dfdd17ff | |||
| 2e8bf53a04 | |||
| d9262a8415 | |||
| 504ec58d93 | |||
| 3de158bbdd | |||
| 6cd3ef4a6c | |||
| d5c8b052c6 | |||
| ffdbefc0f4 | |||
| fccddd9200 | |||
| bdb37c4dee | |||
| 4b28cc78b1 | |||
| 28032cb257 | |||
| 75c3197a62 | |||
| e0459fa069 | |||
| 8f9d845683 | |||
| 7caece7c77 | |||
| e279c75653 | |||
| f678d836fa | |||
| 131a457da8 | |||
| df4e791f47 | |||
| 752d028581 | |||
| 543a2ad266 | |||
| c2fe0b7aa1 | |||
| 9e0436d10e | |||
| b3550c6526 | |||
| 75745bccc7 | |||
| 93bd0c8412 | |||
| f473bfdb95 | |||
| 9300ee0f47 | |||
| c7bd1f0a87 | |||
| 763c5053ad | |||
| 3053760880 | |||
| a428b03db5 | |||
| 0153bb538b | |||
| 1f47e7ae64 | |||
| a78dff15c4 | |||
| def39f94df | |||
| 382afba835 | |||
| 0358e9ad54 | |||
| 8f4bf6d897 | |||
| 6428d1cb60 | |||
| 6b4e68f127 | |||
| 9e9cdaecc0 | |||
| 410f573095 | |||
| 41761ffbcf | |||
| ee09eee374 | |||
| a57c58d5d9 | |||
| 1d3163cda1 | |||
| e3505fd45b | |||
| 5461de5856 | |||
| 88b214067f | |||
| 5304afd69a | |||
| a99c404a78 | |||
| 6b971e36b0 | |||
| e550d8a3bc | |||
| f3ff802aa4 | |||
| 52241a45df | |||
| 86b19c1ffc | |||
| 680e1cc80a | |||
| 017cc23bbf | |||
| d49bb2416b | |||
| 1d2c3bce20 | |||
| 48f69880e0 | |||
| ed7de42ba0 | |||
| 95c4e2170b | |||
| 4e2218b005 | |||
| 8b83918a24 | |||
| a1ce1c9ef6 | |||
| 0419ae8712 | |||
| d8737ba4a1 | |||
| 9bd1835ea1 | |||
| 823a79610f | |||
| 3929189e7a | |||
| fb73240654 | |||
| cc890ebdc0 | |||
| 7708d645c9 | |||
| 29cbea4d50 | |||
| 8b1b3d237d | |||
| 7d7f208fc5 | |||
| 1302a38ddf | |||
| f9d70e5623 | |||
| 0e60d73b10 | |||
| 136928f36e | |||
| d6fc3e6d01 | |||
| 04d8584742 | |||
| de88d6b624 | |||
| 32a15796c1 | |||
| 905e456a15 | |||
| 04c59952d5 | |||
| a93bfb5f8c | |||
| 2579f2993b | |||
| f9cdc4883e | |||
| d8da977b20 | |||
| 4d230a0517 | |||
| 258218b166 | |||
| 211d222291 | |||
| dda412d5ec | |||
| d7fabc5a58 | |||
| 16ab2031fa | |||
| 0ea03bc389 | |||
| 1bc38c191f | |||
| c6baed045d | |||
| 71ae6b28dd | |||
| 57e7335907 | |||
| 15442c3dba | |||
| d5c4b74608 | |||
| a2fcc7aa7a | |||
| de0aba6165 | |||
| 4b529ae167 | |||
| 848d77d3eb | |||
| 069ead6b1c | |||
| aef3332a09 | |||
| 4bfc0e8ea1 | |||
| c19c4a5e27 | |||
| 750e3565fd | |||
| 0eb874864c | |||
| 4c60c40eb8 | |||
| 830a8ddc03 | |||
| d9b14dc492 | |||
| 4743cf89dc | |||
| b6710b54cf | |||
| d9e4b9d199 | |||
| 6a408a3a55 | |||
| 4b2e8f8713 | |||
| c882d73527 | |||
| fd005380b0 | |||
| 6776fa4f0d | |||
| 8be5f7d6c8 | |||
| 1c51f512ea | |||
| 0d724bb00d | |||
| 8b94fef806 | |||
| 919fafc7a4 | |||
| 89b240cd4f | |||
| 91bd852812 | |||
| 99c2c32e1e | |||
| ecf4dbefe3 | |||
| cf73dd467a | |||
| 05769f8a49 | |||
| 51fbb1be07 | |||
| af1f57048b | |||
| 0e62841320 | |||
| d9ddf516f8 | |||
| 1436b8eb5e | |||
| 1645efa2ce | |||
| d0fe2c9a5b | |||
| 84ee94dfd1 | |||
| f585f53144 | |||
| 9b3cdd00c8 | |||
| b650043191 | |||
| 6f422ce56b | |||
| abe630937e | |||
| 7cccd5f920 | |||
| e4f6eb17a0 | |||
| 3951c45519 | |||
| 2a2858554a | |||
| 5b6e0b769d | |||
| c189dc22d7 | |||
| d2abbb3681 | |||
| 8df791c949 | |||
| 1d4cab404d | |||
| 1f3ea76379 | |||
| e01be5d41a | |||
| 13e435a4f9 | |||
| 3592ec19df | |||
| b49bd6fd10 | |||
| 2d16d3a459 | |||
| 72f2824588 | |||
| a229c28e62 | |||
| b6576545ba | |||
| e8c0793874 | |||
| 508636c06e | |||
| ca49ac1730 | |||
| 44c4bc8434 | |||
| a2fb288745 | |||
| c00c5c97f1 | |||
| 2248bbf8fb | |||
| 256a9bf08a | |||
| 4d3b4529f1 | |||
| 723ebff69a | |||
| ffb7093cf3 | |||
| d415ec9a00 | |||
| 2e8962501d | |||
| 8425c15cd7 | |||
| f9f2688468 | |||
| ad0bb5098f | |||
| 81e86c1467 | |||
| 0724dd7a7c | |||
| 92b76e61c4 | |||
| 8eee4d87be | |||
| e6d7413af1 | |||
| 6685464229 | |||
| 20772d045c | |||
| 33b7c915eb | |||
| 31a154d7eb | |||
| f0197632a3 | |||
| ae7306572b | |||
| 1a81248c88 | |||
| 8d40e4df10 | |||
| 431688e44c | |||
| f10a70eaf4 | |||
| 282fb66e75 | |||
| a131a03f28 | |||
| 713d7332b3 | |||
| 00f30eaf6c | |||
| f01a2a91f9 | |||
| e51c94b940 | |||
| fe5640df4b | |||
| 42a777e822 | |||
| 73cc1dadae | |||
| 4b9aa18628 | |||
| 163150dc87 | |||
| f3be876065 | |||
| e499e32c24 | |||
| 3534ac0f32 | |||
| ac6675380c | |||
| c1e8505ebf | |||
| e567702e3f | |||
| 5d2c6ea4b4 | |||
| df1a792675 | |||
| 9c1fa74d2f | |||
| 009cbfd418 | |||
| 81fd2bbf2e | |||
| d5222f22ea | |||
| 69545cba61 | |||
| 625a61a18f | |||
| 2d93402858 | |||
| efbf961c4b | |||
| d64ff4d598 | |||
| c81fcac108 | |||
| 01f3f88c6c | |||
| fa45af588e | |||
| 802ec9d8c2 | |||
| cce64cb9e8 | |||
| b38702d754 | |||
| 6a9268cb38 | |||
| 246bbc7862 | |||
| 7428431609 | |||
| f9308fd9fc | |||
| 504a55304e | |||
| 651177bc0c | |||
| d9a47f256c | |||
| 9a51c24937 | |||
| 28f0bf645f | |||
| 0f68f095f1 | |||
| 7686b348b1 | |||
| fc67aa016a | |||
| 422c0e36cb | |||
| 0bbc631607 | |||
| fbdf16a8fa | |||
| 23af7b4072 | |||
| 4219d06bd9 | |||
| e6e43f6884 | |||
| e52a4c1588 | |||
| efac59be9b | |||
| c2199e9c21 | |||
| 8a9d4d8eea | |||
| 837d91c220 | |||
| a49340dd6c | |||
| dfdab3d006 | |||
| 25a9ca4224 | |||
| f4873646c9 | |||
| da9a7a18dd | |||
| f213389db8 | |||
| bca9f81be1 | |||
| e5c31495cb | |||
| 1e69ede779 | |||
| f24389a45b | |||
| 695fd441fb | |||
| 8cd164ef5e | |||
| dbbfb9ae7d | |||
| 18348b8738 | |||
| c4cb6abef1 | |||
| c9ee68651d | |||
| 709c70c4c4 | |||
| fd7c2e92c5 | |||
| bbce381e16 | |||
| a46fcc1985 | |||
| de914193ff | |||
| b80c43db7b | |||
| 83cc6a76de | |||
| d21b5ae75b | |||
| 406743f0fb | |||
| acc5c18ba3 | |||
| f0ca685a16 | |||
| b001e7e28f | |||
| 1ab12b436a | |||
| 9f67978513 | |||
| ff1440be26 | |||
| 7a9a1ca213 | |||
| 35beaf44a9 | |||
| 63c24cd6e9 | |||
| 6d6f41b212 | |||
| af52ad9124 | |||
| 8cf498e7e3 | |||
| 2bbae7090e | |||
| cb6ba3c418 | |||
| ec4cf8b497 | |||
| 967db1f7b8 | |||
| 5b453290ad | |||
| 972fa41528 | |||
| 5ca0864753 | |||
| ee3d928b5b | |||
| b1b541f903 | |||
| a9e4bd4aaf | |||
| 2f908c171d | |||
| 227454e27e | |||
| 1d9fed2464 | |||
| 4298733db6 | |||
| cee0b951fb | |||
| 932a792289 | |||
| 64a57635e9 | |||
| eefa85e8ec | |||
| 5a400f1bb2 | |||
| 65ec0a2f0a | |||
| ed8b8f005f | |||
| 7166937b13 | |||
| 5481e5d664 | |||
| 2ccab9d182 | |||
| a646ab5bb8 | |||
| f10e55b4d5 | |||
| e769a8e4d5 | |||
| 3ac03c9ae0 | |||
| 8b2533e721 | |||
| bc29ddb9b6 | |||
| 1b455a990e | |||
| 69fa5cb3f7 | |||
| eb05e804b1 | |||
| ab456131cc | |||
| a7267d93ad | |||
| 3047152fb0 | |||
| 220fe36167 | |||
| e52fd3925c | |||
| 8713153f01 | |||
| 4237916759 | |||
| bcf1456df7 | |||
| 147db97d4d | |||
| b462a1ffa9 | |||
| e2e838c4ec | |||
| 6b88ce8d19 | |||
| 4141e1060a | |||
| e66e75593d | |||
| c765d069d1 | |||
| faf1a1565e | |||
| 71cae3c937 | |||
| d5b809a194 | |||
| 36427dae9f | |||
| 2fb0f796c4 | |||
| f473d46cf1 | |||
| 061599554e | |||
| a03aad6f5e | |||
| 0a62277cfa | |||
| 9d045d1d43 | |||
| 6e4c4f43b3 | |||
| 099f632a6c | |||
| 0dc0883531 | |||
| 6d6f4671d8 | |||
| ef942ef1c8 | |||
| 2a75836ca2 | |||
| b845086e63 | |||
| ba45dbaa6a | |||
| 3c90007199 | |||
| 1023c421c7 | |||
| ece269f158 | |||
| 43ba0b9529 | |||
| b28f3f29ec | |||
| 92aeb9f3cb | |||
| 89cade2d22 | |||
| 53529dab1f | |||
| 35dd435397 | |||
| 93aec682de | |||
| c64b84750a | |||
| 01876c4004 | |||
| 9680ad7bb5 | |||
| bc5a79564a | |||
| 88a65358b4 | |||
| dcbd2a2102 | |||
| 11e727e3fa | |||
| ce63aee338 | |||
| d3e11a6183 | |||
| 88798fca7e | |||
| 3c9204e464 | |||
| 48417338b0 | |||
| faf523f698 | |||
| 8a85f488f3 | |||
| f75b4aa117 | |||
| e4a65ca810 | |||
| a36cc3a609 | |||
| cb8075206b | |||
| a04f77b400 | |||
| 0e0ae17cd3 | |||
| 02f0c49d0e | |||
| ca6521b711 | |||
| f0c200f233 | |||
| 84e69db268 | |||
| fbdb5b59ea | |||
| 66530eb69a | |||
| 70a7514f9e | |||
| 55ca32f7b3 | |||
| 569ab416b8 | |||
| b1e99d9927 | |||
| caeb70cf4a | |||
| cc233fb7f6 | |||
| c730910f7c | |||
| fc2be08b60 | |||
| e6565f6561 | |||
| 638aaa2e62 | |||
| e23fb98a41 | |||
| 8d0ddc0513 | |||
| c225d180ba | |||
| 217fd8e271 | |||
| b82c808bad | |||
| 25f97b5ef3 | |||
| 86f1a484a8 | |||
| 49313b65b9 | |||
| c0d564b635 | |||
| b9c14379be | |||
| adb3b390bb | |||
| a192b70406 | |||
| 28e6be8baf | |||
| f5fa89418c | |||
| 3d27cd662a | |||
| 0c8744f7a7 | |||
| 272ce92201 | |||
| c30f74ef9e | |||
| 5912ac1d1b | |||
| a1171f39df | |||
| 69c9172e49 | |||
| c1f3aa14cd | |||
| fae28d8c21 | |||
| 5017f1473c | |||
| 12ec3f69d6 | |||
| f4e43b96c2 | |||
| f958322cef | |||
| 7061ea5d23 | |||
| f7e11a1fb5 | |||
| aab5deaae6 | |||
| 50f68c38ec | |||
| d778e13ffb | |||
| 9918a0b73f | |||
| 374fe99aaf | |||
| ea052fc667 | |||
| d11037f6d2 | |||
| 777c4a5e43 | |||
| a897a320a9 | |||
| a94e9df641 | |||
| 45d8df37fa | |||
| 83639c4cfa | |||
| 6c960183fd | |||
| 0730c7902d | |||
| 012c38cf11 | |||
| 0785fb3a48 | |||
| 563065aeb1 | |||
| 2b1f9531e5 | |||
| 665d1b9581 | |||
| d79d819048 | |||
| 6ae2d9737d | |||
| 54b67ea6b9 | |||
| 41dc113088 | |||
| 3216d767da | |||
| 94987c6769 | |||
| f423b54d5b | |||
| 86dc499a99 | |||
| ec6184322c | |||
| bde4b88f9f | |||
| e0f41f8cc5 | |||
| c49c48381a | |||
| 73d36e464d | |||
| 2e8fd21193 | |||
| 39d525590b | |||
| 4f6adc51b4 | |||
| 73c714b62d | |||
| caa16f2718 | |||
| 1b17441bfe | |||
| 9a97bac05d | |||
| 9252cd2a1b | |||
| 1c790a3c55 | |||
| 11f61c88d8 | |||
| 64d9eb24d2 | |||
| 73b3b9870f | |||
| 1c8781268e | |||
| 662c130caf | |||
| 40d9d6110c | |||
| a6f42d6989 | |||
| e9b4fb828e | |||
| 84c5a25ef3 | |||
| 4a54565f59 | |||
| 1b396b5ce7 | |||
| c3f995b165 | |||
| fe748dbcc8 | |||
| 1e759ee646 | |||
| 99f25267a0 | |||
| 09952d18c0 | |||
| 9176e12f58 | |||
| 97330d19f3 | |||
| e3f0e11aa8 | |||
| 32cd0ee972 | |||
| fc5bc1632d | |||
| 337390df47 | |||
| d29a0114ef | |||
| b97db1914f | |||
| 29f480661c | |||
| d7638b4420 | |||
| ce881c87b9 | |||
| d9b00f16f7 | |||
| 2590bad75f | |||
| 6ff553e41c | |||
| ec3ca7e429 | |||
| 386767868e | |||
| e2bb4747fa | |||
| 885a4e2b2a | |||
| e31fab7fe4 | |||
| 0a2b173270 | |||
| c1a9c3b5fb | |||
| 1550511e54 | |||
| 44999b0b37 | |||
| 0f2931d0a2 | |||
| 064a384318 | |||
| 5708140f10 | |||
| aceeb10a0d | |||
| 0940668263 | |||
| bcac3cc000 | |||
| 5b39fc2e11 | |||
| cb0e0751d8 | |||
| a67d6362c2 | |||
| 5eb8875ad2 | |||
| e6e7106ac5 | |||
| 257d0d3a25 | |||
| 632ba0217a | |||
| bf1a2a532e | |||
| 72bb83ccd0 | |||
| def2001eb6 | |||
| 5bda9b8a8e | |||
| 0b0ad42610 | |||
| 8772e45a39 | |||
| 7c4b453e64 | |||
| 740e0731fe | |||
| b6cf097abd | |||
| aa8d03b092 | |||
| 8c75f425e7 | |||
| f322143272 | |||
| dd5c65b5f9 | |||
| aec9e75ecb | |||
| b05e20245f | |||
| 17d30cc526 | |||
| f631660b32 | |||
| 2b34b2b9ba | |||
| 5be6cfa678 | |||
| b5449dec6c | |||
| be0e36b663 | |||
| f92f130606 | |||
| d931299b9f | |||
| b1778aa7df | |||
| c5d7614d80 | |||
| 0d34df2238 | |||
| ee24b373c0 | |||
| 8e6aa1ee9a | |||
| 9e381db265 | |||
| 6d4f532a32 | |||
| 998aedf367 | |||
| 5f0a3061ac | |||
| c424a39538 | |||
| 150815edfd | |||
| 00ba7d92d0 | |||
| 3dc51365b3 | |||
| d18a45f37c | |||
| d8c1587534 | |||
| a4754b2bd5 | |||
| f3f6ec424f | |||
| 020326ffad | |||
| ac2985af36 | |||
| ca8609c925 | |||
| d2e4c61e0b | |||
| 399c4f9f95 | |||
| 3da7fe7300 | |||
| 40048da314 | |||
| 04c8549c38 | |||
| 4ef6eae02f | |||
| 52dcf6a721 | |||
| 9169e9e227 | |||
| 19e0854036 | |||
| cd41637df0 | |||
| 8973a6d777 | |||
| e6b9c67aad | |||
| eb28c15f69 | |||
| a42a4dd352 | |||
| cc53b6fbef | |||
| b31089cbb9 | |||
| 4d4ff84047 | |||
| 0875e00f9c | |||
| b9d45680c3 | |||
| 1fa2099eba | |||
| 4d74a69277 | |||
| 3aa67bff8b | |||
| 325482353e | |||
| cf4e08cdc0 | |||
| ced02faf0f | |||
| 8afca3ac86 | |||
| d13d2d3112 | |||
| c3d5f713ca | |||
| 042684212e | |||
| 81987efed7 | |||
| f2e123e295 | |||
| 8d193d542b | |||
| 8ca8ac5efc | |||
| f6c7dd0b2f | |||
| b380551e5e | |||
| 7dcb340643 | |||
| f0339936ee | |||
| 1ae10e59ae | |||
| e4bcb52573 | |||
| 292ff0de52 | |||
| 5a70276201 | |||
| 1bd4d51879 | |||
| a95a510770 | |||
| 7d1be155fa | |||
| 41de6bdb70 | |||
| 1d9bca12e0 | |||
| e9c296968d | |||
| 352eab47df | |||
| 002c4dcb80 | |||
| 03250f79c6 | |||
| 1142da1848 | |||
| e53ed10169 | |||
| 3fb54b657d | |||
| 4b3b0145a7 | |||
| ec558f9a9b | |||
| 051f1b4777 | |||
| 2e4e178091 | |||
| 1fa04cb161 | |||
| 69df046cb0 | |||
| d491007da8 | |||
| 71c65e65eb | |||
| 3fad9c616f | |||
| 45875188a4 | |||
| 933787f1a4 | |||
| 726b546004 | |||
| 5e83dee493 | |||
| 11ab6fd2ef | |||
| 0758bd0231 | |||
| 7f63460a8c | |||
| 627e43dccf | |||
| cb8b38b7f7 | |||
| 04b2eefec3 | |||
| 0694180715 | |||
| f51b116c14 | |||
| f001e05262 | |||
| 77e97a7552 | |||
| 80a1249d59 | |||
| a03ba290f9 | |||
| c9e498f36f | |||
| e6a98125af | |||
| 025d2be606 | |||
| 26e21e98db | |||
| e3f32ab30b | |||
| 5d8ac56018 | |||
| 1603fab984 | |||
| 07c7f1ea46 | |||
| 31b68c0937 | |||
| 3a02f4de0c | |||
| 45fe04f630 | |||
| b4a9409ec8 | |||
| 7432d39301 | |||
| c5349f53b2 | |||
| 8b4b4a6ea9 | |||
| 19177123a7 | |||
| dd032b739f | |||
| f0ebe71c4e | |||
| 8bba4d8add | |||
| 7be0f6dd3b | |||
| 571947ae93 | |||
| 338168e145 | |||
| 4190f2f1ca | |||
| e74f026750 | |||
| 525b5c9978 | |||
| 86106cbf4d | |||
| 519ebb1ca6 | |||
| da56c1def5 | |||
| a6f9b6d2de | |||
| d965e9f56c | |||
| 8f0e0ae4a6 | |||
| 57a71b3cb3 | |||
| 71c48ec07f | |||
| 4ac3be7ab5 | |||
| 5dd83b12c0 | |||
| 905f628b79 | |||
| ddad42432b | |||
| 52e999fc11 | |||
| 3114efbf9c | |||
| 8436e8f62e | |||
| ba1303a895 | |||
| 59c4c15afd | |||
| 2831b43042 | |||
| 3c8cb557ed | |||
| 6b8393dced | |||
| 3356b4ac47 | |||
| 3eb7c9d767 | |||
| bbb3ebc2b9 | |||
| ed93695d64 | |||
| 4db2ad53fd | |||
| 149a6825b4 | |||
| dd1581aaca | |||
| 99f025d5d6 | |||
| e0cf20cda9 | |||
| c305384334 | |||
| d879923cdb | |||
| d16526a1f7 | |||
| a774c2ff08 | |||
| 475ff140a7 | |||
| fa6bce3085 | |||
| b2dff15e51 | |||
| 1e3dc05b3d | |||
| 1a12a17b7a | |||
| 8757e1f145 | |||
| 12ca214d6a | |||
| 8e75831318 | |||
| 2f049b95a8 | |||
| 386d4d3eb5 | |||
| 6a14c0df81 | |||
| 083204f737 | |||
| 4d17f625f8 | |||
| 64ebc37204 | |||
| b4dcec9417 | |||
| ede03fd634 | |||
| 926493f05c | |||
| 4f82662263 | |||
| dfa3d8a1c7 | |||
| 677ec44b37 | |||
| 0359e933d1 | |||
| ff88c1c41f | |||
| 0acc632cf5 | |||
| 2c65a30d4a | |||
| 590b8ec221 | |||
| cff1a012af | |||
| 3ea057a145 | |||
| b30ea21ab9 | |||
| 44d42380b8 | |||
| f906afa8ec | |||
| 815a282146 | |||
| 9fe24acbe9 | |||
| f2f3745022 | |||
| d2ecc8c2b6 | |||
| 9166a563c2 | |||
| 7388468b5a | |||
| 21bc658230 | |||
| 72f85aeae4 | |||
| 26b3cfc3b2 | |||
| c91eb1fbbe | |||
| 75ed3aa03b | |||
| 00d783fb23 | |||
| b80e7fc137 | |||
| e1d2bbabfb | |||
| 9de5adf572 | |||
| 2a3eab945d | |||
| df8d77cc47 | |||
| 9d11ea8bcd | |||
| f19d2d7369 | |||
| 7f790137ed | |||
| c56dce0c80 | |||
| 1f105542a3 | |||
| ec499e2bde | |||
| 59202f9a56 | |||
| e437673fbc | |||
| ded222551a | |||
| fe2def8321 | |||
| e19368a291 | |||
| 3980082e3c | |||
| 9f29e423db | |||
| 23d9f6e41f | |||
| 649f0ac246 | |||
| b9dcb569b1 | |||
| 6538ee47aa | |||
| 043a50012e | |||
| 9ed0ded91b | |||
| ad5c0f93ed | |||
| 4265de2232 | |||
| b7ae12f86f | |||
| 20e7fea3ab | |||
| 910469d373 | |||
| ff9c9adeff | |||
| ceac8ba0e1 | |||
| 24f386df2d | |||
| 1006706cf6 | |||
| 0b0a3ae4c2 | |||
| 276de1e5ae | |||
| fcffcc83f9 | |||
| 4122b5a43f | |||
| 2430edbcb8 | |||
| e2269d5d51 | |||
| 2d04c50184 | |||
| 2dc8c94165 | |||
| c1022d15ba | |||
| b620be7780 | |||
| 70c89dfefa | |||
| ace61c330a | |||
| 0626df5cdd | |||
| b1679a03d6 | |||
| af95abe185 | |||
| a23561f9ce | |||
| a93048d3ae | |||
| 1814b187e9 | |||
| d7d7bd2db5 | |||
| f86ca9b59a | |||
| 58cccd76a4 | |||
| fd1477fc92 | |||
| eca9ba107c | |||
| 94126942eb | |||
| e6697ce3a7 | |||
| ccd86d91de | |||
| 2f8ec967f6 | |||
| 709ae6a1bc | |||
| 5e14d44d6a | |||
| 41dd4a386a | |||
| 9af4b14716 | |||
| aed9f18457 | |||
| edf2fbe0e5 | |||
| ca71bf224b | |||
| 012363a785 | |||
| 1a48903f96 | |||
| 44331d0ba6 | |||
| fe7c5b4bc1 | |||
| 1825af1309 | |||
| b5947f5a56 | |||
| 864d6ce8f2 | |||
| ac07cf3d9a | |||
| 6ba3698547 | |||
| d57e5f389d | |||
| 2023aeef97 | |||
| 1c6564f7a5 | |||
| d9d28b5236 | |||
| 8734eab8cb | |||
| 1ad5158f19 | |||
| 453e034a5f | |||
| 9841a3588f | |||
| dd0ae8a8ea | |||
| d8ddd52df8 | |||
| 83696bb9c7 | |||
| 243c4efe20 | |||
| 22c65f2407 | |||
| e553fefc25 | |||
| cf6fc22c87 | |||
| a6d8f588c3 | |||
| 2ef74de0f8 | |||
| 2ee6469b17 | |||
| 9977884a2c | |||
| c331723c55 | |||
| dd3ca1fc2f | |||
| f7f2da6ad2 | |||
| eba74d1420 | |||
| 91c9f6ffa5 | |||
| db319c9f9c | |||
| 134affcd8f | |||
| 9bfa7704e6 | |||
| bda75c1a8f | |||
| c0c2d797b1 | |||
| e4b934d3fa | |||
| c350285044 | |||
| 4a7b43f609 | |||
| 0d66c68fe8 | |||
| a1d540d288 | |||
| af4034ba39 | |||
| 89cbd6b834 | |||
| fe01ded75b | |||
| aab4f3b761 | |||
| adf5cfc78c | |||
| 66bb9dab93 | |||
| c8e4535e8b | |||
| 7199cda549 | |||
| 1eb9c3d0d5 | |||
| 27c03ae3f3 | |||
| a8ddc530a2 | |||
| 8e78583521 | |||
| 7317d52896 | |||
| 54dd5ea28e | |||
| 22fe04fb27 | |||
| 46c5f09721 | |||
| 6401213510 | |||
| 5f92c6df6c | |||
| 75e3659809 | |||
| b458b86bbd | |||
| 645f950368 | |||
| 8dd183fc78 | |||
| 06ccb48efe | |||
| 95f2d02d18 | |||
| 63df8dfcdb | |||
| 637acf63ce | |||
| 9ff23e6bed | |||
| d96f15e8b5 | |||
| 5bf654f6cc | |||
| 16dba3e68e | |||
| 43e30ad466 | |||
| 0d36eec288 | |||
| 2e697f65e8 | |||
| edab3e5a27 | |||
| 8c3147f975 | |||
| 4182ee4f12 | |||
| 601b314236 | |||
| 7d596355b9 | |||
| ccc5f8fa66 | |||
| e29ffbea8c | |||
| efbd508163 | |||
| 5e674fe29e | |||
| c19c8c5341 | |||
| 32b5592475 | |||
| 21b40acdf1 | |||
| 1074c6c34e | |||
| 831454bd30 | |||
| 02a9d11bb5 | |||
| 0d89be26be | |||
| 1144e52fea | |||
| bb65a1a47e | |||
| f6a8b72fd4 | |||
| 196e908961 | |||
| 72744494b8 | |||
| c287be1725 | |||
| 28ec1f3e2d | |||
| d0bf769896 | |||
| 6942b82a70 | |||
| b6d5a1ddd8 | |||
| 5ecbd024cf | |||
| b4974eee41 | |||
| 3ca3b23ef8 | |||
| 9b48df928c | |||
| 884d276458 | |||
| 441e179c48 | |||
| e1248561b6 | |||
| 7b5055f974 | |||
| 206db1c271 | |||
| 033d85fd4f | |||
| 963aef70e8 | |||
| 1dce2c439d | |||
| b3ed9d468b | |||
| 878eca30e9 | |||
| f0f477d481 |
@@ -9,5 +9,6 @@ debug.log
|
||||
/tags
|
||||
/atom-shell/
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
/apm
|
||||
|
||||
+32
-2
@@ -15,7 +15,7 @@ to propose changes to this document in a pull request.
|
||||
|
||||
This is the repository for the core Atom editor only. Atom comes bundled with
|
||||
many packages and themes that are stored in other repos under the
|
||||
[atom org](https://github.com/atom) such as [tabs](https://github.com/atom/tabs),
|
||||
[atom organization](https://github.com/atom) such as [tabs](https://github.com/atom/tabs),
|
||||
[find-and-replace](https://github.com/atom/find-and-replace),
|
||||
[language-javascript](https://github.com/atom/language-javascript),
|
||||
and [atom-light-ui](http://github.com/atom/atom-light-ui).
|
||||
@@ -30,7 +30,7 @@ in the proper package's repository.
|
||||
* Follow the [CoffeeScript](#coffeescript-styleguide),
|
||||
[JavaScript](https://github.com/styleguide/javascript),
|
||||
and [CSS](https://github.com/styleguide/css) styleguides
|
||||
* Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine/)
|
||||
* Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine)
|
||||
specs
|
||||
* Avoid placing files in `vendor`. 3rd-party packages should be added as a
|
||||
`package.json` dependency.
|
||||
@@ -61,3 +61,33 @@ in the proper package's repository.
|
||||
|
||||
* Set parameter defaults without spaces around the equal sign
|
||||
* `clear = (count=1) ->` instead of `clear = (count = 1) ->`
|
||||
|
||||
## Documentation Styleguide
|
||||
|
||||
* Use [TomDoc](http://tomdoc.org).
|
||||
* Use [Markdown](https://daringfireball.net/projects/markdown).
|
||||
* Reference classes with `{ClassName}`.
|
||||
* Reference instance methods with `{ClassName::methodName}`.
|
||||
* Reference class methods with `{ClassName.methodName}`.
|
||||
* Delegate to comments elsewhere with `{Delegates to: ClassName.methodName}`
|
||||
style notation.
|
||||
|
||||
### Example
|
||||
|
||||
```coffee
|
||||
# Public: Disable the package with the given name.
|
||||
#
|
||||
# This method emits multiple events:
|
||||
#
|
||||
# * `package-will-be-disabled` - before the package is disabled.
|
||||
# * `package-disabled` - after the package is disabled.
|
||||
#
|
||||
# name - The {String} name of the package to disable.
|
||||
# options - The {Object} with disable options (default: {}):
|
||||
# :trackTime - `true` to track the amount of time disabling took.
|
||||
# :ignoreErrors - `true` to catch and ignore errors thrown.
|
||||
# callback - The {Function} to call after the package has been disabled.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
disablePackage: (name, options, callback) ->
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Copyright 2014 GitHub, Inc.
|
||||
@@ -1,13 +0,0 @@
|
||||
Copyright 2013 GitHub Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
# Atom — The hackable, ~~collaborative~~ editor
|
||||
# Atom — The hackable editor
|
||||
|
||||

|
||||
|
||||
Check out our [guides and API documentation](https://www.atom.io/docs/latest/).
|
||||
Check out our [guides and API documentation](https://atom.io/docs/latest).
|
||||
|
||||
## Installing
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ fs = require 'fs'
|
||||
path = require 'path'
|
||||
os = require 'os'
|
||||
|
||||
# Add support for obselete APIs of vm module so we can make some third-party
|
||||
# modules work under node v0.11.x.
|
||||
require 'vm-compatibility-layer'
|
||||
|
||||
fm = require 'json-front-matter'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
@@ -223,6 +227,6 @@ module.exports = (grunt) ->
|
||||
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', ['output-disk-space', 'download-atom-shell', 'build', 'set-version', 'lint', 'test', 'codesign', 'publish-build'])
|
||||
grunt.registerTask('ci', ['output-disk-space', 'download-atom-shell', 'build', 'set-version', 'check-licenses', 'lint', 'test', 'codesign', 'publish-build'])
|
||||
grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
|
||||
grunt.registerTask('default', ['download-atom-shell', 'build', 'set-version', 'install'])
|
||||
|
||||
+11
-8
@@ -6,29 +6,32 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"biscotto": "0.0.17",
|
||||
"first-mate": "~0.10.0",
|
||||
"async": "~0.2.9",
|
||||
"biscotto": ">=2.1 <3.0",
|
||||
"formidable": "~1.0.14",
|
||||
"fs-plus": "2.x",
|
||||
"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-coffee": "~0.9.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-cson": "0.6.0",
|
||||
"grunt-download-atom-shell": "git+https://atom-bot:362295be4c5258d3f7b967bbabae662a455ca2a7@github.com/atom/grunt-download-atom-shell#v0.6.0",
|
||||
"grunt-lesslint": "0.13.0",
|
||||
"grunt-markdown": "~0.4.0",
|
||||
"grunt-peg": "~1.1.0",
|
||||
"grunt-shell": "~0.3.1",
|
||||
"harmony-collections": "~0.3.8",
|
||||
"js-yaml": "~2.1.0",
|
||||
"json-front-matter": "~0.1.3",
|
||||
"legal-eagle": "~0.3.0",
|
||||
"rcedit": "~0.1.2",
|
||||
"request": "~2.27.0",
|
||||
"rimraf": "~2.2.2",
|
||||
"runas": "0.5.x",
|
||||
"underscore-plus": "1.x",
|
||||
"unzip": "~0.1.9",
|
||||
"walkdir": "0.0.7",
|
||||
"grunt-peg": "~1.1.0"
|
||||
"vm-compatibility-layer": "~0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ 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 = [
|
||||
@@ -39,30 +38,43 @@ module.exports = (grunt) ->
|
||||
else
|
||||
nonPackageDirectories.push(directory)
|
||||
|
||||
# Put any paths here that shouldn't end up in the built Atom.app
|
||||
# so that it doesn't becomes larger than it needs to be.
|
||||
ignoredPaths = [
|
||||
path.join('git-utils', 'deps')
|
||||
path.join('oniguruma', 'deps')
|
||||
path.join('less', 'dist')
|
||||
path.join('less', 'test')
|
||||
path.join('bootstrap', 'docs')
|
||||
path.join('bootstrap', 'examples')
|
||||
path.join('spellchecker', 'vendor')
|
||||
path.join('xmldom', 'test')
|
||||
path.join('jasmine-reporters', 'ext')
|
||||
path.join('build', 'Release', 'obj.target')
|
||||
path.join('build', 'Release', '.deps')
|
||||
path.join('vendor', 'apm')
|
||||
path.join('resources', 'mac')
|
||||
path.join('resources', 'win')
|
||||
]
|
||||
ignoredPaths = ignoredPaths.map (ignoredPath) -> "(#{ignoredPath})"
|
||||
nodeModulesFilter = new RegExp(ignoredPaths.join('|'))
|
||||
packageFilter = new RegExp("(#{ignoredPaths.join('|')})|(.+\\.(cson|coffee)$)")
|
||||
for directory in nonPackageDirectories
|
||||
cp directory, path.join(appDir, directory), filter: nodeModulesFilter
|
||||
for directory in packageDirectories
|
||||
cp directory, path.join(appDir, directory), filter: /.+\.(cson|coffee)$/
|
||||
cp directory, path.join(appDir, directory), filter: packageFilter
|
||||
|
||||
cp 'spec', path.join(appDir, 'spec')
|
||||
cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee)$/
|
||||
cp 'static', path.join(appDir, 'static')
|
||||
cp 'apm', path.join(appDir, 'apm'), filter: nodeModulesFilter
|
||||
|
||||
if process.platform is 'darwin'
|
||||
grunt.file.recurse path.join('resources', 'mac'), (sourcePath, rootDirectory, subDirectory='', filename) ->
|
||||
unless /.+\.plist/.test(sourcePath)
|
||||
grunt.file.copy(sourcePath, path.resolve(appDir, '..', subDirectory, filename))
|
||||
|
||||
dependencies = ['compile']
|
||||
dependencies = ['compile', "generate-license:save"]
|
||||
dependencies.push('copy-info-plist') if process.platform is 'darwin'
|
||||
dependencies.push('set-exe-icon') if process.platform is 'win32'
|
||||
grunt.task.run(dependencies...)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
module.exports = (grunt) ->
|
||||
grunt.registerTask 'check-licenses', 'Report the licenses of all dependencies', ->
|
||||
legalEagle = require 'legal-eagle'
|
||||
{size, keys} = require 'underscore-plus'
|
||||
done = @async()
|
||||
|
||||
options =
|
||||
path: process.cwd()
|
||||
omitPermissive: true
|
||||
overrides: require './license-overrides'
|
||||
|
||||
legalEagle options, (err, summary) ->
|
||||
if err?
|
||||
console.error(err)
|
||||
exit 1
|
||||
|
||||
if size(summary)
|
||||
console.error "Found dependencies without permissive licenses:"
|
||||
for name in keys(summary).sort()
|
||||
console.error "#{name}"
|
||||
console.error " License: #{summary[name].license}"
|
||||
console.error " Repository: #{summary[name].repository}"
|
||||
process.exit 1
|
||||
done()
|
||||
@@ -1,102 +0,0 @@
|
||||
path = require 'path'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
{ScopeSelector} = require 'first-mate'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
grunt.registerTask 'convert-theme', 'Convert a TextMate theme to an Atom theme', ->
|
||||
if textMateThemePath = grunt.option('path')
|
||||
textMateThemePath = path.resolve(textMateThemePath)
|
||||
if grunt.file.isFile(textMateThemePath)
|
||||
textMateTheme = new TextMateTheme(textMateThemePath)
|
||||
themeName = path.basename(textMateThemePath, path.extname(textMateThemePath))
|
||||
atomThemePath = path.join(path.dirname(textMateThemePath), "#{themeName.toLowerCase()}-syntax.css")
|
||||
grunt.file.write(atomThemePath, textMateTheme.getStylesheet())
|
||||
grunt.log.ok("Atom theme written to: #{atomThemePath}")
|
||||
else
|
||||
grunt.log.error("No theme file found at: #{textMateThemePath}")
|
||||
false
|
||||
else
|
||||
grunt.log.error('Must specify --path=<path to TextMate theme>')
|
||||
false
|
||||
|
||||
class TextMateTheme
|
||||
constructor: (@path) ->
|
||||
@rulesets = []
|
||||
@buildRulesets()
|
||||
|
||||
buildRulesets: ->
|
||||
{settings} = fs.readPlistSync(@path)
|
||||
@buildGlobalSettingsRulesets(settings[0])
|
||||
@buildScopeSelectorRulesets(settings[1..])
|
||||
|
||||
getStylesheet: ->
|
||||
lines = []
|
||||
for {selector, properties} in @getRulesets()
|
||||
lines.push("#{selector} {")
|
||||
lines.push " #{name}: #{value};" for name, value of properties
|
||||
lines.push("}\n")
|
||||
lines.join('\n')
|
||||
|
||||
getRulesets: -> @rulesets
|
||||
|
||||
buildGlobalSettingsRulesets: ({settings}) ->
|
||||
{ background, foreground, caret, selection, lineHighlight } = settings
|
||||
|
||||
@rulesets.push
|
||||
selector: '.editor, .editor .gutter'
|
||||
properties:
|
||||
'background-color': @translateColor(background)
|
||||
'color': @translateColor(foreground)
|
||||
|
||||
@rulesets.push
|
||||
selector: '.editor.is-focused .cursor'
|
||||
properties:
|
||||
'border-color': @translateColor(caret)
|
||||
|
||||
@rulesets.push
|
||||
selector: '.editor.is-focused .selection .region'
|
||||
properties:
|
||||
'background-color': @translateColor(selection)
|
||||
|
||||
@rulesets.push
|
||||
selector: '.editor.is-focused .line-number.cursor-line-no-selection, .editor.is-focused .line.cursor-line'
|
||||
properties:
|
||||
'background-color': @translateColor(lineHighlight)
|
||||
|
||||
buildScopeSelectorRulesets: (scopeSelectorSettings) ->
|
||||
for { name, scope, settings } in scopeSelectorSettings
|
||||
continue unless scope
|
||||
@rulesets.push
|
||||
comment: name
|
||||
selector: @translateScopeSelector(scope)
|
||||
properties: @translateScopeSelectorSettings(settings)
|
||||
|
||||
translateScopeSelector: (textmateScopeSelector) ->
|
||||
new ScopeSelector(textmateScopeSelector).toCssSelector()
|
||||
|
||||
translateScopeSelectorSettings: ({ foreground, background, fontStyle }) ->
|
||||
properties = {}
|
||||
|
||||
if fontStyle
|
||||
fontStyles = fontStyle.split(/\s+/)
|
||||
properties['font-weight'] = 'bold' if _.contains(fontStyles, 'bold')
|
||||
properties['font-style'] = 'italic' if _.contains(fontStyles, 'italic')
|
||||
properties['text-decoration'] = 'underline' if _.contains(fontStyles, 'underline')
|
||||
|
||||
properties['color'] = @translateColor(foreground) if foreground
|
||||
properties['background-color'] = @translateColor(background) if background
|
||||
properties
|
||||
|
||||
translateColor: (textmateColor) ->
|
||||
if textmateColor.length <= 7
|
||||
textmateColor
|
||||
else
|
||||
r = parseInt(textmateColor[1..2], 16)
|
||||
g = parseInt(textmateColor[3..4], 16)
|
||||
b = parseInt(textmateColor[5..6], 16)
|
||||
a = parseInt(textmateColor[7..8], 16)
|
||||
a = Math.round((a / 255.0) * 100) / 100
|
||||
|
||||
"rgba(#{r}, #{g}, #{b}, #{a})"
|
||||
@@ -1,26 +1,63 @@
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
|
||||
async = require 'async'
|
||||
fs = require 'fs-plus'
|
||||
request = require 'request'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{rm} = require('./task-helpers')(grunt)
|
||||
|
||||
cmd = path.join('node_modules', '.bin', 'coffee')
|
||||
commonArgs = [path.join('build', 'node_modules', '.bin', 'biscotto'), '--']
|
||||
opts =
|
||||
stdio: 'inherit'
|
||||
|
||||
grunt.registerTask 'build-docs', 'Builds the API docs in src/app', ->
|
||||
grunt.registerTask 'build-docs', 'Builds the API docs in src', ->
|
||||
done = @async()
|
||||
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)
|
||||
|
||||
downloadIncludes (error, includePaths) ->
|
||||
if error?
|
||||
done(error)
|
||||
else
|
||||
rm('docs/output/api')
|
||||
args = [
|
||||
commonArgs...
|
||||
'--title', 'Atom API Documentation'
|
||||
'-o', 'docs/output/api'
|
||||
'-r', 'docs/README.md'
|
||||
'src/'
|
||||
includePaths...
|
||||
]
|
||||
grunt.util.spawn({cmd, args, opts}, done)
|
||||
|
||||
grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', ->
|
||||
done = @async()
|
||||
args = [commonArgs..., '--noOutput', 'src/']
|
||||
grunt.util.spawn({cmd, args, opts}, done)
|
||||
downloadIncludes (error, includePaths) ->
|
||||
if error?
|
||||
done(error)
|
||||
else
|
||||
args = [
|
||||
commonArgs...
|
||||
'--noOutput'
|
||||
'src/'
|
||||
includePaths...
|
||||
]
|
||||
grunt.util.spawn({cmd, args, opts}, done)
|
||||
|
||||
grunt.registerTask 'missing-docs', 'Generate stats about the doc coverage', ->
|
||||
done = @async()
|
||||
args = [commonArgs..., '--noOutput', '--missing', 'src/']
|
||||
grunt.util.spawn({cmd, args, opts}, done)
|
||||
downloadIncludes (error, includePaths) ->
|
||||
if error?
|
||||
done(error)
|
||||
else
|
||||
args = [
|
||||
commonArgs...
|
||||
'--noOutput'
|
||||
'--missing'
|
||||
'src/'
|
||||
includePaths...
|
||||
]
|
||||
grunt.util.spawn({cmd, args, opts}, done)
|
||||
|
||||
grunt.registerTask 'copy-docs', 'Copies over latest API docs to atom-docs', ->
|
||||
done = @async()
|
||||
@@ -94,3 +131,27 @@ module.exports = (grunt) ->
|
||||
grunt.util.spawn({cmd, args, opts}, callback)
|
||||
|
||||
grunt.util.async.waterfall [fetchTag, stageDocs, fetchSha, commitChanges, pushOrigin, pushHeroku], done
|
||||
|
||||
downloadFileFromRepo = ({repo, file}, callback) ->
|
||||
uri = "https://raw.github.com/atom/#{repo}/master/#{file}"
|
||||
request uri, (error, response, contents) ->
|
||||
return callback(error) if error?
|
||||
downloadPath = path.join('docs', 'includes', repo, file)
|
||||
fs.writeFile downloadPath, contents, (error) ->
|
||||
callback(error, downloadPath)
|
||||
|
||||
downloadIncludes = (callback) ->
|
||||
includes = [
|
||||
{repo: 'first-mate', file: 'src/grammar.coffee'}
|
||||
{repo: 'first-mate', file: 'src/grammar-registry.coffee'}
|
||||
{repo: 'node-pathwatcher', file: 'src/directory.coffee'}
|
||||
{repo: 'node-pathwatcher', file: 'src/file.coffee'}
|
||||
{repo: 'space-pen', file: 'src/space-pen.coffee'}
|
||||
{repo: 'text-buffer', file: 'src/marker.coffee'}
|
||||
{repo: 'text-buffer', file: 'src/point.coffee'}
|
||||
{repo: 'text-buffer', file: 'src/range.coffee'}
|
||||
{repo: 'text-buffer', file: 'src/text-buffer.coffee'}
|
||||
{repo: 'theorist', file: 'src/model.coffee'}
|
||||
]
|
||||
|
||||
async.map(includes, downloadFileFromRepo, callback)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
grunt.registerTask 'generate-license', 'Generate the license, including the licenses of all dependencies', (mode) ->
|
||||
legalEagle = require 'legal-eagle'
|
||||
done = @async()
|
||||
|
||||
options =
|
||||
path: process.cwd()
|
||||
overrides: require './license-overrides'
|
||||
|
||||
legalEagle options, (err, dependencyLicenses) ->
|
||||
if err?
|
||||
console.error(err)
|
||||
exit 1
|
||||
|
||||
licenseText = getLicenseText(dependencyLicenses)
|
||||
if mode is 'save'
|
||||
targetPath = path.join(grunt.config.get('atom.appDir'), 'LICENSE')
|
||||
fs.writeFileSync(targetPath, licenseText)
|
||||
else
|
||||
console.log licenseText
|
||||
done()
|
||||
|
||||
getLicenseText = (dependencyLicenses) ->
|
||||
{keys} = require 'underscore-plus'
|
||||
text = """
|
||||
Copyright 2014 GitHub, Inc.
|
||||
|
||||
This application bundles the following third-party packages in accordance
|
||||
with the following licenses:\n\n
|
||||
"""
|
||||
names = keys(dependencyLicenses).sort()
|
||||
for name in names
|
||||
{license, source, sourceText} = dependencyLicenses[name]
|
||||
|
||||
text += "-------------------------------------------------------------------------\n\n"
|
||||
text += "Package: #{name}\n"
|
||||
text += "License: #{license}\n"
|
||||
text += "License Source: #{source}\n" if source?
|
||||
if sourceText?
|
||||
text += "Source Text:\n\n"
|
||||
text += sourceText
|
||||
text += '\n'
|
||||
text
|
||||
@@ -1,4 +1,5 @@
|
||||
path = require 'path'
|
||||
runas = null
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{cp, mkdir, rm} = require('./task-helpers')(grunt)
|
||||
@@ -6,6 +7,15 @@ module.exports = (grunt) ->
|
||||
grunt.registerTask 'install', 'Install the built application', ->
|
||||
installDir = grunt.config.get('atom.installDir')
|
||||
shellAppDir = grunt.config.get('atom.shellAppDir')
|
||||
rm installDir
|
||||
mkdir path.dirname(installDir)
|
||||
cp shellAppDir, installDir
|
||||
if process.platform is 'win32'
|
||||
runas ?= require 'runas'
|
||||
copyFolder = path.resolve 'script', 'copy-folder.cmd'
|
||||
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: true) isnt 0
|
||||
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
|
||||
|
||||
createShortcut = path.resolve 'script', 'create-shortcut.cmd'
|
||||
runas('cmd', ['/c', createShortcut, path.join(installDir, 'atom.exe'), 'Atom'])
|
||||
else
|
||||
rm installDir
|
||||
mkdir path.dirname(installDir)
|
||||
cp shellAppDir, installDir
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
module.exports =
|
||||
'aws-sign@0.3.0':
|
||||
repository: 'https://github.com/mikeal/aws-sign'
|
||||
license: 'MIT'
|
||||
source: 'index.js'
|
||||
sourceText: """
|
||||
/*!
|
||||
* knox - auth
|
||||
* Copyright(c) 2010 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
<content omitted>
|
||||
"""
|
||||
'bufferjs@2.0.0':
|
||||
repository: 'https://github.com/coolaj86/node-bufferjs'
|
||||
license: 'MIT'
|
||||
source: 'LICENSE.MIT'
|
||||
sourceText: """
|
||||
Copyright (c) 2010 AJ ONeal (and Contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
'buffers@0.1.1':
|
||||
repository: "http://github.com/substack/node-buffers"
|
||||
license: 'MIT'
|
||||
source: 'README.markdown'
|
||||
sourceText: """
|
||||
<content omitted>
|
||||
license
|
||||
=======
|
||||
|
||||
MIT/X11
|
||||
"""
|
||||
'specificity@0.1.3':
|
||||
repository: 'https://github.com/keeganstreet/specificity'
|
||||
license: 'MIT'
|
||||
source: 'package.json in repository'
|
||||
|
||||
'promzard@0.2.0':
|
||||
license: 'ISC'
|
||||
source: 'LICENSE in the repository'
|
||||
sourceText: """
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
@@ -8,7 +8,7 @@ request = require 'request'
|
||||
module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
baseUrl = "https://www.atom.io/api/packages"
|
||||
baseUrl = "https://atom.io/api/packages"
|
||||
|
||||
packageExists = (packageName, token, callback) ->
|
||||
requestSettings =
|
||||
|
||||
@@ -41,7 +41,7 @@ module.exports = (grunt) ->
|
||||
|
||||
strings =
|
||||
CompanyName: 'GitHub, Inc.'
|
||||
FileDescription: 'The hackable, collaborative editor of tomorrow!'
|
||||
FileDescription: 'The hackable editor'
|
||||
LegalCopyright: 'Copyright (C) 2013 GitHub, Inc. All rights reserved'
|
||||
ProductName: 'Atom'
|
||||
ProductVersion: version
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
fs = require 'fs'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
walkdir = require 'walkdir'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
cp: (source, destination, {filter}={}) ->
|
||||
unless grunt.file.exists(source)
|
||||
grunt.fatal("Cannot copy non-existent #{source.cyan} to #{destination.cyan}")
|
||||
|
||||
try
|
||||
walkdir.sync source, (sourcePath, stats) ->
|
||||
return if filter?.test(sourcePath)
|
||||
copyFile = (sourcePath, destinationPath) ->
|
||||
return if filter?.test(sourcePath)
|
||||
|
||||
destinationPath = path.join(destination, path.relative(source, sourcePath))
|
||||
if stats.isSymbolicLink()
|
||||
grunt.file.mkdir(path.dirname(destinationPath))
|
||||
fs.symlinkSync(fs.readlinkSync(sourcePath), destinationPath)
|
||||
else if stats.isFile()
|
||||
grunt.file.copy(sourcePath, destinationPath)
|
||||
stats = fs.lstatSync(sourcePath)
|
||||
if stats.isSymbolicLink()
|
||||
grunt.file.mkdir(path.dirname(destinationPath))
|
||||
fs.symlinkSync(fs.readlinkSync(sourcePath), destinationPath)
|
||||
else if stats.isFile()
|
||||
grunt.file.copy(sourcePath, destinationPath)
|
||||
|
||||
if grunt.file.exists(destinationPath)
|
||||
fs.chmodSync(destinationPath, fs.statSync(sourcePath).mode)
|
||||
catch error
|
||||
grunt.fatal(error)
|
||||
if grunt.file.exists(destinationPath)
|
||||
fs.chmodSync(destinationPath, fs.statSync(sourcePath).mode)
|
||||
|
||||
if grunt.file.isFile(source)
|
||||
copyFile(source, destination)
|
||||
else
|
||||
try
|
||||
onFile = (sourcePath) ->
|
||||
destinationPath = path.join(destination, path.relative(source, sourcePath))
|
||||
copyFile(sourcePath, destinationPath)
|
||||
onDirectory = (sourcePath) ->
|
||||
if fs.isSymbolicLinkSync(sourcePath)
|
||||
destinationPath = path.join(destination, path.relative(source, sourcePath))
|
||||
copyFile(sourcePath, destinationPath)
|
||||
false
|
||||
else
|
||||
true
|
||||
fs.traverseTreeSync source, onFile, onDirectory
|
||||
catch error
|
||||
grunt.fatal(error)
|
||||
|
||||
grunt.verbose.writeln("Copied #{source.cyan} to #{destination.cyan}.")
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
path = require 'path'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
grunt.registerTask 'update-octicons', 'Update octicon font and LESS variables', ->
|
||||
pathToOcticons = path.resolve('..', 'octicons')
|
||||
if grunt.file.isDir(pathToOcticons)
|
||||
# Copy font-file
|
||||
fontSrc = path.join(pathToOcticons, 'octicons', 'octicons.woff')
|
||||
fontDest = path.resolve('static', 'octicons.woff')
|
||||
grunt.file.copy(fontSrc, fontDest)
|
||||
|
||||
# Update Octicon UTF codes
|
||||
glyphsSrc = path.join(pathToOcticons, 'data', 'glyphs.yml')
|
||||
output = []
|
||||
for {css, code} in grunt.file.readYAML(glyphsSrc)
|
||||
output.push "@#{css}: \"\\#{code}\";"
|
||||
|
||||
octiconUtfDest = path.resolve('static', 'variables', 'octicon-utf-codes.less')
|
||||
grunt.file.write(octiconUtfDest, "#{output.join('\n')}\n")
|
||||
else
|
||||
grunt.log.error("octicons repo must be cloned to #{pathToOcticons}")
|
||||
false
|
||||
@@ -0,0 +1,58 @@
|
||||
# Welcome to the Atom API Documentation
|
||||
|
||||

|
||||
|
||||
## FAQ
|
||||
|
||||
### Where do I start?
|
||||
|
||||
Check out [EditorView][EditorView] and [Editor][Editor] classes for a good
|
||||
overview of the main editor API.
|
||||
|
||||
### How do I access these classes?
|
||||
|
||||
Check out the [Atom][Atom] class docs to see what globals are available and
|
||||
what they provide.
|
||||
|
||||
You can also require many of these classes in your package via:
|
||||
|
||||
```coffee
|
||||
{EditorView} = require 'atom'
|
||||
```
|
||||
|
||||
The classes available from `require 'atom'` are:
|
||||
* [BufferedProcess][BufferedProcess]
|
||||
* [BufferedNodeProcess][BufferedNodeProcess]
|
||||
* [EditorView][EditorView]
|
||||
* [Git][Git]
|
||||
* [Point][Point]
|
||||
* [Range][Range]
|
||||
* [ScrollView][ScrollView]
|
||||
* [SelectListView][SelectListView]
|
||||
* [View][View]
|
||||
* [WorkspaceView][WorkspaceView]
|
||||
|
||||
### How do I create a package?
|
||||
|
||||
You probably want to read the [creating a package][creating-a-package]
|
||||
doc first and come back here when you are done.
|
||||
|
||||
### Where are the node docs?
|
||||
|
||||
Atom ships with node 0.11.10 and the comprehensive node API docs are available
|
||||
[here][node-docs].
|
||||
|
||||
[Atom]: ../classes/Atom.html
|
||||
[BufferedProcess]: ../classes/BufferedProcess.html
|
||||
[BufferedNodeProcess]: ../classes/BufferedNodeProcess.html
|
||||
[Editor]: ../classes/Editor.html
|
||||
[EditorView]: ../classes/EditorView.html
|
||||
[Git]: ../classes/Git.html
|
||||
[Point]: ../classes/Point.html
|
||||
[Range]: ../classes/Range.html
|
||||
[ScrollView]: ../classes/ScrollView.html
|
||||
[SelectListView]: ../classes/SelectListView.html
|
||||
[View]: ../classes/View.html
|
||||
[WorkspaceView]: ../classes/WorkspaceView.html
|
||||
[creating-a-package]: https://atom.io/docs/latest/creating-a-package
|
||||
[node-docs]: http://nodejs.org/docs/v0.11.10/api
|
||||
@@ -0,0 +1,76 @@
|
||||
## Configuration API
|
||||
|
||||
### Reading Config Settings
|
||||
|
||||
If you are writing a package that you want to make configurable, you'll need to
|
||||
read config settings via the `atom.config` global. You can read the current
|
||||
value of a namespaced config key with `atom.config.get`:
|
||||
|
||||
```coffeescript
|
||||
# read a value with `config.get`
|
||||
@showInvisibles() if atom.config.get "editor.showInvisibles"
|
||||
```
|
||||
|
||||
Or you can use the `::observeConfig` to track changes from any view object.
|
||||
|
||||
```coffeescript
|
||||
class MyView extends View
|
||||
initialize: ->
|
||||
@observeConfig 'editor.fontSize', () =>
|
||||
@adjustFontSize()
|
||||
```
|
||||
|
||||
The `::observeConfig` method will call the given callback immediately with the
|
||||
current value for the specified key path, and it will also call it in the future
|
||||
whenever the value of that key path changes.
|
||||
|
||||
Subscriptions made with `observeConfig` are automatically canceled when the
|
||||
view is removed. You can cancel config subscriptions manually via the
|
||||
`unobserveConfig` method.
|
||||
|
||||
```coffeescript
|
||||
view1.unobserveConfig() # unobserve all properties
|
||||
```
|
||||
|
||||
You can add the ability to observe config values to non-view classes by
|
||||
extending their prototype with the `ConfigObserver` mixin:
|
||||
|
||||
```coffeescript
|
||||
{ConfigObserver} = require 'atom'
|
||||
|
||||
class MyClass
|
||||
ConfigObserver.includeInto(this)
|
||||
|
||||
constructor: ->
|
||||
@observeConfig 'editor.showInvisibles', -> # ...
|
||||
|
||||
destroy: ->
|
||||
@unobserveConfig()
|
||||
```
|
||||
|
||||
### Writing Config Settings
|
||||
|
||||
The `atom.config` database is populated on startup from `~/.atom/config.cson`,
|
||||
but you can programmatically write to it with `atom.config.set`:
|
||||
|
||||
```coffeescript
|
||||
# basic key update
|
||||
atom.config.set("core.showInvisibles", true)
|
||||
```
|
||||
|
||||
You should never mutate the value of a config key, because that would circumvent
|
||||
the notification of observers. You can however use methods like `pushAtKeyPath`,
|
||||
`unshiftAtKeyPath`, and `removeAtKeyPath` to manipulate mutable config values.
|
||||
|
||||
```coffeescript
|
||||
atom.config.pushAtKeyPath("core.disabledPackages", "wrap-guide")
|
||||
atom.config.removeAtKeyPath("core.disabledPackages", "terminal")
|
||||
```
|
||||
|
||||
You can also use `setDefaults`, which will assign default values for keys that
|
||||
are always overridden by values assigned with `set`. Defaults are not written
|
||||
out to the the `config.json` file to prevent it from becoming cluttered.
|
||||
|
||||
```coffeescript
|
||||
atom.config.setDefaults("editor", fontSize: 18, showInvisibles: true)
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
# Globals
|
||||
|
||||
Atom exposes several services through singleton objects accessible via the
|
||||
`atom` global:
|
||||
|
||||
* atom
|
||||
* workspace:
|
||||
Manipulate and query the state of the user interface for the current
|
||||
window. Open editors, manipulate panes.
|
||||
* workspaceView:
|
||||
Similar to workspace, but provides access to the root of all views in the
|
||||
current window.
|
||||
* project:
|
||||
Access the directory associated with the current window. Load editors,
|
||||
perform project-wide searches, register custom openers for special file
|
||||
types.
|
||||
* config:
|
||||
Read, write, and observe user configuration settings.
|
||||
* keymap:
|
||||
Add and query the currently active keybindings.
|
||||
* deserializers:
|
||||
Deserialize instances from their state objects and register deserializers.
|
||||
* packages:
|
||||
Activate, deactivate, and query user packages.
|
||||
* themes:
|
||||
Activate, deactivate, and query user themes.
|
||||
* contextMenu:
|
||||
Register context menus.
|
||||
* menu:
|
||||
Register application menus.
|
||||
* pasteboard:
|
||||
Read from and write to the system pasteboard.
|
||||
* syntax:
|
||||
Assign and query syntactically-scoped properties.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Keymaps In-Depth
|
||||
|
||||
## Structure of a Keymap File
|
||||
|
||||
Keymap files are encoded as JSON or CSON files containing nested hashes. They
|
||||
work much like stylesheets, but instead of applying style properties to elements
|
||||
matching the selector, they specify the meaning of keystrokes on elements
|
||||
matching the selector. Here is an example of some bindings that apply when
|
||||
keystrokes pass through elements with the class `.editor`:
|
||||
|
||||
```coffee
|
||||
'.editor':
|
||||
'cmd-delete': 'editor:backspace-to-beginning-of-line'
|
||||
'alt-backspace': 'editor:backspace-to-beginning-of-word'
|
||||
'ctrl-A': 'editor:select-to-first-character-of-line'
|
||||
'ctrl-shift-e': 'editor:select-to-end-of-line'
|
||||
'cmd-left': 'editor:move-to-first-character-of-line'
|
||||
|
||||
'.editor:not(.mini)'
|
||||
'cmd-alt-[': 'editor:fold-current-row'
|
||||
'cmd-alt-]': 'editor:unfold-current-row'
|
||||
```
|
||||
|
||||
Beneath the first selector are several bindings, mapping specific *keystroke
|
||||
patterns* to *commands*. When an element with the `.editor` class is focused and
|
||||
`cmd-delete` is pressed, an custom DOM event called
|
||||
`editor:backspace-to-beginning-of-line` is emitted on the `.editor` element.
|
||||
|
||||
The second selector group also targets editors, but only if they don't have the
|
||||
`.mini` class. In this example, the commands for code folding don't really make
|
||||
sense on mini-editors, so the selector restricts them to regular editors.
|
||||
|
||||
### Keystroke Patterns
|
||||
|
||||
Keystroke patterns express one or more keystrokes combined with optional
|
||||
modifier keys. For example: `ctrl-w v`, or `cmd-shift-up`. A keystroke is
|
||||
composed of the following symbols, separated by a `-`. A multi-keystroke pattern
|
||||
can be expressed as keystroke patterns separated by spaces.
|
||||
|
||||
|
||||
| Type | Examples
|
||||
| --------------------|----------------------------
|
||||
| Character literals | `a` `4` `$`
|
||||
| Modifier keys | `cmd` `ctrl` `alt` `shift`
|
||||
| Special keys | `enter` `escape` `backspace` `delete` `tab` `home` `end` `pageup` `pagedown` `left` `right` `up` `down`
|
||||
|
||||
### Commands
|
||||
|
||||
Commands are custom DOM events that are triggered when a keystroke matches a
|
||||
binding. This allows user interface code to listen for named commands without
|
||||
specifying the specific keybinding that triggers it. For example, the following
|
||||
code sets up {EditorView} to listen for commands to move the cursor to the first
|
||||
character of the current line:
|
||||
|
||||
```coffee
|
||||
class EditorView
|
||||
listenForEvents: ->
|
||||
@command 'editor:move-to-first-character-of-line', =>
|
||||
@editor.moveCursorToFirstCharacterOfLine()
|
||||
```
|
||||
|
||||
The `::command` method is basically an enhanced version of jQuery's `::on`
|
||||
method that listens for a custom DOM event and adds some metadata to the DOM,
|
||||
which is read by the command palette.
|
||||
|
||||
When you are looking to bind new keys, it is often useful to use the command
|
||||
palette (`ctrl-shift-p`) to discover what commands are being listened for in a
|
||||
given focus context. Commands are "humanized" following a simple algorithm, so a
|
||||
command like `editor:fold-current-row` would appear as "Editor: Fold Current
|
||||
Row".
|
||||
|
||||
### Specificity and Cascade Order
|
||||
|
||||
As is the case with CSS applying styles, when multiple bindings match for a
|
||||
single element, the conflict is resolved by choosing the most *specific*
|
||||
selector. If two matching selectors have the same specificity, the binding
|
||||
for the selector appearing later in the cascade takes precedence.
|
||||
|
||||
Currently, there's no way to specify selector ordering within a single keymap,
|
||||
because JSON objects do not preserve order. We eventually plan to introduce a
|
||||
custom CSS-like file format for keymaps that allows for ordering within a single
|
||||
file. For now, we've opted to handle cases where selector ordering is critical
|
||||
by breaking the keymap into two separate files, such as `snippets-1.cson` and
|
||||
`snippets-2.cson`.
|
||||
|
||||
## Overloading Key Bindings
|
||||
|
||||
Occasionally, it makes sense to layer multiple actions on top of the same key
|
||||
binding. An example of this is the snippets package. Snippets are inserted by
|
||||
typing a snippet prefix such as `for` and then pressing `tab`. Every time `tab`
|
||||
is pressed, we want to execute code attempting to expand a snippet if one exists
|
||||
for the text preceding the cursor. If a snippet *doesn't* exist, we want `tab`
|
||||
to actually insert whitespace.
|
||||
|
||||
To achieve this, the snippets package makes use of the `.abortKeyBinding()`
|
||||
method on the event object representing the `snippets:expand` command.
|
||||
|
||||
```coffee-script
|
||||
# pseudo-code
|
||||
editor.command 'snippets:expand', (e) =>
|
||||
if @cursorFollowsValidPrefix()
|
||||
@expandSnippet()
|
||||
else
|
||||
e.abortKeyBinding()
|
||||
```
|
||||
|
||||
When the event handler observes that the cursor does not follow a valid prefix,
|
||||
it calls `e.abortKeyBinding()`, telling the keymap system to continue searching
|
||||
for another matching binding.
|
||||
|
||||
## Step-by-Step: How Keydown Events are Mapped to Commands
|
||||
|
||||
* A keydown event occurs on a *focused* element.
|
||||
* Starting at the focused element, the keymap walks upward towards the root of
|
||||
the document, searching for the most specific CSS selector that matches the
|
||||
current DOM element and also contains a keystroke pattern matching the keydown
|
||||
event.
|
||||
* When a matching keystroke pattern is found, the search is terminated and the
|
||||
pattern's corresponding command is triggered on the current element.
|
||||
* If `.abortKeyBinding()` is called on the triggered event object, the search
|
||||
is resumed, triggering a binding on the next-most-specific CSS selector for
|
||||
the same element or continuing upward to parent elements.
|
||||
* If no bindings are found, the event is handled by Chromium normally.
|
||||
@@ -71,27 +71,3 @@ will only attempt to call deserialize if the two versions match, and otherwise
|
||||
return undefined. We plan on implementing a migration system in the future, but
|
||||
this at least protects you from improperly deserializing old state. If you find
|
||||
yourself in dire need of the migration system, let us know.
|
||||
|
||||
### Deferred Package Deserializers
|
||||
|
||||
If your package defers loading on startup with an `activationEvents` property in
|
||||
its `package.cson`, your deserializers won't be loaded until your package is
|
||||
activated. If you want to deserialize an object from your package on startup,
|
||||
this could be a problem.
|
||||
|
||||
The solution is to also supply a `deferredDeserializers` array in your
|
||||
`package.cson` with the names of all your deserializers. When Atom attempts to
|
||||
deserialize some state whose `deserializer` matches one of these names, it will
|
||||
load your package first so it can register any necessary deserializers before
|
||||
proceeding.
|
||||
|
||||
For example, the markdown preview package doesn't fully load until a preview is
|
||||
triggered. But if you refresh a window with a preview pane, it loads the
|
||||
markdown package early so Atom can deserialize the view correctly.
|
||||
|
||||
```coffee-script
|
||||
# markdown-preview/package.cson
|
||||
'activationEvents': 'markdown-preview:toggle': '.editor'
|
||||
'deferredDeserializers': ['MarkdownPreviewView']
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,142 @@
|
||||
# Contributing to Atom Packages
|
||||
|
||||
The following is a set of guidelines for contributing to Atom packages, which
|
||||
are hosted in the [Atom Organization](https://github.com/atom) on GitHub. If
|
||||
you're unsure which package is causing your problem or if you're having an issue
|
||||
with Atom core, please use the feedback form in the application or email
|
||||
[atom@github.com](mailto:atom@github.com).
|
||||
|
||||
## Submitting Issues
|
||||
|
||||
* Include screenshots and animated GIFs whenever possible; they are immensely
|
||||
helpful.
|
||||
* Include the behavior you expected and other places you've seen that behavior
|
||||
such as Emacs, vi, Xcode, etc.
|
||||
* Check the dev tools (`alt-cmd-i`) for errors and stack traces to include.
|
||||
* Check Console.app for stack traces to include if reporting a crash.
|
||||
* Perform a cursory search to see if a similar issue has already been submitted.
|
||||
|
||||
## Hacking on Packages
|
||||
|
||||
### Cloning
|
||||
|
||||
The first step is creating your own clone. You can of course do this manually
|
||||
with git, or you can use the `apm develop` command to create a clone based on
|
||||
the package's `repository` field in the `package.json`.
|
||||
|
||||
For example, if you want to make changes to the `tree-view` package, run the
|
||||
following command:
|
||||
|
||||
```
|
||||
> apm develop tree-view
|
||||
Cloning https://github.com/atom/tree-view ✓
|
||||
Installing modules ✓
|
||||
~/.atom/dev/packages/tree-view -> ~/github/tree-view
|
||||
```
|
||||
|
||||
This clones the `tree-view` repository to `~/github`. If you prefer a different
|
||||
path, specify it via the `ATOM_REPOS_HOME` environment variable.
|
||||
|
||||
### Running in Development Mode
|
||||
|
||||
Editing a package in Atom is a bit of a circular experience: you're using Atom
|
||||
to modify itself. What happens if you temporarily break something? You don't
|
||||
want the version of Atom you're using to edit to become useless in the process.
|
||||
For this reason, you'll only want to load packages in **development mode** while
|
||||
you are working on them. You'll perform your editing in **stable mode**, only
|
||||
switching to development mode to test your changes.
|
||||
|
||||
To open a development mode window, use the "Application: Open Dev" command,
|
||||
which is normally bound to `cmd-shift-o`. You can also run dev mode from the
|
||||
command line with `atom --dev`.
|
||||
|
||||
To load your package in development mode, create a symlink to it in
|
||||
`~/.atom/dev/packages`. This occurs automatically when you clone the package
|
||||
with `apm develop`. You can also run `apm link --dev` and `apm unlink --dev`
|
||||
from the package directory to create and remove dev-mode symlinks.
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
Finally, you need to install the cloned package's dependencies by running
|
||||
`apm install` within the package directory. This step is also performed
|
||||
automatically the first time you run `apm develop`, but you'll want to keep
|
||||
dependencies up to date by running `apm update` after pulling upstream changes.
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
### Code Guidelines
|
||||
|
||||
* Include screenshots and animated GIFs in your pull request whenever possible.
|
||||
* Follow the [CoffeeScript](#coffeescript-styleguide),
|
||||
[JavaScript](https://github.com/styleguide/javascript),
|
||||
and [CSS](https://github.com/styleguide/css) styleguides.
|
||||
* Include thoughtfully-worded, well-structured
|
||||
[Jasmine](http://pivotal.github.com/jasmine) specs.
|
||||
* Document new code based on the
|
||||
[Documentation Styleguide](#documentation-styleguide)
|
||||
* End files with a newline.
|
||||
* Place requires in the following order:
|
||||
* Built in Node Modules (such as `path`)
|
||||
* Built in Atom and Atom Shell Modules (such as `atom`, `shell`)
|
||||
* Local Modules (using relative paths)
|
||||
* Place class properties in the following order:
|
||||
* Class methods and properties (methods starting with a `@`)
|
||||
* Instance methods and properties
|
||||
* Avoid platform-dependent code:
|
||||
* Use `require('atom').fs.getHomeDirectory()` to get the home directory.
|
||||
* Use `path.join()` to concatenate filenames.
|
||||
* Use `os.tmpdir()` rather than `/tmp` when you need to reference the
|
||||
temporary directory.
|
||||
|
||||
### Commit Message Guidelines
|
||||
* Use the present tense ("Add feature" not "Added feature")
|
||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
* Limit the first line to 72 characters or less
|
||||
* Reference issues and pull requests liberally
|
||||
* Consider starting the commit message with an applicable emoji:
|
||||
* :lipstick: when improving the format/structure of the code
|
||||
* :racehorse: when improving performance
|
||||
* :non-potable_water: when plugging memory leaks
|
||||
* :memo: when writing docs
|
||||
* :bulb: Check out the [Emoji Cheat Sheet](http://www.emoji-cheat-sheet.com)
|
||||
for more ideas.
|
||||
|
||||
## CoffeeScript Styleguide
|
||||
|
||||
* Use parentheses if it improves code clarity.
|
||||
* Prefer alphabetic keywords to symbolic keywords:
|
||||
* `a is b` instead of `a == b`
|
||||
* Avoid spaces inside the curly-braces of hash literals:
|
||||
* `{a: 1, b: 2}` instead of `{ a: 1, b: 2 }`
|
||||
* Set parameter defaults without spaces around the equal sign:
|
||||
* `clear = (count=1) ->` instead of `clear = (count = 1) ->`
|
||||
* Include a single line of whitespace between methods.
|
||||
|
||||
## Documentation Styleguide
|
||||
|
||||
* Use [TomDoc](http://tomdoc.org).
|
||||
* Use [Markdown](https://daringfireball.net/projects/markdown).
|
||||
* Reference methods and classes in markdown with the custom `{}` notation:
|
||||
* Reference classes with `{ClassName}`
|
||||
* Reference instance methods with `{ClassName::methodName}`
|
||||
* Reference class methods with `{ClassName.methodName}`
|
||||
|
||||
### Example
|
||||
|
||||
```coffee
|
||||
# Public: Disable the package with the given name.
|
||||
#
|
||||
# This method emits multiple events:
|
||||
#
|
||||
# * `package-will-be-disabled` - before the package is disabled.
|
||||
# * `package-disabled` - after the package is disabled.
|
||||
#
|
||||
# name - The {String} name of the package to disable.
|
||||
# options - The {Object} with disable options (default: {}):
|
||||
# :trackTime - `true` to track the amount of time disabling took.
|
||||
# :ignoreErrors - `true` to catch and ignore errors thrown.
|
||||
# callback - The {Function} to call after the package has been disabled.
|
||||
#
|
||||
# Returns `undefined`.
|
||||
disablePackage: (name, options, callback) ->
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
## Converting a TextMate Bundle
|
||||
|
||||
This guide will show you how to convert a [TextMate][TextMate] bundle to an
|
||||
Atom package.
|
||||
|
||||
Converting a TextMate bundle will allow you to use its editor preferences,
|
||||
snippets, and colorization inside Atom.
|
||||
|
||||
### Install apm
|
||||
|
||||
The `apm` command line utility that ships with Atom supports converting
|
||||
a TextMate bundle to an Atom package.
|
||||
|
||||
Check that you have `apm` installed by running the following command in your
|
||||
terminal:
|
||||
|
||||
```sh
|
||||
apm help init
|
||||
```
|
||||
|
||||
You should see a message print out with details about the `apm init` command.
|
||||
|
||||
If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu
|
||||
to install the `apm` and `atom` commands.
|
||||
|
||||
### Convert the Package
|
||||
|
||||
Let's convert the TextMate bundle for the [R][R] programming language. You can find other existing TextMate bundles [here][TextMateOrg].
|
||||
|
||||
You can convert the R bundle with the following command:
|
||||
|
||||
```sh
|
||||
apm init --package ~/.atom/packages/language-r --convert https://github.com/textmate/r.tmbundle
|
||||
```
|
||||
|
||||
You can now browse to `~/.atom/packages/language-r` to see the converted bundle.
|
||||
|
||||
:tada: Your new package is now ready to use, launch Atom and open a `.r` file in
|
||||
the editor to see it in action!
|
||||
|
||||
### Further Reading
|
||||
|
||||
* Check out [Publishing a Package](publish-a-package.html) for more information
|
||||
on publishing the package you just created to [atom.io][atomio].
|
||||
|
||||
[atomio]: https://atom.io
|
||||
[CSS]: http://en.wikipedia.org/wiki/Cascading_Style_Sheets
|
||||
[LESS]: http://lesscss.org
|
||||
[plist]: http://en.wikipedia.org/wiki/Property_list
|
||||
[R]: http://en.wikipedia.org/wiki/R_(programming_language)
|
||||
[TextMate]: http://macromates.com
|
||||
[TextMateOrg]: https://github.com/textmate/r.tmbundle
|
||||
@@ -0,0 +1,68 @@
|
||||
## Converting a TextMate Theme
|
||||
|
||||
This guide will show you how to convert a [TextMate][TextMate] theme to an Atom
|
||||
theme.
|
||||
|
||||
### Differences
|
||||
|
||||
TextMate themes use [plist][plist] files while Atom themes use [CSS][CSS] or
|
||||
[LESS][LESS] to style the UI and syntax in the editor.
|
||||
|
||||
The utility that converts the theme first parses the theme's plist file and
|
||||
then creates comparable CSS rules and properties that will style Atom similarly.
|
||||
|
||||
### Install apm
|
||||
|
||||
The `apm` command line utility that ships with Atom supports converting
|
||||
a TextMate theme to an Atom theme.
|
||||
|
||||
Check that you have `apm` installed by running the following command in your
|
||||
terminal:
|
||||
|
||||
```sh
|
||||
apm help init
|
||||
```
|
||||
|
||||
You should see a message print out with details about the `apm init` command.
|
||||
|
||||
If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu
|
||||
to install the `apm` and `atom` commands.
|
||||
|
||||
You can now run `apm help init` to see all the options for initializing new
|
||||
packages and themes.
|
||||
|
||||
### Convert the Theme
|
||||
|
||||
Download the theme you wish to convert, you can browse existing TextMate themes
|
||||
[here][TextMateThemes].
|
||||
|
||||
Now, let's say you've downloaded the theme to `~/Downloads/MyTheme.tmTheme`,
|
||||
you can convert the theme with the following command:
|
||||
|
||||
```sh
|
||||
apm init --theme ~/.atom/packages/my-theme --convert ~/Downloads/MyTheme.tmTheme
|
||||
```
|
||||
|
||||
You can browse to `~/.atom/packages/my-theme` to see the converted theme.
|
||||
|
||||
### Activate the Theme
|
||||
|
||||
Now that your theme is installed to `~/.atom/packages` you can enable it
|
||||
by launching Atom and selecting the _Atom > Preferences..._ menu.
|
||||
|
||||
Select the _Themes_ link on the left side and choose _My Theme_ from the
|
||||
__Syntax Theme__ dropdown menu to enable your new theme.
|
||||
|
||||
:tada: Your theme is now enabled, open an editor to see it in action!
|
||||
|
||||
### Further Reading
|
||||
|
||||
* Check out [Publishing a Package](publish-a-package.html) for more information
|
||||
on publishing the theme you just created to [atom.io][atomio].
|
||||
|
||||
[atomio]: https://atom.io
|
||||
[CSS]: http://en.wikipedia.org/wiki/Cascading_Style_Sheets
|
||||
[LESS]: http://lesscss.org
|
||||
[plist]: http://en.wikipedia.org/wiki/Property_list
|
||||
[TextMate]: http://macromates.com
|
||||
[TextMateThemes]: http://wiki.macromates.com/Themes/UserSubmittedThemes
|
||||
@@ -44,13 +44,13 @@ key mappings your package needs to load. If not specified, mappings in the
|
||||
_keymaps_ directory are added alphabetically.
|
||||
- `menus`(**Optional**): an Array of Strings identifying the order of
|
||||
the menu mappings your package needs to load. If not specified, mappings
|
||||
in the _keymap_ directory are added alphabetically.
|
||||
in the _menus_ directory are added alphabetically.
|
||||
- `snippets` (**Optional**): an Array of Strings identifying the order of the
|
||||
snippets your package needs to load. If not specified, snippets in the
|
||||
_snippets_ directory are added alphabetically.
|
||||
- `activationEvents` (**Optional**): an Array of Strings identifying events that
|
||||
trigger your package's activation. You can delay the loading of your package
|
||||
until one of these events is trigged.
|
||||
until one of these events is triggered.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -154,7 +154,7 @@ loaded in alphabetical order. An optional `keymaps` array in your _package.json_
|
||||
can specify which keymaps to load and in what order.
|
||||
|
||||
|
||||
Keybindings are executed by determining which element the keypress occured on. In
|
||||
Keybindings are executed by determining which element the keypress occurred on. In
|
||||
the example above, `changer:magic` command is executed when pressing `ctrl-V` on
|
||||
the `.tree-view-scroller` element.
|
||||
|
||||
@@ -172,7 +172,7 @@ specify which menus to load and in what order.
|
||||
It's recommended that you create an application menu item for common actions
|
||||
with your package that aren't tied to a specific element:
|
||||
|
||||
```coffee-script
|
||||
```coffeescript
|
||||
'menu': [
|
||||
{
|
||||
'label': 'Packages'
|
||||
@@ -202,7 +202,7 @@ by other packages in the order which they were loaded.
|
||||
It's recommended to specify a context menu item for commands that are linked to
|
||||
specific parts of the interface, like adding a file in the tree-view:
|
||||
|
||||
```coffee-script
|
||||
```coffeescript
|
||||
'context-menu':
|
||||
'.tree-view':
|
||||
'Add file': 'tree-view:add-file'
|
||||
@@ -381,7 +381,7 @@ Additional libraries can be found by browsing Atom's *node_modules* folder.
|
||||
[apm]: https://github.com/atom/apm
|
||||
[git-tag]: http://git-scm.com/book/en/Git-Basics-Tagging
|
||||
[wrap-guide]: https://github.com/atom/wrap-guide/
|
||||
[keymaps]: internals/keymaps.md
|
||||
[keymaps]: advanced/keymaps.md
|
||||
[theme-variables]: theme-variables.md
|
||||
[tm-tokens]: http://manual.macromates.com/en/language_grammars.html
|
||||
[spacepen]: https://github.com/nathansobo/space-pen
|
||||
|
||||
+58
-55
@@ -1,124 +1,127 @@
|
||||
# Creating a Theme
|
||||
|
||||
Atom's interface is rendered using HTML, and it's styled via [LESS] (a superset
|
||||
of CSS). Don't worry if you haven't heard of LESS before; it's just like CSS, but
|
||||
with a few handy extensions.
|
||||
of CSS). Don't worry if you haven't heard of LESS before; it's just like CSS,
|
||||
but with a few handy extensions.
|
||||
|
||||
Since CSS is the basis of the theming system, we can load multiple themes within
|
||||
Atom, and the themes behave just as they would on a website. Themes loaded first
|
||||
are overridden by themes which are loaded later. The order of theme loading is
|
||||
controlled within the Settings/Themes pane.
|
||||
Atom supports two types of themes: _UI_ and _syntax_. UI themes style
|
||||
elements such as the tree view, the tabs, drop-down lists, and the status bar.
|
||||
Syntax themes style the code inside the editor.
|
||||
|
||||
This flexibility is helpful for users that prefer a light interface with a dark
|
||||
syntax theme. Atom currently has only interface and syntax themes, but it is
|
||||
possible to create a theme to style something specific — say, changing
|
||||
the colors in the tree view or creating a language specific syntax theme.
|
||||
Themes can be installed and changed from the settings view which you can open
|
||||
by selecting the _Atom > Preferences..._ menu and navigating to the _Themes_
|
||||
section on the left hand side.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Themes are pretty straight forward but it's still helpful to be familiar with
|
||||
Themes are pretty straightforward but it's still helpful to be familiar with
|
||||
a few things before starting:
|
||||
|
||||
* LESS is a superset of CSS, but it has some really handy features like
|
||||
variables. If you aren't familiar with its syntax take a few minutes
|
||||
variables. If you aren't familiar with its syntax, take a few minutes
|
||||
to [familiarize yourself][less-tutorial].
|
||||
* You may also want to review the concept of a _[package.json]_, too. This file
|
||||
is used to help distribute your theme to Atom users.
|
||||
|
||||
There are two types of themes you can create: syntax themes and interface themes.
|
||||
The differences between them are simply a matter of what they target and what
|
||||
they provide. Syntax themes focus on the entire editor pane, while interface themes
|
||||
target elements which are outside of the editor.
|
||||
is used to help distribute your theme to Atom users.
|
||||
* Your theme's _package.json_ must contain a `"theme"` key with a value
|
||||
of `"ui"` or `"syntax"` for Atom to recognize and load it as a theme.
|
||||
* You can find existing themes to install or fork on [atom.io](atomio).
|
||||
|
||||
## Creating a Syntax Theme
|
||||
|
||||
Let's create your first theme.
|
||||
|
||||
To get started, hit `cmd-shift-P`, and start typing "Generate Theme" to generate
|
||||
a package. Select "Generate Theme," and you'll be asked for a theme name. Let's
|
||||
call ours _motif_.
|
||||
To get started, hit `cmd-shift-P`, and start typing "Generate Syntax Theme" to
|
||||
generate a new theme package. Select "Generate Syntax Theme," and you'll be
|
||||
asked for the path where your theme will be created. Let's call ours _motif_.
|
||||
|
||||
Atom will pop open a new window, showing the _motif_ theme, with a default set of
|
||||
folders and files created for us. If you hit `cmd-,` and navigate to the Themes
|
||||
menu option, you'll see the `motif` theme already available. Drag it over from
|
||||
"Enabled Themes" to "Available Themes."
|
||||
Atom will pop open a new window, showing the _motif_ theme, with a default set
|
||||
of folders and files created for us. If you open the settings view (`cmd-,`)
|
||||
and navigate to the _Themes_ section on the left, you'll see the _Motif_ theme
|
||||
listed in the _Syntax Theme_ drop-down. Select it from the menu to activate it,
|
||||
now when you open an editor you should see that your new _motif_ theme in
|
||||
action.
|
||||
|
||||
Open up _stylesheets/colors.less_ to change the various colors variables which
|
||||
have been already been defined. For example, turn `@red` into `#f4c2c1`.
|
||||
|
||||
Then, open _stylesheets/base.less_, and modify the various syntax CSS selectors
|
||||
that have been already been defined. Each of these selectors represents a different
|
||||
part of the Atom window. Themes that don't need to modify a particular region
|
||||
can simply remove the selectors they don't need.
|
||||
Then open _stylesheets/base.less_ and modify the various selectors that have
|
||||
been already been defined. These selectors style different parts of code in the
|
||||
editor such as comments, strings and the line numbers in the gutter.
|
||||
|
||||
As an example, let's make the `.gutter` `background-color` into `@red`.
|
||||
|
||||
Reload Atom by hitting `cmd-r` to see the changes you made reflected in your Atom
|
||||
window. Pretty neat!
|
||||
Reload Atom by pressing `cmd-alt-option-L` to see the changes you made reflected
|
||||
in your Atom window. Pretty neat!
|
||||
|
||||
## Creating an Interface Theme
|
||||
|
||||
Interface themes **must** provide a `ui-variables.less` file which contains all
|
||||
Interface themes **must** provide a `ui-variables.less` file which contains all
|
||||
of the variables provided by the [core themes][ui-variables].
|
||||
|
||||
To create an interface UI theme, do the following:
|
||||
|
||||
1. Fork one of the following repos
|
||||
1. [atom-dark-ui]
|
||||
1. [atom-light-ui]
|
||||
1. Open a terminal in the forked theme's directory
|
||||
1. Open your new theme in a Dev Mode Atom window (run `atom -d .` in the terminal or use the __View > Developer > Open in Dev Mode__ menu)
|
||||
1. Change the name of the theme in the theme's `package.json` file
|
||||
1. Run `apm link` to tell Atom about your new theme
|
||||
1. Reload Atom (`cmd-r`)
|
||||
1. Enable the theme via the themes panel in settings
|
||||
1. Make changes! Since you opened the theme in a Dev Mode window, changes will
|
||||
1. Fork one of the following repositories:
|
||||
* [atom-dark-ui]
|
||||
* [atom-light-ui]
|
||||
2. Clone the forked repository to the local filesystem
|
||||
3. Open a terminal in the forked theme's directory
|
||||
4. Open your new theme in a Dev Mode Atom window run `atom --dev .` in the
|
||||
terminal or use the _View > Developer > Open in Dev Mode_ menu)
|
||||
5. Change the name of the theme in the theme's `package.json` file
|
||||
6. Run `apm link` to symlink your repository to `~/.atom/packages`
|
||||
7. Reload Atom using `cmd-alt-ctrl-L`
|
||||
8. Enable the theme via _UI Theme_ drop-down in the _Themes_ section of the
|
||||
settings view
|
||||
9. Make changes! Since you opened the theme in a Dev Mode window, changes will
|
||||
be instantly reflected in the editor without having to reload.
|
||||
|
||||
## Development workflow
|
||||
|
||||
There are a few of tools to help make theme development faster.
|
||||
There are a few of tools to help make theme development faster and easier.
|
||||
|
||||
### Live Reload
|
||||
|
||||
Reloading by hitting `cmd-r` after you make changes to your theme is less than ideal.
|
||||
Atom supports [live updating][livereload] of styles on Dev Mode Atom windows.
|
||||
Reloading by hitting `cmd-alt-ctrl-L` after you make changes to your theme is
|
||||
less than ideal. Atom supports [live updating][livereload] of styles on Dev Mode
|
||||
Atom windows.
|
||||
|
||||
To enable a Dev Mode window:
|
||||
|
||||
1. Open your theme directory in a dev window by either going to the
|
||||
__View > Developer > Open in Dev Mode__ menu or by hitting the `cmd-shift-o`
|
||||
shortcut
|
||||
1. Make a change to your theme file and save it. Your change should be
|
||||
immediately applied!
|
||||
__View > Developer > Open in Dev Mode__ menu or by hitting the `cmd-shift-o`
|
||||
shortcut
|
||||
2. Make a change to your theme file and save it. Your change should be
|
||||
immediately applied!
|
||||
|
||||
If you'd like to reload all the styles at any time, you can use the shortcut
|
||||
`cmd-ctrl-R`.
|
||||
`cmd-ctrl-shift-r`.
|
||||
|
||||
### Developer Tools
|
||||
|
||||
Atom is based on the Chrome browser, and supports Chrome's Developer Tools. You
|
||||
can open them by selecting the __View > Toggle Developer Tools__ menu, or by using
|
||||
the `cmd-option-i` shortcut.
|
||||
can open them by selecting the _View > Toggle Developer Tools_ menu, or by
|
||||
using the `cmd-alt-i` shortcut.
|
||||
|
||||
The dev tools allow you to inspect elements and take a look at their CSS
|
||||
properties.
|
||||
|
||||
![devtools-img]
|
||||
|
||||
Check out Google's [extensive tutorial][devtools-tutorial] for a short introduction.
|
||||
Check out Google's [extensive tutorial][devtools-tutorial] for a short
|
||||
introduction.
|
||||
|
||||
### Atom Styleguide
|
||||
|
||||
If you are creating an interface theme, you'll want a way to see how your theme
|
||||
changes affect all the components in the system. The [styleguide] is a page with
|
||||
every component Atom supports rendered.
|
||||
changes affect all the components in the system. The [styleguide] is a page that
|
||||
renders every component Atom supports.
|
||||
|
||||
To open the styleguide, open the command palette (`cmd-shift-P`) and search for
|
||||
_styleguide_, or use the shortcut `cmd-ctrl-shift-g`.
|
||||
|
||||
![styleguide-img]
|
||||
|
||||
[atomio]: http://atom.io/packages
|
||||
[less]: http://lesscss.org/
|
||||
[git]: http://git-scm.com/
|
||||
[atom]: https://atom.io/
|
||||
|
||||
+72
-36
@@ -1,34 +1,57 @@
|
||||
# Customizing Atom
|
||||
|
||||
To change a setting, configure a theme, or install a package just open the
|
||||
Settings pane in the current window by pressing `cmd+,`.
|
||||
Settings view in the current window by pressing `cmd+,`.
|
||||
|
||||
## Changing The Theme
|
||||
|
||||
Because Atom themes are based on CSS, it's possible (and encouraged) to have
|
||||
multiple themes active at the same time. Atom comes with both light and dark
|
||||
interface themes as well as several syntax themes (you can also [create your
|
||||
own][create-theme]).
|
||||
Atom comes with both light and dark UI themes as well as several syntax themes.
|
||||
You are also encouraged to [create or fork][create-theme] your own theme.
|
||||
|
||||
To change the active themes just open the Settings pane (`cmd-,`) and select the
|
||||
`Themes` tab. You can install non-bundled themes by going to the `Available
|
||||
Themes` section on the `Packages` tab within the Settings panel.
|
||||
To change the active theme just open the Settings view (`cmd-,`) and select the
|
||||
`Themes` section from the left hand side. You will see a drop-down menu to
|
||||
change the active _Syntax_ and _UI_ themes.
|
||||
|
||||
You can also install more themes from here by browsing the featured themes or
|
||||
searching for a specific theme.
|
||||
|
||||
## Installing Packages
|
||||
|
||||
You can install non-bundled packages by going to the `Available Packages`
|
||||
section on the `Packages` tab within the Settings panel (`cmd-,`).
|
||||
You can install non-bundled packages by going to the `Packages` section on left
|
||||
hand side of the Settings view (`cmd-,`). You will see several featured packages
|
||||
and you can also search for packages from here. The packages listed here have
|
||||
been published to [atom.io](http://atom.io/packages) which is the official
|
||||
registry for Atom packages.
|
||||
|
||||
You can also install packages from the command line using the
|
||||
[apm](https://github.com/atom/apm) command:
|
||||
You can also install packages from the command line using `apm`.
|
||||
|
||||
`apm install <package_name>` to install the latest version.
|
||||
Check that you have `apm` installed by running the following command in your
|
||||
terminal:
|
||||
|
||||
`apm install <package_name>@<package_version>` to install a specific version.
|
||||
```sh
|
||||
apm help install
|
||||
```
|
||||
|
||||
You should see a message print out with details about the `apm install` command.
|
||||
|
||||
If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu
|
||||
to install the `apm` and `atom` commands.
|
||||
|
||||
You can also install packages by using the `apm install` command:
|
||||
|
||||
* `apm install <package_name>` to install the latest version.
|
||||
|
||||
* `apm install <package_name>@<package_version>` to install a specific version.
|
||||
|
||||
For example `apm install emmet@0.1.5` installs the `0.1.5` release of the
|
||||
[Emmet](https://github.com/atom/emmet) package into `~/.atom/packages`.
|
||||
|
||||
You can also use `apm` to find new packages to install:
|
||||
|
||||
* `apm search coffee` to search for CoffeeScript packages.
|
||||
|
||||
* `apm view emmet` to see more information about a specific package.
|
||||
|
||||
## Customizing Key Bindings
|
||||
|
||||
Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors
|
||||
@@ -36,13 +59,13 @@ to apply styles to elements, Atom keymaps use selectors to associate keystrokes
|
||||
with events in specific contexts. Here's a small example, excerpted from Atom's
|
||||
built-in keymaps:
|
||||
|
||||
```coffee-script
|
||||
```coffee
|
||||
'.editor':
|
||||
'enter': 'editor:newline'
|
||||
|
||||
'body':
|
||||
'ctrl-P': 'core:move-up'
|
||||
'ctrl-p': 'core:move-down'
|
||||
'ctrl-b': 'core:move-left'
|
||||
'ctrl-f': 'core:move-right'
|
||||
```
|
||||
|
||||
This keymap defines the meaning of `enter` in two different contexts. In a
|
||||
@@ -55,6 +78,8 @@ By default, `~/.atom/keymap.cson` is loaded when Atom is started. It will always
|
||||
be loaded last, giving you the chance to override bindings that are defined by
|
||||
Atom's core keymaps or third-party packages.
|
||||
|
||||
You can open this file in an editor from the _Atom > Open Your Keymap_ menu.
|
||||
|
||||
You'll want to know all the commands available to you. Open the Settings panel
|
||||
(`cmd-,`) and select the _Keybindings_ tab. It will show you all the keybindings
|
||||
currently in use.
|
||||
@@ -64,16 +89,18 @@ currently in use.
|
||||
Atom loads configuration settings from the `config.cson` file in your _~/.atom_
|
||||
directory, which contains CoffeeScript-style JSON:
|
||||
|
||||
```coffeescript
|
||||
core:
|
||||
excludeVcsIgnoredPaths: true
|
||||
editor:
|
||||
fontSize: 18
|
||||
```coffee
|
||||
'core':
|
||||
'excludeVcsIgnoredPaths': true
|
||||
'editor':
|
||||
'fontSize': 18
|
||||
```
|
||||
|
||||
The configuration itself is grouped by the package name or one of the two core
|
||||
namespaces: `core` and `editor`.
|
||||
|
||||
You can open this file in an editor from the _Atom > Open Your Config_ menu.
|
||||
|
||||
### Configuration Key Reference
|
||||
|
||||
- `core`
|
||||
@@ -108,34 +135,43 @@ namespaces: `core` and `editor`.
|
||||
- `removeTrailingWhitespace`: Enable/disable striping of whitespace at the end of lines (defaults to `true`)
|
||||
- `wrap-guide`
|
||||
- `columns`: Array of hashes with a `pattern` and `column` key to match the
|
||||
the path of the current editor to a column position.
|
||||
the path of the current editor to a column position.
|
||||
|
||||
### Quick Personal Hacks
|
||||
|
||||
### user.coffee
|
||||
### init.coffee
|
||||
|
||||
When Atom finishes loading, it will evaluate _user.coffee_ in your _~/.atom_
|
||||
When Atom finishes loading, it will evaluate _init.coffee_ in your _~/.atom_
|
||||
directory, giving you a chance to run arbitrary personal CoffeeScript code to
|
||||
make customizations. You have full access to Atom's API from code in this file.
|
||||
If customizations become extensive, consider [creating a
|
||||
package][create-a-package].
|
||||
If customizations become extensive, consider [creating a package][create-a-package].
|
||||
|
||||
### user.less
|
||||
You can open this file in an editor from the _Atom > Open Your Init Script_
|
||||
menu.
|
||||
|
||||
This file can also be named _init.js_ and contain JavaScript code.
|
||||
|
||||
### styles.less
|
||||
|
||||
If you want to apply quick-and-dirty personal styling changes without creating
|
||||
an entire theme that you intend to distribute, you can add styles to
|
||||
_user.less_ in your _~/.atom_ directory.
|
||||
an entire theme that you intend to publish, you can add styles to the
|
||||
_styles.less_ file in your _~/.atom_ directory.
|
||||
|
||||
For example, to change the color of the highlighted line number for the line
|
||||
that contains the cursor, you could add the following style to _user.less_:
|
||||
You can open this file in an editor from the _Atom > Open Your Stylesheet_ menu.
|
||||
|
||||
For example, to change the color of the cursor, you could add the following
|
||||
rule to your _~/.atom/styles.less_ file:
|
||||
|
||||
```less
|
||||
@highlight-color: pink;
|
||||
|
||||
.editor .line-number.cursor-line {
|
||||
color: @highlight-color;
|
||||
.editor .cursor {
|
||||
border-color: pink;
|
||||
}
|
||||
```
|
||||
|
||||
Unfamiliar with LESS? Read more about it [here][LESS].
|
||||
|
||||
This file can also be named _styles.css_ and contain CSS.
|
||||
|
||||
[create-a-package]: creating-packages.md
|
||||
[create-theme]: creating-a-theme.md
|
||||
[LESS]: http://www.lesscss.org
|
||||
|
||||
+44
-36
@@ -19,35 +19,30 @@ bindings][key-bindings] section.
|
||||
|
||||
### Working With Files
|
||||
|
||||
Atom windows are scoped to the directory they're opened from. If you launch Atom
|
||||
from the command line everything will be relative to the current directory. This
|
||||
means that the tree view on the left will only show files contained within that
|
||||
directory.
|
||||
|
||||
This can be a useful way to organize multiple projects, as each project will be
|
||||
contained within its own window.
|
||||
Atom windows are scoped to a single directory on disk. If you launch Atom from
|
||||
the command line via the `atom` command and don't specify a path, Atom opens a
|
||||
window for the current working directory. The current window's directory will be
|
||||
visible as the root of the tree view on the left, and also serve as the context
|
||||
for all file-related operations.
|
||||
|
||||
#### Finding Files
|
||||
|
||||
The fastest way to find a file is to use the fuzzy finder. Press `cmd-t` and
|
||||
begin typing the name of the file you're looking for. If you are looking for a
|
||||
file that is already open press `cmd-b` to bring up a searchable list of open
|
||||
files.
|
||||
files. If you are using Git you can use `cmd-shift-b` to search the list of
|
||||
files modified and untracked in your project's repository.
|
||||
|
||||
You can also use the tree view to navigate to a file. To open or move focus to
|
||||
the tree view, press `cmd-\`. You can then navigate to a file using the arrow
|
||||
keys and select it with `return`.
|
||||
You can also use the tree view to navigate to a file. To open and focus the
|
||||
the tree view, press `ctrl-0`. The tree view can be toggled open and closed with
|
||||
`cmd-\`.
|
||||
|
||||
#### Adding, Moving, Deleting Files
|
||||
|
||||
Currently, all file modification is performed via the tree view. To add a file,
|
||||
select a directory in the tree view and press `a`. Then type the name of the
|
||||
file. Any intermediate directories you type will be created automatically if
|
||||
needed.
|
||||
|
||||
To move or rename a file or directory, select it in the tree view and press `m`.
|
||||
|
||||
To delete a file, select it in the tree view and press `delete`.
|
||||
You can add, move, and delete files and folders by right-clicking them in the
|
||||
tree view and selecting the desired operation from the context menu. You can
|
||||
also perform these operations from the keyboard by selecting a file or folder
|
||||
and using `a` to add, `m` to move, and `delete` to delete.
|
||||
|
||||
### Searching
|
||||
|
||||
@@ -58,37 +53,50 @@ To search within a buffer use `cmd-f`. To search the entire project use
|
||||
|
||||
#### Navigating By Symbols
|
||||
|
||||
If you want to jump to a method press `cmd-r`. It opens a list of all symbols
|
||||
in the current file.
|
||||
To jump to a symbol such as a method definition, press `cmd-r`. This opens a
|
||||
list of all symbols in the current file, which you can fuzzy filter similarly to
|
||||
`cmd-t`.
|
||||
|
||||
To search for symbols across your project use `cmd-shift-r`, but you'll need to
|
||||
make sure you have a ctags installed and a tags file generated for your project.
|
||||
Also, if you're editing CoffeeScript, it's a good idea to update your `~/.ctags`
|
||||
file to understand the language. Here is [a good example][ctags].
|
||||
To search for symbols across your project, use `cmd-shift-r`. First you'll need
|
||||
to make sure you have `tags` (or `TAGS`) file generated for your project.
|
||||
This can be done by installing [ctags](http://ctags.sourceforge.net/) and
|
||||
running a command such as `ctags -R src/` from the command line in your
|
||||
project's root directory. Using [Homebrew](http://brew.sh/)? Just run
|
||||
`brew install ctags`.
|
||||
|
||||
You can customize how tags are generated by creating your own `.ctags` file
|
||||
in your home directory (`~/.ctags`). Here is [a good example][ctags] to start
|
||||
from.
|
||||
|
||||
### Split Panes
|
||||
|
||||
You can split any editor pane horizontally or vertically by using `cmd-k right` or
|
||||
`cmd-k down`. Once you have a split pane, you can move focus between them with
|
||||
`cmd-k cmd-right` or `cmd-k cmd-down`. To close a pane, close all tabs inside it.
|
||||
You can split any editor pane horizontally or vertically by using `cmd-k right`
|
||||
or `cmd-k down`. Once you have a split pane, you can move focus between them
|
||||
with `cmd-k cmd-right` or `cmd-k cmd-down`. To close a pane, close all its
|
||||
editors with `meta-w`, then press `meta-w` one more time to close the pane. You
|
||||
can configure panes to auto-close with empty in the preferences.
|
||||
|
||||
### Folding
|
||||
|
||||
You can fold everything with `alt-cmd-{` and unfold everything with
|
||||
`alt-cmd-}`. Or, you can fold / unfold by a single level with `alt-cmd-[` and
|
||||
`alt-cmd-]`.
|
||||
You can fold blocks of code by clicking the arrows that appear when you hover
|
||||
your mouse cursor over the gutter. You can also fold and unfold from the
|
||||
keyboard with `alt-cmd-[` and `alt-cmd-]`. To fold everything, use
|
||||
`alt-cmd-shift-{` and to unfold everything use `alt-cmd-shift-}`. You can also
|
||||
fold at a specific indentation level with `cmd-k cmd-N` where N is the
|
||||
indentation depth.
|
||||
|
||||
### Soft-Wrap
|
||||
|
||||
If you want to toggle soft wrap, trigger the command from the command palette.
|
||||
Press `cmd-shift-P` to open the palette, then type "wrap" to find the correct
|
||||
command.
|
||||
command. By default, lines will wrap based on the size of the editor. If you
|
||||
prefer to wrap at a specific line length, toggle "Wrap at preferred line length"
|
||||
in preferences.
|
||||
|
||||
## Configuration
|
||||
|
||||
Press `cmd-,` to display the a settings pane. This serves as the primary
|
||||
interface for adjusting config settings, installing packages and changing
|
||||
themes.
|
||||
Press `cmd-,` to open the Settings view. This is the place to change settings
|
||||
install packages, and change the theme.
|
||||
|
||||
For more advanced configuration see the [customization guide][customization].
|
||||
|
||||
@@ -98,4 +106,4 @@ For more advanced configuration see the [customization guide][customization].
|
||||
[customization]: customizing-atom.md
|
||||
[key-bindings]: customizing-atom.md#customizing-key-bindings
|
||||
[command palette]: https://f.cloud.github.com/assets/1424/1091618/ee7c3554-166a-11e3-9955-aaa61bb5509c.png
|
||||
[ctags]: https://github.com/kevinsawicki/dotfiles/blob/master/.ctags
|
||||
[ctags]: https://github.com/atom/symbols-view/blob/master/lib/.ctags
|
||||
|
||||
+6
-4
@@ -4,10 +4,12 @@
|
||||
* [Customizing Atom](customizing-atom.md)
|
||||
* [Creating a Package](creating-a-package.md)
|
||||
* [Creating a Theme](creating-a-theme.md)
|
||||
* [Publishing a Package](publishing-a-package.md)
|
||||
* [Contributing](contributing.md)
|
||||
|
||||
### Advanced Topics
|
||||
|
||||
* [Configuration](internals/configuration.md)
|
||||
* [Keymaps](internals/keymaps.md)
|
||||
* [Serialization](internals/serialization.md)
|
||||
* [View System](internals/view-system.md)
|
||||
* [Configuration](advanced/configuration.md)
|
||||
* [Keymaps](advanced/keymaps.md)
|
||||
* [Serialization](advanced/serialization.md)
|
||||
* [View System](advanced/view-system.md)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
## Configuration API
|
||||
|
||||
### Reading Config Settings
|
||||
|
||||
If you are writing a package that you want to make configurable, you'll need to
|
||||
read config settings. You can read a value from `config` with `config.get`:
|
||||
|
||||
```coffeescript
|
||||
# read a value with `config.get`
|
||||
@showInvisibles() if config.get "edtior.showInvisibles"
|
||||
```
|
||||
|
||||
Or you can use `observeConfig` to track changes from a view object.
|
||||
|
||||
```coffeescript
|
||||
class MyView extends View
|
||||
initialize: ->
|
||||
@observeConfig 'editor.fontSize', () =>
|
||||
@adjustFontSize()
|
||||
```
|
||||
|
||||
The `observeConfig` method will call the given callback immediately with the
|
||||
current value for the specified key path, and it will also call it in the future
|
||||
whenever the value of that key path changes.
|
||||
|
||||
Subscriptions made with `observeConfig` are automatically canceled when the
|
||||
view is removed. You can cancel config subscriptions manually via the
|
||||
`unobserveConfig` method.
|
||||
|
||||
```coffeescript
|
||||
view1.unobserveConfig() # unobserve all properties
|
||||
```
|
||||
|
||||
You can add the ability to observe config values to non-view classes by
|
||||
extending their prototype with the `ConfigObserver` mixin:
|
||||
|
||||
```coffeescript
|
||||
ConfigObserver = require 'config-observer'
|
||||
_.extend MyClass.prototype, ConfigObserver
|
||||
```
|
||||
|
||||
### Writing Config Settings
|
||||
|
||||
As discussed above, the config database is automatically populated from
|
||||
`config.cson` when Atom is started, but you can programmatically write to it in
|
||||
the following way:
|
||||
|
||||
```coffeescript
|
||||
# basic key update
|
||||
config.set("core.showInvisibles", true)
|
||||
|
||||
config.pushAtKeyPath("core.disabledPackages", "wrap-guide")
|
||||
```
|
||||
|
||||
You can also use `setDefaults`, which will assign default values for keys that
|
||||
are always overridden by values assigned with `set`. Defaults are not written out
|
||||
to the the `config.json` file to prevent it from becoming cluttered.
|
||||
|
||||
```coffeescript
|
||||
config.setDefaults("editor", fontSize: 18, showInvisibles: true)
|
||||
```
|
||||
@@ -1,69 +0,0 @@
|
||||
## Keymaps In-Depth
|
||||
|
||||
### Structure of a Keymap File
|
||||
|
||||
Keymap files are encoded as JSON or CSON files containing nested hashes. The
|
||||
top-level keys of a keymap are **CSS 3 selectors**, which specify a particular
|
||||
context in Atom's interface. Common selectors are `.editor`, which scopes
|
||||
bindings to just work when an editor is focused, and `body`, which scopes
|
||||
bindings globally.
|
||||
|
||||
Beneath the selectors are hashes mapping **keystroke patterns** to
|
||||
**semantic events**. A keystroke pattern looks like the following examples.
|
||||
Note that the last example describes multiple keystrokes in succession:
|
||||
|
||||
- `p`
|
||||
- `2`
|
||||
- `ctrl-p`
|
||||
- `ctrl-alt-cmd-p`
|
||||
- `tab`
|
||||
- `escape`
|
||||
- `enter`
|
||||
- `ctrl-w w`
|
||||
|
||||
A semantic event is the name of the custom event that will be triggered on the
|
||||
target of the keydown event when a key binding matches. You can use the command
|
||||
palette (bound to `cmd-shift-P`), to get a list of relevant events and their bindings
|
||||
in any focused context in Atom.
|
||||
|
||||
### Rules for Mapping A Keydown Event to A Semantic Event
|
||||
|
||||
A keymap's job is to translate a physical keystroke event (like `cmd-D`) into a
|
||||
semantic event (like `editor:duplicate-line`). Whenever a keydown event occurs
|
||||
on a focused element, it bubbles up the DOM as usual. As soon as an element on
|
||||
the bubble path matches a key binding for the keystroke, the binding's semantic
|
||||
event is triggered on the original target of the keydown event. Just as with
|
||||
CSS, if multiple selectors match an element, the most specific selector is
|
||||
favored. If two selectors have the same specificity, the selector that occurs
|
||||
latest in the cascade is favored.
|
||||
|
||||
Currently, there's no way to specify selector ordering within a single keymap,
|
||||
because JSON hashes do not preserve order. Rather than making the format more
|
||||
awkward in order to preserve order, we've opted to handle cases where order is
|
||||
critical by breaking the keymap into two separate files, such as
|
||||
`snippets-1.cson` and `snippets-2.cson`.
|
||||
|
||||
### Overloading Key Bindings
|
||||
|
||||
Occasionally, it makes sense to layer multiple actions on top of the same key
|
||||
binding. An example of this is the snippets package. You expand a snippet by
|
||||
pressing `tab` immediately following a snippet's prefix. But if the cursor is
|
||||
not following a valid snippet prefix, then we want tab to perform its normal
|
||||
action (probably inserting a tab character or the appropriate number of spaces).
|
||||
|
||||
To achieve this, the snippets package makes use of the `abortKeyBinding` method
|
||||
on the event object that's triggered by the binding for `tab`.
|
||||
|
||||
```coffee-script
|
||||
# pseudo-code
|
||||
editor.command 'snippets:expand', (e) =>
|
||||
if @cursorFollowsValidPrefix()
|
||||
@expandSnippet()
|
||||
else
|
||||
e.abortKeyBinding()
|
||||
```
|
||||
|
||||
When the event handler observes that the cursor does not follow a valid prefix,
|
||||
it calls `e.abortKeyBinding()`, which tells the keymap system to continue
|
||||
searching up the cascade for another matching binding. In this case, the default
|
||||
implementation of `tab` ends up getting triggered.
|
||||
@@ -1,63 +0,0 @@
|
||||
## Atom Documentation Format
|
||||
|
||||
This document describes our documentation format, which is markdown with
|
||||
a few rules.
|
||||
|
||||
### Philosophy
|
||||
|
||||
1. Method and argument names **should** clearly communicate its use.
|
||||
1. Use documentation to enhance and not correct method/argument names.
|
||||
|
||||
#### Basic
|
||||
|
||||
In some cases all that's required is a single line. **Do not** feel
|
||||
obligated to write more because we have a format.
|
||||
|
||||
```markdown
|
||||
# Private: Returns the number of pixels from the top of the screen.
|
||||
```
|
||||
|
||||
* **Each method should declare whether it's public or private by using `Public:`
|
||||
or `Private:`** prefix.
|
||||
* Following the colon, there should be a short description (that isn't redundant with the
|
||||
method name).
|
||||
* Documentation should be hard wrapped to 80 columns.
|
||||
|
||||
### Public vs Private
|
||||
|
||||
If a method is public it can be used by other classes (and possibly by
|
||||
the public API). The appropriate steps should be taken to minimize the impact
|
||||
when changing public methods. In some cases that might mean adding an
|
||||
appropriate release note. In other cases it might mean doing the legwork to
|
||||
ensure all affected packages are updated.
|
||||
|
||||
#### Complex
|
||||
|
||||
For complex methods it's necessary to explain exactly what arguments
|
||||
are required and how different inputs effect the operation of the
|
||||
function.
|
||||
|
||||
The idea is to communicate things that the API user might not know about,
|
||||
so repeating information that can be gleaned from the method or argument names
|
||||
is not useful.
|
||||
|
||||
```markdown
|
||||
# Private: Determine the accelerator for a given command.
|
||||
#
|
||||
# * command:
|
||||
# The name of the command.
|
||||
# * keystrokesByCommand:
|
||||
# An {Object} whose keys are commands and the values are Arrays containing
|
||||
# the keystrokes.
|
||||
# * options:
|
||||
# + accelerators:
|
||||
# Boolean to determine whether accelerators should be shown.
|
||||
#
|
||||
# Returns a String containing the keystroke in a format that can be interpreted
|
||||
# by atom shell to provide nice icons where available.
|
||||
#
|
||||
# Raises an Exception if no window is available.
|
||||
```
|
||||
|
||||
* Use curly brackets `{}` to provide links to other classes.
|
||||
* Use `+` for the options list.
|
||||
@@ -1,70 +0,0 @@
|
||||
**Polish the user experience**
|
||||
|
||||
First and foremost, Atom is a **product**. Atom needs to feel familiar and
|
||||
inviting. This includes a solid introductory experience and parity with the most
|
||||
important features of Sublime Text.
|
||||
|
||||
* First launch UI and flow (actions below should be easily discoverable)
|
||||
* Create a new file
|
||||
* Open a project and edit an existing file
|
||||
* Install a package
|
||||
* Change settings (adjust theme, change key bindings, set config options)
|
||||
* How to use command P
|
||||
* Use collaboration internally
|
||||
* How and where to edit keyBinding should be obvious to new users
|
||||
* Finish find and replace in buffer/project
|
||||
* Atom should start < 300ms
|
||||
* Match Sublime's multiple selection functionality (#523)
|
||||
* Fix softwrap bugs
|
||||
* Menus & Context menus
|
||||
* Track usage/engagement of our users (make this opted in?)
|
||||
* Windows support
|
||||
* Reliably and securely auto-update and list what's new
|
||||
* Secure access to the keychain (don't give every package access to the keychain)
|
||||
* Secure access to GitHub (each package can ask to have it's own oauth token)
|
||||
* Don't crash when opening/editing large (> 10Mb) files
|
||||
* Send js and native crash reports to a remote server
|
||||
|
||||
**Lay solid groundwork for a package and theme ecosystem**
|
||||
|
||||
Extensibility is one of Atom's key value propositions, so a smooth experience
|
||||
for creating and maintaining packages is just as important as the user
|
||||
experience. The package development, dependency and publishing workflow needs to
|
||||
be solid. We also want to have a mechanism for clearly communicating with
|
||||
package authors about breaking API changes.
|
||||
|
||||
* Finish APM backend (integrate with GitHub Releases)
|
||||
* Streamline Dev workflow
|
||||
* `apm create` - create package scaffolding
|
||||
* `apm test` - so users can run focused package tests
|
||||
* `apm publish` - should integrate release best practices (ie npm version)
|
||||
* Determine which classes and methods should be included in the public API
|
||||
* Users can find/install/update/fork existing packages and themes
|
||||
|
||||
**Tighten up the view layer**
|
||||
Our current approach to the view layer need some improvement. We want to
|
||||
actively promote the use of the M-V-VM design pattern, provide some declarative
|
||||
event binding mechanisms in the view layer, and improve the performance of the
|
||||
typical package specs. We don't want the current approach to be used as an
|
||||
example in a bunch of new packages, so it's important to improve it now.
|
||||
|
||||
* Add marker view API
|
||||
|
||||
**Get atom.io online with some exciting articles and documentation**
|
||||
We'd love to send our private alpha candidates to a nice site with information
|
||||
about what Atom is, the philosophies and technologies behind it, and guidance
|
||||
for how to get started.
|
||||
|
||||
* Design and create www.atom.io
|
||||
* Guides
|
||||
* Theme & Package creation guide
|
||||
* Full API per release tag
|
||||
* Changelog per release
|
||||
* Explanation of features
|
||||
* Explain Semver and general plans for the future (reassure developers we care about them)
|
||||
* General Values/Goals
|
||||
* Make docs accessible from Atom
|
||||
* Community/contribution guidelines
|
||||
* Is all communication to be done through issues?
|
||||
* When should you publish a plugin?
|
||||
* Do we need to vet plugins from a security perspective?
|
||||
@@ -1,16 +0,0 @@
|
||||
## Proposed Timeline
|
||||
|
||||
1. **October 30st** - Internal launch - persuade as many githubbers to switch as
|
||||
possible.
|
||||
|
||||
1. Triage bugs and identify what needs to be fixed before private alpha. Maybe
|
||||
talk to @chrissiebrodigan about doing a UX study.
|
||||
|
||||
1. **November 22st** - Private alpha launch
|
||||
|
||||
1. Trickle out invites as people ask/we need more testers.
|
||||
|
||||
1. If our usage metrics/engagement metrics decrease, stop, identify the issue
|
||||
and fix it before continuing.
|
||||
|
||||
1. Launch
|
||||
@@ -0,0 +1,97 @@
|
||||
## Publishing a Package
|
||||
|
||||
This guide will show you how to publish a package or theme to the
|
||||
[atom.io][atomio] package registry.
|
||||
|
||||
Publishing a package allows other people to install it and use it in Atom. It
|
||||
is a great way to share what you've made and get feedback and contributions from
|
||||
others.
|
||||
|
||||
This guide assumes your package's name is `my-package` and but you should pick a
|
||||
better name.
|
||||
|
||||
### Install apm
|
||||
|
||||
The `apm` command line utility that ships with Atom supports publishing packages
|
||||
to the atom.io registry.
|
||||
|
||||
Check that you have `apm` installed by running the following command in your
|
||||
terminal:
|
||||
|
||||
```sh
|
||||
apm help publish
|
||||
```
|
||||
|
||||
You should see a message print out with details about the `apm publish` command.
|
||||
|
||||
If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu
|
||||
to install the `apm` and `atom` commands.
|
||||
|
||||
### Prepare Your Package
|
||||
|
||||
If you've followed the steps in the [your first package][your-first-package]
|
||||
doc then you should be ready to publish and you can skip to the next step.
|
||||
|
||||
If not, there are a few things you should check before publishing:
|
||||
|
||||
* Your *package.json* file has `name`, `description`, and `repository` fields.
|
||||
* Your *package.json* file has a `version` field with a value of `"0.0.0"`.
|
||||
* Your *package.json* file has an `engines` field that contains an entry
|
||||
for Atom such as: `"engines": {"atom": ">=0.50.0"}`.
|
||||
* Your package has a `README.md` file at the root.
|
||||
* Your package is in a Git repository that has been pushed to
|
||||
[GitHub][github]. Follow [this guide][repo-guide] if your package isn't
|
||||
already on GitHub.
|
||||
|
||||
### Publish Your Package
|
||||
|
||||
Before you publish a package it is a good idea to check ahead of time if
|
||||
a package with the same name has already been published to atom.io. You can do
|
||||
that by visiting `http://atom.io/packages/my-package` to see if the package
|
||||
already exists. If it does, update your package's name to something that is
|
||||
available before proceeding.
|
||||
|
||||
Now let's review what the `apm publish` command does:
|
||||
|
||||
1. Registers the package name on atom.io if it is being published for the
|
||||
first time.
|
||||
2. Updates the `version` field in the *package.json* file and commits it.
|
||||
3. Creates a new [Git tag][git-tag] for the version being published.
|
||||
4. Pushes the tag and current branch up to GitHub.
|
||||
5. Updates atom.io with the new version being published.
|
||||
|
||||
Now run the following commands to publish your package:
|
||||
|
||||
```sh
|
||||
cd ~/github/my-package
|
||||
apm publish minor
|
||||
```
|
||||
|
||||
If this is the first package you are publishing, the `apm publish` command may
|
||||
prompt you for your GitHub username and password. This is required to publish
|
||||
and you only need to enter this information the first time you publish. The
|
||||
credentials are stored securely in your [keychain][keychain] once you login.
|
||||
|
||||
:tada: Your package is now published and available on atom.io. Head on over to
|
||||
`http://atom.io/packages/my-package` to see your package's page.
|
||||
|
||||
The `minor` option to the publish command tells apm to increment the second
|
||||
digit of the version before publishing so the published version will be `0.1.0`
|
||||
and the Git tag created will be `v0.1.0`.
|
||||
|
||||
In the future you can run `apm publish major` to publish the `1.0.0` version but
|
||||
since this was the first version being published it is a good idead to start
|
||||
with a minor release.
|
||||
|
||||
### Further Reading
|
||||
|
||||
* Check out [semantic versioning][semver] to learn more about versioning your
|
||||
package releases.
|
||||
|
||||
[atomio]: https://atom.io
|
||||
[github]: https://github.com
|
||||
[git-tag]: http://git-scm.com/book/en/Git-Basics-Tagging
|
||||
[keychain]: http://en.wikipedia.org/wiki/Keychain_(Apple)
|
||||
[repo-guide]: http://guides.github.com/overviews/desktop
|
||||
[semver]: http://semver.org
|
||||
[your-first-package]: your-first-package.html
|
||||
+117
-297
@@ -1,335 +1,155 @@
|
||||
# Creating Your First Package
|
||||
# Create Your First Package
|
||||
|
||||
Let's take a look at creating your first package.
|
||||
This tutorial will guide you though creating a simple command that replaces the
|
||||
selected text with [ascii art](http://en.wikipedia.org/wiki/ASCII_art). When you
|
||||
run our new command with the word "cool" selected, it will be replaced with:
|
||||
|
||||
To get started, hit `cmd-shift-P`, and start typing "Generate Package" to generate
|
||||
a package. Once you select the "Generate Package" command, it'll ask you for a
|
||||
name for your new package. Let's call ours _changer_.
|
||||
|
||||
Atom will pop open a new window, showing the _changer_ package with a default set of
|
||||
folders and files created for us. Hit `cmd-shift-P` and start typing "Changer." You'll
|
||||
see a new `Changer:Toggle` command which, if selected, pops up a greeting. So far,
|
||||
so good!
|
||||
|
||||
In order to demonstrate the capabilities of Atom and its API, our Changer plugin
|
||||
is going to do two things:
|
||||
|
||||
1. It'll show only modified files in the file tree
|
||||
2. It'll append a new pane to the editor with some information about the modified
|
||||
files
|
||||
|
||||
Let's get started!
|
||||
|
||||
## Changing Keybindings and Commands
|
||||
|
||||
Since Changer is primarily concerned with the file tree, let's write a
|
||||
key binding that works only when the tree is focused. Instead of using the
|
||||
default `toggle`, our keybinding executes a new command called `magic`.
|
||||
|
||||
_keymaps/changer.cson_ should change to look like this:
|
||||
|
||||
```coffeescript
|
||||
'.tree-view':
|
||||
'ctrl-V': 'changer:magic'
|
||||
```
|
||||
___
|
||||
/\_ \
|
||||
___ ___ ___\//\ \
|
||||
/'___\ / __`\ / __`\\ \ \
|
||||
/\ \__//\ \L\ \/\ \L\ \\_\ \_
|
||||
\ \____\ \____/\ \____//\____\
|
||||
\/____/\/___/ \/___/ \/____/
|
||||
```
|
||||
|
||||
Notice that the keybinding is called `ctrl-V` — that's actually `ctrl-shift-v`.
|
||||
You can use capital letters to denote using `shift` for your binding.
|
||||
The final package can be viewed at
|
||||
[https://github.com/atom/ascii-art](https://github.com/atom/ascii-art).
|
||||
|
||||
`.tree-view` represents the parent container for the tree view.
|
||||
Keybindings only work within the context of where they're entered. In this case,
|
||||
hitting `ctrl-V` anywhere other than tree won't do anything. Obviously, you can
|
||||
bind to any part of the editor using element, id, or class names. For example,
|
||||
you can map to `body` if you want to scope to anywhere in Atom, or just `.editor`
|
||||
for the editor portion.
|
||||
To begin, press `cmd-shift-P` to bring up the [Command
|
||||
Palette](https://github.com/atom/command-palette). Type "generate package" and
|
||||
select the "Package Generator: Generate Package" command. Now we need to name
|
||||
the package. Let's call it _ascii-art_.
|
||||
|
||||
To bind keybindings to a command, we'll need to do a bit of association in our
|
||||
CoffeeScript code using the `atom.workspaceView.command` method. This method takes a command
|
||||
name and executes a callback function. Open up _lib/changer-view.coffee_, and
|
||||
change `atom.workspaceView.command "changer:toggle"` to look like this:
|
||||
Atom will open a new window with the contents of our new _ascii-art_ package
|
||||
displayed in the Tree View. Because this window is opened **after** the package
|
||||
is created, the ASCII Art package will be loaded and available in our new
|
||||
window. To verify this, toggle the Command Palette (`cmd-shift-P`) and type
|
||||
"ASCII Art". You'll see a new `ASCII Art: Toggle` command. When triggered, this
|
||||
command displays a default message.
|
||||
|
||||
Now let's edit the package files to make our ASCII Art package do something
|
||||
interesting. Since this package doesn't need any UI, we can remove all
|
||||
view-related code. Start by opening up _lib/ascii-art.coffee_. Remove all view
|
||||
code, so the `module.exports` section looks like this:
|
||||
|
||||
```coffeescript
|
||||
atom.workspaceView.command "changer:magic", => @magic()
|
||||
module.exports =
|
||||
activate: ->
|
||||
```
|
||||
|
||||
It's common practice to namespace your commands with your package name, separated
|
||||
with a colon (`:`). Make sure to rename your `toggle` method to `magic` as well.
|
||||
## Create a Command
|
||||
|
||||
Every time you reload the Atom editor, changes to your package code will be reevaluated,
|
||||
just as if you were writing a script for the browser. Reload the editor, click on
|
||||
the tree, hit your keybinding, and...nothing happens! What the heck?!
|
||||
Now let's add a command. We recommend that you namespace your commands with the
|
||||
package name followed by a `:`, so we'll call our command `ascii-art:convert`.
|
||||
Register the command in _lib/ascii-art.coffee_:
|
||||
|
||||
Open up the _package.json_ file, and find the property called `activationEvents`.
|
||||
Basically, this key tells Atom to not load a package until it hears a certain event.
|
||||
Change the event to `changer:magic` and reload the editor:
|
||||
```coffeescript
|
||||
module.exports =
|
||||
activate: ->
|
||||
atom.workspaceView.command "ascii-art:convert", => @convert()
|
||||
|
||||
convert: ->
|
||||
# This assumes the active pane item is an editor
|
||||
editor = atom.workspace.activePaneItem
|
||||
editor.insertText('Hello, World!')
|
||||
```
|
||||
|
||||
The `atom.workspaceView.command` method takes a command name and a callback. The
|
||||
callback executes when the command is triggered. In this case, when the command
|
||||
is triggered the callback will call the `convert` method and insert 'Hello,
|
||||
World!'.
|
||||
|
||||
## Reload the Package
|
||||
|
||||
Before we can trigger `ascii-art:convert`, we need to load the latest code for
|
||||
our package by reloading the window. Run the command `window:reload` from the
|
||||
command palette or by pressing `ctrl-alt-cmd-l`.
|
||||
|
||||
## Trigger the Command
|
||||
|
||||
Now open the command panel and search for the `ascii-art:convert` command. But
|
||||
it's not there! To fix this, open _package.json_ and find the property called
|
||||
`activationEvents`. Activation Events speed up load time by allowing Atom to
|
||||
delay a package's activation until it's needed. So remove the existing command
|
||||
and add `ascii-art:convert` to the `activationEvents` array:
|
||||
|
||||
```json
|
||||
"activationEvents": ["changer:magic"]
|
||||
"activationEvents": ["ascii-art:convert"],
|
||||
```
|
||||
|
||||
Hitting the key binding on the tree now works!
|
||||
First, reload the window by running the command `window:reload`. Now when you
|
||||
run the `ascii-art:convert` command it will output 'Hello, World!'
|
||||
|
||||
## Working with Styles
|
||||
## Add a Key Binding
|
||||
|
||||
The next step is to hide elements in the tree that aren't modified. To do that,
|
||||
we'll first try and get a list of files that have not changed.
|
||||
|
||||
All packages are able to use jQuery in their code. In fact, there's [a list of
|
||||
the bundled libraries Atom provides by default][bundled-libs].
|
||||
|
||||
We bring in jQuery by requiring the `atom` package and binding it to the `$` variable:
|
||||
Now let's add a key binding to trigger the `ascii-art:convert` command. Open
|
||||
_keymaps/ascii-art.cson_ and add a key binding linking `ctrl-alt-a` to the
|
||||
`ascii-art:convert` command. You can delete the pre-existing key binding since
|
||||
you don't need it anymore. When finished, the file will look like this:
|
||||
|
||||
```coffeescript
|
||||
{$, View} = require 'atom'
|
||||
'.editor':
|
||||
'cmd-alt-a': 'ascii-art:convert'
|
||||
```
|
||||
|
||||
Now, we can define the `magic` method to query the tree to get us a list of every
|
||||
file that _wasn't_ modified:
|
||||
Notice `.editor` on the first line. Just like CSS, keymap selectors *scope* key
|
||||
bindings so they only apply to specific elements. In this case, our binding is
|
||||
only active for elements matching the `.editor` selector. If the Tree View has
|
||||
focus, pressing `cmd-alt-a` won't trigger the `ascii-art:convert` command. But
|
||||
if the editor has focus, the `ascii-art:convert` method *will* be triggered.
|
||||
More information on key bindings can be found in the
|
||||
[keymaps](advanced/keymaps.html) documentation.
|
||||
|
||||
```coffeescript
|
||||
magic: ->
|
||||
$('ol.entries li').each (i, el) ->
|
||||
if !$(el).hasClass("status-modified")
|
||||
console.log el
|
||||
```
|
||||
Now reload the window and verify that the key binding works! You can also verify
|
||||
that it **doesn't** work when the Tree View is focused.
|
||||
|
||||
You can access the dev console by hitting `alt-cmd-i`. Here, you'll see all the
|
||||
statements from `console` calls. When we execute the `changer:magic` command, the
|
||||
browser console lists items that are not being modified (_i.e._, those without the
|
||||
`status-modified` class). Let's add a class to each of these elements called `hide-me`:
|
||||
## Add the ASCII Art
|
||||
|
||||
```coffeescript
|
||||
magic: ->
|
||||
$('ol.entries li').each (i, el) ->
|
||||
if !$(el).hasClass("status-modified")
|
||||
$(el).addClass("hide-me")
|
||||
```
|
||||
Now we need to convert the selected text to ASCII art. To do this we will use
|
||||
the [figlet](https://npmjs.org/package/figlet) [node](http://nodejs.org/) module
|
||||
from [npm](https://npmjs.org/). Open _package.json_ and add the latest version of
|
||||
figlet to the dependencies:
|
||||
|
||||
With our newly added class, we can manipulate the visibility of the elements
|
||||
with a simple stylesheet. Open up _changer.css_ in the _stylesheets_ directory,
|
||||
and add a single entry:
|
||||
|
||||
```css
|
||||
ol.entries .hide-me {
|
||||
display: none;
|
||||
```json
|
||||
"dependencies": {
|
||||
"figlet": "1.0.8"
|
||||
}
|
||||
```
|
||||
|
||||
Refresh Atom, and run the `changer` command. You'll see all the non-changed
|
||||
files disappear from the tree. Success!
|
||||
After saving the file, run the command 'update-package-dependencies:update' from
|
||||
the Command Palette. This will install the package's node module dependencies,
|
||||
only figlet in this case. You will need to run
|
||||
'update-package-dependencies:update' whenever you update the dependencies field
|
||||
in your _package.json_ file.
|
||||
|
||||
![Changer_File_View]
|
||||
|
||||
There are a number of ways you can get the list back; let's just naively iterate
|
||||
over the same elements and remove the class:
|
||||
Now require the figlet node module in _lib/ascii-art.coffee_ and instead of
|
||||
inserting 'Hello, World!' convert the selected text to ASCII art.
|
||||
|
||||
```coffeescript
|
||||
magic: ->
|
||||
$('ol.entries li').each (i, el) ->
|
||||
if !$(el).hasClass("status-modified")
|
||||
if !$(el).hasClass("hide-me")
|
||||
$(el).addClass("hide-me")
|
||||
else
|
||||
$(el).removeClass("hide-me")
|
||||
convert: ->
|
||||
# This assumes the active pane item is an editor
|
||||
editor = atom.workspace.activePaneItem
|
||||
selection = editor.getSelection()
|
||||
|
||||
figlet = require 'figlet'
|
||||
figlet selection.getText(), {font: "Larry 3D 2"}, (error, asciiArt) ->
|
||||
if error
|
||||
console.error(error)
|
||||
else
|
||||
selection.insertText("\n#{asciiArt}\n")
|
||||
```
|
||||
|
||||
## Creating a New Panel
|
||||
|
||||
The next goal of this package is to append a panel to the Atom editor that lists
|
||||
some information about the modified files.
|
||||
|
||||
To do that, we're going to first open up [the style guide][styleguide]. The Style
|
||||
Guide lists every type of UI element that can be created by an Atom package. Aside
|
||||
from helping you avoid writing fresh code from scratch, it ensures that packages
|
||||
have the same look and feel no matter how they're built.
|
||||
|
||||
Every package that extends from the `View` class can provide an optional class
|
||||
method called `content`. The `content` method constructs the DOM that your
|
||||
package uses as its UI. The principals of `content` are built entirely on
|
||||
[SpacePen][space-pen], which we'll touch upon only briefly here.
|
||||
|
||||
Our display will simply be an unordered list of the file names, and their
|
||||
modified times. We'll append this list to a panel on the bottom of the editor. A
|
||||
basic `panel` element inside a `tool-panel` will work well for us. Let's start by carving out a
|
||||
`div` to hold the filenames:
|
||||
|
||||
```coffeescript
|
||||
@content: ->
|
||||
@div class: "changer tool-panel panel-bottom", =>
|
||||
@div class: "panel", =>
|
||||
@div class: "panel-heading", "Modified Files"
|
||||
@div class: "panel-body padded", outlet: 'modifiedFilesContainer', =>
|
||||
@ul class: 'modified-files-list', outlet: 'modifiedFilesList', =>
|
||||
@li 'Modified File Test'
|
||||
@li 'Modified File Test'
|
||||
```
|
||||
|
||||
You can add any HTML attribute you like. `outlet` names the variable your
|
||||
package can use to manipulate the element directly. The fat arrow (`=>`)
|
||||
indicates that the next DOM set are nested children.
|
||||
|
||||
Once again, you can style `li` elements using your stylesheets. Let's test that
|
||||
out by adding these lines to the _changer.css_ file:
|
||||
|
||||
```css
|
||||
ul.modified-files-list {
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
We'll add one more line to the end of the `magic` method to make this pane
|
||||
appear:
|
||||
|
||||
```coffeescript
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
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.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.prependToBottom` call with this code:
|
||||
|
||||
```coffeescript
|
||||
# toggles the pane
|
||||
if @hasParent()
|
||||
@remove()
|
||||
else
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
There are about a hundred different ways to toggle a pane on and off, and this
|
||||
might not be the most efficient one. If you know your package needs to be
|
||||
toggled on and off more freely, it might be better to draw the interface during the
|
||||
initialization, then immediately call `hide()` on the element to remove it from
|
||||
the view. You can then swap between `show()` and `hide()`, and instead of
|
||||
forcing Atom to add and remove the element as we're doing here, it'll just set a
|
||||
CSS property to control your package's visibility.
|
||||
|
||||
Refresh Atom, hit the key combo, and watch your test list appear and disappear.
|
||||
|
||||
## Calling Node.js Code
|
||||
|
||||
Since Atom is built on top of [Node.js][node], you can call any of its libraries,
|
||||
including other modules that your package requires.
|
||||
|
||||
We'll iterate through our resulting tree, and construct the path to our modified
|
||||
file based on its depth in the tree. We'll use Node to handle path joining for
|
||||
directories.
|
||||
|
||||
Add the following Node module to the top of your file:
|
||||
|
||||
```coffeescript
|
||||
path = require 'path'
|
||||
```
|
||||
|
||||
Then, add these lines to your `magic` method, _before_ your pane drawing code:
|
||||
|
||||
```coffeescript
|
||||
modifiedFiles = []
|
||||
# for each single entry...
|
||||
$('ol.entries li.file.status-modified span.name').each (i, el) ->
|
||||
filePath = []
|
||||
# ...grab its name...
|
||||
filePath.unshift($(el).text())
|
||||
|
||||
# ... then find its parent directories, and grab their names
|
||||
parents = $(el).parents('.directory.status-modified')
|
||||
parents.each (i, el) ->
|
||||
filePath.unshift($(el).find('div.header span.name').eq(0).text())
|
||||
|
||||
modifiedFilePath = path.join(atom.project.rootDirectory.path, filePath.join(path.sep))
|
||||
modifiedFiles.push modifiedFilePath
|
||||
```
|
||||
|
||||
`modifiedFiles` is an array containing a list of our modified files. We're also
|
||||
using the node.js [`path` library][path] to get the proper directory separator
|
||||
for our system.
|
||||
|
||||
Remove the two `@li` elements we added in `@content`, so that we can
|
||||
populate our `modifiedFilesList` with real information. We'll do that by
|
||||
iterating over `modifiedFiles`, accessing a file's last modified time, and
|
||||
appending it to `modifiedFilesList`:
|
||||
|
||||
```coffeescript
|
||||
# toggles the pane
|
||||
if @hasParent()
|
||||
@remove()
|
||||
else
|
||||
for file in modifiedFiles
|
||||
stat = fs.lstatSync(file)
|
||||
mtime = stat.mtime
|
||||
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
When you toggle the modified files list, your pane is now populated with the
|
||||
filenames and modified times of files in your project:
|
||||
|
||||
![Changer_Panel_Timestamps]
|
||||
|
||||
You might notice that subsequent calls to this command reduplicate information.
|
||||
We could provide an elegant way of rechecking files already in the list, but for
|
||||
this demonstration, we'll just clear the `modifiedFilesList` each time it's closed:
|
||||
|
||||
```coffeescript
|
||||
# toggles the pane
|
||||
if @hasParent()
|
||||
@modifiedFilesList.empty() # added this to clear the list on close
|
||||
@remove()
|
||||
else
|
||||
for file in modifiedFiles
|
||||
stat = fs.lstatSync(file)
|
||||
mtime = stat.mtime
|
||||
@modifiedFilesList.append("<li>#{file} - Modified at #{mtime}")
|
||||
atom.workspaceView.prependToBottom(this)
|
||||
```
|
||||
|
||||
## Coloring UI Elements
|
||||
|
||||
For packages that create new UI elements, adhering to the style guide is just one
|
||||
part to keeping visual consistency. Packages dealing with color, fonts, padding,
|
||||
margins, and other visual cues should rely on [Theme Variables][theme-vars], instead
|
||||
of developing individual styles. Theme variables are variables defined by Atom
|
||||
for use in packages and themes. They're only available in [`LESS`](http://lesscss.org/)
|
||||
stylesheets.
|
||||
|
||||
For our package, let's remove the style defined by `ul.modified-files-list` in
|
||||
_changer.css_. Create a new file under the _stylesheets_ directory called _text-colors.less_.
|
||||
Here, we'll import the _ui-variables.less_ file, and define some Atom-specific
|
||||
styles:
|
||||
|
||||
```less
|
||||
@import "ui-variables";
|
||||
|
||||
ul.modified-files-list {
|
||||
color: @text-color;
|
||||
background-color: @background-color-info;
|
||||
}
|
||||
```
|
||||
|
||||
Using theme variables ensures that packages look great alongside any theme.
|
||||
Select some text in an editor window and hit `cmd-alt-a`. :tada: You're now an
|
||||
ASCII art professional!
|
||||
|
||||
## Further reading
|
||||
|
||||
For more information on the mechanics of packages, check out
|
||||
[Creating a Package][creating-a-package].
|
||||
* [Getting your project on GitHub guide](http://guides.github.com/overviews/desktop)
|
||||
|
||||
[bundled-libs]: creating-a-package.html#included-libraries
|
||||
[styleguide]: https://github.com/atom/styleguide
|
||||
[space-pen]: https://github.com/atom/space-pen
|
||||
[node]: http://nodejs.org/
|
||||
[path]: http://nodejs.org/docs/latest/api/path.html
|
||||
[changer_file_view]: https://f.cloud.github.com/assets/69169/1441187/d7a7cb46-41a7-11e3-8128-d93f70a5d5c1.png
|
||||
[changer_panel_append]: https://f.cloud.github.com/assets/69169/1441189/db0c74da-41a7-11e3-8286-b82dd9190c34.png
|
||||
[changer_panel_timestamps]: https://f.cloud.github.com/assets/69169/1441190/dcc8eeb6-41a7-11e3-830f-1f1b33072fcd.png
|
||||
[theme-vars]: theme-variables.html
|
||||
[creating-a-package]: creating-a-package.html
|
||||
* [Creating a package guide](creating-a-package.html) for more information
|
||||
on the mechanics of packages
|
||||
|
||||
* [Publishing a package guide](publish-a-package.html) for more information
|
||||
on publishing your package to [atom.io](https://atom.io)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Your init script
|
||||
#
|
||||
# Atom will evaluate this file each time a new window is opened. It is run
|
||||
# after packages are loaded/activated and after the previous editor state
|
||||
# has been restored.
|
||||
#
|
||||
# An example hack to make opened Markdown files always be soft wrapped:
|
||||
#
|
||||
# path = require 'path'
|
||||
#
|
||||
# atom.workspaceView.eachEditorView (editorView) ->
|
||||
# editor = editorView.getEditor()
|
||||
# if path.extname(editor.getPath()) is '.md'
|
||||
# editor.setSoftWrap(true)
|
||||
@@ -1,9 +1,13 @@
|
||||
# User keymap
|
||||
# Your keymap
|
||||
#
|
||||
# Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors
|
||||
# to apply styles to elements, Atom keymaps use selectors to associate
|
||||
# keystrokes with events in specific contexts. Here's a small example, excerpted
|
||||
# from Atom's built-in keymaps:
|
||||
# keystrokes with events in specific contexts.
|
||||
#
|
||||
# You can create a new keybinding in this file by typing "key" and then hitting
|
||||
# tab.
|
||||
#
|
||||
# Here's an example taken from Atom's built-in keymap:
|
||||
#
|
||||
# '.editor':
|
||||
# 'enter': 'editor:newline'
|
||||
@@ -11,3 +15,4 @@
|
||||
# 'body':
|
||||
# 'ctrl-P': 'core:move-up'
|
||||
# 'ctrl-p': 'core:move-down'
|
||||
#
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Your snippets
|
||||
#
|
||||
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
|
||||
# expand the prefix into a larger code block with templated values.
|
||||
#
|
||||
# You can create a new snippet in this file by typing "snip" and then hitting
|
||||
# tab.
|
||||
#
|
||||
# An example CoffeeScript snippet to expand log to console.log:
|
||||
#
|
||||
# '.source.coffee':
|
||||
# 'Console log':
|
||||
# 'prefix': 'log'
|
||||
# 'body': 'console.log $1'
|
||||
#
|
||||
@@ -1,44 +0,0 @@
|
||||
".source.coffee":
|
||||
"Describe block":
|
||||
prefix: "de"
|
||||
body: """
|
||||
describe "${1:description}", ->
|
||||
${2:body}
|
||||
"""
|
||||
"It block":
|
||||
prefix: "i"
|
||||
body: """
|
||||
it "$1", ->
|
||||
$2
|
||||
"""
|
||||
"Before each":
|
||||
prefix: "be"
|
||||
body: """
|
||||
beforeEach ->
|
||||
$1
|
||||
"""
|
||||
"After each":
|
||||
prefix: "af"
|
||||
body: """
|
||||
afterEach ->
|
||||
$1
|
||||
"""
|
||||
"Expectation":
|
||||
prefix: "ex"
|
||||
body: "expect($1).to$2"
|
||||
"Console log":
|
||||
prefix: "log"
|
||||
body: "console.log $1"
|
||||
"Range array":
|
||||
prefix: "ra"
|
||||
body: "[[$1, $2], [$3, $4]]"
|
||||
"Point array":
|
||||
prefix: "pt"
|
||||
body: "[$1, $2]"
|
||||
|
||||
"Key-value pair":
|
||||
prefix: ":"
|
||||
body: '${1:"${2:key}"}: ${3:value}'
|
||||
"Create Jasmine spy":
|
||||
prefix: "spy"
|
||||
body: 'jasmine.createSpy("${1:description}")$2'
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Your Stylesheet
|
||||
*
|
||||
* This stylesheet is loaded when Atom starts up and is reloaded automatically
|
||||
* when it is changed.
|
||||
*
|
||||
* If you are unfamiliar with LESS, you can read more about it here:
|
||||
* http://www.lesscss.org
|
||||
*/
|
||||
|
||||
.tree-view {
|
||||
|
||||
}
|
||||
|
||||
.editor {
|
||||
|
||||
}
|
||||
|
||||
.editor .cursor {
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# For more on how to configure atom open `~/github/atom/docs/configuring-and-extending.md`
|
||||
@@ -1,8 +0,0 @@
|
||||
/* User styles */
|
||||
.tree-view {
|
||||
|
||||
}
|
||||
|
||||
.editor {
|
||||
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
{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'
|
||||
File: require '../src/file'
|
||||
fs: require 'fs-plus'
|
||||
Git: require '../src/git'
|
||||
Point: Point
|
||||
Range: Range
|
||||
@@ -20,8 +16,8 @@ unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
|
||||
module.exports.$$ = $$
|
||||
module.exports.$$$ = $$$
|
||||
module.exports.EditorView = require '../src/editor-view'
|
||||
module.exports.WorkspaceView = require '../src/workspace-view'
|
||||
module.exports.SelectList = require '../src/select-list'
|
||||
module.exports.ScrollView = require '../src/scroll-view'
|
||||
module.exports.SelectListView = require '../src/select-list-view'
|
||||
module.exports.Task = require '../src/task'
|
||||
module.exports.View = View
|
||||
module.exports.WorkspaceView = require '../src/workspace-view'
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
'ctrl-shift-up': 'editor:add-selection-above'
|
||||
'ctrl-shift-down': 'editor:add-selection-below'
|
||||
|
||||
'.tool-panel':
|
||||
'escape': 'core:close'
|
||||
|
||||
'.tool-panel.panel-left, .tool-panel.panel-right':
|
||||
'escape': 'tool-panel:unfocus'
|
||||
|
||||
|
||||
+12
-7
@@ -1,4 +1,4 @@
|
||||
'.platform-darwin':
|
||||
'body':
|
||||
# Apple specific
|
||||
'cmd-q': 'application:quit'
|
||||
'cmd-h': 'application:hide'
|
||||
@@ -68,6 +68,8 @@
|
||||
'cmd-=': 'window:increase-font-size'
|
||||
'cmd-+': 'window:increase-font-size'
|
||||
'cmd--': 'window:decrease-font-size'
|
||||
'cmd-_': 'window:decrease-font-size'
|
||||
'cmd-0': 'window:reset-font-size'
|
||||
|
||||
'cmd-k up': 'pane:split-up' # Atom Specific
|
||||
'cmd-k down': 'pane:split-down' # Atom Specific
|
||||
@@ -75,8 +77,12 @@
|
||||
'cmd-k right': 'pane:split-right' # Atom Specific
|
||||
'cmd-k cmd-w': 'pane:close' # Atom Specific
|
||||
'cmd-k alt-cmd-w': 'pane:close-other-items' # Atom Specific
|
||||
'cmd-k cmd-left': 'window:focus-previous-pane'
|
||||
'cmd-k cmd-right': 'window:focus-next-pane'
|
||||
'cmd-k cmd-p': 'window:focus-previous-pane'
|
||||
'cmd-k cmd-n': 'window:focus-next-pane'
|
||||
'cmd-k cmd-up': 'window:focus-pane-above'
|
||||
'cmd-k cmd-down': 'window:focus-pane-below'
|
||||
'cmd-k cmd-left': 'window:focus-pane-on-left'
|
||||
'cmd-k cmd-right': 'window:focus-pane-on-right'
|
||||
'cmd-1': 'pane:show-item-1'
|
||||
'cmd-2': 'pane:show-item-2'
|
||||
'cmd-3': 'pane:show-item-3'
|
||||
@@ -87,7 +93,7 @@
|
||||
'cmd-8': 'pane:show-item-8'
|
||||
'cmd-9': 'pane:show-item-9'
|
||||
|
||||
'.platform-darwin .editor':
|
||||
'.editor':
|
||||
# Apple Specific
|
||||
'cmd-backspace': 'editor:backspace-to-beginning-of-line'
|
||||
'cmd-delete': 'editor:backspace-to-beginning-of-line'
|
||||
@@ -113,12 +119,11 @@
|
||||
'cmd-k cmd-l': 'editor:lower-case'
|
||||
'cmd-l': 'editor:select-line'
|
||||
|
||||
'body.platform-darwin .editor:not(.mini)':
|
||||
'.workspace .editor:not(.mini)':
|
||||
# Atom specific
|
||||
'alt-cmd-z': 'editor:checkout-head-revision'
|
||||
'cmd-<': 'editor:scroll-to-cursor'
|
||||
'alt-cmd-ctrl-f': 'editor:fold-selection'
|
||||
'cmd-=': 'editor:auto-indent'
|
||||
|
||||
# Sublime Parity
|
||||
'cmd-enter': 'editor:newline-below'
|
||||
@@ -148,7 +153,7 @@
|
||||
'cmd-k cmd-9': 'editor:fold-at-indent-level-9'
|
||||
|
||||
# allow standard input fields to work correctly
|
||||
'body.platform-darwin .native-key-bindings':
|
||||
'body .native-key-bindings':
|
||||
'cmd-z': 'native!'
|
||||
'cmd-Z': 'native!'
|
||||
'cmd-x': 'native!'
|
||||
|
||||
+12
-7
@@ -1,4 +1,4 @@
|
||||
'.platform-win32':
|
||||
'body':
|
||||
# Atom Specific
|
||||
'enter': 'core:confirm'
|
||||
'escape': 'core:cancel'
|
||||
@@ -40,6 +40,8 @@
|
||||
'ctrl-=': 'window:increase-font-size'
|
||||
'ctrl-+': 'window:increase-font-size'
|
||||
'ctrl--': 'window:decrease-font-size'
|
||||
'ctrl-_': 'window:decrease-font-size'
|
||||
'ctrl-0': 'window:reset-font-size'
|
||||
|
||||
'ctrl-k up': 'pane:split-up' # Atom Specific
|
||||
'ctrl-k down': 'pane:split-down' # Atom Specific
|
||||
@@ -47,10 +49,14 @@
|
||||
'ctrl-k right': 'pane:split-right' # Atom Specific
|
||||
'ctrl-k ctrl-w': 'pane:close' # Atom Specific
|
||||
'ctrl-k alt-ctrl-w': 'pane:close-other-items' # Atom Specific
|
||||
'ctrl-k ctrl-left': 'window:focus-previous-pane'
|
||||
'ctrl-k ctrl-right': 'window:focus-next-pane'
|
||||
'ctrl-k ctrl-p': 'window:focus-previous-pane'
|
||||
'ctrl-k ctrl-n': 'window:focus-next-pane'
|
||||
'ctrl-k ctrl-up': 'window:focus-pane-above'
|
||||
'ctrl-k ctrl-down': 'window:focus-pane-below'
|
||||
'ctrl-k ctrl-left': 'window:focus-pane-on-left'
|
||||
'ctrl-k ctrl-right': 'window:focus-pane-on-right'
|
||||
|
||||
'.platform-win32 .editor':
|
||||
'.workspace .editor':
|
||||
# Windows specific
|
||||
'ctrl-delete': 'editor:backspace-to-beginning-of-word'
|
||||
|
||||
@@ -60,12 +66,11 @@
|
||||
'ctrl-k ctrl-u': 'editor:upper-case'
|
||||
'ctrl-k ctrl-l': 'editor:lower-case'
|
||||
|
||||
'.platform-win32 .editor:not(.mini)':
|
||||
'.workspace .editor:not(.mini)':
|
||||
# Atom specific
|
||||
'alt-ctrl-z': 'editor:checkout-head-revision'
|
||||
'ctrl-<': 'editor:scroll-to-cursor'
|
||||
'alt-ctrl-f': 'editor:fold-selection'
|
||||
'ctrl-=': 'editor:auto-indent'
|
||||
|
||||
# Sublime Parity
|
||||
'ctrl-enter': 'editor:newline-below'
|
||||
@@ -94,7 +99,7 @@
|
||||
'ctrl-k ctrl-9': 'editor:fold-at-indent-level-9'
|
||||
|
||||
# allow standard input fields to work correctly
|
||||
'.platform-win32 input:not(.hidden-input), .platform-win32 .native-key-bindings':
|
||||
'body .native-key-bindings':
|
||||
'ctrl-z': 'native!'
|
||||
'ctrl-Z': 'native!'
|
||||
'ctrl-x': 'native!'
|
||||
|
||||
+10
-2
@@ -3,10 +3,19 @@
|
||||
label: 'Atom'
|
||||
submenu: [
|
||||
{ label: 'About Atom', command: 'application:about' }
|
||||
{ label: 'View License', command: 'application:open-license' }
|
||||
{ label: "VERSION", enabled: false }
|
||||
{ label: "Install update", command: 'application:install-update', visible: false }
|
||||
{ label: "Restart and Install Update", command: 'application:install-update', visible: false}
|
||||
{ label: "Check for Update", command: 'application:check-for-update', visible: false}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences...', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Hide Atom', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
@@ -157,7 +166,6 @@
|
||||
label: 'Help'
|
||||
submenu: [
|
||||
{ label: 'Documentation', command: 'application:open-documentation' }
|
||||
{ label: 'Report an Issue', command: 'application:report-issue' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
label: '&Help'
|
||||
submenu: [
|
||||
{ label: '&About Atom...', command: 'application:about' }
|
||||
{ label: 'View &License', command: 'application:open-license' }
|
||||
{ label: "VERSION", enabled: false }
|
||||
{ label: "Install &update", command: 'application:install-update', visible: false }
|
||||
{ type: 'separator' }
|
||||
|
||||
+104
-110
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.45.0",
|
||||
"version": "0.54.0",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,126 +16,120 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.7.6",
|
||||
"atomShellVersion": "0.10.1",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"bootstrap": "git://github.com/benogle/bootstrap.git",
|
||||
"clear-cut": "0.2.0",
|
||||
"coffee-script": "1.6.3",
|
||||
"coffeestack": "0.6.0",
|
||||
"emissary": "0.31.0",
|
||||
"first-mate": "0.17.0",
|
||||
"fs-plus": "0.14.0",
|
||||
"bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
|
||||
"clear-cut": "0.4.0",
|
||||
"coffee-script": "1.7.0",
|
||||
"coffeestack": "0.7.0",
|
||||
"delegato": "1.x",
|
||||
"emissary": "1.x",
|
||||
"first-mate": ">=1.1.5 <2.0",
|
||||
"fs-plus": "2.x",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "0.6.0",
|
||||
"git-utils": "0.29.0",
|
||||
"fuzzaldrin": "1.x",
|
||||
"git-utils": "1.x",
|
||||
"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",
|
||||
"jasmine-tagged": ">=1.1.1 <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",
|
||||
"keytar": "0.15.1",
|
||||
"less-cache": "0.12.0",
|
||||
"mixto": "1.x",
|
||||
"nslog": "0.5.0",
|
||||
"oniguruma": "1.x",
|
||||
"optimist": "0.4.0",
|
||||
"pathwatcher": "0.11.0",
|
||||
"pathwatcher": "0.16.0",
|
||||
"pegjs": "0.8.0",
|
||||
"q": "0.9.7",
|
||||
"scandal": "0.11.0",
|
||||
"season": "0.14.0",
|
||||
"property-accessors": "1.x",
|
||||
"q": "1.0.x",
|
||||
"random-words": "0.0.1",
|
||||
"runas": "0.5.x",
|
||||
"scandal": "0.15.0",
|
||||
"season": ">=1.0.2 <2.0",
|
||||
"semver": "1.1.4",
|
||||
"space-pen": "3.1.0",
|
||||
"serializable": "1.x",
|
||||
"space-pen": "3.1.1",
|
||||
"temp": "0.5.0",
|
||||
"text-buffer": "0.12.0",
|
||||
"underscore-plus": "0.6.1",
|
||||
"theorist": "~0.13.0",
|
||||
"delegato": "~0.4.0",
|
||||
"mixto": "~0.4.0"
|
||||
"text-buffer": ">=1.1.2 <2.0",
|
||||
"theorist": "1.x",
|
||||
"underscore-plus": "1.x",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.10.0",
|
||||
"atom-dark-ui": "0.19.0",
|
||||
"atom-light-syntax": "0.10.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.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.16.0",
|
||||
"command-logger": "0.8.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.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.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.17.0",
|
||||
"terminal": "0.23.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.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.11.0",
|
||||
"language-git": "0.3.0",
|
||||
"language-go": "0.2.0",
|
||||
"language-html": "0.2.0",
|
||||
"language-hyperlink": "0.3.0",
|
||||
"language-java": "0.2.0",
|
||||
"language-javascript": "0.4.0",
|
||||
"language-json": "0.2.0",
|
||||
"language-less": "0.1.0",
|
||||
"language-make": "0.1.0",
|
||||
"language-mustache": "0.1.0",
|
||||
"language-objective-c": "0.2.0",
|
||||
"language-pegjs": "0.1.0",
|
||||
"language-perl": "0.2.0",
|
||||
"language-php": "0.2.0",
|
||||
"language-property-list": "0.2.0",
|
||||
"language-puppet": "0.2.0",
|
||||
"language-python": "0.2.0",
|
||||
"language-ruby": "0.6.0",
|
||||
"language-ruby-on-rails": "0.3.0",
|
||||
"language-sass": "0.3.0",
|
||||
"language-shellscript": "0.2.0",
|
||||
"language-source": "0.2.0",
|
||||
"language-sql": "0.2.0",
|
||||
"language-text": "0.2.0",
|
||||
"language-todo": "0.2.0",
|
||||
"language-toml": "0.7.0",
|
||||
"language-xml": "0.2.0",
|
||||
"language-yaml": "0.1.0"
|
||||
"atom-dark-syntax": "0.14.0",
|
||||
"atom-dark-ui": "0.23.0",
|
||||
"atom-light-syntax": "0.14.0",
|
||||
"atom-light-ui": "0.22.0",
|
||||
"base16-tomorrow-dark-theme": "0.12.0",
|
||||
"solarized-dark-syntax": "0.10.0",
|
||||
"solarized-light-syntax": "0.6.0",
|
||||
"archive-view": "0.24.0",
|
||||
"autocomplete": "0.24.0",
|
||||
"autoflow": "0.15.0",
|
||||
"autosave": "0.12.0",
|
||||
"background-tips": "0.8.0",
|
||||
"bookmarks": "0.21.0",
|
||||
"bracket-matcher": "0.22.0",
|
||||
"command-palette": "0.18.0",
|
||||
"dev-live-reload": "0.28.0",
|
||||
"exception-reporting": "0.15.0",
|
||||
"feedback": "0.26.0",
|
||||
"find-and-replace": "0.85.0",
|
||||
"fuzzy-finder": "0.37.0",
|
||||
"git-diff": "0.25.0",
|
||||
"go-to-line": "0.17.0",
|
||||
"grammar-selector": "0.21.0",
|
||||
"image-view": "0.25.0",
|
||||
"keybinding-resolver": "0.11.0",
|
||||
"link": "0.18.0",
|
||||
"markdown-preview": "0.36.0",
|
||||
"metrics": "0.28.0",
|
||||
"open-on-github": "0.21.0",
|
||||
"package-generator": "0.28.0",
|
||||
"release-notes": "0.24.0",
|
||||
"settings-view": "0.82.0",
|
||||
"snippets": "0.32.0",
|
||||
"spell-check": "0.26.0",
|
||||
"status-bar": "0.33.0",
|
||||
"styleguide": "0.25.0",
|
||||
"symbols-view": "0.38.0",
|
||||
"tabs": "0.24.0",
|
||||
"timecop": "0.15.0",
|
||||
"tree-view": "0.71.0",
|
||||
"update-package-dependencies": "0.4.0",
|
||||
"welcome": "0.6.0",
|
||||
"whitespace": "0.15.0",
|
||||
"wrap-guide": "0.15.0",
|
||||
"language-c": "0.12.0",
|
||||
"language-coffee-script": "0.12.0",
|
||||
"language-css": "0.10.0",
|
||||
"language-gfm": "0.18.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.6.0",
|
||||
"language-html": "0.8.0",
|
||||
"language-hyperlink": "0.8.0",
|
||||
"language-java": "0.8.0",
|
||||
"language-javascript": "0.11.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.5.0",
|
||||
"language-make": "0.8.0",
|
||||
"language-objective-c": "0.9.0",
|
||||
"language-perl": "0.8.0",
|
||||
"language-php": "0.8.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.8.0",
|
||||
"language-ruby": "0.13.0",
|
||||
"language-ruby-on-rails": "0.7.0",
|
||||
"language-sass": "0.8.0",
|
||||
"language-shellscript": "0.7.0",
|
||||
"language-source": "0.7.0",
|
||||
"language-sql": "0.7.0",
|
||||
"language-text": "0.6.0",
|
||||
"language-todo": "0.6.0",
|
||||
"language-toml": "0.11.0",
|
||||
"language-xml": "0.7.0",
|
||||
"language-yaml": "0.6.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
Arquivo executável → Arquivo normal
BIN
Arquivo binário não exibido.
|
Antes Largura: | Altura: | Tamanho: 52 KiB Depois Largura: | Altura: | Tamanho: 48 KiB |
Arquivo binário não exibido.
Arquivo binário não exibido.
Arquivo binário não exibido.
|
Antes Largura: | Altura: | Tamanho: 345 KiB Depois Largura: | Altura: | Tamanho: 361 KiB |
@@ -31,6 +31,7 @@ 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 packagesToDedupe = ['fs-plus', 'humanize-plus', 'oniguruma', 'roaster', 'season'];
|
||||
var echoNewLine = process.platform == 'win32' ? 'echo.' : 'echo';
|
||||
var commands = [
|
||||
'git submodule --quiet sync',
|
||||
@@ -43,6 +44,7 @@ var commands = [
|
||||
echoNewLine,
|
||||
'node apm/node_modules/atom-package-manager/bin/apm clean ' + apmFlags,
|
||||
'node apm/node_modules/atom-package-manager/bin/apm install --quiet ' + apmFlags,
|
||||
'node apm/node_modules/atom-package-manager/bin/apm dedupe --quiet ' + apmFlags + ' ' + packagesToDedupe.join(' '),
|
||||
];
|
||||
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
@echo off
|
||||
|
||||
set USAGE=Usage: %0 source destination
|
||||
|
||||
if [%1] == [] (
|
||||
echo %USAGE%
|
||||
exit 1
|
||||
)
|
||||
if [%2] == [] (
|
||||
echo %USAGE%
|
||||
exit 2
|
||||
)
|
||||
|
||||
:: rm -rf %2
|
||||
if exist %2 rmdir %2 /s /q
|
||||
|
||||
:: cp -rf %1 %2
|
||||
xcopy %1 %2 /e /h /c /i /y /r
|
||||
@@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
|
||||
set USAGE=Usage: %0 source name-on-desktop
|
||||
|
||||
if [%1] == [] (
|
||||
echo %USAGE%
|
||||
exit 1
|
||||
)
|
||||
if [%2] == [] (
|
||||
echo %USAGE%
|
||||
exit 2
|
||||
)
|
||||
|
||||
set SCRIPT="%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs"
|
||||
|
||||
echo Set oWS = WScript.CreateObject("WScript.Shell") >> %SCRIPT%
|
||||
echo sLinkFile = "%USERPROFILE%\Desktop\%2.lnk" >> %SCRIPT%
|
||||
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %SCRIPT%
|
||||
echo oLink.TargetPath = %1 >> %SCRIPT%
|
||||
echo oLink.Save >> %SCRIPT%
|
||||
|
||||
cscript /nologo %SCRIPT%
|
||||
del %SCRIPT%
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
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')];
|
||||
// node build/node_modules/.bin/grunt "$@"
|
||||
var gruntPath = path.resolve(__dirname, '..', '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(process.execPath, args, process.exit);
|
||||
cp.safeSpawn(gruntPath, args, process.exit);
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
path = require 'path'
|
||||
CommandInstaller = require '../src/command-installer'
|
||||
|
||||
callback = (error, sourcePath, destinationPath) ->
|
||||
unless error?
|
||||
console.log "#{sourcePath} intalled to #{destinationPath}"
|
||||
callback = (error) ->
|
||||
console.warn error.message if error?
|
||||
|
||||
CommandInstaller.installAtomCommand(path.resolve(__dirname, '..'), callback)
|
||||
CommandInstaller.installApmCommand(path.resolve(__dirname, '..'), callback)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env coffee
|
||||
|
||||
usage = """
|
||||
Usage:
|
||||
update-octicons PATH-TO-OCTICONS
|
||||
"""
|
||||
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
YAML = require 'js-yaml'
|
||||
|
||||
scriptPath = process.argv[1]
|
||||
pathToOcticons = process.argv[2] ? path.join(process.env.HOME, 'github', 'octicons')
|
||||
atomDir = path.resolve(scriptPath, "../../..")
|
||||
|
||||
unless fs.existsSync(pathToOcticons)
|
||||
console.error(usage)
|
||||
process.exit(1)
|
||||
|
||||
# Copy font-file
|
||||
fontSrc = path.join(pathToOcticons, 'octicons', 'octicons.woff')
|
||||
fontDest = path.join(atomDir, 'static', 'octicons.woff')
|
||||
fs.createReadStream(fontSrc).pipe(fs.createWriteStream(fontDest))
|
||||
|
||||
# Update Octicon UTF codes
|
||||
glyphsSrc = path.join(pathToOcticons, 'data', 'glyphs.yml')
|
||||
octiconUtfDest = path.join atomDir, 'static', 'octicon-utf-codes.less'
|
||||
output = []
|
||||
for {css, code} in YAML.load(fs.readFileSync(glyphsSrc).toString())
|
||||
output.push "@#{css}: \"\\#{code}\";"
|
||||
|
||||
fs.writeFileSync octiconUtfDest, "#{output.join('\n')}\n"
|
||||
+63
-67
@@ -1,35 +1,52 @@
|
||||
{View, $, $$} = require '../src/space-pen-extensions'
|
||||
path = require 'path'
|
||||
_ = require 'underscore-plus'
|
||||
{convertStackTrace} = require 'coffeestack'
|
||||
{View, $, $$} = require '../src/space-pen-extensions'
|
||||
|
||||
sourceMaps = {}
|
||||
formatStackTrace = (stackTrace) ->
|
||||
formatStackTrace = (spec, message='', stackTrace) ->
|
||||
return stackTrace unless stackTrace
|
||||
|
||||
jasminePath = require.resolve('../vendor/jasmine')
|
||||
jasminePattern = new RegExp("\\(#{_.escapeRegExp(jasminePath)}:\\d+:\\d+\\)\\s*$")
|
||||
jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
|
||||
firstJasmineLinePattern = /^\s*at \/.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
|
||||
convertedLines = []
|
||||
for line in stackTrace.split('\n')
|
||||
convertedLines.push(line) unless jasminePattern.test(line)
|
||||
break if firstJasmineLinePattern.test(line)
|
||||
|
||||
convertStackTrace(convertedLines.join('\n'), sourceMaps)
|
||||
stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps)
|
||||
lines = stackTrace.split('\n')
|
||||
|
||||
# Remove first line of stack when it is the same as the error message
|
||||
errorMatch = lines[0]?.match(/^Error: (.*)/)
|
||||
lines.shift() if message.trim() is errorMatch?[1]?.trim()
|
||||
|
||||
for line, index in lines
|
||||
# Remove prefix of lines matching: at [object Object].<anonymous> (path:1:2)
|
||||
prefixMatch = line.match(/at \[object Object\]\.<anonymous> \(([^\)]+)\)/)
|
||||
line = "at #{prefixMatch[1]}" if prefixMatch
|
||||
|
||||
# Relativize locations to spec directory
|
||||
lines[index] = line.replace("at #{spec.specDirectory}#{path.sep}", 'at ')
|
||||
|
||||
lines = lines.map (line) -> line.trim()
|
||||
lines.join('\n').trim()
|
||||
|
||||
module.exports =
|
||||
class AtomReporter extends View
|
||||
@content: ->
|
||||
@div id: 'HTMLReporter', class: 'jasmine_reporter', =>
|
||||
@div outlet: 'specPopup', class: "spec-popup"
|
||||
@div class: 'spec-reporter', =>
|
||||
@div outlet: "suites"
|
||||
@div outlet: 'coreArea', =>
|
||||
@div outlet: 'coreHeader', class: 'symbolHeader'
|
||||
@ul outlet: 'coreSummary', class: 'symbolSummary list-unstyled'
|
||||
@div outlet: 'bundledArea', =>
|
||||
@div outlet: 'bundledHeader', class: 'symbolHeader'
|
||||
@ul outlet: 'bundledSummary', class: 'symbolSummary list-unstyled'
|
||||
@div outlet: 'userArea', =>
|
||||
@div outlet: 'userHeader', class: 'symbolHeader'
|
||||
@ul outlet: 'userSummary', class: 'symbolSummary list-unstyled'
|
||||
@div outlet: "status", class: 'status', =>
|
||||
@div outlet: 'coreArea', class: 'symbol-area', =>
|
||||
@div outlet: 'coreHeader', class: 'symbol-header'
|
||||
@ul outlet: 'coreSummary', class: 'symbol-summary list-unstyled'
|
||||
@div outlet: 'bundledArea', class: 'symbol-area', =>
|
||||
@div outlet: 'bundledHeader', class: 'symbol-header'
|
||||
@ul outlet: 'bundledSummary', class: 'symbol-summary list-unstyled'
|
||||
@div outlet: 'userArea', class: 'symbol-area', =>
|
||||
@div outlet: 'userHeader', class: 'symbol-header'
|
||||
@ul outlet: 'userSummary', class: 'symbol-summary list-unstyled'
|
||||
@div outlet: "status", class: 'status alert alert-info', =>
|
||||
@div outlet: "time", class: 'time'
|
||||
@div outlet: "specCount", class: 'spec-count'
|
||||
@div outlet: "message", class: 'message'
|
||||
@@ -46,7 +63,7 @@ class AtomReporter extends View
|
||||
|
||||
reportRunnerStarting: (runner) ->
|
||||
@handleEvents()
|
||||
@startedAt = new Date()
|
||||
@startedAt = Date.now()
|
||||
specs = runner.specs()
|
||||
@totalSpecCount = specs.length
|
||||
@addSpecs(specs)
|
||||
@@ -54,57 +71,29 @@ class AtomReporter extends View
|
||||
|
||||
reportRunnerResults: (runner) ->
|
||||
@updateSpecCounts()
|
||||
if @failedCount == 0
|
||||
@message.text "Success!"
|
||||
@status.addClass('alert-success').removeClass('alert-info') if @failedCount is 0
|
||||
if @failedCount is 1
|
||||
@message.text "#{@failedCount} failure"
|
||||
else
|
||||
@message.text "Game Over"
|
||||
@message.text "#{@failedCount} failures"
|
||||
|
||||
reportSuiteResults: (suite) ->
|
||||
|
||||
reportSpecResults: (spec) ->
|
||||
@completeSpecCount++
|
||||
spec.endedAt = new Date().getTime()
|
||||
spec.endedAt = Date.now()
|
||||
@specComplete(spec)
|
||||
@updateStatusView(spec)
|
||||
|
||||
reportSpecStarting: (spec) ->
|
||||
@specStarted(spec)
|
||||
|
||||
specFilter: (spec) ->
|
||||
globalFocusPriority = jasmine.getEnv().focusPriority
|
||||
parent = spec.parentSuite ? spec.suite
|
||||
|
||||
if !globalFocusPriority
|
||||
true
|
||||
else if spec.focusPriority >= globalFocusPriority
|
||||
true
|
||||
else if not parent
|
||||
false
|
||||
else
|
||||
@specFilter(parent)
|
||||
|
||||
handleEvents: ->
|
||||
$(document).on "mouseover", ".spec-summary", ({currentTarget}) =>
|
||||
element = $(currentTarget)
|
||||
description = element.data("description")
|
||||
return unless description
|
||||
|
||||
clearTimeout @timeoutId if @timeoutId?
|
||||
@specPopup.show()
|
||||
spec = _.find(window.timedSpecs, ({fullName}) -> description is fullName)
|
||||
description = "#{description} #{spec.time}ms" if spec
|
||||
@specPopup.text description
|
||||
{left, top} = element.offset()
|
||||
left += 20
|
||||
top += 20
|
||||
@specPopup.offset({left, top})
|
||||
@timeoutId = setTimeout((=> @specPopup.hide()), 3000)
|
||||
|
||||
$(document).on "click", ".spec-toggle", ({currentTarget}) =>
|
||||
element = $(currentTarget)
|
||||
specFailures = element.parent().find('.spec-failures')
|
||||
specFailures.toggle()
|
||||
if specFailures.is(":visible") then element.text "\uf03d" else element.html "\uf03f"
|
||||
element.toggleClass('folded')
|
||||
false
|
||||
|
||||
updateSpecCounts: ->
|
||||
@@ -116,7 +105,7 @@ class AtomReporter extends View
|
||||
|
||||
updateStatusView: (spec) ->
|
||||
if @failedCount > 0
|
||||
@status.addClass('failed') unless @status.hasClass('failed')
|
||||
@status.addClass('alert-danger').removeClass('alert-info')
|
||||
|
||||
@updateSpecCounts()
|
||||
|
||||
@@ -124,7 +113,7 @@ class AtomReporter extends View
|
||||
rootSuite = rootSuite.parentSuite while rootSuite.parentSuite
|
||||
@message.text rootSuite.description
|
||||
|
||||
time = "#{Math.round((spec.endedAt - @startedAt.getTime()) / 10)}"
|
||||
time = "#{Math.round((spec.endedAt - @startedAt) / 10)}"
|
||||
time = "0#{time}" if time.length < 3
|
||||
@time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s"
|
||||
|
||||
@@ -146,15 +135,22 @@ class AtomReporter extends View
|
||||
@userSummary.append symbol
|
||||
|
||||
if coreSpecs > 0
|
||||
@coreHeader.text("Core Specs (#{coreSpecs}):")
|
||||
@coreHeader.text("Core Specs (#{coreSpecs})")
|
||||
else
|
||||
@coreArea.hide()
|
||||
if bundledPackageSpecs > 0
|
||||
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs}):")
|
||||
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs})")
|
||||
else
|
||||
@bundledArea.hide()
|
||||
if userPackageSpecs > 0
|
||||
@userHeader.text("User Package Specs (#{userPackageSpecs}):")
|
||||
if coreSpecs is 0 and bundledPackageSpecs is 0
|
||||
# Package specs being run, show a more descriptive label
|
||||
{specDirectory} = specs[0]
|
||||
packageFolderName = path.basename(path.dirname(specDirectory))
|
||||
packageName = _.undasherize(_.uncamelcase(packageFolderName))
|
||||
@userHeader.text("#{packageName} Specs")
|
||||
else
|
||||
@userHeader.text("User Package Specs (#{userPackageSpecs})")
|
||||
else
|
||||
@userArea.hide()
|
||||
|
||||
@@ -164,7 +160,7 @@ class AtomReporter extends View
|
||||
specComplete: (spec) ->
|
||||
specSummaryElement = $("#spec-summary-#{spec.id}")
|
||||
specSummaryElement.removeClass('pending')
|
||||
specSummaryElement.data("description", spec.getFullName())
|
||||
specSummaryElement.setTooltip(title: spec.getFullName(), container: '.spec-reporter')
|
||||
|
||||
results = spec.results()
|
||||
if results.skipped
|
||||
@@ -185,11 +181,9 @@ class SuiteResultView extends View
|
||||
@div class: 'suite', =>
|
||||
@div outlet: 'description', class: 'description'
|
||||
|
||||
suite: null
|
||||
|
||||
initialize: (@suite) ->
|
||||
@attr('id', "suite-view-#{@suite.id}")
|
||||
@description.html @suite.description
|
||||
@description.text(@suite.description)
|
||||
|
||||
attach: ->
|
||||
(@parentSuiteView() or $('.results')).append this
|
||||
@@ -206,20 +200,22 @@ class SuiteResultView extends View
|
||||
class SpecResultView extends View
|
||||
@content: ->
|
||||
@div class: 'spec', =>
|
||||
@div "\uf03d", class: 'spec-toggle'
|
||||
@div class: 'spec-toggle'
|
||||
@div outlet: 'description', class: 'description'
|
||||
@div outlet: 'specFailures', class: 'spec-failures'
|
||||
spec: null
|
||||
|
||||
initialize: (@spec) ->
|
||||
@addClass("spec-view-#{@spec.id}")
|
||||
@description.html @spec.description
|
||||
|
||||
description = @spec.description
|
||||
description = "it #{description}" if description.indexOf('it ') isnt 0
|
||||
@description.text(description)
|
||||
|
||||
for result in @spec.results().getItems() when not result.passed()
|
||||
stackTrace = formatStackTrace(result.trace.stack)
|
||||
stackTrace = formatStackTrace(@spec, result.message, result.trace.stack)
|
||||
@specFailures.append $$ ->
|
||||
@div result.message, class: 'resultMessage fail'
|
||||
@div stackTrace, class: 'stackTrace' if stackTrace
|
||||
@div result.message, class: 'result-message fail'
|
||||
@pre stackTrace, class: 'stack-trace padded' if stackTrace
|
||||
|
||||
attach: ->
|
||||
@parentSuiteView().append this
|
||||
|
||||
+226
-150
@@ -1,6 +1,7 @@
|
||||
{$, $$, fs, WorkspaceView} = require 'atom'
|
||||
{$, $$, WorkspaceView} = require 'atom'
|
||||
Exec = require('child_process').exec
|
||||
path = require 'path'
|
||||
Package = require '../src/package'
|
||||
ThemeManager = require '../src/theme-manager'
|
||||
|
||||
describe "the `atom` global", ->
|
||||
@@ -9,35 +10,28 @@ describe "the `atom` global", ->
|
||||
|
||||
describe "package lifecycle methods", ->
|
||||
describe ".loadPackage(name)", ->
|
||||
describe "when the package has deferred deserializers", ->
|
||||
it "requires the package's main module if one of its deferred deserializers is referenced", ->
|
||||
pack = atom.packages.loadPackage('package-with-activation-events')
|
||||
spyOn(pack, 'activateStylesheets').andCallThrough()
|
||||
expect(pack.mainModule).toBeNull()
|
||||
object = atom.deserializers.deserialize({deserializer: 'Foo', data: 5})
|
||||
expect(pack.mainModule).toBeDefined()
|
||||
expect(object.constructor.name).toBe 'Foo'
|
||||
expect(object.data).toBe 5
|
||||
expect(pack.activateStylesheets).toHaveBeenCalled()
|
||||
it "continues if the package has an invalid package.json", ->
|
||||
spyOn(console, 'warn')
|
||||
atom.config.set("core.disabledPackages", [])
|
||||
expect(-> atom.packages.loadPackage("package-with-broken-package-json")).not.toThrow()
|
||||
|
||||
it "continues if the package has an invalid package.json", ->
|
||||
spyOn(console, 'warn')
|
||||
atom.config.set("core.disabledPackages", [])
|
||||
expect(-> atom.packages.loadPackage("package-with-broken-package-json")).not.toThrow()
|
||||
|
||||
it "continues if the package has an invalid keymap", ->
|
||||
atom.config.set("core.disabledPackages", [])
|
||||
expect(-> atom.packages.loadPackage("package-with-broken-keymap")).not.toThrow()
|
||||
it "continues if the package has an invalid keymap", ->
|
||||
atom.config.set("core.disabledPackages", [])
|
||||
expect(-> atom.packages.loadPackage("package-with-broken-keymap")).not.toThrow()
|
||||
|
||||
describe ".unloadPackage(name)", ->
|
||||
describe "when the package is active", ->
|
||||
it "throws an error", ->
|
||||
pack = atom.packages.activatePackage('package-with-main')
|
||||
expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy()
|
||||
expect(atom.packages.isPackageActive(pack.name)).toBeTruthy()
|
||||
expect( -> atom.packages.unloadPackage(pack.name)).toThrow()
|
||||
expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy()
|
||||
expect(atom.packages.isPackageActive(pack.name)).toBeTruthy()
|
||||
pack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-main').then (p) -> pack = p
|
||||
|
||||
runs ->
|
||||
expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy()
|
||||
expect(atom.packages.isPackageActive(pack.name)).toBeTruthy()
|
||||
expect( -> atom.packages.unloadPackage(pack.name)).toThrow()
|
||||
expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy()
|
||||
expect(atom.packages.isPackageActive(pack.name)).toBeTruthy()
|
||||
|
||||
describe "when the package is not loaded", ->
|
||||
it "throws an error", ->
|
||||
@@ -54,44 +48,69 @@ describe "the `atom` global", ->
|
||||
|
||||
describe ".activatePackage(id)", ->
|
||||
describe "atom packages", ->
|
||||
describe "when called multiple times", ->
|
||||
it "it only calls activate on the package once", ->
|
||||
spyOn(Package.prototype, 'activateNow').andCallThrough()
|
||||
atom.packages.activatePackage('package-with-index')
|
||||
atom.packages.activatePackage('package-with-index')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-index')
|
||||
|
||||
runs ->
|
||||
expect(Package.prototype.activateNow.callCount).toBe 1
|
||||
|
||||
describe "when the package has a main module", ->
|
||||
describe "when the metadata specifies a main module path˜", ->
|
||||
it "requires the module at the specified path", ->
|
||||
mainModule = require('./fixtures/packages/package-with-main/main-module')
|
||||
spyOn(mainModule, 'activate')
|
||||
pack = atom.packages.activatePackage('package-with-main')
|
||||
expect(mainModule.activate).toHaveBeenCalled()
|
||||
expect(pack.mainModule).toBe mainModule
|
||||
pack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-main').then (p) -> pack = p
|
||||
|
||||
runs ->
|
||||
expect(mainModule.activate).toHaveBeenCalled()
|
||||
expect(pack.mainModule).toBe mainModule
|
||||
|
||||
describe "when the metadata does not specify a main module", ->
|
||||
it "requires index.coffee", ->
|
||||
indexModule = require('./fixtures/packages/package-with-index/index')
|
||||
spyOn(indexModule, 'activate')
|
||||
pack = atom.packages.activatePackage('package-with-index')
|
||||
expect(indexModule.activate).toHaveBeenCalled()
|
||||
expect(pack.mainModule).toBe indexModule
|
||||
pack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-index').then (p) -> pack = p
|
||||
|
||||
runs ->
|
||||
expect(indexModule.activate).toHaveBeenCalled()
|
||||
expect(pack.mainModule).toBe indexModule
|
||||
|
||||
it "assigns config defaults from the module", ->
|
||||
expect(atom.config.get('package-with-config-defaults.numbers.one')).toBeUndefined()
|
||||
atom.packages.activatePackage('package-with-config-defaults')
|
||||
expect(atom.config.get('package-with-config-defaults.numbers.one')).toBe 1
|
||||
expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-config-defaults')
|
||||
|
||||
runs ->
|
||||
expect(atom.config.get('package-with-config-defaults.numbers.one')).toBe 1
|
||||
expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2
|
||||
|
||||
describe "when the package metadata includes activation events", ->
|
||||
[mainModule, pack] = []
|
||||
[mainModule, promise] = []
|
||||
|
||||
beforeEach ->
|
||||
mainModule = require './fixtures/packages/package-with-activation-events/index'
|
||||
spyOn(mainModule, 'activate').andCallThrough()
|
||||
AtomPackage = require '../src/atom-package'
|
||||
spyOn(AtomPackage.prototype, 'requireMainModule').andCallThrough()
|
||||
pack = atom.packages.activatePackage('package-with-activation-events')
|
||||
spyOn(Package.prototype, 'requireMainModule').andCallThrough()
|
||||
|
||||
promise = atom.packages.activatePackage('package-with-activation-events')
|
||||
|
||||
it "defers requiring/activating the main module until an activation event bubbles to the root view", ->
|
||||
expect(pack.requireMainModule).not.toHaveBeenCalled()
|
||||
expect(mainModule.activate).not.toHaveBeenCalled()
|
||||
expect(promise.isFulfilled()).not.toBeTruthy()
|
||||
atom.workspaceView.trigger 'activation-event'
|
||||
expect(mainModule.activate).toHaveBeenCalled()
|
||||
|
||||
waitsForPromise ->
|
||||
promise
|
||||
|
||||
it "triggers the activation event on all handlers registered during activation", ->
|
||||
atom.workspaceView.openSync()
|
||||
@@ -116,13 +135,17 @@ describe "the `atom` global", ->
|
||||
expect(console.warn).not.toHaveBeenCalled()
|
||||
|
||||
it "passes the activate method the package's previously serialized state if it exists", ->
|
||||
pack = atom.packages.activatePackage("package-with-serialization")
|
||||
expect(pack.mainModule.someNumber).not.toBe 77
|
||||
pack.mainModule.someNumber = 77
|
||||
atom.packages.deactivatePackage("package-with-serialization")
|
||||
spyOn(pack.mainModule, 'activate').andCallThrough()
|
||||
atom.packages.activatePackage("package-with-serialization")
|
||||
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
|
||||
pack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p
|
||||
|
||||
runs ->
|
||||
expect(pack.mainModule.someNumber).not.toBe 77
|
||||
pack.mainModule.someNumber = 77
|
||||
atom.packages.deactivatePackage("package-with-serialization")
|
||||
spyOn(pack.mainModule, 'activate').andCallThrough()
|
||||
atom.packages.activatePackage("package-with-serialization")
|
||||
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
|
||||
|
||||
it "logs warning instead of throwing an exception if the package fails to load", ->
|
||||
atom.config.set("core.disabledPackages", [])
|
||||
@@ -239,116 +262,155 @@ describe "the `atom` global", ->
|
||||
|
||||
describe "grammar loading", ->
|
||||
it "loads the package's grammars", ->
|
||||
atom.packages.activatePackage('package-with-grammars')
|
||||
expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Alot'
|
||||
expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Alittle'
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-grammars')
|
||||
|
||||
runs ->
|
||||
expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Alot'
|
||||
expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Alittle'
|
||||
|
||||
describe "scoped-property loading", ->
|
||||
it "loads the scoped properties", ->
|
||||
atom.packages.activatePackage("package-with-scoped-properties")
|
||||
expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a'
|
||||
|
||||
describe "textmate packages", ->
|
||||
it "loads the package's grammars", ->
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
|
||||
atom.packages.activatePackage('language-ruby', sync: true)
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
|
||||
|
||||
it "translates the package's scoped properties to Atom terms", ->
|
||||
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
|
||||
atom.packages.activatePackage('language-ruby', sync: true)
|
||||
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# '
|
||||
|
||||
describe "when the package has no grammars but does have preferences", ->
|
||||
it "loads the package's preferences as scoped properties", ->
|
||||
jasmine.unspy(window, 'setTimeout')
|
||||
spyOn(atom.syntax, 'addProperties').andCallThrough()
|
||||
|
||||
atom.packages.activatePackage('package-with-preferences-tmbundle')
|
||||
|
||||
waitsFor ->
|
||||
atom.syntax.addProperties.callCount > 0
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-scoped-properties")
|
||||
|
||||
runs ->
|
||||
expect(atom.syntax.getProperty(['.source.pref'], 'editor.increaseIndentPattern')).toBe '^abc$'
|
||||
expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a'
|
||||
|
||||
describe "converted textmate packages", ->
|
||||
it "loads the package's grammars", ->
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-ruby')
|
||||
|
||||
runs ->
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
|
||||
|
||||
it "loads the translated scoped properties", ->
|
||||
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-ruby')
|
||||
|
||||
runs ->
|
||||
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# '
|
||||
|
||||
describe ".deactivatePackage(id)", ->
|
||||
describe "atom packages", ->
|
||||
it "calls `deactivate` on the package's main module if activate was successful", ->
|
||||
pack = atom.packages.activatePackage("package-with-deactivate")
|
||||
expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy()
|
||||
spyOn(pack.mainModule, 'deactivate').andCallThrough()
|
||||
pack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-deactivate").then (p) -> pack = p
|
||||
|
||||
atom.packages.deactivatePackage("package-with-deactivate")
|
||||
expect(pack.mainModule.deactivate).toHaveBeenCalled()
|
||||
expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy()
|
||||
runs ->
|
||||
expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy()
|
||||
spyOn(pack.mainModule, 'deactivate').andCallThrough()
|
||||
|
||||
spyOn(console, 'warn')
|
||||
badPack = atom.packages.activatePackage("package-that-throws-on-activate")
|
||||
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy()
|
||||
spyOn(badPack.mainModule, 'deactivate').andCallThrough()
|
||||
atom.packages.deactivatePackage("package-with-deactivate")
|
||||
expect(pack.mainModule.deactivate).toHaveBeenCalled()
|
||||
expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy()
|
||||
|
||||
atom.packages.deactivatePackage("package-that-throws-on-activate")
|
||||
expect(badPack.mainModule.deactivate).not.toHaveBeenCalled()
|
||||
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy()
|
||||
spyOn(console, 'warn')
|
||||
|
||||
badPack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
|
||||
|
||||
runs ->
|
||||
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy()
|
||||
spyOn(badPack.mainModule, 'deactivate').andCallThrough()
|
||||
|
||||
atom.packages.deactivatePackage("package-that-throws-on-activate")
|
||||
expect(badPack.mainModule.deactivate).not.toHaveBeenCalled()
|
||||
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy()
|
||||
|
||||
it "does not serialize packages that have not been activated called on their main module", ->
|
||||
spyOn(console, 'warn')
|
||||
badPack = atom.packages.activatePackage("package-that-throws-on-activate")
|
||||
spyOn(badPack.mainModule, 'serialize').andCallThrough()
|
||||
badPack = null
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
|
||||
|
||||
atom.packages.deactivatePackage("package-that-throws-on-activate")
|
||||
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
|
||||
runs ->
|
||||
spyOn(badPack.mainModule, 'serialize').andCallThrough()
|
||||
|
||||
atom.packages.deactivatePackage("package-that-throws-on-activate")
|
||||
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
|
||||
|
||||
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
|
||||
spyOn(console, 'error')
|
||||
atom.packages.activatePackage('package-with-serialize-error', immediate: true)
|
||||
atom.packages.activatePackage('package-with-serialization', immediate: true)
|
||||
atom.packages.deactivatePackages()
|
||||
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
|
||||
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-serialize-error')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-serialization')
|
||||
|
||||
runs ->
|
||||
atom.packages.deactivatePackages()
|
||||
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
|
||||
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
|
||||
it "removes the package's grammars", ->
|
||||
atom.packages.activatePackage('package-with-grammars')
|
||||
atom.packages.deactivatePackage('package-with-grammars')
|
||||
expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Null Grammar'
|
||||
expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Null Grammar'
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-grammars')
|
||||
|
||||
runs ->
|
||||
atom.packages.deactivatePackage('package-with-grammars')
|
||||
expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Null Grammar'
|
||||
expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Null Grammar'
|
||||
|
||||
it "removes the package's keymaps", ->
|
||||
atom.packages.activatePackage('package-with-keymaps')
|
||||
atom.packages.deactivatePackage('package-with-keymaps')
|
||||
expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0
|
||||
expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-keymaps')
|
||||
|
||||
runs ->
|
||||
atom.packages.deactivatePackage('package-with-keymaps')
|
||||
expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0
|
||||
expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0
|
||||
|
||||
it "removes the package's stylesheets", ->
|
||||
atom.packages.activatePackage('package-with-stylesheets')
|
||||
atom.packages.deactivatePackage('package-with-stylesheets')
|
||||
one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css")
|
||||
two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less")
|
||||
three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css")
|
||||
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-stylesheets')
|
||||
|
||||
runs ->
|
||||
atom.packages.deactivatePackage('package-with-stylesheets')
|
||||
one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css")
|
||||
two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less")
|
||||
three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css")
|
||||
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
|
||||
|
||||
it "removes the package's scoped-properties", ->
|
||||
atom.packages.activatePackage("package-with-scoped-properties")
|
||||
expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a'
|
||||
atom.packages.deactivatePackage("package-with-scoped-properties")
|
||||
expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined()
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage("package-with-scoped-properties")
|
||||
|
||||
runs ->
|
||||
expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a'
|
||||
atom.packages.deactivatePackage("package-with-scoped-properties")
|
||||
expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined()
|
||||
|
||||
describe "textmate packages", ->
|
||||
it "removes the package's grammars", ->
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
|
||||
atom.packages.activatePackage('language-ruby', sync: true)
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
|
||||
atom.packages.deactivatePackage('language-ruby')
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-ruby')
|
||||
|
||||
runs ->
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
|
||||
atom.packages.deactivatePackage('language-ruby')
|
||||
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
|
||||
|
||||
it "removes the package's scoped properties", ->
|
||||
atom.packages.activatePackage('language-ruby', sync: true)
|
||||
atom.packages.deactivatePackage('language-ruby')
|
||||
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-ruby')
|
||||
|
||||
runs ->
|
||||
atom.packages.deactivatePackage('language-ruby')
|
||||
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
|
||||
|
||||
describe ".activate()", ->
|
||||
packageActivator = null
|
||||
@@ -382,7 +444,7 @@ describe "the `atom` global", ->
|
||||
themes = themeActivator.mostRecentCall.args[0]
|
||||
expect(['theme']).toContain(theme.getType()) for theme in themes
|
||||
|
||||
describe ".en/disablePackage()", ->
|
||||
describe ".enablePackage() and disablePackage()", ->
|
||||
describe "with packages", ->
|
||||
it ".enablePackage() enables a disabled package", ->
|
||||
packageName = 'package-with-main'
|
||||
@@ -391,28 +453,36 @@ describe "the `atom` global", ->
|
||||
expect(atom.config.get('core.disabledPackages')).toContain packageName
|
||||
|
||||
pack = atom.packages.enablePackage(packageName)
|
||||
|
||||
loadedPackages = atom.packages.getLoadedPackages()
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(loadedPackages).toContain(pack)
|
||||
expect(activatedPackages).toContain(pack)
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
activatedPackages = null
|
||||
waitsFor ->
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
activatedPackages.length > 0
|
||||
|
||||
runs ->
|
||||
expect(loadedPackages).toContain(pack)
|
||||
expect(activatedPackages).toContain(pack)
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
it ".disablePackage() disables an enabled package", ->
|
||||
packageName = 'package-with-main'
|
||||
atom.packages.activatePackage(packageName)
|
||||
atom.packages.observeDisabledPackages()
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(packageName)
|
||||
|
||||
pack = atom.packages.disablePackage(packageName)
|
||||
runs ->
|
||||
atom.packages.observeDisabledPackages()
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).not.toContain(pack)
|
||||
expect(atom.config.get('core.disabledPackages')).toContain packageName
|
||||
pack = atom.packages.disablePackage(packageName)
|
||||
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).not.toContain(pack)
|
||||
expect(atom.config.get('core.disabledPackages')).toContain packageName
|
||||
|
||||
describe "with themes", ->
|
||||
beforeEach ->
|
||||
atom.themes.activateThemes()
|
||||
waitsForPromise ->
|
||||
atom.themes.activateThemes()
|
||||
|
||||
afterEach ->
|
||||
atom.themes.deactivateThemes()
|
||||
@@ -426,18 +496,24 @@ describe "the `atom` global", ->
|
||||
|
||||
# enabling of theme
|
||||
pack = atom.packages.enablePackage(packageName)
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).toContain(pack)
|
||||
expect(atom.config.get('core.themes')).toContain packageName
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
# disabling of theme
|
||||
pack = atom.packages.disablePackage(packageName)
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).not.toContain(pack)
|
||||
expect(atom.config.get('core.themes')).not.toContain packageName
|
||||
expect(atom.config.get('core.themes')).not.toContain packageName
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
activatedPackages = null
|
||||
waitsFor ->
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
activatedPackages.length > 0
|
||||
|
||||
runs ->
|
||||
expect(activatedPackages).toContain(pack)
|
||||
expect(atom.config.get('core.themes')).toContain packageName
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
# disabling of theme
|
||||
pack = atom.packages.disablePackage(packageName)
|
||||
activatedPackages = atom.packages.getActivePackages()
|
||||
expect(activatedPackages).not.toContain(pack)
|
||||
expect(atom.config.get('core.themes')).not.toContain packageName
|
||||
expect(atom.config.get('core.themes')).not.toContain packageName
|
||||
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
|
||||
|
||||
describe ".isReleasedVersion()", ->
|
||||
it "returns false if the version is a SHA and true otherwise", ->
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
describe "Clipboard", ->
|
||||
describe "write(text, metadata) and read()", ->
|
||||
it "writes and reads text to/from the native clipboard", ->
|
||||
expect(atom.clipboard.read()).toBe 'initial clipboard content'
|
||||
atom.clipboard.write('next')
|
||||
expect(atom.clipboard.read()).toBe 'next'
|
||||
|
||||
it "returns metadata if the item on the native clipboard matches the last written item", ->
|
||||
atom.clipboard.write('next', {meta: 'data'})
|
||||
expect(atom.clipboard.read()).toBe 'next'
|
||||
expect(atom.clipboard.readWithMetadata().text).toBe 'next'
|
||||
expect(atom.clipboard.readWithMetadata().metadata).toEqual {meta: 'data'}
|
||||
@@ -1,36 +1,34 @@
|
||||
{fs} = require 'atom'
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
temp = require 'temp'
|
||||
installer = require '../src/command-installer'
|
||||
|
||||
describe "install(commandPath, callback)", ->
|
||||
directory = path.join(temp.dir, 'install-atom-command', 'atom')
|
||||
commandPath = path.join(directory, 'source')
|
||||
destinationPath = path.join(directory, 'bin', 'source')
|
||||
commandFilePath = temp.openSync("atom-command").path
|
||||
commandName = path.basename(commandFilePath)
|
||||
installationPath = temp.mkdirSync("atom-bin")
|
||||
installationFilePath = path.join(installationPath, commandName)
|
||||
|
||||
beforeEach ->
|
||||
spyOn(installer, 'findInstallDirectory').andCallFake (callback) ->
|
||||
callback(directory)
|
||||
|
||||
fs.removeSync(directory) if fs.existsSync(directory)
|
||||
fs.chmodSync(commandFilePath, '755')
|
||||
spyOn(installer, 'getInstallDirectory').andReturn installationPath
|
||||
|
||||
describe "on #darwin", ->
|
||||
it "symlinks the command and makes it executable", ->
|
||||
fs.writeFileSync(commandPath, 'test')
|
||||
expect(fs.isFileSync(commandPath)).toBeTruthy()
|
||||
expect(fs.isExecutableSync(commandPath)).toBeFalsy()
|
||||
expect(fs.isFileSync(destinationPath)).toBeFalsy()
|
||||
expect(fs.isFileSync(commandFilePath)).toBeTruthy()
|
||||
expect(fs.isFileSync(installationFilePath)).toBeFalsy()
|
||||
|
||||
installDone = false
|
||||
installError = null
|
||||
installer.install commandPath, (error) ->
|
||||
installer.install commandFilePath, false, (error) ->
|
||||
installDone = true
|
||||
installError = error
|
||||
|
||||
waitsFor -> installDone
|
||||
waitsFor ->
|
||||
installDone
|
||||
|
||||
runs ->
|
||||
expect(installError).toBeNull()
|
||||
expect(fs.isFileSync(destinationPath)).toBeTruthy()
|
||||
expect(fs.realpathSync(destinationPath)).toBe fs.realpathSync(commandPath)
|
||||
expect(fs.isExecutableSync(destinationPath)).toBeTruthy()
|
||||
expect(fs.isFileSync(installationFilePath)).toBeTruthy()
|
||||
expect(fs.realpathSync(installationFilePath)).toBe fs.realpathSync(commandFilePath)
|
||||
expect(fs.isExecutableSync(installationFilePath)).toBeTruthy()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{fs} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
describe "Config", ->
|
||||
dotAtomPath = path.join(temp.dir, 'dot-atom-dir')
|
||||
@@ -63,6 +63,19 @@ describe "Config", ->
|
||||
atom.config.toggle('foo.a')
|
||||
expect(atom.config.get('foo.a')).toBe false
|
||||
|
||||
describe ".restoreDefault(keyPath)", ->
|
||||
it "sets the value of the key path to its default", ->
|
||||
atom.config.setDefaults('a', b: 3)
|
||||
atom.config.set('a.b', 4)
|
||||
expect(atom.config.get('a.b')).toBe 4
|
||||
atom.config.restoreDefault('a.b')
|
||||
expect(atom.config.get('a.b')).toBe 3
|
||||
|
||||
atom.config.set('a.c', 5)
|
||||
expect(atom.config.get('a.c')).toBe 5
|
||||
atom.config.restoreDefault('a.c')
|
||||
expect(atom.config.get('a.c')).toBeUndefined()
|
||||
|
||||
describe ".pushAtKeyPath(keyPath, value)", ->
|
||||
it "pushes the given value to the array at the key path and updates observers", ->
|
||||
atom.config.set("foo.bar.baz", ["a"])
|
||||
@@ -204,7 +217,7 @@ describe "Config", ->
|
||||
expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(dotAtomPath) if fs.existsSync(dotAtomPath)
|
||||
fs.removeSync(dotAtomPath)
|
||||
|
||||
describe "when the configDirPath doesn't exist", ->
|
||||
it "copies the contents of dot-atom to ~/.atom", ->
|
||||
@@ -218,8 +231,10 @@ describe "Config", ->
|
||||
runs ->
|
||||
expect(fs.existsSync(atom.config.configDirPath)).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(atom.config.configDirPath, 'packages'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.join(atom.config.configDirPath, 'snippets'))).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(atom.config.configDirPath, 'snippets.cson'))).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(atom.config.configDirPath, 'config.cson'))).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(atom.config.configDirPath, 'init.coffee'))).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(atom.config.configDirPath, 'styles.less'))).toBeTruthy()
|
||||
|
||||
describe ".loadUserConfig()", ->
|
||||
beforeEach ->
|
||||
@@ -228,7 +243,7 @@ describe "Config", ->
|
||||
expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(dotAtomPath) if fs.existsSync(dotAtomPath)
|
||||
fs.removeSync(dotAtomPath)
|
||||
|
||||
describe "when the config file contains valid cson", ->
|
||||
beforeEach ->
|
||||
@@ -271,7 +286,7 @@ describe "Config", ->
|
||||
|
||||
afterEach ->
|
||||
atom.config.unobserveUserConfig()
|
||||
fs.removeSync(dotAtomPath) if fs.existsSync(dotAtomPath)
|
||||
fs.removeSync(dotAtomPath)
|
||||
|
||||
describe "when the config file changes to contain valid cson", ->
|
||||
it "updates the config data", ->
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
Directory = require '../src/directory'
|
||||
{fs} = require 'atom'
|
||||
path = require 'path'
|
||||
|
||||
describe "Directory", ->
|
||||
directory = null
|
||||
|
||||
beforeEach ->
|
||||
directory = new Directory(path.join(__dirname, 'fixtures'))
|
||||
|
||||
afterEach ->
|
||||
directory.off()
|
||||
|
||||
describe "when the contents of the directory change on disk", ->
|
||||
temporaryFilePath = null
|
||||
|
||||
beforeEach ->
|
||||
temporaryFilePath = path.join(__dirname, 'fixtures', 'temporary')
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
it "triggers 'contents-changed' event handlers", ->
|
||||
changeHandler = null
|
||||
|
||||
runs ->
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
directory.on 'contents-changed', changeHandler
|
||||
fs.writeFileSync(temporaryFilePath, '')
|
||||
|
||||
waitsFor "first change", -> changeHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
changeHandler.reset()
|
||||
fs.removeSync(temporaryFilePath)
|
||||
|
||||
waitsFor "second change", -> changeHandler.callCount > 0
|
||||
|
||||
describe "when the directory unsubscribes from events", ->
|
||||
temporaryFilePath = null
|
||||
|
||||
beforeEach ->
|
||||
temporaryFilePath = path.join(directory.path, 'temporary')
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(temporaryFilePath) if fs.existsSync(temporaryFilePath)
|
||||
|
||||
it "no longer triggers events", ->
|
||||
changeHandler = null
|
||||
|
||||
runs ->
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
directory.on 'contents-changed', changeHandler
|
||||
fs.writeFileSync(temporaryFilePath, '')
|
||||
|
||||
waitsFor "change event", -> changeHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
changeHandler.reset()
|
||||
directory.off()
|
||||
waits 20
|
||||
|
||||
runs -> fs.removeSync(temporaryFilePath)
|
||||
waits 20
|
||||
runs -> expect(changeHandler.callCount).toBe 0
|
||||
|
||||
describe "on #darwin or #linux", ->
|
||||
it "includes symlink information about entries", ->
|
||||
entries = directory.getEntriesSync()
|
||||
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()
|
||||
|
||||
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", ->
|
||||
absolutePath = directory.getPath()
|
||||
expect(directory.relativize(absolutePath)).toBe ''
|
||||
expect(directory.relativize(path.join(absolutePath, "b"))).toBe "b"
|
||||
expect(directory.relativize(path.join(absolutePath, "b/file.coffee"))).toBe "b/file.coffee"
|
||||
expect(directory.relativize(path.join(absolutePath, "file.coffee"))).toBe "file.coffee"
|
||||
|
||||
it "returns a relative path based on the directory's symlinked source path", ->
|
||||
symlinkPath = path.join(__dirname, 'fixtures', 'symlink-to-dir')
|
||||
symlinkDirectory = new Directory(symlinkPath)
|
||||
realFilePath = require.resolve('./fixtures/dir/a')
|
||||
expect(symlinkDirectory.relativize(symlinkPath)).toBe ''
|
||||
expect(symlinkDirectory.relativize(realFilePath)).toBe 'a'
|
||||
|
||||
it "returns the full path if the directory's path is not a prefix of the path", ->
|
||||
expect(directory.relativize('/not/relative')).toBe '/not/relative'
|
||||
|
||||
describe "on #win32", ->
|
||||
it "returns a relative path based on the directory's path", ->
|
||||
absolutePath = directory.getPath()
|
||||
expect(directory.relativize(absolutePath)).toBe ''
|
||||
expect(directory.relativize(path.join(absolutePath, "b"))).toBe "b"
|
||||
expect(directory.relativize(path.join(absolutePath, "b/file.coffee"))).toBe "b\\file.coffee"
|
||||
expect(directory.relativize(path.join(absolutePath, "file.coffee"))).toBe "file.coffee"
|
||||
|
||||
it "returns the full path if the directory's path is not a prefix of the path", ->
|
||||
expect(directory.relativize('/not/relative')).toBe "\\not\\relative"
|
||||
|
||||
describe ".contains(path)", ->
|
||||
it "returns true if the path is a child of the directory's path", ->
|
||||
absolutePath = directory.getPath()
|
||||
expect(directory.contains(path.join(absolutePath, "b"))).toBe true
|
||||
expect(directory.contains(path.join(absolutePath, "b", "file.coffee"))).toBe true
|
||||
expect(directory.contains(path.join(absolutePath, "file.coffee"))).toBe true
|
||||
|
||||
it "returns false if the directory's path is not a prefix of the path", ->
|
||||
expect(directory.contains('/not/relative')).toBe false
|
||||
|
||||
describe "on #darwin or #linux", ->
|
||||
it "returns true if the path is a child of the directory's symlinked source path", ->
|
||||
symlinkPath = path.join(__dirname, 'fixtures', 'symlink-to-dir')
|
||||
symlinkDirectory = new Directory(symlinkPath)
|
||||
realFilePath = require.resolve('./fixtures/dir/a')
|
||||
expect(symlinkDirectory.contains(realFilePath)).toBe true
|
||||
@@ -1,16 +1,19 @@
|
||||
DisplayBuffer = require '../src/display-buffer'
|
||||
{_} = require 'atom'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
describe "DisplayBuffer", ->
|
||||
[displayBuffer, buffer, changeHandler, tabLength] = []
|
||||
beforeEach ->
|
||||
tabLength = 2
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
displayBuffer = new DisplayBuffer({buffer, tabLength})
|
||||
changeHandler = jasmine.createSpy 'changeHandler'
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
afterEach ->
|
||||
displayBuffer.destroy()
|
||||
buffer.release()
|
||||
|
||||
+51
-27
@@ -16,11 +16,13 @@ describe "Editor", ->
|
||||
|
||||
describe "with default options", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
editor = atom.project.openSync('sample.js', autoIndent: false)
|
||||
buffer = editor.buffer
|
||||
lineLengths = buffer.getLines().map (line) -> line.length
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
describe "when the editor is deserialized", ->
|
||||
it "restores selections and folds based on markers in the buffer", ->
|
||||
editor.setSelectedBufferRange([[1, 2], [3, 4]])
|
||||
@@ -1856,12 +1858,12 @@ describe "Editor", ->
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 2]
|
||||
expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength() * 2]
|
||||
|
||||
describe "pasteboard operations", ->
|
||||
describe "clipboard operations", ->
|
||||
beforeEach ->
|
||||
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
|
||||
|
||||
describe ".cutSelectedText()", ->
|
||||
it "removes the selected text from the buffer and places it on the pasteboard", ->
|
||||
it "removes the selected text from the buffer and places it on the clipboard", ->
|
||||
editor.cutSelectedText()
|
||||
expect(buffer.lineForRow(0)).toBe "var = function () {"
|
||||
expect(buffer.lineForRow(1)).toBe " var = function(items) {"
|
||||
@@ -1885,7 +1887,7 @@ describe "Editor", ->
|
||||
editor.cutToEndOfLine()
|
||||
expect(buffer.lineForRow(2)).toBe ' if (items.length'
|
||||
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
|
||||
expect(atom.pasteboard.read()[0]).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
|
||||
expect(atom.clipboard.read()).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
|
||||
|
||||
describe "when text is selected", ->
|
||||
it "only cuts the selected text, not to the end of the line", ->
|
||||
@@ -1895,7 +1897,7 @@ describe "Editor", ->
|
||||
|
||||
expect(buffer.lineForRow(2)).toBe ' if (items.lengthurn items;'
|
||||
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
|
||||
expect(atom.pasteboard.read()[0]).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
|
||||
expect(atom.clipboard.read()).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
|
||||
|
||||
describe ".copySelectedText()", ->
|
||||
it "copies selected text onto the clipboard", ->
|
||||
@@ -1906,7 +1908,7 @@ describe "Editor", ->
|
||||
|
||||
describe ".pasteText()", ->
|
||||
it "pastes text into the buffer", ->
|
||||
atom.pasteboard.write('first')
|
||||
atom.clipboard.write('first')
|
||||
editor.pasteText()
|
||||
expect(editor.buffer.lineForRow(0)).toBe "var first = function () {"
|
||||
expect(buffer.lineForRow(1)).toBe " var first = function(items) {"
|
||||
@@ -2722,21 +2724,37 @@ describe "Editor", ->
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
||||
|
||||
describe ".setIndentationForBufferRow", ->
|
||||
describe "when the editor uses soft tabs but the row has hard tabs", ->
|
||||
it "only replaces whitespace charachters", ->
|
||||
editor.setSoftWrap(true)
|
||||
editor.setText(" 1\n 2")
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.setIndentationForBufferRow(0, 2)
|
||||
expect(editor.getText()).toBe(" 1\n 2")
|
||||
|
||||
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)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-text')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
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")
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-hyperlink')
|
||||
|
||||
expect(tokens[0].value).toBe "var"
|
||||
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
|
||||
runs ->
|
||||
grammar = atom.syntax.selectGrammar("text.js")
|
||||
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
|
||||
|
||||
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"]
|
||||
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", ->
|
||||
@@ -2747,11 +2765,13 @@ describe "Editor", ->
|
||||
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)
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-hyperlink')
|
||||
|
||||
{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"]
|
||||
runs ->
|
||||
{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", ->
|
||||
@@ -2762,14 +2782,18 @@ describe "Editor", ->
|
||||
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)
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('package-with-injection-selector')
|
||||
|
||||
{tokens} = editor.lineForScreenRow(0)
|
||||
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
|
||||
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
|
||||
runs ->
|
||||
{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)
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-sql')
|
||||
|
||||
{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"]
|
||||
runs ->
|
||||
{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"]
|
||||
|
||||
+532
-398
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -1,132 +0,0 @@
|
||||
{File, fs} = require 'atom'
|
||||
path = require 'path'
|
||||
|
||||
describe 'File', ->
|
||||
[filePath, file] = []
|
||||
|
||||
beforeEach ->
|
||||
filePath = path.join(__dirname, 'fixtures', 'atom-file-test.txt') # Don't put in /tmp because /tmp symlinks to /private/tmp and screws up the rename test
|
||||
fs.removeSync(filePath) if fs.existsSync(filePath)
|
||||
fs.writeFileSync(filePath, "this is old!")
|
||||
file = new File(filePath)
|
||||
|
||||
afterEach ->
|
||||
file.off()
|
||||
fs.removeSync(filePath) if fs.existsSync(filePath)
|
||||
|
||||
describe "when the file has not been read", ->
|
||||
describe "when the contents of the file change", ->
|
||||
it "triggers 'contents-changed' event handlers", ->
|
||||
file.on 'contents-changed', changeHandler = jasmine.createSpy('changeHandler')
|
||||
fs.writeFileSync(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when the file has already been read", ->
|
||||
beforeEach ->
|
||||
file.readSync()
|
||||
|
||||
describe "when the contents of the file change", ->
|
||||
it "triggers 'contents-changed' event handlers", ->
|
||||
changeHandler = null
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-changed', changeHandler
|
||||
fs.writeFileSync(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
changeHandler.reset()
|
||||
fs.writeFileSync(file.getPath(), "this is newer!")
|
||||
|
||||
waitsFor "second change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when the file is removed", ->
|
||||
it "triggers 'remove' event handlers", ->
|
||||
removeHandler = null
|
||||
removeHandler = jasmine.createSpy('removeHandler')
|
||||
file.on 'removed', removeHandler
|
||||
fs.removeSync(file.getPath())
|
||||
|
||||
waitsFor "remove event", ->
|
||||
removeHandler.callCount > 0
|
||||
|
||||
describe "when a file is moved (via the filesystem)", ->
|
||||
newPath = null
|
||||
|
||||
beforeEach ->
|
||||
newPath = path.join(path.dirname(filePath), "atom-file-was-moved-test.txt")
|
||||
|
||||
afterEach ->
|
||||
if fs.existsSync(newPath)
|
||||
fs.removeSync(newPath)
|
||||
waitsFor "remove event", (done) -> file.on 'removed', done
|
||||
|
||||
it "it updates its path", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'moved', moveHandler
|
||||
|
||||
fs.moveSync(filePath, newPath)
|
||||
|
||||
waitsFor "move event", ->
|
||||
moveHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(file.getPath()).toBe newPath
|
||||
|
||||
it "maintains 'contents-changed' events set on previous path", ->
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'moved', moveHandler
|
||||
changeHandler = null
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-changed', changeHandler
|
||||
|
||||
fs.moveSync(filePath, newPath)
|
||||
|
||||
waitsFor "move event", ->
|
||||
moveHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
fs.writeFileSync(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when a file is deleted and the recreated within a small amount of time (git sometimes does this)", ->
|
||||
it "triggers a contents change event if the contents change", ->
|
||||
jasmine.unspy(File.prototype, 'detectResurrectionAfterDelay')
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
|
||||
changeHandler = jasmine.createSpy("file changed")
|
||||
removeHandler = jasmine.createSpy("file removed")
|
||||
file.on 'contents-changed', changeHandler
|
||||
file.on 'removed', removeHandler
|
||||
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
|
||||
fs.removeSync(filePath)
|
||||
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
waits 20
|
||||
runs ->
|
||||
fs.writeFileSync(filePath, "HE HAS RISEN!")
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
|
||||
waitsFor "resurrection change event", ->
|
||||
changeHandler.callCount == 1
|
||||
|
||||
runs ->
|
||||
expect(removeHandler).not.toHaveBeenCalled()
|
||||
fs.writeFileSync(filePath, "Hallelujah!")
|
||||
changeHandler.reset()
|
||||
|
||||
waitsFor "post-resurrection change event", ->
|
||||
changeHandler.callCount > 0
|
||||
@@ -1,2 +1 @@
|
||||
'activationEvents': ['activation-event']
|
||||
'deferredDeserializers': ['Foo']
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
I am hidden so I shouldn't be loaded
|
||||
-1
@@ -1 +0,0 @@
|
||||
I am not a valid plist but that shouldn't cause a crisis
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "my preferences",
|
||||
"scope": "source.pref",
|
||||
"settings": {
|
||||
"increaseIndentPattern": "^abc$"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1 @@
|
||||
# This package loads async, otherwise it would log errors when it
|
||||
# is automatically serialized when workspaceView is deactivatated
|
||||
|
||||
'main': 'index.coffee'
|
||||
'activationEvents': ['activation-event']
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"theme": true
|
||||
"theme": "ui"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"theme": true
|
||||
"theme": "ui"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"theme": true
|
||||
"theme": "ui"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"theme": true,
|
||||
"theme": "ui",
|
||||
"stylesheets": ["first.css", "second.less", "last.css"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"theme": true,
|
||||
"theme": "ui",
|
||||
"stylesheets": ["editor.less"]
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,6 +15,6 @@ var quicksort = function () {
|
||||
}
|
||||
return sort(left).concat(pivot).concat(sort(right));
|
||||
};
|
||||
|
||||
// this is a single-line comment
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
temp = require 'temp'
|
||||
Git = require '../src/git'
|
||||
{fs} = require 'atom'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
Task = require '../src/task'
|
||||
|
||||
@@ -187,6 +187,7 @@ describe "Git", ->
|
||||
newPath = atom.project.resolve('git/working-dir/untracked.txt')
|
||||
cleanPath = atom.project.resolve('git/working-dir/other.txt')
|
||||
fs.writeFileSync(newPath, '')
|
||||
newPath = fs.absolute newPath # specs could be running under symbol path.
|
||||
|
||||
afterEach ->
|
||||
fs.writeFileSync(modifiedPath, originalModifiedPathText)
|
||||
|
||||
@@ -4,8 +4,7 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
|
||||
{$, $$} = require 'atom'
|
||||
window[key] = value for key, value of require '../vendor/jasmine'
|
||||
|
||||
require 'jasmine-focused'
|
||||
require 'jasmine-tagged'
|
||||
{TerminalReporter} = require 'jasmine-tagged'
|
||||
|
||||
TimeReporter = require './time-reporter'
|
||||
timeReporter = new TimeReporter()
|
||||
@@ -18,15 +17,10 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
|
||||
process.stderr.write(str)
|
||||
|
||||
if atom.getLoadSettings().exitWhenDone
|
||||
{jasmineNode} = require 'jasmine-node/lib/jasmine-node/reporter'
|
||||
reporter = new jasmineNode.TerminalReporter
|
||||
reporter = new TerminalReporter
|
||||
print: (str) ->
|
||||
log(str)
|
||||
onComplete: (runner) ->
|
||||
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
|
||||
|
||||
@@ -354,6 +354,27 @@ describe "Keymap", ->
|
||||
bindings = keymap.keyBindingsForCommandMatchingElement('cultivate', el)
|
||||
expect(bindings).toHaveLength 0
|
||||
|
||||
describe "loading platform specific keybindings", ->
|
||||
customKeymap = null
|
||||
|
||||
beforeEach ->
|
||||
resourcePath = temp.mkdirSync('atom')
|
||||
customKeymap = new Keymap({configDirPath, resourcePath})
|
||||
|
||||
afterEach ->
|
||||
customKeymap.destroy()
|
||||
|
||||
it "doesn't load keybindings from other platforms", ->
|
||||
win32FilePath = path.join(resourcePath, "keymaps", "win32.cson")
|
||||
darwinFilePath = path.join(resourcePath, "keymaps", "darwin.cson")
|
||||
fs.writeFileSync(win32FilePath, '"body": "ctrl-l": "core:win32-move-left"')
|
||||
fs.writeFileSync(darwinFilePath, '"body": "ctrl-l": "core:darwin-move-left"')
|
||||
|
||||
customKeymap.loadBundledKeymaps()
|
||||
keyBindings = customKeymap.keyBindingsForKeystroke('ctrl-l')
|
||||
expect(keyBindings).toHaveLength 1
|
||||
expect(keyBindings[0].command).toBe "core:#{process.platform}-move-left"
|
||||
|
||||
describe "when the user keymap file is changed", ->
|
||||
it "is reloaded", ->
|
||||
keymapFilePath = path.join(configDirPath, "keymap.cson")
|
||||
@@ -369,3 +390,41 @@ describe "Keymap", ->
|
||||
runs ->
|
||||
keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0]
|
||||
expect(keyBinding.command).toBe 'core:move-right'
|
||||
keymap.loadUserKeymap.reset()
|
||||
fs.removeSync(keymapFilePath)
|
||||
|
||||
waitsFor ->
|
||||
keymap.loadUserKeymap.callCount > 0
|
||||
|
||||
runs ->
|
||||
keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0]
|
||||
expect(keyBinding).toBeUndefined()
|
||||
|
||||
it "logs a warning when it can't be parsed", ->
|
||||
keymapFilePath = path.join(configDirPath, "keymap.json")
|
||||
fs.writeFileSync(keymapFilePath, '')
|
||||
keymap.loadUserKeymap()
|
||||
|
||||
spyOn(keymap, 'loadUserKeymap').andCallThrough()
|
||||
fs.writeFileSync(keymapFilePath, '}{')
|
||||
spyOn(console, 'warn')
|
||||
|
||||
waitsFor ->
|
||||
keymap.loadUserKeymap.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(console.warn.callCount).toBe 1
|
||||
expect(console.warn.argsForCall[0][0].length).toBeGreaterThan 0
|
||||
|
||||
describe "when adding a binding with an invalid selector", ->
|
||||
it "logs a warning and does not add it", ->
|
||||
spyOn(console, 'warn')
|
||||
keybinding =
|
||||
'##selector':
|
||||
'cmd-a': 'invalid-command'
|
||||
keymap.add('test', keybinding)
|
||||
|
||||
expect(console.warn.callCount).toBe 1
|
||||
expect(console.warn.argsForCall[0][0].length).toBeGreaterThan 0
|
||||
expect(-> keymap.keyBindingsMatchingElement(document.body)).not.toThrow()
|
||||
expect(keymap.keyBindingsForCommand('invalid:command')).toEqual []
|
||||
|
||||
@@ -6,10 +6,12 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "javascript", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
editor = atom.project.openSync('sample.js', autoIndent: false)
|
||||
{buffer, languageMode} = editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
describe ".minIndentLevelForRowRange(startRow, endRow)", ->
|
||||
it "returns the minimum indent level for the given row range", ->
|
||||
expect(languageMode.minIndentLevelForRowRange(4, 7)).toBe 2
|
||||
@@ -44,20 +46,12 @@ describe "LanguageMode", ->
|
||||
languageMode.toggleLineCommentsForBufferRows(0, 0)
|
||||
expect(buffer.lineForRow(0)).toBe " // var i;"
|
||||
|
||||
describe "fold suggestion", ->
|
||||
describe ".doesBufferRowStartFold(bufferRow)", ->
|
||||
it "returns true only when the buffer row starts a foldable region", ->
|
||||
expect(languageMode.doesBufferRowStartFold(0)).toBeTruthy()
|
||||
expect(languageMode.doesBufferRowStartFold(1)).toBeTruthy()
|
||||
expect(languageMode.doesBufferRowStartFold(2)).toBeFalsy()
|
||||
expect(languageMode.doesBufferRowStartFold(3)).toBeFalsy()
|
||||
|
||||
describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
|
||||
it "returns the start/end rows of the foldable region starting at the given row", ->
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 12]
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(1)).toEqual [1, 9]
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7]
|
||||
describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
|
||||
it "returns the start/end rows of the foldable region starting at the given row", ->
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 12]
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(1)).toEqual [1, 9]
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
|
||||
expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7]
|
||||
|
||||
describe "suggestedIndentForBufferRow", ->
|
||||
it "returns the suggested indentation based on auto-indent/outdent rules", ->
|
||||
@@ -106,13 +100,14 @@ describe "LanguageMode", ->
|
||||
range = languageMode.rowRangeForParagraphAtBufferRow(15)
|
||||
expect(range).toEqual [[15,0], [15,26]]
|
||||
|
||||
|
||||
describe "coffeescript", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-coffee-script', sync: true)
|
||||
editor = atom.project.openSync('coffee.coffee', autoIndent: false)
|
||||
{buffer, languageMode} = editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
it "comments/uncomments lines in the given range", ->
|
||||
languageMode.toggleLineCommentsForBufferRows(4, 6)
|
||||
@@ -139,13 +134,13 @@ describe "LanguageMode", ->
|
||||
expect(buffer.lineForRow(7)).toBe " # "
|
||||
|
||||
describe "fold suggestion", ->
|
||||
describe ".doesBufferRowStartFold(bufferRow)", ->
|
||||
describe ".isFoldableAtBufferRow(bufferRow)", ->
|
||||
it "returns true only when the buffer row starts a foldable region", ->
|
||||
expect(languageMode.doesBufferRowStartFold(0)).toBeTruthy()
|
||||
expect(languageMode.doesBufferRowStartFold(1)).toBeTruthy()
|
||||
expect(languageMode.doesBufferRowStartFold(2)).toBeFalsy()
|
||||
expect(languageMode.doesBufferRowStartFold(3)).toBeFalsy()
|
||||
expect(languageMode.doesBufferRowStartFold(19)).toBeTruthy()
|
||||
expect(languageMode.isFoldableAtBufferRow(0)).toBeTruthy()
|
||||
expect(languageMode.isFoldableAtBufferRow(1)).toBeTruthy()
|
||||
expect(languageMode.isFoldableAtBufferRow(2)).toBeFalsy()
|
||||
expect(languageMode.isFoldableAtBufferRow(3)).toBeFalsy()
|
||||
expect(languageMode.isFoldableAtBufferRow(19)).toBeTruthy()
|
||||
|
||||
describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
|
||||
it "returns the start/end rows of the foldable region starting at the given row", ->
|
||||
@@ -156,10 +151,12 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "css", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-css', sync: true)
|
||||
editor = atom.project.openSync('css.css', autoIndent: false)
|
||||
{buffer, languageMode} = editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-css')
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
it "comments/uncomments lines in the given range", ->
|
||||
languageMode.toggleLineCommentsForBufferRows(0, 1)
|
||||
@@ -197,11 +194,15 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "less", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-less', sync: true)
|
||||
atom.packages.activatePackage('language-css', sync: true)
|
||||
editor = atom.project.openSync('sample.less', autoIndent: false)
|
||||
{buffer, languageMode} = editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-less')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-css')
|
||||
|
||||
describe "when commenting lines", ->
|
||||
it "only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart`", ->
|
||||
languageMode.toggleLineCommentsForBufferRows(0, 0)
|
||||
@@ -209,10 +210,12 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "folding", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
editor = atom.project.openSync('sample.js', autoIndent: false)
|
||||
{buffer, languageMode} = editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
it "maintains cursor buffer position when a folding/unfolding", ->
|
||||
editor.setCursorBufferPosition([5,5])
|
||||
languageMode.foldAll()
|
||||
@@ -297,12 +300,22 @@ describe "LanguageMode", ->
|
||||
languageMode.unfoldBufferRow(1)
|
||||
expect(editor.lineForScreenRow(1).fold).toBeUndefined()
|
||||
|
||||
describe ".isFoldableAtBufferRow(bufferRow)", ->
|
||||
it "returns true if the line starts a foldable row range", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(0)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(2)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(3)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(4)).toBe true
|
||||
|
||||
describe "folding with comments", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-javascript', sync: true)
|
||||
editor = atom.project.openSync('sample-with-comments.js', autoIndent: false)
|
||||
{buffer, languageMode} = editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
describe ".unfoldAll()", ->
|
||||
it "unfolds every folded line", ->
|
||||
initialScreenLineCount = editor.getScreenLineCount()
|
||||
@@ -350,12 +363,25 @@ describe "LanguageMode", ->
|
||||
fold2 = editor.lineForScreenRow(5).fold
|
||||
expect(fold2).toBeFalsy()
|
||||
|
||||
describe ".isFoldableAtBufferRow(bufferRow)", ->
|
||||
it "returns true if the line starts a multi-line comment", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(6)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(17)).toBe false
|
||||
|
||||
it "does not return true for a line in the middle of a comment that's followed by an indented line", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
|
||||
editor.buffer.insert([8, 0], ' ')
|
||||
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
|
||||
|
||||
describe "css", ->
|
||||
beforeEach ->
|
||||
atom.packages.activatePackage('language-source', sync: true)
|
||||
atom.packages.activatePackage('language-css', sync: true)
|
||||
editor = atom.project.openSync('css.css', autoIndent: true)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-source')
|
||||
atom.packages.activatePackage('language-css')
|
||||
|
||||
describe "suggestedIndentForBufferRow", ->
|
||||
it "does not return negative values (regression)", ->
|
||||
editor.setText('.test {\npadding: 0;\n}')
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{$} = require 'atom'
|
||||
path = require 'path'
|
||||
Package = require '../src/package'
|
||||
ThemePackage = require '../src/theme-package'
|
||||
|
||||
describe "AtomPackage", ->
|
||||
describe "Package", ->
|
||||
describe "theme", ->
|
||||
theme = null
|
||||
|
||||
@@ -16,14 +16,14 @@ describe "AtomPackage", ->
|
||||
it "loads and applies css", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
|
||||
themePath = atom.project.resolve('packages/theme-with-index-css')
|
||||
theme = Package.load(themePath)
|
||||
theme = new ThemePackage(themePath)
|
||||
theme.activate()
|
||||
expect($(".editor").css("padding-top")).toBe "1234px"
|
||||
|
||||
it "parses, loads and applies less", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
|
||||
themePath = atom.project.resolve('packages/theme-with-index-less')
|
||||
theme = Package.load(themePath)
|
||||
theme = new ThemePackage(themePath)
|
||||
theme.activate()
|
||||
expect($(".editor").css("padding-top")).toBe "4321px"
|
||||
|
||||
@@ -34,7 +34,7 @@ describe "AtomPackage", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe("103px")
|
||||
|
||||
themePath = atom.project.resolve('packages/theme-with-package-file')
|
||||
theme = Package.load(themePath)
|
||||
theme = new ThemePackage(themePath)
|
||||
theme.activate()
|
||||
expect($(".editor").css("padding-top")).toBe("101px")
|
||||
expect($(".editor").css("padding-right")).toBe("102px")
|
||||
@@ -47,7 +47,7 @@ describe "AtomPackage", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "30px"
|
||||
|
||||
themePath = atom.project.resolve('packages/theme-without-package-file')
|
||||
theme = Package.load(themePath)
|
||||
theme = new ThemePackage(themePath)
|
||||
theme.activate()
|
||||
expect($(".editor").css("padding-top")).toBe "10px"
|
||||
expect($(".editor").css("padding-right")).toBe "20px"
|
||||
@@ -56,7 +56,7 @@ describe "AtomPackage", ->
|
||||
describe "reloading a theme", ->
|
||||
beforeEach ->
|
||||
themePath = atom.project.resolve('packages/theme-with-package-file')
|
||||
theme = Package.load(themePath)
|
||||
theme = new ThemePackage(themePath)
|
||||
theme.activate()
|
||||
|
||||
it "reloads without readding to the stylesheets list", ->
|
||||
@@ -67,7 +67,7 @@ describe "AtomPackage", ->
|
||||
describe "events", ->
|
||||
beforeEach ->
|
||||
themePath = atom.project.resolve('packages/theme-with-package-file')
|
||||
theme = Package.load(themePath)
|
||||
theme = new ThemePackage(themePath)
|
||||
theme.activate()
|
||||
|
||||
it "deactivated event fires on .deactivate()", ->
|
||||
@@ -36,8 +36,8 @@ describe "PaneContainer", ->
|
||||
[container, pane1, pane2] = []
|
||||
|
||||
beforeEach ->
|
||||
pane1 = new Pane
|
||||
container = new PaneContainer(root: pane1)
|
||||
container = new PaneContainer
|
||||
pane1 = container.root
|
||||
|
||||
it "references the first pane if no pane has been made active", ->
|
||||
expect(container.activePane).toBe pane1
|
||||
@@ -60,18 +60,8 @@ describe "PaneContainer", ->
|
||||
pane2.destroy()
|
||||
expect(container.activePane).toBe pane1
|
||||
expect(pane1.active).toBe true
|
||||
|
||||
it "does not allow the root pane to be destroyed", ->
|
||||
pane1.destroy()
|
||||
expect(container.activePane).toBe null
|
||||
|
||||
describe "when the last pane is removed", ->
|
||||
[container, pane, surrenderedFocusHandler] = []
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane
|
||||
container = new PaneContainer(root: pane)
|
||||
container.on 'surrendered-focus', surrenderedFocusHandler = jasmine.createSpy("surrenderedFocusHandler")
|
||||
|
||||
it "assigns null to the root and the activePane", ->
|
||||
pane.destroy()
|
||||
expect(container.root).toBe null
|
||||
expect(container.activePane).toBe null
|
||||
expect(container.root).toBe pane1
|
||||
expect(pane1.isDestroyed()).toBe false
|
||||
|
||||
@@ -2,7 +2,7 @@ path = require 'path'
|
||||
temp = require 'temp'
|
||||
PaneContainerView = require '../src/pane-container-view'
|
||||
PaneView = require '../src/pane-view'
|
||||
{_, $, View, $$} = require 'atom'
|
||||
{$, View, $$} = require 'atom'
|
||||
|
||||
describe "PaneContainerView", ->
|
||||
[TestView, container, pane1, pane2, pane3] = []
|
||||
@@ -19,40 +19,14 @@ describe "PaneContainerView", ->
|
||||
isEqual: (other) -> @name is other?.name
|
||||
|
||||
container = new PaneContainerView
|
||||
pane1 = new PaneView(new TestView('1'))
|
||||
container.setRoot(pane1)
|
||||
pane1 = container.getRoot()
|
||||
pane1.activateItem(new TestView('1'))
|
||||
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"
|
||||
@@ -71,10 +45,10 @@ describe "PaneContainerView", ->
|
||||
expect(container.getFocusedPane()).toBe pane3
|
||||
expect(container.getActivePane()).toBe pane3
|
||||
|
||||
describe ".eachPane(callback)", ->
|
||||
describe ".eachPaneView(callback)", ->
|
||||
it "runs the callback with all current and future panes until the subscription is cancelled", ->
|
||||
panes = []
|
||||
subscription = container.eachPane (pane) -> panes.push(pane)
|
||||
subscription = container.eachPaneView (pane) -> panes.push(pane)
|
||||
expect(panes).toEqual [pane1, pane2, pane3]
|
||||
|
||||
panes = []
|
||||
@@ -92,7 +66,7 @@ describe "PaneContainerView", ->
|
||||
|
||||
container.saveAll()
|
||||
|
||||
for pane in container.getPanes()
|
||||
for pane in container.getPaneViews()
|
||||
for item in pane.getItems()
|
||||
expect(item.saved).toBeTruthy()
|
||||
|
||||
@@ -121,7 +95,7 @@ describe "PaneContainerView", ->
|
||||
|
||||
describe "serialization", ->
|
||||
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
|
||||
newContainer = atom.deserializers.deserialize(container.serialize())
|
||||
newContainer = new PaneContainerView(container.model.testSerialization())
|
||||
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()
|
||||
@@ -130,12 +104,23 @@ describe "PaneContainerView", ->
|
||||
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 "if there are empty panes after deserialization", ->
|
||||
beforeEach ->
|
||||
# only deserialize pane 1's view successfully
|
||||
TestView.deserialize = ({name}) -> new TestView(name) if name is '1'
|
||||
|
||||
describe "if the 'core.destroyEmptyPanes' config option is false (the default)", ->
|
||||
it "leaves the empty panes intact", ->
|
||||
newContainer = new PaneContainerView(container.model.testSerialization())
|
||||
expect(newContainer.find('.pane-row > :contains(1)')).toExist()
|
||||
expect(newContainer.find('.pane-row > .pane-column > .pane').length).toBe 2
|
||||
|
||||
describe "if the 'core.destroyEmptyPanes' config option is true", ->
|
||||
it "removes empty panes on deserialization", ->
|
||||
atom.config.set('core.destroyEmptyPanes', true)
|
||||
newContainer = new PaneContainerView(container.model.testSerialization())
|
||||
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] = []
|
||||
@@ -147,24 +132,13 @@ describe "PaneContainerView", ->
|
||||
item3a = new TestView('3a')
|
||||
|
||||
container = new PaneContainerView
|
||||
pane1 = container.getRoot()
|
||||
pane1.activateItem(item1a)
|
||||
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)
|
||||
@@ -203,11 +177,6 @@ describe "PaneContainerView", ->
|
||||
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] = []
|
||||
|
||||
@@ -270,3 +239,115 @@ describe "PaneContainerView", ->
|
||||
pane1.remove()
|
||||
pane2.remove()
|
||||
expect(activeItemChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe ".focusNextPaneView()", ->
|
||||
it "focuses the pane following the focused pane or the first pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
container.focusNextPaneView()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPaneView()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPaneView()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPaneView()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".focusPreviousPaneView()", ->
|
||||
it "focuses the pane preceding the focused pane or the last pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
container.getPaneViews()[0].focus() # activate first pane
|
||||
|
||||
container.focusPreviousPaneView()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPaneView()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPaneView()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusPreviousPaneView()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe "changing focus directionally between panes", ->
|
||||
[pane1, pane2, pane3, pane4, pane5, pane6, pane7, pane8, pane9] = []
|
||||
|
||||
beforeEach ->
|
||||
# Set up a grid of 9 panes, in the following arrangement, where the
|
||||
# numbers correspond to the variable names below.
|
||||
#
|
||||
# -------
|
||||
# |1|2|3|
|
||||
# -------
|
||||
# |4|5|6|
|
||||
# -------
|
||||
# |7|8|9|
|
||||
# -------
|
||||
|
||||
container = new PaneContainerView
|
||||
pane1 = container.getRoot()
|
||||
pane1.activateItem(new TestView('1'))
|
||||
pane4 = pane1.splitDown(new TestView('4'))
|
||||
pane7 = pane4.splitDown(new TestView('7'))
|
||||
|
||||
pane2 = pane1.splitRight(new TestView('2'))
|
||||
pane3 = pane2.splitRight(new TestView('3'))
|
||||
|
||||
pane5 = pane4.splitRight(new TestView('5'))
|
||||
pane6 = pane5.splitRight(new TestView('6'))
|
||||
|
||||
pane8 = pane7.splitRight(new TestView('8'))
|
||||
pane9 = pane8.splitRight(new TestView('9'))
|
||||
|
||||
container.height(400)
|
||||
container.width(400)
|
||||
container.attachToDom()
|
||||
|
||||
describe ".focusPaneViewAbove()", ->
|
||||
describe "when there are multiple rows above the focused pane", ->
|
||||
it "focuses up to the adjacent row", ->
|
||||
pane8.focus()
|
||||
container.focusPaneViewAbove()
|
||||
expect(pane5.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe "when there are no rows above the focused pane", ->
|
||||
it "keeps the current pane focused", ->
|
||||
pane2.focus()
|
||||
container.focusPaneViewAbove()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".focusPaneViewBelow()", ->
|
||||
describe "when there are multiple rows below the focused pane", ->
|
||||
it "focuses down to the adjacent row", ->
|
||||
pane2.focus()
|
||||
container.focusPaneViewBelow()
|
||||
expect(pane5.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe "when there are no rows below the focused pane", ->
|
||||
it "keeps the current pane focused", ->
|
||||
pane8.focus()
|
||||
container.focusPaneViewBelow()
|
||||
expect(pane8.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".focusPaneViewOnLeft()", ->
|
||||
describe "when there are multiple columns to the left of the focused pane", ->
|
||||
it "focuses left to the adjacent column", ->
|
||||
pane6.focus()
|
||||
container.focusPaneViewOnLeft()
|
||||
expect(pane5.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe "when there are no columns to the left of the focused pane", ->
|
||||
it "keeps the current pane focused", ->
|
||||
pane4.focus()
|
||||
container.focusPaneViewOnLeft()
|
||||
expect(pane4.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".focusPaneViewOnRight()", ->
|
||||
describe "when there are multiple columns to the right of the focused pane", ->
|
||||
it "focuses right to the adjacent column", ->
|
||||
pane4.focus()
|
||||
container.focusPaneViewOnRight()
|
||||
expect(pane5.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe "when there are no columns to the right of the focused pane", ->
|
||||
it "keeps the current pane focused", ->
|
||||
pane6.focus()
|
||||
container.focusPaneViewOnRight()
|
||||
expect(pane6.activeItem).toMatchSelector ':focus'
|
||||
|
||||
+400
-20
@@ -4,6 +4,366 @@ PaneAxis = require '../src/pane-axis'
|
||||
PaneContainer = require '../src/pane-container'
|
||||
|
||||
describe "Pane", ->
|
||||
class Item extends Model
|
||||
@deserialize: ({name, uri}) -> new this(name, uri)
|
||||
constructor: (@name, @uri) ->
|
||||
getUri: -> @uri
|
||||
getPath: -> @path
|
||||
serialize: -> {deserializer: 'Item', @name, @uri}
|
||||
isEqual: (other) -> @name is other?.name
|
||||
|
||||
beforeEach ->
|
||||
atom.deserializers.add(Item)
|
||||
|
||||
afterEach ->
|
||||
atom.deserializers.remove(Item)
|
||||
|
||||
describe "construction", ->
|
||||
it "sets the active item to the first item", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B")])
|
||||
expect(pane.activeItem).toBe pane.items[0]
|
||||
|
||||
it "compacts the items array", ->
|
||||
pane = new Pane(items: [undefined, new Item("A"), null, new Item("B")])
|
||||
expect(pane.items.length).toBe 2
|
||||
expect(pane.activeItem).toBe pane.items[0]
|
||||
|
||||
describe "::addItem(item, index)", ->
|
||||
it "adds the item at the given index", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B")])
|
||||
[item1, item2] = pane.items
|
||||
item3 = new Item("C")
|
||||
pane.addItem(item3, 1)
|
||||
expect(pane.items).toEqual [item1, item3, item2]
|
||||
|
||||
it "adds the item after the active item ", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
pane.activateItem(item2)
|
||||
item4 = new Item("D")
|
||||
pane.addItem(item4)
|
||||
expect(pane.items).toEqual [item1, item2, item4, item3]
|
||||
|
||||
it "sets the active item after adding the first item", ->
|
||||
pane = new Pane
|
||||
item = new Item("A")
|
||||
events = []
|
||||
pane.on 'item-added', -> events.push('item-added')
|
||||
pane.$activeItem.changes.onValue -> events.push('active-item-changed')
|
||||
|
||||
pane.addItem(item)
|
||||
expect(pane.activeItem).toBe item
|
||||
expect(events).toEqual ['item-added', 'active-item-changed']
|
||||
|
||||
describe "::activateItem(item)", ->
|
||||
pane = null
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B")])
|
||||
|
||||
it "changes the active item to the current item", ->
|
||||
expect(pane.activeItem).toBe pane.items[0]
|
||||
pane.activateItem(pane.items[1])
|
||||
expect(pane.activeItem).toBe pane.items[1]
|
||||
|
||||
it "adds the given item if it isn't present in ::items", ->
|
||||
item = new Item("C")
|
||||
pane.activateItem(item)
|
||||
expect(item in pane.items).toBe true
|
||||
expect(pane.activeItem).toBe item
|
||||
|
||||
describe "::activateNextItem() and ::activatePreviousItem()", ->
|
||||
it "sets the active item to the next/previous item, looping around at either end", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
|
||||
expect(pane.activeItem).toBe item1
|
||||
pane.activatePreviousItem()
|
||||
expect(pane.activeItem).toBe item3
|
||||
pane.activatePreviousItem()
|
||||
expect(pane.activeItem).toBe item2
|
||||
pane.activateNextItem()
|
||||
expect(pane.activeItem).toBe item3
|
||||
pane.activateNextItem()
|
||||
expect(pane.activeItem).toBe item1
|
||||
|
||||
describe "::activateItemAtIndex(index)", ->
|
||||
it "activates the item at the given index", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
pane.activateItemAtIndex(2)
|
||||
expect(pane.activeItem).toBe item3
|
||||
pane.activateItemAtIndex(1)
|
||||
expect(pane.activeItem).toBe item2
|
||||
pane.activateItemAtIndex(0)
|
||||
expect(pane.activeItem).toBe item1
|
||||
|
||||
# Doesn't fail with out-of-bounds indices
|
||||
pane.activateItemAtIndex(100)
|
||||
expect(pane.activeItem).toBe item1
|
||||
pane.activateItemAtIndex(-1)
|
||||
expect(pane.activeItem).toBe item1
|
||||
|
||||
describe "::destroyItem(item)", ->
|
||||
[pane, item1, item2, item3] = []
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
|
||||
it "removes the item from the items list", ->
|
||||
expect(pane.activeItem).toBe item1
|
||||
pane.destroyItem(item2)
|
||||
expect(item2 in pane.items).toBe false
|
||||
expect(pane.activeItem).toBe item1
|
||||
|
||||
pane.destroyItem(item1)
|
||||
expect(item1 in pane.items).toBe false
|
||||
|
||||
describe "when the destroyed item is the active item and is the first item", ->
|
||||
it "activates the next item", ->
|
||||
expect(pane.activeItem).toBe item1
|
||||
pane.destroyItem(item1)
|
||||
expect(pane.activeItem).toBe item2
|
||||
|
||||
describe "when the destroyed item is the active item and is not the first item", ->
|
||||
beforeEach ->
|
||||
pane.activateItem(item2)
|
||||
|
||||
it "activates the previous item", ->
|
||||
expect(pane.activeItem).toBe item2
|
||||
pane.destroyItem(item2)
|
||||
expect(pane.activeItem).toBe item1
|
||||
|
||||
it "emits 'item-removed' with the item, its index, and true indicating the item is being destroyed", ->
|
||||
pane.on 'item-removed', itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
|
||||
pane.destroyItem(item2)
|
||||
expect(itemRemovedHandler).toHaveBeenCalledWith(item2, 1, true)
|
||||
|
||||
describe "if the item is modified", ->
|
||||
itemUri = null
|
||||
|
||||
beforeEach ->
|
||||
item1.shouldPromptToSave = -> true
|
||||
item1.save = jasmine.createSpy("save")
|
||||
item1.saveAs = jasmine.createSpy("saveAs")
|
||||
item1.getUri = -> itemUri
|
||||
|
||||
describe "if the [Save] option is selected", ->
|
||||
describe "when the item has a uri", ->
|
||||
it "saves the item before destroying it", ->
|
||||
itemUri = "test"
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
pane.destroyItem(item1)
|
||||
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(item1 in pane.items).toBe false
|
||||
expect(item1.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", ->
|
||||
itemUri = null
|
||||
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn("/selected/path")
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
pane.destroyItem(item1)
|
||||
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
||||
expect(item1.saveAs).toHaveBeenCalledWith("/selected/path")
|
||||
expect(item1 in pane.items).toBe false
|
||||
expect(item1.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(item1)
|
||||
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
expect(item1 in pane.items).toBe false
|
||||
expect(item1.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(item1)
|
||||
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
expect(item1 in pane.items).toBe true
|
||||
expect(item1.isDestroyed()).toBe false
|
||||
|
||||
describe "when the last item is destroyed", ->
|
||||
describe "when the 'core.destroyEmptyPanes' config option is false (the default)", ->
|
||||
it "does not destroy the pane, but leaves it in place with empty items", ->
|
||||
expect(atom.config.get('core.destroyEmptyPanes')).toBe false
|
||||
pane.destroyItem(item) for item in pane.getItems()
|
||||
expect(pane.isDestroyed()).toBe false
|
||||
expect(pane.activeItem).toBeUndefined()
|
||||
expect(-> pane.saveActiveItem()).not.toThrow()
|
||||
expect(-> pane.saveActiveItemAs()).not.toThrow()
|
||||
|
||||
describe "when the 'core.destroyEmptyPanes' config option is true", ->
|
||||
it "destroys the pane", ->
|
||||
atom.config.set('core.destroyEmptyPanes', true)
|
||||
pane.destroyItem(item) for item in pane.getItems()
|
||||
expect(pane.isDestroyed()).toBe true
|
||||
|
||||
describe "::destroyActiveItem()", ->
|
||||
it "destroys the active item", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B")])
|
||||
activeItem = pane.activeItem
|
||||
pane.destroyActiveItem()
|
||||
expect(activeItem.isDestroyed()).toBe true
|
||||
expect(activeItem in pane.items).toBe false
|
||||
|
||||
it "does not throw an exception if there are no more items", ->
|
||||
pane = new Pane
|
||||
pane.destroyActiveItem()
|
||||
|
||||
describe "::destroyItems()", ->
|
||||
it "destroys all items", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
pane.destroyItems()
|
||||
expect(item1.isDestroyed()).toBe true
|
||||
expect(item2.isDestroyed()).toBe true
|
||||
expect(item3.isDestroyed()).toBe true
|
||||
expect(pane.items).toEqual []
|
||||
|
||||
describe "when an item emits a destroyed event", ->
|
||||
it "removes it from the list of items", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
pane.items[1].destroy()
|
||||
expect(pane.items).toEqual [item1, item3]
|
||||
|
||||
describe "::destroyInactiveItems()", ->
|
||||
it "destroys all items but the active item", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
[item1, item2, item3] = pane.items
|
||||
pane.activateItem(item2)
|
||||
pane.destroyInactiveItems()
|
||||
expect(pane.items).toEqual [item2]
|
||||
|
||||
describe "::saveActiveItem()", ->
|
||||
pane = null
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane(items: [new Item("A")])
|
||||
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
|
||||
|
||||
describe "when the active item has a uri", ->
|
||||
beforeEach ->
|
||||
pane.activeItem.uri = "test"
|
||||
|
||||
describe "when the active item has a save method", ->
|
||||
it "saves the current item", ->
|
||||
pane.activeItem.save = jasmine.createSpy("save")
|
||||
pane.saveActiveItem()
|
||||
expect(pane.activeItem.save).toHaveBeenCalled()
|
||||
|
||||
describe "when the current item has no save method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.save).toBeUndefined()
|
||||
pane.saveActiveItem()
|
||||
|
||||
describe "when the current item has no uri", ->
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens a save dialog and saves the current item as the selected path", ->
|
||||
pane.activeItem.saveAs = jasmine.createSpy("saveAs")
|
||||
pane.saveActiveItem()
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
||||
expect(pane.activeItem.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()", ->
|
||||
pane = null
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane(items: [new Item("A")])
|
||||
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", ->
|
||||
pane.activeItem.path = __filename
|
||||
pane.activeItem.saveAs = jasmine.createSpy("saveAs")
|
||||
pane.saveActiveItemAs()
|
||||
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(__dirname)
|
||||
expect(pane.activeItem.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 "::itemForUri(uri)", ->
|
||||
it "returns the item for which a call to .getUri() returns the given uri", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")])
|
||||
[item1, item2, item3] = pane.items
|
||||
item1.uri = "a"
|
||||
item2.uri = "b"
|
||||
expect(pane.itemForUri("a")).toBe item1
|
||||
expect(pane.itemForUri("b")).toBe item2
|
||||
expect(pane.itemForUri("bogus")).toBeUndefined()
|
||||
|
||||
describe "::moveItem(item, index)", ->
|
||||
it "moves the item to the given index and emits an 'item-moved' event with the item and its new index", ->
|
||||
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")])
|
||||
[item1, item2, item3, item4] = pane.items
|
||||
pane.on 'item-moved', itemMovedHandler = jasmine.createSpy("itemMovedHandler")
|
||||
|
||||
pane.moveItem(item1, 2)
|
||||
expect(pane.getItems()).toEqual [item2, item3, item1, item4]
|
||||
expect(itemMovedHandler).toHaveBeenCalledWith(item1, 2)
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(item2, 3)
|
||||
expect(pane.getItems()).toEqual [item3, item1, item4, item2]
|
||||
expect(itemMovedHandler).toHaveBeenCalledWith(item2, 3)
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(item2, 1)
|
||||
expect(pane.getItems()).toEqual [item3, item2, item1, item4]
|
||||
expect(itemMovedHandler).toHaveBeenCalledWith(item2, 1)
|
||||
|
||||
describe "::moveItemToPane(item, pane, index)", ->
|
||||
[container, pane1, pane2] = []
|
||||
[item1, item2, item3, item4, item5] = []
|
||||
|
||||
beforeEach ->
|
||||
pane1 = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
|
||||
container = new PaneContainer(root: pane1)
|
||||
pane2 = pane1.splitRight(items: [new Item("D"), new Item("E")])
|
||||
[item1, item2, item3] = pane1.items
|
||||
[item4, item5] = pane2.items
|
||||
|
||||
it "moves the item to the given pane at the given index", ->
|
||||
pane1.moveItemToPane(item2, pane2, 1)
|
||||
expect(pane1.items).toEqual [item1, item3]
|
||||
expect(pane2.items).toEqual [item4, item2, item5]
|
||||
|
||||
describe "when the moved item the last item in the source pane", ->
|
||||
beforeEach ->
|
||||
item5.destroy()
|
||||
|
||||
describe "when the 'core.destroyEmptyPanes' config option is false (the default)", ->
|
||||
it "does not destroy the pane or the item", ->
|
||||
pane2.moveItemToPane(item4, pane1, 0)
|
||||
expect(pane2.isDestroyed()).toBe false
|
||||
expect(item4.isDestroyed()).toBe false
|
||||
|
||||
describe "when the 'core.destroyEmptyPanes' config option is true", ->
|
||||
it "destroys the pane, but not the item", ->
|
||||
atom.config.set('core.destroyEmptyPanes', true)
|
||||
pane2.moveItemToPane(item4, pane1, 0)
|
||||
expect(pane2.isDestroyed()).toBe true
|
||||
expect(item4.isDestroyed()).toBe false
|
||||
|
||||
describe "split methods", ->
|
||||
[pane1, container] = []
|
||||
|
||||
@@ -84,27 +444,14 @@ describe "Pane", ->
|
||||
pane2 = pane1.splitRight()
|
||||
expect(pane2.focused).toBe true
|
||||
|
||||
describe "::destroyItem(item)", ->
|
||||
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 "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]
|
||||
|
||||
describe "::destroy()", ->
|
||||
[pane1, container] = []
|
||||
[container, pane1, pane2] = []
|
||||
|
||||
beforeEach ->
|
||||
pane1 = new Pane(items: [new Model, new Model])
|
||||
container = new PaneContainer(root: pane1)
|
||||
container = new PaneContainer
|
||||
pane1 = container.root
|
||||
pane1.addItems([new Item("A"), new Item("B")])
|
||||
pane2 = pane1.splitRight()
|
||||
|
||||
it "destroys the pane's destroyable items", ->
|
||||
[item1, item2] = pane1.items
|
||||
@@ -112,9 +459,14 @@ describe "Pane", ->
|
||||
expect(item1.isDestroyed()).toBe true
|
||||
expect(item2.isDestroyed()).toBe true
|
||||
|
||||
describe "if the pane is active", ->
|
||||
it "makes the next pane active", ->
|
||||
expect(pane2.isActive()).toBe true
|
||||
pane2.destroy()
|
||||
expect(pane1.isActive()).to
|
||||
|
||||
describe "if the pane's parent has more than two children", ->
|
||||
it "removes the pane from its parent", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitRight()
|
||||
|
||||
expect(container.root.children).toEqual [pane1, pane2, pane3]
|
||||
@@ -123,7 +475,6 @@ describe "Pane", ->
|
||||
|
||||
describe "if the pane's parent has two children", ->
|
||||
it "replaces the parent with its last remaining child", ->
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitDown()
|
||||
|
||||
expect(container.root.children[0]).toBe pane1
|
||||
@@ -132,3 +483,32 @@ describe "Pane", ->
|
||||
expect(container.root.children).toEqual [pane1, pane2]
|
||||
pane2.destroy()
|
||||
expect(container.root).toBe pane1
|
||||
|
||||
describe "serialization", ->
|
||||
pane = null
|
||||
|
||||
beforeEach ->
|
||||
pane = new Pane(items: [new Item("A", "a"), new Item("B", "b"), new Item("C", "c")])
|
||||
|
||||
it "can serialize and deserialize the pane and all its items", ->
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.items).toEqual pane.items
|
||||
|
||||
it "restores the active item on deserialization", ->
|
||||
pane.activateItemAtIndex(1)
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.activeItem).toEqual newPane.items[1]
|
||||
|
||||
it "does not include items that cannot be deserialized", ->
|
||||
spyOn(console, 'warn')
|
||||
unserializable = {}
|
||||
pane.activateItem(unserializable)
|
||||
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.activeItem).toEqual pane.items[0]
|
||||
expect(newPane.items.length).toBe pane.items.length - 1
|
||||
|
||||
it "includes the pane's focus state in the serialized state", ->
|
||||
pane.focus()
|
||||
newPane = pane.testSerialization()
|
||||
expect(newPane.focused).toBe true
|
||||
|
||||
+131
-492
@@ -1,11 +1,12 @@
|
||||
PaneContainerView = require '../src/pane-container-view'
|
||||
PaneView = require '../src/pane-view'
|
||||
{fs, $, View} = require 'atom'
|
||||
fs = require 'fs-plus'
|
||||
{$, View} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
|
||||
describe "PaneView", ->
|
||||
[container, view1, view2, editor1, editor2, pane] = []
|
||||
[container, view1, view2, editor1, editor2, pane, paneModel] = []
|
||||
|
||||
class TestView extends View
|
||||
@deserialize: ({id, text}) -> new TestView({id, text})
|
||||
@@ -22,378 +23,121 @@ describe "PaneView", ->
|
||||
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)
|
||||
pane = container.getRoot()
|
||||
paneModel = pane.model
|
||||
paneModel.addItems([view1, editor1, view2, editor2])
|
||||
|
||||
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", ->
|
||||
describe "when the active pane item changes", ->
|
||||
it "hides all item views except the active one", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
expect(view1.css('display')).not.toBe 'none'
|
||||
|
||||
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()
|
||||
it "triggers 'pane:active-item-changed'", ->
|
||||
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
|
||||
container.on 'pane:active-item-changed', itemChangedHandler
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.activateItem(view2)
|
||||
pane.activateItem(view2)
|
||||
paneModel.activateItem(view2)
|
||||
paneModel.activateItem(view2)
|
||||
|
||||
expect(itemChangedHandler.callCount).toBe 1
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe view2
|
||||
itemChangedHandler.reset()
|
||||
|
||||
pane.activateItem(editor1)
|
||||
paneModel.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'
|
||||
it "transfers focus to the new active view if the previous view was focused", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
expect(pane.activeView).not.toBe view2
|
||||
expect(pane.activeView).toMatchSelector ':focus'
|
||||
paneModel.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
|
||||
describe "when the new activeItem is a model", ->
|
||||
it "shows the item's view or creates and shows a new view for the item if none exists", ->
|
||||
initialViewCount = pane.itemViews.find('.test-view').length
|
||||
|
||||
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
|
||||
model1 =
|
||||
id: 'test-model-1'
|
||||
text: 'Test Model 1'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
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]]
|
||||
model2 =
|
||||
id: 'test-model-2'
|
||||
text: 'Test Model 2'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
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
|
||||
paneModel.activateItem(model1)
|
||||
paneModel.activateItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
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
|
||||
paneModel.activatePreviousItem()
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
it "creates a new view with the item", ->
|
||||
initialViewCount = pane.itemViews.find('.test-view').length
|
||||
paneModel.destroyItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
|
||||
|
||||
model1 =
|
||||
id: 'test-model-1'
|
||||
text: 'Test Model 1'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
paneModel.destroyItem(model1)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
|
||||
|
||||
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", ->
|
||||
describe "when the new activeItem is a view", ->
|
||||
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)
|
||||
paneModel.activateItem(view2)
|
||||
expect(pane.itemViews.find('#view-2')).toExist()
|
||||
expect(pane.activeView).toBe view2
|
||||
paneModel.activateItem(view1)
|
||||
paneModel.activateItem(view2)
|
||||
expect(pane.itemViews.find('#view-2').length).toBe 1
|
||||
|
||||
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", ->
|
||||
describe "when an item is destroyed", ->
|
||||
it "triggers the 'pane:item-removed' event with the item and its former index", ->
|
||||
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
|
||||
pane.on 'pane:item-removed', itemRemovedHandler
|
||||
pane.destroyItem(editor1)
|
||||
paneModel.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", ->
|
||||
describe "when the destroyed item is a view", ->
|
||||
it "removes the item from the 'item-views' div", ->
|
||||
expect(view1.parent()).toMatchSelector pane.itemViews
|
||||
pane.destroyItem(view1)
|
||||
paneModel.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()
|
||||
describe "when the destroyed item is a model", ->
|
||||
it "removes the associated view", ->
|
||||
paneModel.activateItem(editor1)
|
||||
expect(pane.itemViews.find('.editor').length).toBe 1
|
||||
pane.destroyItem(editor1)
|
||||
expect(pane.itemViews.find('.editor')).not.toExist()
|
||||
expect(pane.itemViews.find('.editor').length).toBe 0
|
||||
|
||||
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]
|
||||
describe "when an item is moved within the same pane", ->
|
||||
it "emits a 'pane:item-moved' event with the item and the new index", ->
|
||||
pane.on 'pane:item-moved', itemMovedHandler = jasmine.createSpy("itemMovedHandler")
|
||||
paneModel.moveItem(view1, 2)
|
||||
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 an item is moved to another pane", ->
|
||||
it "detaches the item's view rather than removing it", ->
|
||||
paneModel2 = paneModel.splitRight()
|
||||
view1.data('preservative', 1234)
|
||||
paneModel.moveItemToPane(view1, paneModel2, 1)
|
||||
expect(view1.data('preservative')).toBe 1234
|
||||
paneModel2.activateItemAtIndex(1)
|
||||
expect(view1.data('preservative')).toBe 1234
|
||||
|
||||
describe "when the title of the active item changes", ->
|
||||
it "emits pane:active-item-title-changed", ->
|
||||
@@ -421,67 +165,32 @@ describe "PaneView", ->
|
||||
expect(pane.items).toHaveLength(5)
|
||||
|
||||
fs.removeSync(filePath)
|
||||
waitsFor ->
|
||||
waitsFor 30000, ->
|
||||
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
|
||||
describe "when a pane is destroyed", ->
|
||||
[pane2, pane2Model] = []
|
||||
|
||||
beforeEach ->
|
||||
pane2Model = paneModel.splitRight() # Can't destroy the last pane, so we add another
|
||||
pane2 = pane2Model._view
|
||||
|
||||
it "triggers a 'pane:removed' event with the pane", ->
|
||||
removedHandler = jasmine.createSpy("removedHandler")
|
||||
container.on 'pane:removed', removedHandler
|
||||
pane.remove()
|
||||
paneModel.destroy()
|
||||
expect(removedHandler).toHaveBeenCalled()
|
||||
expect(removedHandler.argsForCall[0][1]).toBe pane
|
||||
|
||||
describe "when there are other panes", ->
|
||||
describe "if the destroyed pane has focus", ->
|
||||
[paneToLeft, paneToRight] = []
|
||||
|
||||
beforeEach ->
|
||||
pane.activateItem(editor1)
|
||||
paneToLeft = pane.splitLeft(pane.copyActiveItem())
|
||||
paneToRight = pane.splitRight(pane.copyActiveItem())
|
||||
it "focuses the next pane", ->
|
||||
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()
|
||||
expect(pane.hasFocus()).toBe false
|
||||
expect(pane2.hasFocus()).toBe true
|
||||
pane2Model.destroy()
|
||||
expect(pane.hasFocus()).toBe true
|
||||
|
||||
describe "::getNextPane()", ->
|
||||
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
|
||||
@@ -491,148 +200,78 @@ describe "PaneView", ->
|
||||
expect(pane.getNextPane()).toBe pane2
|
||||
expect(pane2.getNextPane()).toBe pane
|
||||
|
||||
describe "when the pane's active status changes", ->
|
||||
[pane2, pane2Model] = []
|
||||
|
||||
beforeEach ->
|
||||
pane2Model = paneModel.splitRight(items: [pane.copyActiveItem()])
|
||||
pane2 = pane2Model._view
|
||||
expect(pane2Model.isActive()).toBe true
|
||||
|
||||
it "adds or removes the .active class as appropriate", ->
|
||||
expect(pane).not.toHaveClass('active')
|
||||
paneModel.activate()
|
||||
expect(pane).toHaveClass('active')
|
||||
pane2Model.activate()
|
||||
expect(pane).not.toHaveClass('active')
|
||||
|
||||
it "triggers 'pane:became-active' or 'pane:became-inactive' according to the current status", ->
|
||||
pane.on 'pane:became-active', becameActiveHandler = jasmine.createSpy("becameActiveHandler")
|
||||
pane.on 'pane:became-inactive', becameInactiveHandler = jasmine.createSpy("becameInactiveHandler")
|
||||
paneModel.activate()
|
||||
|
||||
expect(becameActiveHandler.callCount).toBe 1
|
||||
expect(becameInactiveHandler.callCount).toBe 0
|
||||
|
||||
pane2Model.activate()
|
||||
expect(becameActiveHandler.callCount).toBe 1
|
||||
expect(becameInactiveHandler.callCount).toBe 1
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
beforeEach ->
|
||||
container.attachToDom()
|
||||
|
||||
it "focuses the active item view", ->
|
||||
it "transfers focus to the active 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
|
||||
it "makes the pane active", ->
|
||||
paneModel.splitRight(items: [pane.copyActiveItem()])
|
||||
expect(paneModel.isActive()).toBe false
|
||||
pane.focus()
|
||||
expect(paneModel.isActive()).toBe true
|
||||
|
||||
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 ->
|
||||
describe "when a pane is split", ->
|
||||
it "builds the appropriate pane-row and pane-column views", ->
|
||||
pane1 = pane
|
||||
pane1Model = pane.model
|
||||
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
|
||||
pane2Model = pane1Model.splitRight(items: [pane1Model.copyActiveItem()])
|
||||
pane3Model = pane2Model.splitDown(items: [pane2Model.copyActiveItem()])
|
||||
pane2 = pane2Model._view
|
||||
pane3 = pane3Model._view
|
||||
|
||||
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]]
|
||||
expect(container.find('> .pane-row > .pane').toArray()).toEqual [pane1[0]]
|
||||
expect(container.find('> .pane-row > .pane-column > .pane').toArray()).toEqual [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
|
||||
pane1Model.destroy()
|
||||
expect(container.find('> .pane-column > .pane').toArray()).toEqual [pane2[0], pane3[0]]
|
||||
|
||||
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()
|
||||
container2 = new PaneContainerView(container.model.testSerialization())
|
||||
pane2 = container2.getRoot()
|
||||
container2.attachToDom()
|
||||
expect(pane2).toMatchSelector(':has(:focus)')
|
||||
|
||||
$(document.activeElement).blur()
|
||||
container3 = container.testSerialization()
|
||||
container3 = new PaneContainerView(container.model.testSerialization())
|
||||
pane3 = container3.getRoot()
|
||||
container3.attachToDom()
|
||||
expect(pane3).not.toMatchSelector(':has(:focus)')
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
describe "Pasteboard", ->
|
||||
describe "write(text, metadata) and read()", ->
|
||||
it "writes and reads text to/from the native pasteboard", ->
|
||||
expect(atom.pasteboard.read()).toEqual ['initial pasteboard content']
|
||||
atom.pasteboard.write('next')
|
||||
expect(atom.pasteboard.read()[0]).toBe 'next'
|
||||
|
||||
it "returns metadata if the item on the native pasteboard matches the last written item", ->
|
||||
atom.pasteboard.write('next', {meta: 'data'})
|
||||
expect(atom.pasteboard.read()).toEqual ['next', {meta: 'data'}]
|
||||
+69
-105
@@ -1,7 +1,8 @@
|
||||
temp = require 'temp'
|
||||
fstream = require 'fstream'
|
||||
Project = require '../src/project'
|
||||
{_, fs} = require 'atom'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
platform = require './spec-helper-platform'
|
||||
BufferedProcess = require '../src/buffered-process'
|
||||
@@ -72,7 +73,7 @@ describe "Project", ->
|
||||
expect(atom.project.getEditors()[1]).toBe editor2
|
||||
|
||||
describe ".openSync(path)", ->
|
||||
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditorHandler] = []
|
||||
[absolutePath, newBufferHandler, newEditorHandler] = []
|
||||
beforeEach ->
|
||||
absolutePath = require.resolve('./fixtures/dir/a')
|
||||
newBufferHandler = jasmine.createSpy('newBufferHandler')
|
||||
@@ -80,129 +81,92 @@ describe "Project", ->
|
||||
newEditorHandler = jasmine.createSpy('newEditorHandler')
|
||||
atom.project.on 'editor-created', newEditorHandler
|
||||
|
||||
fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/)
|
||||
barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//)
|
||||
atom.project.registerOpener(fooOpener)
|
||||
atom.project.registerOpener(barOpener)
|
||||
describe "when given an absolute path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = atom.project.openSync(absolutePath)
|
||||
expect(editor.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
afterEach ->
|
||||
atom.project.unregisterOpener(fooOpener)
|
||||
atom.project.unregisterOpener(barOpener)
|
||||
describe "when given a relative path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = atom.project.openSync('a')
|
||||
expect(editor.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when passed a path that doesn't match a custom opener", ->
|
||||
describe "when given an absolute path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = atom.project.openSync(absolutePath)
|
||||
describe "when passed the path to a buffer that has already been opened", ->
|
||||
it "returns a new edit session containing previously opened buffer and emits a 'editor-created' event", ->
|
||||
editor = atom.project.openSync(absolutePath)
|
||||
newBufferHandler.reset()
|
||||
expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer
|
||||
expect(atom.project.openSync('a').buffer).toBe editor.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = atom.project.openSync()
|
||||
expect(editor.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe ".open(path)", ->
|
||||
[absolutePath, newBufferHandler, newEditorHandler] = []
|
||||
|
||||
beforeEach ->
|
||||
absolutePath = require.resolve('./fixtures/dir/a')
|
||||
newBufferHandler = jasmine.createSpy('newBufferHandler')
|
||||
atom.project.on 'buffer-created', newBufferHandler
|
||||
newEditorHandler = jasmine.createSpy('newEditorHandler')
|
||||
atom.project.on 'editor-created', newEditorHandler
|
||||
|
||||
describe "when given an absolute path that isn't currently open", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open(absolutePath).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when given a relative path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = atom.project.openSync('a')
|
||||
describe "when given a relative path that isn't currently opened", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open(absolutePath).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when passed the path to a buffer that has already been opened", ->
|
||||
it "returns a new edit session containing previously opened buffer and emits a 'editor-created' event", ->
|
||||
editor = atom.project.openSync(absolutePath)
|
||||
describe "when passed the path to a buffer that is currently opened", ->
|
||||
it "returns a new edit session containing currently opened buffer and emits a 'editor-created' event", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open(absolutePath).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
newBufferHandler.reset()
|
||||
expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer
|
||||
expect(atom.project.openSync('a').buffer).toBe editor.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = atom.project.openSync()
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open().then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when passed a path that matches a custom opener", ->
|
||||
it "returns the resource returned by the custom opener", ->
|
||||
pathToOpen = atom.project.resolve('a.foo')
|
||||
expect(atom.project.openSync(pathToOpen, hey: "there")).toEqual { foo: pathToOpen, options: {hey: "there"} }
|
||||
expect(atom.project.openSync("bar://baz")).toEqual { bar: "bar://baz" }
|
||||
|
||||
describe ".open(path)", ->
|
||||
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditorHandler] = []
|
||||
|
||||
beforeEach ->
|
||||
absolutePath = require.resolve('./fixtures/dir/a')
|
||||
newBufferHandler = jasmine.createSpy('newBufferHandler')
|
||||
atom.project.on 'buffer-created', newBufferHandler
|
||||
newEditorHandler = jasmine.createSpy('newEditorHandler')
|
||||
atom.project.on 'editor-created', newEditorHandler
|
||||
|
||||
fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/)
|
||||
barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//)
|
||||
atom.project.registerOpener(fooOpener)
|
||||
atom.project.registerOpener(barOpener)
|
||||
|
||||
afterEach ->
|
||||
atom.project.unregisterOpener(fooOpener)
|
||||
atom.project.unregisterOpener(barOpener)
|
||||
|
||||
describe "when passed a path that doesn't match a custom opener", ->
|
||||
describe "when given an absolute path that isn't currently open", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open(absolutePath).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when given a relative path that isn't currently opened", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open(absolutePath).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when passed the path to a buffer that is currently opened", ->
|
||||
it "returns a new edit session containing currently opened buffer and emits a 'editor-created' event", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open(absolutePath).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
newBufferHandler.reset()
|
||||
expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer
|
||||
expect(atom.project.openSync('a').buffer).toBe editor.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
|
||||
editor = null
|
||||
waitsForPromise ->
|
||||
atom.project.open().then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
expect(editor.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
|
||||
expect(newEditorHandler).toHaveBeenCalledWith editor
|
||||
|
||||
describe "when passed a path that matches a custom opener", ->
|
||||
it "returns the resource returned by the custom opener", ->
|
||||
waitsForPromise ->
|
||||
pathToOpen = atom.project.resolve('a.foo')
|
||||
atom.project.open(pathToOpen, hey: "there").then (item) ->
|
||||
expect(item).toEqual { foo: pathToOpen, options: {hey: "there"} }
|
||||
|
||||
waitsForPromise ->
|
||||
atom.project.open("bar://baz").then (item) ->
|
||||
expect(item).toEqual { bar: "bar://baz" }
|
||||
|
||||
it "returns number of read bytes as progress indicator", ->
|
||||
filePath = atom.project.resolve 'a'
|
||||
totalBytes = 0
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
{times, random} = require 'underscore-plus'
|
||||
randomWords = require 'random-words'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Editor = require '../src/editor'
|
||||
|
||||
describe "Editor", ->
|
||||
[editor, tokenizedBuffer, buffer, steps, previousSteps] = []
|
||||
|
||||
softWrapColumn = 80
|
||||
|
||||
beforeEach ->
|
||||
atom.config.set('editor.softWrapAtPreferredLineLength', true)
|
||||
atom.config.set('editor.preferredLineLength', softWrapColumn)
|
||||
|
||||
it "properly renders soft-wrapped lines when randomly mutated", ->
|
||||
previousSteps = JSON.parse(localStorage.steps ? '[]')
|
||||
|
||||
times 10, (i) ->
|
||||
buffer = new TextBuffer
|
||||
editor = new Editor({buffer})
|
||||
editor.setEditorWidthInChars(80)
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
steps = []
|
||||
|
||||
times 30, ->
|
||||
randomlyMutateEditor()
|
||||
verifyLines()
|
||||
|
||||
verifyLines = ->
|
||||
{bufferRows, screenLines} = getReferenceScreenLines()
|
||||
for referenceBufferRow, screenRow in bufferRows
|
||||
referenceScreenLine = screenLines[screenRow]
|
||||
actualBufferRow = editor.bufferRowForScreenRow(screenRow)
|
||||
unless actualBufferRow is referenceBufferRow
|
||||
logLines()
|
||||
throw new Error("Invalid buffer row #{actualBufferRow} for screen row #{screenRow}", )
|
||||
|
||||
actualScreenLine = editor.lineForScreenRow(screenRow)
|
||||
unless actualScreenLine.text is referenceScreenLine.text
|
||||
logLines()
|
||||
throw new Error("Invalid line text at screen row #{screenRow}")
|
||||
|
||||
logLines = ->
|
||||
console.log "==== screen lines ===="
|
||||
editor.logScreenLines()
|
||||
console.log "==== reference lines ===="
|
||||
{bufferRows, screenLines} = getReferenceScreenLines()
|
||||
for bufferRow, screenRow in bufferRows
|
||||
console.log screenRow, bufferRow, screenLines[screenRow].text
|
||||
|
||||
randomlyMutateEditor = ->
|
||||
if Math.random() < .2
|
||||
softWrap = not editor.getSoftWrap()
|
||||
steps.push(['setSoftWrap', softWrap])
|
||||
editor.setSoftWrap(softWrap)
|
||||
else
|
||||
range = getRandomRange()
|
||||
text = getRandomText()
|
||||
steps.push(['setTextInBufferRange', range, text])
|
||||
editor.setTextInBufferRange(range, text)
|
||||
|
||||
getRandomRange = ->
|
||||
startRow = random(0, buffer.getLastRow())
|
||||
startColumn = random(0, buffer.lineForRow(startRow).length)
|
||||
endRow = random(startRow, buffer.getLastRow())
|
||||
endColumn = random(0, buffer.lineForRow(endRow).length)
|
||||
[[startRow, startColumn], [endRow, endColumn]]
|
||||
|
||||
getRandomText = ->
|
||||
text = []
|
||||
max = buffer.getText().split(/\s/).length * 0.75
|
||||
|
||||
times random(5, max), ->
|
||||
if Math.random() < .1
|
||||
text += '\n'
|
||||
else
|
||||
text += " " if /\w$/.test(text)
|
||||
text += randomWords(exactly: 1)
|
||||
text
|
||||
|
||||
getReferenceScreenLines = ->
|
||||
if editor.getSoftWrap()
|
||||
screenLines = []
|
||||
bufferRows = []
|
||||
for bufferRow in [0..tokenizedBuffer.getLastRow()]
|
||||
for screenLine in softWrapLine(tokenizedBuffer.lineForScreenRow(bufferRow))
|
||||
screenLines.push(screenLine)
|
||||
bufferRows.push(bufferRow)
|
||||
else
|
||||
screenLines = tokenizedBuffer.tokenizedLines.slice()
|
||||
bufferRows = [0..tokenizedBuffer.getLastRow()]
|
||||
{screenLines, bufferRows}
|
||||
|
||||
softWrapLine = (tokenizedLine) ->
|
||||
wrappedLines = []
|
||||
while tokenizedLine.text.length > softWrapColumn and wrapScreenColumn = findWrapColumn(tokenizedLine.text)
|
||||
[wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn)
|
||||
wrappedLines.push(wrappedLine)
|
||||
wrappedLines.push(tokenizedLine)
|
||||
wrappedLines
|
||||
|
||||
findWrapColumn = (line) ->
|
||||
if /\s/.test(line[softWrapColumn])
|
||||
# search forward for the start of a word past the boundary
|
||||
for column in [softWrapColumn..line.length]
|
||||
return column if /\S/.test(line[column])
|
||||
return line.length
|
||||
else
|
||||
# search backward for the start of the word on the boundary
|
||||
for column in [softWrapColumn..0]
|
||||
return column + 1 if /\s/.test(line[column])
|
||||
return softWrapColumn
|
||||
+86
-243
@@ -6,256 +6,99 @@ describe "RowMap", ->
|
||||
beforeEach ->
|
||||
map = new RowMap
|
||||
|
||||
describe "when no mappings have been recorded", ->
|
||||
it "maps buffer rows to screen rows 1:1", ->
|
||||
describe "::screenRowRangeForBufferRow(bufferRow)", ->
|
||||
it "returns the range of screen rows corresponding to the given buffer row", ->
|
||||
map.spliceRegions(0, 0, [
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 1, screenRows: 5}
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 5, screenRows: 1}
|
||||
])
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
expect(map.screenRowRangeForBufferRow(100)).toEqual [100, 101]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 10]
|
||||
expect(map.screenRowRangeForBufferRow(6)).toEqual [10, 11]
|
||||
expect(map.screenRowRangeForBufferRow(11)).toEqual [15, 16]
|
||||
expect(map.screenRowRangeForBufferRow(12)).toEqual [15, 16]
|
||||
expect(map.screenRowRangeForBufferRow(16)).toEqual [16, 17]
|
||||
|
||||
describe ".mapBufferRowRange(startBufferRow, endBufferRow, screenRows)", ->
|
||||
describe "when mapping to a single screen row (like a visible fold)", ->
|
||||
beforeEach ->
|
||||
map.mapBufferRowRange(5, 10, 1)
|
||||
map.mapBufferRowRange(15, 20, 1)
|
||||
map.mapBufferRowRange(25, 30, 1)
|
||||
describe "::bufferRowRangeForScreenRow(screenRow)", ->
|
||||
it "returns the range of buffer rows corresponding to the given screen row", ->
|
||||
map.spliceRegions(0, 0, [
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 1, screenRows: 5}
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 5, screenRows: 1}
|
||||
])
|
||||
|
||||
it "accounts for the mapping when translating buffer rows to screen row ranges", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 6]
|
||||
expect(map.bufferRowRangeForScreenRow(6)).toEqual [5, 6]
|
||||
expect(map.bufferRowRangeForScreenRow(10)).toEqual [6, 7]
|
||||
expect(map.bufferRowRangeForScreenRow(14)).toEqual [10, 11]
|
||||
expect(map.bufferRowRangeForScreenRow(15)).toEqual [11, 16]
|
||||
expect(map.bufferRowRangeForScreenRow(16)).toEqual [16, 17]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [6, 7]
|
||||
describe "::spliceRegions(startBufferRow, bufferRowCount, regions)", ->
|
||||
it "can insert regions when empty", ->
|
||||
regions = [
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 1, screenRows: 5}
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 5, screenRows: 1}
|
||||
]
|
||||
map.spliceRegions(0, 0, regions)
|
||||
expect(map.getRegions()).toEqual regions
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(14)).toEqual [10, 11]
|
||||
expect(map.screenRowRangeForBufferRow(15)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [12, 13]
|
||||
it "can insert wrapped lines into rectangular regions", ->
|
||||
map.spliceRegions(0, 0, [{bufferRows: 10, screenRows: 10}])
|
||||
map.spliceRegions(5, 0, [{bufferRows: 1, screenRows: 3}])
|
||||
expect(map.getRegions()).toEqual [
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 1, screenRows: 3}
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(24)).toEqual [16, 17]
|
||||
expect(map.screenRowRangeForBufferRow(25)).toEqual [17, 18]
|
||||
expect(map.screenRowRangeForBufferRow(29)).toEqual [17, 18]
|
||||
expect(map.screenRowRangeForBufferRow(30)).toEqual [18, 19]
|
||||
it "can splice wrapped lines into rectangular regions", ->
|
||||
map.spliceRegions(0, 0, [{bufferRows: 10, screenRows: 10}])
|
||||
map.spliceRegions(5, 1, [{bufferRows: 1, screenRows: 3}])
|
||||
expect(map.getRegions()).toEqual [
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 1, screenRows: 3}
|
||||
{bufferRows: 4, screenRows: 4}
|
||||
]
|
||||
|
||||
it "accounts for the mapping when translating screen rows to buffer row ranges", ->
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
it "can splice folded lines into rectangular regions", ->
|
||||
map.spliceRegions(0, 0, [{bufferRows: 10, screenRows: 10}])
|
||||
map.spliceRegions(5, 3, [{bufferRows: 3, screenRows: 1}])
|
||||
expect(map.getRegions()).toEqual [
|
||||
{bufferRows: 5, screenRows: 5}
|
||||
{bufferRows: 3, screenRows: 1}
|
||||
{bufferRows: 2, screenRows: 2}
|
||||
]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(4)).toEqual [4, 5]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(6)).toEqual [10, 11]
|
||||
it "can replace folded regions with a folded region that surrounds them", ->
|
||||
map.spliceRegions(0, 0, [
|
||||
{bufferRows: 3, screenRows: 3}
|
||||
{bufferRows: 3, screenRows: 1}
|
||||
{bufferRows: 1, screenRows: 1}
|
||||
{bufferRows: 3, screenRows: 1}
|
||||
{bufferRows: 3, screenRows: 3}
|
||||
])
|
||||
map.spliceRegions(2, 8, [{bufferRows: 8, screenRows: 1}])
|
||||
expect(map.getRegions()).toEqual [
|
||||
{bufferRows: 2, screenRows: 2}
|
||||
{bufferRows: 8, screenRows: 1}
|
||||
{bufferRows: 3, screenRows: 3}
|
||||
]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(10)).toEqual [14, 15]
|
||||
expect(map.bufferRowRangeForScreenRow(11)).toEqual [15, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(12)).toEqual [20, 21]
|
||||
it "merges adjacent rectangular regions", ->
|
||||
map.spliceRegions(0, 0, [
|
||||
{bufferRows: 3, screenRows: 3}
|
||||
{bufferRows: 3, screenRows: 1}
|
||||
{bufferRows: 1, screenRows: 1}
|
||||
{bufferRows: 3, screenRows: 1}
|
||||
{bufferRows: 3, screenRows: 3}
|
||||
])
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(16)).toEqual [24, 25]
|
||||
expect(map.bufferRowRangeForScreenRow(17)).toEqual [25, 30]
|
||||
expect(map.bufferRowRangeForScreenRow(18)).toEqual [30, 31]
|
||||
|
||||
describe "when mapping to zero screen rows (like an invisible fold)", ->
|
||||
beforeEach ->
|
||||
map.mapBufferRowRange(5, 10, 0)
|
||||
map.mapBufferRowRange(15, 20, 0)
|
||||
map.mapBufferRowRange(25, 30, 0)
|
||||
|
||||
it "accounts for the mapping when translating buffer rows to screen row ranges", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 5]
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [5, 5]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [5, 6]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(14)).toEqual [9, 10]
|
||||
expect(map.screenRowRangeForBufferRow(15)).toEqual [10, 10]
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [10, 10]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [10, 11]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(24)).toEqual [14, 15]
|
||||
expect(map.screenRowRangeForBufferRow(25)).toEqual [15, 15]
|
||||
expect(map.screenRowRangeForBufferRow(29)).toEqual [15, 15]
|
||||
expect(map.screenRowRangeForBufferRow(30)).toEqual [15, 16]
|
||||
|
||||
it "accounts for the mapping when translating screen rows to buffer row ranges", ->
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(4)).toEqual [4, 5]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [10, 11]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(9)).toEqual [14, 15]
|
||||
expect(map.bufferRowRangeForScreenRow(10)).toEqual [20, 21]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(14)).toEqual [24, 25]
|
||||
expect(map.bufferRowRangeForScreenRow(15)).toEqual [30, 31]
|
||||
|
||||
describe "when mapping a single buffer row to multiple screen rows (like a wrapped line)", ->
|
||||
beforeEach ->
|
||||
map.mapBufferRowRange(5, 6, 3)
|
||||
map.mapBufferRowRange(10, 11, 2)
|
||||
map.mapBufferRowRange(20, 21, 5)
|
||||
|
||||
it "accounts for the mapping when translating buffer rows to screen row ranges", ->
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 8]
|
||||
expect(map.screenRowRangeForBufferRow(6)).toEqual [8, 9]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [12, 14]
|
||||
expect(map.screenRowRangeForBufferRow(11)).toEqual [14, 15]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [22, 23]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [23, 28]
|
||||
expect(map.screenRowRangeForBufferRow(21)).toEqual [28, 29]
|
||||
|
||||
it "accounts for the mapping when translating screen rows to buffer row ranges", ->
|
||||
expect(map.bufferRowRangeForScreenRow(0)).toEqual [0, 1]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(4)).toEqual [4, 5]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 6]
|
||||
expect(map.bufferRowRangeForScreenRow(7)).toEqual [5, 6]
|
||||
expect(map.bufferRowRangeForScreenRow(8)).toEqual [6, 7]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(11)).toEqual [9, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(12)).toEqual [10, 11]
|
||||
expect(map.bufferRowRangeForScreenRow(13)).toEqual [10, 11]
|
||||
expect(map.bufferRowRangeForScreenRow(14)).toEqual [11, 12]
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(22)).toEqual [19, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(23)).toEqual [20, 21]
|
||||
expect(map.bufferRowRangeForScreenRow(27)).toEqual [20, 21]
|
||||
expect(map.bufferRowRangeForScreenRow(28)).toEqual [21, 22]
|
||||
|
||||
describe "after re-mapping a row range to a new number of screen rows", ->
|
||||
beforeEach ->
|
||||
map.applyScreenDelta(12, 2) # simulate adding 2 more soft wraps
|
||||
map.mapBufferRowRange(10, 11, 4)
|
||||
|
||||
it "updates translation accordingly", ->
|
||||
expect(map.screenRowRangeForBufferRow(4)).toEqual [4, 5]
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 8]
|
||||
expect(map.screenRowRangeForBufferRow(6)).toEqual [8, 9]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [11, 12]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [12, 16]
|
||||
expect(map.screenRowRangeForBufferRow(11)).toEqual [16, 17]
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [24, 25]
|
||||
expect(map.screenRowRangeForBufferRow(20)).toEqual [25, 30]
|
||||
expect(map.screenRowRangeForBufferRow(21)).toEqual [30, 31]
|
||||
|
||||
describe "when the row range is inside an existing 1:1 region", ->
|
||||
it "preserves the starting screen row of subsequent 1:N regions", ->
|
||||
map.mapBufferRowRange(5, 10, 1)
|
||||
map.mapBufferRowRange(25, 30, 1)
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(21)).toEqual [25, 30]
|
||||
|
||||
map.mapBufferRowRange(15, 20, 1)
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(11)).toEqual [15, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 10]
|
||||
expect(map.bufferRowRangeForScreenRow(21)).toEqual [25, 30]
|
||||
|
||||
describe "when the row range surrounds existing regions", ->
|
||||
it "replaces the regions inside the given buffer row range with a single region", ->
|
||||
map.mapBufferRowRange(5, 10, 1) # inner fold 1
|
||||
map.mapBufferRowRange(11, 13, 1) # inner fold 2
|
||||
map.mapBufferRowRange(15, 20, 1) # inner fold 3
|
||||
map.mapBufferRowRange(22, 27, 1) # following fold
|
||||
|
||||
map.mapBufferRowRange(5, 20, 1)
|
||||
|
||||
expect(map.bufferRowRangeForScreenRow(5)).toEqual [5, 20]
|
||||
expect(map.bufferRowRangeForScreenRow(6)).toEqual [20, 21]
|
||||
expect(map.bufferRowRangeForScreenRow(7)).toEqual [21, 22]
|
||||
expect(map.bufferRowRangeForScreenRow(8)).toEqual [22, 27]
|
||||
|
||||
it "replaces regions that cover 0 buffer rows at the start or end of the buffer row range", ->
|
||||
map.mapBufferRowRange(0, 0, 1)
|
||||
map.mapBufferRowRange(0, 1, 1)
|
||||
map.mapBufferRowRange(1, 1, 1)
|
||||
map.mapBufferRowRange(0, 1, 3)
|
||||
expect(map.screenRowRangeForBufferRow(0)).toEqual [0, 3]
|
||||
|
||||
describe "when the row range straddles existing regions", ->
|
||||
it "splits the straddled regions and places the new region between them", ->
|
||||
# filler region 0
|
||||
map.mapBufferRowRange(4, 7, 1) # region 1
|
||||
# filler region 2
|
||||
map.mapBufferRowRange(13, 15, 1) # region 3
|
||||
|
||||
# create region straddling region 0 and region 2
|
||||
map.mapBufferRowRange(2, 10, 1)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 2, screenRows: 2)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 8, screenRows: 1)
|
||||
expect(map.regions[2]).toEqual(bufferRows: 3, screenRows: 8)
|
||||
expect(map.regions[3]).toEqual(bufferRows: 2, screenRows: 1)
|
||||
|
||||
it "merges adjacent isomorphic mappings", ->
|
||||
map.mapBufferRowRange(2, 4, 1)
|
||||
map.mapBufferRowRange(4, 5, 2)
|
||||
|
||||
map.mapBufferRowRange(1, 4, 3)
|
||||
expect(map.regions).toEqual [{bufferRows: 5, screenRows: 5}]
|
||||
|
||||
describe ".applyBufferDelta(startBufferRow, delta)", ->
|
||||
describe "when applying a positive delta", ->
|
||||
it "expands the region containing the given start row by the given delta", ->
|
||||
map.mapBufferRowRange(4, 8, 1)
|
||||
|
||||
map.applyBufferDelta(5, 4)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 4, screenRows: 4)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 8, screenRows: 1)
|
||||
|
||||
describe "when applying a negative delta", ->
|
||||
it "shrinks regions starting at the start row until the entire delta is consumed", ->
|
||||
map.mapBufferRowRange(4, 8, 1)
|
||||
map.mapBufferRowRange(10, 14, 1)
|
||||
|
||||
map.applyBufferDelta(3, -6)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 3, screenRows: 4)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 0, screenRows: 1)
|
||||
expect(map.regions[2]).toEqual(bufferRows: 1, screenRows: 2)
|
||||
expect(map.regions[3]).toEqual(bufferRows: 4, screenRows: 1)
|
||||
|
||||
describe ".applyScreenDelta(startScreenRow, delta)", ->
|
||||
describe "when applying a positive delta", ->
|
||||
it "can enlarge the screen side of existing regions", ->
|
||||
map.mapBufferRowRange(5, 6, 3) # wrapped line
|
||||
map.applyScreenDelta(5, 2) # wrap it twice more
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 10]
|
||||
|
||||
describe "when applying a negative delta", ->
|
||||
it "can collapse the screen side of multiple regions to 0 until the entire delta has been applied", ->
|
||||
map.mapBufferRowRange(5, 10, 1) # inner fold 1
|
||||
map.mapBufferRowRange(11, 13, 1) # inner fold 2
|
||||
map.mapBufferRowRange(15, 20, 1) # inner fold 3
|
||||
map.mapBufferRowRange(22, 27, 1) # following fold
|
||||
|
||||
map.applyScreenDelta(6, -5)
|
||||
|
||||
expect(map.screenRowRangeForBufferRow(5)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(9)).toEqual [5, 6]
|
||||
expect(map.screenRowRangeForBufferRow(10)).toEqual [6, 6]
|
||||
expect(map.screenRowRangeForBufferRow(19)).toEqual [6, 6]
|
||||
expect(map.screenRowRangeForBufferRow(22)).toEqual [8, 9]
|
||||
expect(map.screenRowRangeForBufferRow(26)).toEqual [8, 9]
|
||||
|
||||
it "starts collapsing the first region at the start row, not before", ->
|
||||
map.mapBufferRowRange(5, 6, 4)
|
||||
map.mapBufferRowRange(11, 13, 1)
|
||||
|
||||
map.applyScreenDelta(7, -5)
|
||||
|
||||
expect(map.regions[0]).toEqual(bufferRows: 5, screenRows: 5)
|
||||
expect(map.regions[1]).toEqual(bufferRows: 1, screenRows: 2)
|
||||
expect(map.regions[2]).toEqual(bufferRows: 5, screenRows: 2)
|
||||
|
||||
it "does not throw an exception when applying a delta beyond the last region", ->
|
||||
map.mapBufferRowRange(5, 10, 1) # inner fold 1
|
||||
map.applyScreenDelta(15, 10)
|
||||
map.spliceRegions(3, 7, [{bufferRows: 5, screenRows: 5}])
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
SelectList = require '../src/select-list'
|
||||
SelectListView = require '../src/select-list-view'
|
||||
{$, $$} = require 'atom'
|
||||
|
||||
describe "SelectList", ->
|
||||
[selectList, array, list, miniEditor] = []
|
||||
describe "SelectListView", ->
|
||||
[selectList, items, list, filterEditorView] = []
|
||||
|
||||
beforeEach ->
|
||||
array = [
|
||||
items = [
|
||||
["A", "Alpha"], ["B", "Bravo"], ["C", "Charlie"],
|
||||
["D", "Delta"], ["E", "Echo"], ["F", "Foxtrot"]
|
||||
]
|
||||
|
||||
selectList = new SelectList
|
||||
selectList.maxItems = 4
|
||||
selectList.filterKey = 1
|
||||
selectList.itemForElement = (element) ->
|
||||
$$ -> @li element[1], class: element[0]
|
||||
selectList = new SelectListView
|
||||
selectList.setMaxItems(4)
|
||||
selectList.getFilterKey = -> 1
|
||||
selectList.viewForItem = (item) ->
|
||||
$$ -> @li item[1], class: item[0]
|
||||
|
||||
selectList.confirmed = jasmine.createSpy('confirmed hook')
|
||||
selectList.cancelled = jasmine.createSpy('cancelled hook')
|
||||
|
||||
selectList.setArray(array)
|
||||
{list, miniEditor} = selectList
|
||||
selectList.setItems(items)
|
||||
{list, filterEditorView} = selectList
|
||||
|
||||
describe "when an array is assigned", ->
|
||||
it "populates the list with up to maxItems items, based on the liForElement function", ->
|
||||
@@ -28,12 +28,27 @@ describe "SelectList", ->
|
||||
expect(list.find('li:eq(0)')).toHaveText 'Alpha'
|
||||
expect(list.find('li:eq(0)')).toHaveClass 'A'
|
||||
|
||||
describe "viewForItem(item)", ->
|
||||
it "allows raw DOM elements to be returned", ->
|
||||
selectList.viewForItem = (item) ->
|
||||
li = document.createElement('li')
|
||||
li.classList.add(item[0])
|
||||
li.innerText = item[1]
|
||||
li
|
||||
|
||||
selectList.setItems(items)
|
||||
|
||||
expect(list.find('li').length).toBe selectList.maxItems
|
||||
expect(list.find('li:eq(0)')).toHaveText 'Alpha'
|
||||
expect(list.find('li:eq(0)')).toHaveClass 'A'
|
||||
expect(selectList.getSelectedItem()).toBe items[0]
|
||||
|
||||
describe "when the text of the mini editor changes", ->
|
||||
beforeEach ->
|
||||
selectList.attachToDom()
|
||||
|
||||
it "filters the elements in the list based on the scoreElement function and selects the first item", ->
|
||||
miniEditor.insertText('la')
|
||||
filterEditorView.getEditor().insertText('la')
|
||||
window.advanceClock(selectList.inputThrottle)
|
||||
|
||||
expect(list.find('li').length).toBe 2
|
||||
@@ -43,54 +58,54 @@ describe "SelectList", ->
|
||||
expect(selectList.error).not.toBeVisible()
|
||||
|
||||
it "displays an error if there are no matches, removes error when there are matches", ->
|
||||
miniEditor.insertText('nothing will match this')
|
||||
filterEditorView.getEditor().insertText('nothing will match this')
|
||||
window.advanceClock(selectList.inputThrottle)
|
||||
|
||||
expect(list.find('li').length).toBe 0
|
||||
expect(selectList.error).not.toBeHidden()
|
||||
|
||||
miniEditor.setText('la')
|
||||
filterEditorView.getEditor().setText('la')
|
||||
window.advanceClock(selectList.inputThrottle)
|
||||
|
||||
expect(list.find('li').length).toBe 2
|
||||
expect(selectList.error).not.toBeVisible()
|
||||
|
||||
it "displays no elements until the array has been set on the list", ->
|
||||
selectList.array = null
|
||||
selectList.items = null
|
||||
selectList.list.empty()
|
||||
miniEditor.insertText('la')
|
||||
filterEditorView.getEditor().insertText('la')
|
||||
window.advanceClock(selectList.inputThrottle)
|
||||
|
||||
expect(list.find('li').length).toBe 0
|
||||
expect(selectList.error).toBeHidden()
|
||||
selectList.setArray(array)
|
||||
selectList.setItems(items)
|
||||
expect(list.find('li').length).toBe 2
|
||||
|
||||
describe "when core:move-up / core:move-down are triggered on the miniEditor", ->
|
||||
describe "when core:move-up / core:move-down are triggered on the filterEditorView", ->
|
||||
it "selects the previous / next item in the list, or wraps around to the other side", ->
|
||||
expect(list.find('li:first')).toHaveClass 'selected'
|
||||
|
||||
miniEditor.trigger 'core:move-up'
|
||||
filterEditorView.trigger 'core:move-up'
|
||||
|
||||
expect(list.find('li:first')).not.toHaveClass 'selected'
|
||||
expect(list.find('li:last')).toHaveClass 'selected'
|
||||
|
||||
miniEditor.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
|
||||
expect(list.find('li:first')).toHaveClass 'selected'
|
||||
expect(list.find('li:last')).not.toHaveClass 'selected'
|
||||
|
||||
miniEditor.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
|
||||
expect(list.find('li:eq(0)')).not.toHaveClass 'selected'
|
||||
expect(list.find('li:eq(1)')).toHaveClass 'selected'
|
||||
|
||||
miniEditor.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
|
||||
expect(list.find('li:eq(1)')).not.toHaveClass 'selected'
|
||||
expect(list.find('li:eq(2)')).toHaveClass 'selected'
|
||||
|
||||
miniEditor.trigger 'core:move-up'
|
||||
filterEditorView.trigger 'core:move-up'
|
||||
|
||||
expect(list.find('li:eq(2)')).not.toHaveClass 'selected'
|
||||
expect(list.find('li:eq(1)')).toHaveClass 'selected'
|
||||
@@ -100,43 +115,43 @@ describe "SelectList", ->
|
||||
itemHeight = list.find('li').outerHeight()
|
||||
list.height(itemHeight * 2)
|
||||
|
||||
miniEditor.trigger 'core:move-down'
|
||||
miniEditor.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
expect(list.scrollBottom()).toBe itemHeight * 3
|
||||
|
||||
miniEditor.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
expect(list.scrollBottom()).toBe itemHeight * 4
|
||||
|
||||
miniEditor.trigger 'core:move-up'
|
||||
miniEditor.trigger 'core:move-up'
|
||||
filterEditorView.trigger 'core:move-up'
|
||||
filterEditorView.trigger 'core:move-up'
|
||||
expect(list.scrollTop()).toBe itemHeight
|
||||
|
||||
describe "the core:confirm event", ->
|
||||
describe "when there is an item selected (because the list in not empty)", ->
|
||||
it "triggers the selected hook with the selected array element", ->
|
||||
miniEditor.trigger 'core:move-down'
|
||||
miniEditor.trigger 'core:move-down'
|
||||
miniEditor.trigger 'core:confirm'
|
||||
expect(selectList.confirmed).toHaveBeenCalledWith(array[2])
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:move-down'
|
||||
filterEditorView.trigger 'core:confirm'
|
||||
expect(selectList.confirmed).toHaveBeenCalledWith(items[2])
|
||||
|
||||
describe "when there is no item selected (because the list is empty)", ->
|
||||
beforeEach ->
|
||||
selectList.attachToDom()
|
||||
|
||||
it "does not trigger the confirmed hook", ->
|
||||
miniEditor.insertText("i will never match anything")
|
||||
filterEditorView.getEditor().insertText("i will never match anything")
|
||||
window.advanceClock(selectList.inputThrottle)
|
||||
|
||||
expect(list.find('li')).not.toExist()
|
||||
miniEditor.trigger 'core:confirm'
|
||||
filterEditorView.trigger 'core:confirm'
|
||||
expect(selectList.confirmed).not.toHaveBeenCalled()
|
||||
|
||||
it "does trigger the cancelled hook", ->
|
||||
miniEditor.insertText("i will never match anything")
|
||||
filterEditorView.getEditor().insertText("i will never match anything")
|
||||
window.advanceClock(selectList.inputThrottle)
|
||||
|
||||
expect(list.find('li')).not.toExist()
|
||||
miniEditor.trigger 'core:confirm'
|
||||
filterEditorView.trigger 'core:confirm'
|
||||
expect(selectList.cancelled).toHaveBeenCalled()
|
||||
|
||||
describe "when a list item is clicked", ->
|
||||
@@ -147,12 +162,12 @@ describe "SelectList", ->
|
||||
expect(item).toHaveClass 'selected'
|
||||
item.mouseup()
|
||||
|
||||
expect(selectList.confirmed).toHaveBeenCalledWith(array[1])
|
||||
expect(selectList.confirmed).toHaveBeenCalledWith(items[1])
|
||||
|
||||
describe "the core:cancel event", ->
|
||||
it "triggers the cancelled hook and detaches and empties the select list", ->
|
||||
spyOn(selectList, 'detach')
|
||||
miniEditor.trigger 'core:cancel'
|
||||
filterEditorView.trigger 'core:cancel'
|
||||
expect(selectList.cancelled).toHaveBeenCalled()
|
||||
expect(selectList.detach).toHaveBeenCalled()
|
||||
expect(selectList.list).toBeEmpty()
|
||||
@@ -160,7 +175,7 @@ describe "SelectList", ->
|
||||
describe "when the mini editor loses focus", ->
|
||||
it "triggers the cancelled hook and detaches the select list", ->
|
||||
spyOn(selectList, 'detach')
|
||||
miniEditor.hiddenInput.trigger 'focusout'
|
||||
filterEditorView.hiddenInput.trigger 'focusout'
|
||||
expect(selectList.cancelled).toHaveBeenCalled()
|
||||
expect(selectList.detach).toHaveBeenCalled()
|
||||
|
||||
@@ -11,35 +11,6 @@ describe "SpacePen extensions", ->
|
||||
parent = $$ -> @div()
|
||||
parent.append(view)
|
||||
|
||||
describe "View.observeConfig(keyPath, callback)", ->
|
||||
observeHandler = null
|
||||
|
||||
beforeEach ->
|
||||
observeHandler = jasmine.createSpy("observeHandler")
|
||||
view.observeConfig "foo.bar", observeHandler
|
||||
expect(view.hasParent()).toBeTruthy()
|
||||
|
||||
it "observes the keyPath and cancels the subscription when `.unobserveConfig()` is called", ->
|
||||
expect(observeHandler).toHaveBeenCalledWith(undefined)
|
||||
observeHandler.reset()
|
||||
|
||||
atom.config.set("foo.bar", "hello")
|
||||
|
||||
expect(observeHandler).toHaveBeenCalledWith("hello", previous: undefined)
|
||||
observeHandler.reset()
|
||||
|
||||
view.unobserveConfig()
|
||||
|
||||
atom.config.set("foo.bar", "goodbye")
|
||||
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "unobserves when the view is removed", ->
|
||||
observeHandler.reset()
|
||||
parent.remove()
|
||||
atom.config.set("foo.bar", "hello")
|
||||
expect(observeHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe "View.subscribe(eventEmitter, eventName, callback)", ->
|
||||
[emitter, eventHandler] = []
|
||||
|
||||
@@ -65,3 +36,19 @@ describe "SpacePen extensions", ->
|
||||
|
||||
it "replaces multiple keystroke", ->
|
||||
expect(humanizeKeystrokes('cmd-o ctrl-2')).toEqual '⌘O ⌃2'
|
||||
|
||||
describe "when the window is resized", ->
|
||||
it "hides the tooltips", ->
|
||||
class TooltipView extends View
|
||||
@content: ->
|
||||
@div()
|
||||
|
||||
view = new TooltipView()
|
||||
view.attachToDom()
|
||||
view.setTooltip('this is a tip')
|
||||
|
||||
view.tooltip('show')
|
||||
expect($(document.body).find('.tooltip')).toBeVisible()
|
||||
|
||||
$(window).trigger('resize')
|
||||
expect($(document.body).find('.tooltip')).not.toExist()
|
||||
|
||||
@@ -5,7 +5,11 @@ try
|
||||
require '../src/window'
|
||||
Atom = require '../src/atom'
|
||||
window.atom = Atom.loadOrCreate('spec')
|
||||
window.atom.show() unless atom.getLoadSettings().exitWhenDone
|
||||
|
||||
# Show window synchronously so a focusout doesn't fire on input elements
|
||||
# that are focused in the very first spec run.
|
||||
atom.getCurrentWindow().show() unless atom.getLoadSettings().exitWhenDone
|
||||
|
||||
{runSpecSuite} = require './jasmine-helper'
|
||||
|
||||
document.title = "Spec Suite"
|
||||
|
||||
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