Compare commits
824 Commits
Author | SHA1 | Date |
---|---|---|
George Sokianos | ff3e5282f6 | |
George Sokianos | 89e2defb5b | |
George Sokianos | 1e90105944 | |
George Sokianos | f2e42ca2fa | |
George Sokianos | decd5deeae | |
George Sokianos | b5831ace20 | |
George Sokianos | 8fe3c75339 | |
George Sokianos | 9b599aaa78 | |
George Sokianos | ddb81648c2 | |
George Sokianos | b919e5b942 | |
George Sokianos | 05cf40c1c9 | |
George Sokianos | ad75b7521f | |
Adam | 8101c1eae3 | |
Adam | 19c239de42 | |
Adam Harrison | 5ee465f382 | |
Jipok | 941b283681 | |
Jipok | 8178fc46bb | |
Takase | f2ff32d5a7 | |
Jipok | 0461d3bdce | |
Jipok | 4183d3b2fd | |
Guldoman | 2e9619f630 | |
Guldoman | 4cf9d85986 | |
Adam | 4d5d3e2565 | |
Adam Harrison | 9cf795dabf | |
Adam Harrison | 443177cc1c | |
Adam Harrison | cb13afd749 | |
Adam Harrison | b4384eb49d | |
Adam | baf43084b6 | |
Jipok | a8f7e9a35e | |
Adam | 7ae33bf6f4 | |
Adam | a019420bf9 | |
Adam Harrison | eb9918089a | |
Adam Harrison | b7631ba44a | |
Guldoman | 69de42b078 | |
Adam | 997b3efbb7 | |
Adam | dadfa4b3e8 | |
Adam | 9a813c818b | |
Adam Harrison | ff5c3c1492 | |
Adam Harrison | 95945d86ab | |
Adam Harrison | 7b55470159 | |
Adam | 29ece95962 | |
Adam | f1054b8280 | |
Adam | a1401efd1f | |
Jipok | c3b7234315 | |
Guldoman | 4faaf089ef | |
Guldoman | c16d6b3d8d | |
Guldoman | fdb29f28cf | |
Guldoman | 7cb2068bb8 | |
Adam | 994c62b64a | |
Adam | 4b3d25e15a | |
Adam | 8120654c59 | |
Adam | 2997aa2652 | |
Adam | a70e7e945e | |
Nightwing | 0705c23c35 | |
Guldoman | 5029e2ce29 | |
takase1121 | d307c57d05 | |
takase1121 | aff1261b08 | |
Guldoman | d7afcb08b1 | |
Guldoman | e500368ce4 | |
Guldoman | ef4c02ab0e | |
Adam | 7b82d787c0 | |
Adam Harrison | 01e38f041a | |
Adam | a607ef95f9 | |
Adam | 4767ffe58d | |
Adam | 1b22c85dd5 | |
Adam | e56fd4c05a | |
Joshua Minor | 272ecd64bf | |
Adam | 3e79595e24 | |
Adam | 4e078cc217 | |
Francesco Abbate | 0c488c9492 | |
Adam | fe6ba4adb7 | |
Adam | 9b24563b84 | |
Guldoman | 59f64088e1 | |
Guldoman | 5dca37b11a | |
Adam Harrison | f7b3a2b0c2 | |
Adam Harrison | 7ee23da187 | |
Adam Harrison | 463605ff41 | |
Adam Harrison | 64f66e5d1e | |
Adam Harrison | cc3fddd1e5 | |
Adam Harrison | 3162f4ea4f | |
PIESEL | 00d555b016 | |
Guldoman | cd2adb4a30 | |
Adam | d7b6fe3d42 | |
Adam Harrison | 96db380c73 | |
Adam | 43a6e21135 | |
Adam Harrison | bc5be3c9b7 | |
Guldoman | 955acf53a3 | |
cukmekerb | 65f1251767 | |
Adam | 9869484ec6 | |
Guldoman | 23a0f6ca79 | |
Adam | 724faf8dcf | |
Joshua Minor | 373007a767 | |
Joshua Minor | bede0ff878 | |
Guldoman | 3176b467ca | |
Guldoman | 2de48b6ac8 | |
Guldoman | 3d9901695b | |
Guldoman | b77b1c0221 | |
Guldoman | d0a2c913f5 | |
Guldoman | f24aa64cd5 | |
Adam | b2940d6dc0 | |
Joshua Minor | 4f55555ca9 | |
Guldoman | 298ec8a1e9 | |
Takase | d7c309d8e2 | |
Adam | 16679dcbce | |
Guldoman | 6a7a02542f | |
takase1121 | b42f48782b | |
Adam | 0525e6e96a | |
Takase | 8e6f594790 | |
takase1121 | 6d36f2684a | |
Adam Harrison | 18959aebef | |
Adam | c58029df8b | |
cukmekerb | 46f81e0bad | |
Adam | 0ab0abec4b | |
Takase | 285ea4f495 | |
Adam | ca448c5fdb | |
Adam | a7bbd3d6f7 | |
Adam Harrison | 2463c5d209 | |
Adam Harrison | 6750ddca2a | |
Adam Harrison | acc6667f57 | |
Adam Harrison | d8473a3e00 | |
Adam Harrison | 2931bdeb68 | |
Adam Harrison | 50c0659445 | |
Adam Harrison | 7babed1e6b | |
Adam Harrison | 7a3e8ed86a | |
Adam Harrison | c04dc648de | |
Adam Harrison | 1968d31b7c | |
Adam Harrison | 6bdcfc824d | |
Adam Harrison | 7905ddd26f | |
Adam Harrison | 4e313d9fc5 | |
Adam Harrison | ce2ec9f442 | |
Adam Harrison | 6f53ee1b69 | |
Adam Harrison | 4a0d390a7c | |
Adam Harrison | 612818ca05 | |
Adam | 09bf8bab2f | |
Adam Harrison | 1376eaf54d | |
Adam | 0db3648966 | |
Guldoman | 0353d1d435 | |
obtusedev | dfa48ddb51 | |
Takase | 2033b67dae | |
Guldoman | 6bc4fbb238 | |
Guldoman | b3eef15e7a | |
Takase | d22bf520ed | |
takase1121 | 68eb6810d9 | |
takase1121 | b26ffb4731 | |
takase1121 | 8bcc2d20f4 | |
Guldoman | bcfd33a7df | |
Adam Harrison | 5b8c08e93a | |
Adam Harrison | 24669293c7 | |
Adam | 3b38e7c351 | |
Guldoman | f99afcd29c | |
Adam | 4e26a9fb2d | |
Adam | 40f698e4bf | |
Adam | b7042fd9f7 | |
Adam Harrison | 934f9c05d4 | |
Adam Harrison | a184ec9cc3 | |
Adam | 4b6a6a76f7 | |
Adam Harrison | 05dcddaeec | |
Adam | c1a5e6e16d | |
takase1121 | e785bbf1bf | |
takase1121 | ad58f8898f | |
takase1121 | d0a54227d8 | |
takase1121 | ac50404337 | |
takase1121 | 3982a8b31c | |
takase1121 | acab4e3b26 | |
takase1121 | 9003a9124a | |
takase1121 | a9d7ddc6e1 | |
Cukmekerb | 3867ff2be7 | |
takase1121 | 46f9bcb90c | |
Adam | 286183f917 | |
obtusedev | d0ece35705 | |
Adam | dd9fc72fbd | |
Adam | faea2a7f91 | |
PIESEL | cd10497b49 | |
Adam | 90714c48e1 | |
Adam | 920982cbe7 | |
Adam Harrison | b7cb7e2b67 | |
Adam Harrison | 9e67995feb | |
Adam | 033575c9f4 | |
Jan200101 | e313eb0e2e | |
Guldoman | 9e721937af | |
Jan200101 | 065fe07696 | |
Guldoman | df665ddc38 | |
Guldoman | 92db048e7c | |
Francesco Abbate | e68d6016f8 | |
Francesco Abbate | 80d837b05b | |
Francesco Abbate | d41aed61c9 | |
Francesco Abbate | 5cdd800910 | |
Francesco Abbate | ffb66cefd7 | |
Guldoman | 331b8e90ec | |
Francesco | 6f732f67f9 | |
Francesco Abbate | ddb6196e9e | |
Francesco Abbate | 7bdfcd529e | |
Francesco Abbate | e9c16c4367 | |
Francesco Abbate | 167e41de65 | |
Francesco Abbate | f18ac849fb | |
Francesco Abbate | 43374b036f | |
Francesco Abbate | 9c52c420c5 | |
Francesco Abbate | f472c24c73 | |
Adam Harrison | 461533eabf | |
Adam | 3f1378ab2e | |
Adam | c973b9306b | |
Adam Harrison | b816a04d27 | |
Adam Harrison | 16fc15daee | |
Adam Harrison | cab315bed1 | |
Adam Harrison | c7c4a3c528 | |
Adam Harrison | d1fcdacacd | |
Adam Harrison | 7575d2eee6 | |
Adam Harrison | 3092dca919 | |
Guldoman | 780c8c6d0d | |
Guldoman | ef60b24f63 | |
Adam Harrison | f2488fdd8d | |
Adam | 3e2b0f28c8 | |
Adam Harrison | 7c1ff0f3d8 | |
Francesco | e99d0e51c1 | |
Jan200101 | d3fa64ce59 | |
Adam Harrison | d2e16ce0b5 | |
Guldoman | 8a516d35ce | |
Guldoman | 1872e82141 | |
Guldoman | 038e335c8c | |
Guldoman | 3a71528087 | |
Francesco Abbate | 0d2166c9ce | |
Francesco Abbate | 8b634daa66 | |
Francesco Abbate | c7aa3ebe01 | |
Francesco Abbate | 0f8d7f3202 | |
Francesco Abbate | cb08c5cbb7 | |
Francesco | 228d6ff101 | |
Francesco | c179f909e2 | |
Guldoman | 3a1274fd08 | |
Guldoman | cfe0c79a04 | |
Guldoman | af925d603b | |
Guldoman | 1976facaf1 | |
Guldoman | e7be9652c9 | |
Guldoman | 56eace627a | |
Francesco Abbate | a99dd947ed | |
Francesco Abbate | 7dd5699c96 | |
Francesco Abbate | 911a3cee08 | |
Francesco Abbate | a9f6f01ed0 | |
Francesco Abbate | bba42adc73 | |
Francesco Abbate | 9c43727ebc | |
Francesco Abbate | 92362586df | |
Francesco Abbate | 7a435a568a | |
Adam Harrison | fe787de97a | |
Francesco Abbate | 44d7f3738f | |
Francesco Abbate | 8477818c96 | |
Takase | b128d8b4c4 | |
Adam | 6264caffe1 | |
Francesco | 9fb166d3cc | |
Guldoman | db3643653e | |
Adam Harrison | 26ec2d7090 | |
Francesco | eb79381c89 | |
Francesco Abbate | 3589031579 | |
Francesco Abbate | 72c950338c | |
Guldoman | 20ddbd6e9f | |
Guldoman | 57bfb67f6a | |
Guldoman | 468229e4d0 | |
Guldoman | f6b9d9ab67 | |
Rongfei Wang | b0b3485152 | |
Jean-André Santoni | 0b4d1e2bce | |
Not-a-web-Developer | a97a3d80da | |
Adam | 6aa316e3c3 | |
Adam | ed3ea35ed5 | |
Adam Harrison | 291616df3f | |
Francesco Abbate | 34983668d8 | |
Francesco Abbate | d067cc8577 | |
Francesco Abbate | 48475c70a0 | |
Adam | ab73f914ad | |
Guldoman | e2f7c984de | |
Francesco | 0ff0ee2c61 | |
Guldoman | d817944170 | |
Guldoman | dced6da03d | |
Guldoman | 86632b68de | |
Adam Harrison | f80f920bda | |
Adam Harrison | ee61b34084 | |
Guldoman | bf06aa1c4d | |
Guldoman | b5f0b340d2 | |
Adam Harrison | c5f60a7865 | |
Guldoman | 3b280401e4 | |
Adam Harrison | 531cd3bedb | |
Guldoman | ab0ca031fa | |
Rongfei Wang | cf11d67895 | |
Jean-André Santoni | eb73ad3f8a | |
Adam | f0a3f50e6a | |
Not-a-web-Developer | 86bf023c90 | |
Adam Harrison | 7a21ec382f | |
Adam Harrison | 4690459a13 | |
Adam | 8f8af19cbe | |
Adam | 84622a0009 | |
Adam Harrison | ecbdb7a945 | |
Adam Harrison | 8816131780 | |
Adam Harrison | b17aa3b068 | |
Adam Harrison | 42d72cc296 | |
Adam Harrison | 806e4bc970 | |
Adam Harrison | d07d0e6d22 | |
Adam | efbec1e84a | |
takase1121 | 27fe185ed4 | |
Adam Harrison | c5fda5237f | |
Adam Harrison | 940db0f9c7 | |
Adam Harrison | 16deedc8a3 | |
Adam Harrison | 10f28079ba | |
Adam Harrison | a68fff2fff | |
Adam Harrison | b6829cb041 | |
Adam Harrison | 425a4f600b | |
Adam Harrison | 67032f72ac | |
Adam Harrison | 19b90aae18 | |
Adam Harrison | c879e016cc | |
Adam Harrison | be6bcbcacc | |
Adam Harrison | 2209c327a7 | |
Adam Harrison | e25f2e9c5c | |
Adam | 8c32950f4b | |
Adam Harrison | 466464d8a4 | |
Adam Harrison | b8da46e10e | |
Adam Harrison | 5ffe4eae90 | |
Adam Harrison | 713ef787c2 | |
Adam Harrison | e13529444f | |
Adam Harrison | c01c5a23b0 | |
Adam Harrison | 3ca127793a | |
Francesco Abbate | 14dd6f1cd6 | |
Francesco Abbate | 8d3680ab45 | |
Francesco Abbate | 849614a3cb | |
Adam | 075061b80c | |
Guldoman | 80a6b2245e | |
Francesco | c018ca3c60 | |
Guldoman | f6b6634868 | |
Guldoman | 3eba7cd7f1 | |
Guldoman | 6a3f59c423 | |
Adam | 1836639651 | |
Adam Harrison | 2ac7c7f09b | |
Adam Harrison | 4b828eff65 | |
Adam Harrison | fbc11c00eb | |
Adam Harrison | 1721b8f1c9 | |
Adam Harrison | 10c3c9d4cf | |
Adam Harrison | 801b7dd0d9 | |
Adam Harrison | 03b467d9d9 | |
Adam Harrison | 2987554097 | |
Adam Harrison | 377ce1cd06 | |
Guldoman | 66bfff2e26 | |
Guldoman | 9bfec4aca5 | |
Guldoman | fb955e4e12 | |
Guldoman | 1e8031a0e8 | |
Adam Harrison | e9f48ce949 | |
Adam Harrison | a66a76f9c9 | |
Francesco Abbate | 25744d93ce | |
Francesco Abbate | 18189e63b6 | |
Francesco Abbate | 8dd530e5cf | |
Francesco Abbate | 218999dff8 | |
Francesco Abbate | afd0672197 | |
Francesco Abbate | cec1e4efb9 | |
Francesco Abbate | 83607aec4a | |
Francesco Abbate | d9afc40a17 | |
Guldoman | aa0e083cb9 | |
Francesco Abbate | 4964c30e12 | |
Guldoman | 17185075c6 | |
Francesco Abbate | 04250a206a | |
Francesco Abbate | 403b7f6fb6 | |
Francesco Abbate | b440a22581 | |
Francesco Abbate | fa8b3b33b1 | |
Francesco Abbate | 4bcc1cc07c | |
Francesco Abbate | f85fe102d9 | |
Francesco Abbate | dfb64fbdf1 | |
takase1121 | 7a24dbb17e | |
takase1121 | 1a21c66353 | |
takase1121 | 5cc23348a1 | |
takase1121 | 7e4236a82f | |
Francesco Abbate | 51f27e47ca | |
Francesco Abbate | ce1a6ea5b5 | |
Francesco Abbate | 16170e8db9 | |
Guldoman | 0a52861129 | |
Francesco Abbate | 48c709a95f | |
Guldoman | 0a36e66aba | |
Guldoman | 474952645c | |
Francesco Abbate | 2b277bb502 | |
Francesco Abbate | 67d7b894ae | |
Francesco Abbate | 90c721b823 | |
redtide | aa9e2e2df5 | |
Francesco Abbate | b4080ba148 | |
redtide | 261ab5daf2 | |
Francesco Abbate | 91da2ddfdd | |
Francesco Abbate | cc20849afd | |
Timofffee | 9246a16e67 | |
Francesco | de038a633d | |
Guldoman | f9c7eeeeb8 | |
Francesco Abbate | 8d355bd3a0 | |
Guldoman | 3278eebdad | |
Guldoman | 9887dd4746 | |
Guldoman | 59aa7f0090 | |
redtide | 11521ff883 | |
Guldoman | de4072e207 | |
Adam Harrison | b7f2d1ad03 | |
Adam Harrison | 7811660caf | |
redtide | a75aca2538 | |
Timofffee | 604626fa32 | |
Adam Harrison | 1d61cf989f | |
Adam Harrison | d352eb1cb9 | |
Adam Harrison | 9d4e944549 | |
Francesco Abbate | 68c1cc606f | |
Francesco Abbate | 8dde1dbb86 | |
Zack A | 97ca19c73f | |
boppyt | e34ec195b4 | |
boppyt | ea795aa411 | |
Guldoman | e93bbc559c | |
Guldoman | d3bd35b577 | |
Guldoman | 7f338fc993 | |
Francesco Abbate | e23b6176f4 | |
Jan200101 | fc4c7a29ee | |
Takase | fb907c9bf4 | |
takase1121 | 97493a1a4e | |
takase1121 | 30d3751632 | |
takase1121 | 14565b5226 | |
takase1121 | e25ea1196a | |
takase1121 | 622b162225 | |
takase1121 | afaf0a718d | |
Francesco Abbate | 28e8a98ffc | |
Francesco Abbate | 368ffca40a | |
Francesco Abbate | 3cc4cd1ada | |
Guldoman | 444c929e3c | |
Guldoman | 0d2a89cb76 | |
Guldoman | 501be5fdfe | |
Guldoman | e99c76f8b8 | |
Francesco Abbate | 4f68f7fd92 | |
redtide | df667ad28e | |
Francesco Abbate | f69e42a43c | |
redtide | 38d85f2483 | |
Francesco Abbate | 9e5d404b29 | |
Francesco Abbate | 4e6f4d5c39 | |
Timofffee | d42a9173fe | |
Francesco | 4732ba743d | |
Francesco | dfc45ad3b3 | |
Francesco | fdc8762241 | |
Francesco | 9e5df4e660 | |
Guldoman | 371914af67 | |
Francesco Abbate | a134c8ea99 | |
Timofffee | 8866a5dddf | |
Timofffee | e8f5a5e002 | |
Timofffee | aefa3ca205 | |
Adam | a0508103b1 | |
Guldoman | e559afaefc | |
Guldoman | f18629ab64 | |
Guldoman | 167cda23f6 | |
Guldoman | 46d1203d08 | |
redtide | fb45b27da5 | |
Guldoman | f31312fd16 | |
Guldoman | a920e5b0e6 | |
Adam | 06b64f2928 | |
Adam Harrison | e541236c22 | |
Adam | 49bee555fa | |
redtide | df8c1a98e4 | |
Adam Harrison | 1c4a4e763e | |
Adam | 8d6ac47cd0 | |
Guldoman | 235b1f0385 | |
Guldoman | 4d0656ad7e | |
Guldoman | 92bbb30d06 | |
Guldoman | 4a03aec073 | |
Guldoman | 2d33fdc656 | |
Guldoman | 5c7b133e0b | |
Guldoman | f106993d0e | |
Guldoman | 3e6afeccc0 | |
Guldoman | c16145d562 | |
Guldoman | f1ca00fbed | |
Guldoman | e52362e55f | |
Adam | 3eb6f1dbd4 | |
Francesco Abbate | e94718c5c4 | |
Francesco Abbate | dcf84c743d | |
Timofffee | 6e5452844c | |
Zack A | e9246bcb56 | |
boppyt | 3b35c86c96 | |
boppyt | 08e4e5275f | |
Adam Harrison | 4ae16615e8 | |
Adam Harrison | bbe4e21f52 | |
Guldoman | ab6eac399c | |
Guldoman | 76334a7946 | |
takase1121 | 30ccde896d | |
Adam Harrison | 58f4963ade | |
Francesco Abbate | ccba91261d | |
Francesco Abbate | f1c004411c | |
Francesco Abbate | eeac85d4b4 | |
Daniel Rocha | 2b1c157a36 | |
Daniel Rocha | 49ec7c88e8 | |
Daniel Rocha | dac3a9cba5 | |
Guldoman | 5e80149295 | |
Daniel Rocha | 95e86b040b | |
Daniel Rocha | 8335b11273 | |
Daniel Rocha | e342a017e1 | |
Guldoman | 07c23fbf17 | |
Francesco Abbate | 06252382ec | |
Francesco Abbate | 4f8de02bcf | |
Francesco Abbate | d46475532f | |
Francesco Abbate | 9592ce85f5 | |
Guldoman | c7d044f178 | |
Francesco Abbate | a8f4c0c4e5 | |
Francesco Abbate | bb6b99b167 | |
Francesco Abbate | 7f4d9789d6 | |
Francesco Abbate | 456f6eda65 | |
Francesco Abbate | daf916769f | |
Francesco Abbate | dc501cb41a | |
Francesco Abbate | 609795701d | |
Jan200101 | 973acb787a | |
Takase | 816ceb4493 | |
takase1121 | cf7ebdad1f | |
takase1121 | 1d9f04e7d6 | |
takase1121 | 6ac1428b51 | |
takase1121 | 2fec3052ce | |
takase1121 | 7c3daa0f39 | |
takase1121 | cb639700b3 | |
Francesco Abbate | b76917ef9e | |
Francesco Abbate | c9669410ad | |
Francesco Abbate | 717f4eb782 | |
Francesco Abbate | d9e73a97ea | |
Francesco Abbate | e0722448a3 | |
Nikolai Sinyov | 1687bbd92d | |
harens | 2d088256b1 | |
redtide | dd7c345fd9 | |
Adam Harrison | b6af395fc7 | |
takase1121 | 419cd58c8f | |
takase1121 | 35fd29fc39 | |
redtide | 02d59c8ec2 | |
Francesco Abbate | 7ffe1b49d7 | |
Adam | 2ea62eee8f | |
Adam | 9e45b1de58 | |
Adam Harrison | 5f1e68b824 | |
Adam Harrison | 8e9c410d27 | |
redtide | 48ab8c9836 | |
redtide | 904214378d | |
Adam | 37dcc4725f | |
Adam | db3e9cb914 | |
Adam | f3e750ccb4 | |
Adam | d9fc6d407b | |
Francesco Abbate | 8c86cc51b0 | |
Francesco Abbate | 3396a6c802 | |
redtide | 2fdde9cc99 | |
Adam Harrison | e2a7578553 | |
Adam Harrison | 5e66f74f38 | |
Adam Harrison | acd122bc34 | |
Adam Harrison | aa4d91a03f | |
luarocks | 205b52b08a | |
luarocks | 0bafece6a6 | |
Adam Harrison | c644ca7df6 | |
Adam Harrison | 40c68ffcc6 | |
Adam Harrison | 851dc07408 | |
Adam Harrison | 26a77542e3 | |
Adam Harrison | 255c45b30b | |
Adam Harrison | 71e62ce84f | |
Adam | f3a8e264fe | |
Francesco Abbate | c552d373ca | |
redtide | 6955f87aaf | |
redtide | 0f9fa8044b | |
redtide | 3468164518 | |
Adam Harrison | 3c8da0fc3f | |
Adam Harrison | 2bf56e67c5 | |
Francesco Abbate | 0b2bf227a8 | |
redtide | cee1639d34 | |
redtide | f1f3eb1185 | |
Francesco Abbate | 62bcc6abc2 | |
takase1121 | 1725b3ab83 | |
takase1121 | 3b3677ca4b | |
Adam | 5155f7a2a4 | |
Adam | 47eaca18d8 | |
Francesco Abbate | 135ad072bd | |
cukmekerb | 4ad353eb4b | |
cukmekerb | 63f406773b | |
Francesco Abbate | 8103f21991 | |
ep | af22a6a824 | |
ep | 2df363747b | |
Adam | 0a0bc87319 | |
Adam Harrison | 152fd6c66c | |
Adam Harrison | c461cfae93 | |
Adam Harrison | 0777a6f0b8 | |
Francesco Abbate | 7605b626e8 | |
Francesco Abbate | d3f1a3a5b2 | |
Adam | 69da9655d1 | |
Adam | 7d40458489 | |
Adam Harrison | e144ad3271 | |
Adam Harrison | 6330f4d596 | |
Adam Harrison | a218a95c45 | |
Adam Harrison | 0dda252096 | |
takase1121 | 6bcdaa9d7a | |
takase1121 | 169b8abae5 | |
takase1121 | 4ef707e941 | |
takase1121 | c7bbf221ee | |
takase1121 | 192a93014d | |
takase1121 | 818e21610c | |
takase1121 | de3013ce88 | |
takase1121 | e7b025203b | |
takase1121 | 8bbb26a469 | |
takase1121 | c41747c8fb | |
takase1121 | f4f33bd36b | |
redtide | 6e460a20ac | |
Francesco Abbate | 265501bb9e | |
Francesco Abbate | e1530c0951 | |
Adam | 0426fc26c2 | |
Adam Harrison | 423cd33810 | |
Adam Harrison | e539310e6d | |
Adam | d10865bcc4 | |
redtide | 0ed707c68f | |
jgmdev | afa0c175e8 | |
jgmdev | a4d5622eda | |
Francesco Abbate | ad0530dafa | |
Francesco Abbate | b3b99b9dcd | |
jgmdev | 900e9d1422 | |
Adam | e1ae94a01b | |
Adam | 1a87d0e4fd | |
lqdev | e5b2a7cbe8 | |
Adam | f29b6d1cc8 | |
Adam | 9126b5e64d | |
jgmdev | 68459a9199 | |
jgmdev | 4188269cef | |
Adam Harrison | c2c59e9c99 | |
Adam | 4d4c49e36e | |
Adam Harrison | f899848631 | |
Adam Harrison | fd3f25608f | |
redtide | df63775dca | |
jgmdev | 18eee34aa9 | |
jgmdev | bd50401687 | |
Adam Harrison | cc568e65fc | |
redtide | 36ff3b1c76 | |
redtide | 3f58e554ba | |
Adam | 83a604dfb7 | |
redtide | 8c71b35888 | |
jgmdev | 88704c6ecb | |
Francesco Abbate | 98663461bb | |
Ulhar | a2cf0019ff | |
Adam Harrison | f9edca712d | |
Francesco Abbate | 1cf0f2009c | |
Adam Harrison | 8b2fb67b9b | |
Adam Harrison | 46e939a3e6 | |
Adam Harrison | 305921299f | |
Francesco Abbate | b086db24e8 | |
Francesco Abbate | ee404965a1 | |
Adam | 2486f253eb | |
Jefferson González | 559be66e80 | |
jgmdev | 1a51dad23c | |
Adam Harrison | bd02095a13 | |
Adam | 4931110208 | |
Adam Harrison | 704e04396f | |
Adam Harrison | 292c98935c | |
Adam Harrison | 3541ab4aa1 | |
Adam Harrison | 0210264552 | |
Adam Harrison | b5cbe3a2fb | |
Adam Harrison | dfc57bd884 | |
Adam Harrison | 6915d86d59 | |
Adam Harrison | b065b52067 | |
Adam Harrison | 0f229b039d | |
Adam Harrison | c494d52caf | |
Adam Harrison | d3b3f26316 | |
Adam Harrison | c6f7e473f0 | |
Adam Harrison | 858f7a2a50 | |
Adam Harrison | 75658b4f3f | |
Adam Harrison | 2475b1624f | |
Adam Harrison | b42708fe56 | |
Adam Harrison | 316671e5b7 | |
Adam Harrison | 6c0d124410 | |
Adam Harrison | 08ab6cba05 | |
Adam Harrison | a7f39017ff | |
Adam Harrison | 93670a314d | |
Adam Harrison | 37a3884ee2 | |
Jefferson González | 0d65725b27 | |
jgmdev | 5d2734de81 | |
lqdev | 6195b246a5 | |
Adam | e4fd3afa88 | |
Francesco Abbate | cfd3bebfcc | |
cukmekerb | e493fa1b0a | |
cukmekerb | e1d85af69b | |
Cukmekerb | eed5b79030 | |
jgmdev | e9e1214e59 | |
Adam | d45ec645d3 | |
Francesco Abbate | 66275fe207 | |
Adam Harrison | 4fc910dbdb | |
Francesco Abbate | 2af92e9af1 | |
Francesco Abbate | 704a8dea09 | |
Björn Buckwalter | 212e5e326c | |
Björn Buckwalter | 3fe8b135af | |
Francesco Abbate | 1ea28eb38b | |
Francesco Abbate | ab9960cddf | |
Francesco Abbate | f682215b41 | |
Ferdinand Prantl | 2365dfa9c0 | |
Ferdinand Prantl | d941535600 | |
Francesco Abbate | 73bda963a5 | |
Adam | 5151e36981 | |
Adam | bdc37f1f6c | |
Francesco Abbate | 1ad4289e76 | |
Francesco Abbate | 4c9083398a | |
Francesco Abbate | b39db791f9 | |
Adam Harrison | 2fc245eb69 | |
Adam Harrison | aed643893e | |
jgmdev | e070dbebc1 | |
Francesco | 98164f6d4f | |
Jefferson González | 9823da8531 | |
Adam | 804429e3b6 | |
Adam | 637b7f952d | |
Adam | 6097ba36d7 | |
Adam Harrison | 66b76f15c2 | |
Adam Harrison | a18eeafbc8 | |
Adam Harrison | f729d7d008 | |
Jan200101 | 7ea096247c | |
Francesco Abbate | 0f2ac136d0 | |
Francesco Abbate | 752ecd5ece | |
jgmdev | 9153f5695d | |
Adam Harrison | 214e6898df | |
Adam Harrison | aa9f16c74c | |
Björn Buckwalter | 702ab2625c | |
Adam Harrison | 29837d0c41 | |
Adam | 7de1fde9cf | |
Björn Buckwalter | ef48d5d48c | |
Francesco Abbate | e7d0709828 | |
Jefferson González | b4896ed69e | |
Takase | 0fd1fa8872 | |
Takase | 130b29438a | |
Adam Harrison | 2170ab9649 | |
Adam Harrison | 090d67f252 | |
Adam Harrison | 3b816a2b4a | |
takase1121 | cd5c64fe8c | |
takase1121 | 794cf3813a | |
Francesco Abbate | c5acd030a1 | |
Francesco Abbate | 29e25a7605 | |
Francesco | b046afccf9 | |
Ferdinand Prantl | d0adb748a6 | |
Ferdinand Prantl | 1eabf99054 | |
Tommi Jalkanen | f23419994d | |
Adam | a128790112 | |
redtide | 8c1a25100c | |
Adam | 248d70a8ca | |
lqdev | ea5e9b0ce5 | |
Francesco Abbate | 4e93eabbac | |
Francesco Abbate | 6d044224c1 | |
Francesco Abbate | f7e3e41ab1 | |
redtide | 818a7abb0a | |
Adam | 4ffb5e3672 | |
Francesco Abbate | 34e38dd04a | |
Francesco Abbate | ee25e3c5f4 | |
Francesco Abbate | 0a55b246b5 | |
Francesco Abbate | ad7d17caca | |
Francesco Abbate | fe828b6ed9 | |
Francesco Abbate | 934f12cded | |
Janis-Leuenberger | 1394c53dbc | |
Adam Harrison | f1a4bf8218 | |
liquidev | cb610055d0 | |
Francesco Abbate | 10fde6e264 | |
Adam Harrison | 8acb3fae8c | |
Francesco Abbate | f17f5a4d6d | |
Francesco | 3634c212a9 | |
Francesco Abbate | 739763675e | |
Francesco Abbate | 81e8c8a223 | |
Francesco Abbate | df3e1157d0 | |
Francesco Abbate | bbc3ea4104 | |
Francesco Abbate | 217360ed31 | |
Adam | a254d393db | |
Alexandr "Nil" Shchelov | 04c7a49d00 | |
Francesco Abbate | abc69a7a19 | |
Adam | 50f6ebe8ee | |
Francesco Abbate | aa0b2bb5fc | |
Adam Harrison | 3a5f04fae5 | |
Adam Harrison | 18fe6576d8 | |
Adam | 949692860e | |
liquidev | 78999cabe2 | |
Janis-Leuenberger | b15914d664 | |
Francesco Abbate | a02691f18e | |
Adam | b278306fc9 | |
liquidev | 86a7037ed9 | |
Francesco Abbate | a92d15fe30 | |
lqdev | ba4fbde33d | |
Francesco Abbate | ca05562cc0 | |
Francesco Abbate | 1fb01f36df | |
Francesco Abbate | 13529c28d4 | |
Francesco Abbate | ba40bc0dfc | |
Francesco Abbate | 92322986b8 | |
Adam | 6e08c3321c | |
Adam | e43f1b9df9 | |
Adam | 85d0d684de | |
Adam | e54ffc49ea | |
Cukmekerb | bd4efa14a2 | |
cukmekerb | 27aa162148 | |
Adam | c7b1a6f53e | |
Francesco Abbate | 9c9fbe4c8b | |
Francesco Abbate | b37c190db2 | |
Francesco Abbate | 90e41daa99 | |
Francesco Abbate | b223dbca6f | |
Francesco Abbate | f30dfc20fe | |
Francesco Abbate | 50a4fc212f | |
Francesco Abbate | e2fcc41b4a | |
Francesco Abbate | e58d7600ee | |
tsukanov-as | f637dc4db8 | |
Francesco Abbate | 55a6888818 | |
Francesco Abbate | f1621192f9 | |
Francesco Abbate | d9a7d9e5ae | |
Francesco Abbate | 4c99a18341 | |
Francesco Abbate | 5cef643e02 | |
Adam | 135dfa6f03 | |
Francesco Abbate | 9f7f55926b | |
Francesco Abbate | d63afe02ed | |
Francesco Abbate | d8244120e9 | |
Francesco Abbate | 820d520fc3 | |
Adam | 4c42dd4adc | |
Francesco | fa99d5401e | |
Adam | 67ec7e8065 | |
Francesco Abbate | b42d9de0be | |
Francesco | fcbb424f16 | |
Francesco | c2775b496c | |
Adam | e4da235f7e | |
Adam | dd604c1336 | |
Adam Harrison | b69242312d | |
Francesco Abbate | 1a05e00fdd | |
Francesco Abbate | e6d88909a8 | |
Francesco Abbate | 5766329313 | |
Francesco Abbate | 857807b23a | |
adamharrison | 3fe6665b9a | |
Francesco Abbate | a72431ace7 | |
Francesco Abbate | 3d84fe5488 | |
Francesco Abbate | cec302c04e | |
Francesco Abbate | 7c79105d2f | |
Francesco Abbate | 9486940082 | |
Francesco Abbate | 67f431c69c | |
Francesco Abbate | 0fe8415bb4 | |
Francesco Abbate | 8b9fbecd74 | |
Francesco Abbate | f2a33a567b | |
Francesco Abbate | 46c3bdea67 | |
adamharrison | 2e7817f609 | |
lqdev | c859498d68 | |
lqdev | 34e94bb8aa | |
lqdev | 064fc35c77 | |
lqdev | 5e8e2f54b5 | |
Francesco Abbate | 57e6de978b | |
Francesco Abbate | 18bcfa7e54 | |
Francesco Abbate | 33fe7295c0 | |
Francesco Abbate | 685b8c82d0 | |
Francesco | 58422271ce |
|
@ -0,0 +1,11 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[meson.build]
|
||||
indent_size = 4
|
|
@ -1,3 +1,7 @@
|
|||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
# See https://help.github.com/en/articles/dealing-with-line-endings
|
||||
* text=auto
|
||||
|
||||
winlib/* linguist-vendored
|
||||
src/lib/* linguist-vendored
|
||||
icon.inl linguist-vendored
|
||||
resources/icons/icon.inl linguist-vendored
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
"Category: CI":
|
||||
- .github/workflows/*
|
||||
|
||||
"Category: Meta":
|
||||
- ./*
|
||||
- .github/*
|
||||
- .github/ISSUE_TEMPLATE/*
|
||||
- .github/PULL_REQUEST_TEMPLATE/*
|
||||
- .gitignore
|
||||
|
||||
"Category: Build System":
|
||||
- meson.build
|
||||
- meson_options.txt
|
||||
- subprojects/*
|
||||
|
||||
"Category: Documentation":
|
||||
- docs/**/*
|
||||
|
||||
"Category: Resources":
|
||||
- resources/**/*
|
||||
|
||||
"Category: Themes":
|
||||
- data/colors/*
|
||||
|
||||
"Category: Lua Core":
|
||||
- data/core/**/*
|
||||
|
||||
"Category: Fonts":
|
||||
- data/fonts/*
|
||||
|
||||
"Category: Plugins":
|
||||
- data/plugins/*
|
||||
|
||||
"Category: C Core":
|
||||
- src/**/*
|
|
@ -0,0 +1,16 @@
|
|||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply Type Label
|
||||
uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: "" # works around actions/labeler#104
|
|
@ -1,18 +1,44 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the master branch
|
||||
push:
|
||||
branches: [ master gh-actions ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
# All builds use lhelper only for releases,
|
||||
# otherwise for normal builds dependencies are dynamically linked.
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
# tags:
|
||||
# - 'v[0-9]*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
name: Build Linux
|
||||
archive_source_code:
|
||||
name: Source Code Tarball
|
||||
runs-on: ubuntu-18.04
|
||||
# Only on tags/releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get install -qq ninja-build
|
||||
pip3 install meson
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: bash scripts/package.sh --version ${GITHUB_REF##*/} --debug --source
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Source Code Tarball
|
||||
path: "lite-xl-*-src.tar.gz"
|
||||
|
||||
build_linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-18.04
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -23,49 +49,204 @@ jobs:
|
|||
CC: ${{ matrix.config.cc }}
|
||||
CXX: ${{ matrix.config.cxx }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -qq libsdl2-dev libfreetype6 ninja-build
|
||||
pip3 install meson
|
||||
- name: Build package
|
||||
run: bash build-packages.sh x86-64
|
||||
- name: upload packages
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Ubuntu Package
|
||||
path: lite-xl-linux-*.tar.gz
|
||||
- name: Set Environment Variables
|
||||
if: ${{ matrix.config.cc == 'gcc' }}
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Update Packages
|
||||
run: sudo apt-get update
|
||||
- name: Install Dependencies
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
run: bash scripts/install-dependencies.sh --debug
|
||||
- name: Install Release Dependencies
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: |
|
||||
bash scripts/install-dependencies.sh --debug --lhelper
|
||||
bash scripts/lhelper.sh --debug
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --debug --forcefallback
|
||||
- name: Package
|
||||
if: ${{ matrix.config.cc == 'gcc' }}
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary
|
||||
- name: AppImage
|
||||
if: ${{ matrix.config.cc == 'gcc' && startsWith(github.ref, 'refs/tags/') }}
|
||||
run: bash scripts/appimage.sh --nobuild --version ${INSTALL_REF}
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.config.cc == 'gcc' }}
|
||||
with:
|
||||
name: Linux Artifacts
|
||||
path: |
|
||||
${{ env.INSTALL_NAME }}.tar.gz
|
||||
LiteXL-${{ env.INSTALL_REF }}-x86_64.AppImage
|
||||
|
||||
build-macox:
|
||||
name: Build Mac OS X
|
||||
build_macos:
|
||||
name: macOS (x86_64)
|
||||
runs-on: macos-10.15
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
steps:
|
||||
- name: System Information
|
||||
run: |
|
||||
system_profiler SPSoftwareDataType
|
||||
bash --version
|
||||
gcc -v
|
||||
xcodebuild -version
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Dependencies
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
run: bash scripts/install-dependencies.sh --debug
|
||||
- name: Install Release Dependencies
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: |
|
||||
bash scripts/install-dependencies.sh --debug --lhelper
|
||||
bash scripts/lhelper.sh --debug
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --bundle --debug --forcefallback
|
||||
- name: Error Logs
|
||||
if: failure()
|
||||
run: |
|
||||
mkdir ${INSTALL_NAME}
|
||||
cp /usr/var/lhenv/lite-xl/logs/* ${INSTALL_NAME}
|
||||
tar czvf ${INSTALL_NAME}.tar.gz ${INSTALL_NAME}
|
||||
# - name: Package
|
||||
# if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
# run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons
|
||||
- name: Create DMG Image
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg
|
||||
- name: Upload DMG Image
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: macOS DMG Image
|
||||
path: ${{ env.INSTALL_NAME }}.dmg
|
||||
- name: Upload Error Logs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: Error Logs
|
||||
path: ${{ env.INSTALL_NAME }}.tar.gz
|
||||
|
||||
build_windows_msys2:
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- { name: "GCC", cc: gcc-10, cxx: g++-10 }
|
||||
- { name: "clang", cc: clang, cxx: clang++ }
|
||||
env:
|
||||
CC: ${{ matrix.config.cc }}
|
||||
CXX: ${{ matrix.config.cxx }}
|
||||
msystem: [MINGW32, MINGW64]
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip3 install meson
|
||||
brew install ninja sdl2
|
||||
- name: Build package
|
||||
run: bash build-packages.sh x86-64
|
||||
- name: upload packages
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Mac OS X Package
|
||||
path: lite-xl-macosx-*.zip
|
||||
- uses: actions/checkout@v2
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
#msystem: MINGW64
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
zip
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-$(uname -m)" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||
- name: Install Dependencies
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
run: bash scripts/install-dependencies.sh --debug
|
||||
- name: Install Release Dependencies
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: bash scripts/install-dependencies.sh --debug --lhelper
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --debug --forcefallback
|
||||
- name: Error Logs
|
||||
if: failure()
|
||||
run: |
|
||||
mkdir ${INSTALL_NAME}
|
||||
cp /usr/var/lhenv/lite-xl/logs/* ${INSTALL_NAME}
|
||||
tar czvf ${INSTALL_NAME}.tar.gz ${INSTALL_NAME}
|
||||
- name: Package
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary
|
||||
- name: Build Installer
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: bash scripts/innosetup/innosetup.sh --debug
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Windows Artifacts
|
||||
path: |
|
||||
LiteXL*.exe
|
||||
${{ env.INSTALL_NAME }}.zip
|
||||
- name: Upload Error Logs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: Error Logs
|
||||
path: ${{ env.INSTALL_NAME }}.tar.gz
|
||||
|
||||
deploy:
|
||||
name: Deployment
|
||||
runs-on: ubuntu-18.04
|
||||
# if: startsWith(github.ref, 'refs/tags/')
|
||||
if: false
|
||||
needs:
|
||||
- archive_source_code
|
||||
- build_linux
|
||||
- build_macos
|
||||
- build_windows_msys2
|
||||
steps:
|
||||
- name: Set Environment Variables
|
||||
run: echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: Linux Artifacts
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: macOS DMG Image
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: Source Code Tarball
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: Windows Artifacts
|
||||
- name: Display File Information
|
||||
shell: bash
|
||||
run: ls -lR
|
||||
# Note: not using `actions/create-release@v1`
|
||||
# because it cannot update an existing release
|
||||
# see https://github.com/actions/create-release/issues/29
|
||||
- uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.INSTALL_REF }}
|
||||
name: Release ${{ env.INSTALL_REF }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: |
|
||||
lite-xl-${{ env.INSTALL_REF }}-*
|
||||
LiteXL*.AppImage
|
||||
LiteXL*.exe
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
build*
|
||||
.build*
|
||||
.run*
|
||||
*.zip
|
||||
build*/
|
||||
.build*/
|
||||
lhelper/
|
||||
submodules/
|
||||
subprojects/lua/
|
||||
subprojects/reproc/
|
||||
/appimage*
|
||||
.ccls-cache
|
||||
.lite-debug.log
|
||||
subprojects/lua
|
||||
subprojects/libagg
|
||||
sybprojects/lua
|
||||
.run*
|
||||
*.diff
|
||||
*.exe
|
||||
*.tar.gz
|
||||
*.zip
|
||||
*.DS_Store
|
||||
*App*
|
||||
compile_commands.json
|
||||
error.txt
|
||||
lite-xl*
|
||||
LiteXL*
|
||||
lite
|
||||
.config/
|
||||
*.lha
|
||||
release_files
|
||||
*.o
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2020 rxi
|
||||
Copyright (c) 2020-2021 Francesco Abbate
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
#
|
||||
# Project: Lite XL
|
||||
#
|
||||
# Created on: 26-12-2021
|
||||
#
|
||||
|
||||
LiteXL_OBJ := \
|
||||
src/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
|
||||
src/renwindow.o src/api/api.o src/api/regex.o \
|
||||
src/api/renderer.o src/api/system.o src/platform/amigaos4.o
|
||||
|
||||
|
||||
outfile := lite
|
||||
compiler := gcc
|
||||
cxxcompiler := g++
|
||||
|
||||
INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 -I/sdk/local/common/include/freetype2
|
||||
DFLAGS := -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
|
||||
# -DLITE_USE_SDL_RENDERER
|
||||
# -Wextra -Wall
|
||||
CFLAGS := -Werror -Wwrite-strings -O3 -g -std=gnu11 -fno-strict-aliasing
|
||||
# "-gstabs -finstrument-functions -fno-inline -DPROFILING"
|
||||
LFLAGS := -mcrt=newlib -static-libgcc -static-libstdc++ -lauto -lpcre2 -lSDL2 -llua -lagg -lfreetype -lm -lunix -lpthread -athread=native
|
||||
# " -lprofyle"
|
||||
|
||||
|
||||
|
||||
.PHONY: LiteXL clean release
|
||||
|
||||
default: LiteXL
|
||||
|
||||
clean:
|
||||
@echo "Cleaning compiler objects..."
|
||||
@rm -f $(LiteXL_OBJ)
|
||||
|
||||
LiteXL: $(LiteXL_OBJ)
|
||||
@echo "Linking LiteXL"
|
||||
@$(cxxcompiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
|
||||
|
||||
|
||||
|
||||
.c.o:
|
||||
@echo "Compiling $<"
|
||||
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
|
||||
|
||||
src/dirmonitor.o: src/dirmonitor.c src/platform/amigaos4.h
|
||||
|
||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
||||
src/renderer.h src/platform/amigaos4.h src/dirmonitor.h
|
||||
|
||||
src/rencache.o: src/rencache.c
|
||||
|
||||
src/renderer.o: src/renderer.c
|
||||
|
||||
src/renwindow.o: src/renwindow.c
|
||||
|
||||
src/api/api.o: src/api/api.c
|
||||
|
||||
src/api/regex.o: src/api/regex.c
|
||||
|
||||
src/api/renderer.o: src/api/renderer.c
|
||||
|
||||
src/api/system.o: src/api/system.c
|
||||
|
||||
src/platform/amigaos4.o: src/platform/amigaos4.c
|
||||
|
||||
|
||||
|
||||
|
||||
release:
|
||||
mkdir -p release/LiteXL2
|
||||
cp release_files/* release/LiteXL2/ -r
|
||||
mv release/LiteXL2/LiteXL2.info release/
|
||||
cp data release/LiteXL2/ -r
|
||||
cp changelog.md release/LiteXL2/
|
||||
cp lite release/LiteXL2/
|
||||
strip release/LiteXL2/lite
|
||||
cp README.md release/LiteXL2/
|
||||
cp README_OS4.md release/LiteXL2/
|
||||
cp LICENSE release/LiteXL2/
|
||||
lha -aeqr3 a LiteXL2_OS4.lha release/
|
||||
|
194
README.md
194
README.md
|
@ -1,125 +1,159 @@
|
|||
![build](https://github.com/franko/lite-xl/actions/workflows/build.yml/badge.svg)
|
||||
# Lite XL
|
||||
|
||||
![screenshot-dark](https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png)
|
||||
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
|
||||
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
||||
|
||||
A lightweight text editor written in Lua, adapted from [lite](https://github.com/rxi/lite)
|
||||
![screenshot-dark]
|
||||
|
||||
* **[Get Lite XL](https://github.com/franko/lite-xl/releases/latest)** — Download
|
||||
for Windows and Linux
|
||||
* **[Get started](doc/usage.md)** — A quick overview on how to get started
|
||||
* **[Get plugins](https://github.com/franko/lite-plugins)** — Add additional
|
||||
functionality, adapted for Lite XL
|
||||
* **[Get color themes](https://github.com/rxi/lite-colors)** — Add additional colors
|
||||
themes
|
||||
A lightweight text editor written in Lua, adapted from [lite].
|
||||
|
||||
* **[Get Lite XL]** — Download for Windows, Linux and Mac OS.
|
||||
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
||||
* **[Get color themes]** — Add additional colors themes.
|
||||
|
||||
Please refer to our [website] for the user and developer documentation,
|
||||
including [build] instructions details. A quick build guide is described below.
|
||||
|
||||
Lite XL has support for high DPI display on Windows and Linux and,
|
||||
since 1.16.7 release, it supports **retina displays** on macOS.
|
||||
|
||||
Please note that Lite XL is compatible with lite for most plugins and all color themes.
|
||||
We provide a separate lite-plugins repository for Lite XL, because in some cases some adaptations may be needed to make them work better with Lite XL.
|
||||
The repository with modified plugins is http://github.com/franko/lite-plugins.
|
||||
We provide a separate lite-xl-plugins repository for Lite XL, because in some cases
|
||||
some adaptations may be needed to make them work better with Lite XL.
|
||||
The repository with modified plugins is https://github.com/lite-xl/lite-xl-plugins.
|
||||
|
||||
The changes and differences between Lite XL and rxi/lite are listed in the [changelog](https://github.com/franko/lite-xl/blob/master/changelog.md).
|
||||
The changes and differences between Lite XL and rxi/lite are listed in the
|
||||
[changelog].
|
||||
|
||||
## Overview
|
||||
Lite XL is derived from lite. It is a lightweight text editor written mostly in Lua — it aims to provide
|
||||
something practical, pretty, *small* and fast easy to modify and extend, or to use without doing either.
|
||||
|
||||
The aim of Lite XL compared to lite is to be more user friendly, improve the quality of font rendering, and reduce CPU usage.
|
||||
Lite XL is derived from lite.
|
||||
It is a lightweight text editor written mostly in Lua — it aims to provide
|
||||
something practical, pretty, *small* and fast easy to modify and extend,
|
||||
or to use without doing either.
|
||||
|
||||
The aim of Lite XL compared to lite is to be more user friendly,
|
||||
improve the quality of font rendering, and reduce CPU usage.
|
||||
|
||||
## Customization
|
||||
Additional functionality can be added through plugins which are available in
|
||||
the [plugins repository](https://github.com/rxi/lite-plugins) or in the [Lite XL-specific plugins repository](https://github.com/franko/lite-plugins).
|
||||
|
||||
Additional color themes can be found in the [colors repository](https://github.com/rxi/lite-colors).
|
||||
Additional functionality can be added through plugins which are available in
|
||||
the [plugins repository] or in the [Lite XL plugins repository].
|
||||
|
||||
Additional color themes can be found in the [colors repository].
|
||||
These color themes are bundled with all releases of Lite XL by default.
|
||||
|
||||
The editor can be customized by making changes to the [user module](data/user/init.lua).
|
||||
## Quick Build Guide
|
||||
|
||||
## Building
|
||||
|
||||
You can build Lite XL yourself using Meson.
|
||||
|
||||
In addition, the `build-packages.sh` script can be used to compile Lite XL and create an OS-specific package for Linux, Windows or Mac OS.
|
||||
|
||||
The following libraries are required:
|
||||
|
||||
- freetype2
|
||||
- SDL2
|
||||
|
||||
The following libraries are optional:
|
||||
|
||||
- libagg
|
||||
- Lua 5.2
|
||||
|
||||
If they are not found, they will be downloaded and compiled by Meson.
|
||||
Otherwise, if they are present, they will be used to compile Lite XL.
|
||||
|
||||
On Debian-based systems the required libraries and Meson can be installed using the following commands:
|
||||
If you compile Lite XL yourself, it is recommended to use the script
|
||||
`build-packages.sh`:
|
||||
|
||||
```sh
|
||||
# To install the required libraries:
|
||||
sudo apt install libfreetype6-dev libsdl2-dev
|
||||
|
||||
# To install Meson:
|
||||
sudo apt install meson
|
||||
# or pip3 install --user meson
|
||||
bash build-packages.sh -h
|
||||
```
|
||||
|
||||
To build Lite XL with Meson the commands below can be used:
|
||||
The script will run Meson and create a tar compressed archive with the application or,
|
||||
for Windows, a zip file. Lite XL can be easily installed
|
||||
by unpacking the archive in any directory of your choice.
|
||||
|
||||
Otherwise the following is an example of basic commands if you want to customize
|
||||
the build:
|
||||
|
||||
```sh
|
||||
meson setup --buildtype=release build
|
||||
meson setup --buildtype=release --prefix <prefix> build
|
||||
meson compile -C build
|
||||
meson install -C build
|
||||
DESTDIR="$(pwd)/lite-xl" meson install --skip-subprojects -C build
|
||||
```
|
||||
|
||||
If you are using a version of Meson below 0.54 you need to use diffent commands to compile and install:
|
||||
where `<prefix>` might be one of `/`, `/usr` or `/opt`, the default is `/`.
|
||||
To build a bundle application on macOS:
|
||||
|
||||
```sh
|
||||
meson setup --buildtype=release build
|
||||
ninja -C build
|
||||
ninja -C build install
|
||||
meson setup --buildtype=release --Dbundle=true --prefix / build
|
||||
meson compile -C build
|
||||
DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
|
||||
```
|
||||
|
||||
When performing the `meson setup` command you may enable the `-Dportable=true` option to specify whether files should be installed as in a portable application.
|
||||
Please note that the package is relocatable to any prefix and the option prefix
|
||||
affects only the place where the application is actually installed.
|
||||
|
||||
If `portable` is enabled, Lite XL is built to use a `data` directory placed next to the executable.
|
||||
Otherwise, Lite XL will use unix-like directory locations.
|
||||
In this case, the `data` directory will be `$prefix/share/lite-xl` and the executable will be located in `$prefix/bin`.
|
||||
`$prefix` is determined when the application starts as a directory such that `$prefix/bin` corresponds to the location of the executable.
|
||||
## Installing Prebuilt
|
||||
|
||||
The `user` directory does not depend on the `portable` option and will always be `$HOME/.config/lite-xl`.
|
||||
`$HOME` is determined from the corresponding environment variable.
|
||||
As a special case on Windows the variable `$USERPROFILE` will be used instead.
|
||||
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
||||
|
||||
If you compile Lite XL yourself, it is recommended to use the script `build-packages.sh`:
|
||||
### Linux
|
||||
|
||||
Unzip the file and `cd` into the `lite-xl` directory:
|
||||
|
||||
```sh
|
||||
bash build-packages.sh <arch>
|
||||
tar -xzf <file>
|
||||
cd lite-xl
|
||||
```
|
||||
|
||||
The script will run Meson and create a zip file with the application or, for linux, a tar compressed archive.
|
||||
Lite XL can be easily installed by unpacking the archive in any directory of your choice.
|
||||
To run lite-xl without installing:
|
||||
```sh
|
||||
cd bin
|
||||
./lite-xl
|
||||
```
|
||||
|
||||
On Windows two packages will be created, one called "portable" using the "data" folder next to the executable and
|
||||
the other one using a unix-like file layout. Both packages works correctly. The one with unix-like file layout
|
||||
is meant for people using a unix-like shell and the command line.
|
||||
To install lite-xl copy files over into appropriate directories:
|
||||
|
||||
Please note that there aren't any hard-coded directories in the executable, so that the
|
||||
package can be extracted and used in any directory.
|
||||
```sh
|
||||
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
|
||||
cp -r share $HOME/.local
|
||||
```
|
||||
|
||||
If `$HOME/.local/bin` is not in PATH:
|
||||
|
||||
```sh
|
||||
echo -e 'export PATH=$PATH:$HOME/.local/bin' >> $HOME/.bashrc
|
||||
```
|
||||
|
||||
To get the icon to show up in app launcher:
|
||||
|
||||
```sh
|
||||
xdg-desktop-menu forceupdate
|
||||
```
|
||||
|
||||
You may need to logout and login again to see icon in app launcher.
|
||||
|
||||
To uninstall just run:
|
||||
|
||||
```sh
|
||||
rm -f $HOME/.local/bin/lite-xl
|
||||
rm -rf $HOME/.local/share/icons/hicolor/scalable/apps/lite-xl.svg \
|
||||
$HOME/.local/share/applications/org.lite_xl.lite_xl.desktop \
|
||||
$HOME/.local/share/metainfo/org.lite_xl.lite_xl.appdata.xml \
|
||||
$HOME/.local/share/lite-xl
|
||||
```
|
||||
|
||||
Mac OS X is fully supported and a notarized app disk image is provided in the release page.
|
||||
In addition the application can be compiled using the generic
|
||||
instructions given above.
|
||||
|
||||
## Contributing
|
||||
Any additional functionality that can be added through a plugin should be done
|
||||
as a plugin, after which a pull request to the
|
||||
[plugins repository](https://github.com/rxi/lite-plugins) can be made.
|
||||
|
||||
If the plugin uses any Lite XL-specific functionality, please open a pull request to the
|
||||
[Lite XL plugins repository](https://github.com/franko/lite-plugins).
|
||||
Any additional functionality that can be added through a plugin should be done
|
||||
as a plugin, after which a pull request to the [Lite XL plugins repository] can be made.
|
||||
|
||||
Pull requests to improve or modify the editor itself are welcome.
|
||||
|
||||
## License
|
||||
## Licenses
|
||||
|
||||
This project is free software; you can redistribute it and/or modify it under
|
||||
the terms of the MIT license. See [LICENSE](LICENSE) for details.
|
||||
the terms of the MIT license. See [LICENSE] for details.
|
||||
|
||||
See the [licenses] file for details on licenses used by the required dependencies.
|
||||
|
||||
|
||||
[CI]: https://github.com/lite-xl/lite-xl/actions/workflows/build.yml/badge.svg
|
||||
[Discord Badge Image]: https://img.shields.io/discord/847122429742809208?label=discord&logo=discord
|
||||
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
||||
[lite]: https://github.com/rxi/lite
|
||||
[website]: https://lite-xl.com
|
||||
[build]: https://lite-xl.com/en/documentation/build/
|
||||
[Get Lite XL]: https://github.com/lite-xl/lite-xl/releases/latest
|
||||
[Get plugins]: https://github.com/lite-xl/lite-xl-plugins
|
||||
[Get color themes]: https://github.com/lite-xl/lite-xl-colors
|
||||
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
|
||||
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins
|
||||
[colors repository]: https://github.com/lite-xl/lite-xl-colors
|
||||
[LICENSE]: LICENSE
|
||||
[licenses]: licenses/licenses.md
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
# Lite XL v2 for AmigaOS 4.1 FE
|
||||
|
||||
Lite XL is a lightweight text editor written in Lua.
|
||||
|
||||
The port is not perfect and might has issues here and there. For example
|
||||
the filesystem notifications are not working yet. So when you make changes
|
||||
at a project folder those will not be reflected in Lite XL automatically.
|
||||
|
||||
It might crash from time to time, if there is a path problem, but overall
|
||||
it works pretty well. This is my daily editor for any kind of development.
|
||||
|
||||
## New features against Lite XL v1
|
||||
- Faster file scrolling
|
||||
- Faster switch between tabs
|
||||
- Reposition tabs at the side or at the bottom of other tabs, making
|
||||
multiple columns/rows of opened files
|
||||
- Multiple cursor editing
|
||||
- Better font manipulation and appearance
|
||||
- Faster transitions
|
||||
|
||||
## Installation
|
||||
You can extract the Lite XL archive wherever you want and run the *lite*
|
||||
editor.
|
||||
|
||||
## Configuration folder
|
||||
This editor creates a `.config` folder where the configuration is saved, as
|
||||
well as plugins, themes etc.. By default this AmigaOS 4.1 FE version uses the
|
||||
executable folder, but if you want to ovveride it, create an ENV variable
|
||||
named `HOME` and set there your path.
|
||||
|
||||
You can check if there is one already set by executing the following command
|
||||
in a shell
|
||||
```
|
||||
GetEnv HOME
|
||||
```
|
||||
If there is one set, then you will see the path at the output.
|
||||
|
||||
Otherwise, you can set your home path be executing the following command.
|
||||
Change the path to the one of your preference.
|
||||
```
|
||||
SetEnv SAVE HOME "Sys:home/"
|
||||
```
|
||||
|
||||
## Addons
|
||||
### Colors
|
||||
Colors are lua files that set the color scheme of the editor. There are
|
||||
light and dark themes for you to choose.
|
||||
|
||||
To install and use them you have to copy the ones you would like from
|
||||
`addons/colors/light` or `addons/colors/dark` into the folder
|
||||
`.config/lite-xl/colors/`. Don't add light or dark folders. Just copy the
|
||||
.lua files in there.
|
||||
|
||||
Then you have to start Lite XL and open your configuration by clicking
|
||||
at the cog icon at the toolbar (bottom left sixth icon). Go at the line
|
||||
that looks like below
|
||||
```
|
||||
-- core.reload_module("colors.summer")
|
||||
```
|
||||
and change the `summer` with the name of your color theme. Also, remove
|
||||
the two dashes `--` at the start of the line and save the file. If you
|
||||
did everything right, the color schema should change instantly.
|
||||
|
||||
The themes can also be found at
|
||||
https://github.com/lite-xl/lite-xl-colors
|
||||
|
||||
### Plugins
|
||||
The Lite XL that you are using on AmigaOS 4 is based on version 2.0.4
|
||||
and not the latest version that is available by the development team.
|
||||
This means that some of the latest plugins might not working at all
|
||||
or need some modifications to work.
|
||||
|
||||
To make it easier for you, I gathered some of the plugins that are working
|
||||
well, and I included them under `addons/plugins`. For you to install the
|
||||
ones you would like to use, you have to copy the `.lua` files into the
|
||||
folder `.config/lite-xl/plugins/` and restart the editor.
|
||||
|
||||
Please, choose wisely, because adding all the plugins might make the editor
|
||||
slower on your system. I would recommend you add only those that you really
|
||||
need.
|
||||
|
||||
The included plugins are the following:
|
||||
|
||||
**autoinsert**
|
||||
Automatically inserts closing brackets and quotes. Also allows selected
|
||||
text to be wrapped with brackets or quotes.
|
||||
|
||||
**autowrap**
|
||||
Automatically hardwraps lines when typing
|
||||
|
||||
**bigclock**
|
||||
Shows the current time and date in a view with large text
|
||||
|
||||
**bracketmatch**
|
||||
Underlines matching pair for bracket under the caret
|
||||
|
||||
**colorpreview**
|
||||
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
|
||||
resultant color.
|
||||
|
||||
**eofnewline-xl**
|
||||
Make sure the file ends with one blank line.
|
||||
|
||||
**ephemeral_tabs**
|
||||
Preview tabs. Opening a doc will replace the contents of the preview tab.
|
||||
Marks tabs as non-preview on any change or tab double clicking.
|
||||
|
||||
**ghmarkdown**
|
||||
Opens a preview of the current markdown file in a browser window
|
||||
|
||||
**indentguide**
|
||||
Adds indent guides
|
||||
|
||||
**language_make**
|
||||
Syntax for the Make build system language
|
||||
|
||||
**language_sh**
|
||||
Syntax for shell scripting language
|
||||
|
||||
**lfautoinsert**
|
||||
Automatically inserts indentation and closing bracket/text after newline
|
||||
|
||||
**markers**
|
||||
Add markers to docs and jump between them quickly
|
||||
|
||||
**minimap**
|
||||
Shows a minimap on the right-hand side of the docview. Please note that
|
||||
this plugin will make the editor slower on file loading and scrolling.
|
||||
|
||||
**navigate**
|
||||
Allows moving back and forward between document positions, reducing the
|
||||
amount of scrolling
|
||||
|
||||
**rainbowparen**
|
||||
Show nesting of parentheses with rainbow colours
|
||||
|
||||
**restoretabs**
|
||||
Keep a list of recently closed tabs, and restore the tab in order on
|
||||
cntrl+shift+t.
|
||||
|
||||
**selectionhighlight**
|
||||
Highlights regions of code that match the current selection
|
||||
|
||||
**smallclock**
|
||||
It adds a small clock at the bottom right corner.
|
||||
|
||||
## Tips and tricks
|
||||
|
||||
### Transitions
|
||||
|
||||
If you want to disable the transitions and make the editor faster,
|
||||
open your configuration file by clicking at the cog icon at the toolbar
|
||||
(bottom left, 6th icon) and add the following line at the end of the file,
|
||||
and then save it. You might need to restart your editor (CTRL+SHIFT+R)
|
||||
|
||||
```
|
||||
config.transitions = false
|
||||
```
|
||||
|
||||
### Hide files from the file list
|
||||
|
||||
If you would like to hide files or whole folder from the left side bar list,
|
||||
open your configuration by clicking at the cog icon at the toolbar
|
||||
(bottom left sixth icon) and add the followline at the end of the file and
|
||||
save it. This hides all the files that start with a dot, and all the `.info`
|
||||
files. You might need to restart your editor (CTRL+SHIFT+R)
|
||||
|
||||
```
|
||||
config.ignore_files = {"^%.", "%.info$"}
|
||||
```
|
||||
|
||||
You can add as many rules as you want in there, to hide files or
|
||||
folders, as you like.
|
||||
|
||||
## I would like to thank
|
||||
|
||||
- IconDesigner for the proper glow icons that are included in the release
|
||||
- Capehill for his tireless work on SDL port
|
||||
- Michael Trebilcock for his port on liblua
|
||||
- Lite XL original team for being helpful and providing info
|
||||
|
||||
Without all the above Lite XL would not be possible
|
||||
|
||||
## Support
|
||||
If you enjoy what I am doing and would like to keep me up during the night,
|
||||
please consider to buy me a coffee at:
|
||||
https://ko-fi.com/walkero
|
||||
|
||||
## Known issues
|
||||
You can find the known issues at
|
||||
https://git.walkero.gr/walkero/lite-xl/issues
|
||||
|
||||
# Changelog
|
||||
|
||||
## [2.0.3r1] - 2022-03-30
|
||||
### Changed
|
||||
- Applied all the necessary changes to make it run under AmigaOS 4.1 FE
|
||||
- Fixes and changes
|
||||
|
||||
# Disclaimer
|
||||
YOU MAY USE IT AT YOUR OWN RISK!
|
||||
I will not be held responsible for any data loss or problem you might get
|
||||
by using this software.
|
||||
|
Binary file not shown.
|
@ -1,219 +1,164 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# strip-components is normally set to 1 to strip the initial "data" from the
|
||||
# directory path.
|
||||
copy_directory_from_repo () {
|
||||
local tar_options=()
|
||||
if [[ $1 == --strip-components=* ]]; then
|
||||
tar_options+=($1)
|
||||
shift
|
||||
fi
|
||||
local dirname="$1"
|
||||
local destdir="$2"
|
||||
git archive "$use_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||
if [ ! -e "src/api/api.h" ]; then
|
||||
echo "Please run this script from the root directory of Lite XL."; exit 1
|
||||
fi
|
||||
|
||||
source scripts/common.sh
|
||||
|
||||
show_help() {
|
||||
echo
|
||||
echo "Usage: $0 <OPTIONS>"
|
||||
echo
|
||||
echo "Common options:"
|
||||
echo
|
||||
echo "-h --help Show this help and exit."
|
||||
echo "-b --builddir DIRNAME Set the name of the build directory (not path)."
|
||||
echo " Default: '$(get_default_build_dir)'."
|
||||
echo "-p --prefix PREFIX Install directory prefix."
|
||||
echo " Default: '/'."
|
||||
echo " --debug Debug this script."
|
||||
echo
|
||||
echo "Build options:"
|
||||
echo
|
||||
echo "-f --forcefallback Force to build subprojects dependencies statically."
|
||||
echo "-B --bundle Create an App bundle (macOS only)"
|
||||
echo "-P --portable Create a portable package."
|
||||
echo "-O --pgo Use profile guided optimizations (pgo)."
|
||||
echo " Requires running the application iteractively."
|
||||
echo
|
||||
echo "Package options:"
|
||||
echo
|
||||
echo "-d --destdir DIRNAME Set the name of the package directory (not path)."
|
||||
echo " Default: 'lite-xl'."
|
||||
echo "-v --version VERSION Sets the version on the package name."
|
||||
echo "-A --appimage Create an AppImage (Linux only)."
|
||||
echo "-D --dmg Create a DMG disk image (macOS only)."
|
||||
echo " Requires NPM and AppDMG."
|
||||
echo "-I --innosetup Create an InnoSetup installer (Windows only)."
|
||||
echo "-S --source Create a source code package,"
|
||||
echo " including subprojects dependencies."
|
||||
echo
|
||||
}
|
||||
|
||||
# Check if build directory is ok to be used to build.
|
||||
build_dir_is_usable () {
|
||||
local build="$1"
|
||||
if [[ $build == */* || -z "$build" ]]; then
|
||||
echo "invalid build directory, no path allowed: \"$build\""
|
||||
return 1
|
||||
fi
|
||||
git ls-files --error-unmatch "$build" &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "invalid path, \"$build\" is under revision control"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
main() {
|
||||
local build_dir
|
||||
local build_dir_option=()
|
||||
local dest_dir
|
||||
local dest_dir_option=()
|
||||
local prefix
|
||||
local prefix_option=()
|
||||
local version
|
||||
local version_option=()
|
||||
local debug
|
||||
local force_fallback
|
||||
local appimage
|
||||
local bundle
|
||||
local innosetup
|
||||
local portable
|
||||
local pgo
|
||||
|
||||
# Ordinary release build
|
||||
lite_build () {
|
||||
local build="$1"
|
||||
build_dir_is_usable "$build" || exit 1
|
||||
rm -fr "$build"
|
||||
meson setup --buildtype=release "$build" || exit 1
|
||||
ninja -C "$build" || exit 1
|
||||
}
|
||||
|
||||
# Build using Profile Guided Optimizations (PGO)
|
||||
lite_build_pgo () {
|
||||
local build="$1"
|
||||
build_dir_is_usable "$build" || exit 1
|
||||
rm -fr "$build"
|
||||
meson setup --buildtype=release -Db_pgo=generate "$build" || exit 1
|
||||
ninja -C "$build" || exit 1
|
||||
copy_directory_from_repo data "$build/src"
|
||||
"$build/src/lite"
|
||||
meson configure -Db_pgo=use "$build"
|
||||
ninja -C "$build" || exit 1
|
||||
}
|
||||
|
||||
lite_build_package_windows () {
|
||||
local portable=""
|
||||
if [ "$1" == "-portable" ]; then
|
||||
portable="-portable"
|
||||
shift
|
||||
fi
|
||||
local build="$1"
|
||||
local arch="$2"
|
||||
local os="win"
|
||||
local pdir=".package-build/lite-xl"
|
||||
if [ "$portable" == "-portable" ]; then
|
||||
local bindir="$pdir"
|
||||
local datadir="$pdir/data"
|
||||
else
|
||||
local bindir="$pdir/bin"
|
||||
local datadir="$pdir/share/lite-xl"
|
||||
fi
|
||||
mkdir -p "$bindir"
|
||||
mkdir -p "$datadir"
|
||||
for module_name in core plugins colors fonts; do
|
||||
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
||||
for i in "$@"; do
|
||||
case $i in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-b|--builddir)
|
||||
build_dir="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-d|--destdir)
|
||||
dest_dir="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-f|--forcefallback)
|
||||
force_fallback="--forcefallback"
|
||||
shift
|
||||
;;
|
||||
-p|--prefix)
|
||||
prefix="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-v|--version)
|
||||
version="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-A|--appimage)
|
||||
appimage="--appimage"
|
||||
shift
|
||||
;;
|
||||
-B|--bundle)
|
||||
bundle="--bundle"
|
||||
shift
|
||||
;;
|
||||
-D|--dmg)
|
||||
dmg="--dmg"
|
||||
shift
|
||||
;;
|
||||
-I|--innosetup)
|
||||
innosetup="--innosetup"
|
||||
shift
|
||||
;;
|
||||
-P|--portable)
|
||||
portable="--portable"
|
||||
shift
|
||||
;;
|
||||
-S|--source)
|
||||
source="--source"
|
||||
shift
|
||||
;;
|
||||
-O|--pgo)
|
||||
pgo="--pgo"
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
debug="--debug"
|
||||
set -x
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
# unknown option
|
||||
;;
|
||||
esac
|
||||
done
|
||||
for module_name in plugins colors; do
|
||||
cp -r "$build/third/data/$module_name" "$datadir"
|
||||
done
|
||||
cp "$build/src/lite.exe" "$bindir"
|
||||
strip --strip-all "$bindir/lite.exe"
|
||||
pushd ".package-build"
|
||||
local package_name="lite-xl-$os-$arch$portable.zip"
|
||||
zip "$package_name" -r "lite-xl"
|
||||
mv "$package_name" ..
|
||||
popd
|
||||
rm -fr ".package-build"
|
||||
echo "created package $package_name"
|
||||
}
|
||||
|
||||
lite_build_package_macosx () {
|
||||
local build="$1"
|
||||
local arch="$2"
|
||||
local os="macosx"
|
||||
|
||||
local appdir=".package-build/lite-xl.app"
|
||||
local bindir="$appdir/Contents/MacOS"
|
||||
local datadir="$appdir/Contents/Resources"
|
||||
mkdir -p "$bindir" "$datadir"
|
||||
for module_name in core plugins colors fonts; do
|
||||
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
||||
done
|
||||
for module_name in plugins colors; do
|
||||
cp -r "$build/third/data/$module_name" "$datadir"
|
||||
done
|
||||
cp dev-utils/icon.icns "$appdir/Contents/Resources/icon.icns"
|
||||
cp dev-utils/Info.plist "$appdir/Contents/Info.plist"
|
||||
cp "$build/src/lite" "$bindir/lite-xl"
|
||||
strip "$bindir/lite-xl"
|
||||
pushd ".package-build"
|
||||
local package_name="lite-xl-$os-$arch.zip"
|
||||
zip "$package_name" -r "lite-xl.app"
|
||||
mv "$package_name" ..
|
||||
popd
|
||||
rm -fr ".package-build"
|
||||
local dmg_name="lite-xl-$os-$arch.dmg"
|
||||
rm -f "$dmg_name" && hdiutil create -volname lite-xl -srcfolder lite-xl.app -ov -format UDBZ "$dmg_name"
|
||||
echo "created package $package_name"
|
||||
echo "created disk image $dmg_name"
|
||||
}
|
||||
|
||||
lite_build_package_linux () {
|
||||
local portable=""
|
||||
if [ "$1" == "-portable" ]; then
|
||||
portable="-portable"
|
||||
shift
|
||||
fi
|
||||
local build="$1"
|
||||
local arch="$2"
|
||||
local os="linux"
|
||||
local pdir=".package-build/lite-xl"
|
||||
if [ "$portable" == "-portable" ]; then
|
||||
local bindir="$pdir"
|
||||
local datadir="$pdir/data"
|
||||
else
|
||||
local bindir="$pdir/bin"
|
||||
local datadir="$pdir/share/lite-xl"
|
||||
fi
|
||||
mkdir -p "$bindir"
|
||||
mkdir -p "$datadir"
|
||||
for module_name in core plugins colors fonts; do
|
||||
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
||||
done
|
||||
for module_name in plugins colors; do
|
||||
cp -r "$build/third/data/$module_name" "$datadir"
|
||||
done
|
||||
cp "$build/src/lite" "$bindir"
|
||||
strip "$bindir/lite"
|
||||
if [ -z "$portable" ]; then
|
||||
mkdir -p "$pdir/share/applications" "$pdir/share/icons/hicolor/scalable/apps"
|
||||
cp "dev-utils/lite-xl.desktop" "$pdir/share/applications"
|
||||
cp "dev-utils/lite.svg" "$pdir/share/icons/hicolor/scalable/apps/lite-xl.svg"
|
||||
fi
|
||||
pushd ".package-build"
|
||||
local package_name="lite-xl-$os-$arch$portable.tar.gz"
|
||||
tar czf "$package_name" "lite-xl"
|
||||
mv "$package_name" ..
|
||||
popd
|
||||
rm -fr ".package-build"
|
||||
echo "created package $package_name"
|
||||
}
|
||||
|
||||
lite_build_package () {
|
||||
if [[ "$OSTYPE" == msys || "$OSTYPE" == win32 ]]; then
|
||||
lite_build_package_windows "$@"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
lite_build_package_macosx "$@"
|
||||
elif [[ "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* ]]; then
|
||||
lite_build_package_linux "$@"
|
||||
else
|
||||
echo "Unknown OS type \"$OSTYPE\""
|
||||
if [[ -n $1 ]]; then
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n $build_dir ]]; then build_dir_option=("--builddir" "${build_dir}"); fi
|
||||
if [[ -n $dest_dir ]]; then dest_dir_option=("--destdir" "${dest_dir}"); fi
|
||||
if [[ -n $prefix ]]; then prefix_option=("--prefix" "${prefix}"); fi
|
||||
if [[ -n $version ]]; then version_option=("--version" "${version}"); fi
|
||||
|
||||
source scripts/build.sh \
|
||||
${build_dir_option[@]} \
|
||||
${prefix_option[@]} \
|
||||
$debug \
|
||||
$force_fallback \
|
||||
$bundle \
|
||||
$portable \
|
||||
$pgo
|
||||
|
||||
source scripts/package.sh \
|
||||
${build_dir_option[@]} \
|
||||
${dest_dir_option[@]} \
|
||||
${prefix_option[@]} \
|
||||
${version_option[@]} \
|
||||
--binary \
|
||||
--addons \
|
||||
$debug \
|
||||
$appimage \
|
||||
$dmg \
|
||||
$innosetup \
|
||||
$source
|
||||
}
|
||||
|
||||
lite_copy_third_party_modules () {
|
||||
local build="$1"
|
||||
curl --insecure -L "https://github.com/rxi/lite-colors/archive/master.zip" -o "$build/rxi-lite-colors.zip"
|
||||
mkdir -p "$build/third/data/colors" "$build/third/data/plugins"
|
||||
unzip "$build/rxi-lite-colors.zip" -d "$build"
|
||||
mv "$build/lite-colors-master/colors" "$build/third/data"
|
||||
rm -fr "$build/lite-colors-master"
|
||||
}
|
||||
|
||||
unset arch
|
||||
while [ ! -z {$1+x} ]; do
|
||||
case $1 in
|
||||
-pgo)
|
||||
pgo=true
|
||||
shift
|
||||
;;
|
||||
-branch=*)
|
||||
use_branch="${1#-branch=}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
arch="$1"
|
||||
break
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z ${arch+set} ]; then
|
||||
echo "usage: $0 [options] <arch>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z ${use_branch+set} ]; then
|
||||
use_branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
fi
|
||||
|
||||
build_dir=".build-$arch"
|
||||
|
||||
if [ -z ${pgo+set} ]; then
|
||||
lite_build "$build_dir"
|
||||
else
|
||||
lite_build_pgo "$build_dir"
|
||||
fi
|
||||
lite_copy_third_party_modules "$build_dir"
|
||||
lite_build_package "$build_dir" "$arch"
|
||||
if [[ ! ( "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* || "$OSTYPE" == "darwin"* ) ]]; then
|
||||
lite_build_package -portable "$build_dir" "$arch"
|
||||
fi
|
||||
main "$@"
|
||||
|
|
39
build.sh
39
build.sh
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer"
|
||||
cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)"
|
||||
lflags="-static-libgcc -static-libstdc++"
|
||||
for package in libagg freetype2 lua5.2 x11; do
|
||||
lflags+=" $(pkg-config --libs $package)"
|
||||
done
|
||||
lflags+=" $(sdl2-config --libs) -lm"
|
||||
|
||||
if [[ $* == *windows* ]]; then
|
||||
echo "cross compiling for windows is not yet supported"
|
||||
exit 1
|
||||
else
|
||||
outfile="lite"
|
||||
compiler="gcc"
|
||||
cxxcompiler="g++"
|
||||
fi
|
||||
|
||||
lib/font_renderer/build.sh || exit 1
|
||||
libs=libfontrenderer.a
|
||||
|
||||
echo "compiling lite..."
|
||||
for f in `find src -name "*.c"`; do
|
||||
$compiler -c $cflags $f -o "${f//\//_}.o"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
got_error=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! $got_error ]]; then
|
||||
echo "linking..."
|
||||
$cxxcompiler -o $outfile *.o $libs $lflags
|
||||
fi
|
||||
|
||||
echo "cleaning up..."
|
||||
rm *.o *.a
|
||||
echo "done"
|
||||
|
184
changelog.md
184
changelog.md
|
@ -1,7 +1,187 @@
|
|||
Lite XL is following closely [rxi/lite](https://github.com/rxi/lite) but with some enhancements.
|
||||
|
||||
This files document the changes done in Lite XL for each release.
|
||||
|
||||
### 2.0.3
|
||||
|
||||
Replace periodic rescan of project folder with a notification based system using the
|
||||
[dmon library](https://github.com/septag/dmon). Improves performance especially for
|
||||
large project folders since the application no longer needs to rescan.
|
||||
The application also reports immediatly any change in the project directory even
|
||||
when the application is unfocused.
|
||||
|
||||
Improved find-replace reverse and forward search.
|
||||
|
||||
Fixed a bug in incremental syntax highlighting affecting documents with multiple-lines
|
||||
comments or strings.
|
||||
|
||||
The application now always shows the tabs in the documents' view even when a single
|
||||
document is opened. Can be changed with the option `config.always_show_tabs`.
|
||||
|
||||
Fix problem with numeric keypad function keys not properly working.
|
||||
|
||||
Fix problem with pixel not correctly drawn at the window's right edge.
|
||||
|
||||
Treat correctly and open network paths on Windows.
|
||||
|
||||
Add some improvements for very slow network filesystems.
|
||||
|
||||
Fix problem with python syntax highliting, contributed by @dflock.
|
||||
|
||||
### 2.0.2
|
||||
|
||||
Fix problem project directory when starting the application from Launcher on macOS.
|
||||
|
||||
Improved LogView. Entries can now be expanded and there is a context menu to copy the item's content.
|
||||
|
||||
Change the behavior of `ctrl+d` to add a multi-cursor selection to the next occurrence.
|
||||
The old behavior to move the selection to the next occurrence is now done using the shortcut `ctrl+f3`.
|
||||
|
||||
Added a command to create a multi-cursor with all the occurrences of the current selection.
|
||||
Activated with the shortcut `ctrl+shift+l`.
|
||||
|
||||
Fix problem when trying to close an unsaved new document.
|
||||
|
||||
No longer shows an error for the `-psn` argument passed to the application on macOS.
|
||||
|
||||
Fix `treeview:open-in-system` command on Windows.
|
||||
|
||||
Fix rename command to update name of document if opened.
|
||||
|
||||
Improve the find and replace dialog so that previously used expressions can be recalled
|
||||
using "up" and "down" keys.
|
||||
|
||||
Build package script rewrite with many improvements.
|
||||
|
||||
Use bigger fonts by default.
|
||||
|
||||
Other minor improvements and fixes.
|
||||
|
||||
With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101.
|
||||
|
||||
### 2.0.1
|
||||
|
||||
Fix a few bugs and we mandate the mod-version 2 for plugins.
|
||||
This means that users should ensure they have up-to-date plugins for Lite XL 2.0.
|
||||
|
||||
Here some details about the bug fixes:
|
||||
|
||||
- fix a bug that created a fatal error when using the command to change project folder or when closing all the active documents
|
||||
- add a limit to avoid scaling fonts too much and fix a related invalid memory access for very small fonts
|
||||
- fix focus problem with NagView when switching project directory
|
||||
- fix error that prevented the verification of plugins versions
|
||||
- fix error on X11 that caused a bug window event on exit
|
||||
|
||||
### 2.0
|
||||
|
||||
The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured;
|
||||
any custom plugins may need to be adjusted accordingly (see note below about plugin namespacing).
|
||||
|
||||
Contains the following new features:
|
||||
|
||||
Full PCRE (regex) support for find and replace, as well as in language syntax definitions. Can be accessed
|
||||
programatically via the lua `regex` module.
|
||||
|
||||
A full, finalized subprocess API, using libreproc. Subprocess can be started and interacted with using
|
||||
`Process.new`.
|
||||
|
||||
Support for multi-cursor editing. Cursors can be created by either ctrl+clicking on the screen, or by using
|
||||
the keyboard shortcuts ctrl+shift+up/down to create an additional cursor on the previous/next line.
|
||||
|
||||
All build systems other than meson removed.
|
||||
|
||||
A more organized directory structure has been implemented; in particular a docs folder which contains C api
|
||||
documentation, and a resource folder which houses all build resources.
|
||||
|
||||
Plugin config namespacing has been implemented. This means that instead of using `config.myplugin.a`,
|
||||
to read settings, and `config.myplugin = false` to disable plugins, this has been changed to
|
||||
`config.plugins.myplugin.a`, and `config.plugins.myplugin = false` repsectively. This may require changes to
|
||||
your user plugin, or to any custom plugins you have.
|
||||
|
||||
A context menu on right click has been added.
|
||||
|
||||
Changes to how we deal with indentation have been implemented; in particular, hitting home no longer brings you
|
||||
to the start of a line, it'll bring you to the start of indentation, which is more in line with other editors.
|
||||
|
||||
Lineguide, and scale plugins moved into the core, and removed from `lite-plugins`. This may also require you to
|
||||
adjust your personal plugin folder to remove these if they're present.
|
||||
|
||||
In addition, there have been many other small fixes and improvements, too numerous to list here.
|
||||
|
||||
### 1.16.11
|
||||
|
||||
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
|
||||
The application remains functional and the directories can be explored without using too much memory.
|
||||
In this operating mode the files of the project are not indexed so the command "Core: Find File" will act as the "Core: Open File" command.
|
||||
The "Project Search: Find" will work by searching all the files present in the project directory even if they are not indexed.
|
||||
|
||||
Implemented changing fonts per syntax group by @liquidev.
|
||||
|
||||
Example user module snippet that makes all comments italic:
|
||||
|
||||
```lua
|
||||
local style = require "core.style"
|
||||
|
||||
-- italic.ttf must be provided by the user
|
||||
local italic = renderer.font.load("italic.ttf", 14)
|
||||
style.syntax_fonts["comment"] = italic
|
||||
```
|
||||
|
||||
Improved indentation behavior by @adamharrison.
|
||||
|
||||
Fix bug with close button not working in borderless window mode.
|
||||
|
||||
Fix problem with normalization of filename for opened documents.
|
||||
|
||||
### 1.16.10
|
||||
|
||||
Improved syntax highlight system thanks to @liquidev and @adamharrison.
|
||||
Thanks to the new system we provide more a accurate syntax highlighting for Lua, C and C++.
|
||||
Other syntax improvements contributed by @vincens2005.
|
||||
|
||||
Move to JetBrains Mono and Fira Sans fonts for code and UI respectively.
|
||||
Thet are provided under the SIL Open Font License, Version 1.1.
|
||||
See `doc/licenses.md` for license details.
|
||||
|
||||
Fixed bug with fonts and rencache module.
|
||||
Under very specific situations the application was crashing due to invalid memory access.
|
||||
|
||||
Add documentation for keymap binding, thanks to @Janis-Leuenberger.
|
||||
|
||||
Added a contributors page in `doc/contributors.md`.
|
||||
|
||||
### 1.16.9
|
||||
|
||||
Fix a bug related to nested panes resizing.
|
||||
|
||||
Fix problem preventing creating a new file.
|
||||
|
||||
### 1.16.8
|
||||
|
||||
Fix application crash when using the command `core:restart`.
|
||||
|
||||
Improve application startup to reduce "flashing".
|
||||
|
||||
Move to new plugins versioning using tag `mod-version:1`.
|
||||
The mod-version is a single digit version that tracks the
|
||||
plugins compatibility version independently from the lite-xl
|
||||
version.
|
||||
|
||||
For backward compatibility the tag `-- lite-xl 1.16` is considered equivalent to
|
||||
`mod-version:1` so users don't need to update their plugins.
|
||||
|
||||
Both kind of tags can appear in new plugins in the form:
|
||||
|
||||
```lua
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
```
|
||||
|
||||
where the old tag needs to appear at the end for compatibility.
|
||||
|
||||
### 1.16.7
|
||||
|
||||
Add support for retina displays on Mac OS X.
|
||||
|
||||
Fix a few problems related to file paths.
|
||||
|
||||
### 1.16.6
|
||||
|
||||
Implement a system to check the compatibility of plugins by checking a release tag.
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
local b05 = 'rgba(0,0,0,0.5)' local red = '#994D4D'
|
||||
local b80 = '#333333' local orange = '#B3661A'
|
||||
local b60 = '#808080' local green = '#52994D'
|
||||
local b40 = '#ADADAD' local teal = '#4D9999'
|
||||
local b20 = '#CECECE' local blue = '#1A66B3'
|
||||
local b00 = '#E6E6E6' local magenta = '#994D99'
|
||||
--------------------------=--------------------------
|
||||
local style = require 'core.style'
|
||||
local common = require 'core.common'
|
||||
--------------------------=--------------------------
|
||||
style.line_highlight = { common.color(b20) }
|
||||
style.background = { common.color(b00) }
|
||||
style.background2 = { common.color(b20) }
|
||||
style.background3 = { common.color(b20) }
|
||||
style.text = { common.color(b60) }
|
||||
style.caret = { common.color(b80) }
|
||||
style.accent = { common.color(b80) }
|
||||
style.dim = { common.color(b60) }
|
||||
style.divider = { common.color(b40) }
|
||||
style.selection = { common.color(b40) }
|
||||
style.line_number = { common.color(b60) }
|
||||
style.line_number2 = { common.color(b80) }
|
||||
style.scrollbar = { common.color(b40) }
|
||||
style.scrollbar2 = { common.color(b60) }
|
||||
style.nagbar = { common.color(red) }
|
||||
style.nagbar_text = { common.color(b00) }
|
||||
style.nagbar_dim = { common.color(b05) }
|
||||
--------------------------=--------------------------
|
||||
style.syntax = {}
|
||||
style.syntax['normal'] = { common.color(b80) }
|
||||
style.syntax['symbol'] = { common.color(b80) }
|
||||
style.syntax['comment'] = { common.color(b60) }
|
||||
style.syntax['keyword'] = { common.color(blue) }
|
||||
style.syntax['keyword2'] = { common.color(red) }
|
||||
style.syntax['number'] = { common.color(teal) }
|
||||
style.syntax['literal'] = { common.color(blue) }
|
||||
style.syntax['string'] = { common.color(green) }
|
||||
style.syntax['operator'] = { common.color(magenta) }
|
||||
style.syntax['function'] = { common.color(blue) }
|
||||
--------------------------=--------------------------
|
||||
style.syntax.paren1 = { common.color(magenta) }
|
||||
style.syntax.paren2 = { common.color(orange) }
|
||||
style.syntax.paren3 = { common.color(teal) }
|
||||
style.syntax.paren4 = { common.color(blue) }
|
||||
style.syntax.paren5 = { common.color(red) }
|
||||
--------------------------=--------------------------
|
||||
style.lint = {}
|
||||
style.lint.info = { common.color(blue) }
|
||||
style.lint.hint = { common.color(green) }
|
||||
style.lint.warning = { common.color(red) }
|
||||
style.lint.error = { common.color(orange) }
|
|
@ -41,11 +41,14 @@ function command.get_all_valid()
|
|||
return res
|
||||
end
|
||||
|
||||
function command.is_valid(name, ...)
|
||||
return command.map[name] and command.map[name].predicate(...)
|
||||
end
|
||||
|
||||
local function perform(name)
|
||||
local function perform(name, ...)
|
||||
local cmd = command.map[name]
|
||||
if cmd and cmd.predicate() then
|
||||
cmd.perform()
|
||||
if cmd and cmd.predicate(...) then
|
||||
cmd.perform(...)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
@ -59,7 +62,10 @@ end
|
|||
|
||||
|
||||
function command.add_defaults()
|
||||
local reg = { "core", "root", "command", "doc", "findreplace", "files", "drawwhitespace" }
|
||||
local reg = {
|
||||
"core", "root", "command", "doc", "findreplace",
|
||||
"files", "drawwhitespace", "dialog"
|
||||
}
|
||||
for _, name in ipairs(reg) do
|
||||
require("core.commands." .. name)
|
||||
end
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
local function has_commandview()
|
||||
return core.active_view:is(CommandView)
|
||||
end
|
||||
|
||||
|
||||
command.add(has_commandview, {
|
||||
command.add("core.commandview", {
|
||||
["command:submit"] = function()
|
||||
core.active_view:submit()
|
||||
end,
|
||||
|
|
|
@ -6,10 +6,12 @@ local LogView = require "core.logview"
|
|||
|
||||
|
||||
local fullscreen = false
|
||||
local restore_title_view = false
|
||||
|
||||
local function suggest_directory(text)
|
||||
text = common.home_expand(text)
|
||||
return common.home_encode_list(text == "" and core.recent_projects or common.dir_path_suggest(text))
|
||||
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
|
||||
and core.recent_projects or common.dir_path_suggest(text))
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
|
@ -27,9 +29,12 @@ command.add(nil, {
|
|||
|
||||
["core:toggle-fullscreen"] = function()
|
||||
fullscreen = not fullscreen
|
||||
if fullscreen then
|
||||
restore_title_view = core.title_view.visible
|
||||
end
|
||||
system.set_window_mode(fullscreen and "fullscreen" or "normal")
|
||||
core.show_title_bar(not fullscreen)
|
||||
core.title_view:configure_hit_test(not fullscreen)
|
||||
core.show_title_bar(not fullscreen and restore_title_view)
|
||||
core.title_view:configure_hit_test(not fullscreen and restore_title_view)
|
||||
end,
|
||||
|
||||
["core:reload-module"] = function()
|
||||
|
@ -66,6 +71,9 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:find-file"] = function()
|
||||
if not core.project_files_number() then
|
||||
return command.perform "core:open-file"
|
||||
end
|
||||
local files = {}
|
||||
for dir, item in core.get_project_files() do
|
||||
if item.type == "file" then
|
||||
|
@ -86,10 +94,38 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:open-file"] = function()
|
||||
local view = core.active_view
|
||||
if view.doc and view.doc.abs_filename then
|
||||
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||
if dirname then
|
||||
dirname = core.normalize_to_project_dir(dirname)
|
||||
local text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
||||
core.command_view:set_text(text)
|
||||
end
|
||||
end
|
||||
core.command_view:enter("Open File", function(text)
|
||||
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
||||
local filename = system.absolute_path(common.home_expand(text))
|
||||
core.root_view:open_doc(core.open_doc(filename))
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end, nil, function(text)
|
||||
local filename = common.home_expand(text)
|
||||
local path_stat, err = system.get_file_info(filename)
|
||||
if err then
|
||||
if err:find("No such file", 1, true) then
|
||||
-- check if the containing directory exists
|
||||
local dirname = common.dirname(filename)
|
||||
local dir_stat = dirname and system.get_file_info(dirname)
|
||||
if not dirname or (dir_stat and dir_stat.type == 'dir') then
|
||||
return true
|
||||
end
|
||||
end
|
||||
core.error("Cannot open file %s: %s", text, err)
|
||||
elseif path_stat.type == 'dir' then
|
||||
core.error("Cannot open %s, is a folder", text)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
||||
|
@ -116,6 +152,10 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:change-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname))
|
||||
end
|
||||
core.command_view:enter("Change Project Folder", function(text, item)
|
||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||
if text == core.project_dir then return end
|
||||
|
@ -124,11 +164,15 @@ command.add(nil, {
|
|||
core.error("Cannot open folder %q", text)
|
||||
return
|
||||
end
|
||||
core.confirm_close_all(core.open_folder_project, text)
|
||||
core.confirm_close_docs(core.docs, core.open_folder_project, text)
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
["core:open-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname))
|
||||
end
|
||||
core.command_view:enter("Open Project", function(text, item)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
local path_stat = system.get_file_info(text)
|
||||
|
@ -152,8 +196,6 @@ command.add(nil, {
|
|||
return
|
||||
end
|
||||
core.add_project_directory(system.absolute_path(text))
|
||||
-- TODO: add the name of directory to prioritize
|
||||
core.reschedule_project_scan()
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
|
||||
command.add("core.nagview", {
|
||||
["dialog:previous-entry"] = function()
|
||||
local v = core.active_view
|
||||
local hover = v.hovered_item or 1
|
||||
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
||||
end,
|
||||
["dialog:next-entry"] = function()
|
||||
local v = core.active_view
|
||||
local hover = v.hovered_item or 1
|
||||
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
||||
end,
|
||||
["dialog:select-yes"] = function()
|
||||
local v = core.active_view
|
||||
if v ~= core.nag_view then return end
|
||||
v:change_hovered(common.find_index(v.options, "default_yes"))
|
||||
command.perform "dialog:select"
|
||||
end,
|
||||
["dialog:select-no"] = function()
|
||||
local v = core.active_view
|
||||
if v ~= core.nag_view then return end
|
||||
v:change_hovered(common.find_index(v.options, "default_no"))
|
||||
command.perform "dialog:select"
|
||||
end,
|
||||
["dialog:select"] = function()
|
||||
local v = core.active_view
|
||||
if v.hovered_item then
|
||||
v.on_selected(v.options[v.hovered_item])
|
||||
v:next()
|
||||
end
|
||||
end
|
||||
})
|
|
@ -16,63 +16,88 @@ local function doc()
|
|||
end
|
||||
|
||||
|
||||
local function get_indent_string()
|
||||
if config.tab_type == "hard" then
|
||||
return "\t"
|
||||
end
|
||||
return string.rep(" ", config.indent_size)
|
||||
end
|
||||
|
||||
|
||||
local function doc_multiline_selection(sort)
|
||||
local line1, col1, line2, col2, swap = doc():get_selection(sort)
|
||||
if line2 > line1 and col2 == 1 then
|
||||
line2 = line2 - 1
|
||||
col2 = #doc().lines[line2]
|
||||
end
|
||||
return line1, col1, line2, col2, swap
|
||||
end
|
||||
|
||||
|
||||
local function insert_at_start_of_selected_lines(text, skip_empty)
|
||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||
for line = line1, line2 do
|
||||
local line_text = doc().lines[line]
|
||||
if (not skip_empty or line_text:find("%S")) then
|
||||
doc():insert(line, 1, text)
|
||||
local function doc_multiline_selections(sort)
|
||||
local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort)
|
||||
return function()
|
||||
idx, line1, col1, line2, col2 = iter(state, idx)
|
||||
if idx and line2 > line1 and col2 == 1 then
|
||||
line2 = line2 - 1
|
||||
col2 = #doc().lines[line2]
|
||||
end
|
||||
return idx, line1, col1, line2, col2
|
||||
end
|
||||
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
|
||||
end
|
||||
|
||||
|
||||
local function remove_from_start_of_selected_lines(text, skip_empty)
|
||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||
for line = line1, line2 do
|
||||
local line_text = doc().lines[line]
|
||||
if line_text:sub(1, #text) == text
|
||||
and (not skip_empty or line_text:find("%S"))
|
||||
then
|
||||
doc():remove(line, 1, line, #text + 1)
|
||||
end
|
||||
end
|
||||
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
|
||||
end
|
||||
|
||||
|
||||
local function append_line_if_last_line(line)
|
||||
if line >= #doc().lines then
|
||||
doc():insert(line, math.huge, "\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function save(filename)
|
||||
doc():save(filename)
|
||||
core.on_doc_save(filename)
|
||||
core.log("Saved \"%s\"", doc().filename)
|
||||
local abs_filename
|
||||
if filename then
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
abs_filename = core.project_absolute_path(filename)
|
||||
end
|
||||
doc():save(filename, abs_filename)
|
||||
local saved_filename = doc().filename
|
||||
core.log("Saved \"%s\"", saved_filename)
|
||||
end
|
||||
|
||||
local function cut_or_copy(delete)
|
||||
local full_text = ""
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
local text = doc():get_text(line1, col1, line2, col2)
|
||||
if delete then
|
||||
doc():delete_to_cursor(idx, 0)
|
||||
end
|
||||
full_text = full_text == "" and text or (full_text .. "\n" .. text)
|
||||
doc().cursor_clipboard[idx] = text
|
||||
else
|
||||
doc().cursor_clipboard[idx] = ""
|
||||
end
|
||||
end
|
||||
doc().cursor_clipboard["full"] = full_text
|
||||
system.set_clipboard(full_text)
|
||||
end
|
||||
|
||||
local function split_cursor(direction)
|
||||
local new_cursors = {}
|
||||
for _, line1, col1 in doc():get_selections() do
|
||||
if line1 + direction >= 1 and line1 + direction <= #doc().lines then
|
||||
table.insert(new_cursors, { line1 + direction, col1 })
|
||||
end
|
||||
end
|
||||
for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
local function set_cursor(x, y, snap_type)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
doc():set_selection(line, col, line, col)
|
||||
if snap_type == "word" or snap_type == "lines" then
|
||||
command.perform("doc:select-" .. snap_type)
|
||||
end
|
||||
dv().mouse_selecting = { line, col, snap_type }
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
local selection_commands = {
|
||||
["doc:cut"] = function()
|
||||
cut_or_copy(true)
|
||||
end,
|
||||
|
||||
["doc:copy"] = function()
|
||||
cut_or_copy(false)
|
||||
end,
|
||||
|
||||
["doc:select-none"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
doc():set_selection(line, col)
|
||||
end
|
||||
}
|
||||
|
||||
local commands = {
|
||||
["doc:undo"] = function()
|
||||
|
@ -83,172 +108,190 @@ local commands = {
|
|||
doc():redo()
|
||||
end,
|
||||
|
||||
["doc:cut"] = function()
|
||||
if doc():has_selection() then
|
||||
local text = doc():get_text(doc():get_selection())
|
||||
system.set_clipboard(text)
|
||||
doc():delete_to(0)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:copy"] = function()
|
||||
if doc():has_selection() then
|
||||
local text = doc():get_text(doc():get_selection())
|
||||
system.set_clipboard(text)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:paste"] = function()
|
||||
doc():text_input(system.get_clipboard():gsub("\r", ""))
|
||||
local clipboard = system.get_clipboard()
|
||||
-- If the clipboard has changed since our last look, use that instead
|
||||
if doc().cursor_clipboard["full"] ~= clipboard then
|
||||
doc().cursor_clipboard = {}
|
||||
end
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
local value = doc().cursor_clipboard[idx] or clipboard
|
||||
doc():text_input(value:gsub("\r", ""), idx)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
if col <= #indent then
|
||||
indent = indent:sub(#indent + 2 - col)
|
||||
for idx, line, col in doc():get_selections(false, true) do
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
if col <= #indent then
|
||||
indent = indent:sub(#indent + 2 - col)
|
||||
end
|
||||
doc():text_input("\n" .. indent, idx)
|
||||
end
|
||||
doc():text_input("\n" .. indent)
|
||||
end,
|
||||
|
||||
["doc:newline-below"] = function()
|
||||
local line = doc():get_selection()
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, math.huge, "\n" .. indent)
|
||||
doc():set_selection(line + 1, math.huge)
|
||||
for idx, line in doc():get_selections(false, true) do
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, math.huge, "\n" .. indent)
|
||||
doc():set_selections(idx, line + 1, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline-above"] = function()
|
||||
local line = doc():get_selection()
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, 1, indent .. "\n")
|
||||
doc():set_selection(line, math.huge)
|
||||
for idx, line in doc():get_selections(false, true) do
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, 1, indent .. "\n")
|
||||
doc():set_selections(idx, line, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:delete"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
|
||||
doc():remove(line, col, line, math.huge)
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then
|
||||
doc():remove(line1, col1, line1, math.huge)
|
||||
end
|
||||
doc():delete_to_cursor(idx, translate.next_char)
|
||||
end
|
||||
doc():delete_to(translate.next_char)
|
||||
end,
|
||||
|
||||
["doc:backspace"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
if not doc():has_selection() then
|
||||
local text = doc():get_text(line, 1, line, col)
|
||||
if #text >= config.indent_size and text:find("^ *$") then
|
||||
doc():delete_to(0, -config.indent_size)
|
||||
return
|
||||
local _, indent_size = doc():get_indent_info()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 == line2 and col1 == col2 then
|
||||
local text = doc():get_text(line1, 1, line1, col1)
|
||||
if #text >= indent_size and text:find("^ *$") then
|
||||
doc():delete_to_cursor(idx, 0, -indent_size)
|
||||
return
|
||||
end
|
||||
end
|
||||
doc():delete_to_cursor(idx, translate.previous_char)
|
||||
end
|
||||
doc():delete_to(translate.previous_char)
|
||||
end,
|
||||
|
||||
["doc:select-all"] = function()
|
||||
doc():set_selection(1, 1, math.huge, math.huge)
|
||||
end,
|
||||
|
||||
["doc:select-none"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
doc():set_selection(line, col)
|
||||
end,
|
||||
|
||||
["doc:select-lines"] = function()
|
||||
local line1, _, line2, _, swap = doc():get_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
doc():set_selection(line1, 1, line2 + 1, 1, swap)
|
||||
for idx, line1, _, line2 in doc():get_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
doc():set_selections(idx, line1, 1, line2 + 1, 1)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:select-word"] = function()
|
||||
local line1, col1 = doc():get_selection(true)
|
||||
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
for idx, line1, col1 in doc():get_selections(true) do
|
||||
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
||||
doc():set_selections(idx, line2, col2, line1, col1)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:join-lines"] = function()
|
||||
local line1, _, line2 = doc():get_selection(true)
|
||||
if line1 == line2 then line2 = line2 + 1 end
|
||||
local text = doc():get_text(line1, 1, line2, math.huge)
|
||||
text = text:gsub("(.-)\n[\t ]*", function(x)
|
||||
return x:find("^%s*$") and x or x .. " "
|
||||
end)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():remove(line1, #text + 1, line2, math.huge)
|
||||
if doc():has_selection() then
|
||||
doc():set_selection(line1, math.huge)
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 == line2 then line2 = line2 + 1 end
|
||||
local text = doc():get_text(line1, 1, line2, math.huge)
|
||||
text = text:gsub("(.-)\n[\t ]*", function(x)
|
||||
return x:find("^%s*$") and x or x .. " "
|
||||
end)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():remove(line1, #text + 1, line2, math.huge)
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
doc():set_selections(idx, line1, math.huge)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:indent"] = function()
|
||||
local text = get_indent_string()
|
||||
if doc():has_selection() then
|
||||
insert_at_start_of_selected_lines(text)
|
||||
else
|
||||
doc():text_input(text)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
||||
if l1 then
|
||||
doc():set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:unindent"] = function()
|
||||
local text = get_indent_string()
|
||||
remove_from_start_of_selected_lines(text)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2)
|
||||
if l1 then
|
||||
doc():set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:duplicate-lines"] = function()
|
||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
local n = line2 - line1 + 1
|
||||
doc():set_selection(line1 + n, col1, line2 + n, col2, swap)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
local n = line2 - line1 + 1
|
||||
doc():set_selections(idx, line1 + n, col1, line2 + n, col2)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:delete-lines"] = function()
|
||||
local line1, col1, line2 = doc_multiline_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
doc():remove(line1, 1, line2 + 1, 1)
|
||||
doc():set_selection(line1, col1)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
doc():remove(line1, 1, line2 + 1, 1)
|
||||
doc():set_selections(idx, line1, col1)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:move-lines-up"] = function()
|
||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
if line1 > 1 then
|
||||
local text = doc().lines[line1 - 1]
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
doc():remove(line1 - 1, 1, line1, 1)
|
||||
doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
if line1 > 1 then
|
||||
local text = doc().lines[line1 - 1]
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
doc():remove(line1 - 1, 1, line1, 1)
|
||||
doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:move-lines-down"] = function()
|
||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||
append_line_if_last_line(line2 + 1)
|
||||
if line2 < #doc().lines then
|
||||
local text = doc().lines[line2 + 1]
|
||||
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2 + 1)
|
||||
if line2 < #doc().lines then
|
||||
local text = doc().lines[line2 + 1]
|
||||
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:toggle-line-comments"] = function()
|
||||
local comment = doc().syntax.comment
|
||||
if not comment then return end
|
||||
local indentation = doc():get_indent_string()
|
||||
local comment_text = comment .. " "
|
||||
local line1, _, line2 = doc():get_selection(true)
|
||||
local uncomment = true
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
|
||||
uncomment = false
|
||||
for idx, line1, _, line2 in doc_multiline_selections(true) do
|
||||
local uncomment = true
|
||||
local start_offset = math.huge
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
local cs, ce = text:find(comment_text, s, true)
|
||||
if s and cs ~= s then
|
||||
uncomment = false
|
||||
start_offset = math.min(start_offset, s)
|
||||
end
|
||||
end
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
if uncomment then
|
||||
local cs, ce = text:find(comment_text, s, true)
|
||||
if ce then
|
||||
doc():remove(line, cs, line, ce + 1)
|
||||
end
|
||||
elseif s then
|
||||
doc():insert(line, start_offset, comment_text)
|
||||
end
|
||||
end
|
||||
end
|
||||
if uncomment then
|
||||
remove_from_start_of_selected_lines(comment_text, true)
|
||||
else
|
||||
insert_at_start_of_selected_lines(comment_text, true)
|
||||
end
|
||||
end,
|
||||
|
||||
|
@ -296,8 +339,12 @@ local commands = {
|
|||
end,
|
||||
|
||||
["doc:save-as"] = function()
|
||||
local last_doc = core.last_active_view and core.last_active_view.doc
|
||||
if doc().filename then
|
||||
core.command_view:set_text(doc().filename)
|
||||
elseif last_doc and last_doc.filename then
|
||||
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Save As", function(filename)
|
||||
save(common.home_expand(filename))
|
||||
|
@ -314,7 +361,7 @@ local commands = {
|
|||
end
|
||||
end,
|
||||
|
||||
["doc:rename"] = function()
|
||||
["file:rename"] = function()
|
||||
local old_filename = doc().filename
|
||||
if not old_filename then
|
||||
core.error("Cannot rename unsaved doc")
|
||||
|
@ -322,13 +369,65 @@ local commands = {
|
|||
end
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(filename)
|
||||
doc():save(filename)
|
||||
save(common.home_expand(filename))
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
if filename ~= old_filename then
|
||||
os.remove(old_filename)
|
||||
end
|
||||
end, common.path_suggest)
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end)
|
||||
end,
|
||||
|
||||
|
||||
["file:delete"] = function()
|
||||
local filename = doc().abs_filename
|
||||
if not filename then
|
||||
core.error("Cannot remove unsaved doc")
|
||||
return
|
||||
end
|
||||
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
|
||||
local node = core.root_view.root_node:get_node_for_view(docview)
|
||||
node:close_view(core.root_view, docview)
|
||||
end
|
||||
os.remove(filename)
|
||||
core.log("Removed \"%s\"", filename)
|
||||
end,
|
||||
|
||||
["doc:select-to-cursor"] = function(x, y, clicks)
|
||||
local line1, col1 = select(3, doc():get_selection())
|
||||
local line2, col2 = dv():resolve_screen_position(x, y)
|
||||
dv().mouse_selecting = { line1, col1, nil }
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
end,
|
||||
|
||||
["doc:set-cursor"] = function(x, y)
|
||||
set_cursor(x, y, "set")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-word"] = function(x, y)
|
||||
set_cursor(x, y, "word")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-line"] = function(x, y, clicks)
|
||||
set_cursor(x, y, "lines")
|
||||
end,
|
||||
|
||||
["doc:split-cursor"] = function(x, y, clicks)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
doc():add_selection(line, col, line, col)
|
||||
end,
|
||||
|
||||
["doc:create-cursor-previous-line"] = function()
|
||||
split_cursor(-1)
|
||||
doc():merge_cursors()
|
||||
end,
|
||||
|
||||
["doc:create-cursor-next-line"] = function()
|
||||
split_cursor(1)
|
||||
doc():merge_cursors()
|
||||
end
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -344,6 +443,7 @@ local translations = {
|
|||
["start-of-line"] = translate.start_of_line,
|
||||
["end-of-line"] = translate.end_of_line,
|
||||
["start-of-word"] = translate.start_of_word,
|
||||
["start-of-indentation"] = translate.start_of_indentation,
|
||||
["end-of-word"] = translate.end_of_word,
|
||||
["previous-line"] = DocView.translate.previous_line,
|
||||
["next-line"] = DocView.translate.next_line,
|
||||
|
@ -358,21 +458,24 @@ for name, fn in pairs(translations) do
|
|||
end
|
||||
|
||||
commands["doc:move-to-previous-char"] = function()
|
||||
if doc():has_selection() then
|
||||
local line, col = doc():get_selection(true)
|
||||
doc():set_selection(line, col)
|
||||
else
|
||||
doc():move_to(translate.previous_char)
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
doc():set_selections(idx, line1, col1)
|
||||
end
|
||||
end
|
||||
doc():move_to(translate.previous_char)
|
||||
end
|
||||
|
||||
commands["doc:move-to-next-char"] = function()
|
||||
if doc():has_selection() then
|
||||
local _, _, line, col = doc():get_selection(true)
|
||||
doc():set_selection(line, col)
|
||||
else
|
||||
doc():move_to(translate.next_char)
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
doc():set_selections(idx, line2, col2)
|
||||
end
|
||||
end
|
||||
doc():move_to(translate.next_char)
|
||||
end
|
||||
|
||||
command.add("core.docview", commands)
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
|
||||
end ,selection_commands)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
|
||||
command.add(nil, {
|
||||
["files:create-directory"] = function()
|
||||
core.command_view:enter("New directory name", function(text)
|
||||
local success, err = system.mkdir(text)
|
||||
local success, err, path = common.mkdirp(text)
|
||||
if not success then
|
||||
core.error("cannot create directory %q: %s", text, err)
|
||||
core.error("cannot create directory %q: %s", path, err)
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
|
|
@ -2,67 +2,85 @@ local core = require "core"
|
|||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local search = require "core.doc.search"
|
||||
local keymap = require "core.keymap"
|
||||
local DocView = require "core.docview"
|
||||
local CommandView = require "core.commandview"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
local max_previous_finds = 50
|
||||
local last_view, last_fn, last_text, last_sel
|
||||
|
||||
local case_sensitive = config.find_case_sensitive or false
|
||||
local find_regex = config.find_regex or false
|
||||
local found_expression
|
||||
|
||||
local function doc()
|
||||
return core.active_view.doc
|
||||
local is_DocView = core.active_view:is(DocView) and not core.active_view:is(CommandView)
|
||||
return is_DocView and core.active_view.doc or (last_view and last_view.doc)
|
||||
end
|
||||
|
||||
local function get_find_tooltip()
|
||||
local rf = keymap.get_binding("find-replace:repeat-find")
|
||||
local ti = keymap.get_binding("find-replace:toggle-sensitivity")
|
||||
local tr = keymap.get_binding("find-replace:toggle-regex")
|
||||
return (find_regex and "[Regex] " or "") ..
|
||||
(case_sensitive and "[Sensitive] " or "") ..
|
||||
(rf and ("Press " .. rf .. " to select the next match.") or "") ..
|
||||
(ti and (" " .. ti .. " toggles case sensitivity.") or "") ..
|
||||
(tr and (" " .. tr .. " toggles regex find.") or "")
|
||||
end
|
||||
|
||||
local function update_preview(sel, search_fn, text)
|
||||
local ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
|
||||
sel[1], sel[2], text, case_sensitive, find_regex)
|
||||
if ok and line1 and text ~= "" then
|
||||
last_view.doc:set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
found_expression = true
|
||||
else
|
||||
last_view.doc:set_selection(table.unpack(sel))
|
||||
found_expression = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local previous_finds
|
||||
local last_doc
|
||||
local last_fn, last_text
|
||||
|
||||
|
||||
local function push_previous_find(doc, sel)
|
||||
if last_doc ~= doc then
|
||||
last_doc = doc
|
||||
previous_finds = {}
|
||||
local function insert_unique(t, v)
|
||||
local n = #t
|
||||
for i = 1, n do
|
||||
if t[i] == v then return end
|
||||
end
|
||||
if #previous_finds >= max_previous_finds then
|
||||
table.remove(previous_finds, 1)
|
||||
end
|
||||
table.insert(previous_finds, sel or { doc:get_selection() })
|
||||
t[n + 1] = v
|
||||
end
|
||||
|
||||
|
||||
local function find(label, search_fn)
|
||||
local dv = core.active_view
|
||||
local sel = { dv.doc:get_selection() }
|
||||
local text = dv.doc:get_text(table.unpack(sel))
|
||||
local found = false
|
||||
last_view, last_sel = core.active_view,
|
||||
{ core.active_view.doc:get_selection() }
|
||||
local text = last_view.doc:get_text(unpack(last_sel))
|
||||
found_expression = false
|
||||
|
||||
core.command_view:set_text(text, true)
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
|
||||
core.command_view:enter(label, function(text)
|
||||
if found then
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(label, function(text, item)
|
||||
insert_unique(core.previous_find, text)
|
||||
core.status_view:remove_tooltip()
|
||||
if found_expression then
|
||||
last_fn, last_text = search_fn, text
|
||||
previous_finds = {}
|
||||
push_previous_find(dv.doc, sel)
|
||||
else
|
||||
core.error("Couldn't find %q", text)
|
||||
dv.doc:set_selection(table.unpack(sel))
|
||||
dv:scroll_to_make_visible(sel[1], sel[2])
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
|
||||
end, function(text)
|
||||
local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text)
|
||||
if ok and line1 and text ~= "" then
|
||||
dv.doc:set_selection(line2, col2, line1, col1)
|
||||
dv:scroll_to_line(line2, true)
|
||||
found = true
|
||||
else
|
||||
dv.doc:set_selection(table.unpack(sel))
|
||||
found = false
|
||||
end
|
||||
|
||||
update_preview(last_sel, search_fn, text)
|
||||
last_fn, last_text = search_fn, text
|
||||
return core.previous_find
|
||||
end, function(explicit)
|
||||
core.status_view:remove_tooltip()
|
||||
if explicit then
|
||||
dv.doc:set_selection(table.unpack(sel))
|
||||
dv:scroll_to_make_visible(sel[1], sel[2])
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
@ -71,82 +89,113 @@ end
|
|||
local function replace(kind, default, fn)
|
||||
core.command_view:set_text(default, true)
|
||||
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||
insert_unique(core.previous_find, old)
|
||||
core.command_view:set_text(old, true)
|
||||
|
||||
local s = string.format("Replace %s %q With", kind, old)
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(s, function(new)
|
||||
core.status_view:remove_tooltip()
|
||||
insert_unique(core.previous_replace, new)
|
||||
local n = doc():replace(function(text)
|
||||
return fn(text, old, new)
|
||||
end)
|
||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||
end, function() return core.previous_replace end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end, function() return core.previous_find end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function has_selection()
|
||||
return core.active_view:is(DocView)
|
||||
and core.active_view.doc:has_selection()
|
||||
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
||||
end
|
||||
|
||||
command.add(has_selection, {
|
||||
["find-replace:select-next"] = function()
|
||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
local l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
||||
local function has_unique_selection()
|
||||
if not doc() then return false end
|
||||
local text = nil
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
||||
if line1 == line2 and col1 == col2 then return false end
|
||||
local selection = doc():get_text(line1, col1, line2, col2)
|
||||
if text ~= nil and text ~= selection then return false end
|
||||
text = selection
|
||||
end
|
||||
return text ~= nil
|
||||
end
|
||||
|
||||
local function is_in_selection(line, col, l1, c1, l2, c2)
|
||||
if line < l1 or line > l2 then return false end
|
||||
if line == l1 and col <= c1 then return false end
|
||||
if line == l2 and col > c2 then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
local function is_in_any_selection(line, col)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
|
||||
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function select_add_next(all)
|
||||
local il1, ic1 = doc():get_selection(true)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
repeat
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l1 == il1 and c1 == ic1 then break end
|
||||
if l2 and (all or not is_in_any_selection(l2, c2)) then
|
||||
doc():add_selection(l2, c2, l1, c1)
|
||||
if not all then
|
||||
core.active_view:scroll_to_make_visible(l2, c2)
|
||||
return
|
||||
end
|
||||
end
|
||||
until not all or not l2
|
||||
if all then break end
|
||||
end
|
||||
end
|
||||
|
||||
local function select_next(reverse)
|
||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
if reverse then
|
||||
l1, c1, l2, c2 = search.find(doc(), l1, c1, text, { wrap = true, reverse = true })
|
||||
else
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
end
|
||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
||||
end
|
||||
|
||||
command.add(has_unique_selection, {
|
||||
["find-replace:select-next"] = select_next,
|
||||
["find-replace:select-previous"] = function() select_next(true) end,
|
||||
["find-replace:select-add-next"] = select_add_next,
|
||||
["find-replace:select-add-all"] = function() select_add_next(true) end
|
||||
})
|
||||
|
||||
command.add("core.docview", {
|
||||
["find-replace:find"] = function()
|
||||
find("Find Text", function(doc, line, col, text)
|
||||
local opt = { wrap = true, no_case = true }
|
||||
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse)
|
||||
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }
|
||||
return search.find(doc, line, col, text, opt)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:find-pattern"] = function()
|
||||
find("Find Text Pattern", function(doc, line, col, text)
|
||||
local opt = { wrap = true, no_case = true, pattern = true }
|
||||
return search.find(doc, line, col, text, opt)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:repeat-find"] = function()
|
||||
if not last_fn then
|
||||
core.error("No find to continue from")
|
||||
else
|
||||
local line, col = doc():get_selection()
|
||||
local line1, col1, line2, col2 = last_fn(doc(), line, col, last_text)
|
||||
if line1 then
|
||||
push_previous_find(doc())
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
core.active_view:scroll_to_line(line2, true)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["find-replace:previous-find"] = function()
|
||||
local sel = table.remove(previous_finds)
|
||||
if not sel or doc() ~= last_doc then
|
||||
core.error("No previous finds")
|
||||
return
|
||||
end
|
||||
doc():set_selection(table.unpack(sel))
|
||||
core.active_view:scroll_to_line(sel[3], true)
|
||||
end,
|
||||
|
||||
["find-replace:replace"] = function()
|
||||
replace("Text", "", function(text, old, new)
|
||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:replace-pattern"] = function()
|
||||
replace("Pattern", "", function(text, old, new)
|
||||
return text:gsub(old, new)
|
||||
local l1, c1, l2, c2 = doc():get_selection()
|
||||
local selected_text = doc():get_text(l1, c1, l2, c2)
|
||||
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
|
||||
if not find_regex then
|
||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
end
|
||||
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
|
||||
return result, #matches
|
||||
end)
|
||||
end,
|
||||
|
||||
|
@ -168,3 +217,53 @@ command.add("core.docview", {
|
|||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
local function valid_for_finding()
|
||||
return core.active_view:is(DocView) or core.active_view:is(CommandView)
|
||||
end
|
||||
|
||||
command.add(valid_for_finding, {
|
||||
["find-replace:repeat-find"] = function()
|
||||
if not last_fn then
|
||||
core.error("No find to continue from")
|
||||
else
|
||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex, false)
|
||||
if line1 then
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
else
|
||||
core.error("Couldn't find %q", last_text)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["find-replace:previous-find"] = function()
|
||||
if not last_fn then
|
||||
core.error("No find to continue from")
|
||||
else
|
||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc1, last_text, case_sensitive, find_regex, true)
|
||||
if line1 then
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
else
|
||||
core.error("Couldn't find %q", last_text)
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
command.add("core.commandview", {
|
||||
["find-replace:toggle-sensitivity"] = function()
|
||||
case_sensitive = not case_sensitive
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
||||
end,
|
||||
|
||||
["find-replace:toggle-regex"] = function()
|
||||
find_regex = not find_regex
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
||||
end
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@ local style = require "core.style"
|
|||
local DocView = require "core.docview"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
||||
|
||||
local t = {
|
||||
|
@ -11,10 +12,25 @@ local t = {
|
|||
node:close_active_view(core.root_view.root_node)
|
||||
end,
|
||||
|
||||
["root:close-all"] = function()
|
||||
core.confirm_close_all(core.root_view.close_all_docviews, core.root_view)
|
||||
["root:close-or-quit"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
if node and (not node:is_empty() or not node.is_primary_node) then
|
||||
node:close_active_view(core.root_view.root_node)
|
||||
else
|
||||
core.quit()
|
||||
end
|
||||
end,
|
||||
|
||||
["root:close-all"] = function()
|
||||
core.confirm_close_docs(core.docs, core.root_view.close_all_docviews, core.root_view)
|
||||
end,
|
||||
|
||||
["root:close-all-others"] = function()
|
||||
local active_doc, docs = core.active_view and core.active_view.doc, {}
|
||||
for i, v in ipairs(core.docs) do if v ~= active_doc then table.insert(docs, v) end end
|
||||
core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true)
|
||||
end,
|
||||
|
||||
["root:switch-to-previous-tab"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
|
@ -48,7 +64,7 @@ local t = {
|
|||
table.insert(node.views, idx + 1, core.active_view)
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
["root:shrink"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local parent = node:get_parent_node(core.root_view.root_node)
|
||||
|
@ -61,7 +77,7 @@ local t = {
|
|||
local parent = node:get_parent_node(core.root_view.root_node)
|
||||
local n = (parent.a == node) and 0.1 or -0.1
|
||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||
end,
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
|
@ -97,7 +113,8 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
|
|||
y = node.position.y + (dir == "up" and -1 or node.size.y + style.divider_size)
|
||||
end
|
||||
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
|
||||
if not node:get_locked_size() then
|
||||
local sx, sy = node:get_locked_size()
|
||||
if not sx and not sy then
|
||||
core.set_active_view(node.active_view)
|
||||
end
|
||||
end
|
||||
|
@ -105,5 +122,17 @@ end
|
|||
|
||||
command.add(function()
|
||||
local node = core.root_view:get_active_node()
|
||||
return not node:get_locked_size()
|
||||
local sx, sy = node:get_locked_size()
|
||||
return not sx and not sy
|
||||
end, t)
|
||||
|
||||
command.add(nil, {
|
||||
["root:scroll"] = function(delta)
|
||||
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
||||
if view and view.scrollable then
|
||||
view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
})
|
||||
|
|
|
@ -15,6 +15,8 @@ end
|
|||
|
||||
local CommandView = DocView:extend()
|
||||
|
||||
CommandView.context = "application"
|
||||
|
||||
local max_suggestions = 10
|
||||
|
||||
local noop = function() end
|
||||
|
@ -23,6 +25,7 @@ local default_state = {
|
|||
submit = noop,
|
||||
suggest = noop,
|
||||
cancel = noop,
|
||||
validate = function() return true end
|
||||
}
|
||||
|
||||
|
||||
|
@ -31,6 +34,7 @@ function CommandView:new()
|
|||
self.suggestion_idx = 1
|
||||
self.suggestions = {}
|
||||
self.suggestions_height = 0
|
||||
self.show_suggestions = true
|
||||
self.last_change_id = 0
|
||||
self.gutter_width = 0
|
||||
self.gutter_text_brightness = 0
|
||||
|
@ -42,6 +46,11 @@ function CommandView:new()
|
|||
end
|
||||
|
||||
|
||||
function CommandView:set_hidden_suggestions()
|
||||
self.show_suggestions = false
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_name()
|
||||
return View.get_name(self)
|
||||
end
|
||||
|
@ -80,10 +89,29 @@ end
|
|||
|
||||
|
||||
function CommandView:move_suggestion_idx(dir)
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
if self.show_suggestions then
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
else
|
||||
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
|
||||
local text = self:get_text()
|
||||
if text == current_suggestion then
|
||||
local n = self.suggestion_idx + dir
|
||||
if n == 0 and self.save_suggestion then
|
||||
self:set_text(self.save_suggestion)
|
||||
else
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
end
|
||||
else
|
||||
self.save_suggestion = text
|
||||
self:complete()
|
||||
end
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
self.state.suggest(self:get_text())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -97,13 +125,15 @@ end
|
|||
function CommandView:submit()
|
||||
local suggestion = self.suggestions[self.suggestion_idx]
|
||||
local text = self:get_text()
|
||||
local submit = self.state.submit
|
||||
self:exit(true)
|
||||
submit(text, suggestion)
|
||||
if self.state.validate(text) then
|
||||
local submit = self.state.submit
|
||||
self:exit(true)
|
||||
submit(text, suggestion)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function CommandView:enter(text, submit, suggest, cancel)
|
||||
function CommandView:enter(text, submit, suggest, cancel, validate)
|
||||
if self.state ~= default_state then
|
||||
return
|
||||
end
|
||||
|
@ -111,6 +141,7 @@ function CommandView:enter(text, submit, suggest, cancel)
|
|||
submit = submit or noop,
|
||||
suggest = suggest or noop,
|
||||
cancel = cancel or noop,
|
||||
validate = validate or function() return true end
|
||||
}
|
||||
core.set_active_view(self)
|
||||
self:update_suggestions()
|
||||
|
@ -128,6 +159,8 @@ function CommandView:exit(submitted, inexplicit)
|
|||
self.doc:reset()
|
||||
self.suggestions = {}
|
||||
if not submitted then cancel(not inexplicit) end
|
||||
self.show_suggestions = true
|
||||
self.save_suggestion = nil
|
||||
end
|
||||
|
||||
|
||||
|
@ -181,7 +214,7 @@ function CommandView:update()
|
|||
|
||||
-- update suggestions box height
|
||||
local lh = self:get_suggestion_line_height()
|
||||
local dest = math.min(#self.suggestions, max_suggestions) * lh
|
||||
local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
||||
self:move_towards("suggestions_height", dest)
|
||||
|
||||
-- update suggestion cursor offset
|
||||
|
@ -250,7 +283,9 @@ end
|
|||
|
||||
function CommandView:draw()
|
||||
CommandView.super.draw(self)
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
if self.show_suggestions then
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
local common = {}
|
||||
|
||||
|
||||
function common.is_utf8_cont(char)
|
||||
local byte = char:byte()
|
||||
function common.is_utf8_cont(s, offset)
|
||||
local byte = s:byte(offset or 1)
|
||||
return byte >= 0x80 and byte < 0xc0
|
||||
end
|
||||
|
||||
|
@ -22,6 +22,13 @@ function common.round(n)
|
|||
end
|
||||
|
||||
|
||||
function common.find_index(tbl, prop)
|
||||
for i, o in ipairs(tbl) do
|
||||
if o[prop] then return i end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function common.lerp(a, b, t)
|
||||
if type(a) ~= "table" then
|
||||
return a + (b - a) * t
|
||||
|
@ -34,34 +41,59 @@ function common.lerp(a, b, t)
|
|||
end
|
||||
|
||||
|
||||
function common.distance(x1, y1, x2, y2)
|
||||
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
|
||||
end
|
||||
|
||||
|
||||
function common.color(str)
|
||||
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||
local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$")
|
||||
if r then
|
||||
r = tonumber(r, 16)
|
||||
g = tonumber(g, 16)
|
||||
b = tonumber(b, 16)
|
||||
a = 1
|
||||
a = tonumber(a, 16) or 0xff
|
||||
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
|
||||
local f = str:gmatch("[%d.]+")
|
||||
r = (f() or 0)
|
||||
g = (f() or 0)
|
||||
b = (f() or 0)
|
||||
a = f() or 1
|
||||
a = (f() or 1) * 0xff
|
||||
else
|
||||
error(string.format("bad color string '%s'", str))
|
||||
end
|
||||
return r, g, b, a * 0xff
|
||||
return r, g, b, a
|
||||
end
|
||||
|
||||
|
||||
function common.splice(t, at, remove, insert)
|
||||
insert = insert or {}
|
||||
local offset = #insert - remove
|
||||
local old_len = #t
|
||||
if offset < 0 then
|
||||
for i = at - offset, old_len - offset do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
elseif offset > 0 then
|
||||
for i = old_len, at, -1 do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
end
|
||||
for i, item in ipairs(insert) do
|
||||
t[at + i - 1] = item
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function compare_score(a, b)
|
||||
return a.score > b.score
|
||||
end
|
||||
|
||||
local function fuzzy_match_items(items, needle)
|
||||
local function fuzzy_match_items(items, needle, files)
|
||||
local res = {}
|
||||
for _, item in ipairs(items) do
|
||||
local score = system.fuzzy_match(tostring(item), needle)
|
||||
local score = system.fuzzy_match(tostring(item), needle, files)
|
||||
if score then
|
||||
table.insert(res, { text = item, score = score })
|
||||
end
|
||||
|
@ -74,11 +106,11 @@ local function fuzzy_match_items(items, needle)
|
|||
end
|
||||
|
||||
|
||||
function common.fuzzy_match(haystack, needle)
|
||||
function common.fuzzy_match(haystack, needle, files)
|
||||
if type(haystack) == "table" then
|
||||
return fuzzy_match_items(haystack, needle)
|
||||
return fuzzy_match_items(haystack, needle, files)
|
||||
end
|
||||
return system.fuzzy_match(haystack, needle)
|
||||
return system.fuzzy_match(haystack, needle, files)
|
||||
end
|
||||
|
||||
|
||||
|
@ -86,16 +118,16 @@ function common.fuzzy_match_with_recents(haystack, recents, needle)
|
|||
if needle == "" then
|
||||
local recents_ext = {}
|
||||
for i = 2, #recents do
|
||||
table.insert(recents_ext, recents[i])
|
||||
table.insert(recents_ext, recents[i])
|
||||
end
|
||||
table.insert(recents_ext, recents[1])
|
||||
local others = common.fuzzy_match(haystack, "")
|
||||
local others = common.fuzzy_match(haystack, "", true)
|
||||
for i = 1, #others do
|
||||
table.insert(recents_ext, others[i])
|
||||
end
|
||||
return recents_ext
|
||||
else
|
||||
return fuzzy_match_items(haystack, needle)
|
||||
return fuzzy_match_items(haystack, needle, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -203,11 +235,19 @@ function common.basename(path)
|
|||
end
|
||||
|
||||
|
||||
-- can return nil if there is no directory part in the path
|
||||
function common.dirname(path)
|
||||
return path:match("(.+)[\\/][^\\/]+$")
|
||||
end
|
||||
|
||||
|
||||
function common.home_encode(text)
|
||||
if HOME then
|
||||
local n = #HOME
|
||||
if text:sub(1, n) == HOME and text:sub(n + 1, n + 1):match("[/\\\\]") then
|
||||
return "~" .. text:sub(n + 1)
|
||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||
local dir_pos = #HOME + 1
|
||||
-- ensure we don't replace if the text is just "$HOME" or "$HOME/" so
|
||||
-- it must have a "/" following the $HOME and some characters following.
|
||||
if string.find(text, PATHSEP, dir_pos, true) == dir_pos and #text > dir_pos then
|
||||
return "~" .. text:sub(dir_pos)
|
||||
end
|
||||
end
|
||||
return text
|
||||
|
@ -228,18 +268,11 @@ function common.home_expand(text)
|
|||
end
|
||||
|
||||
|
||||
function common.normalize_path(filename)
|
||||
if PATHSEP == '\\' then
|
||||
filename = filename:gsub('[/\\]', '\\')
|
||||
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
||||
return drive and drive:upper() .. rem or filename
|
||||
end
|
||||
return filename
|
||||
end
|
||||
|
||||
|
||||
local function split_on_slash(s, sep_pattern)
|
||||
local t = {}
|
||||
if s:match("^[/\\]") then
|
||||
t[#t + 1] = ""
|
||||
end
|
||||
for fragment in string.gmatch(s, "([^/\\]+)") do
|
||||
t[#t + 1] = fragment
|
||||
end
|
||||
|
@ -247,7 +280,76 @@ local function split_on_slash(s, sep_pattern)
|
|||
end
|
||||
|
||||
|
||||
-- The filename argument given to the function is supposed to
|
||||
-- come from system.absolute_path and as such should be an
|
||||
-- absolute path without . or .. elements.
|
||||
-- This function exists because on Windows the drive letter returned
|
||||
-- by system.absolute_path is sometimes with a lower case and sometimes
|
||||
-- with an upper case to we normalize to upper case.
|
||||
function common.normalize_volume(filename)
|
||||
if not filename then return end
|
||||
if PATHSEP == '\\' then
|
||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
|
||||
if drive then
|
||||
return drive:upper() .. rem
|
||||
end
|
||||
end
|
||||
return filename
|
||||
end
|
||||
|
||||
|
||||
function common.normalize_path(filename)
|
||||
if not filename then return end
|
||||
local volume
|
||||
if PATHSEP == '\\' then
|
||||
filename = filename:gsub('[/\\]', '\\')
|
||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
|
||||
if drive then
|
||||
volume, filename = drive:upper(), rem
|
||||
else
|
||||
drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)')
|
||||
if drive then
|
||||
volume, filename = drive, rem
|
||||
end
|
||||
end
|
||||
else
|
||||
local relpath = filename:match('^/(.+)')
|
||||
if relpath then
|
||||
volume, filename = "/", relpath
|
||||
end
|
||||
end
|
||||
local parts = split_on_slash(filename, PATHSEP)
|
||||
local accu = {}
|
||||
for _, part in ipairs(parts) do
|
||||
if part == '..' then
|
||||
if #accu > 0 and accu[#accu] ~= ".." then
|
||||
table.remove(accu)
|
||||
elseif volume then
|
||||
error("invalid path " .. volume .. filename)
|
||||
else
|
||||
table.insert(accu, part)
|
||||
end
|
||||
elseif part ~= '.' then
|
||||
table.insert(accu, part)
|
||||
end
|
||||
end
|
||||
local npath = table.concat(accu, PATHSEP)
|
||||
return (volume or "") .. (npath == "" and PATHSEP or npath)
|
||||
end
|
||||
|
||||
|
||||
function common.path_belongs_to(filename, path)
|
||||
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||
end
|
||||
|
||||
|
||||
function common.relative_path(ref_dir, dir)
|
||||
local drive_pattern = "^(%a):\\"
|
||||
local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern)
|
||||
if drive and ref_drive and drive ~= ref_drive then
|
||||
-- Windows, different drives, system.absolute_path fails for C:\..\D:\
|
||||
return dir
|
||||
end
|
||||
local ref_ls = split_on_slash(ref_dir)
|
||||
local dir_ls = split_on_slash(dir)
|
||||
local i = 1
|
||||
|
@ -259,11 +361,80 @@ function common.relative_path(ref_dir, dir)
|
|||
end
|
||||
local ups = ""
|
||||
for k = i, #ref_ls do
|
||||
ups = ups .. "../"
|
||||
ups = ups .. ".." .. PATHSEP
|
||||
end
|
||||
local rel_path = ups .. table.concat(dir_ls, "/", i)
|
||||
local rel_path = ups .. table.concat(dir_ls, PATHSEP, i)
|
||||
return rel_path ~= "" and rel_path or "."
|
||||
end
|
||||
|
||||
|
||||
function common.mkdirp(path)
|
||||
local stat = system.get_file_info(path)
|
||||
if stat and stat.type then
|
||||
return false, "path exists", path
|
||||
end
|
||||
local subdirs = {}
|
||||
while path and path ~= "" do
|
||||
local success_mkdir = system.mkdir(path)
|
||||
if success_mkdir then break end
|
||||
local updir, basedir = path:match("(.*)[/\\](.+)$")
|
||||
table.insert(subdirs, 1, basedir or path)
|
||||
path = updir
|
||||
end
|
||||
for _, dirname in ipairs(subdirs) do
|
||||
path = path and path .. PATHSEP .. dirname or dirname
|
||||
if not system.mkdir(path) then
|
||||
return false, "cannot create directory", path
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function common.rm(path, recursively)
|
||||
local stat = system.get_file_info(path)
|
||||
if not stat or (stat.type ~= "file" and stat.type ~= "dir") then
|
||||
return false, "invalid path given", path
|
||||
end
|
||||
|
||||
if stat.type == "file" then
|
||||
local removed, error = os.remove(path)
|
||||
if not removed then
|
||||
return false, error, path
|
||||
end
|
||||
else
|
||||
local contents = system.list_dir(path)
|
||||
if #contents > 0 and not recursively then
|
||||
return false, "directory is not empty", path
|
||||
end
|
||||
|
||||
for _, item in pairs(contents) do
|
||||
local item_path = path .. PATHSEP .. item
|
||||
local item_stat = system.get_file_info(item_path)
|
||||
|
||||
if not item_stat then
|
||||
return false, "invalid file encountered", item_path
|
||||
end
|
||||
|
||||
if item_stat.type == "dir" then
|
||||
local deleted, error, ipath = common.rm(item_path, recursively)
|
||||
if not deleted then
|
||||
return false, error, ipath
|
||||
end
|
||||
elseif item_stat.type == "file" then
|
||||
local removed, error = os.remove(item_path)
|
||||
if not removed then
|
||||
return false, error, item_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local removed, error = system.rmdir(path)
|
||||
if not removed then
|
||||
return false, error, path
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return common
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
local config = {}
|
||||
|
||||
config.project_scan_rate = 5
|
||||
config.fps = 60
|
||||
config.max_log_items = 80
|
||||
config.message_timeout = 3
|
||||
config.message_timeout = 5
|
||||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
config.scroll_past_end = true
|
||||
config.file_size_limit = 10
|
||||
config.ignore_files = "^%."
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
config.undo_merge_timeout = 0.3
|
||||
config.max_undos = 10000
|
||||
config.max_tabs = 8
|
||||
config.always_show_tabs = true
|
||||
config.highlight_current_line = true
|
||||
config.line_height = 1.2
|
||||
config.indent_size = 2
|
||||
|
@ -21,11 +23,18 @@ config.max_project_files = 2000
|
|||
config.transitions = true
|
||||
config.animation_rate = 1.0
|
||||
config.blink_period = 0.8
|
||||
config.disable_blink = false
|
||||
config.draw_whitespace = false
|
||||
config.borderless = false
|
||||
config.tab_close_button = true
|
||||
config.max_clicks = 3
|
||||
|
||||
-- Disable plugin loading setting to false the config entry
|
||||
-- of the same name.
|
||||
config.trimwhitespace = false
|
||||
config.plugins = {}
|
||||
|
||||
config.plugins.trimwhitespace = false
|
||||
config.plugins.lineguide = false
|
||||
config.plugins.drawwhitespace = false
|
||||
|
||||
return config
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
|
||||
local border_width = 1
|
||||
local divider_width = 1
|
||||
local DIVIDER = {}
|
||||
|
||||
local ContextMenu = Object:extend()
|
||||
|
||||
ContextMenu.DIVIDER = DIVIDER
|
||||
|
||||
function ContextMenu:new()
|
||||
self.itemset = {}
|
||||
self.show_context_menu = false
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
self.position = { x = 0, y = 0 }
|
||||
end
|
||||
|
||||
local function get_item_size(item)
|
||||
local lw, lh
|
||||
if item == DIVIDER then
|
||||
lw = 0
|
||||
lh = divider_width
|
||||
else
|
||||
lw = style.font:get_width(item.text)
|
||||
if item.info then
|
||||
lw = lw + style.padding.x + style.font:get_width(item.info)
|
||||
end
|
||||
lh = style.font:get_height() + style.padding.y
|
||||
end
|
||||
return lw, lh
|
||||
end
|
||||
|
||||
function ContextMenu:register(predicate, items)
|
||||
if type(predicate) == "string" then
|
||||
predicate = require(predicate)
|
||||
end
|
||||
if type(predicate) == "table" then
|
||||
local class = predicate
|
||||
predicate = function() return core.active_view:is(class) end
|
||||
end
|
||||
|
||||
local width, height = 0, 0 --precalculate the size of context menu
|
||||
for i, item in ipairs(items) do
|
||||
if item ~= DIVIDER then
|
||||
item.info = keymap.get_binding(item.command)
|
||||
end
|
||||
local lw, lh = get_item_size(item)
|
||||
width = math.max(width, lw)
|
||||
height = height + lh
|
||||
end
|
||||
width = width + style.padding.x * 2
|
||||
items.width, items.height = width, height
|
||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||
end
|
||||
|
||||
function ContextMenu:show(x, y)
|
||||
self.items = nil
|
||||
local items_list = { width = 0, height = 0 }
|
||||
for _, items in ipairs(self.itemset) do
|
||||
if items.predicate(x, y) then
|
||||
items_list.width = math.max(items_list.width, items.items.width)
|
||||
items_list.height = items_list.height
|
||||
for _, subitems in ipairs(items.items) do
|
||||
if not subitems.command or command.is_valid(subitems.command) then
|
||||
local lw, lh = get_item_size(subitems)
|
||||
items_list.height = items_list.height + lh
|
||||
table.insert(items_list, subitems)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #items_list > 0 then
|
||||
self.items = items_list
|
||||
local w, h = self.items.width, self.items.height
|
||||
|
||||
-- by default the box is opened on the right and below
|
||||
if x + w >= core.root_view.size.x then
|
||||
x = x - w
|
||||
end
|
||||
if y + h >= core.root_view.size.y then
|
||||
y = y - h
|
||||
end
|
||||
|
||||
self.position.x, self.position.y = x, y
|
||||
self.show_context_menu = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ContextMenu:hide()
|
||||
self.show_context_menu = false
|
||||
self.items = nil
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
end
|
||||
|
||||
function ContextMenu:each_item()
|
||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||
local oy = y
|
||||
return coroutine.wrap(function()
|
||||
for i, item in ipairs(self.items) do
|
||||
local _, lh = get_item_size(item)
|
||||
if y - oy > self.height then break end
|
||||
coroutine.yield(i, item, x, y, w, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_moved(px, py)
|
||||
if not self.show_context_menu then return end
|
||||
|
||||
self.selected = -1
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if px > x and px <= x + w and py > y and py <= y + h then
|
||||
self.selected = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if self.selected >= 0 then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function ContextMenu:on_selected(item)
|
||||
if type(item.command) == "string" then
|
||||
command.perform(item.command)
|
||||
else
|
||||
item.command()
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||
local selected = (self.items or {})[self.selected]
|
||||
local caught = false
|
||||
|
||||
self:hide()
|
||||
if button == "left" then
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
caught = true
|
||||
end
|
||||
end
|
||||
|
||||
if button == "right" then
|
||||
caught = self:show(x, y)
|
||||
end
|
||||
return caught
|
||||
end
|
||||
|
||||
-- copied from core.docview
|
||||
function ContextMenu:move_towards(t, k, dest, rate)
|
||||
if type(t) ~= "table" then
|
||||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
rate = rate or 0.5
|
||||
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
||||
local dt = 60 / config.fps
|
||||
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
||||
end
|
||||
t[k] = common.lerp(val, dest, rate)
|
||||
end
|
||||
if val ~= dest then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:update()
|
||||
if self.show_context_menu then
|
||||
self:move_towards("height", self.items.height)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:draw()
|
||||
if not self.show_context_menu then return end
|
||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||
end
|
||||
|
||||
function ContextMenu:draw_context_menu()
|
||||
if not self.items then return end
|
||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
||||
|
||||
renderer.draw_rect(
|
||||
bx - border_width,
|
||||
by - border_width,
|
||||
bw + (border_width * 2),
|
||||
bh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
||||
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if item == DIVIDER then
|
||||
renderer.draw_rect(x, y, w, h, style.caret)
|
||||
else
|
||||
if i == self.selected then
|
||||
renderer.draw_rect(x, y, w, h, style.selection)
|
||||
end
|
||||
|
||||
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
||||
if item.info then
|
||||
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ContextMenu
|
|
@ -1,4 +1,5 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local tokenizer = require "core.tokenizer"
|
||||
local Object = require "core.object"
|
||||
|
@ -24,7 +25,7 @@ function Highlighter:new(doc)
|
|||
for i = self.first_invalid_line, max do
|
||||
local state = (i > 1) and self.lines[i - 1].state
|
||||
local line = self.lines[i]
|
||||
if not (line and line.init_state == state) then
|
||||
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
|
||||
self.lines[i] = self:tokenize_line(i, state)
|
||||
end
|
||||
end
|
||||
|
@ -40,16 +41,36 @@ end
|
|||
|
||||
function Highlighter:reset()
|
||||
self.lines = {}
|
||||
self:soft_reset()
|
||||
end
|
||||
|
||||
function Highlighter:soft_reset()
|
||||
for i=1,#self.lines do
|
||||
self.lines[i] = false
|
||||
end
|
||||
self.first_invalid_line = 1
|
||||
self.max_wanted_line = 0
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:invalidate(idx)
|
||||
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
||||
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
||||
end
|
||||
|
||||
function Highlighter:insert_notify(line, n)
|
||||
self:invalidate(line)
|
||||
local blanks = { }
|
||||
for i = 1, n do
|
||||
blanks[i] = false
|
||||
end
|
||||
common.splice(self.lines, line, 0, blanks)
|
||||
end
|
||||
|
||||
function Highlighter:remove_notify(line, n)
|
||||
self:invalidate(line)
|
||||
common.splice(self.lines, line, n)
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:tokenize_line(idx, state)
|
||||
local res = {}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local Object = require "core.object"
|
||||
local Highlighter = require "core.doc.highlighter"
|
||||
local core = require "core"
|
||||
local syntax = require "core.syntax"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
|
@ -17,36 +18,22 @@ local function split_lines(text)
|
|||
end
|
||||
|
||||
|
||||
local function splice(t, at, remove, insert)
|
||||
insert = insert or {}
|
||||
local offset = #insert - remove
|
||||
local old_len = #t
|
||||
if offset < 0 then
|
||||
for i = at - offset, old_len - offset do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
elseif offset > 0 then
|
||||
for i = old_len, at, -1 do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
end
|
||||
for i, item in ipairs(insert) do
|
||||
t[at + i - 1] = item
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:new(filename)
|
||||
function Doc:new(filename, abs_filename, new_file)
|
||||
self.new_file = new_file
|
||||
self:reset()
|
||||
if filename then
|
||||
self:load(filename)
|
||||
self:set_filename(filename, abs_filename)
|
||||
if not new_file then
|
||||
self:load(filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:reset()
|
||||
self.lines = { "\n" }
|
||||
self.selection = { a = { line=1, col=1 }, b = { line=1, col=1 } }
|
||||
self.selections = { 1, 1, 1, 1 }
|
||||
self.cursor_clipboard = {}
|
||||
self.undo_stack = { idx = 1 }
|
||||
self.redo_stack = { idx = 1 }
|
||||
self.clean_change_id = 1
|
||||
|
@ -60,29 +47,30 @@ function Doc:reset_syntax()
|
|||
local syn = syntax.get(self.filename or "", header)
|
||||
if self.syntax ~= syn then
|
||||
self.syntax = syn
|
||||
self.highlighter:reset()
|
||||
self.highlighter:soft_reset()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_filename(filename)
|
||||
filename = common.normalize_path(filename)
|
||||
function Doc:set_filename(filename, abs_filename)
|
||||
self.filename = filename
|
||||
self.abs_filename = common.home_encode(system.absolute_path(filename))
|
||||
self.abs_filename = abs_filename
|
||||
end
|
||||
|
||||
|
||||
function Doc:load(filename)
|
||||
local fp = assert( io.open(filename, "rb") )
|
||||
self:reset()
|
||||
self:set_filename(filename)
|
||||
self.lines = {}
|
||||
local i = 1
|
||||
for line in fp:lines() do
|
||||
if line:byte(-1) == 13 then
|
||||
line = line:sub(1, -2)
|
||||
self.crlf = true
|
||||
end
|
||||
table.insert(self.lines, line .. "\n")
|
||||
self.highlighter.lines[i] = false
|
||||
i = i + 1
|
||||
end
|
||||
if #self.lines == 0 then
|
||||
table.insert(self.lines, "\n")
|
||||
|
@ -92,17 +80,20 @@ function Doc:load(filename)
|
|||
end
|
||||
|
||||
|
||||
function Doc:save(filename)
|
||||
filename = filename or assert(self.filename, "no filename set to default to")
|
||||
function Doc:save(filename, abs_filename)
|
||||
if not filename then
|
||||
assert(self.filename, "no filename set to default to")
|
||||
filename = self.filename
|
||||
abs_filename = self.abs_filename
|
||||
end
|
||||
local fp = assert( io.open(filename, "wb") )
|
||||
for _, line in ipairs(self.lines) do
|
||||
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||
fp:write(line)
|
||||
end
|
||||
fp:close()
|
||||
if filename then
|
||||
self:set_filename(filename)
|
||||
end
|
||||
self:set_filename(filename, abs_filename)
|
||||
self.new_file = false
|
||||
self:reset_syntax()
|
||||
self:clean()
|
||||
end
|
||||
|
@ -114,7 +105,11 @@ end
|
|||
|
||||
|
||||
function Doc:is_dirty()
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
if self.new_file then
|
||||
return #self.lines > 1 or #self.lines[1] > 1
|
||||
else
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -123,49 +118,121 @@ function Doc:clean()
|
|||
end
|
||||
|
||||
|
||||
function Doc:get_indent_info()
|
||||
if not self.indent_info then return config.tab_type, config.indent_size, false end
|
||||
return self.indent_info.type or config.tab_type,
|
||||
self.indent_info.size or config.indent_size,
|
||||
self.indent_info.confirmed
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_change_id()
|
||||
return self.undo_stack.idx
|
||||
end
|
||||
|
||||
-- Cursor section. Cursor indices are *only* valid during a get_selections() call.
|
||||
-- Cursors will always be iterated in order from top to bottom. Through normal operation
|
||||
-- curors can never swap positions; only merge or split, or change their position in cursor
|
||||
-- order.
|
||||
function Doc:get_selection(sort)
|
||||
local idx, line1, col1, line2, col2 = self:get_selections(sort)({ self.selections, sort }, 0)
|
||||
return line1, col1, line2, col2, sort
|
||||
end
|
||||
|
||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||
assert(not line2 == not col2, "expected 2 or 4 arguments")
|
||||
function Doc:get_selection_text(limit)
|
||||
limit = limit or math.huge
|
||||
local result = {}
|
||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
||||
if idx > limit then break end
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
if text ~= "" then result[#result + 1] = text end
|
||||
end
|
||||
end
|
||||
return table.concat(result, "\n")
|
||||
end
|
||||
|
||||
function Doc:has_selection()
|
||||
local line1, col1, line2, col2 = self:get_selection(false)
|
||||
return line1 ~= line2 or col1 ~= col2
|
||||
end
|
||||
|
||||
function Doc:has_any_selection()
|
||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
||||
if line1 ~= line2 or col1 ~= col2 then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Doc:sanitize_selection()
|
||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
||||
self:set_selections(idx, line1, col1, line2, col2)
|
||||
end
|
||||
end
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2 or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
|
||||
assert(not line2 == not col2, "expected 3 or 5 arguments")
|
||||
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
||||
self.selection.a.line, self.selection.a.col = line1, col1
|
||||
self.selection.b.line, self.selection.b.col = line2, col2
|
||||
common.splice(self.selections, (idx - 1)*4 + 1, rm == nil and 4 or rm, { line1, col1, line2, col2 })
|
||||
end
|
||||
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2
|
||||
or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1, true
|
||||
function Doc:add_selection(line1, col1, line2, col2, swap)
|
||||
local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1)
|
||||
local target = #self.selections / 4 + 1
|
||||
for idx, tl1, tc1 in self:get_selections(true) do
|
||||
if l1 < tl1 or l1 == tl1 and c1 < tc1 then
|
||||
target = idx
|
||||
break
|
||||
end
|
||||
end
|
||||
return line1, col1, line2, col2, false
|
||||
self:set_selections(target, line1, col1, line2, col2, swap, 0)
|
||||
end
|
||||
|
||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||
self.selections, self.cursor_clipboard = {}, {}
|
||||
self:set_selections(1, line1, col1, line2, col2, swap)
|
||||
end
|
||||
|
||||
function Doc:get_selection(sort)
|
||||
local a, b = self.selection.a, self.selection.b
|
||||
if sort then
|
||||
return sort_positions(a.line, a.col, b.line, b.col)
|
||||
function Doc:merge_cursors(idx)
|
||||
for i = (idx or (#self.selections - 3)), (idx or 5), -4 do
|
||||
for j = 1, i - 4, 4 do
|
||||
if self.selections[i] == self.selections[j] and
|
||||
self.selections[i+1] == self.selections[j+1] then
|
||||
common.splice(self.selections, i, 4)
|
||||
common.splice(self.cursor_clipboard, i, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return a.line, a.col, b.line, b.col
|
||||
if #self.selections <= 4 then self.cursor_clipboard = {} end
|
||||
end
|
||||
|
||||
|
||||
function Doc:has_selection()
|
||||
local a, b = self.selection.a, self.selection.b
|
||||
return not (a.line == b.line and a.col == b.col)
|
||||
local function selection_iterator(invariant, idx)
|
||||
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
||||
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
||||
if invariant[2] then
|
||||
return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4))
|
||||
else
|
||||
return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:sanitize_selection()
|
||||
self:set_selection(self:get_selection())
|
||||
-- If idx_reverse is true, it'll reverse iterate. If nil, or false, regular iterate.
|
||||
-- If a number, runs for exactly that iteration.
|
||||
function Doc:get_selections(sort_intra, idx_reverse)
|
||||
return selection_iterator, { self.selections, sort_intra, idx_reverse },
|
||||
idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1)+1)
|
||||
end
|
||||
|
||||
-- End of cursor seciton.
|
||||
|
||||
function Doc:sanitize_position(line, col)
|
||||
line = common.clamp(line, 1, #self.lines)
|
||||
|
@ -252,14 +319,12 @@ local function pop_undo(self, undo_stack, redo_stack, modified)
|
|||
if cmd.type == "insert" then
|
||||
local line, col, text = table.unpack(cmd)
|
||||
self:raw_insert(line, col, text, redo_stack, cmd.time)
|
||||
|
||||
elseif cmd.type == "remove" then
|
||||
local line1, col1, line2, col2 = table.unpack(cmd)
|
||||
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
||||
|
||||
elseif cmd.type == "selection" then
|
||||
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
|
||||
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
|
||||
self.selections = { table.unpack(cmd) }
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
modified = modified or (cmd.type ~= "selection")
|
||||
|
@ -280,6 +345,7 @@ end
|
|||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
-- split text into lines and merge with line at insertion point
|
||||
local lines = split_lines(text)
|
||||
local len = #lines[#lines]
|
||||
local before = self.lines[line]:sub(1, col - 1)
|
||||
local after = self.lines[line]:sub(col)
|
||||
for i = 1, #lines - 1 do
|
||||
|
@ -289,15 +355,23 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
|||
lines[#lines] = lines[#lines] .. after
|
||||
|
||||
-- splice lines into line array
|
||||
splice(self.lines, line, 1, lines)
|
||||
common.splice(self.lines, line, 1, lines)
|
||||
|
||||
-- keep cursors where they should be
|
||||
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
||||
if cline1 < line then break end
|
||||
local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0
|
||||
local column_addition = line == cline1 and ccol1 > col and len or 0
|
||||
self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition)
|
||||
end
|
||||
|
||||
-- push undo
|
||||
local line2, col2 = self:position_offset(line, col, #text)
|
||||
push_undo(undo_stack, time, "selection", self:get_selection())
|
||||
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
||||
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line)
|
||||
self.highlighter:insert_notify(line, #lines - 1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
@ -305,7 +379,7 @@ end
|
|||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
-- push undo
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
push_undo(undo_stack, time, "selection", self:get_selection())
|
||||
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
||||
push_undo(undo_stack, time, "insert", line1, col1, text)
|
||||
|
||||
-- get line content before/after removed text
|
||||
|
@ -313,10 +387,18 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
|||
local after = self.lines[line2]:sub(col2)
|
||||
|
||||
-- splice line into line array
|
||||
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||
|
||||
-- move all cursors back if they share a line with the removed text
|
||||
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
||||
if cline1 < line2 then break end
|
||||
local line_removal = line2 - line1
|
||||
local column_removal = line2 == cline2 and col2 < ccol1 and (line2 == line1 and col2 - col1 or col2) or 0
|
||||
self:set_selections(idx, cline1 - line_removal, ccol1 - column_removal, cline2 - line_removal, ccol2 - column_removal)
|
||||
end
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line1)
|
||||
self.highlighter:remove_notify(line1, line2 - line1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
@ -349,66 +431,147 @@ function Doc:redo()
|
|||
end
|
||||
|
||||
|
||||
function Doc:text_input(text)
|
||||
if self:has_selection() then
|
||||
self:delete_to()
|
||||
function Doc:text_input(text, idx)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
self:delete_to_cursor(sidx)
|
||||
end
|
||||
self:insert(line1, col1, text)
|
||||
self:move_to_cursor(sidx, #text)
|
||||
end
|
||||
local line, col = self:get_selection()
|
||||
self:insert(line, col, text)
|
||||
self:move_to(#text)
|
||||
end
|
||||
|
||||
|
||||
function Doc:replace(fn)
|
||||
local line1, col1, line2, col2, swap
|
||||
local had_selection = self:has_selection()
|
||||
if had_selection then
|
||||
line1, col1, line2, col2, swap = self:get_selection(true)
|
||||
else
|
||||
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
||||
end
|
||||
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, n = fn(old_text)
|
||||
if old_text ~= new_text then
|
||||
self:insert(line2, col2, new_text)
|
||||
self:remove(line1, col1, line2, col2)
|
||||
if had_selection then
|
||||
if line1 == line2 and col1 == col2 then
|
||||
line2, col2 = self:position_offset(line1, col1, #new_text)
|
||||
self:set_selection(line1, col1, line2, col2, swap)
|
||||
self:set_selections(idx, line1, col1, line2, col2)
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
function Doc:delete_to(...)
|
||||
local line, col = self:get_selection(true)
|
||||
if self:has_selection() then
|
||||
self:remove(self:get_selection())
|
||||
else
|
||||
local line2, col2 = self:position_offset(line, col, ...)
|
||||
self:remove(line, col, line2, col2)
|
||||
line, col = sort_positions(line, col, line2, col2)
|
||||
function Doc:replace(fn)
|
||||
local has_selection, n = false, 0
|
||||
for idx, line1, col1, line2, col2 in self:get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
has_selection = true
|
||||
end
|
||||
end
|
||||
self:set_selection(line, col)
|
||||
if not has_selection then
|
||||
self:set_selection(table.unpack(self.selections))
|
||||
n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
function Doc:move_to(...)
|
||||
local line, col = self:get_selection()
|
||||
self:set_selection(self:position_offset(line, col, ...))
|
||||
function Doc:delete_to_cursor(idx, ...)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
self:remove(line1, col1, line2, col2)
|
||||
else
|
||||
local l2, c2 = self:position_offset(line1, col1, ...)
|
||||
self:remove(line1, col1, l2, c2)
|
||||
line1, col1 = sort_positions(line1, col1, l2, c2)
|
||||
end
|
||||
self:set_selections(sidx, line1, col1)
|
||||
end
|
||||
self:merge_cursors(idx)
|
||||
end
|
||||
function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end
|
||||
|
||||
function Doc:move_to_cursor(idx, ...)
|
||||
for sidx, line, col in self:get_selections(false, idx) do
|
||||
self:set_selections(sidx, self:position_offset(line, col, ...))
|
||||
end
|
||||
self:merge_cursors(idx)
|
||||
end
|
||||
function Doc:move_to(...) return self:move_to_cursor(nil, ...) end
|
||||
|
||||
|
||||
function Doc:select_to_cursor(idx, ...)
|
||||
for sidx, line, col, line2, col2 in self:get_selections(false, idx) do
|
||||
line, col = self:position_offset(line, col, ...)
|
||||
self:set_selections(sidx, line, col, line2, col2)
|
||||
end
|
||||
self:merge_cursors(idx)
|
||||
end
|
||||
function Doc:select_to(...) return self:select_to_cursor(nil, ...) end
|
||||
|
||||
|
||||
function Doc:get_indent_string()
|
||||
local indent_type, indent_size = self:get_indent_info()
|
||||
if indent_type == "hard" then
|
||||
return "\t"
|
||||
end
|
||||
return string.rep(" ", indent_size)
|
||||
end
|
||||
|
||||
-- returns the size of the original indent, and the indent
|
||||
-- in your config format, rounded either up or down
|
||||
function Doc:get_line_indent(line, rnd_up)
|
||||
local _, e = line:find("^[ \t]+")
|
||||
local indent_type, indent_size = self:get_indent_info()
|
||||
local soft_tab = string.rep(" ", indent_size)
|
||||
if indent_type == "hard" then
|
||||
local indent = e and line:sub(1, e):gsub(soft_tab, "\t") or ""
|
||||
return e, indent:gsub(" +", rnd_up and "\t" or "")
|
||||
else
|
||||
local indent = e and line:sub(1, e):gsub("\t", soft_tab) or ""
|
||||
local number = #indent / #soft_tab
|
||||
return e, indent:sub(1,
|
||||
(rnd_up and math.ceil(number) or math.floor(number))*#soft_tab)
|
||||
end
|
||||
end
|
||||
|
||||
function Doc:select_to(...)
|
||||
local line, col, line2, col2 = self:get_selection()
|
||||
line, col = self:position_offset(line, col, ...)
|
||||
self:set_selection(line, col, line2, col2)
|
||||
-- un/indents text; behaviour varies based on selection and un/indent.
|
||||
-- * if there's a selection, it will stay static around the
|
||||
-- text for both indenting and unindenting.
|
||||
-- * if you are in the beginning whitespace of a line, and are indenting, the
|
||||
-- cursor will insert the exactly appropriate amount of spaces, and jump the
|
||||
-- cursor to the beginning of first non whitespace characters
|
||||
-- * if you are not in the beginning whitespace of a line, and you indent, it
|
||||
-- inserts the appropriate whitespace, as if you typed them normally.
|
||||
-- * if you are unindenting, the cursor will jump to the start of the line,
|
||||
-- and remove the appropriate amount of spaces (or a tab).
|
||||
function Doc:indent_text(unindent, line1, col1, line2, col2)
|
||||
local text = self:get_indent_string()
|
||||
local _, se = self.lines[line1]:find("^[ \t]+")
|
||||
local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1)
|
||||
local has_selection = line1 ~= line2 or col1 ~= col2
|
||||
if unindent or has_selection or in_beginning_whitespace then
|
||||
local l1d, l2d = #self.lines[line1], #self.lines[line2]
|
||||
for line = line1, line2 do
|
||||
local e, rnded = self:get_line_indent(self.lines[line], unindent)
|
||||
self:remove(line, 1, line, (e or 0) + 1)
|
||||
self:insert(line, 1,
|
||||
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
||||
end
|
||||
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
|
||||
if (unindent or in_beginning_whitespace) and not has_selection then
|
||||
local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1])
|
||||
return line1, start_cursor, line2, start_cursor
|
||||
end
|
||||
return line1, col1 + l1d, line2, col2 + l2d
|
||||
end
|
||||
self:insert(line1, col1, text)
|
||||
return line1, col1 + #text, line1, col1 + #text
|
||||
end
|
||||
|
||||
-- For plugins to add custom actions of document change
|
||||
function Doc:on_text_change(type)
|
||||
end
|
||||
|
||||
-- For plugins to get notified when a document is closed
|
||||
function Doc:on_close()
|
||||
core.log_quiet("Closed doc \"%s\"", self:get_name())
|
||||
end
|
||||
|
||||
|
||||
return Doc
|
||||
|
|
|
@ -15,36 +15,69 @@ local function init_args(doc, line, col, text, opt)
|
|||
opt = opt or default_opt
|
||||
line, col = doc:sanitize_position(line, col)
|
||||
|
||||
if opt.no_case then
|
||||
if opt.pattern then
|
||||
text = text:gsub("%%?.", pattern_lower)
|
||||
else
|
||||
text = text:lower()
|
||||
end
|
||||
if opt.no_case and not opt.regex then
|
||||
text = text:lower()
|
||||
end
|
||||
|
||||
return doc, line, col, text, opt
|
||||
end
|
||||
|
||||
-- This function is needed to uniform the behavior of
|
||||
-- `regex:cmatch` and `string.find`.
|
||||
local function regex_func(text, re, index, _)
|
||||
local s, e = re:cmatch(text, index)
|
||||
return s, e and e - 1
|
||||
end
|
||||
|
||||
local function rfind(func, text, pattern, index, plain)
|
||||
local s, e = func(text, pattern, 1, plain)
|
||||
local last_s, last_e
|
||||
if index < 0 then index = #text - index + 1 end
|
||||
while e and e <= index do
|
||||
last_s, last_e = s, e
|
||||
s, e = func(text, pattern, s + 1, plain)
|
||||
end
|
||||
return last_s, last_e
|
||||
end
|
||||
|
||||
|
||||
function search.find(doc, line, col, text, opt)
|
||||
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
||||
|
||||
for line = line, #doc.lines do
|
||||
local plain = not opt.pattern
|
||||
local pattern = text
|
||||
local search_func = string.find
|
||||
if opt.regex then
|
||||
pattern = regex.compile(text, opt.no_case and "i" or "")
|
||||
search_func = regex_func
|
||||
end
|
||||
local start, finish, step = line, #doc.lines, 1
|
||||
if opt.reverse then
|
||||
start, finish, step = line, 1, -1
|
||||
end
|
||||
for line = start, finish, step do
|
||||
local line_text = doc.lines[line]
|
||||
if opt.no_case then
|
||||
if opt.no_case and not opt.regex then
|
||||
line_text = line_text:lower()
|
||||
end
|
||||
local s, e = line_text:find(text, col, not opt.pattern)
|
||||
local s, e
|
||||
if opt.reverse then
|
||||
s, e = rfind(search_func, line_text, pattern, col - 1, plain)
|
||||
else
|
||||
s, e = search_func(line_text, pattern, col, plain)
|
||||
end
|
||||
if s then
|
||||
return line, s, line, e + 1
|
||||
end
|
||||
col = 1
|
||||
col = opt.reverse and -1 or 1
|
||||
end
|
||||
|
||||
if opt.wrap then
|
||||
opt = { no_case = opt.no_case, pattern = opt.pattern }
|
||||
return search.find(doc, 1, 1, text, opt)
|
||||
opt = { no_case = opt.no_case, regex = opt.regex, reverse = opt.reverse }
|
||||
if opt.reverse then
|
||||
return search.find(doc, #doc.lines, #doc.lines[#doc.lines], text, opt)
|
||||
else
|
||||
return search.find(doc, 1, 1, text, opt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -117,6 +117,10 @@ function translate.start_of_line(doc, line, col)
|
|||
return line, 1
|
||||
end
|
||||
|
||||
function translate.start_of_indentation(doc, line, col)
|
||||
local s, e = doc.lines[line]:find("^%s*")
|
||||
return line, col > e + 1 and e + 1 or 1
|
||||
end
|
||||
|
||||
function translate.end_of_line(doc, line, col)
|
||||
return line, math.huge
|
||||
|
|
|
@ -9,6 +9,7 @@ local View = require "core.view"
|
|||
|
||||
local DocView = View:extend()
|
||||
|
||||
DocView.context = "session"
|
||||
|
||||
local function move_to_line_offset(dv, line, col, offset)
|
||||
local xo = dv.last_x_offset
|
||||
|
@ -90,13 +91,16 @@ end
|
|||
function DocView:get_filename()
|
||||
if self.doc.abs_filename then
|
||||
local post = self.doc:is_dirty() and "*" or ""
|
||||
return self.doc.abs_filename .. post
|
||||
return common.home_encode(self.doc.abs_filename) .. post
|
||||
end
|
||||
return self:get_name()
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_scrollable_size()
|
||||
if not config.scroll_past_end then
|
||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
||||
end
|
||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||
end
|
||||
|
||||
|
@ -112,7 +116,8 @@ end
|
|||
|
||||
|
||||
function DocView:get_gutter_width()
|
||||
return self:get_font():get_width(#self.doc.lines) + style.padding.x * 2
|
||||
local padding = style.padding.x * 2
|
||||
return self:get_font():get_width(#self.doc.lines) + padding, padding
|
||||
end
|
||||
|
||||
|
||||
|
@ -141,29 +146,43 @@ end
|
|||
|
||||
|
||||
function DocView:get_col_x_offset(line, col)
|
||||
local text = self.doc.lines[line]
|
||||
if not text then return 0 end
|
||||
return self:get_font():get_width(text:sub(1, col - 1))
|
||||
local default_font = self:get_font()
|
||||
local column = 1
|
||||
local xoffset = 0
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
if column == col then
|
||||
return xoffset
|
||||
end
|
||||
xoffset = xoffset + font:get_width(char)
|
||||
column = column + #char
|
||||
end
|
||||
end
|
||||
|
||||
return xoffset
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_x_offset_col(line, x)
|
||||
local text = self.doc.lines[line]
|
||||
local line_text = self.doc.lines[line]
|
||||
|
||||
local xoffset, last_i, i = 0, 1, 1
|
||||
local subpixel_scale = self:get_font():subpixel_scale();
|
||||
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = self:get_font():get_width_subpixel(char)
|
||||
if xoffset >= subpixel_scale * x then
|
||||
return (xoffset - x_subpixel > w / 2) and last_i or i
|
||||
local default_font = self:get_font()
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = font:get_width(char)
|
||||
if xoffset >= x then
|
||||
return (xoffset - x > w / 2) and last_i or i
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
i = i + #char
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
i = i + #char
|
||||
end
|
||||
|
||||
return #text
|
||||
return #line_text
|
||||
end
|
||||
|
||||
|
||||
|
@ -206,47 +225,6 @@ function DocView:scroll_to_make_visible(line, col)
|
|||
end
|
||||
|
||||
|
||||
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
|
||||
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
||||
if swap then
|
||||
line1, col1, line2, col2 = line2, col2, line1, col1
|
||||
end
|
||||
if clicks == 2 then
|
||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
||||
elseif clicks == 3 then
|
||||
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
|
||||
doc:insert(math.huge, math.huge, "\n")
|
||||
end
|
||||
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
|
||||
end
|
||||
if swap then
|
||||
return line2, col2, line1, col1
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_pressed(button, x, y, clicks)
|
||||
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught then
|
||||
return
|
||||
end
|
||||
if keymap.modkeys["shift"] then
|
||||
if clicks == 1 then
|
||||
local line1, col1 = select(3, self.doc:get_selection())
|
||||
local line2, col2 = self:resolve_screen_position(x, y)
|
||||
self.doc:set_selection(line2, col2, line1, col1)
|
||||
end
|
||||
else
|
||||
local line, col = self:resolve_screen_position(x, y)
|
||||
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
||||
self.mouse_selecting = { line, col, clicks = clicks }
|
||||
end
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_moved(x, y, ...)
|
||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||
|
||||
|
@ -258,13 +236,41 @@ function DocView:on_mouse_moved(x, y, ...)
|
|||
|
||||
if self.mouse_selecting then
|
||||
local l1, c1 = self:resolve_screen_position(x, y)
|
||||
local l2, c2 = table.unpack(self.mouse_selecting)
|
||||
local clicks = self.mouse_selecting.clicks
|
||||
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
|
||||
local l2, c2, snap_type = table.unpack(self.mouse_selecting)
|
||||
if keymap.modkeys["ctrl"] then
|
||||
if l1 > l2 then l1, l2 = l2, l1 end
|
||||
self.doc.selections = { }
|
||||
for i = l1, l2 do
|
||||
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
|
||||
end
|
||||
else
|
||||
if snap_type then
|
||||
l1, c1, l2, c2 = self:mouse_selection(self.doc, snap_type, l1, c1, l2, c2)
|
||||
end
|
||||
self.doc:set_selection(l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
|
||||
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
||||
if swap then
|
||||
line1, col1, line2, col2 = line2, col2, line1, col1
|
||||
end
|
||||
if snap_type == "word" then
|
||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
||||
elseif snap_type == "lines" then
|
||||
col1, col2 = 1, math.huge
|
||||
end
|
||||
if swap then
|
||||
return line2, col2, line1, col1
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_released(button)
|
||||
DocView.super.on_mouse_released(self, button)
|
||||
self.mouse_selecting = nil
|
||||
|
@ -308,92 +314,110 @@ end
|
|||
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local font = self:get_font()
|
||||
local subpixel_scale = font:subpixel_scale()
|
||||
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
||||
local default_font = self:get_font()
|
||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
local color = style.syntax[type]
|
||||
if config.draw_whitespace then
|
||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
||||
else
|
||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
|
||||
end
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
tx = renderer.draw_text(font, text, tx, ty, color)
|
||||
end
|
||||
end
|
||||
|
||||
function DocView:draw_caret(x, y)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x, y, style.caret_width, lh, style.caret)
|
||||
end
|
||||
|
||||
function DocView:draw_line_body(idx, x, y)
|
||||
local line, col = self.doc:get_selection()
|
||||
|
||||
-- draw selection if it overlaps this line
|
||||
local line1, col1, line2, col2 = self.doc:get_selection(true)
|
||||
if idx >= line1 and idx <= line2 then
|
||||
local text = self.doc.lines[idx]
|
||||
if line1 ~= idx then col1 = 1 end
|
||||
if line2 ~= idx then col2 = #text + 1 end
|
||||
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
-- draw highlight if any selection ends on this line
|
||||
local draw_highlight = false
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||
if line1 == idx then
|
||||
draw_highlight = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
end
|
||||
|
||||
-- draw line highlight if caret is on this line
|
||||
if config.highlight_current_line and not self.doc:has_selection()
|
||||
and line == idx and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
-- draw selection if it overlaps this line
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
if idx >= line1 and idx <= line2 then
|
||||
local text = self.doc.lines[idx]
|
||||
if line1 ~= idx then col1 = 1 end
|
||||
if line2 ~= idx then col2 = #text + 1 end
|
||||
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||
local lh = self:get_line_height()
|
||||
if x1 ~= x2 then
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- draw line's text
|
||||
self:draw_line_text(idx, x, y)
|
||||
|
||||
-- draw caret if it overlaps this line
|
||||
local T = config.blink_period
|
||||
if line == idx and core.active_view == self
|
||||
and (core.blink_timer - core.blink_start) % T < T / 2
|
||||
and system.window_has_focus() then
|
||||
local lh = self:get_line_height()
|
||||
local x1 = x + self:get_col_x_offset(line, col)
|
||||
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_gutter(idx, x, y)
|
||||
function DocView:draw_line_gutter(idx, x, y, width)
|
||||
local color = style.line_number
|
||||
local line1, _, line2, _ = self.doc:get_selection(true)
|
||||
if idx >= line1 and idx <= line2 then
|
||||
color = style.line_number2
|
||||
for _, line1, _, line2 in self.doc:get_selections(true) do
|
||||
if idx >= line1 and idx <= line2 then
|
||||
color = style.line_number2
|
||||
break
|
||||
end
|
||||
end
|
||||
local yoffset = self:get_line_text_y_offset()
|
||||
x = x + style.padding.x
|
||||
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
|
||||
common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_overlay()
|
||||
if core.active_view == self then
|
||||
local minline, maxline = self:get_visible_line_range()
|
||||
-- draw caret if it overlaps this line
|
||||
local T = config.blink_period
|
||||
for _, line, col in self.doc:get_selections() do
|
||||
if line >= minline and line <= maxline
|
||||
and system.window_has_focus() then
|
||||
if config.disable_blink
|
||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DocView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local font = self:get_font()
|
||||
font:set_tab_size(config.indent_size)
|
||||
local _, indent_size = self.doc:get_indent_info()
|
||||
self:get_font():set_tab_size(indent_size)
|
||||
|
||||
local minline, maxline = self:get_visible_line_range()
|
||||
local lh = self:get_line_height()
|
||||
|
||||
local _, y = self:get_line_screen_position(minline)
|
||||
local x = self.position.x
|
||||
local x, y = self:get_line_screen_position(minline)
|
||||
local gw, gpad = self:get_gutter_width()
|
||||
for i = minline, maxline do
|
||||
self:draw_line_gutter(i, x, y)
|
||||
self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw)
|
||||
y = y + lh
|
||||
end
|
||||
|
||||
local x, y = self:get_line_screen_position(minline)
|
||||
local gw = self:get_gutter_width()
|
||||
local pos = self.position
|
||||
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
|
||||
x, y = self:get_line_screen_position(minline)
|
||||
-- the clip below ensure we don't write on the gutter region. On the
|
||||
-- right side it is redundant with the Node's clip.
|
||||
core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y)
|
||||
for i = minline, maxline do
|
||||
self:draw_line_body(i, x, y)
|
||||
y = y + lh
|
||||
end
|
||||
self:draw_overlay()
|
||||
core.pop_clip_rect()
|
||||
|
||||
self:draw_scrollbar()
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local View = require "core.view"
|
||||
|
||||
local EmptyView = View:extend()
|
||||
|
||||
local function draw_text(x, y, color)
|
||||
local th = style.big_font:get_height()
|
||||
local dh = 2 * th + style.padding.y * 2
|
||||
local x1, y1 = x, y + (dh - th) / 2
|
||||
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
||||
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
||||
x = x + style.padding.x
|
||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
||||
local lines = {
|
||||
{ fmt = "%s to run a command", cmd = "core:find-command" },
|
||||
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
||||
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
|
||||
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
|
||||
}
|
||||
th = style.font:get_height()
|
||||
y = y + (dh - (th + style.padding.y) * #lines) / 2
|
||||
local w = 0
|
||||
for _, line in ipairs(lines) do
|
||||
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
|
||||
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
|
||||
y = y + th + style.padding.y
|
||||
end
|
||||
return w, dh
|
||||
end
|
||||
|
||||
function EmptyView:draw()
|
||||
self:draw_background(style.background)
|
||||
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
|
||||
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
|
||||
local y = self.position.y + (self.size.y - h) / 2
|
||||
draw_text(x, y, style.dim)
|
||||
end
|
||||
|
||||
return EmptyView
|
File diff suppressed because it is too large
Load Diff
|
@ -6,32 +6,35 @@ local function keymap_macos(keymap)
|
|||
["cmd+n"] = "core:new-doc",
|
||||
["cmd+shift+c"] = "core:change-project-folder",
|
||||
["cmd+shift+o"] = "core:open-project-folder",
|
||||
["alt+return"] = "core:toggle-fullscreen",
|
||||
["cmd+shift+r"] = "core:restart",
|
||||
["cmd+ctrl+return"] = "core:toggle-fullscreen",
|
||||
|
||||
["alt+shift+j"] = "root:split-left",
|
||||
["alt+shift+l"] = "root:split-right",
|
||||
["alt+shift+i"] = "root:split-up",
|
||||
["alt+shift+k"] = "root:split-down",
|
||||
["alt+j"] = "root:switch-to-left",
|
||||
["alt+l"] = "root:switch-to-right",
|
||||
["alt+i"] = "root:switch-to-up",
|
||||
["alt+k"] = "root:switch-to-down",
|
||||
["cmd+ctrl+shift+j"] = "root:split-left",
|
||||
["cmd+ctrl+shift+l"] = "root:split-right",
|
||||
["cmd+ctrl+shift+i"] = "root:split-up",
|
||||
["cmd+ctrl+shift+k"] = "root:split-down",
|
||||
["cmd+ctrl+j"] = "root:switch-to-left",
|
||||
["cmd+ctrl+l"] = "root:switch-to-right",
|
||||
["cmd+ctrl+i"] = "root:switch-to-up",
|
||||
["cmd+ctrl+k"] = "root:switch-to-down",
|
||||
|
||||
["ctrl+w"] = "root:close",
|
||||
["cmd+tab"] = "root:switch-to-next-tab",
|
||||
["cmd+shift+tab"] = "root:switch-to-previous-tab",
|
||||
|
||||
["cmd+w"] = "root:close-or-quit",
|
||||
["ctrl+tab"] = "root:switch-to-next-tab",
|
||||
["ctrl+shift+tab"] = "root:switch-to-previous-tab",
|
||||
["cmd+pageup"] = "root:move-tab-left",
|
||||
["cmd+pagedown"] = "root:move-tab-right",
|
||||
["alt+1"] = "root:switch-to-tab-1",
|
||||
["alt+2"] = "root:switch-to-tab-2",
|
||||
["alt+3"] = "root:switch-to-tab-3",
|
||||
["alt+4"] = "root:switch-to-tab-4",
|
||||
["alt+5"] = "root:switch-to-tab-5",
|
||||
["alt+6"] = "root:switch-to-tab-6",
|
||||
["alt+7"] = "root:switch-to-tab-7",
|
||||
["alt+8"] = "root:switch-to-tab-8",
|
||||
["alt+9"] = "root:switch-to-tab-9",
|
||||
|
||||
["cmd+1"] = "root:switch-to-tab-1",
|
||||
["cmd+2"] = "root:switch-to-tab-2",
|
||||
["cmd+3"] = "root:switch-to-tab-3",
|
||||
["cmd+4"] = "root:switch-to-tab-4",
|
||||
["cmd+5"] = "root:switch-to-tab-5",
|
||||
["cmd+6"] = "root:switch-to-tab-6",
|
||||
["cmd+7"] = "root:switch-to-tab-7",
|
||||
["cmd+8"] = "root:switch-to-tab-8",
|
||||
["cmd+9"] = "root:switch-to-tab-9",
|
||||
["wheel"] = "root:scroll",
|
||||
|
||||
["cmd+f"] = "find-replace:find",
|
||||
["cmd+r"] = "find-replace:replace",
|
||||
["f3"] = "find-replace:repeat-find",
|
||||
|
@ -52,23 +55,27 @@ local function keymap_macos(keymap)
|
|||
["shift+tab"] = "doc:unindent",
|
||||
["backspace"] = "doc:backspace",
|
||||
["shift+backspace"] = "doc:backspace",
|
||||
["cmd+backspace"] = "doc:delete-to-previous-word-start",
|
||||
["option+backspace"] = "doc:delete-to-previous-word-start",
|
||||
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
||||
["cmd+backspace"] = "doc:delete-to-start-of-indentation",
|
||||
["delete"] = "doc:delete",
|
||||
["shift+delete"] = "doc:delete",
|
||||
["cmd+delete"] = "doc:delete-to-next-word-end",
|
||||
["option+delete"] = "doc:delete-to-next-word-end",
|
||||
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
||||
["cmd+delete"] = "doc:delete-to-end-of-line",
|
||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||
["cmd+return"] = "doc:newline-below",
|
||||
["cmd+shift+return"] = "doc:newline-above",
|
||||
["cmd+j"] = "doc:join-lines",
|
||||
["cmd+a"] = "doc:select-all",
|
||||
["cmd+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||
["cmd+f3"] = "find-replace:select-next",
|
||||
["cmd+l"] = "doc:select-lines",
|
||||
["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["cmd+/"] = "doc:toggle-line-comments",
|
||||
["cmd+up"] = "doc:move-lines-up",
|
||||
["cmd+down"] = "doc:move-lines-down",
|
||||
["option+up"] = "doc:move-lines-up",
|
||||
["option+down"] = "doc:move-lines-down",
|
||||
["cmd+shift+d"] = "doc:duplicate-lines",
|
||||
["cmd+shift+k"] = "doc:delete-lines",
|
||||
|
||||
|
@ -76,31 +83,42 @@ local function keymap_macos(keymap)
|
|||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
||||
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
||||
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
||||
["cmd+left"] = "doc:move-to-previous-word-start",
|
||||
["cmd+right"] = "doc:move-to-next-word-end",
|
||||
["option+left"] = "doc:move-to-previous-word-start",
|
||||
["option+right"] = "doc:move-to-next-word-end",
|
||||
["cmd+left"] = "doc:move-to-start-of-indentation",
|
||||
["cmd+right"] = "doc:move-to-end-of-line",
|
||||
["cmd+["] = "doc:move-to-previous-block-start",
|
||||
["cmd+]"] = "doc:move-to-next-block-end",
|
||||
["home"] = "doc:move-to-start-of-line",
|
||||
["home"] = "doc:move-to-start-of-indentation",
|
||||
["end"] = "doc:move-to-end-of-line",
|
||||
["cmd+home"] = "doc:move-to-start-of-doc",
|
||||
["cmd+end"] = "doc:move-to-end-of-doc",
|
||||
["cmd+up"] = "doc:move-to-start-of-doc",
|
||||
["cmd+down"] = "doc:move-to-end-of-doc",
|
||||
["pageup"] = "doc:move-to-previous-page",
|
||||
["pagedown"] = "doc:move-to-next-page",
|
||||
|
||||
["shift+1lclick"] = "doc:select-to-cursor",
|
||||
["ctrl+1lclick"] = "doc:split-cursor",
|
||||
["1lclick"] = "doc:set-cursor",
|
||||
["2lclick"] = "doc:set-cursor-word",
|
||||
["3lclick"] = "doc:set-cursor-line",
|
||||
["shift+left"] = "doc:select-to-previous-char",
|
||||
["shift+right"] = "doc:select-to-next-char",
|
||||
["shift+up"] = "doc:select-to-previous-line",
|
||||
["shift+down"] = "doc:select-to-next-line",
|
||||
["cmd+shift+left"] = "doc:select-to-previous-word-start",
|
||||
["cmd+shift+right"] = "doc:select-to-next-word-end",
|
||||
["option+shift+left"] = "doc:select-to-previous-word-start",
|
||||
["option+shift+right"] = "doc:select-to-next-word-end",
|
||||
["cmd+shift+left"] = "doc:select-to-start-of-indentation",
|
||||
["cmd+shift+right"] = "doc:select-to-end-of-line",
|
||||
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
||||
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
||||
["shift+home"] = "doc:select-to-start-of-line",
|
||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
||||
["shift+end"] = "doc:select-to-end-of-line",
|
||||
["cmd+shift+home"] = "doc:select-to-start-of-doc",
|
||||
["cmd+shift+end"] = "doc:select-to-end-of-doc",
|
||||
["cmd+shift+up"] = "doc:select-to-start-of-doc",
|
||||
["cmd+shift+down"] = "doc:select-to-end-of-doc",
|
||||
["shift+pageup"] = "doc:select-to-previous-page",
|
||||
["shift+pagedown"] = "doc:select-to-next-page",
|
||||
["cmd+option+up"] = "doc:create-cursor-previous-line",
|
||||
["cmd+option+down"] = "doc:create-cursor-next-line"
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = {}
|
||||
|
||||
keymap.modkeys = {}
|
||||
keymap.map = {}
|
||||
keymap.reverse_map = {}
|
||||
|
||||
local macos = rawget(_G, "MACOS_RESOURCES")
|
||||
local macos = PLATFORM == "Mac OS X"
|
||||
local os4 = PLATFORM == "AmigaOS 4"
|
||||
|
||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
|
||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or "generic"))
|
||||
local modkey_map = modkeys_os.map
|
||||
local modkeys = modkeys_os.keys
|
||||
|
||||
|
@ -30,7 +32,8 @@ function keymap.add_direct(map)
|
|||
end
|
||||
keymap.map[stroke] = commands
|
||||
for _, cmd in ipairs(commands) do
|
||||
keymap.reverse_map[cmd] = stroke
|
||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||
table.insert(keymap.reverse_map[cmd], stroke)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,18 +55,43 @@ function keymap.add(map, overwrite)
|
|||
end
|
||||
end
|
||||
for _, cmd in ipairs(commands) do
|
||||
keymap.reverse_map[cmd] = stroke
|
||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||
table.insert(keymap.reverse_map[cmd], stroke)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function keymap.get_binding(cmd)
|
||||
return keymap.reverse_map[cmd]
|
||||
local function remove_only(tbl, k, v)
|
||||
for key, values in pairs(tbl) do
|
||||
if key == k then
|
||||
if v then
|
||||
for i, value in ipairs(values) do
|
||||
if value == v then
|
||||
table.remove(values, i)
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[key] = nil
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function keymap.on_key_pressed(k)
|
||||
function keymap.unbind(key, cmd)
|
||||
remove_only(keymap.map, key, cmd)
|
||||
remove_only(keymap.reverse_map, cmd, key)
|
||||
end
|
||||
|
||||
|
||||
function keymap.get_binding(cmd)
|
||||
return table.unpack(keymap.reverse_map[cmd] or {})
|
||||
end
|
||||
|
||||
|
||||
function keymap.on_key_pressed(k, ...)
|
||||
local mk = modkey_map[k]
|
||||
if mk then
|
||||
keymap.modkeys[mk] = true
|
||||
|
@ -73,18 +101,30 @@ function keymap.on_key_pressed(k)
|
|||
end
|
||||
else
|
||||
local stroke = key_to_stroke(k)
|
||||
local commands = keymap.map[stroke]
|
||||
local commands, performed = keymap.map[stroke]
|
||||
if commands then
|
||||
for _, cmd in ipairs(commands) do
|
||||
local performed = command.perform(cmd)
|
||||
performed = command.perform(cmd, ...)
|
||||
if performed then break end
|
||||
end
|
||||
return true
|
||||
return performed
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function keymap.on_mouse_wheel(delta, ...)
|
||||
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
|
||||
or keymap.on_key_pressed("wheel", delta, ...))
|
||||
end
|
||||
|
||||
function keymap.on_mouse_pressed(button, x, y, clicks)
|
||||
local click_number = (((clicks - 1) % config.max_clicks) + 1)
|
||||
return not (keymap.on_key_pressed(click_number .. button:sub(1,1) .. "click", x, y, clicks) or
|
||||
keymap.on_key_pressed(button:sub(1,1) .. "click", x, y, clicks) or
|
||||
keymap.on_key_pressed(click_number .. "click", x, y, clicks) or
|
||||
keymap.on_key_pressed("click", x, y, clicks))
|
||||
end
|
||||
|
||||
function keymap.on_key_released(k)
|
||||
local mk = modkey_map[k]
|
||||
|
@ -107,7 +147,9 @@ keymap.add_direct {
|
|||
["ctrl+n"] = "core:new-doc",
|
||||
["ctrl+shift+c"] = "core:change-project-folder",
|
||||
["ctrl+shift+o"] = "core:open-project-folder",
|
||||
["ctrl+shift+r"] = "core:restart",
|
||||
["alt+return"] = "core:toggle-fullscreen",
|
||||
["f11"] = "core:toggle-fullscreen",
|
||||
|
||||
["alt+shift+j"] = "root:split-left",
|
||||
["alt+shift+l"] = "root:split-right",
|
||||
|
@ -132,11 +174,14 @@ keymap.add_direct {
|
|||
["alt+7"] = "root:switch-to-tab-7",
|
||||
["alt+8"] = "root:switch-to-tab-8",
|
||||
["alt+9"] = "root:switch-to-tab-9",
|
||||
["wheel"] = "root:scroll",
|
||||
|
||||
["ctrl+f"] = "find-replace:find",
|
||||
["ctrl+r"] = "find-replace:replace",
|
||||
["f3"] = "find-replace:repeat-find",
|
||||
["shift+f3"] = "find-replace:previous-find",
|
||||
["ctrl+i"] = "find-replace:toggle-sensitivity",
|
||||
["ctrl+shift+i"] = "find-replace:toggle-regex",
|
||||
["ctrl+g"] = "doc:go-to-line",
|
||||
["ctrl+s"] = "doc:save",
|
||||
["ctrl+shift+s"] = "doc:save-as",
|
||||
|
@ -165,8 +210,11 @@ keymap.add_direct {
|
|||
["ctrl+shift+return"] = "doc:newline-above",
|
||||
["ctrl+j"] = "doc:join-lines",
|
||||
["ctrl+a"] = "doc:select-all",
|
||||
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||
["ctrl+f3"] = "find-replace:select-next",
|
||||
["ctrl+shift+f3"] = "find-replace:select-previous",
|
||||
["ctrl+l"] = "doc:select-lines",
|
||||
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
|
@ -181,13 +229,18 @@ keymap.add_direct {
|
|||
["ctrl+right"] = "doc:move-to-next-word-end",
|
||||
["ctrl+["] = "doc:move-to-previous-block-start",
|
||||
["ctrl+]"] = "doc:move-to-next-block-end",
|
||||
["home"] = "doc:move-to-start-of-line",
|
||||
["home"] = "doc:move-to-start-of-indentation",
|
||||
["end"] = "doc:move-to-end-of-line",
|
||||
["ctrl+home"] = "doc:move-to-start-of-doc",
|
||||
["ctrl+end"] = "doc:move-to-end-of-doc",
|
||||
["pageup"] = "doc:move-to-previous-page",
|
||||
["pagedown"] = "doc:move-to-next-page",
|
||||
|
||||
["shift+1lclick"] = "doc:select-to-cursor",
|
||||
["ctrl+1lclick"] = "doc:split-cursor",
|
||||
["1lclick"] = "doc:set-cursor",
|
||||
["2lclick"] = "doc:set-cursor-word",
|
||||
["3lclick"] = "doc:set-cursor-line",
|
||||
["shift+left"] = "doc:select-to-previous-char",
|
||||
["shift+right"] = "doc:select-to-next-char",
|
||||
["shift+up"] = "doc:select-to-previous-line",
|
||||
|
@ -196,12 +249,15 @@ keymap.add_direct {
|
|||
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
||||
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
||||
["ctrl+shift+]"] = "doc:select-to-next-block-end",
|
||||
["shift+home"] = "doc:select-to-start-of-line",
|
||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
||||
["shift+end"] = "doc:select-to-end-of-line",
|
||||
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||
["shift+pageup"] = "doc:select-to-previous-page",
|
||||
["shift+pagedown"] = "doc:select-to-next-page",
|
||||
["ctrl+shift+up"] = "doc:create-cursor-previous-line",
|
||||
["ctrl+shift+down"] = "doc:create-cursor-next-line"
|
||||
}
|
||||
|
||||
return keymap
|
||||
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local function lines(text)
|
||||
if text == "" then return 0 end
|
||||
local l = 1
|
||||
for _ in string.gmatch(text, "\n") do
|
||||
l = l + 1
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
|
||||
local item_height_result = {}
|
||||
|
||||
|
||||
local function get_item_height(item)
|
||||
local h = item_height_result[item]
|
||||
if not h then
|
||||
h = {}
|
||||
local l = 1 + lines(item.text) + lines(item.info or "")
|
||||
h.normal = style.font:get_height() + style.padding.y
|
||||
h.expanded = l * style.font:get_height() + style.padding.y
|
||||
h.current = h.normal
|
||||
h.target = h.current
|
||||
item_height_result[item] = h
|
||||
end
|
||||
return h
|
||||
end
|
||||
|
||||
|
||||
local LogView = View:extend()
|
||||
|
||||
LogView.context = "session"
|
||||
|
||||
function LogView:new()
|
||||
LogView.super.new(self)
|
||||
self.last_item = core.log_items[#core.log_items]
|
||||
self.expanding = {}
|
||||
self.scrollable = true
|
||||
self.yoffset = 0
|
||||
end
|
||||
|
@ -19,6 +50,55 @@ function LogView:get_name()
|
|||
end
|
||||
|
||||
|
||||
local function is_expanded(item)
|
||||
local item_height = get_item_height(item)
|
||||
return item_height.target == item_height.expanded
|
||||
end
|
||||
|
||||
|
||||
function LogView:expand_item(item)
|
||||
item = get_item_height(item)
|
||||
item.target = item.target == item.expanded and item.normal or item.expanded
|
||||
table.insert(self.expanding, item)
|
||||
end
|
||||
|
||||
|
||||
function LogView:each_item()
|
||||
local x, y = self:get_content_offset()
|
||||
y = y + style.padding.y + self.yoffset
|
||||
return coroutine.wrap(function()
|
||||
for i = #core.log_items, 1, -1 do
|
||||
local item = core.log_items[i]
|
||||
local h = get_item_height(item).current
|
||||
coroutine.yield(i, item, x, y, self.size.x, h)
|
||||
y = y + h
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_moved(px, py, ...)
|
||||
LogView.super.on_mouse_moved(self, px, py, ...)
|
||||
local hovered = false
|
||||
for _, item, x, y, w, h in self:each_item() do
|
||||
if px >= x and py >= y and px < x + w and py < y + h then
|
||||
hovered = true
|
||||
self.hovered_item = item
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hovered then self.hovered_item = nil end
|
||||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_pressed(button, mx, my, clicks)
|
||||
if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||
if self.hovered_item then
|
||||
self:expand_item(self.hovered_item)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function LogView:update()
|
||||
local item = core.log_items[#core.log_items]
|
||||
if self.last_item ~= item then
|
||||
|
@ -27,6 +107,14 @@ function LogView:update()
|
|||
self.yoffset = -(style.font:get_height() + style.padding.y)
|
||||
end
|
||||
|
||||
local expanding = self.expanding[1]
|
||||
if expanding then
|
||||
self:move_towards(expanding, "current", expanding.target)
|
||||
if expanding.current == expanding.target then
|
||||
table.remove(self.expanding, 1)
|
||||
end
|
||||
end
|
||||
|
||||
self:move_towards("yoffset", 0)
|
||||
|
||||
LogView.super.update(self)
|
||||
|
@ -35,38 +123,48 @@ end
|
|||
|
||||
local function draw_text_multiline(font, text, x, y, color)
|
||||
local th = font:get_height()
|
||||
local resx, resy = x, y
|
||||
local resx = x
|
||||
for line in text:gmatch("[^\n]+") do
|
||||
resy = y
|
||||
resx = renderer.draw_text(style.font, line, x, y, color)
|
||||
y = y + th
|
||||
end
|
||||
return resx, resy
|
||||
return resx, y
|
||||
end
|
||||
|
||||
|
||||
function LogView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local ox, oy = self:get_content_offset()
|
||||
local th = style.font:get_height()
|
||||
local y = oy + style.padding.y + self.yoffset
|
||||
|
||||
for i = #core.log_items, 1, -1 do
|
||||
local x = ox + style.padding.x
|
||||
local item = core.log_items[i]
|
||||
local time = os.date(nil, item.time)
|
||||
x = renderer.draw_text(style.font, time, x, y, style.dim)
|
||||
local lh = th + style.padding.y -- for one line
|
||||
for _, item, x, y, w in self:each_item() do
|
||||
x = x + style.padding.x
|
||||
local subx = x
|
||||
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
|
||||
y = y + th
|
||||
if item.info then
|
||||
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
||||
y = y + th
|
||||
|
||||
local time = os.date(nil, item.time)
|
||||
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
|
||||
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
w = w - (x - self:get_content_offset())
|
||||
|
||||
if is_expanded(item) then
|
||||
y = y + common.round(style.padding.y / 2)
|
||||
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||
|
||||
local at = "at " .. common.home_encode(item.at)
|
||||
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
|
||||
|
||||
if item.info then
|
||||
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
|
||||
end
|
||||
else
|
||||
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
|
||||
if has_newline ~= "" then
|
||||
line = line .. " ..."
|
||||
end
|
||||
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
||||
end
|
||||
y = y + style.padding.y
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
local modkeys = {}
|
||||
|
||||
modkeys.map = {
|
||||
["left amiga"] = "cmd",
|
||||
["right amiga"] = "cmd",
|
||||
["control"] = "ctrl",
|
||||
["left shift"] = "shift",
|
||||
["right shift"] = "shift",
|
||||
["left alt"] = "alt",
|
||||
["right alt"] = "altgr",
|
||||
}
|
||||
|
||||
modkeys.keys = { "cmd", "ctrl", "alt", "altgr", "shift" }
|
||||
|
||||
return modkeys
|
|
@ -170,12 +170,6 @@ function NagView:draw()
|
|||
end
|
||||
end
|
||||
|
||||
local function findindex(tbl, prop)
|
||||
for i, o in ipairs(tbl) do
|
||||
if o[prop] then return i end
|
||||
end
|
||||
end
|
||||
|
||||
function NagView:get_message_height()
|
||||
local h = 0
|
||||
for str in string.gmatch(self.message, "(.-)\n") do
|
||||
|
@ -196,10 +190,11 @@ function NagView:next()
|
|||
-- self.target_height is the nagview height needed to display the message and
|
||||
-- the buttons, excluding the top and bottom padding space.
|
||||
self.target_height = math.max(message_height, self:get_buttons_height())
|
||||
self:change_hovered(findindex(self.options, "default_yes"))
|
||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||
end
|
||||
self.force_focus = self.message ~= nil
|
||||
core.set_active_view(self.message ~= nil and self or core.last_active_view)
|
||||
core.set_active_view(self.message ~= nil and self or
|
||||
core.next_active_view or core.last_active_view)
|
||||
end
|
||||
|
||||
function NagView:show(title, message, options, on_select)
|
||||
|
@ -212,36 +207,4 @@ function NagView:show(title, message, options, on_select)
|
|||
if #self.queue > 0 and not self.title then self:next() end
|
||||
end
|
||||
|
||||
command.add(NagView, {
|
||||
["dialog:previous-entry"] = function()
|
||||
local v = core.active_view
|
||||
local hover = v.hovered_item or 1
|
||||
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
||||
end,
|
||||
["dialog:next-entry"] = function()
|
||||
local v = core.active_view
|
||||
local hover = v.hovered_item or 1
|
||||
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
||||
end,
|
||||
["dialog:select-yes"] = function()
|
||||
local v = core.active_view
|
||||
if v ~= core.nag_view then return end
|
||||
v:change_hovered(findindex(v.options, "default_yes"))
|
||||
command.perform "dialog:select"
|
||||
end,
|
||||
["dialog:select-no"] = function()
|
||||
local v = core.active_view
|
||||
if v ~= core.nag_view then return end
|
||||
v:change_hovered(findindex(v.options, "default_no"))
|
||||
command.perform "dialog:select"
|
||||
end,
|
||||
["dialog:select"] = function()
|
||||
local v = core.active_view
|
||||
if v.hovered_item then
|
||||
v.on_selected(v.options[v.hovered_item])
|
||||
v:next()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return NagView
|
||||
return NagView
|
||||
|
|
|
@ -0,0 +1,737 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
local EmptyView = require "core.emptyview"
|
||||
local View = require "core.view"
|
||||
|
||||
local Node = Object:extend()
|
||||
|
||||
function Node:new(type)
|
||||
self.type = type or "leaf"
|
||||
self.position = { x = 0, y = 0 }
|
||||
self.size = { x = 0, y = 0 }
|
||||
self.views = {}
|
||||
self.divider = 0.5
|
||||
if self.type == "leaf" then
|
||||
self:add_view(EmptyView())
|
||||
end
|
||||
self.hovered = {x = -1, y = -1 }
|
||||
self.hovered_close = 0
|
||||
self.tab_shift = 0
|
||||
self.tab_offset = 1
|
||||
self.tab_width = style.tab_width
|
||||
self.move_towards = View.move_towards
|
||||
end
|
||||
|
||||
|
||||
function Node:propagate(fn, ...)
|
||||
self.a[fn](self.a, ...)
|
||||
self.b[fn](self.b, ...)
|
||||
end
|
||||
|
||||
|
||||
function Node:on_mouse_moved(x, y, ...)
|
||||
if self.type == "leaf" then
|
||||
self.hovered.x, self.hovered.y = x, y
|
||||
self.active_view:on_mouse_moved(x, y, ...)
|
||||
else
|
||||
self:propagate("on_mouse_moved", x, y, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:on_mouse_released(...)
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_released(...)
|
||||
else
|
||||
self:propagate("on_mouse_released", ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:consume(node)
|
||||
for k, _ in pairs(self) do self[k] = nil end
|
||||
for k, v in pairs(node) do self[k] = v end
|
||||
end
|
||||
|
||||
|
||||
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
|
||||
|
||||
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
|
||||
-- and it indicates if the node want to have a fixed size along the axis where the
|
||||
-- boolean is true. If not it will be expanded to take all the available space.
|
||||
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
|
||||
-- by the user. If the node is marked as resizable their view should provide a
|
||||
-- set_target_size method.
|
||||
function Node:split(dir, view, locked, resizable)
|
||||
assert(self.type == "leaf", "Tried to split non-leaf node")
|
||||
local node_type = assert(type_map[dir], "Invalid direction")
|
||||
local last_active = core.active_view
|
||||
local child = Node()
|
||||
child:consume(self)
|
||||
self:consume(Node(node_type))
|
||||
self.a = child
|
||||
self.b = Node()
|
||||
if view then self.b:add_view(view) end
|
||||
if locked then
|
||||
assert(type(locked) == 'table')
|
||||
self.b.locked = locked
|
||||
self.b.resizable = resizable or false
|
||||
core.set_active_view(last_active)
|
||||
end
|
||||
if dir == "up" or dir == "left" then
|
||||
self.a, self.b = self.b, self.a
|
||||
return self.a
|
||||
end
|
||||
return self.b
|
||||
end
|
||||
|
||||
function Node:remove_view(root, view)
|
||||
if #self.views > 1 then
|
||||
local idx = self:get_view_idx(view)
|
||||
if idx < self.tab_offset then
|
||||
self.tab_offset = self.tab_offset - 1
|
||||
end
|
||||
table.remove(self.views, idx)
|
||||
if self.active_view == view then
|
||||
self:set_active_view(self.views[idx] or self.views[#self.views])
|
||||
end
|
||||
else
|
||||
local parent = self:get_parent_node(root)
|
||||
local is_a = (parent.a == self)
|
||||
local other = parent[is_a and "b" or "a"]
|
||||
local locked_size_x, locked_size_y = other:get_locked_size()
|
||||
local locked_size
|
||||
if parent.type == "hsplit" then
|
||||
locked_size = locked_size_x
|
||||
else
|
||||
locked_size = locked_size_y
|
||||
end
|
||||
local next_primary
|
||||
if self.is_primary_node then
|
||||
next_primary = core.root_view:select_next_primary_node()
|
||||
end
|
||||
if locked_size or (self.is_primary_node and not next_primary) then
|
||||
self.views = {}
|
||||
self:add_view(EmptyView())
|
||||
else
|
||||
if other == next_primary then
|
||||
next_primary = parent
|
||||
end
|
||||
parent:consume(other)
|
||||
local p = parent
|
||||
while p.type ~= "leaf" do
|
||||
p = p[is_a and "a" or "b"]
|
||||
end
|
||||
p:set_active_view(p.active_view)
|
||||
if self.is_primary_node then
|
||||
next_primary.is_primary_node = true
|
||||
end
|
||||
end
|
||||
end
|
||||
core.last_active_view = nil
|
||||
end
|
||||
|
||||
function Node:close_view(root, view)
|
||||
local do_close = function()
|
||||
self:remove_view(root, view)
|
||||
end
|
||||
view:try_close(do_close)
|
||||
end
|
||||
|
||||
|
||||
function Node:close_active_view(root)
|
||||
self:close_view(root, self.active_view)
|
||||
end
|
||||
|
||||
|
||||
function Node:add_view(view, idx)
|
||||
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
||||
assert(not self.locked, "Tried to add view to locked node")
|
||||
if self.views[1] and self.views[1]:is(EmptyView) then
|
||||
table.remove(self.views)
|
||||
end
|
||||
table.insert(self.views, idx or (#self.views + 1), view)
|
||||
self:set_active_view(view)
|
||||
end
|
||||
|
||||
|
||||
function Node:set_active_view(view)
|
||||
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
||||
self.active_view = view
|
||||
core.set_active_view(view)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_view_idx(view)
|
||||
for i, v in ipairs(self.views) do
|
||||
if v == view then return i end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_node_for_view(view)
|
||||
for _, v in ipairs(self.views) do
|
||||
if v == view then return self end
|
||||
end
|
||||
if self.type ~= "leaf" then
|
||||
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_parent_node(root)
|
||||
if root.a == self or root.b == self then
|
||||
return root
|
||||
elseif root.type ~= "leaf" then
|
||||
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_children(t)
|
||||
t = t or {}
|
||||
for _, view in ipairs(self.views) do
|
||||
table.insert(t, view)
|
||||
end
|
||||
if self.a then self.a:get_children(t) end
|
||||
if self.b then self.b:get_children(t) end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
-- return the width including the padding space and separately
|
||||
-- the padding space itself
|
||||
local function get_scroll_button_width()
|
||||
local w = style.icon_font:get_width(">")
|
||||
local pad = w
|
||||
return w + 2 * pad, pad
|
||||
end
|
||||
|
||||
|
||||
function Node:get_divider_overlapping_point(px, py)
|
||||
if self.type ~= "leaf" then
|
||||
local axis = self.type == "hsplit" and "x" or "y"
|
||||
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
w, h = w + p * 2, h + p * 2
|
||||
if px > x and py > y and px < x + w and py < y + h then
|
||||
return self
|
||||
end
|
||||
end
|
||||
return self.a:get_divider_overlapping_point(px, py)
|
||||
or self.b:get_divider_overlapping_point(px, py)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_visible_tabs_number()
|
||||
return math.min(#self.views - self.tab_offset + 1, config.max_tabs)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_overlapping_point(px, py)
|
||||
if not self:should_show_tabs() then return nil end
|
||||
local tabs_number = self:get_visible_tabs_number()
|
||||
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
||||
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
||||
if px >= x1 and py >= y1 and px < x2 and py < y1 + h then
|
||||
return math.floor((px - x1) / w) + self.tab_offset
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:should_show_tabs()
|
||||
if self.locked then return false end
|
||||
local dn = core.root_view.dragged_node
|
||||
if #self.views > 1
|
||||
or (dn and dn.dragging) then -- show tabs while dragging
|
||||
return true
|
||||
elseif config.always_show_tabs then
|
||||
return not self.views[1]:is(EmptyView)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function close_button_location(x, w)
|
||||
local cw = style.icon_font:get_width("C")
|
||||
local pad = style.padding.y
|
||||
return x + w - pad - cw, cw, pad
|
||||
end
|
||||
|
||||
|
||||
function Node:get_scroll_button_index(px, py)
|
||||
if #self.views == 1 then return end
|
||||
for i = 1, 2 do
|
||||
local x, y, w, h = self:get_scroll_button_rect(i)
|
||||
if px >= x and px < x + w and py >= y and py < y + h then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:tab_hovered_update(px, py)
|
||||
local tab_index = self:get_tab_overlapping_point(px, py)
|
||||
self.hovered_tab = tab_index
|
||||
self.hovered_close = 0
|
||||
self.hovered_scroll_button = 0
|
||||
if tab_index then
|
||||
local x, y, w, h = self:get_tab_rect(tab_index)
|
||||
local cx, cw = close_button_location(x, w)
|
||||
if px >= cx and px < cx + cw and py >= y and py < y + h and config.tab_close_button then
|
||||
self.hovered_close = tab_index
|
||||
end
|
||||
else
|
||||
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_child_overlapping_point(x, y)
|
||||
local child
|
||||
if self.type == "leaf" then
|
||||
return self
|
||||
elseif self.type == "hsplit" then
|
||||
child = (x < self.b.position.x) and self.a or self.b
|
||||
elseif self.type == "vsplit" then
|
||||
child = (y < self.b.position.y) and self.a or self.b
|
||||
end
|
||||
return child:get_child_overlapping_point(x, y)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_scroll_button_rect(index)
|
||||
local w, pad = get_scroll_button_width()
|
||||
local h = style.font:get_height() + style.padding.y * 2
|
||||
local x = self.position.x + (index == 1 and 0 or self.size.x - w)
|
||||
return x, self.position.y, w, h, pad
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_rect(idx)
|
||||
local sbw = get_scroll_button_width()
|
||||
local maxw = self.size.x - 2 * sbw
|
||||
local x0 = self.position.x + sbw
|
||||
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
|
||||
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
|
||||
local h = style.font:get_height() + style.padding.y * 2
|
||||
return x1, self.position.y, x2 - x1, h
|
||||
end
|
||||
|
||||
|
||||
function Node:get_divider_rect()
|
||||
local x, y = self.position.x, self.position.y
|
||||
if self.type == "hsplit" then
|
||||
return x + self.a.size.x, y, style.divider_size, self.size.y
|
||||
elseif self.type == "vsplit" then
|
||||
return x, y + self.a.size.y, self.size.x, style.divider_size
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Return two values for x and y axis and each of them is either falsy or a number.
|
||||
-- A falsy value indicate no fixed size along the corresponding direction.
|
||||
function Node:get_locked_size()
|
||||
if self.type == "leaf" then
|
||||
if self.locked then
|
||||
local size = self.active_view.size
|
||||
-- The values below should be either a falsy value or a number
|
||||
local sx = (self.locked and self.locked.x) and size.x
|
||||
local sy = (self.locked and self.locked.y) and size.y
|
||||
return sx, sy
|
||||
end
|
||||
else
|
||||
local x1, y1 = self.a:get_locked_size()
|
||||
local x2, y2 = self.b:get_locked_size()
|
||||
-- The values below should be either a falsy value or a number
|
||||
local sx, sy
|
||||
if self.type == 'hsplit' then
|
||||
if x1 and x2 then
|
||||
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
|
||||
sx = x1 + x2 + dsx
|
||||
end
|
||||
sy = y1 or y2
|
||||
else
|
||||
if y1 and y2 then
|
||||
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
|
||||
sy = y1 + y2 + dsy
|
||||
end
|
||||
sx = x1 or x2
|
||||
end
|
||||
return sx, sy
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node.copy_position_and_size(dst, src)
|
||||
dst.position.x, dst.position.y = src.position.x, src.position.y
|
||||
dst.size.x, dst.size.y = src.size.x, src.size.y
|
||||
end
|
||||
|
||||
|
||||
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
|
||||
-- axis are swapped; this function lets us use the same code for both
|
||||
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
|
||||
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
|
||||
local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
|
||||
self.a.position[x] = self.position[x]
|
||||
self.a.position[y] = self.position[y]
|
||||
self.a.size[x] = n - ds
|
||||
self.a.size[y] = self.size[y]
|
||||
self.b.position[x] = self.position[x] + n
|
||||
self.b.position[y] = self.position[y]
|
||||
self.b.size[x] = self.size[x] - n
|
||||
self.b.size[y] = self.size[y]
|
||||
end
|
||||
|
||||
|
||||
function Node:update_layout()
|
||||
if self.type == "leaf" then
|
||||
local av = self.active_view
|
||||
if self:should_show_tabs() then
|
||||
local _, _, _, th = self:get_tab_rect(1)
|
||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
||||
else
|
||||
Node.copy_position_and_size(av, self)
|
||||
end
|
||||
else
|
||||
local x1, y1 = self.a:get_locked_size()
|
||||
local x2, y2 = self.b:get_locked_size()
|
||||
if self.type == "hsplit" then
|
||||
calc_split_sizes(self, "x", "y", x1, x2)
|
||||
elseif self.type == "vsplit" then
|
||||
calc_split_sizes(self, "y", "x", y1, y2)
|
||||
end
|
||||
self.a:update_layout()
|
||||
self.b:update_layout()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:scroll_tabs_to_visible()
|
||||
local index = self:get_view_idx(self.active_view)
|
||||
if index then
|
||||
local tabs_number = self:get_visible_tabs_number()
|
||||
if self.tab_offset > index then
|
||||
self.tab_offset = index
|
||||
elseif self.tab_offset + tabs_number - 1 < index then
|
||||
self.tab_offset = index - tabs_number + 1
|
||||
elseif tabs_number < config.max_tabs and self.tab_offset > 1 then
|
||||
self.tab_offset = #self.views - config.max_tabs + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:scroll_tabs(dir)
|
||||
local view_index = self:get_view_idx(self.active_view)
|
||||
if dir == 1 then
|
||||
if self.tab_offset > 1 then
|
||||
self.tab_offset = self.tab_offset - 1
|
||||
local last_index = self.tab_offset + self:get_visible_tabs_number() - 1
|
||||
if view_index > last_index then
|
||||
self:set_active_view(self.views[last_index])
|
||||
end
|
||||
end
|
||||
elseif dir == 2 then
|
||||
local tabs_number = self:get_visible_tabs_number()
|
||||
if self.tab_offset + tabs_number - 1 < #self.views then
|
||||
self.tab_offset = self.tab_offset + 1
|
||||
local view_index = self:get_view_idx(self.active_view)
|
||||
if view_index < self.tab_offset then
|
||||
self:set_active_view(self.views[self.tab_offset])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:target_tab_width()
|
||||
local n = self:get_visible_tabs_number()
|
||||
local w = self.size.x - get_scroll_button_width() * 2
|
||||
return common.clamp(style.tab_width, w / config.max_tabs, w / n)
|
||||
end
|
||||
|
||||
|
||||
function Node:update()
|
||||
if self.type == "leaf" then
|
||||
self:scroll_tabs_to_visible()
|
||||
for _, view in ipairs(self.views) do
|
||||
view:update()
|
||||
end
|
||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
||||
local tab_width = self:target_tab_width()
|
||||
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1))
|
||||
self:move_towards("tab_width", tab_width)
|
||||
else
|
||||
self.a:update()
|
||||
self.b:update()
|
||||
end
|
||||
end
|
||||
|
||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if standalone then
|
||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||
end
|
||||
if is_active then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = is_close_hovered and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
if is_hovered then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
function Node:draw_tabs()
|
||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
core.push_clip_rect(x, y, self.size.x, h)
|
||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||
|
||||
if self.tab_offset > 1 then
|
||||
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
|
||||
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
|
||||
end
|
||||
|
||||
local tabs_number = self:get_visible_tabs_number()
|
||||
if #self.views > self.tab_offset + tabs_number - 1 then
|
||||
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
||||
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
|
||||
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
||||
end
|
||||
|
||||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||
local view = self.views[i]
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
self:draw_tab(view:get_name(), view == self.active_view,
|
||||
i == self.hovered_tab, i == self.hovered_close,
|
||||
x, y, w, h)
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
||||
function Node:draw()
|
||||
if self.type == "leaf" then
|
||||
if self:should_show_tabs() then
|
||||
self:draw_tabs()
|
||||
end
|
||||
local pos, size = self.active_view.position, self.active_view.size
|
||||
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
|
||||
self.active_view:draw()
|
||||
core.pop_clip_rect()
|
||||
else
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
renderer.draw_rect(x, y, w, h, style.divider)
|
||||
self:propagate("draw")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:is_empty()
|
||||
if self.type == "leaf" then
|
||||
return #self.views == 0 or (#self.views == 1 and self.views[1]:is(EmptyView))
|
||||
else
|
||||
return self.a:is_empty() and self.b:is_empty()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:close_all_docviews(keep_active)
|
||||
local node_active_view = self.active_view
|
||||
local lost_active_view = false
|
||||
if self.type == "leaf" then
|
||||
local i = 1
|
||||
while i <= #self.views do
|
||||
local view = self.views[i]
|
||||
if view.context == "session" and (not keep_active or view ~= self.active_view) then
|
||||
table.remove(self.views, i)
|
||||
if view == node_active_view then
|
||||
lost_active_view = true
|
||||
end
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
self.tab_offset = 1
|
||||
if #self.views == 0 and self.is_primary_node then
|
||||
-- if we are not the primary view and we had the active view it doesn't
|
||||
-- matter to reattribute the active view because, within the close_all_docviews
|
||||
-- top call, the primary node will take the active view anyway.
|
||||
-- Set the empty view and takes the active view.
|
||||
self:add_view(EmptyView())
|
||||
elseif #self.views > 0 and lost_active_view then
|
||||
-- In practice we never get there but if a view remain we need
|
||||
-- to reset the Node's active view.
|
||||
self:set_active_view(self.views[1])
|
||||
end
|
||||
else
|
||||
self.a:close_all_docviews(keep_active)
|
||||
self.b:close_all_docviews(keep_active)
|
||||
if self.a:is_empty() and not self.a.is_primary_node then
|
||||
self:consume(self.b)
|
||||
elseif self.b:is_empty() and not self.b.is_primary_node then
|
||||
self:consume(self.a)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns true for nodes that accept either "proportional" resizes (based on the
|
||||
-- node.divider) or "locked" resizable nodes (along the resize axis).
|
||||
function Node:is_resizable(axis)
|
||||
if self.type == 'leaf' then
|
||||
return not self.locked or not self.locked[axis] or self.resizable
|
||||
else
|
||||
local a_resizable = self.a:is_resizable(axis)
|
||||
local b_resizable = self.b:is_resizable(axis)
|
||||
return a_resizable and b_resizable
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Return true iff it is a locked pane along the rezise axis and is
|
||||
-- declared "resizable".
|
||||
function Node:is_locked_resizable(axis)
|
||||
return self.locked and self.locked[axis] and self.resizable
|
||||
end
|
||||
|
||||
|
||||
function Node:resize(axis, value)
|
||||
-- the application works fine with non-integer values but to have pixel-perfect
|
||||
-- placements of view elements, like the scrollbar, we round the value to be
|
||||
-- an integer.
|
||||
value = math.floor(value)
|
||||
if self.type == 'leaf' then
|
||||
-- If it is not locked we don't accept the
|
||||
-- resize operation here because for proportional panes the resize is
|
||||
-- done using the "divider" value of the parent node.
|
||||
if self:is_locked_resizable(axis) then
|
||||
return self.active_view:set_target_size(axis, value)
|
||||
end
|
||||
else
|
||||
if self.type == (axis == "x" and "hsplit" or "vsplit") then
|
||||
-- we are resizing a node that is splitted along the resize axis
|
||||
if self.a:is_locked_resizable(axis) and self.b:is_locked_resizable(axis) then
|
||||
local rem_value = value - self.a.size[axis]
|
||||
if rem_value >= 0 then
|
||||
return self.b.active_view:set_target_size(axis, rem_value)
|
||||
else
|
||||
self.b.active_view:set_target_size(axis, 0)
|
||||
return self.a.active_view:set_target_size(axis, value)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- we are resizing a node that is splitted along the axis perpendicular
|
||||
-- to the resize axis
|
||||
local a_resizable = self.a:is_resizable(axis)
|
||||
local b_resizable = self.b:is_resizable(axis)
|
||||
if a_resizable and b_resizable then
|
||||
self.a:resize(axis, value)
|
||||
self.b:resize(axis, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_split_type(mouse_x, mouse_y)
|
||||
local x, y = self.position.x, self.position.y
|
||||
local w, h = self.size.x, self.size.y
|
||||
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
||||
y = y + tab_h
|
||||
h = h - tab_h
|
||||
|
||||
local local_mouse_x = mouse_x - x
|
||||
local local_mouse_y = mouse_y - y
|
||||
|
||||
if local_mouse_y < 0 then
|
||||
return "tab"
|
||||
else
|
||||
local left_pct = local_mouse_x * 100 / w
|
||||
local top_pct = local_mouse_y * 100 / h
|
||||
if left_pct <= 30 then
|
||||
return "left"
|
||||
elseif left_pct >= 70 then
|
||||
return "right"
|
||||
elseif top_pct <= 30 then
|
||||
return "up"
|
||||
elseif top_pct >= 70 then
|
||||
return "down"
|
||||
end
|
||||
return "middle"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
||||
local tab_index = self:get_tab_overlapping_point(x, y)
|
||||
if not tab_index then
|
||||
local first_tab_x = self:get_tab_rect(1)
|
||||
if x < first_tab_x then
|
||||
-- mouse before first visible tab
|
||||
tab_index = self.tab_offset or 1
|
||||
else
|
||||
-- mouse after last visible tab
|
||||
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
||||
end
|
||||
end
|
||||
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
||||
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
||||
-- use next tab
|
||||
tab_x = tab_x + tab_w
|
||||
tab_index = tab_index + 1
|
||||
end
|
||||
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
||||
-- the tab we are moving is counted in tab_index
|
||||
tab_index = tab_index - 1
|
||||
tab_x = tab_x - tab_w
|
||||
end
|
||||
return tab_index, tab_x, tab_y, tab_w, tab_h
|
||||
end
|
||||
|
||||
return Node
|
|
@ -20,17 +20,6 @@ function Object:extend()
|
|||
end
|
||||
|
||||
|
||||
function Object:implement(...)
|
||||
for _, cls in pairs({...}) do
|
||||
for k, v in pairs(cls) do
|
||||
if self[k] == nil and type(v) == "function" then
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
-- So that in addition to regex.gsub(pattern, string), we can also do
|
||||
-- pattern:gsub(string).
|
||||
regex.__index = function(table, key) return regex[key]; end
|
||||
|
||||
regex.match = function(pattern_string, string, offset, options)
|
||||
local pattern = type(pattern_string) == "table" and
|
||||
pattern_string or regex.compile(pattern_string)
|
||||
local s, e = regex.cmatch(pattern, string, offset or 1, options or 0)
|
||||
return s, e and e - 1
|
||||
end
|
||||
|
||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
||||
-- mid character.
|
||||
local function previous_character(str, index)
|
||||
local byte
|
||||
repeat
|
||||
index = index - 1
|
||||
byte = string.byte(str, index)
|
||||
until byte < 128 or byte >= 192
|
||||
return index
|
||||
end
|
||||
|
||||
-- Moves to the end of the identified character.
|
||||
local function end_character(str, index)
|
||||
local byte = string.byte(str, index + 1)
|
||||
while byte and byte >= 128 and byte < 192 do
|
||||
index = index + 1
|
||||
byte = string.byte(str, index + 1)
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
-- Build off matching. For now, only support basic replacements, but capture
|
||||
-- groupings should be doable. We can even have custom group replacements and
|
||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||
-- as \1 - \9.
|
||||
-- Should work on UTF-8 text.
|
||||
regex.gsub = function(pattern_string, str, replacement)
|
||||
local pattern = type(pattern_string) == "table" and
|
||||
pattern_string or regex.compile(pattern_string)
|
||||
local result, indices = ""
|
||||
local matches, replacements = {}, {}
|
||||
repeat
|
||||
indices = { regex.cmatch(pattern, str) }
|
||||
if #indices > 0 then
|
||||
table.insert(matches, indices)
|
||||
local currentReplacement = replacement
|
||||
if #indices > 2 then
|
||||
for i = 1, (#indices/2 - 1) do
|
||||
currentReplacement = string.gsub(
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
||||
)
|
||||
end
|
||||
end
|
||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||
if indices[1] > 1 then
|
||||
result = result ..
|
||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||
else
|
||||
result = result .. currentReplacement
|
||||
end
|
||||
str = str:sub(indices[2])
|
||||
end
|
||||
until #indices == 0 or indices[1] == indices[2]
|
||||
return result .. str, matches, replacements
|
||||
end
|
||||
|
|
@ -1,522 +1,11 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local Object = require "core.object"
|
||||
local Node = require "core.node"
|
||||
local View = require "core.view"
|
||||
local CommandView = require "core.commandview"
|
||||
local NagView = require "core.nagview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
|
||||
local EmptyView = View:extend()
|
||||
|
||||
local function draw_text(x, y, color)
|
||||
local th = style.big_font:get_height()
|
||||
local dh = 2 * th + style.padding.y * 2
|
||||
local x1, y1 = x, y + (dh - th) / 2
|
||||
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
||||
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
||||
x = x + style.padding.x
|
||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
||||
local lines = {
|
||||
{ fmt = "%s to run a command", cmd = "core:find-command" },
|
||||
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
||||
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
|
||||
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
|
||||
}
|
||||
th = style.font:get_height()
|
||||
y = y + (dh - (th + style.padding.y) * #lines) / 2
|
||||
local w = 0
|
||||
for _, line in ipairs(lines) do
|
||||
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
|
||||
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
|
||||
y = y + th + style.padding.y
|
||||
end
|
||||
return w, dh
|
||||
end
|
||||
|
||||
function EmptyView:draw()
|
||||
self:draw_background(style.background)
|
||||
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
|
||||
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
|
||||
local y = self.position.y + (self.size.y - h) / 2
|
||||
draw_text(x, y, style.dim)
|
||||
end
|
||||
|
||||
|
||||
|
||||
local Node = Object:extend()
|
||||
|
||||
function Node:new(type)
|
||||
self.type = type or "leaf"
|
||||
self.position = { x = 0, y = 0 }
|
||||
self.size = { x = 0, y = 0 }
|
||||
self.views = {}
|
||||
self.divider = 0.5
|
||||
if self.type == "leaf" then
|
||||
self:add_view(EmptyView())
|
||||
end
|
||||
self.hovered_close = 0
|
||||
end
|
||||
|
||||
|
||||
function Node:propagate(fn, ...)
|
||||
self.a[fn](self.a, ...)
|
||||
self.b[fn](self.b, ...)
|
||||
end
|
||||
|
||||
|
||||
function Node:on_mouse_moved(x, y, ...)
|
||||
self.hovered_tab = self:get_tab_overlapping_point(x, y)
|
||||
if self.type == "leaf" then
|
||||
self:tab_mouse_moved(x, y)
|
||||
self.active_view:on_mouse_moved(x, y, ...)
|
||||
else
|
||||
self:propagate("on_mouse_moved", x, y, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:on_mouse_released(...)
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_released(...)
|
||||
else
|
||||
self:propagate("on_mouse_released", ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:consume(node)
|
||||
for k, _ in pairs(self) do self[k] = nil end
|
||||
for k, v in pairs(node) do self[k] = v end
|
||||
end
|
||||
|
||||
|
||||
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
|
||||
|
||||
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
|
||||
-- and it indicates if the node want to have a fixed size along the axis where the
|
||||
-- boolean is true. If not it will be expanded to take all the available space.
|
||||
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
|
||||
-- by the user. If the node is marked as resizable their view should provide a
|
||||
-- set_target_size method.
|
||||
function Node:split(dir, view, locked, resizable)
|
||||
assert(self.type == "leaf", "Tried to split non-leaf node")
|
||||
local node_type = assert(type_map[dir], "Invalid direction")
|
||||
local last_active = core.active_view
|
||||
local child = Node()
|
||||
child:consume(self)
|
||||
self:consume(Node(node_type))
|
||||
self.a = child
|
||||
self.b = Node()
|
||||
if view then self.b:add_view(view) end
|
||||
if locked then
|
||||
assert(type(locked) == 'table')
|
||||
self.b.locked = locked
|
||||
self.b.resizable = resizable or false
|
||||
core.set_active_view(last_active)
|
||||
end
|
||||
if dir == "up" or dir == "left" then
|
||||
self.a, self.b = self.b, self.a
|
||||
return self.a
|
||||
end
|
||||
return self.b
|
||||
end
|
||||
|
||||
|
||||
function Node:close_view(root, view)
|
||||
local new_active_view = view == self.active_view
|
||||
local do_close = function()
|
||||
if #self.views > 1 then
|
||||
local idx = self:get_view_idx(view)
|
||||
table.remove(self.views, idx)
|
||||
if new_active_view then
|
||||
self:set_active_view(self.views[idx] or self.views[#self.views])
|
||||
end
|
||||
else
|
||||
local parent = self:get_parent_node(root)
|
||||
local is_a = (parent.a == self)
|
||||
local other = parent[is_a and "b" or "a"]
|
||||
if other:get_locked_size() then
|
||||
self.views = {}
|
||||
self:add_view(EmptyView())
|
||||
else
|
||||
parent:consume(other)
|
||||
local p = parent
|
||||
while p.type ~= "leaf" do
|
||||
p = p[is_a and "a" or "b"]
|
||||
end
|
||||
p:set_active_view(p.active_view)
|
||||
if self.is_primary_node then
|
||||
p.is_primary_node = true
|
||||
end
|
||||
end
|
||||
end
|
||||
core.last_active_view = nil
|
||||
end
|
||||
view:try_close(do_close)
|
||||
end
|
||||
|
||||
|
||||
function Node:close_active_view(root)
|
||||
self:close_view(root, self.active_view)
|
||||
end
|
||||
|
||||
|
||||
function Node:add_view(view)
|
||||
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
||||
assert(not self.locked, "Tried to add view to locked node")
|
||||
if self.views[1] and self.views[1]:is(EmptyView) then
|
||||
table.remove(self.views)
|
||||
end
|
||||
table.insert(self.views, view)
|
||||
self:set_active_view(view)
|
||||
end
|
||||
|
||||
|
||||
function Node:set_active_view(view)
|
||||
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
||||
self.active_view = view
|
||||
core.set_active_view(view)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_view_idx(view)
|
||||
for i, v in ipairs(self.views) do
|
||||
if v == view then return i end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_node_for_view(view)
|
||||
for _, v in ipairs(self.views) do
|
||||
if v == view then return self end
|
||||
end
|
||||
if self.type ~= "leaf" then
|
||||
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_parent_node(root)
|
||||
if root.a == self or root.b == self then
|
||||
return root
|
||||
elseif root.type ~= "leaf" then
|
||||
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_children(t)
|
||||
t = t or {}
|
||||
for _, view in ipairs(self.views) do
|
||||
table.insert(t, view)
|
||||
end
|
||||
if self.a then self.a:get_children(t) end
|
||||
if self.b then self.b:get_children(t) end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function Node:get_divider_overlapping_point(px, py)
|
||||
if self.type ~= "leaf" then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
w, h = w + p * 2, h + p * 2
|
||||
if px > x and py > y and px < x + w and py < y + h then
|
||||
return self
|
||||
end
|
||||
return self.a:get_divider_overlapping_point(px, py)
|
||||
or self.b:get_divider_overlapping_point(px, py)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_overlapping_point(px, py)
|
||||
if #self.views == 1 then return nil end
|
||||
local x, y, w, h = self:get_tab_rect(1)
|
||||
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
|
||||
return math.floor((px - x) / w) + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function close_button_location(x, w)
|
||||
local cw = style.icon_font:get_width("C")
|
||||
local pad = style.padding.y
|
||||
return x + w - pad - cw, cw, pad
|
||||
end
|
||||
|
||||
|
||||
function Node:tab_mouse_moved(px, py)
|
||||
local tab_index = self:get_tab_overlapping_point(px, py)
|
||||
if tab_index then
|
||||
local x, y, w, h = self:get_tab_rect(tab_index)
|
||||
local cx, cw = close_button_location(x, w)
|
||||
if px >= cx and px < cx + cw and py >= y and py < y + h then
|
||||
self.hovered_close = tab_index
|
||||
return
|
||||
end
|
||||
end
|
||||
self.hovered_close = 0
|
||||
end
|
||||
|
||||
|
||||
function Node:get_child_overlapping_point(x, y)
|
||||
local child
|
||||
if self.type == "leaf" then
|
||||
return self
|
||||
elseif self.type == "hsplit" then
|
||||
child = (x < self.b.position.x) and self.a or self.b
|
||||
elseif self.type == "vsplit" then
|
||||
child = (y < self.b.position.y) and self.a or self.b
|
||||
end
|
||||
return child:get_child_overlapping_point(x, y)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_rect(idx)
|
||||
local tw = math.min(style.tab_width, math.ceil(self.size.x / #self.views))
|
||||
local h = style.font:get_height() + style.padding.y * 2
|
||||
return self.position.x + (idx-1) * tw, self.position.y, tw, h
|
||||
end
|
||||
|
||||
|
||||
function Node:get_divider_rect()
|
||||
local x, y = self.position.x, self.position.y
|
||||
if self.type == "hsplit" then
|
||||
return x + self.a.size.x, y, style.divider_size, self.size.y
|
||||
elseif self.type == "vsplit" then
|
||||
return x, y + self.a.size.y, self.size.x, style.divider_size
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Return two values for x and y axis and each of them is either falsy or a number.
|
||||
-- A falsy value indicate no fixed size along the corresponding direction.
|
||||
function Node:get_locked_size()
|
||||
if self.type == "leaf" then
|
||||
if self.locked then
|
||||
local size = self.active_view.size
|
||||
-- The values below should be either a falsy value or a number
|
||||
local sx = (self.locked and self.locked.x) and size.x
|
||||
local sy = (self.locked and self.locked.y) and size.y
|
||||
return sx, sy
|
||||
end
|
||||
else
|
||||
local x1, y1 = self.a:get_locked_size()
|
||||
local x2, y2 = self.b:get_locked_size()
|
||||
-- The values below should be either a falsy value or a number
|
||||
local sx, sy
|
||||
if self.type == 'hsplit' then
|
||||
if x1 and x2 then
|
||||
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
|
||||
sx = x1 + x2 + dsx
|
||||
end
|
||||
sy = y1 or y2
|
||||
else
|
||||
if y1 and y2 then
|
||||
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
|
||||
sy = y1 + y2 + dsy
|
||||
end
|
||||
sx = x1 or x2
|
||||
end
|
||||
return sx, sy
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function copy_position_and_size(dst, src)
|
||||
dst.position.x, dst.position.y = src.position.x, src.position.y
|
||||
dst.size.x, dst.size.y = src.size.x, src.size.y
|
||||
end
|
||||
|
||||
|
||||
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
|
||||
-- axis are swapped; this function lets us use the same code for both
|
||||
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
|
||||
local n
|
||||
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
|
||||
if x1 then
|
||||
n = x1 + ds
|
||||
elseif x2 then
|
||||
n = self.size[x] - x2
|
||||
else
|
||||
n = math.floor(self.size[x] * self.divider)
|
||||
end
|
||||
self.a.position[x] = self.position[x]
|
||||
self.a.position[y] = self.position[y]
|
||||
self.a.size[x] = n - ds
|
||||
self.a.size[y] = self.size[y]
|
||||
self.b.position[x] = self.position[x] + n
|
||||
self.b.position[y] = self.position[y]
|
||||
self.b.size[x] = self.size[x] - n
|
||||
self.b.size[y] = self.size[y]
|
||||
end
|
||||
|
||||
|
||||
function Node:update_layout()
|
||||
if self.type == "leaf" then
|
||||
local av = self.active_view
|
||||
if #self.views > 1 then
|
||||
local _, _, _, th = self:get_tab_rect(1)
|
||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
||||
else
|
||||
copy_position_and_size(av, self)
|
||||
end
|
||||
else
|
||||
local x1, y1 = self.a:get_locked_size()
|
||||
local x2, y2 = self.b:get_locked_size()
|
||||
if self.type == "hsplit" then
|
||||
calc_split_sizes(self, "x", "y", x1, x2)
|
||||
elseif self.type == "vsplit" then
|
||||
calc_split_sizes(self, "y", "x", y1, y2)
|
||||
end
|
||||
self.a:update_layout()
|
||||
self.b:update_layout()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:update()
|
||||
if self.type == "leaf" then
|
||||
for _, view in ipairs(self.views) do
|
||||
view:update()
|
||||
end
|
||||
else
|
||||
self.a:update()
|
||||
self.b:update()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:draw_tabs()
|
||||
local x, y, _, h = self:get_tab_rect(1)
|
||||
local ds = style.divider_size
|
||||
core.push_clip_rect(x, y, self.size.x, h)
|
||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||
|
||||
for i, view in ipairs(self.views) do
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
local text = view:get_name()
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if view == self.active_view then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = (view == self.active_view or i == self.hovered_tab)
|
||||
if show_close_button then
|
||||
local close_style = self.hovered_close == i and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
if i == self.hovered_tab then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
core.push_clip_rect(x, y, show_close_button and cx - x - cspace or w - padx, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = style.font:get_width(text) > w and "left" or "center"
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
||||
function Node:draw()
|
||||
if self.type == "leaf" then
|
||||
if #self.views > 1 then
|
||||
self:draw_tabs()
|
||||
end
|
||||
local pos, size = self.active_view.position, self.active_view.size
|
||||
core.push_clip_rect(pos.x, pos.y, size.x + pos.x % 1, size.y + pos.y % 1)
|
||||
self.active_view:draw()
|
||||
core.pop_clip_rect()
|
||||
else
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
renderer.draw_rect(x, y, w, h, style.divider)
|
||||
self:propagate("draw")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:is_empty()
|
||||
if self.type == "leaf" then
|
||||
return #self.views == 0
|
||||
else
|
||||
return self.a:is_empty() and self.b:is_empty()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:close_all_docviews()
|
||||
if self.type == "leaf" then
|
||||
local i = 1
|
||||
while i <= #self.views do
|
||||
local view = self.views[i]
|
||||
if view:is(DocView) and not view:is(CommandView) then
|
||||
table.remove(self.views, i)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
if #self.views == 0 and self.is_primary_node then
|
||||
self:add_view(EmptyView())
|
||||
end
|
||||
else
|
||||
self.a:close_all_docviews()
|
||||
self.b:close_all_docviews()
|
||||
if self.a:is_empty() then
|
||||
self:consume(self.b)
|
||||
elseif self.b:is_empty() then
|
||||
self:consume(self.a)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:is_resizable(axis)
|
||||
if self.type == 'leaf' then
|
||||
return not self.locked or not self.locked[axis] or self.resizable
|
||||
else
|
||||
local a_resizable = self.a:is_resizable(axis)
|
||||
local b_resizable = self.b:is_resizable(axis)
|
||||
return a_resizable and b_resizable
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:resize(axis, value)
|
||||
if self.type == 'leaf' then
|
||||
-- The logic here is: accept the resize only if locked along the axis
|
||||
-- and is declared "resizable". If it is not locked we don't accept the
|
||||
-- resize operation here because for proportional panes the resize is
|
||||
-- done using the "divider" value of the parent node.
|
||||
if (self.locked and self.locked[axis]) and self.resizable then
|
||||
assert(self.active_view.set_target_size, "internal error: the view of a resizable \"locked\" node do not provide a set_target_size method")
|
||||
return self.active_view:set_target_size(axis, value)
|
||||
end
|
||||
else
|
||||
local a_resizable = self.a:is_resizable(axis)
|
||||
local b_resizable = self.b:is_resizable(axis)
|
||||
if a_resizable and b_resizable then
|
||||
self.a:resize(axis, value)
|
||||
self.b:resize(axis, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local RootView = View:extend()
|
||||
|
||||
function RootView:new()
|
||||
|
@ -524,6 +13,14 @@ function RootView:new()
|
|||
self.root_node = Node()
|
||||
self.deferred_draws = {}
|
||||
self.mouse = { x = 0, y = 0 }
|
||||
self.drag_overlay = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||
base_color = style.drag_overlay,
|
||||
color = { table.unpack(style.drag_overlay) } }
|
||||
self.drag_overlay.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
self.drag_overlay_tab = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||
base_color = style.drag_overlay_tab,
|
||||
color = { table.unpack(style.drag_overlay_tab) } }
|
||||
self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
end
|
||||
|
||||
|
||||
|
@ -564,6 +61,24 @@ function RootView:get_primary_node()
|
|||
end
|
||||
|
||||
|
||||
local function select_next_primary_node(node)
|
||||
if node.is_primary_node then return end
|
||||
if node.type ~= "leaf" then
|
||||
return select_next_primary_node(node.a) or select_next_primary_node(node.b)
|
||||
else
|
||||
local lx, ly = node:get_locked_size()
|
||||
if not lx and not ly then
|
||||
return node
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:select_next_primary_node()
|
||||
return select_next_primary_node(self.root_node)
|
||||
end
|
||||
|
||||
|
||||
function RootView:open_doc(doc)
|
||||
local node = self:get_active_node_default()
|
||||
for i, view in ipairs(node.views) do
|
||||
|
@ -580,42 +95,122 @@ function RootView:open_doc(doc)
|
|||
end
|
||||
|
||||
|
||||
function RootView:close_all_docviews()
|
||||
self.root_node:close_all_docviews()
|
||||
function RootView:close_all_docviews(keep_active)
|
||||
self.root_node:close_all_docviews(keep_active)
|
||||
end
|
||||
|
||||
|
||||
-- Function to intercept mouse pressed events on the active view.
|
||||
-- Do nothing by default.
|
||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if div then
|
||||
self.dragged_divider = div
|
||||
return
|
||||
end
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
if div and (node and not node.active_view:scrollbar_overlaps_point(x, y)) then
|
||||
self.dragged_divider = div
|
||||
return true
|
||||
end
|
||||
if node.hovered_scroll_button > 0 then
|
||||
node:scroll_tabs(node.hovered_scroll_button)
|
||||
return true
|
||||
end
|
||||
local idx = node:get_tab_overlapping_point(x, y)
|
||||
if idx then
|
||||
if button == "middle" or node.hovered_close == idx then
|
||||
node:close_view(self.root_node, node.views[idx])
|
||||
return true
|
||||
else
|
||||
if button == "left" then
|
||||
self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y}
|
||||
end
|
||||
node:set_active_view(node.views[idx])
|
||||
return true
|
||||
end
|
||||
else
|
||||
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
|
||||
core.set_active_view(node.active_view)
|
||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
return self.on_view_mouse_pressed(button, x, y, clicks) or node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(...)
|
||||
function RootView:get_overlay_base_color(overlay)
|
||||
if overlay == self.drag_overlay then
|
||||
return style.drag_overlay
|
||||
else
|
||||
return style.drag_overlay_tab
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:set_show_overlay(overlay, status)
|
||||
overlay.visible = status
|
||||
if status then -- reset colors
|
||||
-- reload base_color
|
||||
overlay.base_color = self:get_overlay_base_color(overlay)
|
||||
overlay.color[1] = overlay.base_color[1]
|
||||
overlay.color[2] = overlay.base_color[2]
|
||||
overlay.color[3] = overlay.base_color[3]
|
||||
overlay.color[4] = overlay.base_color[4]
|
||||
overlay.opacity = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(button, x, y, ...)
|
||||
if self.dragged_divider then
|
||||
self.dragged_divider = nil
|
||||
end
|
||||
self.root_node:on_mouse_released(...)
|
||||
if self.dragged_node then
|
||||
if button == "left" then
|
||||
if self.dragged_node.dragging then
|
||||
local node = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||
local dragged_node = self.dragged_node.node
|
||||
|
||||
if node and not node.locked
|
||||
-- don't do anything if dragging onto own node, with only one view
|
||||
and (node ~= dragged_node or #node.views > 1) then
|
||||
local split_type = node:get_split_type(self.mouse.x, self.mouse.y)
|
||||
local view = dragged_node.views[self.dragged_node.idx]
|
||||
|
||||
if split_type ~= "middle" and split_type ~= "tab" then -- needs splitting
|
||||
local new_node = node:split(split_type)
|
||||
self.root_node:get_node_for_view(view):remove_view(self.root_node, view)
|
||||
new_node:add_view(view)
|
||||
elseif split_type == "middle" and node ~= dragged_node then -- move to other node
|
||||
dragged_node:remove_view(self.root_node, view)
|
||||
node:add_view(view)
|
||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||
elseif split_type == "tab" then -- move besides other tabs
|
||||
local tab_index = node:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y, dragged_node, self.dragged_node.idx)
|
||||
dragged_node:remove_view(self.root_node, view)
|
||||
node:add_view(view, tab_index)
|
||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||
end
|
||||
self.root_node:update_layout()
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
if self.dragged_node and self.dragged_node.dragging then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
self.dragged_node = nil
|
||||
end
|
||||
else -- avoid sending on_mouse_released events when dragging tabs
|
||||
self.root_node:on_mouse_released(button, x, y, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function resize_child_node(node, axis, value, delta)
|
||||
local accept_resize = node.a:resize(axis, value) or node.b:resize(axis, node.size[axis] - value)
|
||||
local accept_resize = node.a:resize(axis, value)
|
||||
if not accept_resize then
|
||||
accept_resize = node.b:resize(axis, node.size[axis] - value)
|
||||
end
|
||||
if not accept_resize then
|
||||
node.divider = node.divider + delta / node.size[axis]
|
||||
end
|
||||
|
@ -624,7 +219,7 @@ end
|
|||
|
||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||
if core.active_view == core.nag_view then
|
||||
system.set_cursor("arrow")
|
||||
core.request_cursor("arrow")
|
||||
core.active_view:on_mouse_moved(x, y, dx, dy)
|
||||
return
|
||||
end
|
||||
|
@ -643,19 +238,33 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
end
|
||||
|
||||
self.mouse.x, self.mouse.y = x, y
|
||||
|
||||
local dn = self.dragged_node
|
||||
if dn and not dn.dragging then
|
||||
-- start dragging only after enough movement
|
||||
dn.dragging = common.distance(x, y, dn.drag_start_x, dn.drag_start_y) > style.tab_width * .05
|
||||
if dn.dragging then
|
||||
core.request_cursor("hand")
|
||||
end
|
||||
end
|
||||
|
||||
-- avoid sending on_mouse_moved events when dragging tabs
|
||||
if dn then return end
|
||||
|
||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
self.overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if div then
|
||||
local axis = (div.type == "hsplit" and "x" or "y")
|
||||
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
||||
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
end
|
||||
elseif node:get_tab_overlapping_point(x, y) then
|
||||
system.set_cursor("arrow")
|
||||
else
|
||||
system.set_cursor(node.active_view.cursor)
|
||||
local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
|
||||
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
|
||||
core.request_cursor("arrow")
|
||||
elseif div and (self.overlapping_node and not self.overlapping_node.active_view:scrollbar_overlaps_point(x, y)) then
|
||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
elseif tab_index then
|
||||
core.request_cursor("arrow")
|
||||
elseif self.overlapping_node then
|
||||
core.request_cursor(self.overlapping_node.active_view.cursor)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -663,7 +272,7 @@ end
|
|||
function RootView:on_mouse_wheel(...)
|
||||
local x, y = self.mouse.x, self.mouse.y
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
node.active_view:on_mouse_wheel(...)
|
||||
return node.active_view:on_mouse_wheel(...)
|
||||
end
|
||||
|
||||
|
||||
|
@ -677,10 +286,110 @@ function RootView:on_focus_lost(...)
|
|||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
function RootView:interpolate_drag_overlay(overlay)
|
||||
self:move_towards(overlay, "x", overlay.to.x)
|
||||
self:move_towards(overlay, "y", overlay.to.y)
|
||||
self:move_towards(overlay, "w", overlay.to.w)
|
||||
self:move_towards(overlay, "h", overlay.to.h)
|
||||
|
||||
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0)
|
||||
overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
|
||||
end
|
||||
|
||||
|
||||
function RootView:update()
|
||||
copy_position_and_size(self.root_node, self)
|
||||
Node.copy_position_and_size(self.root_node, self)
|
||||
self.root_node:update()
|
||||
self.root_node:update_layout()
|
||||
|
||||
self:update_drag_overlay()
|
||||
self:interpolate_drag_overlay(self.drag_overlay)
|
||||
self:interpolate_drag_overlay(self.drag_overlay_tab)
|
||||
end
|
||||
|
||||
|
||||
function RootView:set_drag_overlay(overlay, x, y, w, h, immediate)
|
||||
overlay.to.x = x
|
||||
overlay.to.y = y
|
||||
overlay.to.w = w
|
||||
overlay.to.h = h
|
||||
if immediate then
|
||||
overlay.x = x
|
||||
overlay.y = y
|
||||
overlay.w = w
|
||||
overlay.h = h
|
||||
end
|
||||
if not overlay.visible then
|
||||
self:set_show_overlay(overlay, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_split_sizes(split_type, x, y, w, h)
|
||||
if split_type == "left" then
|
||||
w = w * .5
|
||||
elseif split_type == "right" then
|
||||
x = x + w * .5
|
||||
w = w * .5
|
||||
elseif split_type == "up" then
|
||||
h = h * .5
|
||||
elseif split_type == "down" then
|
||||
y = y + h * .5
|
||||
h = h * .5
|
||||
end
|
||||
return x, y, w, h
|
||||
end
|
||||
|
||||
|
||||
function RootView:update_drag_overlay()
|
||||
if not (self.dragged_node and self.dragged_node.dragging) then return end
|
||||
local over = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||
if over and not over.locked then
|
||||
local _, _, _, tab_h = over:get_scroll_button_rect(1)
|
||||
local x, y = over.position.x, over.position.y
|
||||
local w, h = over.size.x, over.size.y
|
||||
local split_type = over:get_split_type(self.mouse.x, self.mouse.y)
|
||||
|
||||
if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then
|
||||
local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y)
|
||||
self:set_drag_overlay(self.drag_overlay_tab,
|
||||
tab_x + (tab_index and 0 or tab_w), tab_y,
|
||||
style.caret_width, tab_h,
|
||||
-- avoid showing tab overlay moving between nodes
|
||||
over ~= self.drag_overlay_tab.last_over)
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self.drag_overlay_tab.last_over = over
|
||||
else
|
||||
if (over ~= self.dragged_node.node or #over.views > 1) then
|
||||
y = y + tab_h
|
||||
h = h - tab_h
|
||||
x, y, w, h = get_split_sizes(split_type, x, y, w, h)
|
||||
end
|
||||
self:set_drag_overlay(self.drag_overlay, x, y, w, h)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
end
|
||||
else
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw_grabbed_tab()
|
||||
local dn = self.dragged_node
|
||||
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
||||
local x = self.mouse.x - w / 2
|
||||
local y = self.mouse.y - h / 2
|
||||
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
||||
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw_drag_overlay(ov)
|
||||
if ov.opacity > 0 then
|
||||
renderer.draw_rect(ov.x, ov.y, ov.w, ov.h, ov.color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -690,6 +399,16 @@ function RootView:draw()
|
|||
local t = table.remove(self.deferred_draws)
|
||||
t.fn(table.unpack(t))
|
||||
end
|
||||
|
||||
self:draw_drag_overlay(self.drag_overlay)
|
||||
self:draw_drag_overlay(self.drag_overlay_tab)
|
||||
if self.dragged_node and self.dragged_node.dragging then
|
||||
self:draw_grabbed_tab()
|
||||
end
|
||||
if core.cursor_change_req then
|
||||
system.set_cursor(core.cursor_change_req)
|
||||
core.cursor_change_req = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
-- this file is used by lite-xl to setup the Lua environment
|
||||
-- when starting
|
||||
VERSION = "1.16.6"
|
||||
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||
VERSION = "2.0.3r1"
|
||||
MOD_VERSION = "2"
|
||||
|
||||
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
||||
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
|
||||
PATHSEP = package.config:sub(1, 1)
|
||||
|
||||
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
|
||||
|
@ -12,10 +12,23 @@ else
|
|||
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
||||
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
||||
end
|
||||
USERDIR = HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user')
|
||||
USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")
|
||||
or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
|
||||
|
||||
package.path = DATADIR .. '/?.lua;' .. package.path
|
||||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
||||
|
||||
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
||||
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
|
||||
package.native_plugins = {}
|
||||
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
||||
local path = package.searchpath(modname, package.cpath)
|
||||
if not path then return nil end
|
||||
return system.load_native_plugin, path
|
||||
end }
|
||||
|
||||
table.pack = table.pack or pack or function(...) return {...} end
|
||||
table.unpack = table.unpack or unpack
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ local style = require "core.style"
|
|||
local DocView = require "core.docview"
|
||||
local LogView = require "core.logview"
|
||||
local View = require "core.view"
|
||||
local Object = require "core.object"
|
||||
|
||||
|
||||
local StatusView = View:extend()
|
||||
|
@ -29,6 +30,7 @@ function StatusView:on_mouse_pressed()
|
|||
and not core.active_view:is(LogView) then
|
||||
command.perform "core:open-log"
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
@ -70,7 +72,7 @@ local function draw_items(self, items, x, y, draw_fn)
|
|||
local color = style.text
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
if type(item) == "userdata" then
|
||||
if Object.is(item, renderer.font) then
|
||||
font = item
|
||||
elseif type(item) == "table" then
|
||||
color = item
|
||||
|
@ -107,9 +109,9 @@ function StatusView:get_items()
|
|||
local dv = core.active_view
|
||||
local line, col = dv.doc:get_selection()
|
||||
local dirty = dv.doc:is_dirty()
|
||||
local indent = dv.doc.indent_info
|
||||
local indent_label = (indent and indent.type == "hard") and "tabs: " or "spaces: "
|
||||
local indent_size = indent and tostring(indent.size) .. (indent.confirmed and "" or "*") or "unknown"
|
||||
local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info()
|
||||
local indent_label = (indent_type == "hard") and "tabs: " or "spaces: "
|
||||
local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown"
|
||||
|
||||
return {
|
||||
dirty and style.accent or style.text, style.icon_font, "f",
|
||||
|
|
|
@ -21,40 +21,55 @@ style.tab_width = common.round(170 * SCALE)
|
|||
--
|
||||
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
||||
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
||||
style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
|
||||
style.big_font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 34 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 20 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
|
||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
|
||||
style.big_font = style.font:copy(46 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(23 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
||||
|
||||
style.background = { common.color "#2e2e32" }
|
||||
style.background2 = { common.color "#252529" }
|
||||
style.background3 = { common.color "#252529" }
|
||||
style.background = { common.color "#2e2e32" } -- Docview
|
||||
style.background2 = { common.color "#252529" } -- Treeview
|
||||
style.background3 = { common.color "#252529" } -- Command view
|
||||
style.text = { common.color "#97979c" }
|
||||
style.caret = { common.color "#93DDFA" }
|
||||
style.accent = { common.color "#e1e1e6" }
|
||||
-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and
|
||||
-- search result, hotkeys for context menu and command view
|
||||
style.dim = { common.color "#525257" }
|
||||
style.divider = { common.color "#202024" }
|
||||
style.divider = { common.color "#202024" } -- Line between nodes
|
||||
style.selection = { common.color "#48484f" }
|
||||
style.line_number = { common.color "#525259" }
|
||||
style.line_number2 = { common.color "#83838f" }
|
||||
style.line_number2 = { common.color "#83838f" } -- With cursor
|
||||
style.line_highlight = { common.color "#343438" }
|
||||
style.scrollbar = { common.color "#414146" }
|
||||
style.scrollbar2 = { common.color "#4b4b52" }
|
||||
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
|
||||
style.nagbar = { common.color "#FF0000" }
|
||||
style.nagbar_text = { common.color "#FFFFFF" }
|
||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
||||
style.good = { common.color "#72b886" }
|
||||
style.warn = { common.color "#FFA94D" }
|
||||
style.error = { common.color "#FF3333" }
|
||||
style.modified = { common.color "#1c7c9c" }
|
||||
|
||||
style.syntax = {}
|
||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
||||
style.syntax["comment"] = { common.color "#676b6f" }
|
||||
style.syntax["keyword"] = { common.color "#E58AC9" }
|
||||
style.syntax["keyword2"] = { common.color "#F77483" }
|
||||
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
|
||||
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
|
||||
style.syntax["number"] = { common.color "#FFA94D" }
|
||||
style.syntax["literal"] = { common.color "#FFA94D" }
|
||||
style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil
|
||||
style.syntax["string"] = { common.color "#f7c95c" }
|
||||
style.syntax["operator"] = { common.color "#93DDFA" }
|
||||
style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < >
|
||||
style.syntax["function"] = { common.color "#93DDFA" }
|
||||
|
||||
-- This can be used to override fonts per syntax group.
|
||||
-- The syntax highlighter will take existing values from this table and
|
||||
-- override style.code_font on a per-token basis, so you can choose to eg.
|
||||
-- render comments in an italic font if you want to.
|
||||
style.syntax_fonts = {}
|
||||
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
||||
|
||||
return style
|
||||
|
|
|
@ -3,7 +3,7 @@ local common = require "core.common"
|
|||
local syntax = {}
|
||||
syntax.items = {}
|
||||
|
||||
local plain_text_syntax = { patterns = {}, symbols = {} }
|
||||
local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
|
||||
|
||||
|
||||
function syntax.add(t)
|
||||
|
@ -22,7 +22,7 @@ end
|
|||
|
||||
function syntax.get(filename, header)
|
||||
return find(filename, "files")
|
||||
or find(header, "headers")
|
||||
or (header and find(header, "headers"))
|
||||
or plain_text_syntax
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
local tokenizer = {}
|
||||
local syntax = require "core.syntax"
|
||||
local common = require "core.common"
|
||||
|
||||
local tokenizer = {}
|
||||
|
||||
local function push_token(t, type, text)
|
||||
local prev_type = t[#t-1]
|
||||
|
@ -14,72 +16,220 @@ local function push_token(t, type, text)
|
|||
end
|
||||
|
||||
|
||||
local function is_escaped(text, idx, esc)
|
||||
local byte = esc:byte()
|
||||
local count = 0
|
||||
for i = idx - 1, 1, -1 do
|
||||
if text:byte(i) ~= byte then break end
|
||||
count = count + 1
|
||||
local function push_tokens(t, syn, pattern, full_text, find_results)
|
||||
if #find_results > 2 then
|
||||
-- We do some manipulation with find_results so that it's arranged
|
||||
-- like this:
|
||||
-- { start, end, i_1, i_2, i_3, …, i_last }
|
||||
-- Each position spans characters from i_n to ((i_n+1) - 1), to form
|
||||
-- consecutive spans of text.
|
||||
--
|
||||
-- If i_1 is not equal to start, start is automatically inserted at
|
||||
-- that index.
|
||||
if find_results[3] ~= find_results[1] then
|
||||
table.insert(find_results, 3, find_results[1])
|
||||
end
|
||||
-- Copy the ending index to the end of the table, so that an ending index
|
||||
-- always follows a starting index after position 3 in the table.
|
||||
table.insert(find_results, find_results[2] + 1)
|
||||
-- Then, we just iterate over our modified table.
|
||||
for i = 3, #find_results - 1 do
|
||||
local start = find_results[i]
|
||||
local fin = find_results[i + 1] - 1
|
||||
local type = pattern.type[i - 2]
|
||||
-- ↑ (i - 2) to convert from [3; n] to [1; n]
|
||||
local text = full_text:sub(start, fin)
|
||||
push_token(t, syn.symbols[text] or type, text)
|
||||
end
|
||||
else
|
||||
local start, fin = find_results[1], find_results[2]
|
||||
local text = full_text:sub(start, fin)
|
||||
push_token(t, syn.symbols[text] or pattern.type, text)
|
||||
end
|
||||
return count % 2 == 1
|
||||
end
|
||||
|
||||
|
||||
local function find_non_escaped(text, pattern, offset, esc)
|
||||
while true do
|
||||
local s, e = text:find(pattern, offset)
|
||||
if not s then break end
|
||||
if esc and is_escaped(text, s, esc) then
|
||||
offset = e + 1
|
||||
else
|
||||
return s, e
|
||||
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
||||
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
||||
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
||||
-- does not support further highlighting.
|
||||
|
||||
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
|
||||
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
|
||||
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
|
||||
-- that we're following in the syntax. The top of the stack can be any valid
|
||||
-- pattern index, any integer lower in the stack must represent a pattern that
|
||||
-- specifies a subsyntax.
|
||||
|
||||
-- If you do not have subsyntaxes in your syntax, the three most
|
||||
-- singificant numbers will always be 0, the stack will only ever be length 1
|
||||
-- and the state variable will only ever range from 0-255.
|
||||
local function retrieve_syntax_state(incoming_syntax, state)
|
||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
incoming_syntax, nil, state, 0
|
||||
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
|
||||
-- If we have higher bits, then decode them one at a time, and find which
|
||||
-- syntax we're using. Rather than walking the bytes, and calling into
|
||||
-- `syntax` each time, we could probably cache this in a single table.
|
||||
for i = 0, 2 do
|
||||
local target = bit32.extract(state, i*8, 8)
|
||||
if target ~= 0 then
|
||||
if current_syntax.patterns[target].syntax then
|
||||
subsyntax_info = current_syntax.patterns[target]
|
||||
current_syntax = type(subsyntax_info.syntax) == "table" and
|
||||
subsyntax_info.syntax or syntax.get(subsyntax_info.syntax)
|
||||
current_pattern_idx = 0
|
||||
current_level = i+1
|
||||
else
|
||||
current_pattern_idx = target
|
||||
break
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return current_syntax, subsyntax_info, current_pattern_idx, current_level
|
||||
end
|
||||
|
||||
|
||||
function tokenizer.tokenize(syntax, text, state)
|
||||
function tokenizer.tokenize(incoming_syntax, text, state)
|
||||
local res = {}
|
||||
local i = 1
|
||||
|
||||
if #syntax.patterns == 0 then
|
||||
if #incoming_syntax.patterns == 0 then
|
||||
return { "normal", text }
|
||||
end
|
||||
|
||||
state = state or 0
|
||||
-- incoming_syntax : the parent syntax of the file.
|
||||
-- state : a 32-bit number representing syntax state (see above)
|
||||
|
||||
-- current_syntax : the syntax we're currently in.
|
||||
-- subsyntax_info : info about the delimiters of this subsyntax.
|
||||
-- current_pattern_idx: the index of the pattern we're on for this syntax.
|
||||
-- current_level : how many subsyntaxes deep we are.
|
||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
retrieve_syntax_state(incoming_syntax, state)
|
||||
|
||||
-- Should be used to set the state variable. Don't modify it directly.
|
||||
local function set_subsyntax_pattern_idx(pattern_idx)
|
||||
current_pattern_idx = pattern_idx
|
||||
state = bit32.replace(state, pattern_idx, current_level*8, 8)
|
||||
end
|
||||
|
||||
|
||||
local function push_subsyntax(entering_syntax, pattern_idx)
|
||||
set_subsyntax_pattern_idx(pattern_idx)
|
||||
current_level = current_level + 1
|
||||
subsyntax_info = entering_syntax
|
||||
current_syntax = type(entering_syntax.syntax) == "table" and
|
||||
entering_syntax.syntax or syntax.get(entering_syntax.syntax)
|
||||
current_pattern_idx = 0
|
||||
end
|
||||
|
||||
local function pop_subsyntax()
|
||||
set_subsyntax_pattern_idx(0)
|
||||
current_level = current_level - 1
|
||||
set_subsyntax_pattern_idx(0)
|
||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
retrieve_syntax_state(incoming_syntax, state)
|
||||
end
|
||||
|
||||
local function find_text(text, p, offset, at_start, close)
|
||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
|
||||
local code = type(target) == "table" and target[close and 2 or 1] or target
|
||||
if p.regex and type(p.regex) ~= "table" then
|
||||
p._regex = p._regex or regex.compile(p.regex)
|
||||
code = p._regex
|
||||
end
|
||||
repeat
|
||||
local next = res[2] + 1
|
||||
-- go to the start of the next utf-8 character
|
||||
while text:byte(next) and common.is_utf8_cont(text, next) do
|
||||
next = next + 1
|
||||
end
|
||||
res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
|
||||
or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
|
||||
if res[1] and close and target[3] then
|
||||
local count = 0
|
||||
for i = res[1] - 1, 1, -1 do
|
||||
if text:byte(i) ~= target[3]:byte() then break end
|
||||
count = count + 1
|
||||
end
|
||||
-- Check to see if the escaped character is there,
|
||||
-- and if it is not itself escaped.
|
||||
if count % 2 == 0 then break end
|
||||
end
|
||||
until not res[1] or not close or not target[3]
|
||||
return table.unpack(res)
|
||||
end
|
||||
|
||||
while i <= #text do
|
||||
-- continue trying to match the end pattern of a pair if we have a state set
|
||||
if state then
|
||||
local p = syntax.patterns[state]
|
||||
local s, e = find_non_escaped(text, p.pattern[2], i, p.pattern[3])
|
||||
if current_pattern_idx > 0 then
|
||||
local p = current_syntax.patterns[current_pattern_idx]
|
||||
local s, e = find_text(text, p, i, false, true)
|
||||
|
||||
local cont = true
|
||||
-- If we're in subsyntax mode, always check to see if we end our syntax
|
||||
-- first, before the found delimeter, as ending the subsyntax takes
|
||||
-- precedence over ending the delimiter in the subsyntax.
|
||||
if subsyntax_info then
|
||||
local ss, se = find_text(text, subsyntax_info, i, false, true)
|
||||
-- If we find that we end the subsyntax before the
|
||||
-- delimiter, push the token, and signal we shouldn't
|
||||
-- treat the bit after as a token to be normally parsed
|
||||
-- (as it's the syntax delimiter).
|
||||
if ss and (s == nil or ss < s) then
|
||||
push_token(res, p.type, text:sub(i, ss - 1))
|
||||
i = ss
|
||||
cont = false
|
||||
end
|
||||
end
|
||||
-- If we don't have any concerns about syntax delimiters,
|
||||
-- continue on as normal.
|
||||
if cont then
|
||||
if s then
|
||||
push_token(res, p.type, text:sub(i, e))
|
||||
set_subsyntax_pattern_idx(0)
|
||||
i = e + 1
|
||||
else
|
||||
push_token(res, p.type, text:sub(i))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- General end of syntax check. Applies in the case where
|
||||
-- we're ending early in the middle of a delimiter, or
|
||||
-- just normally, upon finding a token.
|
||||
if subsyntax_info then
|
||||
local s, e = find_text(text, subsyntax_info, i, true, true)
|
||||
if s then
|
||||
push_token(res, p.type, text:sub(i, e))
|
||||
state = nil
|
||||
push_token(res, subsyntax_info.type, text:sub(i, e))
|
||||
-- On finding unescaped delimiter, pop it.
|
||||
pop_subsyntax()
|
||||
i = e + 1
|
||||
else
|
||||
push_token(res, p.type, text:sub(i))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- find matching pattern
|
||||
local matched = false
|
||||
for n, p in ipairs(syntax.patterns) do
|
||||
local pattern = (type(p.pattern) == "table") and p.pattern[1] or p.pattern
|
||||
local s, e = text:find("^" .. pattern, i)
|
||||
|
||||
if s then
|
||||
-- matched pattern; make and add token
|
||||
local t = text:sub(s, e)
|
||||
push_token(res, syntax.symbols[t] or p.type, t)
|
||||
|
||||
for n, p in ipairs(current_syntax.patterns) do
|
||||
local find_results = { find_text(text, p, i, true, false) }
|
||||
if find_results[1] then
|
||||
-- matched pattern; make and add tokens
|
||||
push_tokens(res, current_syntax, p, text, find_results)
|
||||
-- update state if this was a start|end pattern pair
|
||||
if type(p.pattern) == "table" then
|
||||
state = n
|
||||
if type(p.pattern or p.regex) == "table" then
|
||||
-- If we have a subsyntax, push that onto the subsyntax stack.
|
||||
if p.syntax then
|
||||
push_subsyntax(p, n)
|
||||
else
|
||||
set_subsyntax_pattern_idx(n)
|
||||
end
|
||||
end
|
||||
|
||||
-- move cursor past this token
|
||||
i = e + 1
|
||||
i = find_results[2] + 1
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
|
@ -87,8 +237,13 @@ function tokenizer.tokenize(syntax, text, state)
|
|||
|
||||
-- consume character if we didn't match
|
||||
if not matched then
|
||||
push_token(res, "normal", text:sub(i, i))
|
||||
i = i + 1
|
||||
local n = 0
|
||||
-- reach the next character
|
||||
while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do
|
||||
n = n + 1
|
||||
end
|
||||
push_token(res, "normal", text:sub(i, i + n))
|
||||
i = i + n + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ local Object = require "core.object"
|
|||
|
||||
local View = Object:extend()
|
||||
|
||||
-- context can be "application" or "session". The instance of objects
|
||||
-- with context "session" will be closed when a project session is
|
||||
-- terminated. The context "application" is for functional UI elements.
|
||||
View.context = "application"
|
||||
|
||||
function View:new()
|
||||
self.position = { x = 0, y = 0 }
|
||||
|
@ -98,13 +102,9 @@ function View:on_text_input(text)
|
|||
-- no-op
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_wheel(y)
|
||||
if self.scrollable then
|
||||
self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function View:get_content_bounds()
|
||||
local x = self.scroll.x
|
||||
|
@ -136,7 +136,7 @@ end
|
|||
function View:draw_background(color)
|
||||
local x, y = self.position.x, self.position.y
|
||||
local w, h = self.size.x, self.size.y
|
||||
renderer.draw_rect(x, y, w + x % 1, h + y % 1, color)
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
@ -8,25 +8,65 @@ local keymap = require "core.keymap"
|
|||
local translate = require "core.doc.translate"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.autocomplete_max_suggestions = 6
|
||||
config.plugins.autocomplete = {
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 3,
|
||||
-- The max amount of visible items
|
||||
max_height = 6,
|
||||
-- The max amount of scrollable items
|
||||
max_suggestions = 100,
|
||||
}
|
||||
|
||||
local autocomplete = {}
|
||||
autocomplete.map = {}
|
||||
|
||||
autocomplete.map = {}
|
||||
autocomplete.map_manually = {}
|
||||
autocomplete.on_close = nil
|
||||
|
||||
-- Flag that indicates if the autocomplete box was manually triggered
|
||||
-- with the autocomplete.complete() function to prevent the suggestions
|
||||
-- from getting cluttered with arbitrary document symbols by using the
|
||||
-- autocomplete.map_manually table.
|
||||
local triggered_manually = false
|
||||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
function autocomplete.add(t)
|
||||
function autocomplete.add(t, triggered_manually)
|
||||
local items = {}
|
||||
for text, info in pairs(t.items) do
|
||||
info = (type(info) == "string") and info
|
||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||
if type(info) == "table" then
|
||||
table.insert(
|
||||
items,
|
||||
setmetatable(
|
||||
{
|
||||
text = text,
|
||||
info = info.info,
|
||||
desc = info.desc, -- Description shown on item selected
|
||||
cb = info.cb, -- A callback called once when item is selected
|
||||
data = info.data -- Optional data that can be used on cb
|
||||
},
|
||||
mt
|
||||
)
|
||||
)
|
||||
else
|
||||
info = (type(info) == "string") and info
|
||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||
end
|
||||
end
|
||||
|
||||
if not triggered_manually then
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
else
|
||||
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
|
||||
end
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
end
|
||||
|
||||
local max_symbols = config.max_symbols or 2000
|
||||
--
|
||||
-- Thread that scans open document symbols and cache them
|
||||
--
|
||||
local max_symbols = config.max_symbols
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
@ -109,16 +149,39 @@ local last_line, last_col
|
|||
local function reset_suggestions()
|
||||
suggestions_idx = 1
|
||||
suggestions = {}
|
||||
|
||||
triggered_manually = false
|
||||
|
||||
local doc = core.active_view.doc
|
||||
if autocomplete.on_close then
|
||||
autocomplete.on_close(doc, suggestions[suggestions_idx])
|
||||
autocomplete.on_close = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function in_table(value, table_array)
|
||||
for i, element in pairs(table_array) do
|
||||
if element == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function update_suggestions()
|
||||
local doc = core.active_view.doc
|
||||
local filename = doc and doc.filename or ""
|
||||
|
||||
local map = autocomplete.map
|
||||
|
||||
if triggered_manually then
|
||||
map = autocomplete.map_manually
|
||||
end
|
||||
|
||||
-- get all relevant suggestions for given filename
|
||||
local items = {}
|
||||
for _, v in pairs(autocomplete.map) do
|
||||
for _, v in pairs(map) do
|
||||
if common.match_pattern(filename, v.files) then
|
||||
for _, item in pairs(v.items) do
|
||||
table.insert(items, item)
|
||||
|
@ -129,7 +192,7 @@ local function update_suggestions()
|
|||
-- fuzzy match, remove duplicates and store
|
||||
items = common.fuzzy_match(items, partial)
|
||||
local j = 1
|
||||
for i = 1, config.autocomplete_max_suggestions do
|
||||
for i = 1, config.plugins.autocomplete.max_suggestions do
|
||||
suggestions[i] = items[j]
|
||||
while items[j] and items[i].text == items[j].text do
|
||||
items[i].info = items[i].info or items[j].info
|
||||
|
@ -138,7 +201,6 @@ local function update_suggestions()
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_partial_symbol()
|
||||
local doc = core.active_view.doc
|
||||
local line2, col2 = doc:get_selection()
|
||||
|
@ -146,14 +208,12 @@ local function get_partial_symbol()
|
|||
return doc:get_text(line1, col1, line2, col2)
|
||||
end
|
||||
|
||||
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_suggestions_rect(av)
|
||||
if #suggestions == 0 then
|
||||
return 0, 0, 0, 0
|
||||
|
@ -175,15 +235,67 @@ local function get_suggestions_rect(av)
|
|||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
local max_items = #suggestions
|
||||
if max_items > ah then
|
||||
max_items = ah
|
||||
end
|
||||
|
||||
-- additional line to display total items
|
||||
max_items = max_items + 1
|
||||
|
||||
if max_width < 150 then
|
||||
max_width = 150
|
||||
end
|
||||
|
||||
return
|
||||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width + style.padding.x * 2,
|
||||
#suggestions * (th + style.padding.y) + style.padding.y
|
||||
max_items * (th + style.padding.y) + style.padding.y
|
||||
end
|
||||
|
||||
local function draw_description_box(text, av, sx, sy, sw, sh)
|
||||
local width = 0
|
||||
|
||||
local lines = {}
|
||||
for line in string.gmatch(text.."\n", "(.-)\n") do
|
||||
width = math.max(width, style.font:get_width(line))
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
local height = #lines * style.font:get_height()
|
||||
|
||||
-- draw background rect
|
||||
renderer.draw_rect(
|
||||
sx + sw + style.padding.x / 4,
|
||||
sy,
|
||||
width + style.padding.x * 2,
|
||||
height + style.padding.y * 2,
|
||||
style.background3
|
||||
)
|
||||
|
||||
-- draw text
|
||||
local lh = style.font:get_height()
|
||||
local y = sy + style.padding.y
|
||||
local x = sx + sw + style.padding.x / 4
|
||||
|
||||
for _, line in pairs(lines) do
|
||||
common.draw_text(
|
||||
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
|
||||
)
|
||||
y = y + lh
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_suggestions_box(av)
|
||||
if #suggestions <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
@ -192,7 +304,14 @@ local function draw_suggestions_box(av)
|
|||
local font = av:get_font()
|
||||
local lh = font:get_height() + style.padding.y
|
||||
local y = ry + style.padding.y / 2
|
||||
for i, s in ipairs(suggestions) do
|
||||
local show_count = #suggestions <= ah and #suggestions or ah
|
||||
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not suggestions[i] then
|
||||
break
|
||||
end
|
||||
local s = suggestions[i]
|
||||
local color = (i == suggestions_idx) and style.accent or style.text
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
if s.info then
|
||||
|
@ -200,26 +319,55 @@ local function draw_suggestions_box(av)
|
|||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||
end
|
||||
y = y + lh
|
||||
if suggestions_idx == i then
|
||||
if s.cb then
|
||||
s.cb(suggestions_idx, s)
|
||||
s.cb = nil
|
||||
s.data = nil
|
||||
end
|
||||
if s.desc and #s.desc > 0 then
|
||||
draw_description_box(s.desc, av, rx, ry, rw, rh)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
||||
renderer.draw_rect(rx, y+2, rw, lh, style.background)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
"Items",
|
||||
"left",
|
||||
rx + style.padding.x, y, rw, lh
|
||||
)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
tostring(suggestions_idx) .. "/" .. tostring(#suggestions),
|
||||
"right",
|
||||
rx, y, rw - style.padding.x, lh
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
-- patch event logic into RootView
|
||||
local on_text_input = RootView.on_text_input
|
||||
local update = RootView.update
|
||||
local draw = RootView.draw
|
||||
|
||||
|
||||
RootView.on_text_input = function(...)
|
||||
on_text_input(...)
|
||||
|
||||
local function show_autocomplete()
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
-- update partial symbol and suggestions
|
||||
partial = get_partial_symbol()
|
||||
if #partial >= 3 then
|
||||
|
||||
if #partial >= config.plugins.autocomplete.min_len or triggered_manually then
|
||||
update_suggestions()
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
|
||||
if not triggered_manually then
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
else
|
||||
local line, col = av.doc:get_selection()
|
||||
local char = av.doc:get_char(line, col-1, line, col-1)
|
||||
|
||||
if char:match("%s") or (char:match("%p") and col ~= last_col) then
|
||||
reset_suggestions()
|
||||
end
|
||||
end
|
||||
else
|
||||
reset_suggestions()
|
||||
end
|
||||
|
@ -233,6 +381,30 @@ RootView.on_text_input = function(...)
|
|||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Patch event logic into RootView and Doc
|
||||
--
|
||||
local on_text_input = RootView.on_text_input
|
||||
local on_text_remove = Doc.remove
|
||||
local update = RootView.update
|
||||
local draw = RootView.draw
|
||||
|
||||
RootView.on_text_input = function(...)
|
||||
on_text_input(...)
|
||||
show_autocomplete()
|
||||
end
|
||||
|
||||
Doc.remove = function(self, line1, col1, line2, col2)
|
||||
on_text_remove(self, line1, col1, line2, col2)
|
||||
|
||||
if triggered_manually and line1 == line2 then
|
||||
if last_col >= col1 then
|
||||
reset_suggestions()
|
||||
else
|
||||
show_autocomplete()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RootView.update = function(...)
|
||||
update(...)
|
||||
|
@ -241,13 +413,19 @@ RootView.update = function(...)
|
|||
if av then
|
||||
-- reset suggestions if caret was moved
|
||||
local line, col = av.doc:get_selection()
|
||||
if line ~= last_line or col ~= last_col then
|
||||
reset_suggestions()
|
||||
|
||||
if not triggered_manually then
|
||||
if line ~= last_line or col ~= last_col then
|
||||
reset_suggestions()
|
||||
end
|
||||
else
|
||||
if line ~= last_line or col < last_col then
|
||||
reset_suggestions()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
RootView.draw = function(...)
|
||||
draw(...)
|
||||
|
||||
|
@ -258,12 +436,53 @@ RootView.draw = function(...)
|
|||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Public functions
|
||||
--
|
||||
function autocomplete.open(on_close)
|
||||
triggered_manually = true
|
||||
|
||||
if on_close then
|
||||
autocomplete.on_close = on_close
|
||||
end
|
||||
|
||||
local av = get_active_view()
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
update_suggestions()
|
||||
end
|
||||
|
||||
function autocomplete.close()
|
||||
reset_suggestions()
|
||||
end
|
||||
|
||||
function autocomplete.is_open()
|
||||
return #suggestions > 0
|
||||
end
|
||||
|
||||
function autocomplete.complete(completions, on_close)
|
||||
reset_suggestions()
|
||||
|
||||
autocomplete.map_manually = {}
|
||||
autocomplete.add(completions, true)
|
||||
|
||||
autocomplete.open(on_close)
|
||||
end
|
||||
|
||||
function autocomplete.can_complete()
|
||||
if #partial >= config.plugins.autocomplete.min_len then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Commands
|
||||
--
|
||||
local function predicate()
|
||||
return get_active_view() and #suggestions > 0
|
||||
end
|
||||
|
||||
|
||||
command.add(predicate, {
|
||||
["autocomplete:complete"] = function()
|
||||
local doc = core.active_view.doc
|
||||
|
@ -283,12 +502,19 @@ command.add(predicate, {
|
|||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||
end,
|
||||
|
||||
["autocomplete:cycle"] = function()
|
||||
local newidx = suggestions_idx + 1
|
||||
suggestions_idx = newidx > #suggestions and 1 or newidx
|
||||
end,
|
||||
|
||||
["autocomplete:cancel"] = function()
|
||||
reset_suggestions()
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
--
|
||||
-- Keymaps
|
||||
--
|
||||
keymap.add {
|
||||
["tab"] = "autocomplete:complete",
|
||||
["up"] = "autocomplete:previous",
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
|
||||
local times = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function update_time(doc)
|
||||
|
@ -11,7 +10,6 @@ local function update_time(doc)
|
|||
times[doc] = info.modified
|
||||
end
|
||||
|
||||
|
||||
local function reload_doc(doc)
|
||||
local fp = io.open(doc.filename, "r")
|
||||
local text = fp:read("*a")
|
||||
|
@ -27,23 +25,19 @@ local function reload_doc(doc)
|
|||
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
||||
end
|
||||
|
||||
local on_modify = core.on_dirmonitor_modify
|
||||
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
-- check all doc modified times
|
||||
for _, doc in ipairs(core.docs) do
|
||||
local info = system.get_file_info(doc.filename or "")
|
||||
if info and times[doc] ~= info.modified then
|
||||
reload_doc(doc)
|
||||
end
|
||||
coroutine.yield()
|
||||
core.on_dirmonitor_modify = function(dir, filepath)
|
||||
local abs_filename = dir.name .. PATHSEP .. filepath
|
||||
for _, doc in ipairs(core.docs) do
|
||||
local info = system.get_file_info(doc.filename or "")
|
||||
if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then
|
||||
reload_doc(doc)
|
||||
break
|
||||
end
|
||||
|
||||
-- wait for next scan
|
||||
coroutine.yield(config.project_scan_rate)
|
||||
end
|
||||
end)
|
||||
|
||||
on_modify(dir, filepath)
|
||||
end
|
||||
|
||||
-- patch `Doc.save|load` to store modified time
|
||||
local load = Doc.load
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
|
||||
local menu = ContextMenu()
|
||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
||||
local on_mouse_moved = RootView.on_mouse_moved
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
function RootView:on_mouse_moved(...)
|
||||
if menu:on_mouse_moved(...) then return end
|
||||
on_mouse_moved(self, ...)
|
||||
end
|
||||
|
||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||
-- We give the priority to the menu to process mouse pressed events.
|
||||
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
||||
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
|
||||
function RootView:update(...)
|
||||
root_view_update(self, ...)
|
||||
menu:update()
|
||||
end
|
||||
|
||||
function RootView:draw(...)
|
||||
root_view_draw(self, ...)
|
||||
menu:draw()
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["context:show"] = function()
|
||||
menu:show(core.active_view.position.x, core.active_view.position.y)
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["menu"] = "context:show"
|
||||
}
|
||||
|
||||
local function copy_log()
|
||||
local item = core.active_view.hovered_item
|
||||
if item then
|
||||
system.set_clipboard(core.get_log(item))
|
||||
end
|
||||
end
|
||||
|
||||
local function open_as_doc()
|
||||
local doc = core.open_doc("logs.txt")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:insert(1, 1, core.get_log())
|
||||
end
|
||||
|
||||
menu:register("core.logview", {
|
||||
{ text = "Copy entry", command = copy_log },
|
||||
{ text = "Open as file", command = open_as_doc }
|
||||
})
|
||||
|
||||
if require("plugins.scale") then
|
||||
menu:register("core.docview", {
|
||||
{ text = "Cut", command = "doc:cut" },
|
||||
{ text = "Copy", command = "doc:copy" },
|
||||
{ text = "Paste", command = "doc:paste" },
|
||||
{ text = "Font +", command = "scale:increase" },
|
||||
{ text = "Font -", command = "scale:decrease" },
|
||||
{ text = "Font Reset", command = "scale:reset" },
|
||||
ContextMenu.DIVIDER,
|
||||
{ text = "Find", command = "find-replace:find" },
|
||||
{ text = "Replace", command = "find-replace:replace" }
|
||||
})
|
||||
end
|
||||
|
||||
return menu
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
|
@ -76,7 +76,7 @@ local function get_non_empty_lines(syntax, lines)
|
|||
end
|
||||
|
||||
|
||||
local auto_detect_max_lines = 200
|
||||
local auto_detect_max_lines = 100
|
||||
|
||||
local function detect_indent_stat(doc)
|
||||
local stat = {}
|
||||
|
@ -99,22 +99,16 @@ local function detect_indent_stat(doc)
|
|||
end
|
||||
|
||||
|
||||
local doc_on_text_change = Doc.on_text_change
|
||||
local adjust_threshold = 4
|
||||
|
||||
local function update_cache(doc)
|
||||
local type, size, score = detect_indent_stat(doc)
|
||||
cache[doc] = { type = type, size = size, confirmed = (score >= adjust_threshold) }
|
||||
doc.indent_info = cache[doc]
|
||||
if score < adjust_threshold and doc_on_text_change then
|
||||
Doc.on_text_change = function(self, ...)
|
||||
doc_on_text_change(self, ...)
|
||||
update_cache(self)
|
||||
end
|
||||
elseif score >= adjust_threshold and doc_on_text_change then
|
||||
Doc.on_text_change = doc_on_text_change
|
||||
doc_on_text_change = nil
|
||||
local score_threshold = 4
|
||||
if score < score_threshold then
|
||||
-- use default values
|
||||
type = config.tab_type
|
||||
size = config.indent_size
|
||||
end
|
||||
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
|
||||
|
@ -127,31 +121,86 @@ end
|
|||
local clean = Doc.clean
|
||||
function Doc:clean(...)
|
||||
clean(self, ...)
|
||||
update_cache(self)
|
||||
end
|
||||
|
||||
|
||||
local function with_indent_override(doc, fn, ...)
|
||||
local c = cache[doc]
|
||||
if not c then
|
||||
return fn(...)
|
||||
local _, _, confirmed = self:get_indent_info()
|
||||
if not confirmed then
|
||||
update_cache(self)
|
||||
end
|
||||
local type, size = config.tab_type, config.indent_size
|
||||
config.tab_type, config.indent_size = c.type, c.size or config.indent_size
|
||||
local r1, r2, r3 = fn(...)
|
||||
config.tab_type, config.indent_size = type, size
|
||||
return r1, r2, r3
|
||||
end
|
||||
|
||||
|
||||
local perform = command.perform
|
||||
function command.perform(...)
|
||||
return with_indent_override(core.active_view.doc, perform, ...)
|
||||
local function set_indent_type(doc, type)
|
||||
local _, indent_size = doc:get_indent_info()
|
||||
cache[doc] = {type = type,
|
||||
size = indent_size,
|
||||
confirmed = true}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
local function set_indent_type_command()
|
||||
core.command_view:enter(
|
||||
"Specify indent style for this file",
|
||||
function(value) -- submit
|
||||
local doc = core.active_view.doc
|
||||
value = value:lower()
|
||||
set_indent_type(doc, value == "tabs" and "hard" or "soft")
|
||||
end,
|
||||
function(text) -- suggest
|
||||
return common.fuzzy_match({"tabs", "spaces"}, text)
|
||||
end,
|
||||
nil, -- cancel
|
||||
function(text) -- validate
|
||||
local t = text:lower()
|
||||
return t == "tabs" or t == "spaces"
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
local draw = DocView.draw
|
||||
function DocView:draw(...)
|
||||
return with_indent_override(self.doc, draw, self, ...)
|
||||
local function set_indent_size(doc, size)
|
||||
local indent_type = doc:get_indent_info()
|
||||
cache[doc] = {type = indent_type,
|
||||
size = size,
|
||||
confirmed = true}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
local function set_indent_size_command()
|
||||
core.command_view:enter(
|
||||
"Specify indent size for current file",
|
||||
function(value) -- submit
|
||||
local value = math.floor(tonumber(value))
|
||||
local doc = core.active_view.doc
|
||||
set_indent_size(doc, value)
|
||||
end,
|
||||
nil, -- suggest
|
||||
nil, -- cancel
|
||||
function(value) -- validate
|
||||
local value = tonumber(value)
|
||||
return value ~= nil and value >= 1
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["indent:set-file-indent-type"] = set_indent_type_command,
|
||||
["indent:set-file-indent-size"] = set_indent_size_command
|
||||
})
|
||||
|
||||
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "soft"
|
||||
end, {
|
||||
["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
|
||||
})
|
||||
|
||||
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "hard"
|
||||
end, {
|
||||
["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
|
||||
})
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local common = require "core.common"
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
|
||||
local color = style.syntax.whitespace or style.syntax.comment
|
||||
local ty = y + self:get_line_text_y_offset()
|
||||
local tx
|
||||
local text, offset, s, e = self.doc.lines[idx], 1
|
||||
local x1, _, x2, _ = self:get_content_bounds()
|
||||
local _offset = self:get_x_offset_col(idx, x1)
|
||||
offset = _offset
|
||||
while true do
|
||||
s, e = text:find(" +", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
|
||||
if tx > x + x2 then break end
|
||||
offset = e + 1
|
||||
end
|
||||
offset = _offset
|
||||
while true do
|
||||
s, e = text:find("\t", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, "»", tx, ty, color)
|
||||
if tx > x + x2 then break end
|
||||
offset = e + 1
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
|
@ -1,21 +1,25 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$" },
|
||||
name = "C",
|
||||
files = { "%.c$", "%.h$", "%.inl$" },
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { "#", "[^\\]\n" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "-?0x%x+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "-?%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x%x+", type = "number" },
|
||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
|
@ -29,8 +33,6 @@ syntax.add {
|
|||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["union"] = "keyword",
|
||||
["typedef"] = "keyword",
|
||||
["enum"] = "keyword",
|
||||
["extern"] = "keyword",
|
||||
|
@ -42,8 +44,7 @@ syntax.add {
|
|||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["void"] = "keyword",
|
||||
["void"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
["long"] = "keyword2",
|
||||
|
@ -55,6 +56,17 @@ syntax.add {
|
|||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["NULL"] = "literal",
|
||||
["#include"] = "keyword",
|
||||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
["#define"] = "keyword",
|
||||
["#warning"] = "keyword",
|
||||
["#error"] = "keyword",
|
||||
["#pragma"] = "keyword",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
pcall(require, "plugins.language_c")
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "C++",
|
||||
files = {
|
||||
"%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
|
||||
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
|
||||
},
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x%x+", type = "number" },
|
||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "[%a_][%w_]*::", type = "symbol" },
|
||||
{ pattern = "::", type = "symbol" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||
},
|
||||
symbols = {
|
||||
["alignof"] = "keyword",
|
||||
["alignas"] = "keyword",
|
||||
["private"] = "keyword",
|
||||
["protected"] = "keyword",
|
||||
["public"] = "keyword",
|
||||
["register"] = "keyword",
|
||||
["nullptr"] = "keyword",
|
||||
["operator"] = "keyword",
|
||||
["asm"] = "keyword",
|
||||
["catch"] = "keyword",
|
||||
["throw"] = "keyword",
|
||||
["try"] = "keyword",
|
||||
["compl"] = "keyword",
|
||||
["explicit"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["concept"] = "keyword",
|
||||
["consteval"] = "keyword",
|
||||
["constexpr"] = "keyword",
|
||||
["constinit"] = "keyword",
|
||||
["const_cast"] = "keyword",
|
||||
["dynamic_cast"] = "keyword",
|
||||
["reinterpret_cast"] = "keyword",
|
||||
["static_cast"] = "keyword",
|
||||
["static_assert"] = "keyword",
|
||||
["template"] = "keyword",
|
||||
["this"] = "keyword",
|
||||
["thread_local"] = "keyword",
|
||||
["requires"] = "keyword",
|
||||
["co_wait"] = "keyword",
|
||||
["co_return"] = "keyword",
|
||||
["co_yield"] = "keyword",
|
||||
["decltype"] = "keyword",
|
||||
["delete"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["friend"] = "keyword",
|
||||
["typeid"] = "keyword",
|
||||
["typename"] = "keyword",
|
||||
["mutable"] = "keyword",
|
||||
["override"] = "keyword",
|
||||
["virtual"] = "keyword",
|
||||
["using"] = "keyword",
|
||||
["new"] = "keyword",
|
||||
["noexcept"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
["then"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["elseif"] = "keyword",
|
||||
["do"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["typedef"] = "keyword",
|
||||
["enum"] = "keyword",
|
||||
["extern"] = "keyword",
|
||||
["static"] = "keyword",
|
||||
["volatile"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["inline"] = "keyword",
|
||||
["switch"] = "keyword",
|
||||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["void"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
["long"] = "keyword2",
|
||||
["float"] = "keyword2",
|
||||
["double"] = "keyword2",
|
||||
["char"] = "keyword2",
|
||||
["unsigned"] = "keyword2",
|
||||
["bool"] = "keyword2",
|
||||
["true"] = "keyword2",
|
||||
["false"] = "keyword2",
|
||||
["#include"] = "keyword",
|
||||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
["#define"] = "keyword",
|
||||
["#warning"] = "keyword",
|
||||
["#error"] = "keyword",
|
||||
["#pragma"] = "keyword",
|
||||
},
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "CSS",
|
||||
files = { "%.css$" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "HTML",
|
||||
files = { "%.html?$" },
|
||||
patterns = {
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
|
||||
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
||||
"<%s*/[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
|
||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][tT][yY][lL][eE][^>]*>",
|
||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
||||
},
|
||||
syntax = ".css",
|
||||
type = "function"
|
||||
},
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.]*f?", type = "number" },
|
||||
{ pattern = "-?%.?%d+f?", type = "number" },
|
||||
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
|
||||
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
|
||||
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "keyword" },
|
||||
{ pattern = "[/<>=]", type = "operator" },
|
||||
},
|
||||
symbols = {},
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "JavaScript",
|
||||
files = { "%.js$", "%.json$", "%.cson$" },
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '/', '/', '\\' }, type = "string" },
|
||||
{ pattern = { '/[^= ]', '/', '\\' },type = "string" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { "`", "`", '\\' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "0x[%da-fA-F_]+n?", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE_n]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
|
@ -41,6 +42,7 @@ syntax.add {
|
|||
["if"] = "keyword",
|
||||
["import"] = "keyword",
|
||||
["in"] = "keyword",
|
||||
["of"] = "keyword",
|
||||
["instanceof"] = "keyword",
|
||||
["let"] = "keyword",
|
||||
["new"] = "keyword",
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "Lua",
|
||||
files = "%.lua$",
|
||||
headers = "^#!.*[ /]lua",
|
||||
comment = "--",
|
||||
patterns = {
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
||||
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
||||
{ pattern = "%-%-.-\n", type = "comment" },
|
||||
{ pattern = "-?0x%x+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "<%a+>", type = "keyword2" },
|
||||
{ pattern = "%.%.%.?", type = "operator" },
|
||||
{ pattern = "[<>~=]=", type = "operator" },
|
||||
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%s*%f[(\"{]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
||||
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
||||
{ pattern = "%-%-.-\n", type = "comment" },
|
||||
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
|
||||
{ pattern = "0x%x+%.%x*", type = "number" },
|
||||
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
|
||||
{ pattern = "0x%.%x+", type = "number" },
|
||||
{ pattern = "0x%x+[pP][-+]?%d+", type = "number" },
|
||||
{ pattern = "0x%x+", type = "number" },
|
||||
{ pattern = "%d%.%d*[eE][-+]?%d+", type = "number" },
|
||||
{ pattern = "%d%.%d*", type = "number" },
|
||||
{ pattern = "%.?%d*[eE][-+]?%d+", type = "number" },
|
||||
{ pattern = "%.?%d+", type = "number" },
|
||||
{ pattern = "<%a+>", type = "keyword2" },
|
||||
{ pattern = "%.%.%.?", type = "operator" },
|
||||
{ pattern = "[<>~=]=", type = "operator" },
|
||||
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*()%s*%f[(\"'{]", type = {"function", "normal"} },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
|
|
|
@ -1,22 +1,56 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
|
||||
|
||||
syntax.add {
|
||||
name = "Markdown",
|
||||
files = { "%.md$", "%.markdown$" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
||||
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
||||
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
||||
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
|
||||
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
|
||||
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
|
||||
{ pattern = { "```julia", "```" }, type = "string", syntax = ".jl" },
|
||||
{ pattern = { "```rust", "```" }, type = "string", syntax = ".rs" },
|
||||
{ pattern = { "```dart", "```" }, type = "string", syntax = ".dart" },
|
||||
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" },
|
||||
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
|
||||
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
|
||||
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
|
||||
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
|
||||
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
|
||||
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" },
|
||||
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
|
||||
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
},
|
||||
symbols = { },
|
||||
}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.py$", "%.pyw$" },
|
||||
name = "Python",
|
||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
{ pattern = { "#", "\n" }, type = "comment" },
|
||||
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { '"""', '"""' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = { "#", "\n" }, type = "comment" },
|
||||
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
|
||||
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["class"] = "keyword",
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.xml$", "%.html?$" },
|
||||
name = "XML",
|
||||
files = { "%.xml$" },
|
||||
headers = "<%?xml",
|
||||
patterns = {
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
local draw_overlay = DocView.draw_overlay
|
||||
|
||||
function DocView:draw_overlay(...)
|
||||
if not self:is(CommandView) then
|
||||
local offset = self:get_font():get_width("n") * config.line_limit
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
local h = self.size.y
|
||||
|
||||
local color = style.guide or style.selection
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
draw_overlay(self, ...)
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
|
@ -9,6 +9,7 @@ local View = require "core.view"
|
|||
|
||||
local ResultsView = View:extend()
|
||||
|
||||
ResultsView.context = "session"
|
||||
|
||||
function ResultsView:new(text, fn)
|
||||
ResultsView.super.new(self)
|
||||
|
@ -30,7 +31,10 @@ local function find_all_matches_in_file(t, filename, fn)
|
|||
for line in fp:lines() do
|
||||
local s = fn(line)
|
||||
if s then
|
||||
table.insert(t, { file = filename, text = line, line = n, col = s })
|
||||
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
|
||||
-- things tend to get sluggish. If our line is longer than 80 characters, begin to truncate the thing.
|
||||
local start_index = math.max(s - 80, 1)
|
||||
table.insert(t, { file = filename, text = (start_index > 1 and "..." or "") .. line:sub(start_index, 256 + start_index), line = n, col = s })
|
||||
core.redraw = true
|
||||
end
|
||||
if n % 100 == 0 then coroutine.yield() end
|
||||
|
@ -88,7 +92,7 @@ end
|
|||
function ResultsView:on_mouse_pressed(...)
|
||||
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
||||
if not caught then
|
||||
self:open_selected_result()
|
||||
return self:open_selected_result()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -104,6 +108,7 @@ function ResultsView:open_selected_result()
|
|||
dv.doc:set_selection(res.line, res.col)
|
||||
dv:scroll_to_line(res.line, false, true)
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
@ -167,12 +172,17 @@ function ResultsView:draw()
|
|||
local ox, oy = self:get_content_offset()
|
||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||
local files_number = core.project_files_number()
|
||||
local per = self.last_file_idx / files_number
|
||||
local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1)
|
||||
local text
|
||||
if self.searching then
|
||||
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
||||
per * 100, self.last_file_idx, files_number,
|
||||
#self.results, self.query)
|
||||
if files_number then
|
||||
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
||||
per * 100, self.last_file_idx, files_number,
|
||||
#self.results, self.query)
|
||||
else
|
||||
text = string.format("Searching (%d files, %d matches) for %q...",
|
||||
self.last_file_idx, #self.results, self.query)
|
||||
end
|
||||
else
|
||||
text = string.format("Found %d matches for %q",
|
||||
#self.results, self.query)
|
||||
|
@ -229,9 +239,12 @@ command.add(nil, {
|
|||
end)
|
||||
end,
|
||||
|
||||
["project-search:find-pattern"] = function()
|
||||
core.command_view:enter("Find Pattern In Project", function(text)
|
||||
begin_search(text, function(line_text) return line_text:find(text) end)
|
||||
["project-search:find-regex"] = function()
|
||||
core.command_view:enter("Find Regex In Project", function(text)
|
||||
local re = regex.compile(text, "i")
|
||||
begin_search(text, function(line_text)
|
||||
return regex.cmatch(re, line_text)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
|
||||
|
@ -265,12 +278,38 @@ command.add(ResultsView, {
|
|||
["project-search:refresh"] = function()
|
||||
core.active_view:refresh()
|
||||
end,
|
||||
|
||||
["project-search:move-to-previous-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y - view.size.y
|
||||
end,
|
||||
|
||||
["project-search:move-to-next-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y + view.size.y
|
||||
end,
|
||||
|
||||
["project-search:move-to-start-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = 0
|
||||
end,
|
||||
|
||||
["project-search:move-to-end-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view:get_scrollable_size()
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["f5"] = "project-search:refresh",
|
||||
["ctrl+shift+f"] = "project-search:find",
|
||||
["up"] = "project-search:select-previous",
|
||||
["down"] = "project-search:select-next",
|
||||
["return"] = "project-search:open-selected",
|
||||
["f5"] = "project-search:refresh",
|
||||
["ctrl+shift+f"] = "project-search:find",
|
||||
["up"] = "project-search:select-previous",
|
||||
["down"] = "project-search:select-next",
|
||||
["return"] = "project-search:open-selected",
|
||||
["pageup"] = "project-search:move-to-previous-page",
|
||||
["pagedown"] = "project-search:move-to-next-page",
|
||||
["ctrl+home"] = "project-search:move-to-start-of-doc",
|
||||
["ctrl+end"] = "project-search:move-to-end-of-doc",
|
||||
["home"] = "project-search:move-to-start-of-doc",
|
||||
["end"] = "project-search:move-to-end-of-doc"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.plugins.scale = {
|
||||
mode = "code",
|
||||
use_mousewheel = true
|
||||
}
|
||||
|
||||
local scale_steps = 0.05
|
||||
|
||||
local current_scale = SCALE
|
||||
local default_scale = SCALE
|
||||
|
||||
local function set_scale(scale)
|
||||
scale = common.clamp(scale, 0.2, 6)
|
||||
|
||||
-- save scroll positions
|
||||
local scrolls = {}
|
||||
for _, view in ipairs(core.root_view.root_node:get_children()) do
|
||||
local n = view:get_scrollable_size()
|
||||
if n ~= math.huge and not view:is(CommandView) and n > view.size.y then
|
||||
scrolls[view] = view.scroll.y / (n - view.size.y)
|
||||
end
|
||||
end
|
||||
|
||||
local s = scale / current_scale
|
||||
current_scale = scale
|
||||
|
||||
if config.plugins.scale.mode == "ui" then
|
||||
SCALE = scale
|
||||
|
||||
style.padding.x = style.padding.x * s
|
||||
style.padding.y = style.padding.y * s
|
||||
style.divider_size = style.divider_size * s
|
||||
style.scrollbar_size = style.scrollbar_size * s
|
||||
style.caret_width = style.caret_width * s
|
||||
style.tab_width = style.tab_width * s
|
||||
|
||||
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
||||
style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
|
||||
end
|
||||
else
|
||||
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
-- restore scroll positions
|
||||
for view, n in pairs(scrolls) do
|
||||
view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
|
||||
view.scroll.to.y = view.scroll.y
|
||||
end
|
||||
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
local function get_scale()
|
||||
return current_scale
|
||||
end
|
||||
|
||||
local function res_scale()
|
||||
set_scale(default_scale)
|
||||
end
|
||||
|
||||
local function inc_scale()
|
||||
set_scale(current_scale + scale_steps)
|
||||
end
|
||||
|
||||
local function dec_scale()
|
||||
set_scale(current_scale - scale_steps)
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["scale:reset" ] = function() res_scale() end,
|
||||
["scale:decrease"] = function() dec_scale() end,
|
||||
["scale:increase"] = function() inc_scale() end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+0"] = "scale:reset",
|
||||
["ctrl+-"] = "scale:decrease",
|
||||
["ctrl+="] = "scale:increase",
|
||||
["ctrl+wheelup"] = "scale:increase",
|
||||
["ctrl+wheeldown"] = "scale:decrease"
|
||||
}
|
||||
|
||||
return {
|
||||
["set"] = set_scale,
|
||||
["get"] = get_scale,
|
||||
["increase"] = inc_scale,
|
||||
["decrease"] = dec_scale,
|
||||
["reset"] = res_scale
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local translate = require "core.doc.translate"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
@ -6,9 +6,12 @@ local config = require "core.config"
|
|||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
|
||||
|
||||
local default_treeview_size = 200 * SCALE
|
||||
local tooltip_offset = style.font:get_height("A")
|
||||
local tooltip_offset = style.font:get_height()
|
||||
local tooltip_border = 1
|
||||
local tooltip_delay = 0.5
|
||||
local tooltip_alpha = 255
|
||||
|
@ -38,8 +41,18 @@ function TreeView:new()
|
|||
self.init_size = true
|
||||
self.target_size = default_treeview_size
|
||||
self.cache = {}
|
||||
self.last = {}
|
||||
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
||||
|
||||
self.item_icon_width = 0
|
||||
self.item_text_spacing = 0
|
||||
|
||||
local on_dirmonitor_modify = core.on_dirmonitor_modify
|
||||
function core.on_dirmonitor_modify(dir, filepath)
|
||||
if self.cache[dir.name] then
|
||||
self.cache[dir.name][filepath] = nil
|
||||
end
|
||||
on_dirmonitor_modify(dir, filepath)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -51,7 +64,7 @@ function TreeView:set_target_size(axis, value)
|
|||
end
|
||||
|
||||
|
||||
function TreeView:get_cached(item, dirname)
|
||||
function TreeView:get_cached(dir, item, dirname)
|
||||
local dir_cache = self.cache[dirname]
|
||||
if not dir_cache then
|
||||
dir_cache = {}
|
||||
|
@ -77,6 +90,7 @@ function TreeView:get_cached(item, dirname)
|
|||
end
|
||||
t.name = basename
|
||||
t.type = item.type
|
||||
t.dir = dir -- points to top level "dir" item
|
||||
dir_cache[cache_name] = t
|
||||
end
|
||||
return t
|
||||
|
@ -93,21 +107,21 @@ function TreeView:get_item_height()
|
|||
end
|
||||
|
||||
|
||||
function TreeView:invalidate_cache(dirname)
|
||||
for _, v in pairs(self.cache[dirname]) do
|
||||
v.skip = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:check_cache()
|
||||
-- invalidate cache's skip values if project_files has changed
|
||||
for i = 1, #core.project_directories do
|
||||
local dir = core.project_directories[i]
|
||||
local last_files = self.last[dir.name]
|
||||
if not last_files then
|
||||
self.last[dir.name] = dir.files
|
||||
else
|
||||
if dir.files ~= last_files then
|
||||
for _, v in pairs(self.cache[dir.name]) do
|
||||
v.skip = nil
|
||||
end
|
||||
self.last[dir.name] = dir.files
|
||||
end
|
||||
-- invalidate cache's skip values if directory is declared dirty
|
||||
if dir.is_dirty and self.cache[dir.name] then
|
||||
self:invalidate_cache(dir.name)
|
||||
end
|
||||
dir.is_dirty = false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -123,14 +137,14 @@ function TreeView:each_item()
|
|||
|
||||
for k = 1, #core.project_directories do
|
||||
local dir = core.project_directories[k]
|
||||
local dir_cached = self:get_cached(dir.item, dir.name)
|
||||
local dir_cached = self:get_cached(dir, dir.item, dir.name)
|
||||
coroutine.yield(dir_cached, ox, y, w, h)
|
||||
count_lines = count_lines + 1
|
||||
y = y + h
|
||||
local i = 1
|
||||
while i <= #dir.files and dir_cached.expanded do
|
||||
local item = dir.files[i]
|
||||
local cached = self:get_cached(item, dir.name)
|
||||
local cached = self:get_cached(dir, item, dir.name)
|
||||
|
||||
coroutine.yield(cached, ox, y, w, h)
|
||||
count_lines = count_lines + 1
|
||||
|
@ -168,13 +182,13 @@ end
|
|||
function TreeView:on_mouse_moved(px, py, ...)
|
||||
TreeView.super.on_mouse_moved(self, px, py, ...)
|
||||
if self.dragging_scrollbar then return end
|
||||
|
||||
|
||||
local item_changed, tooltip_changed
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
if px > x and py > y and px <= x + w and py <= y + h then
|
||||
item_changed = true
|
||||
self.hovered_item = item
|
||||
|
||||
|
||||
x,y,w,h = self:get_text_bounding_box(item, x,y,w,h)
|
||||
if px > x and py > y and px <= x + w and py <= y + h then
|
||||
tooltip_changed = true
|
||||
|
@ -198,30 +212,38 @@ local function create_directory_in(item)
|
|||
core.error("cannot create directory %q: %s", dirname, err)
|
||||
end
|
||||
item.expanded = true
|
||||
core.reschedule_project_scan()
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught then
|
||||
return
|
||||
if caught or button ~= "left" then
|
||||
return true
|
||||
end
|
||||
if not self.hovered_item then
|
||||
return
|
||||
elseif self.hovered_item.type == "dir" then
|
||||
local hovered_item = self.hovered_item
|
||||
if not hovered_item then
|
||||
return false
|
||||
elseif hovered_item.type == "dir" then
|
||||
if keymap.modkeys["ctrl"] and button == "left" then
|
||||
create_directory_in(self.hovered_item)
|
||||
create_directory_in(hovered_item)
|
||||
else
|
||||
self.hovered_item.expanded = not self.hovered_item.expanded
|
||||
hovered_item.expanded = not hovered_item.expanded
|
||||
if hovered_item.dir.files_limit then
|
||||
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
end
|
||||
end
|
||||
else
|
||||
core.try(function()
|
||||
local doc_filename = common.relative_path(core.project_dir, self.hovered_item.abs_filename)
|
||||
if core.last_active_view and core.active_view == self then
|
||||
core.set_active_view(core.last_active_view)
|
||||
end
|
||||
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
end)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
@ -234,7 +256,7 @@ function TreeView:update()
|
|||
else
|
||||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
|
||||
local duration = system.get_time() - self.tooltip.begin
|
||||
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
|
||||
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
|
||||
|
@ -242,6 +264,9 @@ function TreeView:update()
|
|||
self.tooltip.alpha = 0
|
||||
end
|
||||
|
||||
self.item_icon_width = style.icon_font:get_width("D")
|
||||
self.item_text_spacing = style.icon_font:get_width("f") / 2
|
||||
|
||||
TreeView.super.update(self)
|
||||
end
|
||||
|
||||
|
@ -270,47 +295,90 @@ function TreeView:draw_tooltip()
|
|||
end
|
||||
|
||||
|
||||
function TreeView:get_item_icon(item, active, hovered)
|
||||
local character = "f"
|
||||
if item.type == "dir" then
|
||||
character = item.expanded and "D" or "d"
|
||||
end
|
||||
local font = style.icon_font
|
||||
local color = style.text
|
||||
if active or hovered then
|
||||
color = style.accent
|
||||
end
|
||||
return character, font, color
|
||||
end
|
||||
|
||||
function TreeView:get_item_text(item, active, hovered)
|
||||
local text = item.name
|
||||
local font = style.font
|
||||
local color = style.text
|
||||
if active or hovered then
|
||||
color = style.accent
|
||||
end
|
||||
return text, font, color
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw_item_text(item, active, hovered, x, y, w, h)
|
||||
local item_text, item_font, item_color = self:get_item_text(item, active, hovered)
|
||||
common.draw_text(item_font, item_color, item_text, nil, x, y, 0, h)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw_item_icon(item, active, hovered, x, y, w, h)
|
||||
local icon_char, icon_font, icon_color = self:get_item_icon(item, active, hovered)
|
||||
common.draw_text(icon_font, icon_color, icon_char, nil, x, y, 0, h)
|
||||
return self.item_icon_width + self.item_text_spacing
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw_item_body(item, active, hovered, x, y, w, h)
|
||||
x = x + self:draw_item_icon(item, active, hovered, x, y, w, h)
|
||||
self:draw_item_text(item, active, hovered, x, y, w, h)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw_item_chevron(item, active, hovered, x, y, w, h)
|
||||
if item.type == "dir" then
|
||||
local chevron_icon = item.expanded and "-" or "+"
|
||||
local chevron_color = hovered and style.accent or style.text
|
||||
common.draw_text(style.icon_font, chevron_color, chevron_icon, nil, x, y, 0, h)
|
||||
end
|
||||
return style.padding.x
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
|
||||
if hovered then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw_item(item, active, hovered, x, y, w, h)
|
||||
self:draw_item_background(item, active, hovered, x, y, w, h)
|
||||
|
||||
x = x + item.depth * style.padding.x + style.padding.x
|
||||
x = x + self:draw_item_chevron(item, active, hovered, x, y, w, h)
|
||||
|
||||
self:draw_item_body(item, active, hovered, x, y, w, h)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:draw()
|
||||
self:draw_background(style.background2)
|
||||
|
||||
local icon_width = style.icon_font:get_width("D")
|
||||
local spacing = style.icon_font:get_width("f") / 2
|
||||
local _y, _h = self.position.y, self.size.y
|
||||
|
||||
local doc = core.active_view.doc
|
||||
local active_filename = doc and system.absolute_path(doc.filename or "")
|
||||
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
local color = style.text
|
||||
|
||||
-- highlight active_view doc
|
||||
if item.abs_filename == active_filename then
|
||||
color = style.accent
|
||||
if y + h >= _y and y < _y + _h then
|
||||
self:draw_item(item,
|
||||
item.abs_filename == active_filename,
|
||||
item == self.hovered_item,
|
||||
x, y, w, h)
|
||||
end
|
||||
|
||||
-- hovered item background
|
||||
if item == self.hovered_item then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
color = style.accent
|
||||
end
|
||||
|
||||
-- icons
|
||||
x = x + item.depth * style.padding.x + style.padding.x
|
||||
if item.type == "dir" then
|
||||
local icon1 = item.expanded and "-" or "+"
|
||||
local icon2 = item.expanded and "D" or "d"
|
||||
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.icon_font, color, icon2, nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
else
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
end
|
||||
|
||||
-- text
|
||||
x = x + spacing
|
||||
x = common.draw_text(style.font, color, item.name, nil, x, y, 0, h)
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
|
@ -331,9 +399,10 @@ local treeview_node = node:split("left", view, {x = true}, true)
|
|||
-- plugin to be independent of each other. In addition it is not the
|
||||
-- plugin module that plug itself in the active node but it is plugged here
|
||||
-- in the treeview node.
|
||||
local toolbar_view = nil
|
||||
local toolbar_plugin, ToolbarView = core.try(require, "plugins.toolbarview")
|
||||
if config.toolbarview ~= false and toolbar_plugin then
|
||||
local toolbar_view = ToolbarView()
|
||||
if config.plugins.toolbarview ~= false and toolbar_plugin then
|
||||
toolbar_view = ToolbarView()
|
||||
treeview_node:split("down", toolbar_view, {y = true})
|
||||
local min_toolbar_width = toolbar_view:get_min_width()
|
||||
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
|
||||
|
@ -344,12 +413,181 @@ if config.toolbarview ~= false and toolbar_plugin then
|
|||
})
|
||||
end
|
||||
|
||||
-- Add a context menu to the treeview
|
||||
local menu = ContextMenu()
|
||||
|
||||
-- register commands and keymap
|
||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
||||
local on_mouse_moved = RootView.on_mouse_moved
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
function RootView:on_mouse_moved(...)
|
||||
if menu:on_mouse_moved(...) then return end
|
||||
on_mouse_moved(self, ...)
|
||||
end
|
||||
|
||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||
-- We give the priority to the menu to process mouse pressed events.
|
||||
if button == "right" then
|
||||
view.tooltip.alpha = 0
|
||||
view.tooltip.x, view.tooltip.y = nil, nil
|
||||
end
|
||||
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
||||
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
|
||||
function RootView:update(...)
|
||||
root_view_update(self, ...)
|
||||
menu:update()
|
||||
end
|
||||
|
||||
function RootView:draw(...)
|
||||
root_view_draw(self, ...)
|
||||
menu:draw()
|
||||
end
|
||||
|
||||
local function is_project_folder(path)
|
||||
return core.project_dir == path
|
||||
end
|
||||
|
||||
menu:register(function() return view.hovered_item end, {
|
||||
{ text = "Open in System", command = "treeview:open-in-system" },
|
||||
ContextMenu.DIVIDER
|
||||
})
|
||||
|
||||
menu:register(
|
||||
function()
|
||||
return view.hovered_item
|
||||
and not is_project_folder(view.hovered_item.abs_filename)
|
||||
end,
|
||||
{
|
||||
{ text = "Rename", command = "treeview:rename" },
|
||||
{ text = "Delete", command = "treeview:delete" },
|
||||
}
|
||||
)
|
||||
|
||||
menu:register(
|
||||
function()
|
||||
return view.hovered_item and view.hovered_item.type == "dir"
|
||||
end,
|
||||
{
|
||||
{ text = "New File", command = "treeview:new-file" },
|
||||
{ text = "New Folder", command = "treeview:new-folder" },
|
||||
}
|
||||
)
|
||||
|
||||
-- Register the TreeView commands and keymap
|
||||
command.add(nil, {
|
||||
["treeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end})
|
||||
|
||||
|
||||
command.add(function() return view.hovered_item ~= nil end, {
|
||||
["treeview:rename"] = function()
|
||||
local old_filename = view.hovered_item.filename
|
||||
local old_abs_filename = view.hovered_item.abs_filename
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(filename)
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
local abs_filename = core.project_absolute_path(filename)
|
||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
||||
if res then -- successfully renamed
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
else
|
||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||
end
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-file"] = function()
|
||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
||||
end
|
||||
core.command_view:enter("Filename", function(filename)
|
||||
local doc_filename = core.project_dir .. PATHSEP .. filename
|
||||
local file = io.open(doc_filename, "a+")
|
||||
file:write("")
|
||||
file:close()
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
core.log("Created %s", doc_filename)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-folder"] = function()
|
||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
||||
end
|
||||
core.command_view:enter("Folder Name", function(filename)
|
||||
local dir_path = core.project_dir .. PATHSEP .. filename
|
||||
common.mkdirp(dir_path)
|
||||
core.log("Created %s", dir_path)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:delete"] = function()
|
||||
local filename = view.hovered_item.abs_filename
|
||||
local relfilename = view.hovered_item.filename
|
||||
local file_info = system.get_file_info(filename)
|
||||
local file_type = file_info.type == "dir" and "Directory" or "File"
|
||||
-- Ask before deleting
|
||||
local opt = {
|
||||
{ font = style.font, text = "Yes", default_yes = true },
|
||||
{ font = style.font, text = "No" , default_no = true }
|
||||
}
|
||||
core.nag_view:show(
|
||||
string.format("Delete %s", file_type),
|
||||
string.format(
|
||||
"Are you sure you want to delete the %s?\n%s: %s",
|
||||
file_type:lower(), file_type, relfilename
|
||||
),
|
||||
opt,
|
||||
function(item)
|
||||
if item.text == "Yes" then
|
||||
if file_info.type == "dir" then
|
||||
local deleted, error, path = common.rm(filename, true)
|
||||
if not deleted then
|
||||
core.error("Error: %s - \"%s\" ", error, path)
|
||||
return
|
||||
end
|
||||
else
|
||||
local removed, error = os.remove(filename)
|
||||
if not removed then
|
||||
core.error("Error: %s - \"%s\"", error, filename)
|
||||
return
|
||||
end
|
||||
end
|
||||
core.log("Deleted \"%s\"", filename)
|
||||
end
|
||||
end
|
||||
)
|
||||
end,
|
||||
|
||||
["treeview:open-in-system"] = function()
|
||||
local hovered_item = view.hovered_item
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
||||
elseif string.find(PLATFORM, "Mac") then
|
||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
||||
elseif PLATFORM == "Linux" then
|
||||
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
||||
|
||||
-- Return the treeview with toolbar and contextmenu to allow
|
||||
-- user or plugin modifications
|
||||
view.toolbar = toolbar_view
|
||||
view.contextmenu = menu
|
||||
|
||||
return view
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local Doc = require "core.doc"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
-- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
local LogView = require "core.logview"
|
||||
|
||||
|
||||
local function workspace_files_for(project_dir)
|
||||
|
@ -11,7 +12,7 @@ local function workspace_files_for(project_dir)
|
|||
if not info_wsdir then
|
||||
local ok, err = system.mkdir(workspace_dir)
|
||||
if not ok then
|
||||
error("cannot create workspace directory: %s", err)
|
||||
error("cannot create workspace directory: \"" .. err .. "\"")
|
||||
end
|
||||
end
|
||||
return coroutine.wrap(function()
|
||||
|
@ -29,7 +30,7 @@ local function workspace_files_for(project_dir)
|
|||
end
|
||||
|
||||
|
||||
local function load_workspace_file(project_dir)
|
||||
local function consume_workspace_file(project_dir)
|
||||
for filename, id in workspace_files_for(project_dir) do
|
||||
local load_f = loadfile(filename)
|
||||
local workspace = load_f and load_f()
|
||||
|
@ -85,6 +86,7 @@ local function save_view(view)
|
|||
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||
}
|
||||
end
|
||||
if mt == LogView then return end
|
||||
for name, mod in pairs(package.loaded) do
|
||||
if mod == mt then
|
||||
return {
|
||||
|
@ -99,16 +101,26 @@ end
|
|||
|
||||
local function load_view(t)
|
||||
if t.type == "doc" then
|
||||
local ok, doc = pcall(core.open_doc, t.filename)
|
||||
if not ok then
|
||||
return DocView(core.open_doc())
|
||||
local dv
|
||||
if not t.filename then
|
||||
-- document not associated to a file
|
||||
dv = DocView(core.open_doc())
|
||||
if t.text then dv.doc:insert(1, 1, t.text) end
|
||||
else
|
||||
-- we have a filename, try to read the file
|
||||
local ok, doc = pcall(core.open_doc, t.filename)
|
||||
if ok then
|
||||
dv = DocView(doc)
|
||||
end
|
||||
end
|
||||
-- doc view "dv" can be nil here if the filename associated to the document
|
||||
-- cannot be read.
|
||||
if dv and dv.doc then
|
||||
dv.doc:set_selection(table.unpack(t.selection))
|
||||
dv.last_line, dv.last_col = dv.doc:get_selection()
|
||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||
end
|
||||
local dv = DocView(doc)
|
||||
if t.text then doc:insert(1, 1, t.text) end
|
||||
doc:set_selection(table.unpack(t.selection))
|
||||
dv.last_line, dv.last_col = doc:get_selection()
|
||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||
return dv
|
||||
end
|
||||
return require(t.module)()
|
||||
|
@ -141,13 +153,19 @@ end
|
|||
local function load_node(node, t)
|
||||
if t.type == "leaf" then
|
||||
local res
|
||||
for _, v in ipairs(t.views) do
|
||||
local active_view
|
||||
for i, v in ipairs(t.views) do
|
||||
local view = load_view(v)
|
||||
if v.active then res = view end
|
||||
node:add_view(view)
|
||||
if view then
|
||||
if v.active then res = view end
|
||||
node:add_view(view)
|
||||
if t.active_view == i then
|
||||
active_view = view
|
||||
end
|
||||
end
|
||||
end
|
||||
if t.active_view then
|
||||
node:set_active_view(node.views[t.active_view])
|
||||
if active_view then
|
||||
node:set_active_view(active_view)
|
||||
end
|
||||
return res
|
||||
else
|
||||
|
@ -184,7 +202,7 @@ end
|
|||
|
||||
|
||||
local function load_workspace()
|
||||
local workspace = load_workspace_file(core.project_dir)
|
||||
local workspace = consume_workspace_file(core.project_dir)
|
||||
if workspace then
|
||||
local root = get_unlocked_root(core.root_view.root_node)
|
||||
local active_view = load_node(root, workspace.documents)
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
-- put user settings here
|
||||
-- this module will be loaded after everything else when the application starts
|
||||
-- it will be automatically reloaded when saved
|
||||
|
||||
local core = require "core"
|
||||
local keymap = require "core.keymap"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
|
||||
------------------------------ Themes ----------------------------------------
|
||||
|
||||
-- light theme:
|
||||
-- core.reload_module("colors.summer")
|
||||
|
||||
--------------------------- Key bindings -------------------------------------
|
||||
|
||||
-- key binding:
|
||||
-- keymap.add { ["ctrl+escape"] = "core:quit" }
|
||||
|
||||
|
||||
------------------------------- Fonts ----------------------------------------
|
||||
|
||||
-- customize fonts:
|
||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
|
||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
|
||||
--
|
||||
-- font names used by lite:
|
||||
-- style.font : user interface
|
||||
-- style.big_font : big text in welcome screen
|
||||
-- style.icon_font : icons
|
||||
-- style.icon_big_font : toolbar icons
|
||||
-- style.code_font : code
|
||||
--
|
||||
-- the function to load the font accept a 3rd optional argument like:
|
||||
--
|
||||
-- {antialiasing="grayscale", hinting="full"}
|
||||
--
|
||||
-- possible values are:
|
||||
-- antialiasing: grayscale, subpixel
|
||||
-- hinting: none, slight, full
|
||||
|
||||
------------------------------ Plugins ----------------------------------------
|
||||
|
||||
-- enable or disable plugin loading setting config entries:
|
||||
|
||||
-- enable trimwhitespace, otherwise it is disable by default:
|
||||
-- config.trimwhitespace = true
|
||||
--
|
||||
-- disable detectindent, otherwise it is enabled by default
|
||||
-- config.detectindent = false
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>lite-xl</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>lite-xl</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>lite-xl</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.16.5</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© 2019-2021 rxi franko</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,448 +0,0 @@
|
|||
{
|
||||
"name": "",
|
||||
"css_prefix_text": "icon-",
|
||||
"css_use_suffix": false,
|
||||
"hinting": true,
|
||||
"units_per_em": 1000,
|
||||
"ascent": 850,
|
||||
"glyphs": [
|
||||
{
|
||||
"uid": "9dd9e835aebe1060ba7190ad2b2ed951",
|
||||
"css": "search-1",
|
||||
"code": 76,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "12f4ece88e46abd864e40b35e05b11cd",
|
||||
"css": "ok-1",
|
||||
"code": 59402,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "43ab845088317bd348dee1d975700c48",
|
||||
"css": "ok-circled-1",
|
||||
"code": 59403,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "ad33e708f4d2e25c5056c931da1528d6",
|
||||
"css": "ok-circled2",
|
||||
"code": 59405,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "1400d5103edd2fa6d2d61688fee79a5a",
|
||||
"css": "ok-squared",
|
||||
"code": 61770,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "5211af474d3a9848f67f945e2ccaf143",
|
||||
"css": "cancel-1",
|
||||
"code": 67,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "0f4cae16f34ae243a6144c18a003f2d8",
|
||||
"css": "cancel-circled-1",
|
||||
"code": 99,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "d7271d490b71df4311e32cdacae8b331",
|
||||
"css": "home-1",
|
||||
"code": 59407,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "3d4ea8a78dc34efe891f3a0f3d961274",
|
||||
"css": "info",
|
||||
"code": 61737,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "ce3cf091d6ebd004dd0b52d24074e6e3",
|
||||
"css": "help",
|
||||
"code": 61736,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "17ebadd1e3f274ff0205601eef7b9cc4",
|
||||
"css": "help-circled-1",
|
||||
"code": 59408,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
||||
"css": "info-circled-1",
|
||||
"code": 59409,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "c1f1975c885aa9f3dad7810c53b82074",
|
||||
"css": "lock",
|
||||
"code": 59410,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "657ab647f6248a6b57a5b893beaf35a9",
|
||||
"css": "lock-open-1",
|
||||
"code": 59411,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "05376be04a27d5a46e855a233d6e8508",
|
||||
"css": "lock-open-alt-1",
|
||||
"code": 61758,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "3db5347bd219f3bce6025780f5d9ef45",
|
||||
"css": "tag",
|
||||
"code": 59412,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "a3f89e106175a5c5c4e9738870b12e55",
|
||||
"css": "tags",
|
||||
"code": 59413,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "7034e4d22866af82bef811f52fb1ba46",
|
||||
"css": "code",
|
||||
"code": 61729,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
|
||||
"css": "pencil-1",
|
||||
"code": 59414,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "44fae3bfdd54754dc68ec50d37efea37",
|
||||
"css": "pencil-squared",
|
||||
"code": 61771,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "41087bc74d4b20b55059c60a33bf4008",
|
||||
"css": "edit",
|
||||
"code": 59415,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "ecb97add13804c190456025e43ec003b",
|
||||
"css": "keyboard",
|
||||
"code": 61724,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "c76b7947c957c9b78b11741173c8349b",
|
||||
"css": "attention-1",
|
||||
"code": 33,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "00391fac5d419345ffcccd95b6f76263",
|
||||
"css": "attention-alt-1",
|
||||
"code": 61738,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "b035c28eba2b35c6ffe92aee8b0df507",
|
||||
"css": "attention-circled",
|
||||
"code": 59417,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
|
||||
"css": "trash-empty",
|
||||
"code": 59418,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
||||
"css": "doc-1",
|
||||
"code": 102,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "c8585e1e5b0467f28b70bce765d5840c",
|
||||
"css": "docs",
|
||||
"code": 61637,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "5408be43f7c42bccee419c6be53fdef5",
|
||||
"css": "doc-text",
|
||||
"code": 61686,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "178053298e3e5b03551d754d4b9acd8b",
|
||||
"css": "doc-inv",
|
||||
"code": 61787,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "c08a1cde48d96cba21d8c05fa7d7feb1",
|
||||
"css": "doc-text-inv",
|
||||
"code": 61788,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "9daa1fdf0838118518a7e22715e83abc",
|
||||
"css": "file-pdf",
|
||||
"code": 61889,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "310ffd629da85142bc8669f010556f2d",
|
||||
"css": "file-word",
|
||||
"code": 61890,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "edcd4022de8d8df266ef7c42d2658ca5",
|
||||
"css": "file-powerpoint",
|
||||
"code": 61892,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "3c961c1a8d874815856fc6637dc5a13c",
|
||||
"css": "file-image",
|
||||
"code": 61893,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "e80ae555c1413a4ec18b33fb348b4049",
|
||||
"css": "file-archive",
|
||||
"code": 61894,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "81db033e704eb7c586a365559d7c0f36",
|
||||
"css": "file-audio",
|
||||
"code": 61895,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "dd69d9aa589ea7bc0a82a3fe67039f4b",
|
||||
"css": "file-video",
|
||||
"code": 61896,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "26613a2e6bc41593c54bead46f8c8ee3",
|
||||
"css": "file-code",
|
||||
"code": 61897,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f8aa663c489bcbd6e68ec8147dca841e",
|
||||
"css": "folder-1",
|
||||
"code": 100,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "c95735c17a10af81448c7fed98a04546",
|
||||
"css": "folder-open-1",
|
||||
"code": 68,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "b091a8bd0fdade174951f17d936f51e4",
|
||||
"css": "folder-empty-1",
|
||||
"code": 61716,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "6533bdc16ab201eb3f3b27ce989cab33",
|
||||
"css": "folder-open-empty-1",
|
||||
"code": 61717,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "559647a6f430b3aeadbecd67194451dd",
|
||||
"css": "menu-1",
|
||||
"code": 61641,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "e99461abfef3923546da8d745372c995",
|
||||
"css": "cog",
|
||||
"code": 59422,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "98687378abd1faf8f6af97c254eb6cd6",
|
||||
"css": "cog-alt",
|
||||
"code": 59423,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "5bb103cd29de77e0e06a52638527b575",
|
||||
"css": "wrench",
|
||||
"code": 59424,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "0b2b66e526028a6972d51a6f10281b4b",
|
||||
"css": "zoom-in",
|
||||
"code": 59425,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "d25d10efa900f529ad1d275657cfd30e",
|
||||
"css": "zoom-out",
|
||||
"code": 59426,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f3f90c8c89795da30f7444634476ea4f",
|
||||
"css": "angle-left",
|
||||
"code": 61700,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "7bf14281af5633a597f85b061ef1cfb9",
|
||||
"css": "angle-right",
|
||||
"code": 43,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "5de9370846a26947e03f63142a3f1c07",
|
||||
"css": "angle-up",
|
||||
"code": 61701,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "e4dde1992f787163e2e2b534b8c8067d",
|
||||
"css": "angle-down",
|
||||
"code": 45,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "bbfb51903f40597f0b70fd75bc7b5cac",
|
||||
"css": "trash",
|
||||
"code": 61944,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
|
||||
"css": "chart-line",
|
||||
"code": 103,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f4445feb55521283572ee88bc304f928",
|
||||
"css": "floppy",
|
||||
"code": 83,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "b429436ec5a518c78479d44ef18dbd60",
|
||||
"css": "paste",
|
||||
"code": 61674,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "8772331a9fec983cdb5d72902a6f9e0e",
|
||||
"css": "scissors",
|
||||
"code": 59428,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "9755f76110ae4d12ac5f9466c9152031",
|
||||
"css": "book",
|
||||
"code": 59429,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f9cbf7508cd04145ade2800169959eef",
|
||||
"css": "font",
|
||||
"code": 59430,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
|
||||
"css": "resize-vertical",
|
||||
"code": 59431,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "3c73d058e4589b65a8d959c0fc8f153d",
|
||||
"css": "resize-horizontal",
|
||||
"code": 59432,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "e594fc6e5870b4ab7e49f52571d52577",
|
||||
"css": "resize-full",
|
||||
"code": 59433,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "5278ef7773e948d56c4d442c8c8c98cf",
|
||||
"css": "lightbulb",
|
||||
"code": 61675,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "598a5f2bcf3521d1615de8e1881ccd17",
|
||||
"css": "clock",
|
||||
"code": 59434,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "1c4068ed75209e21af36017df8871802",
|
||||
"css": "down-big",
|
||||
"code": 59435,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "555ef8c86832e686fef85f7af2eb7cde",
|
||||
"css": "left-big",
|
||||
"code": 59436,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "ad6b3fbb5324abe71a9c0b6609cbb9f1",
|
||||
"css": "right-big",
|
||||
"code": 59437,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "95376bf082bfec6ce06ea1cda7bd7ead",
|
||||
"css": "up-big",
|
||||
"code": 59438,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "107ce08c7231097c7447d8f4d059b55f",
|
||||
"css": "ellipsis",
|
||||
"code": 61761,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "750058837a91edae64b03d60fc7e81a7",
|
||||
"css": "ellipsis-vert",
|
||||
"code": 61762,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "8fb55fd696d9a0f58f3b27c1d8633750",
|
||||
"css": "table",
|
||||
"code": 61646,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "53dd31a6cc6438192b2d7b09b1c1dd45",
|
||||
"css": "columns",
|
||||
"code": 61659,
|
||||
"src": "fontawesome"
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
|
@ -1,192 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="640"
|
||||
height="640.00006"
|
||||
viewBox="0 0 169.33333 169.33334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="lite.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.83124992"
|
||||
inkscape:cx="320"
|
||||
inkscape:cy="320.00003"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
units="px"
|
||||
inkscape:window-width="1299"
|
||||
inkscape:window-height="713"
|
||||
inkscape:window-x="67"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:lockguides="false" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-18.891505,-52.827384)">
|
||||
<rect
|
||||
style="opacity:1;fill:#302e31;fill-opacity:1;stroke:none;stroke-width:0.50622272;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
id="rect905"
|
||||
width="169.33333"
|
||||
height="169.33333"
|
||||
x="18.891504"
|
||||
y="52.827408"
|
||||
ry="9.2305775" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,87.223213 h 13.229166 v 7.937499 H 40.05817 Z"
|
||||
id="rect821" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 61.224838,87.223213 h 18.520834 v 7.937499 H 61.224838 Z"
|
||||
id="rect823" />
|
||||
<path
|
||||
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="M 79.745674,87.223213 H 122.079 v 7.937499 H 79.745674 Z"
|
||||
id="rect825" />
|
||||
<path
|
||||
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 124.72483,87.223213 h 5.29167 v 7.937499 h -5.29167 z"
|
||||
id="rect827" />
|
||||
<path
|
||||
style="opacity:1;fill:#f1a04a;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 132.66232,87.223213 h 21.16667 v 7.937499 h -21.16667 z"
|
||||
id="rect829" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,100.45238 h 13.229166 v 7.93749 H 40.05817 Z"
|
||||
id="rect831" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,113.68155 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||
id="rect833" />
|
||||
<path
|
||||
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 58.579006,113.68155 h 13.229166 v 7.9375 H 58.579006 Z"
|
||||
id="rect835" />
|
||||
<path
|
||||
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 74.454002,113.68155 h 15.875 v 7.9375 h -15.875 z"
|
||||
id="rect837" />
|
||||
<path
|
||||
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 92.974831,113.68155 h 26.458339 v 7.9375 H 92.974831 Z"
|
||||
id="rect839" />
|
||||
<path
|
||||
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 122.07899,113.68155 h 10.58333 v 7.9375 h -10.58333 z"
|
||||
id="rect841" />
|
||||
<path
|
||||
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 137.95399,113.68155 h 15.875 v 7.9375 h -15.875 z"
|
||||
id="rect843" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,126.91071 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||
id="rect845" />
|
||||
<path
|
||||
style="opacity:1;fill:#e77280;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="M 58.579006,126.91071 H 77.09984 v 7.9375 H 58.579006 Z"
|
||||
id="rect847" />
|
||||
<path
|
||||
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 77.099838,126.91071 h 58.208332 v 7.93751 H 77.099838 Z"
|
||||
id="rect849" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,140.13988 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||
id="rect851" />
|
||||
<path
|
||||
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 58.579006,140.13988 h 66.145824 v 7.9375 H 58.579006 Z"
|
||||
id="rect853"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 124.72483,140.13988 h 37.04167 v 7.9375 h -37.04167 z"
|
||||
id="rect855" />
|
||||
<path
|
||||
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 161.76651,140.13988 h 5.29165 v 7.9375 h -5.29165 z"
|
||||
id="rect857" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,153.36905 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||
id="rect859" />
|
||||
<path
|
||||
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 69.162338,153.36905 h 23.812498 v 7.9375 H 69.162338 Z"
|
||||
id="rect861" />
|
||||
<path
|
||||
style="opacity:1;fill:#f1a04a;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 95.620674,153.36905 h 13.229156 v 7.9375 H 95.620674 Z"
|
||||
id="rect863" />
|
||||
<path
|
||||
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="M 111.49567,153.36905 H 122.079 v 7.9375 h -10.58333 z"
|
||||
id="rect865" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,166.59822 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||
id="rect867" />
|
||||
<path
|
||||
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 40.05817,179.82738 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||
id="rect869" />
|
||||
<path
|
||||
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 58.579006,179.82738 h 13.229166 v 7.9375 H 58.579006 Z"
|
||||
id="rect871" />
|
||||
<path
|
||||
style="opacity:1;fill:#e77280;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 74.454002,179.82738 h 15.875 v 7.9375 h -15.875 z"
|
||||
id="rect873" />
|
||||
<path
|
||||
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="M 90.329002,179.82738 H 137.954 v 7.9375 H 90.329002 Z"
|
||||
id="rect875"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="opacity:1;fill:#e77280;fill-opacity:1;stroke:none;stroke-width:0.52916676;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
id="rect14"
|
||||
width="18.520836"
|
||||
height="7.9375005"
|
||||
x="61.224838"
|
||||
y="87.223213" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 12 KiB |
|
@ -1,87 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
|
||||
option_copy=on
|
||||
pargs=()
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
-keep)
|
||||
option_copy=off
|
||||
;;
|
||||
-global)
|
||||
option_global=on
|
||||
;;
|
||||
-*)
|
||||
echo "error: unknown option \"$1\""
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
pargs+=("$1")
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "${#pargs[@]}" -lt 3 ]; then
|
||||
echo "usage: $0 [options] <plugin-dir> <plugin-name> <build-dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
plugin_dir="${pargs[0]}"
|
||||
plugin="${pargs[1]}"
|
||||
|
||||
if [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "mingw"* ]]; then
|
||||
run_windows=yes
|
||||
fi
|
||||
|
||||
rundir=".run"
|
||||
bindir="$rundir/bin"
|
||||
datadir="$rundir/share/lite-xl"
|
||||
|
||||
userdir="$(realpath "$rundir")"
|
||||
builddir="${pargs[2]}"
|
||||
|
||||
build_lite () {
|
||||
echo "running ninja"
|
||||
ninja -C "$builddir"
|
||||
}
|
||||
|
||||
copy_lite_build () {
|
||||
echo "copying lite executable and data"
|
||||
rm -fr "$rundir"
|
||||
mkdir -p "$bindir" "$datadir"
|
||||
if [ ! -z ${run_windows+x} ]; then
|
||||
cp "$builddir/src/lite.exe" "$bindir"
|
||||
else
|
||||
cp "$builddir/src/lite" "$bindir"
|
||||
fi
|
||||
for module_name in core plugins colors fonts; do
|
||||
cp -r "data/$module_name" "$datadir"
|
||||
done
|
||||
}
|
||||
|
||||
run_lite () {
|
||||
if [ ! -z ${option_global+x} ]; then
|
||||
echo "running \"lite ${pargs[@]:3}\""
|
||||
exec "$bindir/lite" "${pargs[@]:3}"
|
||||
else
|
||||
echo "running \"lite ${pargs[@]:3}\" with local HOME"
|
||||
if [ ! -z ${run_windows+x} ]; then
|
||||
USERPROFILE="$userdir" exec "$bindir/lite" "${pargs[@]:3}"
|
||||
else
|
||||
HOME="$userdir" exec "$bindir/lite" "${pargs[@]:3}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
copy_plugin () {
|
||||
echo "-- lite-xl 1.16" | cat - "$plugin_dir/$plugin.lua" > "$datadir/plugins/$plugin.lua"
|
||||
}
|
||||
|
||||
if [ $option_copy == on ]; then
|
||||
build_lite
|
||||
copy_lite_build
|
||||
fi
|
||||
copy_plugin
|
||||
run_lite
|
200
doc/usage.md
200
doc/usage.md
|
@ -1,200 +0,0 @@
|
|||
# lite
|
||||
|
||||
![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png)
|
||||
|
||||
## Overview
|
||||
Lite is a lightweight text editor written mostly in Lua — it aims to provide
|
||||
something practical, pretty, *small* and fast, implemented as simply as
|
||||
possible; easy to modify and extend, or to use without doing either.
|
||||
|
||||
Lite XL is based on the Lite editor itself and provide some enhancements
|
||||
while remaining generally compatible with Lite.
|
||||
|
||||
|
||||
## Getting Started
|
||||
Lite works using a *project directory* — this is the directory where your
|
||||
project's code and other data resides.
|
||||
|
||||
To open lite with a specific project directory the directory name can be passed
|
||||
as a command-line argument *(`.` can be passed to use the current directory)* or
|
||||
the directory can be dragged onto either the lite executable or a running
|
||||
instance of lite.
|
||||
|
||||
Once started the project directory can be changed using the command
|
||||
`core:change-project-folder`. The command will close all the documents
|
||||
currently opened and switch to the new project directory.
|
||||
|
||||
If you want to open a project directory in a new window the command
|
||||
`core:open-project-folder` will open a new editor window with the selected
|
||||
project directory.
|
||||
|
||||
The main way of opening files in lite is through the `core:find-file` command
|
||||
— this provides a fuzzy finder over all of the project's files and can be
|
||||
opened using the **`ctrl+p`** shortcut by default.
|
||||
|
||||
Commands can be run using keyboard shortcuts, or by using the `core:find-command`
|
||||
command bound to **`ctrl+shift+p`** by default. For example, pressing
|
||||
`ctrl+shift+p` and typing `newdoc` then pressing `return` would open a new
|
||||
document. The current keyboard shortcut for a command can be seen to the right
|
||||
of the command name on the command finder, thus to find the shortcut for a command
|
||||
`ctrl+shift+p` can be pressed and the command name typed.
|
||||
|
||||
|
||||
## User Module
|
||||
lite can be configured through use of the user module. The user module can be
|
||||
used for changing options in the config module, adding additional key bindings,
|
||||
loading custom color themes, modifying the style or changing any other part of
|
||||
lite to your personal preference.
|
||||
|
||||
The user module is loaded by lite when the application starts, after the plugins
|
||||
have been loaded.
|
||||
|
||||
The user module can be modified by running the `core:open-user-module` command
|
||||
or otherwise directly opening the `$HOME/.config/lite-xl/init.lua` file.
|
||||
|
||||
On Windows, the variable `$USERPROFILE` will be used instead of
|
||||
`$HOME`.
|
||||
|
||||
Please note that Lite XL differs from the standard Lite editor for the location
|
||||
of the user's module.
|
||||
|
||||
## Project Module
|
||||
The project module is an optional module which is loaded from the current
|
||||
project's directory when lite is started. Project modules can be useful for
|
||||
things like adding custom commands for project-specific build systems, or
|
||||
loading project-specific plugins.
|
||||
|
||||
The project module is loaded by lite when the application starts, after both the
|
||||
plugins and user module have been loaded.
|
||||
|
||||
The project module can be edited by running the `core:open-project-module`
|
||||
command — if the module does not exist for the current project when the
|
||||
command is run it will be created.
|
||||
|
||||
## Add directories to a project
|
||||
|
||||
In addition to the project directories it is possible to add other directories
|
||||
using the command `core:add-directory`.
|
||||
Once added a directory it will be shown in the tree-view on the left side and
|
||||
the additional files will be reachable using the `ctrl+p` command (find file).
|
||||
The additonal files will be also visible when searching across the project.
|
||||
|
||||
The additional directories can be removed using the command `core:remove-directory`.
|
||||
|
||||
When you will open again Lite XL on the same project folder the application will
|
||||
remember your workspace including the additonal project directories.
|
||||
|
||||
Since version 1.15 Lite XL does not need a workspace plugin as it is now
|
||||
bundled with the editor.
|
||||
|
||||
|
||||
## Create new empty directory
|
||||
|
||||
Using the command `files:create-directory` or control-click in a directory in the
|
||||
tree-view to create a new empty subdirectory.
|
||||
|
||||
|
||||
## Commands
|
||||
Commands in lite are used both through the command finder (`ctrl+shift+p`) and
|
||||
by lite's keyboard shortcut system. Commands consist of 3 components:
|
||||
* **Name** — The command name in the form of `namespace:action-name`, for
|
||||
example: `doc:select-all`
|
||||
* **Predicate** — A function that returns true if the command can be ran, for
|
||||
example, for any document commands the predicate checks whether the active
|
||||
view is a document
|
||||
* **Function** — The function which performs the command itself
|
||||
|
||||
Commands can be added using the `command.add` function provided by the
|
||||
`core.command` module:
|
||||
```lua
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
|
||||
command.add("core.docview", {
|
||||
["doc:save"] = function()
|
||||
core.active_view.doc:save()
|
||||
core.log("Saved '%s', core.active_view.doc.filename)
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
Commands can be performed programatically (eg. from another command or by your
|
||||
user module) by calling the `command.perform` function after requiring the
|
||||
`command` module:
|
||||
```lua
|
||||
local command = require "core.command"
|
||||
command.perform "core:quit"
|
||||
```
|
||||
|
||||
|
||||
## Keymap
|
||||
All keyboard shortcuts in lite are handled by the `core.keymap` module. A key
|
||||
binding in lite maps a "stroke" (eg. `ctrl+q`) to one or more commands (eg.
|
||||
`core:quit`). When the shortcut is pressed lite will iterate each command
|
||||
assigned to that key and run the *predicate function* for that command — if the
|
||||
predicate passes it stops iterating and runs the command.
|
||||
|
||||
An example of where this used is the default binding of the `tab` key:
|
||||
``` lua
|
||||
["tab"] = { "command:complete", "doc:indent" },
|
||||
```
|
||||
When tab is pressed the `command:complete` command is attempted which will only
|
||||
succeed if the command-input at the bottom of the window is active. Otherwise
|
||||
the `doc:indent` command is attempted which will only succeed if we have a
|
||||
document as our active view.
|
||||
|
||||
A new mapping can be added by your user module as follows:
|
||||
```lua
|
||||
local keymap = require "core.keymap"
|
||||
keymap.add { ["ctrl+q"] = "core:quit" }
|
||||
```
|
||||
|
||||
|
||||
## Plugins
|
||||
Plugins in lite are normal lua modules and are treated as such — no
|
||||
complicated plugin manager is provided, and, once a plugin is loaded, it is never
|
||||
expected be to have to unload itself.
|
||||
|
||||
To install a plugin simply drop it in the `plugins` directory in the user
|
||||
module directory.
|
||||
When Lite XL starts it will first load the plugins included in the data directory
|
||||
and will then loads the plugins located in the user module directory.
|
||||
|
||||
To uninstall a plugin the
|
||||
plugin file can be deleted — any plugin (including those included with lite's
|
||||
default installation) can be deleted to remove its functionality.
|
||||
|
||||
If you want to load a plugin only under a certain circumstance (for example,
|
||||
only on a given project) the plugin can be placed somewhere other than the
|
||||
`plugins` directory so that it is not automatically loaded. The plugin can
|
||||
then be loaded manually as needed by using the `require` function.
|
||||
|
||||
Plugins can be downloaded from the [plugins repository](https://github.com/rxi/lite-plugins).
|
||||
|
||||
|
||||
## Restarting the editor
|
||||
|
||||
If you modifies the user configuration file or some of the Lua implementation files you may
|
||||
restart the editor using the command `core:restart`.
|
||||
All the application will be restarting by keeping the window that is already in use.
|
||||
|
||||
|
||||
## Color Themes
|
||||
Colors themes in lite are lua modules which overwrite the color fields of lite's
|
||||
`core.style` module.
|
||||
Pre-defined color methods are located in the `colors` folder in the data directory.
|
||||
Additional color themes can be installed in the user's directory in a folder named
|
||||
`colors`.
|
||||
|
||||
A color theme can be set by requiring it in your user module:
|
||||
```lua
|
||||
core.reload_module "colors.winter"
|
||||
```
|
||||
|
||||
In the Lite editor the function `require` is used instead of `core.reload_module`.
|
||||
In Lite XL `core.reload_module` should be used to ensure that the color module
|
||||
is actually reloaded when saving the user's configuration file.
|
||||
|
||||
Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors).
|
||||
They are included with Lite XL release packages.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Interface Files
|
||||
|
||||
This directory holds the documentation for the Lua C API that
|
||||
is hidden in the C source files of Lite. The idea of these files
|
||||
is to serve you as a quick reference about the functionality
|
||||
that is not written in Lua it self. Please note that they
|
||||
don't have any real code, just metadata or annotations.
|
||||
|
||||
Also, these interfaces are using
|
||||
[EmmyLua annotation syntax](https://emmylua.github.io/annotation.html)
|
||||
which is supported by LSP servers like the
|
||||
[Sumneko Lua LSP](https://github.com/sumneko/lua-language-server).
|
||||
This means that you can get nice code autocompletion and descriptions
|
||||
of Lite core libraries and symbols when developing plugins or adding
|
||||
any options to your **User Module File** (init.lua).
|
||||
|
||||
## The Base Core
|
||||
|
||||
Most of the code that is written in Lua for Lite is powered by the exposed
|
||||
C API in the four namespaces that follow:
|
||||
|
||||
* [system](api/system.lua)
|
||||
* [renderer](api/renderer.lua)
|
||||
* [regex](api/regex.lua)
|
||||
* [process](api/process.lua)
|
||||
|
||||
Finally, all global variables are documented in the file named
|
||||
[globals.lua](api/globals.lua).
|
|
@ -0,0 +1,21 @@
|
|||
---@meta
|
||||
|
||||
---The command line arguments given to lite.
|
||||
---@type table<integer, string>
|
||||
ARGS = {}
|
||||
|
||||
---The current operating system.
|
||||
---@type string | "'Windows'" | "'Mac OS X'" | "'Linux'" | "'iOS'" | "'Android'"
|
||||
PLATFORM = "Operating System"
|
||||
|
||||
---The current text or ui scale.
|
||||
---@type number
|
||||
SCALE = 1.0
|
||||
|
||||
---Full path of lite executable.
|
||||
---@type string
|
||||
EXEFILE = "/path/to/lite"
|
||||
|
||||
---Path to the users home directory.
|
||||
---@type string
|
||||
HOME = "/path/to/user/dir"
|
|
@ -0,0 +1,235 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Functionality that allows you to launch subprocesses and read
|
||||
---or write to them in a non-blocking fashion.
|
||||
---@class process
|
||||
process = {}
|
||||
|
||||
---Error triggered when the stdout, stderr or stdin fails while reading
|
||||
---or writing, its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_PIPE = -1
|
||||
|
||||
---Error triggered when a read or write action is blocking,
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_WOULDBLOCK = -2
|
||||
|
||||
---Error triggered when a process takes more time than that specified
|
||||
---by the deadline parameter given on process:start(),
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_TIMEDOUT = -3
|
||||
|
||||
---Error triggered when trying to terminate or kill a non running process,
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_INVAL = -4
|
||||
|
||||
---Error triggered when no memory is available to allocate the process,
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_NOMEM = -5
|
||||
|
||||
---Used for the process:close_stream() method to close stdin.
|
||||
---@type integer
|
||||
process.STREAM_STDIN = 0
|
||||
|
||||
---Used for the process:close_stream() method to close stdout.
|
||||
---@type integer
|
||||
process.STREAM_STDOUT = 1
|
||||
|
||||
---Used for the process:close_stream() method to close stderr.
|
||||
---@type integer
|
||||
process.STREAM_STDERR = 2
|
||||
|
||||
---Instruct process:wait() to wait until the process ends.
|
||||
---@type integer
|
||||
process.WAIT_INFINITE = -1
|
||||
|
||||
---Instruct process:wait() to wait until the deadline given on process:start()
|
||||
---@type integer
|
||||
process.WAIT_DEADLINE = -2
|
||||
|
||||
---Default behavior for redirecting streams.
|
||||
---This flag is deprecated and for backwards compatibility with reproc only.
|
||||
---The behavior of this flag may change in future versions of Lite XL.
|
||||
---@type integer
|
||||
process.REDIRECT_DEFAULT = 0
|
||||
|
||||
---Allow Process API to read this stream via process:read functions.
|
||||
---@type integer
|
||||
process.REDIRECT_PIPE = 1
|
||||
|
||||
---Redirect this stream to the parent.
|
||||
---@type integer
|
||||
process.REDIRECT_PARENT = 2
|
||||
|
||||
---Discard this stream (piping it to /dev/null)
|
||||
---@type integer
|
||||
process.REDIRECT_DISCARD = 3
|
||||
|
||||
---Redirect this stream to stdout.
|
||||
---This flag can only be used on process.options.stderr.
|
||||
---@type integer
|
||||
process.REDIRECT_STDOUT = 4
|
||||
|
||||
---@alias process.errortype
|
||||
---|>'process.ERROR_PIPE'
|
||||
---| 'process.ERROR_WOULDBLOCK'
|
||||
---| 'process.ERROR_TIMEDOUT'
|
||||
---| 'process.ERROR_INVAL'
|
||||
---| 'process.ERROR_NOMEM'
|
||||
|
||||
---@alias process.streamtype
|
||||
---|>'process.STREAM_STDIN'
|
||||
---| 'process.STREAM_STDOUT'
|
||||
---| 'process.STREAM_STDERR'
|
||||
|
||||
---@alias process.waittype
|
||||
---|>'process.WAIT_INFINITE'
|
||||
---| 'process.WAIT_DEADLINE'
|
||||
|
||||
---@alias process.redirecttype
|
||||
---|>'process.REDIRECT_DEFAULT'
|
||||
---| 'process.REDIRECT_PIPE'
|
||||
---| 'process.REDIRECT_PARENT'
|
||||
---| 'process.REDIRECT_DISCARD'
|
||||
---| 'process.REDIRECT_STDOUT'
|
||||
|
||||
---
|
||||
--- Options that can be passed to process.start()
|
||||
---@class process.options
|
||||
---@field public timeout number
|
||||
---@field public cwd string
|
||||
---@field public stdin process.redirecttype
|
||||
---@field public stdout process.redirecttype
|
||||
---@field public stderr process.redirecttype
|
||||
---@field public env table<string, string>
|
||||
process.options = {}
|
||||
|
||||
---
|
||||
---Create and start a new process
|
||||
---
|
||||
---@param command_and_params table First index is the command to execute
|
||||
---and subsequente elements are parameters for the command.
|
||||
---@param options process.options
|
||||
---
|
||||
---@return process | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:start(command_and_params, options) end
|
||||
|
||||
---
|
||||
---Translates an error code into a useful text message
|
||||
---
|
||||
---@param code integer
|
||||
---
|
||||
---@return string | nil
|
||||
function process.strerror(code) end
|
||||
|
||||
---
|
||||
---Get the process id.
|
||||
---
|
||||
---@return integer id Process id or 0 if not running.
|
||||
function process:pid() end
|
||||
|
||||
---
|
||||
---Read from the given stream type, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param stream process.streamtype
|
||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
||||
---
|
||||
---@return string | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:read(stream, len) end
|
||||
|
||||
---
|
||||
---Read from stdout, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
||||
---
|
||||
---@return string | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:read_stdout(len) end
|
||||
|
||||
---
|
||||
---Read from stderr, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
||||
---
|
||||
---@return string | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:read_stderr(len) end
|
||||
|
||||
---
|
||||
---Write to the stdin, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param data string
|
||||
---
|
||||
---@return integer | nil bytes The amount of bytes written or nil if error
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:write(data) end
|
||||
|
||||
---
|
||||
---Allows you to close a stream pipe that you will not be using.
|
||||
---
|
||||
---@param stream process.streamtype
|
||||
---
|
||||
---@return integer | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:close_stream(stream) end
|
||||
|
||||
---
|
||||
---Wait the specified amount of time for the process to exit.
|
||||
---
|
||||
---@param timeout integer | process.waittype Time to wait in milliseconds,
|
||||
---if 0, the function will only check if process is running without waiting.
|
||||
---
|
||||
---@return integer | nil exit_status The process exit status or nil on error
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:wait(timeout) end
|
||||
|
||||
---
|
||||
---Sends SIGTERM to the process
|
||||
---
|
||||
---@return boolean | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:terminate() end
|
||||
|
||||
---
|
||||
---Sends SIGKILL to the process
|
||||
---
|
||||
---@return boolean | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:kill() end
|
||||
|
||||
---
|
||||
---Get the exit code of the process or nil if still running.
|
||||
---
|
||||
---@return number | nil
|
||||
function process:returncode() end
|
||||
|
||||
---
|
||||
---Check if the process is running
|
||||
---
|
||||
---@return boolean
|
||||
function process:running() end
|
|
@ -0,0 +1,57 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Provides the base functionality for regular expressions matching.
|
||||
---@class regex
|
||||
regex = {}
|
||||
|
||||
---Instruct regex:cmatch() to match only at the first position.
|
||||
---@type integer
|
||||
regex.ANCHORED = 0x80000000
|
||||
|
||||
---Tell regex:cmatch() that the pattern can match only at end of subject.
|
||||
---@type integer
|
||||
regex.ENDANCHORED = 0x20000000
|
||||
|
||||
---Tell regex:cmatch() that subject string is not the beginning of a line.
|
||||
---@type integer
|
||||
regex.NOTBOL = 0x00000001
|
||||
|
||||
---Tell regex:cmatch() that subject string is not the end of a line.
|
||||
---@type integer
|
||||
regex.NOTEOL = 0x00000002
|
||||
|
||||
---Tell regex:cmatch() that an empty string is not a valid match.
|
||||
---@type integer
|
||||
regex.NOTEMPTY = 0x00000004
|
||||
|
||||
---Tell regex:cmatch() that an empty string at the start of the
|
||||
---subject is not a valid match.
|
||||
---@type integer
|
||||
regex.NOTEMPTY_ATSTART = 0x00000008
|
||||
|
||||
---@alias regex.modifiers
|
||||
---|>'"i"' # Case insesitive matching
|
||||
---| '"m"' # Multiline matching
|
||||
---| '"s"' # Match all characters with dot (.) metacharacter even new lines
|
||||
|
||||
---
|
||||
---Compiles a regular expression pattern that can be used to search in strings.
|
||||
---
|
||||
---@param pattern string
|
||||
---@param options? regex.modifiers A string of one or more pattern modifiers.
|
||||
---
|
||||
---@return regex|string regex Ready to use regular expression object or error
|
||||
---message if compiling the pattern failed.
|
||||
function regex.compile(pattern, options) end
|
||||
|
||||
---
|
||||
---Search a string for valid matches and returns a list of matching offsets.
|
||||
---
|
||||
---@param subject string The string to search for valid matches.
|
||||
---@param offset? integer The position on the subject to start searching.
|
||||
---@param options? integer A bit field of matching options, eg:
|
||||
---regex.NOTBOL | regex.NOTEMPTY
|
||||
---
|
||||
---@return table<integer, integer> list List of offsets where a match was found.
|
||||
function regex:cmatch(subject, offset, options) end
|
|
@ -0,0 +1,169 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Core functionality to render or draw elements into the screen.
|
||||
---@class renderer
|
||||
renderer = {}
|
||||
|
||||
---
|
||||
---Represents a color used by the rendering functions.
|
||||
---@class renderer.color
|
||||
---@field public r number Red
|
||||
---@field public g number Green
|
||||
---@field public b number Blue
|
||||
---@field public a number Alpha
|
||||
renderer.color = {}
|
||||
|
||||
---
|
||||
---Represent options that affect a font's rendering.
|
||||
---@class renderer.fontoptions
|
||||
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
||||
---@field public hinting "'slight'" | "'none'" | '"full"'
|
||||
-- @field public bold boolean
|
||||
-- @field public italic boolean
|
||||
-- @field public underline boolean
|
||||
renderer.fontoptions = {}
|
||||
|
||||
---
|
||||
---@class renderer.font
|
||||
renderer.font = {}
|
||||
|
||||
---
|
||||
---Create a new font object.
|
||||
---
|
||||
---@param path string
|
||||
---@param size number
|
||||
---@param options renderer.fontoptions
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font.load(path, size, options) end
|
||||
|
||||
---
|
||||
---Clones a font object into a new one.
|
||||
---
|
||||
---@param size? number Optional new size for cloned font.
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font:copy(size) end
|
||||
|
||||
---
|
||||
---Set the amount of characters that represent a tab.
|
||||
---
|
||||
---@param chars integer Also known as tab width.
|
||||
function renderer.font:set_tab_size(chars) end
|
||||
|
||||
---
|
||||
---Get the width in pixels of the given text when
|
||||
---rendered with this font.
|
||||
---
|
||||
---@param text string
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_width(text) end
|
||||
|
||||
---
|
||||
---Get the height in pixels that occupies a single character
|
||||
---when rendered with this font.
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_height() end
|
||||
|
||||
---
|
||||
---Get the current size of the font.
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_size() end
|
||||
|
||||
---
|
||||
---Set a new size for the font.
|
||||
---
|
||||
---@param size number
|
||||
function renderer.font:set_size(size) end
|
||||
|
||||
---
|
||||
---Assistive functionality to replace characters in a
|
||||
---rendered text with other characters.
|
||||
---@class renderer.replacements
|
||||
renderer.replacements = {}
|
||||
|
||||
---
|
||||
---Create a new character replacements object.
|
||||
---
|
||||
---@return renderer.replacements
|
||||
function renderer.replacements.new() end
|
||||
|
||||
---
|
||||
---Add to internal map a character to character replacement.
|
||||
---
|
||||
---@param original_char string Should be a single character like '\t'
|
||||
---@param replacement_char string Should be a single character like '»'
|
||||
function renderer.replacements:add(original_char, replacement_char) end
|
||||
|
||||
---
|
||||
---Toggles drawing debugging rectangles on the currently rendered sections
|
||||
---of the window to help troubleshoot the renderer.
|
||||
---
|
||||
---@param enable boolean
|
||||
function renderer.show_debug(enable) end
|
||||
|
||||
---
|
||||
---Get the size of the screen area been rendered.
|
||||
---
|
||||
---@return number width
|
||||
---@return number height
|
||||
function renderer.get_size() end
|
||||
|
||||
---
|
||||
---Tell the rendering system that we want to build a new frame to render.
|
||||
function renderer.begin_frame() end
|
||||
|
||||
---
|
||||
---Tell the rendering system that we finished building the frame.
|
||||
function renderer.end_frame() end
|
||||
|
||||
---
|
||||
---Set the region of the screen where draw operations will take effect.
|
||||
---
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
function renderer.set_clip_rect(x, y, width, height) end
|
||||
|
||||
---
|
||||
---Draw a rectangle.
|
||||
---
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param color renderer.color
|
||||
function renderer.draw_rect(x, y, width, height, color) end
|
||||
|
||||
---
|
||||
---Draw text.
|
||||
---
|
||||
---@param font renderer.font
|
||||
---@param text string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param color renderer.color
|
||||
---@param replace renderer.replacements
|
||||
---@param color_replace renderer.color
|
||||
---
|
||||
---@return number x_subpixel
|
||||
function renderer.draw_text(font, text, x, y, color, replace, color_replace) end
|
||||
|
||||
---
|
||||
---Draw text at subpixel level.
|
||||
---
|
||||
---@param font renderer.font
|
||||
---@param text string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param color renderer.color
|
||||
---@param replace renderer.replacements
|
||||
---@param color_replace renderer.color
|
||||
---
|
||||
---@return number x_subpixel
|
||||
function renderer.draw_text_subpixel(font, text, x, y, color, replace, color_replace) end
|
|
@ -0,0 +1,234 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Utilites for managing current window, files and more.
|
||||
---@class system
|
||||
system = {}
|
||||
|
||||
---@alias system.fileinfotype
|
||||
---|>'"file"' # It is a file.
|
||||
---| '"dir"' # It is a directory.
|
||||
|
||||
---
|
||||
---@class system.fileinfo
|
||||
---@field public modified number A timestamp in seconds.
|
||||
---@field public size number Size in bytes.
|
||||
---@field public type system.fileinfotype Type of file
|
||||
system.fileinfo = {}
|
||||
|
||||
---
|
||||
---Core function used to retrieve the current event been triggered by SDL.
|
||||
---
|
||||
---The following is a list of event types emitted by this function and
|
||||
---the arguments for each of them if applicable.
|
||||
---
|
||||
---Window events:
|
||||
--- * "quit"
|
||||
--- * "resized" -> width, height
|
||||
--- * "exposed"
|
||||
--- * "minimized"
|
||||
--- * "maximized"
|
||||
--- * "restored"
|
||||
--- * "focuslost"
|
||||
---
|
||||
---File events:
|
||||
--- * "filedropped" -> filename, x, y
|
||||
---
|
||||
---Keyboard events:
|
||||
--- * "keypressed" -> key_name
|
||||
--- * "keyreleased" -> key_name
|
||||
--- * "textinput" -> text
|
||||
---
|
||||
---Mouse events:
|
||||
--- * "mousepressed" -> button_name, x, y, amount_of_clicks
|
||||
--- * "mousereleased" -> button_name, x, y
|
||||
--- * "mousemoved" -> x, y, relative_x, relative_y
|
||||
--- * "mousewheel" -> y
|
||||
---
|
||||
---@return string type
|
||||
---@return any? arg1
|
||||
---@return any? arg2
|
||||
---@return any? arg3
|
||||
---@return any? arg4
|
||||
function system.poll_event() end
|
||||
|
||||
---
|
||||
---Wait until an event is triggered.
|
||||
---
|
||||
---@param timeout number Amount of seconds, also supports fractions
|
||||
---of a second, eg: 0.01
|
||||
---
|
||||
---@return boolean status True on success or false if there was an error.
|
||||
function system.wait_event(timeout) end
|
||||
|
||||
---
|
||||
---Change the cursor type displayed on screen.
|
||||
---
|
||||
---@param type string | "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
|
||||
function system.set_cursor(type) end
|
||||
|
||||
---
|
||||
---Change the window title.
|
||||
---
|
||||
---@param title string
|
||||
function system.set_window_title(title) end
|
||||
|
||||
---@alias system.windowmode
|
||||
---|>'"normal"'
|
||||
---| '"minimized"'
|
||||
---| '"maximized"'
|
||||
---| '"fullscreen"'
|
||||
|
||||
---
|
||||
---Change the window mode.
|
||||
---
|
||||
---@param mode system.windowmode
|
||||
function system.set_window_mode(mode) end
|
||||
|
||||
---
|
||||
---Retrieve the current window mode.
|
||||
---
|
||||
---@return system.windowmode mode
|
||||
function system.get_window_mode() end
|
||||
|
||||
---
|
||||
---Toggle between bordered and borderless.
|
||||
---
|
||||
---@param bordered boolean
|
||||
function system.set_window_bordered(bordered) end
|
||||
|
||||
---
|
||||
---When then window is run borderless (without system decorations), this
|
||||
---function allows to set the size of the different regions that allow
|
||||
---for custom window management.
|
||||
---
|
||||
---@param title_height number
|
||||
---@param controls_width number This is for minimize, maximize, close, etc...
|
||||
---@param resize_border number The amount of pixels reserved for resizing
|
||||
function system.set_window_hit_test(title_height, controls_width, resize_border) end
|
||||
|
||||
---
|
||||
---Get the size and coordinates of the window.
|
||||
---
|
||||
---@return number width
|
||||
---@return number height
|
||||
---@return number x
|
||||
---@return number y
|
||||
function system.get_window_size() end
|
||||
|
||||
---
|
||||
---Sets the size and coordinates of the window.
|
||||
---
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param x number
|
||||
---@param y number
|
||||
function system.set_window_size(width, height, x, y) end
|
||||
|
||||
---
|
||||
---Check if the window currently has focus.
|
||||
---
|
||||
---@return boolean
|
||||
function system.window_has_focus() end
|
||||
|
||||
---
|
||||
---Opens a message box to display an error message.
|
||||
---
|
||||
---@param title string
|
||||
---@param message string
|
||||
function system.show_fatal_error(title, message) end
|
||||
|
||||
---
|
||||
---Change the current directory path which affects relative file operations.
|
||||
---This function raises an error if the path doesn't exists.
|
||||
---
|
||||
---@param path string
|
||||
function system.chdir(path) end
|
||||
|
||||
---
|
||||
---Create a new directory, note that this function doesn't recursively
|
||||
---creates the directories on the given path.
|
||||
---
|
||||
---@param directory_path string
|
||||
---
|
||||
---@return boolean created True on success or false on failure.
|
||||
function system.mkdir(directory_path) end
|
||||
|
||||
---
|
||||
---Gets a list of files and directories for a given path.
|
||||
---
|
||||
---@param path string
|
||||
---
|
||||
---@return table|nil list List of directories or nil if empty or error.
|
||||
---@return string? message Error message in case of error.
|
||||
function system.list_dir(path) end
|
||||
|
||||
---
|
||||
---Converts a relative path from current directory to the absolute one.
|
||||
---
|
||||
---@param path string
|
||||
---
|
||||
---@return string
|
||||
function system.absolute_path(path) end
|
||||
|
||||
---
|
||||
---Get details about a given file or path.
|
||||
---
|
||||
---@param path string Can be a file or a directory path
|
||||
---
|
||||
---@return system.fileinfo|nil info Path details or nil if empty or error.
|
||||
---@return string? message Error message in case of error.
|
||||
function system.get_file_info(path) end
|
||||
|
||||
---
|
||||
---Retrieve the text currently stored on the clipboard.
|
||||
---
|
||||
---@return string
|
||||
function system.get_clipboard() end
|
||||
|
||||
---
|
||||
---Set the content of the clipboard.
|
||||
---
|
||||
---@param text string
|
||||
function system.set_clipboard(text) end
|
||||
|
||||
---
|
||||
---Get amount of iterations since the application was launched
|
||||
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()
|
||||
---
|
||||
---@return number
|
||||
function system.get_time() end
|
||||
|
||||
---
|
||||
---Sleep for the given amount of seconds.
|
||||
---
|
||||
---@param seconds number Also supports fractions of a second, eg: 0.01
|
||||
function system.sleep(seconds) end
|
||||
|
||||
---
|
||||
---Similar to os.execute() but does not return the exit status of the
|
||||
---executed command and executes the process in a non blocking way by
|
||||
---forking it to the background.
|
||||
---
|
||||
---@param command string The command to execute.
|
||||
function system.exec(command) end
|
||||
|
||||
---
|
||||
---Generates a matching score depending on how well the value of the
|
||||
---given needle compares to that of the value in the haystack.
|
||||
---
|
||||
---@param haystack string
|
||||
---@param needle string
|
||||
---@param file boolean Reverse the algorithm to prioritize the end
|
||||
---of the haystack, eg: with a haystack "/my/path/to/file" and a needle
|
||||
---"file", will get better score than with this option not set to true.
|
||||
---
|
||||
---@return integer score
|
||||
function system.fuzzy_match(haystack, needle, file) end
|
||||
|
||||
---
|
||||
---Change the opacity (also known as transparency) of the window.
|
||||
---
|
||||
---@param opacity number A value from 0.0 to 1.0, the lower the value
|
||||
---the less visible the window will be.
|
||||
function system.set_window_opacity(opacity) end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,162 @@
|
|||
#ifndef __DMON_EXTRA_H__
|
||||
#define __DMON_EXTRA_H__
|
||||
|
||||
//
|
||||
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
|
||||
// License: https://github.com/septag/dmon#license-bsd-2-clause
|
||||
//
|
||||
// Extra header functionality for dmon.h for the backend based on inotify
|
||||
//
|
||||
// Add/Remove directory functions:
|
||||
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
||||
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
||||
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
|
||||
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
|
||||
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
|
||||
// will be reached. The default maximum is 8192.
|
||||
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
|
||||
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
|
||||
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
|
||||
//
|
||||
|
||||
#ifndef __DMON_H__
|
||||
#error "Include 'dmon.h' before including this file"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
|
||||
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DMON_IMPL
|
||||
#if DMON_OS_LINUX
|
||||
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
||||
{
|
||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
||||
|
||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
// check if the directory exists
|
||||
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
|
||||
// else, we assume that watchdir is correct, so save it as it is
|
||||
struct stat st;
|
||||
dmon__watch_subdir subdir;
|
||||
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
||||
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
|
||||
}
|
||||
} else {
|
||||
char fullpath[DMON_MAX_PATH];
|
||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
||||
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
|
||||
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
||||
}
|
||||
|
||||
int dirlen = (int)strlen(subdir.rootdir);
|
||||
if (subdir.rootdir[dirlen - 1] != '/') {
|
||||
subdir.rootdir[dirlen] = '/';
|
||||
subdir.rootdir[dirlen + 1] = '\0';
|
||||
}
|
||||
|
||||
// check that the directory is not already added
|
||||
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
|
||||
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
|
||||
char fullpath[DMON_MAX_PATH];
|
||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
||||
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
|
||||
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
|
||||
if (wd == -1) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
stb_sb_push(watch->subdirs, subdir);
|
||||
stb_sb_push(watch->wds, wd);
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
||||
{
|
||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
||||
|
||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
char subdir[DMON_MAX_PATH];
|
||||
dmon__strcpy(subdir, sizeof(subdir), watchdir);
|
||||
if (strstr(subdir, watch->rootdir) == subdir) {
|
||||
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
|
||||
}
|
||||
|
||||
int dirlen = (int)strlen(subdir);
|
||||
if (subdir[dirlen - 1] != '/') {
|
||||
subdir[dirlen] = '/';
|
||||
subdir[dirlen + 1] = '\0';
|
||||
}
|
||||
|
||||
int i, c = stb_sb_count(watch->subdirs);
|
||||
for (i = 0; i < c; i++) {
|
||||
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= c) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
inotify_rm_watch(watch->fd, watch->wds[i]);
|
||||
|
||||
/* Remove entry from subdirs and wds by swapping position with the last entry */
|
||||
watch->subdirs[i] = stb_sb_last(watch->subdirs);
|
||||
stb_sb_pop(watch->subdirs);
|
||||
|
||||
watch->wds[i] = stb_sb_last(watch->wds);
|
||||
stb_sb_pop(watch->wds);
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return true;
|
||||
}
|
||||
#endif // DMON_OS_LINUX
|
||||
#endif // DMON_IMPL
|
||||
|
||||
#endif // __DMON_EXTRA_H__
|
||||
|
|
@ -0,0 +1 @@
|
|||
lite_includes += include_directories('.')
|
File diff suppressed because it is too large
Load Diff
|
@ -1,198 +0,0 @@
|
|||
//----------------------------------------------------------------------------
|
||||
// Anti-Grain Geometry - Version 2.4
|
||||
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
|
||||
//
|
||||
// Permission to copy, use, modify, sell and distribute this software
|
||||
// is granted provided this copyright notice appears in all copies.
|
||||
// This software is provided "as is" without express or implied
|
||||
// warranty, and with no claim as to its suitability for any purpose.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
// Contact: mcseem@antigrain.com
|
||||
// mcseemagg@yahoo.com
|
||||
// http://www.antigrain.com
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// See implementation agg_font_freetype.cpp
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#ifndef AGG_FONT_FREETYPE_INCLUDED
|
||||
#define AGG_FONT_FREETYPE_INCLUDED
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
|
||||
#include "agg_scanline_storage_aa.h"
|
||||
#include "agg_scanline_storage_bin.h"
|
||||
#include "agg_scanline_u.h"
|
||||
#include "agg_scanline_bin.h"
|
||||
#include "agg_path_storage_integer.h"
|
||||
#include "agg_rasterizer_scanline_aa.h"
|
||||
#include "agg_conv_curve.h"
|
||||
#include "agg_font_cache_manager.h"
|
||||
#include "agg_trans_affine.h"
|
||||
|
||||
namespace agg
|
||||
{
|
||||
|
||||
|
||||
//-----------------------------------------------font_engine_freetype_base
|
||||
class font_engine_freetype_base
|
||||
{
|
||||
public:
|
||||
//--------------------------------------------------------------------
|
||||
typedef serialized_scanlines_adaptor_aa<int8u> gray8_adaptor_type;
|
||||
typedef serialized_scanlines_adaptor_bin mono_adaptor_type;
|
||||
typedef scanline_storage_aa8 scanlines_aa_type;
|
||||
typedef scanline_storage_bin scanlines_bin_type;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
~font_engine_freetype_base();
|
||||
font_engine_freetype_base(bool flag32, unsigned max_faces = 32);
|
||||
|
||||
// Set font parameters
|
||||
//--------------------------------------------------------------------
|
||||
void resolution(unsigned dpi);
|
||||
bool load_font(const char* font_name, unsigned face_index, glyph_rendering ren_type,
|
||||
const char* font_mem = 0, const long font_mem_size = 0);
|
||||
bool attach(const char* file_name);
|
||||
bool char_map(FT_Encoding map);
|
||||
bool height(double h);
|
||||
bool width(double w);
|
||||
void hinting(bool h);
|
||||
void flip_y(bool f);
|
||||
void transform(const trans_affine& affine);
|
||||
|
||||
// Set Gamma
|
||||
//--------------------------------------------------------------------
|
||||
template<class GammaF> void gamma(const GammaF& f)
|
||||
{
|
||||
m_rasterizer.gamma(f);
|
||||
}
|
||||
|
||||
// Accessors
|
||||
//--------------------------------------------------------------------
|
||||
int last_error() const { return m_last_error; }
|
||||
unsigned resolution() const { return m_resolution; }
|
||||
const char* name() const { return m_name; }
|
||||
unsigned num_faces() const;
|
||||
FT_Encoding char_map() const { return m_char_map; }
|
||||
double height() const { return double(m_height) / 64.0; }
|
||||
double width() const { return double(m_width) / 64.0; }
|
||||
double ascender() const;
|
||||
double descender() const;
|
||||
int face_height() const;
|
||||
int face_units_em()const;
|
||||
bool hinting() const { return m_hinting; }
|
||||
bool flip_y() const { return m_flip_y; }
|
||||
|
||||
|
||||
// Interface mandatory to implement for font_cache_manager
|
||||
//--------------------------------------------------------------------
|
||||
const char* font_signature() const { return m_signature; }
|
||||
int change_stamp() const { return m_change_stamp; }
|
||||
|
||||
bool prepare_glyph(unsigned glyph_code);
|
||||
unsigned glyph_index() const { return m_glyph_index; }
|
||||
unsigned data_size() const { return m_data_size; }
|
||||
glyph_data_type data_type() const { return m_data_type; }
|
||||
const rect_i& bounds() const { return m_bounds; }
|
||||
double advance_x() const { return m_advance_x; }
|
||||
double advance_y() const { return m_advance_y; }
|
||||
void write_glyph_to(int8u* data) const;
|
||||
bool add_kerning(unsigned first, unsigned second,
|
||||
double* x, double* y);
|
||||
|
||||
private:
|
||||
font_engine_freetype_base(const font_engine_freetype_base&);
|
||||
const font_engine_freetype_base& operator = (const font_engine_freetype_base&);
|
||||
|
||||
void update_char_size();
|
||||
void update_signature();
|
||||
int find_face(const char* face_name) const;
|
||||
|
||||
bool m_flag32;
|
||||
int m_change_stamp;
|
||||
int m_last_error;
|
||||
char* m_name;
|
||||
unsigned m_name_len;
|
||||
unsigned m_face_index;
|
||||
FT_Encoding m_char_map;
|
||||
char* m_signature;
|
||||
unsigned m_height;
|
||||
unsigned m_width;
|
||||
bool m_hinting;
|
||||
bool m_flip_y;
|
||||
bool m_library_initialized;
|
||||
FT_Library m_library; // handle to library
|
||||
FT_Face* m_faces; // A pool of font faces
|
||||
char** m_face_names;
|
||||
unsigned m_num_faces;
|
||||
unsigned m_max_faces;
|
||||
FT_Face m_cur_face; // handle to the current face object
|
||||
int m_resolution;
|
||||
glyph_rendering m_glyph_rendering;
|
||||
unsigned m_glyph_index;
|
||||
unsigned m_data_size;
|
||||
glyph_data_type m_data_type;
|
||||
rect_i m_bounds;
|
||||
double m_advance_x;
|
||||
double m_advance_y;
|
||||
trans_affine m_affine;
|
||||
|
||||
path_storage_integer<int16, 6> m_path16;
|
||||
path_storage_integer<int32, 6> m_path32;
|
||||
conv_curve<path_storage_integer<int16, 6> > m_curves16;
|
||||
conv_curve<path_storage_integer<int32, 6> > m_curves32;
|
||||
scanline_u8 m_scanline_aa;
|
||||
scanline_bin m_scanline_bin;
|
||||
scanlines_aa_type m_scanlines_aa;
|
||||
scanlines_bin_type m_scanlines_bin;
|
||||
rasterizer_scanline_aa<> m_rasterizer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------font_engine_freetype_int16
|
||||
// This class uses values of type int16 (10.6 format) for the vector cache.
|
||||
// The vector cache is compact, but when rendering glyphs of height
|
||||
// more that 200 there integer overflow can occur.
|
||||
//
|
||||
class font_engine_freetype_int16 : public font_engine_freetype_base
|
||||
{
|
||||
public:
|
||||
typedef serialized_integer_path_adaptor<int16, 6> path_adaptor_type;
|
||||
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
|
||||
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
|
||||
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
|
||||
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
|
||||
|
||||
font_engine_freetype_int16(unsigned max_faces = 32) :
|
||||
font_engine_freetype_base(false, max_faces) {}
|
||||
};
|
||||
|
||||
//------------------------------------------------font_engine_freetype_int32
|
||||
// This class uses values of type int32 (26.6 format) for the vector cache.
|
||||
// The vector cache is twice larger than in font_engine_freetype_int16,
|
||||
// but it allows you to render glyphs of very large sizes.
|
||||
//
|
||||
class font_engine_freetype_int32 : public font_engine_freetype_base
|
||||
{
|
||||
public:
|
||||
typedef serialized_integer_path_adaptor<int32, 6> path_adaptor_type;
|
||||
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
|
||||
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
|
||||
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
|
||||
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
|
||||
|
||||
font_engine_freetype_int32(unsigned max_faces = 32) :
|
||||
font_engine_freetype_base(true, max_faces) {}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,73 +0,0 @@
|
|||
// Adapted by Francesco Abbate for GSL Shell
|
||||
// Original code's copyright below.
|
||||
//----------------------------------------------------------------------------
|
||||
// Anti-Grain Geometry - Version 2.4
|
||||
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
|
||||
//
|
||||
// Permission to copy, use, modify, sell and distribute this software
|
||||
// is granted provided this copyright notice appears in all copies.
|
||||
// This software is provided "as is" without express or implied
|
||||
// warranty, and with no claim as to its suitability for any purpose.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
// Contact: mcseem@antigrain.com
|
||||
// mcseemagg@yahoo.com
|
||||
// http://www.antigrain.com
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#ifndef AGG_LCD_DISTRIBUTION_LUT_INCLUDED
|
||||
#define AGG_LCD_DISTRIBUTION_LUT_INCLUDED
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "agg_basics.h"
|
||||
|
||||
namespace agg
|
||||
{
|
||||
|
||||
//=====================================================lcd_distribution_lut
|
||||
class lcd_distribution_lut
|
||||
{
|
||||
public:
|
||||
lcd_distribution_lut(double prim, double second, double tert)
|
||||
{
|
||||
double norm = 1.0 / (prim + second*2 + tert*2);
|
||||
prim *= norm;
|
||||
second *= norm;
|
||||
tert *= norm;
|
||||
for(unsigned i = 0; i < 256; i++)
|
||||
{
|
||||
unsigned b = (i << 8);
|
||||
unsigned s = round(second * b);
|
||||
unsigned t = round(tert * b);
|
||||
unsigned p = b - (2*s + 2*t);
|
||||
|
||||
m_data[3*i + 1] = s; /* secondary */
|
||||
m_data[3*i + 2] = t; /* tertiary */
|
||||
m_data[3*i ] = p; /* primary */
|
||||
}
|
||||
}
|
||||
|
||||
unsigned convolution(const int8u* covers, int i0, int i_min, int i_max) const
|
||||
{
|
||||
unsigned sum = 0;
|
||||
int k_min = (i0 >= i_min + 2 ? -2 : i_min - i0);
|
||||
int k_max = (i0 <= i_max - 2 ? 2 : i_max - i0);
|
||||
for (int k = k_min; k <= k_max; k++)
|
||||
{
|
||||
/* select the primary, secondary or tertiary channel */
|
||||
int channel = std::abs(k) % 3;
|
||||
int8u c = covers[i0 + k];
|
||||
sum += m_data[3*c + channel];
|
||||
}
|
||||
|
||||
return (sum + 128) >> 8;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned short m_data[256*3];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,93 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include "agg_basics.h"
|
||||
#include "agg_rendering_buffer.h"
|
||||
|
||||
namespace agg
|
||||
{
|
||||
// This is a special purpose color type that only has the alpha channel.
|
||||
// It can be thought as a gray color but with the intensity always on.
|
||||
// It is actually used to store coverage information.
|
||||
struct alpha8
|
||||
{
|
||||
typedef int8u value_type;
|
||||
typedef int32u calc_type;
|
||||
typedef int32 long_type;
|
||||
enum base_scale_e
|
||||
{
|
||||
base_shift = 8,
|
||||
base_scale = 1 << base_shift,
|
||||
base_mask = base_scale - 1
|
||||
};
|
||||
|
||||
value_type a;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
alpha8(unsigned a_=base_mask) :
|
||||
a(int8u(a_)) {}
|
||||
};
|
||||
|
||||
// Pixer format to store coverage information.
|
||||
class pixfmt_alpha8
|
||||
{
|
||||
public:
|
||||
typedef alpha8 color_type;
|
||||
typedef int8u value_type;
|
||||
typedef int32u calc_type;
|
||||
typedef agg::rendering_buffer::row_data row_data;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
pixfmt_alpha8(rendering_buffer& rb): m_rbuf(&rb)
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
unsigned width() const {
|
||||
return m_rbuf->width();
|
||||
}
|
||||
unsigned height() const {
|
||||
return m_rbuf->height();
|
||||
}
|
||||
|
||||
// This method should never be called when using the scanline_u8.
|
||||
// The use of scanline_p8 should be avoided because if does not works
|
||||
// properly for rendering fonts because single hspan are split in many
|
||||
// hline/hspan elements and pixel whitening happens.
|
||||
void blend_hline(int x, int y, unsigned len,
|
||||
const color_type& c, int8u cover)
|
||||
{ }
|
||||
|
||||
void copy_hline(int x, int y, unsigned len, const color_type& c)
|
||||
{
|
||||
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
|
||||
do
|
||||
{
|
||||
*p = c.a;
|
||||
p++;
|
||||
}
|
||||
while(--len);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
void blend_solid_hspan(int x, int y,
|
||||
unsigned len,
|
||||
const color_type& c,
|
||||
const int8u* covers)
|
||||
{
|
||||
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
|
||||
do
|
||||
{
|
||||
calc_type alpha = (calc_type(c.a) * (calc_type(*covers) + 1)) >> 8;
|
||||
*p = alpha;
|
||||
p++;
|
||||
++covers;
|
||||
}
|
||||
while(--len);
|
||||
}
|
||||
|
||||
private:
|
||||
rendering_buffer* m_rbuf;
|
||||
};
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue