Compare commits
603 Commits
Author | SHA1 | Date |
---|---|---|
Manos Pitsidianakis | 11a0586d56 | |
Manos Pitsidianakis | f70496f14c | |
Manos Pitsidianakis | 8a16cf6db4 | |
Manos Pitsidianakis | 11f3077b06 | |
Manos Pitsidianakis | dedee908d1 | |
Manos Pitsidianakis | 255e93764a | |
Manos Pitsidianakis | c5e9e67604 | |
Manos Pitsidianakis | ae96038fbf | |
Manos Pitsidianakis | 07072e2e3f | |
Manos Pitsidianakis | aa5737a004 | |
Manos Pitsidianakis | 48cb9ee204 | |
Guillaume Ranquet | c53a32de4c | |
Manos Pitsidianakis | a69c674c07 | |
Manos Pitsidianakis | 6a66afe93e | |
Manos Pitsidianakis | 974502c6ff | |
Manos Pitsidianakis | 3e9144657b | |
Manos Pitsidianakis | 35a9f33aab | |
Manos Pitsidianakis | 475609fe92 | |
Manos Pitsidianakis | 38bca8f8bc | |
Manos Pitsidianakis | ec01a4412a | |
Manos Pitsidianakis | 4e941a9e8b | |
Manos Pitsidianakis | 742f038f74 | |
Manos Pitsidianakis | 484712b0c3 | |
Manos Pitsidianakis | 264782d228 | |
Manos Pitsidianakis | 41e965b8a3 | |
Manos Pitsidianakis | f31b5c4000 | |
Manos Pitsidianakis | 8014af2563 | |
Manos Pitsidianakis | 4ce616aeca | |
Manos Pitsidianakis | a3aaec382a | |
Manos Pitsidianakis | b8b24282a0 | |
Manos Pitsidianakis | e481880321 | |
Geert Stappers | a88b8c5ea0 | |
Manos Pitsidianakis | b820bd6d9c | |
Manos Pitsidianakis | 3b93fa8e7c | |
Manos Pitsidianakis | 634bd1917a | |
Manos Pitsidianakis | b5fd3f57a7 | |
Manos Pitsidianakis | 1fcb1d59b8 | |
Manos Pitsidianakis | e2cdebe89c | |
Manos Pitsidianakis | 3884c0da1f | |
Manos Pitsidianakis | 26928e3ae9 | |
Manos Pitsidianakis | 070930e671 | |
Manos Pitsidianakis | c7aee72525 | |
Manos Pitsidianakis | 30a3205e4f | |
Manos Pitsidianakis | 9af284b8db | |
Manos Pitsidianakis | 62aee4644b | |
Manos Pitsidianakis | 5af2e1ee66 | |
Manos Pitsidianakis | 4e7b665672 | |
Manos Pitsidianakis | fd64fe0bf8 | |
Manos Pitsidianakis | 51e3f163d4 | |
Manos Pitsidianakis | 417b24cd84 | |
Manos Pitsidianakis | 873a67d0fb | |
Manos Pitsidianakis | c332c2f5ff | |
Manos Pitsidianakis | 1048ce6824 | |
Manos Pitsidianakis | 70fc2b455c | |
Manos Pitsidianakis | 8de8addd11 | |
Manos Pitsidianakis | 1fe3619208 | |
Manos Pitsidianakis | 0b468d88ad | |
Manos Pitsidianakis | 1eca34b398 | |
Manos Pitsidianakis | 5afc078587 | |
Guillaume Ranquet | a37d5fc1d1 | |
Manos Pitsidianakis | 60f26f9dae | |
Ethra | e80ea9c9de | |
Manos Pitsidianakis | 64e60cb0ee | |
Manos Pitsidianakis | 81d1c0536b | |
Manos Pitsidianakis | cd448924ed | |
Manos Pitsidianakis | 61a0c3c27f | |
Manos Pitsidianakis | 7952006870 | |
Manos Pitsidianakis | ddab3179c2 | |
Manos Pitsidianakis | 7861fb0402 | |
Manos Pitsidianakis | 148f0433d9 | |
Manos Pitsidianakis | 8185f2cf7d | |
Manos Pitsidianakis | 0270db0123 | |
Manos Pitsidianakis | 8ddd673dd8 | |
Manos Pitsidianakis | e3351d2755 | |
Manos Pitsidianakis | 31401fa35c | |
Manos Pitsidianakis | 33408146a1 | |
Manos Pitsidianakis | 8a95febb78 | |
Manos Pitsidianakis | 73d5b24e98 | |
Manos Pitsidianakis | 0da97dd8c1 | |
Manos Pitsidianakis | 933bf157ae | |
Manos Pitsidianakis | f685726eac | |
Manos Pitsidianakis | ab1b946fd9 | |
Manos Pitsidianakis | ce4ba06ce9 | |
Manos Pitsidianakis | bebb473d1b | |
Manos Pitsidianakis | f0866a3965 | |
Manos Pitsidianakis | f63774fa6d | |
Manos Pitsidianakis | 808aa4942d | |
Manos Pitsidianakis | 08518e1ca8 | |
Manos Pitsidianakis | 34a2d52e7e | |
Manos Pitsidianakis | 4026e25428 | |
Manos Pitsidianakis | ca7d7bb95d | |
Manos Pitsidianakis | ebe1b3da7e | |
Manos Pitsidianakis | 506ae9f594 | |
Manos Pitsidianakis | b6f769b2f4 | |
Manos Pitsidianakis | 3691cd2962 | |
Manos Pitsidianakis | 97eb636375 | |
Manos Pitsidianakis | b3079715f6 | |
Manos Pitsidianakis | 86bbf1ea57 | |
Manos Pitsidianakis | 1b0bdd0a9a | |
Manos Pitsidianakis | 7412c23870 | |
Manos Pitsidianakis | 500fe7f7e4 | |
Manos Pitsidianakis | 2419f4bd40 | |
Manos Pitsidianakis | 59c99fdc79 | |
Manos Pitsidianakis | 5f8d7c8039 | |
Manos Pitsidianakis | 876616d45b | |
Manos Pitsidianakis | c41f35fdd5 | |
Manos Pitsidianakis | 773254864b | |
Manos Pitsidianakis | e19f3e572c | |
Manos Pitsidianakis | 1617212c5b | |
Manos Pitsidianakis | 3ba1603af2 | |
Manos Pitsidianakis | 0a617410ec | |
Manos Pitsidianakis | 5ff4e8ae68 | |
Manos Pitsidianakis | c4344529e3 | |
Manos Pitsidianakis | f900dbea46 | |
Manos Pitsidianakis | f3e85738e7 | |
Manos Pitsidianakis | 3a70979483 | |
Manos Pitsidianakis | 24971d1960 | |
Manos Pitsidianakis | e37997d697 | |
Manos Pitsidianakis | 3adba40e32 | |
Manos Pitsidianakis | da251455a0 | |
Manos Pitsidianakis | d16afc7d8d | |
Manos Pitsidianakis | 7eedd86051 | |
Manos Pitsidianakis | c751b2e845 | |
Manos Pitsidianakis | 031d0f7dc7 | |
Manos Pitsidianakis | 2c6f180df9 | |
Manos Pitsidianakis | 63a63253d7 | |
Manos Pitsidianakis | 71f3ffe740 | |
Manos Pitsidianakis | 10c3b0eabe | |
Andrei Zisu | 64898a0583 | |
Andrei Zisu | 77a8d9e2c2 | |
Manos Pitsidianakis | ed8a5de2cb | |
Manos Pitsidianakis | b5cc2a095f | |
Manos Pitsidianakis | 5dd71ef1cd | |
Manos Pitsidianakis | 28fa66cc2a | |
Manos Pitsidianakis | 3b4acc15a5 | |
Manos Pitsidianakis | 23c15261e7 | |
Manos Pitsidianakis | 62b8465f2c | |
Manos Pitsidianakis | 1c1be7d6c9 | |
Manos Pitsidianakis | ccf6f9a26e | |
Manos Pitsidianakis | 3495ffd61b | |
Manos Pitsidianakis | 458258e1aa | |
Manos Pitsidianakis | d018f07aa5 | |
Manos Pitsidianakis | 0114e69542 | |
Manos Pitsidianakis | 0a74c7d0e5 | |
Manos Pitsidianakis | 54d21f25fd | |
Manos Pitsidianakis | ba7a97e90b | |
Manos Pitsidianakis | bcec745c24 | |
Manos Pitsidianakis | b61fc3ab64 | |
Manos Pitsidianakis | c2ae19d120 | |
Manos Pitsidianakis | 84f3641ec1 | |
Manos Pitsidianakis | 0e3a0c4b70 | |
Manos Pitsidianakis | 7645ff1b87 | |
Manos Pitsidianakis | e0adcdfe15 | |
Manos Pitsidianakis | ab14f81900 | |
Manos Pitsidianakis | cd2ba80f8e | |
Manos Pitsidianakis | c1c41c9126 | |
Manos Pitsidianakis | a1cbb1988b | |
Manos Pitsidianakis | 470cae6b88 | |
Manos Pitsidianakis | 23507932f9 | |
Manos Pitsidianakis | 0500e451da | |
Manos Pitsidianakis | 6506fffb94 | |
Manos Pitsidianakis | f81a1e2338 | |
Manos Pitsidianakis | ef30228e08 | |
Manos Pitsidianakis | 111a1160ad | |
Manos Pitsidianakis | bfc78a0803 | |
Manos Pitsidianakis | 7387b67eee | |
Manos Pitsidianakis | af241d25cb | |
Manos Pitsidianakis | fa33a9468a | |
Manos Pitsidianakis | 2db021fa0a | |
Manos Pitsidianakis | 43bfd4131d | |
Manos Pitsidianakis | ac2a5dcdd1 | |
Manos Pitsidianakis | 688e39a67e | |
Manos Pitsidianakis | 0e60bdf26e | |
Manos Pitsidianakis | 8a21be2177 | |
Manos Pitsidianakis | 606f487fc5 | |
Manos Pitsidianakis | 0f3b529459 | |
Manos Pitsidianakis | 5a7919bb03 | |
Manos Pitsidianakis | f702dc220c | |
Andrei Zisu | e95c275d68 | |
Andrei Zisu | 3105a0373b | |
Andrei Zisu | 7aec5b8e78 | |
Manos Pitsidianakis | e1b55340fa | |
Manos Pitsidianakis | d3cbf184e6 | |
Manos Pitsidianakis | e88957ae6e | |
Manos Pitsidianakis | 3d85ca2edf | |
Manos Pitsidianakis | 7888d8b2a5 | |
Manos Pitsidianakis | eb5d49c41a | |
Manos Pitsidianakis | 714744366f | |
Manos Pitsidianakis | 73b3ed559d | |
Manos Pitsidianakis | 22525d40fb | |
Manos Pitsidianakis | 7eed82783a | |
Manos Pitsidianakis | 3944e4e60e | |
Manos Pitsidianakis | fe0a96f085 | |
Manos Pitsidianakis | 81974311c2 | |
Manos Pitsidianakis | 64ba0459ee | |
Manos Pitsidianakis | 39e99770da | |
Manos Pitsidianakis | a4f0dbac26 | |
Manos Pitsidianakis | 7e4ed2fa10 | |
Manos Pitsidianakis | 45d4f611b1 | |
Manos Pitsidianakis | 747e39bf55 | |
Manos Pitsidianakis | 9b9c38f769 | |
Manos Pitsidianakis | bb4d200036 | |
Manos Pitsidianakis | 63abf1e890 | |
Manos Pitsidianakis | 7e3e938631 | |
Manos Pitsidianakis | c43aeb0eb1 | |
Manos Pitsidianakis | 54862f8651 | |
Manos Pitsidianakis | b673af02ac | |
Manos Pitsidianakis | dd4d0b7972 | |
Manos Pitsidianakis | 6476985ce6 | |
Manos Pitsidianakis | 6d5ebb5b04 | |
Manos Pitsidianakis | f0075b86cf | |
Manos Pitsidianakis | a615b4701b | |
Manos Pitsidianakis | 0a9c89b6b3 | |
Manos Pitsidianakis | 49c36009ce | |
Manos Pitsidianakis | c7825c76c3 | |
Manos Pitsidianakis | 3344a8dbf6 | |
Manos Pitsidianakis | 1b3bebe304 | |
Manos Pitsidianakis | 85af524458 | |
Manos Pitsidianakis | 0132677ff5 | |
Manos Pitsidianakis | 2dc2940586 | |
Manos Pitsidianakis | 49a38a23bf | |
Manos Pitsidianakis | b4f2f33576 | |
Manos Pitsidianakis | a337e2269e | |
Manos Pitsidianakis | 46636d8748 | |
Manos Pitsidianakis | 65e82d8896 | |
Manos Pitsidianakis | 1c79786ea2 | |
Manos Pitsidianakis | 290cfb86c0 | |
Damian Poddebniak | 5459a84f3d | |
Manos Pitsidianakis | 31aa9ad29e | |
Manos Pitsidianakis | 59513b2670 | |
Manos Pitsidianakis | 38bc1369cc | |
Manos Pitsidianakis | 5d8f07c805 | |
Manos Pitsidianakis | b95f778335 | |
Manos Pitsidianakis | 29fd8522e6 | |
Manos Pitsidianakis | 31982931f5 | |
Manos Pitsidianakis | d9467d5fcd | |
Manos Pitsidianakis | 11432ba2c3 | |
Manos Pitsidianakis | 4f9b97736a | |
Manos Pitsidianakis | 6ebdc7f9ae | |
Manos Pitsidianakis | 37a787e6bb | |
Manos Pitsidianakis | c875dda496 | |
Manos Pitsidianakis | f7a4741bf1 | |
Manos Pitsidianakis | 3433f7c41e | |
Manos Pitsidianakis | 9037f08495 | |
Manos Pitsidianakis | ffba203a3b | |
Manos Pitsidianakis | 8551e1ba0b | |
Manos Pitsidianakis | 64982b4cab | |
Manos Pitsidianakis | 4d22b669bf | |
Manos Pitsidianakis | 974b3a5305 | |
Manos Pitsidianakis | f162239fcc | |
Manos Pitsidianakis | 946309c6f3 | |
Manos Pitsidianakis | 66c21ab173 | |
Manos Pitsidianakis | 3963103d55 | |
Manos Pitsidianakis | ab57e9420d | |
Manos Pitsidianakis | 095d24f914 | |
Manos Pitsidianakis | 96f0b3e6b4 | |
Manos Pitsidianakis | f193bdf685 | |
Manos Pitsidianakis | f93adb683a | |
Manos Pitsidianakis | a1e7006186 | |
Manos Pitsidianakis | 52874f9a97 | |
Manos Pitsidianakis | b3858de2f4 | |
Manos Pitsidianakis | dc2b00442b | |
Manos Pitsidianakis | da8e810448 | |
Damian Poddebniak | 4f6081b663 | |
Manos Pitsidianakis | 67d2da0f88 | |
Manos Pitsidianakis | df638cceec | |
Manos Pitsidianakis | 97d3686815 | |
kdwarn | 6578a56668 | |
Manos Pitsidianakis | 0f60009ea9 | |
Manos Pitsidianakis | 5c2b04719b | |
Manos Pitsidianakis | 7c9a4b4b7c | |
Manos Pitsidianakis | 64ab65ddff | |
Manos Pitsidianakis | 005bf3881e | |
Manos Pitsidianakis | a5446975c2 | |
Manos Pitsidianakis | 7c7f6e1923 | |
Manos Pitsidianakis | 84081f4ed7 | |
Manos Pitsidianakis | bf543855dc | |
Manos Pitsidianakis | 448e0635e0 | |
Manos Pitsidianakis | 4e654d2d02 | |
Manos Pitsidianakis | 40d4ecefa0 | |
Manos Pitsidianakis | 0ee1b6e018 | |
Manos Pitsidianakis | 8cb2a515e1 | |
Manos Pitsidianakis | 6e27edcb77 | |
Manos Pitsidianakis | ae25ffba43 | |
Manos Pitsidianakis | 1e084c1d85 | |
Manos Pitsidianakis | 9216e7bc65 | |
Manos Pitsidianakis | 8ecdb6df31 | |
Manos Pitsidianakis | b65934facc | |
Manos Pitsidianakis | 89c90f224a | |
Manos Pitsidianakis | 7db930cabd | |
Manos Pitsidianakis | e9f09a153c | |
Manos Pitsidianakis | fe7dcc508e | |
Manos Pitsidianakis | fe027fa300 | |
Manos Pitsidianakis | 129f10911b | |
Manos Pitsidianakis | 51e9fbe8f2 | |
Manos Pitsidianakis | 4874e30f3c | |
Manos Pitsidianakis | 073d43b9b8 | |
Manos Pitsidianakis | 8e698cabcf | |
Manos Pitsidianakis | 1d0405ed5b | |
Manos Pitsidianakis | bb7e119ade | |
Manos Pitsidianakis | 29b43e2c88 | |
Manos Pitsidianakis | 2df7354751 | |
Manos Pitsidianakis | 6280bc75e5 | |
Manos Pitsidianakis | 48a10f7241 | |
Manos Pitsidianakis | 0219dc8707 | |
Manos Pitsidianakis | c4c245ee19 | |
Manos Pitsidianakis | 53cba4beee | |
Manos Pitsidianakis | 561ba9c87b | |
Manos Pitsidianakis | 8abc9358a7 | |
Manos Pitsidianakis | e9cd800f49 | |
Manos Pitsidianakis | 519257b08f | |
Manos Pitsidianakis | ab418c1d39 | |
Manos Pitsidianakis | cf9a04a591 | |
Manos Pitsidianakis | 369c1dbdac | |
Manos Pitsidianakis | 4e55fbc90d | |
Manos Pitsidianakis | 5ceddf412e | |
Manos Pitsidianakis | 13fe64a027 | |
Manos Pitsidianakis | 6086a3789d | |
Manos Pitsidianakis | 5b5869a2ec | |
Manos Pitsidianakis | 866166eb8e | |
Manos Pitsidianakis | d4e605c098 | |
Manos Pitsidianakis | a5770c89f4 | |
Manos Pitsidianakis | 74e15316db | |
Manos Pitsidianakis | d93ee413a7 | |
Manos Pitsidianakis | c2ed3e283f | |
Manos Pitsidianakis | b0e867eb68 | |
Manos Pitsidianakis | b5657201db | |
rek2 | 3803d788ab | |
rek2 | b5f205b77b | |
Damian Poddebniak | 7c33f8999b | |
Damian Poddebniak | 34a54d3c05 | |
Damian Poddebniak | 9d51b6bd52 | |
Manos Pitsidianakis | 7998e1e77e | |
Manos Pitsidianakis | 957abf4e72 | |
Manos Pitsidianakis | e3dfeaad7e | |
Manos Pitsidianakis | 619fbef129 | |
Manos Pitsidianakis | 342df091a0 | |
Manos Pitsidianakis | 1bcc0bbece | |
Manos Pitsidianakis | e8e49e741b | |
Manos Pitsidianakis | 1dc1d86848 | |
Manos Pitsidianakis | ba7f5dce1c | |
Manos Pitsidianakis | 0b258a1f05 | |
Manos Pitsidianakis | 5f29faa640 | |
Manos Pitsidianakis | 6858ee1fab | |
Manos Pitsidianakis | f98e36cee5 | |
Manos Pitsidianakis | f0d88005fb | |
Manos Pitsidianakis | e64923eeaa | |
Manos Pitsidianakis | 65179d4816 | |
Manos Pitsidianakis | 0c0a678cff | |
Manos Pitsidianakis | f5cfbd32e6 | |
Manos Pitsidianakis | 363f493099 | |
Manos Pitsidianakis | 8cab9d9da8 | |
Manos Pitsidianakis | b05d929975 | |
Manos Pitsidianakis | 5699baecfb | |
Manos Pitsidianakis | 02e86d1fad | |
Manos Pitsidianakis | fdc0861ac0 | |
Manos Pitsidianakis | 45bac6eb16 | |
Manos Pitsidianakis | 575509f1ed | |
Manos Pitsidianakis | 5c9b3fb044 | |
Manos Pitsidianakis | 155fb41b93 | |
Manos Pitsidianakis | 96537e48c5 | |
Manos Pitsidianakis | 4da5366959 | |
Manos Pitsidianakis | fd0faade06 | |
Manos Pitsidianakis | 8f14a2373e | |
Damian Poddebniak | 330887c4f5 | |
Damian Poddebniak | 6c6d9f4b4e | |
Damian Poddebniak | 579372b4a7 | |
Manos Pitsidianakis | b6c93e49f2 | |
Manos Pitsidianakis | d33f9d54c7 | |
Manos Pitsidianakis | cd85d83324 | |
Manos Pitsidianakis | d7e6b40b7e | |
Manos Pitsidianakis | e0257c9d8d | |
Manos Pitsidianakis | 27a4dcb916 | |
Manos Pitsidianakis | bf615e7d93 | |
Manos Pitsidianakis | b92a80a23a | |
Manos Pitsidianakis | f8623d4b2c | |
Manos Pitsidianakis | 299c8e0f99 | |
Manos Pitsidianakis | c5ecaceae1 | |
Manos Pitsidianakis | 6bf1756de8 | |
Manos Pitsidianakis | 23d95973d4 | |
Manos Pitsidianakis | 6388bea9a0 | |
Manos Pitsidianakis | f537c24909 | |
Guillaume Ranquet | daf42fd456 | |
Manos Pitsidianakis | 58889bcadd | |
Manos Pitsidianakis | d332e4578d | |
Manos Pitsidianakis | 954329d848 | |
Manos Pitsidianakis | aebff3d3d9 | |
Manos Pitsidianakis | 235fceaf21 | |
Damian Poddebniak | 1eea8bab77 | |
Damian Poddebniak | 30866f752b | |
Manos Pitsidianakis | 1f8ac2287b | |
Manos Pitsidianakis | c9d26bb415 | |
Manos Pitsidianakis | cc27639fca | |
Damian Poddebniak | f63f6445ad | |
Damian Poddebniak | 682ea5547e | |
Manos Pitsidianakis | 24103f3310 | |
Manos Pitsidianakis | 91557c2c43 | |
Manos Pitsidianakis | 428f752b20 | |
Manos Pitsidianakis | 77020e0c19 | |
Manos Pitsidianakis | 8c671935f9 | |
Manos Pitsidianakis | 1f1ea30769 | |
Manos Pitsidianakis | 85d4316a6a | |
Manos Pitsidianakis | 30cc5d3d02 | |
Manos Pitsidianakis | b1a7188771 | |
Manos Pitsidianakis | 3a02b6fb80 | |
3nt3 | 34bb532e8d | |
Manos Pitsidianakis | 47e6d5d935 | |
Manos Pitsidianakis | 39d9c2af3b | |
Manos Pitsidianakis | d679a74450 | |
Manos Pitsidianakis | d9c07def0f | |
Manos Pitsidianakis | 939dc15e28 | |
Manos Pitsidianakis | 3adf72aed0 | |
Johannes Schilling | 2447a2cbfe | |
Manos Pitsidianakis | d7ec97f03b | |
Johannes Schilling | fbc1007ff4 | |
cos | 256a3e252e | |
Manos Pitsidianakis | 3a10953f05 | |
Manos Pitsidianakis | 11140b4a76 | |
cos | 671ce9f694 | |
Johannes Schilling | 12cb717bda | |
Manos Pitsidianakis | f9ac9b607a | |
Manos Pitsidianakis | 660bacb926 | |
Manos Pitsidianakis | de2f46fe61 | |
Manos Pitsidianakis | 5443b7e8f3 | |
Manos Pitsidianakis | 3c847ad26a | |
Manos Pitsidianakis | 2878bbb8c8 | |
Manos Pitsidianakis | 40c6647db8 | |
Manos Pitsidianakis | f63ce388f7 | |
Manos Pitsidianakis | c06c3f5893 | |
Manos Pitsidianakis | abc56eae43 | |
Manos Pitsidianakis | 7606317f24 | |
Manos Pitsidianakis | 4f45b10974 | |
Manos Pitsidianakis | 5634f95553 | |
Manos Pitsidianakis | 259aeb0087 | |
Manos Pitsidianakis | 7382e30160 | |
Manos Pitsidianakis | 2427b097c5 | |
Manos Pitsidianakis | 252d2bdf2f | |
Manos Pitsidianakis | eaecc5ea12 | |
Manos Pitsidianakis | 4b96bd591f | |
Manos Pitsidianakis | b9030a684c | |
Manos Pitsidianakis | 2224a7100f | |
Manos Pitsidianakis | 7924aa8bfe | |
Manos Pitsidianakis | 7af893597f | |
Manos Pitsidianakis | 7d9cabb023 | |
Manos Pitsidianakis | ee9d458b05 | |
Manos Pitsidianakis | 5ba7b2cd7b | |
Manos Pitsidianakis | 104352e595 | |
Manos Pitsidianakis | bd22f986f0 | |
_ | ded9adde61 | |
Manos Pitsidianakis | 6317984136 | |
Manos Pitsidianakis | db227dea34 | |
Manos Pitsidianakis | 282af86e83 | |
Manos Pitsidianakis | cc439b239a | |
Manos Pitsidianakis | b776409d6c | |
Manos Pitsidianakis | 56fc43bcf8 | |
spike | 59b95f83d2 | |
Manos Pitsidianakis | 88a1f0d4bc | |
Manos Pitsidianakis | 64346dd3fe | |
Manos Pitsidianakis | 17b42b1a6c | |
Manos Pitsidianakis | 6d20abdde7 | |
Manos Pitsidianakis | 803d3414fd | |
Manos Pitsidianakis | 3697b7d960 | |
Manos Pitsidianakis | dd0baa82e9 | |
Manos Pitsidianakis | 0ef4dde939 | |
Manos Pitsidianakis | 55ed962425 | |
Manos Pitsidianakis | 46a038dc68 | |
Manos Pitsidianakis | 16646976d7 | |
Manos Pitsidianakis | ffb12c6d1a | |
Manos Pitsidianakis | 7e09b1807f | |
Manos Pitsidianakis | 129573e0fd | |
Manos Pitsidianakis | 0c08cb737c | |
Manos Pitsidianakis | 117d7fbe04 | |
Manos Pitsidianakis | 347be54305 | |
Manos Pitsidianakis | 7935e49a00 | |
Manos Pitsidianakis | c54a31f7cc | |
Manos Pitsidianakis | c3fdafde3b | |
Manos Pitsidianakis | c6bdda03cf | |
Manos Pitsidianakis | e450ad0f9c | |
Manos Pitsidianakis | 0ed10711ef | |
Manos Pitsidianakis | d8d43a16fe | |
Manos Pitsidianakis | b87d54ea3f | |
Manos Pitsidianakis | a7a50d3078 | |
Manos Pitsidianakis | b138d9bc61 | |
Manos Pitsidianakis | 787c64c2da | |
Manos Pitsidianakis | 0df46a63ec | |
Manos Pitsidianakis | 94bd84b45d | |
Manos Pitsidianakis | 388d4e35d6 | |
Manos Pitsidianakis | 9cbbf71e0f | |
Manos Pitsidianakis | 3688369278 | |
Manos Pitsidianakis | 3c0f5d8274 | |
Manos Pitsidianakis | a72c96a26a | |
Manos Pitsidianakis | 8c7b001aa5 | |
Manos Pitsidianakis | 9dc4d4055c | |
Manos Pitsidianakis | 3d92b41075 | |
Manos Pitsidianakis | 7c7115427d | |
Manos Pitsidianakis | 5fa4b6260c | |
Manos Pitsidianakis | 4a20fc42e1 | |
Manos Pitsidianakis | f76f4ea3f7 | |
Manos Pitsidianakis | 2de69d17f1 | |
Manos Pitsidianakis | cbe593cf31 | |
Manos Pitsidianakis | a484b397c6 | |
Manos Pitsidianakis | eb5949dc9b | |
Manos Pitsidianakis | aa99b0d787 | |
Manos Pitsidianakis | da9c80ccfd | |
Manos Pitsidianakis | a73885acb1 | |
Manos Pitsidianakis | 480000ebbb | |
Manos Pitsidianakis | 29042aba59 | |
Manos Pitsidianakis | a42a6ca868 | |
Manos Pitsidianakis | bde87af387 | |
Manos Pitsidianakis | 10497952f7 | |
Manos Pitsidianakis | 0c0bee4482 | |
Manos Pitsidianakis | ca48896865 | |
Manos Pitsidianakis | 7650805c60 | |
Manos Pitsidianakis | e29041f733 | |
Manos Pitsidianakis | f4e0970d46 | |
Manos Pitsidianakis | 9cb66ef818 | |
Guillaume Ranquet | d921b3c320 | |
Guillaume Ranquet | 9205f3b8af | |
Guillaume Ranquet | 97ff3e787f | |
Manos Pitsidianakis | 824f614a69 | |
Manos Pitsidianakis | ed3dbc8586 | |
Manos Pitsidianakis | 7fca5f01ef | |
Manos Pitsidianakis | b716e4383e | |
Manos Pitsidianakis | 4a79b2021d | |
Manos Pitsidianakis | daa900ec9a | |
Manos Pitsidianakis | ca84906d7d | |
Manos Pitsidianakis | ce269c64e1 | |
Manos Pitsidianakis | 0f6f3e30c6 | |
Manos Pitsidianakis | e6d6e1f588 | |
Manos Pitsidianakis | dc5afa13db | |
Manos Pitsidianakis | d6355a3043 | |
Manos Pitsidianakis | 6a843d4983 | |
Manos Pitsidianakis | 9558b2ae92 | |
Manos Pitsidianakis | 4fdc90b31e | |
Manos Pitsidianakis | 8563bccd1b | |
Manos Pitsidianakis | 721891c295 | |
Manos Pitsidianakis | 2c23ca34cd | |
Manos Pitsidianakis | 2eb22a290a | |
Manos Pitsidianakis | 5823178cc2 | |
Manos Pitsidianakis | 81184b182c | |
Manos Pitsidianakis | aa3524dd30 | |
Manos Pitsidianakis | 23c2355662 | |
Manos Pitsidianakis | d3e62e3d74 | |
Manos Pitsidianakis | a866b29499 | |
Manos Pitsidianakis | f5dc25ae0d | |
Manos Pitsidianakis | d0de04854e | |
Manos Pitsidianakis | 340d6451a3 | |
Manos Pitsidianakis | e9aaa7b067 | |
Manos Pitsidianakis | d4b690d5d3 | |
Manos Pitsidianakis | ce2068d36b | |
Manos Pitsidianakis | 0d8bedd2d5 | |
Manos Pitsidianakis | 81d1265601 | |
Manos Pitsidianakis | d8e9a00563 | |
Geoff Beier | 330a2b20ed | |
Manos Pitsidianakis | 36e29cb6fd | |
Manos Pitsidianakis | 5f003a31be | |
Manos Pitsidianakis | 2580522931 | |
Manos Pitsidianakis | 15ca25af73 | |
Manos Pitsidianakis | 37d0846195 | |
Manos Pitsidianakis | ffc498a5d0 | |
Manos Pitsidianakis | d25eb00a11 | |
Manos Pitsidianakis | 240374950a | |
Manos Pitsidianakis | 505adca54d | |
Manos Pitsidianakis | e090c31f96 | |
Manos Pitsidianakis | 20feb50475 | |
Manos Pitsidianakis | f975e1004c | |
Manos Pitsidianakis | b88c3c573d | |
Manos Pitsidianakis | 32901f57d2 | |
Manos Pitsidianakis | d1712557cb | |
Manos Pitsidianakis | a977351f0a | |
Manos Pitsidianakis | e7b9d2963c | |
Manos Pitsidianakis | 25579d8807 | |
Manos Pitsidianakis | 22fb2ed46c | |
Manos Pitsidianakis | 733de5a5fb | |
Manos Pitsidianakis | 592339bdca | |
Manos Pitsidianakis | ae8c2addab | |
Manos Pitsidianakis | bc08bf1d13 | |
Manos Pitsidianakis | 7533df86e0 | |
Manos Pitsidianakis | 526a246430 | |
Alex.F | 69916f267b | |
Manos Pitsidianakis | 13c5798c7b | |
Manos Pitsidianakis | 07e166e1fb | |
Manos Pitsidianakis | 72a2ba20dc | |
Manos Pitsidianakis | c8da6d2049 | |
Manos Pitsidianakis | 90042379a6 | |
Manos Pitsidianakis | f40ae9e11b | |
Manos Pitsidianakis | 09f3edba76 | |
Manos Pitsidianakis | 09dc0a2409 | |
Manos Pitsidianakis | 3bc187c570 | |
Manos Pitsidianakis | 05393d8caa | |
Manos Pitsidianakis | b49d965695 | |
Manos Pitsidianakis | 6235164df2 | |
Manos Pitsidianakis | 521f634e7b | |
Manos Pitsidianakis | 978939d8e3 | |
Manos Pitsidianakis | d1437ff275 | |
Manos Pitsidianakis | f097593bed | |
Ludovic LANGE | b08570349d | |
Manos Pitsidianakis | d6bf700175 | |
Manos Pitsidianakis | 5a9f63c51e | |
Manos Pitsidianakis | 9d7e877510 | |
Manos Pitsidianakis | ad2a10478e | |
Manos Pitsidianakis | 64b62352d0 | |
Manos Pitsidianakis | b411daddaa |
|
@ -0,0 +1,2 @@
|
|||
[env]
|
||||
PCRE2_SYS_STATIC = "1"
|
|
@ -0,0 +1,2 @@
|
|||
# Use cargo-derivefmt to sort derives alphabetically
|
||||
f900dbea468e822c5a510a72ecc6367549443927
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
|
||||
name: "Pull Request"
|
||||
about: "Standard pull request template."
|
||||
title: "WIP: "
|
||||
ref: "master"
|
||||
|
||||
---
|
||||
|
||||
<!-- If your PR is ready to merge/review, remove the `WIP: ` prefix from the title. -->
|
||||
|
||||
### Summary of the PR
|
||||
|
||||
<!-- Changes introduced in this PR. -->
|
||||
|
||||
### Requirements
|
||||
|
||||
Before submitting your PR, please make sure you have addressed the following requirements:
|
||||
|
||||
* [ ] All commits in this PR are signed (with `git commit -s`), and the commit has a message describing the motivation behind the change, if appropriate.
|
||||
* [ ] All added/changed public-facing functionality, especially configuration options, are documented in the manual pages.
|
||||
* [ ] Any newly added `unsafe` code is properly documented.
|
||||
* [ ] Each commit has been formatted with `rustfmt`. Run `make fmt` in the project root.
|
||||
* [ ] Each commit has been linted with `clippy`. Run `make lint` in the project root.
|
||||
* [ ] Each commit does not break any test. Run `make test` in the project root. If you have `cargo-nextest` installed, you can run `cargo nextest run --all --no-fail-fast --all-features --future-incompat-report` instead.
|
|
@ -0,0 +1,65 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Build .deb package
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Package for debian on ${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
arch: amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
artifact_name: 'linux-amd64'
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y mandoc debhelper quilt build-essential
|
||||
- id: rustup-setup
|
||||
name: Install rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal ${{ matrix.rust }} --target ${{ matrix.target }}
|
||||
rustup default ${{ matrix.rust }}
|
||||
fi
|
||||
- name: Build binary
|
||||
run: |
|
||||
VERSION=$(grep -m1 version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1)
|
||||
make deb-dist
|
||||
mkdir artifacts
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
mv ../meli_*.deb artifacts/meli-${VERSION}-${{ matrix.artifact_name }}.deb
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: meli-${{env.VERSION}}-${{ matrix.artifact_name }}.deb
|
||||
path: artifacts/meli-${{env.VERSION}}-${{ matrix.artifact_name }}.deb
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
|
@ -0,0 +1,90 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Build release binary
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build on ${{ matrix.build }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
artifact_name: 'meli-linux-amd64'
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y libdbus-1-dev pkg-config mandoc libssl-dev
|
||||
#- id: cache-rustup
|
||||
# name: Cache Rust toolchain
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: ~/.rustup
|
||||
# key: toolchain-${{ matrix.os }}-${{ matrix.rust }}
|
||||
#- if: ${{ steps.cache-rustup.outputs.cache-hit != 'true' }}
|
||||
- id: rustup-setup
|
||||
name: Install rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
rustup toolchain install --profile minimal ${{ matrix.rust }} --target ${{ matrix.target }}
|
||||
fi
|
||||
- name: Configure cargo data directory
|
||||
# After this point, all cargo registry and crate data is stored in
|
||||
# $GITHUB_WORKSPACE/.cargo_home. This allows us to cache only the files
|
||||
# that are needed during the build process. Additionally, this works
|
||||
# around a bug in the 'cache' action that causes directories outside of
|
||||
# the workspace dir to be saved/restored incorrectly.
|
||||
run: echo "CARGO_HOME=$(pwd)/.cargo_home" >> $GITHUB_ENV
|
||||
#- id: cache-cargo
|
||||
# name: Cache cargo configuration and installations
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: ${{ env.CARGO_HOME }}
|
||||
# key: cargo-${{ matrix.os }}-${{ matrix.rust }}
|
||||
#- if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} && matrix.target
|
||||
- name: Setup Rust target
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- name: Build binary
|
||||
run: |
|
||||
make
|
||||
mkdir artifacts
|
||||
mv target/*/release/* target/ || true
|
||||
mv target/release/* target/ || true
|
||||
mv target/meli artifacts/
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: artifacts/meli
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
|
@ -0,0 +1,111 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Run cargo lints
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.gitea/**'
|
||||
- 'melib/src/**'
|
||||
- 'melib/Cargo.toml'
|
||||
- 'meli/src/**'
|
||||
- 'meli/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint on ${{ matrix.build }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y libdbus-1-dev pkg-config mandoc libssl-dev
|
||||
#- id: cache-rustup
|
||||
# name: Cache Rust toolchain
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: ~/.rustup
|
||||
# key: toolchain-${{ matrix.os }}-${{ matrix.rust }}
|
||||
#- if: ${{ steps.cache-rustup.outputs.cache-hit != 'true' }}
|
||||
- id: rustup-setup
|
||||
name: Install Rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
rustup toolchain install --profile minimal --component clippy,rustfmt --target ${{ matrix.target }} -- "${{ matrix.rust }}"
|
||||
rustup default ${{ matrix.rust }}
|
||||
fi
|
||||
- name: Configure cargo data directory
|
||||
# After this point, all cargo registry and crate data is stored in
|
||||
# $GITHUB_WORKSPACE/.cargo_home. This allows us to cache only the files
|
||||
# that are needed during the build process. Additionally, this works
|
||||
# around a bug in the 'cache' action that causes directories outside of
|
||||
# the workspace dir to be saved/restored incorrectly.
|
||||
run: echo "CARGO_HOME=$(pwd)/.cargo_home" >> $GITHUB_ENV
|
||||
#- id: cache-cargo
|
||||
# name: Cache cargo configuration and installations
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: ${{ env.CARGO_HOME }}
|
||||
# key: cargo-${{ matrix.os }}-${{ matrix.rust }}
|
||||
#- if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} && matrix.target
|
||||
- name: Setup Rust target
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} && matrix.target
|
||||
name: Add lint dependencies
|
||||
run: |
|
||||
cargo install --quiet --version 1.0.9 --target "${{ matrix.target }}" cargo-sort
|
||||
RUSTFLAGS="" cargo install --locked --target "${{ matrix.target }}" --git https://github.com/dcchut/cargo-derivefmt --rev 2ff93de7fb418180458dd1ba27e5655607c23ab6 --bin cargo-derivefmt
|
||||
- name: rustfmt
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo fmt --check --all
|
||||
- name: clippy
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo clippy --no-deps --all-features --all --tests --examples --benches --bins
|
||||
- name: cargo-derivefmt melib
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo derivefmt --manifest-path ./melib/Cargo.toml
|
||||
- name: cargo-derivefmt meli
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo derivefmt --manifest-path ./meli/Cargo.toml
|
||||
- name: cargo-derivefmt fuzz
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo derivefmt --manifest-path ./fuzz/Cargo.toml
|
||||
- name: cargo-derivefmt tools
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo derivefmt --manifest-path ./tools/Cargo.toml
|
|
@ -0,0 +1,91 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Cargo manifest lints
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.gitea/**'
|
||||
- 'melib/Cargo.toml'
|
||||
- 'meli/Cargo.toml'
|
||||
- 'fuzz/Cargo.toml'
|
||||
- 'tool/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- '.cargo/config.toml'
|
||||
|
||||
jobs:
|
||||
manifest_lint:
|
||||
name: Lint Cargo manifests on ${{ matrix.build }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y mandoc
|
||||
- name: Find meli MSRV from meli/Cargo.toml.
|
||||
run: echo MELI_MSRV=$(grep -m1 rust-version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1) >> $GITHUB_ENV
|
||||
- id: rustup-setup
|
||||
name: Install Rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal --component "rustfmt" --target "${{ matrix.target }}" -- "${{ env.MELI_MSRV }}"
|
||||
rustup component add rustfmt --toolchain ${{ env.MELI_MSRV }}-${{ matrix.target }}
|
||||
rustup toolchain install --profile minimal --component "rustfmt" --target "${{ matrix.target }}" -- "${{ matrix.rust }}"
|
||||
rustup component add rustfmt --toolchain ${{ matrix.rust }}-${{ matrix.target }}
|
||||
rustup default ${{ matrix.rust }}
|
||||
fi
|
||||
- name: Setup Rust target
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} && matrix.target
|
||||
name: Add manifest lint dependencies
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
cargo install --quiet --version 1.0.9 --target "${{ matrix.target }}" cargo-sort
|
||||
cargo install --quiet --version 0.15.1 --target "${{ matrix.target }}" cargo-msrv
|
||||
- name: cargo-msrv verify melib MSRV
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
cargo-msrv --output-format json --log-level trace --log-target stdout --path meli verify
|
||||
cargo-msrv --output-format json --log-level trace --log-target stdout --path melib verify
|
||||
- name: cargo-sort
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
cargo-sort --check --check-format --grouped --order package,bin,lib,dependencies,features,build-dependencies,dev-dependencies,workspace fuzz
|
||||
cargo-sort --check --check-format --grouped --order package,bin,lib,dependencies,features,build-dependencies,dev-dependencies,workspace tools
|
||||
cargo-sort --check --check-format --grouped --order package,bin,lib,dependencies,features,build-dependencies,dev-dependencies,workspace --workspace
|
||||
- name: Check debian/changelog is up-to-date.
|
||||
if: success() || failure()
|
||||
run: |
|
||||
./scripts/check_debian_changelog.sh
|
|
@ -0,0 +1,103 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Run Tests
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.gitea/**'
|
||||
- 'melib/src/**'
|
||||
- 'melib/Cargo.toml'
|
||||
- 'meli/src/**'
|
||||
- 'meli/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on ${{ matrix.build }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y libdbus-1-dev pkg-config mandoc libssl-dev make
|
||||
#- id: cache-rustup
|
||||
# name: Cache Rust toolchain
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: ~/.rustup
|
||||
# key: toolchain-${{ matrix.os }}-${{ matrix.rust }}
|
||||
#- if: ${{ steps.cache-rustup.outputs.cache-hit != 'true' }}
|
||||
- id: rustup-setup
|
||||
name: Install rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
rustup toolchain install --profile minimal ${{ matrix.rust }} --target ${{ matrix.target }}
|
||||
fi
|
||||
- name: Configure cargo data directory
|
||||
# After this point, all cargo registry and crate data is stored in
|
||||
# $GITHUB_WORKSPACE/.cargo_home. This allows us to cache only the files
|
||||
# that are needed during the build process. Additionally, this works
|
||||
# around a bug in the 'cache' action that causes directories outside of
|
||||
# the workspace dir to be saved/restored incorrectly.
|
||||
run: echo "CARGO_HOME=$(pwd)/.cargo_home" >> $GITHUB_ENV
|
||||
#- id: cache-cargo
|
||||
# name: Cache cargo configuration and installations
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: ${{ env.CARGO_HOME }}
|
||||
# key: cargo-${{ matrix.os }}-${{ matrix.rust }}
|
||||
#- if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} && matrix.target
|
||||
- name: Setup Rust target
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} && matrix.target
|
||||
name: Add test dependencies
|
||||
run: |
|
||||
cargo install --quiet --version 0.9.54 --target "${{ matrix.target }}" cargo-nextest
|
||||
- name: cargo-check
|
||||
run: |
|
||||
cargo check --all-features --all --tests --examples --benches --bins
|
||||
- name: Compile
|
||||
if: success() || failure()
|
||||
run: cargo test --all --no-fail-fast --all-features --no-run --locked
|
||||
- name: cargo test
|
||||
run: |
|
||||
cargo nextest run --all --no-fail-fast --all-features --future-incompat-report -E 'not (test(smtp::test::test_smtp))'
|
||||
#cargo test --all --no-fail-fast --all-features -- --nocapture --quiet
|
||||
- name: rustdoc build
|
||||
if: success() || failure() # always run even if other steps fail, except when cancelled <https://stackoverflow.com/questions/58858429/how-to-run-a-github-actions-step-even-if-the-previous-step-fails-while-still-f>
|
||||
run: |
|
||||
make build-rustdoc
|
||||
- name: rustdoc tests
|
||||
if: success() || failure()
|
||||
run: |
|
||||
make test-docs
|
|
@ -15,3 +15,6 @@ debian/debhelper-build-stamp
|
|||
debian/files
|
||||
debian/meli.substvars
|
||||
debian/meli/
|
||||
|
||||
# CLion IDE
|
||||
.idea
|
|
@ -0,0 +1,76 @@
|
|||
# Build `meli`
|
||||
|
||||
For a quick start, build and install locally:
|
||||
|
||||
```sh
|
||||
PREFIX=~/.local make install
|
||||
```
|
||||
|
||||
Available subcommands for `make` are listed with `make help`.
|
||||
The Makefile *should* be POSIX portable and not require a specific `make` version.
|
||||
|
||||
`meli` requires rust version 1.68.2 or later and rust's package manager, Cargo.
|
||||
Information on how to get it on your system can be found here: <https://doc.rust-lang.org/cargo/getting-started/installation.html>
|
||||
|
||||
With Cargo available, the project can be built with `make` and the resulting binary will then be found under `target/release/meli`.
|
||||
Run `make install` to install the binary and man pages.
|
||||
This requires root, so I suggest you override the default paths and install it in your `$HOME`: `make PREFIX=${HOME}/.local install`.
|
||||
|
||||
You can build and run `meli` with one command: `cargo run --release`.
|
||||
|
||||
## Build features
|
||||
|
||||
Some functionality is held behind "feature gates", or compile-time flags. The following list explains each feature's purpose:
|
||||
|
||||
- `gpgme` enables GPG support via `libgpgme` (on by default)
|
||||
- `dbus-notifications` enables showing notifications using `dbus` (on by default)
|
||||
- `notmuch` provides support for using a notmuch database as a mail backend (on by default)
|
||||
- `jmap` provides support for connecting to a jmap server and use it as a mail backend (on by default)
|
||||
- `sqlite3` provides support for builting fast search indexes in local sqlite3 databases (on by default)
|
||||
- `cli-docs` includes the manpage documentation compiled by either `mandoc` or `man` binary to plain text in `meli`'s command line. Embedded documentation can be viewed with the subcommand `meli man [PAGE]` (on by default).
|
||||
- `regexp` provides experimental support for theming some e-mail fields based
|
||||
on regular expressions.
|
||||
It uses the `pcre2` library.
|
||||
Since it's actual use in the code is very limited, it is not recommended to use this (off by default).
|
||||
- `static` and `*-static` bundle C libraries in dependencies so that you don't need them installed in your system (on by default).
|
||||
|
||||
Though not a feature, the presence of the environment variable `UNICODE_REGENERATE_TABLES` in compile-time of the `melib` crate will force the regeneration of unicode tables.
|
||||
Otherwise the tables are included with the source code, and there's no real reason to regenerate them unless you intend to modify the code or update to a new Unicode version.
|
||||
|
||||
## Build Debian package (*deb*)
|
||||
|
||||
Building with Debian's packaged cargo might require the installation of these two packages: `librust-openssl-sys-dev librust-libdbus-sys-dev`
|
||||
|
||||
A `*.deb` package can be built with `make deb-dist`
|
||||
|
||||
## Using notmuch
|
||||
|
||||
To use the optional notmuch backend feature, you must have `libnotmuch5` installed in your system.
|
||||
In Debian-like systems, install the `libnotmuch5` packages.
|
||||
`meli` detects the library's presence on runtime.
|
||||
If it is not detected, you can use the `library_file_path` setting on your notmuch account to specify the absolute path of the library.
|
||||
|
||||
## Using GPG
|
||||
|
||||
To use the optional gpg feature, you must have `libgpgme` installed in your system.
|
||||
In Debian-like systems, install the `libgpgme11` package.
|
||||
`meli` detects the library's presence on runtime.
|
||||
|
||||
## Development
|
||||
|
||||
Development builds can be built and/or run with
|
||||
|
||||
```
|
||||
cargo build
|
||||
cargo run
|
||||
```
|
||||
|
||||
There is a debug/tracing log feature that can be enabled by using the flag `--feature debug-tracing` after uncommenting the features in `Cargo.toml`.
|
||||
The logs are printed in stderr when the env var `MELI_DEBUG_STDERR` is defined, thus you can run `meli` with a redirection (i.e `2> log`).
|
||||
|
||||
To trace network and protocol communications you can enable the following features:
|
||||
|
||||
- `imap-trace`
|
||||
- `jmap-trace`
|
||||
- `nntp-trace`
|
||||
- `smtp-trace`
|
626
CHANGELOG.md
|
@ -8,9 +8,623 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Add import command to import email from files into accounts
|
||||
- Add add-attachment-file-picker command and `file_picker_command` setting to
|
||||
|
||||
- [0e3a0c4b](https://git.meli-email.org/meli/meli/commit/0e3a0c4b7049139994a65c6fe914dd3587c6713e) Add safe UI widget area drawing API
|
||||
- [0114e695](https://git.meli-email.org/meli/meli/commit/0114e695428579ef4461b289d7372e3b392b5e62) Add next_search_result and previous_search_result shortcuts
|
||||
- [c4344529](https://git.meli-email.org/meli/meli/commit/c4344529e30b3385149d6dc3c1c4b34306a85491) Add .git-blame-ignore-revs file
|
||||
|
||||
### Fixed
|
||||
|
||||
- [bcec745c](https://git.meli-email.org/meli/meli/commit/bcec745c241d7ed5d7d455ccdd65c6c95e1862b0) Fix command and status bar drawing
|
||||
- [62b8465f](https://git.meli-email.org/meli/meli/commit/62b8465f2cd99789576d70008f1f321243b81fc3) Fix ThreadView for new TUI API
|
||||
- [28fa66cc](https://git.meli-email.org/meli/meli/commit/28fa66cc2ad05e67708377fc99ffd65aa1b14386) Fix ThreadedListing for new TUI API
|
||||
- [2c6f180d](https://git.meli-email.org/meli/meli/commit/2c6f180df987976c1f4cba7ceac878e697c73d27) Fix macos compilation
|
||||
- [24971d19](https://git.meli-email.org/meli/meli/commit/24971d1960418bad92d89af9eb744933445baf99) Fix compilation with 1.70.0 cargo
|
||||
|
||||
### Changed
|
||||
|
||||
- [a1cbb198](https://git.meli-email.org/meli/meli/commit/a1cbb1988b34951046045f724f52bed2925b3880) Return Results instead of panicking
|
||||
- [7645ff1b](https://git.meli-email.org/meli/meli/commit/7645ff1b875e3920389567eb5e61d800291e8a27) Rename write_string{to_grid,}
|
||||
- [c2ae19d1](https://git.meli-email.org/meli/meli/commit/c2ae19d1208f2eb5cca341a04e019c3e285637a8) Return Option from current_pos
|
||||
- [b61fc3ab](https://git.meli-email.org/meli/meli/commit/b61fc3ab6482dcef4f5cc1c09db3539b7e401f78) Add HelpView struct for shortcuts widget
|
||||
- [ba7a97e9](https://git.meli-email.org/meli/meli/commit/ba7a97e90b4c474299a7b12fa74b7ea06c1535c8) Add x axis scroll support
|
||||
- [3495ffd6](https://git.meli-email.org/meli/meli/commit/3495ffd61b5888f8538304ecb6e441819b373bdc) Change UIEvent::Notification structure
|
||||
- [ccf6f9a2](https://git.meli-email.org/meli/meli/commit/ccf6f9a26e95437fb24464f90736c653e3f5dfed) Remember previous `set [index_style]]` preferences
|
||||
- [23c15261](https://git.meli-email.org/meli/meli/commit/23c15261e79c63791c569f225c1745df1b90ce2d) Abstract envelope view filters away
|
||||
- [031d0f7d](https://git.meli-email.org/meli/meli/commit/031d0f7dc76700ac938e1ee4a767fab8deebb9f2) Add area.is_empty() checks in cell iterators
|
||||
- [e37997d6](https://git.meli-email.org/meli/meli/commit/e37997d697f1f0b8faaa56a36f43c9f1da4bbb41) Store Link URL value in Link type
|
||||
|
||||
### Refactoring
|
||||
|
||||
- [0500e451](https://git.meli-email.org/meli/meli/commit/0500e451dab5f129d71a9279913531e77981e868) Add missing EnvelopeRemove event handler
|
||||
- [ab14f819](https://git.meli-email.org/meli/meli/commit/ab14f81900a03a07ef00a6b3232cb29d78e8edf5) Make write_string_to_grid a CellBuffer method
|
||||
- [e0adcdfe](https://git.meli-email.org/meli/meli/commit/e0adcdfe15b8a78c333de199ba734a83181f53be) Move rest of methods under CellBuffer
|
||||
- [0a74c7d0](https://git.meli-email.org/meli/meli/commit/0a74c7d0e5c318dd29c8ace01e588d441e0fcfb6) Overhaul refactor
|
||||
- [3b4acc15](https://git.meli-email.org/meli/meli/commit/3b4acc15a535c9bfd084b2e33f2cd00b5b5d4eb0) Add tests
|
||||
- [7eedd860](https://git.meli-email.org/meli/meli/commit/7eedd860518e3f7f5000a1888e4fa58ddbfb43bc) Remove address_list! macro
|
||||
- [f3e85738](https://git.meli-email.org/meli/meli/commit/f3e85738e7981755e96468213c02af78432f8cdd) Move build.rs scripts to build directory
|
||||
- [77325486](https://git.meli-email.org/meli/meli/commit/773254864bd8436712905eeb0c725d1d05277e60) Remove on-push hooks for actions w/ run on-pr
|
||||
|
||||
### Documentation
|
||||
|
||||
- [d018f07a](https://git.meli-email.org/meli/meli/commit/d018f07aa51fc293bf696fa7d7beff8e59ac91a8) Retouch manual pages
|
||||
- [3adba40e](https://git.meli-email.org/meli/meli/commit/3adba40e32a8a66271ea2a8f5ddf27858744ecd6) Add macos manpage mirror url
|
||||
|
||||
### Packaging
|
||||
|
||||
- [cd2ba80f](https://git.meli-email.org/meli/meli/commit/cd2ba80f8e5424be08421b4dcc5113977418f240) Update metadata
|
||||
- [5f8d7c80](https://git.meli-email.org/meli/meli/commit/5f8d7c8039c0623b3950fd1a8eb566f943fc309d) Update deb-dist target command with author metadata
|
||||
- [59c99fdc](https://git.meli-email.org/meli/meli/commit/59c99fdc79bb31fb42cb99d4b95613022396a499) Update debian package metadata
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [6506fffb](https://git.meli-email.org/meli/meli/commit/6506fffb9427ba13ba4368cd6b2c0dba12e5294c) Rewrite email flag modifications
|
||||
- [23507932](https://git.meli-email.org/meli/meli/commit/23507932f94257a71f2ca8db23840ee0716072b6) Update cache on set_flags
|
||||
- [470cae6b](https://git.meli-email.org/meli/meli/commit/470cae6b885c9b4851195fbb8274b1663bfa75cb) Update thread cache on email flag modifications
|
||||
- [c1c41c91](https://git.meli-email.org/meli/meli/commit/c1c41c9126005266f00d4979777718463dddf7b2) Update README.md and add Codeberg mirror
|
||||
- [84f3641e](https://git.meli-email.org/meli/meli/commit/84f3641ec1401a0522811add0ed87a131be449b9) Re-add on-screen message display
|
||||
- [54d21f25](https://git.meli-email.org/meli/meli/commit/54d21f25fdb716d36fd3678dd149eb880e16698d) Re-add contact list and editor support
|
||||
- [458258e1](https://git.meli-email.org/meli/meli/commit/458258e1aab91f3883d6a9201a175462511349e9) Re-enable compact listing style
|
||||
- [1c1be7d6](https://git.meli-email.org/meli/meli/commit/1c1be7d6c9bfc9f14c3a62ce464e1e15f2e6c4ec) Add display_name(), display_slice(), display_name_slice() methods
|
||||
- [5dd71ef1](https://git.meli-email.org/meli/meli/commit/5dd71ef1cd93aebaadb0554eac692d0a0fa4aecd) Upgrade JobsView component to new TUI API
|
||||
- [b5cc2a09](https://git.meli-email.org/meli/meli/commit/b5cc2a095f0268bb90cab150e903b0bbaffe1479) Upgrade MailboxManager component to new TUI API
|
||||
- [ed8a5de2](https://git.meli-email.org/meli/meli/commit/ed8a5de2cb4b93ad766803d3590f7041f28cc419) Re-enable EditAttachments component
|
||||
- [77a8d9e2](https://git.meli-email.org/meli/meli/commit/77a8d9e2c2094e84e06f5d624cb6f8afda24a400) Make ModSequence publicly accessible
|
||||
- [64898a05](https://git.meli-email.org/meli/meli/commit/64898a0583e348fef3cd266a7196425e7015a871) Make UIDStore constructor pub
|
||||
- [10c3b0ea](https://git.meli-email.org/meli/meli/commit/10c3b0eabe1684699c775e03c4c58038ea7979af) Bump version to 0.8.5-rc.1
|
||||
- [71f3ffe7](https://git.meli-email.org/meli/meli/commit/71f3ffe740276087f20d85d62440ef5d3fe426f6) Update Makefile
|
||||
- [63a63253](https://git.meli-email.org/meli/meli/commit/63a63253d77f6e1b9a42ec55ecf0bbc45a011245) Use type alias for c_char
|
||||
- [c751b2e8](https://git.meli-email.org/meli/meli/commit/c751b2e8450aa83b7a8f5e8afbeccadf333f74ba) Re-enable conversations listing style
|
||||
- [d16afc7d](https://git.meli-email.org/meli/meli/commit/d16afc7d8d9e2eddb81664673e9a4ef82da2e303) Bump version to 0.8.5-rc.2
|
||||
- [da251455](https://git.meli-email.org/meli/meli/commit/da251455a0185e207e0ec2d51273f6ddbdb572a8) Bump meli version to 0.8.5-rc.2
|
||||
- [3a709794](https://git.meli-email.org/meli/meli/commit/3a7097948308981204132a0eed2d28338f9d6b33) Update minimum rust version from 1.65.0 to 1.68.2
|
||||
- [f900dbea](https://git.meli-email.org/meli/meli/commit/f900dbea468e822c5a510a72ecc6367549443927) Use cargo-derivefmt to sort derives alphabetically
|
||||
- [5ff4e8ae](https://git.meli-email.org/meli/meli/commit/5ff4e8ae68182db8d4535d8537d26a3f398c815b) Run builds.yaml when any manifest file changes
|
||||
- [0a617410](https://git.meli-email.org/meli/meli/commit/0a617410ec1ce5f6fb43772e4ad43f45f58a7f4d) Split test.yaml to test.yaml and lints.yaml
|
||||
- [3ba1603a](https://git.meli-email.org/meli/meli/commit/3ba1603af2a9e408659717b9c8dace7406a8b142) Add manifest file only lints workflow
|
||||
- [1617212c](https://git.meli-email.org/meli/meli/commit/1617212c5b0948174155ece4a9d0584764bd7dac) Add scripts/check_debian_changelog.sh lint
|
||||
- [e19f3e57](https://git.meli-email.org/meli/meli/commit/e19f3e572c0ac585a6c2023e50f8fd0bd2ea2dae) Cargo-sort all Cargo.toml files
|
||||
- [c41f35fd](https://git.meli-email.org/meli/meli/commit/c41f35fdd55bf093656b68cc69eab4cf4b9a8ec4) Use actions/checkout@v3
|
||||
- [876616d4](https://git.meli-email.org/meli/meli/commit/876616d45b7798131ecdda82bb90d1d481842f5c) Use actions/upload-artifact@v3
|
||||
- [2419f4bd](https://git.meli-email.org/meli/meli/commit/2419f4bd40fb1a732cf1df42dde48ba8ca812072) Add debian package build workflow
|
||||
|
||||
## [v0.8.4](https://git.meli-email.org/meli/meli/releases/tag/v0.8.4) - 2023-11-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- [ef30228e](https://git.meli-email.org/meli/meli/commit/ef30228e08efe6e36ab9858a5ba32876d6d8fdae) Fix failing test
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [f81a1e23](https://git.meli-email.org/meli/meli/commit/f81a1e23382208390394be71e3aaa27ee505cb0f) Bump version to 0.8.4
|
||||
|
||||
## [v0.8.3](https://git.meli-email.org/meli/meli/releases/tag/v0.8.3) - 2023-11-22
|
||||
|
||||
### Added
|
||||
|
||||
- [3105a037](https://git.meli-email.org/meli/meli/commit/3105a0373b8754f37b326239c1cf7129fae06e1b) Add quit command
|
||||
|
||||
### Fixed
|
||||
|
||||
- [d3cbf184](https://git.meli-email.org/meli/meli/commit/d3cbf184e606d5b7ade9cfb125db01f45d7180ae) Add extra_submission_headers fields in composer form and autocomplete for Newsgroups
|
||||
- [7aec5b8e](https://git.meli-email.org/meli/meli/commit/7aec5b8e78d80e7717a9aedd7344db6b108534f5) Fix SMTP example doc
|
||||
- [f702dc22](https://git.meli-email.org/meli/meli/commit/f702dc220c9ab97ce0fddfae194d5e2935a20193) Fix new clippy lints.
|
||||
- [688e39a6](https://git.meli-email.org/meli/meli/commit/688e39a67e6a467ca649acbe20b1f368fbc1e9f0) Fix clippy lints
|
||||
|
||||
### Changed
|
||||
|
||||
- [5a7919bb](https://git.meli-email.org/meli/meli/commit/5a7919bb03641be6d7bc5b9002d44e16ee358f12) Use ConversationsListing::format_date
|
||||
- [0f3b5294](https://git.meli-email.org/meli/meli/commit/0f3b52945959b53c8d809eb434a91ec4c561b2d4) Hoist format_date() to ListingTrait method
|
||||
|
||||
### Refactoring
|
||||
|
||||
- [e1b55340](https://git.meli-email.org/meli/meli/commit/e1b55340fa258a2a7b118fd18c11614fb2b5e173) Show error description when TIOCGWINSZ ioctl fails
|
||||
- [e95c275d](https://git.meli-email.org/meli/meli/commit/e95c275d68fe3dbd588046c110ae8b3fa966f6de) Remove duplicate end sequence
|
||||
- [8a21be21](https://git.meli-email.org/meli/meli/commit/8a21be21775cb474a6b65e1c0bffd771c0df6f2f) Replace splice with truncate
|
||||
- [2db021fa](https://git.meli-email.org/meli/meli/commit/2db021fa0a9a707cd7cdb6c8bf140bf5c8acf906) Remove regexp from default features
|
||||
- [fa33a946](https://git.meli-email.org/meli/meli/commit/fa33a9468a16c50361353efa269fca79bd58e284) Move managesieve-client binary to tools/
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [e88957ae](https://git.meli-email.org/meli/meli/commit/e88957ae6edfee7fabb41e9210f9d906866cda8d) Add extra_submission_headers field in MailBackendCapabilities struct
|
||||
- [606f487f](https://git.meli-email.org/meli/meli/commit/606f487fc5e227f1727697a5911e27cbec174089) Add IRC channel badge
|
||||
- [0e60bdf2](https://git.meli-email.org/meli/meli/commit/0e60bdf26eb842744f59257800ca8e30b1a43836) Add "iterator" feature to signal-hook
|
||||
- [ac2a5dcd](https://git.meli-email.org/meli/meli/commit/ac2a5dcdd10d97f5ed9c8a8c83e1641b373dd31a) Add display() method for Address
|
||||
- [43bfd413](https://git.meli-email.org/meli/meli/commit/43bfd4131d5cab39319d1943bcad46e929ec4d56) Update ahash dependency
|
||||
- [af241d25](https://git.meli-email.org/meli/meli/commit/af241d25cbab20227a88ec4d557222cdeed98dde) Bump version to 0.8.3
|
||||
- [7387b67e](https://git.meli-email.org/meli/meli/commit/7387b67eeee27aefbc4d20ca2a1d503aa0fb1838) Enable "static" build for C library dependencies by default
|
||||
- [bfc78a08](https://git.meli-email.org/meli/meli/commit/bfc78a0803524e236bc883833838d3ad78918621) Replace CRLF with LF when editing
|
||||
- [111a1160](https://git.meli-email.org/meli/meli/commit/111a1160adf2e0fef00a90350784307c859a198b) Bump version to 0.8.3
|
||||
|
||||
## [v0.8.2](https://git.meli-email.org/meli/meli/releases/tag/v0.8.2) - 2023-09-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- [73b3ed55](https://git.meli-email.org/meli/meli/commit/73b3ed559d21dcc7cdee7f96119461e2447c1906) Fix forward dialog not workng
|
||||
- [7888d8b2](https://git.meli-email.org/meli/meli/commit/7888d8b2a5dc977f0f18094a32dc73893a5cfc4f) Fix doc test compilation
|
||||
|
||||
### Changed
|
||||
|
||||
- [22525d40](https://git.meli-email.org/meli/meli/commit/22525d40fb48661f86657151e35fdf9c95c4b45e) Go to end when pressing next/page down for the second time
|
||||
- [71474436](https://git.meli-email.org/meli/meli/commit/714744366f5e26fc1b6609e8e785d64489f9a68d) Revert 22525d40 behavior when sidebar not focused
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [eb5d49c4](https://git.meli-email.org/meli/meli/commit/eb5d49c41ac58c5068011620c22e21b5fa115417) Use Self in self methods
|
||||
- [3d85ca2e](https://git.meli-email.org/meli/meli/commit/3d85ca2edfca9abff4b3ffdd837b25e68c6586c2) Bump version to 0.8.2
|
||||
|
||||
## [v0.8.1](https://git.meli-email.org/meli/meli/releases/tag/v0.8.1) - 2023-09-13
|
||||
|
||||
### Added
|
||||
|
||||
- [6476985c](https://git.meli-email.org/meli/meli/commit/6476985ce6abbb9048ba5aec19f6c5144bfe89b7) Add Cross.toml for aarch64-unknown-linux-gnu builds
|
||||
- [45d4f611](https://git.meli-email.org/meli/meli/commit/45d4f611b170d7b80afca5810c51fea1bf084c10) Add install-man cli subcommand to install manpages on your system
|
||||
- [a4f0dbac](https://git.meli-email.org/meli/meli/commit/a4f0dbac26126c03886115e518b3cd2ede0b88cb) Add current working directory tracking to Context
|
||||
|
||||
### Fixed
|
||||
|
||||
- [49a38a23](https://git.meli-email.org/meli/meli/commit/49a38a23bf522a18e636385632cfe3533c4f525c) Fix invalid Type link references
|
||||
- [85af5244](https://git.meli-email.org/meli/meli/commit/85af524458bc06421ac39689469474efb8164c1c) Fix invalid mailto() results when body field exists
|
||||
- [c7825c76](https://git.meli-email.org/meli/meli/commit/c7825c76c3ac6be89f64f1f04afd9c0ca08bdf76) Handle dialog Esc in the parent component
|
||||
- [dd4d0b79](https://git.meli-email.org/meli/meli/commit/dd4d0b79721d8cd5b29cdaca9cd01412974f2e13) Fix typo
|
||||
- [c43aeb0e](https://git.meli-email.org/meli/meli/commit/c43aeb0eb103f2a8fd802f84eab56551c6e65418) Fix invalid address parse on folded values
|
||||
- [7e3e9386](https://git.meli-email.org/meli/meli/commit/7e3e9386316ef344580d9e44edb3f8b0c196c3c5) Fix out-of-bounds draw when terminal is small
|
||||
- [7e4ed2fa](https://git.meli-email.org/meli/meli/commit/7e4ed2fa107eca2ef309bcaa211440c315730b6c) Fix some out of bounds drawing.
|
||||
|
||||
### Changed
|
||||
|
||||
- [1b3bebe3](https://git.meli-email.org/meli/meli/commit/1b3bebe3049ae5c7cb2210ed95c355c9b5c709f8) Open earliest unread email instead of first in thread
|
||||
- [49c36009](https://git.meli-email.org/meli/meli/commit/49c36009cec8c88d61d796162787990216bfeeab) Don't initialize entire thread at once
|
||||
- [0a9c89b6](https://git.meli-email.org/meli/meli/commit/0a9c89b6b357fc3d002c3eb451fd67e7a49ce7f5) Add toggle_layout shortcut
|
||||
- [64ba0459](https://git.meli-email.org/meli/meli/commit/64ba0459ee3652eaf451d10222853a898d85e337) Init cursor at To: header field
|
||||
- [81974311](https://git.meli-email.org/meli/meli/commit/81974311c200b8ad66c0e626f8b8db6686e565ff) Show current number command buffer
|
||||
|
||||
### Refactoring
|
||||
|
||||
- [a337e226](https://git.meli-email.org/meli/meli/commit/a337e2269e584769314cdf325cdeb6e57cb0c622) Refactor module structure
|
||||
- [b4f2f335](https://git.meli-email.org/meli/meli/commit/b4f2f3357613729e493e5f41a48def7610dc65aa) Remove deflate feature; make it a hard dependency
|
||||
- [2dc29405](https://git.meli-email.org/meli/meli/commit/2dc29405868b9df0dfff25e341814526a478db00) Add feature to use cache instead of downloading unicode data
|
||||
- [0132677f](https://git.meli-email.org/meli/meli/commit/0132677ff54a9618d3c59b08a188b73ae0c062c7) Introduce CommandError with context
|
||||
- [3344a8db](https://git.meli-email.org/meli/meli/commit/3344a8dbf6b478a85d2b933fc1fa1a6001c600f4) Remove unnecessary Clone derives
|
||||
- [b673af02](https://git.meli-email.org/meli/meli/commit/b673af02ac9e9d4be95daa2490ce24d0bc9b10d9) Move to crate root
|
||||
- [54862f86](https://git.meli-email.org/meli/meli/commit/54862f8651cb7dfe3bca7f5924fe776b93ac6aee) Add hide_sidebar_on_launch option
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [a615b470](https://git.meli-email.org/meli/meli/commit/a615b4701b7e852a9112b317e2e31997c6cbe82e) Embed xdg-utils crate
|
||||
- [f0075b86](https://git.meli-email.org/meli/meli/commit/f0075b86cf636a3d39d4edf1ff6d58c112bbecf7) Show descriptive tab names for composer and threads
|
||||
- [6d5ebb5b](https://git.meli-email.org/meli/meli/commit/6d5ebb5b04279fe6e4fbf598504cae2f012fa494) Split code into submodules, add better error reporting
|
||||
- [63abf1e8](https://git.meli-email.org/meli/meli/commit/63abf1e890b93fcadf35f88b3dbea473c0d8f5cd) Update README.md
|
||||
- [bb4d2000](https://git.meli-email.org/meli/meli/commit/bb4d20003690d72b62a66d46a1fc5ae914e2bf64) Unify toggle_* parsers
|
||||
- [9b9c38f7](https://git.meli-email.org/meli/meli/commit/9b9c38f769abae0ff86e4b71e4db0ad65fdacfb4) Don't flood user with sqlite3 errors if db is corrupted
|
||||
- [747e39bf](https://git.meli-email.org/meli/meli/commit/747e39bf55cfc19b6eeece3ca7c71bad98d92389) Add print-used-paths subcommand
|
||||
- [39e99770](https://git.meli-email.org/meli/meli/commit/39e99770da4b51d0986a4b561fbe36b27d04565d) Use Context::current_dir() when saving files to relative paths
|
||||
- [fe0a96f0](https://git.meli-email.org/meli/meli/commit/fe0a96f0855486207280430064a93cab94dffeb2) Update to 2021 edition
|
||||
- [3944e4e6](https://git.meli-email.org/meli/meli/commit/3944e4e60e431247eefc0b3cf35af27fb011f37b) Update to 2021 edition
|
||||
- [7eed8278](https://git.meli-email.org/meli/meli/commit/7eed82783a3dbac513e233be4f0bce06904fe8c8) Bump version to 0.8.1
|
||||
|
||||
## [v0.8.0](https://git.meli-email.org/meli/meli/releases/tag/v0.8.0) - 2023-08-29
|
||||
|
||||
### Added
|
||||
|
||||
- [36e29cb6](https://git.meli-email.org/meli/meli/commit/36e29cb6fd00c798ad83e3064e0ff78c8153dced) Add configurable mailbox sort order
|
||||
- [81184b18](https://git.meli-email.org/meli/meli/commit/81184b182c5f5d65614653b817981fddc6a84ffa) Add extra_identities configuration flag
|
||||
- [b716e438](https://git.meli-email.org/meli/meli/commit/b716e4383ea3163cabe760cd5512b7d70b218915) Add collapse option for mailboxes in sidebar menu
|
||||
- [3d92b410](https://git.meli-email.org/meli/meli/commit/3d92b41075fc16214675cf141acd9c89fb6f5c49) Add cli-docs feature to the default set
|
||||
- [104352e5](https://git.meli-email.org/meli/meli/commit/104352e5950598f4a659bd593d587910af8adc12) Add table UI widget
|
||||
- [7d9cabb0](https://git.meli-email.org/meli/meli/commit/7d9cabb023b510e6175fd6b2523f0414a6da1f3f) Add mailbox manager tab
|
||||
- [660bacb9](https://git.meli-email.org/meli/meli/commit/660bacb9262dac7457bd8c421cc70343a0db3cd5) Add `mailto` command to open composer with initial values from mailto template
|
||||
- [3adf72ae](https://git.meli-email.org/meli/meli/commit/3adf72aed0772fea39fbd6cbaec680fb2995e92d) Add support for utf-7 encoding
|
||||
- [d9c07def](https://git.meli-email.org/meli/meli/commit/d9c07def0f5db655aa11c5981d1419a336c3d91a) Add command to select charset encoding for email
|
||||
- [8c671935](https://git.meli-email.org/meli/meli/commit/8c671935f9ad5bd2894c0ecdaec9c2f378e461ca) Add compose (pre-submission) hooks for validation/linting
|
||||
- [96537e48](https://git.meli-email.org/meli/meli/commit/96537e48c5f5c8d54076ec5db76e94a499cbe1e6) Add {Timer,Component}Id wrapper types over Uuid
|
||||
- [b5f205b7](https://git.meli-email.org/meli/meli/commit/b5f205b77b8911a1fb6019767bb026e5f4a7f79e) Add availability to use server_password_command in the nntp backend like in the IMAP backend
|
||||
- [a5770c89](https://git.meli-email.org/meli/meli/commit/a5770c89f46b908d17d6eb4573c8337a952f99a8) Add Woodpecker-CI check pipeline
|
||||
- [d4e605c0](https://git.meli-email.org/meli/meli/commit/d4e605c098ba13b8bc2d9f14d07ea45da38e9a2f) Add tagref source code annotations
|
||||
- [cf9a04a5](https://git.meli-email.org/meli/meli/commit/cf9a04a5910c9d82e1acb10a2f4d40c2af0335ed) Add metadata to Jobs, and add JobManager tab
|
||||
- [bb7e119a](https://git.meli-email.org/meli/meli/commit/bb7e119ade131e8fe1bcac39b616741af817808c) Add gitea CI workflows
|
||||
- [1c79786e](https://git.meli-email.org/meli/meli/commit/1c79786ea210e53ee7d566455d83d74fe4699d28) Add scripts/make_html_manual_page.py
|
||||
- [65e82d88](https://git.meli-email.org/meli/meli/commit/65e82d8896500e8ef586656e3bde4bc102b84aba) Add meli/README.md symbolic link
|
||||
|
||||
### Fixed
|
||||
|
||||
- [ce2068d3](https://git.meli-email.org/meli/meli/commit/ce2068d36bb5d8ad0bb8f886bc19cb4aab75c4e8) Fix background watch using JSON paths incorrectly
|
||||
- [e9aaa7b0](https://git.meli-email.org/meli/meli/commit/e9aaa7b067903040acd7f3d7c685de94b3b98450) Use *const c_char instead of *const i8 for portability
|
||||
- [aa3524dd](https://git.meli-email.org/meli/meli/commit/aa3524dd305f2cf293eaaf7120b812478255f79c) Fix tag not being removed in set_flags()
|
||||
- [daa900ec](https://git.meli-email.org/meli/meli/commit/daa900ec9a566460833c020feba10933c0248162) Fix embed terminal in macos
|
||||
- [7fca5f01](https://git.meli-email.org/meli/meli/commit/7fca5f01ef53069958403dd794ee0e5c310f4e45) Fix jmap build with isahc 1.7.2
|
||||
- [ed3dbc85](https://git.meli-email.org/meli/meli/commit/ed3dbc85861ab61fee56077c7ba94306b0a96dc4) Fix crashes when listing is empty
|
||||
- [824f614a](https://git.meli-email.org/meli/meli/commit/824f614a69e55a25d67832593cb8aadb9671e306) Fix HtmlView not being redrawn when parent is dirty
|
||||
- [97ff3e78](https://git.meli-email.org/meli/meli/commit/97ff3e787fbfb5ff50e3ba787f067829509f7cd2) Only add toml files to the themes
|
||||
- [9cb66ef8](https://git.meli-email.org/meli/meli/commit/9cb66ef818f6598eb779f931e201a8d38e86a484) Fix all clippy warnings in `meli` crate
|
||||
- [0c0bee44](https://git.meli-email.org/meli/meli/commit/0c0bee4482d4fbfa675b97ca30405fdc77655936) Add missing .PHONY targets, fix missing tab indentation
|
||||
- [a73885ac](https://git.meli-email.org/meli/meli/commit/a73885acb14cd94d4a6a54ebd5b39a001d7e21e1) Improve embed terminal
|
||||
- [da9c80cc](https://git.meli-email.org/meli/meli/commit/da9c80ccfd7aa87842c2c3c089ba2b784a583ab6) Enhance SubjectPrefix with strip_prefixes_from_list() method
|
||||
- [aa99b0d7](https://git.meli-email.org/meli/meli/commit/aa99b0d787463be4267913b801117bd4d2ea5003) Implement configurable subject prefix stripping when replying
|
||||
- [cbe593cf](https://git.meli-email.org/meli/meli/commit/cbe593cf31308dcf549d7880eea2d82e5024dd73) Add configurable header preample suffix and prefix for editing
|
||||
- [2de69d17](https://git.meli-email.org/meli/meli/commit/2de69d17f14e79ce2a35564d278b5e895d16a48f) Fix erroneous placement of newlnes for wrap_header_preamble suffix
|
||||
- [94bd84b4](https://git.meli-email.org/meli/meli/commit/94bd84b45d53b0e0fae52198fbdc05179b87cccc) Fix clippy lints for `meli` crate
|
||||
- [b138d9bc](https://git.meli-email.org/meli/meli/commit/b138d9bc6166b763febf035b50109d810e3c18c9) Fix some clippy lints
|
||||
- [c6bdda03](https://git.meli-email.org/meli/meli/commit/c6bdda03cf451ab52a3d414cad1344bb32c82879) Fix notmuch error shown on any missing backend
|
||||
- [16646976](https://git.meli-email.org/meli/meli/commit/16646976d75284665c1fa0d7b7e3e3cde3531d66) Fix reply subject prefixes stripping original prefix
|
||||
- [88a1f0d4](https://git.meli-email.org/meli/meli/commit/88a1f0d4bc17b60f8f23ea71f33a81aee78f8769) Fix FETCH response parsing bug
|
||||
- [59b95f83](https://git.meli-email.org/meli/meli/commit/59b95f83d2b388b30a3a855f68bf5952355597d7) Fix docs
|
||||
- [282af86e](https://git.meli-email.org/meli/meli/commit/282af86e83807772f042b115af24ffe2e0575b9e) Fix NAME sections manual pages for correct whatis(1) parsing
|
||||
- [bd22f986](https://git.meli-email.org/meli/meli/commit/bd22f986f0c06f6dae535733d484aa89f610ed46) Fix clippy lints
|
||||
- [5ba7b2cd](https://git.meli-email.org/meli/meli/commit/5ba7b2cd7bb07abe8faafe5e45db6145b3f90bc9) Fix clippy lints for meli binary
|
||||
- [7924aa8b](https://git.meli-email.org/meli/meli/commit/7924aa8bfe8f0fbcd557bb8bb3a9d3ebeab2220a) Fix compilation
|
||||
- [b9030a68](https://git.meli-email.org/meli/meli/commit/b9030a684c0ad64951a388e49d5825c12b483fb4) Fix selection not appearing immediately and invalid motions
|
||||
- [4f45b109](https://git.meli-email.org/meli/meli/commit/4f45b109745ebc29febc452b9bcb0cd88f131ffc) Fix tag updates not showing up right away
|
||||
- [abc56eae](https://git.meli-email.org/meli/meli/commit/abc56eae431153d2e48f8b1eb3e0d2a140b600d8) Fix SEEN flag update hiding mail view momentarily
|
||||
- [40c6647d](https://git.meli-email.org/meli/meli/commit/40c6647db83c5137b79c9bec233972a8a78aeb76) Fix multipart/related with main text/html part not displayed correctly
|
||||
- [11140b4a](https://git.meli-email.org/meli/meli/commit/11140b4a76419a6f8c83db38823e83aeac8fbb98) Fix test output
|
||||
- [3a10953f](https://git.meli-email.org/meli/meli/commit/3a10953f05ea4944a8a20c2c5d647d5862dca907) Update fix-prefix-for-debian.patch
|
||||
- [939dc15e](https://git.meli-email.org/meli/meli/commit/939dc15e289e06a0fad72e44f9e91133892a4ec0) Fix melib tests
|
||||
- [39d9c2af](https://git.meli-email.org/meli/meli/commit/39d9c2af3b7daf39c6aa7eab5f2d95f1b9c3a562) Fix test smtp server logic
|
||||
- [34bb532e](https://git.meli-email.org/meli/meli/commit/34bb532e8d91c5f35bdc058821da63ac543ecfa6) Mention w3m dependency
|
||||
- [b1a71887](https://git.meli-email.org/meli/meli/commit/b1a71887710153f0f98b25b2f224fbe37f7a6889) Clippy fixes
|
||||
- [1f8ac228](https://git.meli-email.org/meli/meli/commit/1f8ac2287b960e0ed5c44dadbf68b924f035d321) Fix ftplugin location and add example mail.vim file
|
||||
- [1eea8bab](https://git.meli-email.org/meli/meli/commit/1eea8bab77cc20fb911f13aa16322a217b36b06b) Fix `test_imap_fetch_response`.
|
||||
- [daf42fd4](https://git.meli-email.org/meli/meli/commit/daf42fd456bad5ddf65ac515c2fb277896d1fea3) Fix build error with quote 1.0.28
|
||||
- [6388bea9](https://git.meli-email.org/meli/meli/commit/6388bea9a063f776398ffc503fdb0789ce9af9f1) Fix &[u8] index in HeaderMap
|
||||
- [c5ecacea](https://git.meli-email.org/meli/meli/commit/c5ecaceae1ab50a1c337f5cab9e97c0b061cb2d5) Fix some search criteria in Query type
|
||||
- [27a4dcb9](https://git.meli-email.org/meli/meli/commit/27a4dcb916e0bed723490df9d82bfd7c83f10a83) Fix some rustdoc lints
|
||||
- [fdc0861a](https://git.meli-email.org/meli/meli/commit/fdc0861ac0ac725e6e5031d120bd4682752c0267) Fix expanded_hash argument off by one error
|
||||
- [0c0a678c](https://git.meli-email.org/meli/meli/commit/0c0a678cffec73940065923bb3837deb85075f9f) Fix overlay widgets not being reaped after Unrealize event
|
||||
- [65179d48](https://git.meli-email.org/meli/meli/commit/65179d4816a39b0c92e9c6a981b491c60313634f) Fix cursor/widget focus scrolling logic
|
||||
- [e64923ee](https://git.meli-email.org/meli/meli/commit/e64923eeaaf1fdf0ee485cceff0c57b2d43f165a) Fix debug_assert condition
|
||||
- [5f29faa6](https://git.meli-email.org/meli/meli/commit/5f29faa640ebe7b14e76e56227a482207b8d952e) Clippy lint fixes
|
||||
- [0b258a1f](https://git.meli-email.org/meli/meli/commit/0b258a1f058fa08b143a8e573883a4abe89dc7e1) Clippy lint fixes
|
||||
- [ba7f5dce](https://git.meli-email.org/meli/meli/commit/ba7f5dce1c37c04768aa060b35f3803e6db3840e) Fix display of threaded conversations tree structure
|
||||
- [1dc1d868](https://git.meli-email.org/meli/meli/commit/1dc1d86848eb6d187120bcaa00296f2b4e2025ca) Fix infinite loop bug
|
||||
- [e8e49e74](https://git.meli-email.org/meli/meli/commit/e8e49e741b0f888d44da69f52aa3fff2e03e7ced) Fix wrong per message offset
|
||||
- [e3dfeaad](https://git.meli-email.org/meli/meli/commit/e3dfeaad7e4f838af5fb2e6e398d3e1aa37fe511) Fix compilation error when building without `gpgme` feature
|
||||
- [7998e1e7](https://git.meli-email.org/meli/meli/commit/7998e1e77ef057bab28434edefb79d7be6a4de33) Add missing LC libc constants for openbsd target_os
|
||||
- [b5657201](https://git.meli-email.org/meli/meli/commit/b5657201db4828c6e61c52e7ce338ac1a6e6f9fc) Fix doctest compilation errors
|
||||
- [c2ed3e28](https://git.meli-email.org/meli/meli/commit/c2ed3e283f6729ac7e112d00ae54dd99a2ada5e6) Fix Source::* view showing only envelope body
|
||||
- [d93ee413](https://git.meli-email.org/meli/meli/commit/d93ee413a766f35a4ef88d9fc3ace9cf37d28dd1) Add timestamp_to_string_utc
|
||||
- [6086a378](https://git.meli-email.org/meli/meli/commit/6086a3789d4d01818322dab1f1a9eb4c1f6a2b25) Fix libgpgme segfault error and re-enable gpg
|
||||
- [ab418c1d](https://git.meli-email.org/meli/meli/commit/ab418c1d39d02840bc5c61996c1a5416e2f35464) Refresh documentation, fix encryption/signing
|
||||
- [0219dc87](https://git.meli-email.org/meli/meli/commit/0219dc870798a16fd4d9f546d14c115f9e2c6bd8) Respect max_objects_in_get when fetching email
|
||||
- [6280bc75](https://git.meli-email.org/meli/meli/commit/6280bc75e550332a73c1a51dd46475cd54cc0a34) Fix blob download URL formatting
|
||||
- [2df73547](https://git.meli-email.org/meli/meli/commit/2df73547515fd3464e1fc2b88aa67462f583a8ec) Fix overflow substracts
|
||||
- [8e698cab](https://git.meli-email.org/meli/meli/commit/8e698cabcfe58ddd566133ba2c33249c23180a74) Fix unreachable-pub and disjoint-capture lint errors
|
||||
- [40d4ecef](https://git.meli-email.org/meli/meli/commit/40d4ecefa013caaa13af493233c693fb495360ca) Accept invalid (non-ascii) address comment text
|
||||
- [4e654d2d](https://git.meli-email.org/meli/meli/commit/4e654d2d02044be7340b63f1250d37b2ca57b221) Limit LIST ACTIVE command length to 512 octets
|
||||
- [84081f4e](https://git.meli-email.org/meli/meli/commit/84081f4ed7570dd8bcc23d90b9c4cbff55620636) Minor style fix
|
||||
- [97d36868](https://git.meli-email.org/meli/meli/commit/97d3686815c011bb8f1d4e448f12b2294693730d) Use Happy Eyeballs algorithm Ꙭ
|
||||
- [96f0b3e6](https://git.meli-email.org/meli/meli/commit/96f0b3e6b484c9cbb7eaddcaad2b59811b733545) Fix shortcut section order
|
||||
- [64982b4c](https://git.meli-email.org/meli/meli/commit/64982b4cab0b0c2d396cb5dcf7add6f268fd4551) Fix page{up,down} event bubbling up
|
||||
- [8551e1ba](https://git.meli-email.org/meli/meli/commit/8551e1ba0b4fa6d9587bbb249f11e9b80d24e4d3) Fix new 1.72 default clippy lints
|
||||
|
||||
### Changed
|
||||
|
||||
- [8563bccd](https://git.meli-email.org/meli/meli/commit/8563bccd1b6d48dc06dd521f77228c3cbecf7613) Don't cache CellBuffer, only row info
|
||||
- [0f6f3e30](https://git.meli-email.org/meli/meli/commit/0f6f3e30c67f209e0b5e03d2dd2e1e48180d9855) Add IMAP config in config parse test
|
||||
- [ce269c64](https://git.meli-email.org/meli/meli/commit/ce269c64e16db344f0e65461e56dbced2f1a4d64) Don't fail on `server_password_command`
|
||||
- [9dc4d405](https://git.meli-email.org/meli/meli/commit/9dc4d4055cb2f854e835748315677bf4a2db2012) Add focus_{left,right} shortcuts to switch focus
|
||||
- [4b96bd59](https://git.meli-email.org/meli/meli/commit/4b96bd591f18bf7c8a3c922d469b81072d1782a2) Add ColorCache constructor to deduplicate code
|
||||
- [c06c3f58](https://git.meli-email.org/meli/meli/commit/c06c3f589315f017a412f31d80559a5a734d7b89) Draw gap between list and mail view
|
||||
- [c9d26bb4](https://git.meli-email.org/meli/meli/commit/c9d26bb4158e2f423c795f82bcb2c91a0f0c46ec) Add configurable custom hooks with shell commands
|
||||
- [02e86d1f](https://git.meli-email.org/meli/meli/commit/02e86d1fade9faefc14b890e3cec8ed2255bb839) Check for subject overflow on draw
|
||||
- [8cab9d9d](https://git.meli-email.org/meli/meli/commit/8cab9d9da8710257f2b62832bfac802c2a35b368) Add option to hide consecutive identical From values inside a thread
|
||||
- [363f4930](https://git.meli-email.org/meli/meli/commit/363f4930994d1d2e88220878b3848f176b8c5f97) Add {previous,next}_entry shortcuts to quickly open other mail entries
|
||||
- [342df091](https://git.meli-email.org/meli/meli/commit/342df091a076bce1f8477dabbad193312d8cdd67) Don't set all thread to seen when opening a thread entry
|
||||
- [74e15316](https://git.meli-email.org/meli/meli/commit/74e15316dbbf67254023e619924e522f80e77cb9) Open message/rfc822 attachments in subview instead of new tab
|
||||
- [369c1dbd](https://git.meli-email.org/meli/meli/commit/369c1dbdac9842746270a3d3c5bf7ed2205cb644) Show `open` command in status bar
|
||||
- [519257b0](https://git.meli-email.org/meli/meli/commit/519257b08f7029fe71efd2f61ab3a29a4b43b862) Add relative_menu_indices setting for menubar
|
||||
- [8abc9358](https://git.meli-email.org/meli/meli/commit/8abc9358a70465b12a11168be1718ab06479d6e2) Add newline after Version: 1 header
|
||||
- [561ba9c8](https://git.meli-email.org/meli/meli/commit/561ba9c87b57e1012ad89bde08506a2beacb7fff) Add relative_list_indices setting for thread listing
|
||||
- [52874f9a](https://git.meli-email.org/meli/meli/commit/52874f9a97a4799fcff2e14c43cafe9692f21cb6) Cancel previous jobs on MailView drop/update
|
||||
- [9037f084](https://git.meli-email.org/meli/meli/commit/9037f08495894c15a7817594ba91e0d5561c6e69) Replace hardcoded Key::{Home,End} values with shortcut values
|
||||
- [31aa9ad2](https://git.meli-email.org/meli/meli/commit/31aa9ad29e33f285314d0d320a02f00071f61282) Autogen mbox filename when exporting mail to directories
|
||||
|
||||
### Refactoring
|
||||
|
||||
- [330a2b20](https://git.meli-email.org/meli/meli/commit/330a2b20ed492f6b6ea86c196d43d67430487faa) Flush stdout in Ask() after printing
|
||||
- [340d6451](https://git.meli-email.org/meli/meli/commit/340d6451a330861af09fd02231c17ba4168d9654) Add config setting for sidebar ratio
|
||||
- [d0de0485](https://git.meli-email.org/meli/meli/commit/d0de04854ec4770b54e4d8303a9b8ab9eb5d68b0) Add {in,de}crease_sidebar shortcuts
|
||||
- [f5dc25ae](https://git.meli-email.org/meli/meli/commit/f5dc25ae0d5b8d6fb15a534fa49557385d6894d0) Check that all conf flags are recognized in validation
|
||||
- [d3e62e3d](https://git.meli-email.org/meli/meli/commit/d3e62e3d74bdc55872bbdf92c01d18aa00b0affd) Use conf shortcuts for scroll {up, down}
|
||||
- [23c23556](https://git.meli-email.org/meli/meli/commit/23c2355662d589c091dd3c86c8d91c7988eb941c) Fill and align shortcut table columns
|
||||
- [5823178c](https://git.meli-email.org/meli/meli/commit/5823178cc26f66ba902a901522f0506b4348b22e) Add test that looks in source code for invalid theme key references
|
||||
- [9205f3b8](https://git.meli-email.org/meli/meli/commit/9205f3b8afe28ef3a68959d590ed967946a5d622) Handle a per account mail order parameter
|
||||
- [d921b3c3](https://git.meli-email.org/meli/meli/commit/d921b3c3209ff7fe865b5a3b90e20098b3ff211f) Use mail sorting parameters from config
|
||||
- [f4e0970d](https://git.meli-email.org/meli/meli/commit/f4e0970d46e3ec73d684e2ddcc5011f61e87314d) Add ability to kill embed process
|
||||
- [bde87af3](https://git.meli-email.org/meli/meli/commit/bde87af3877d4a0b071e331c93a07e0acf51bf7a) Refactor filter() method in Listing trait
|
||||
- [a42a6ca8](https://git.meli-email.org/meli/meli/commit/a42a6ca868e4590a8b93560737173e80993ecaec) Show notifications in terminal if no alternative
|
||||
- [eb5949dc](https://git.meli-email.org/meli/meli/commit/eb5949dc9bbcf05f86c58b3c93d1066204313e2a) Switch summary<->details identifiers
|
||||
- [8c7b001a](https://git.meli-email.org/meli/meli/commit/8c7b001aa5d4cb6bbaf438f3f47cd91cc2fd6833) Add `thread_subject_pack` command to pack different inner thread subjects in entry title
|
||||
- [388d4e35](https://git.meli-email.org/meli/meli/commit/388d4e35d65f8f770526c4c5f44767c55eda23f8) Add in-progress messages while connecting in IMAP
|
||||
- [787c64c2](https://git.meli-email.org/meli/meli/commit/787c64c2da8af5cc0dafcb92c1d3bea6b54f3659) Remove expect()s from create_config_file()
|
||||
- [b87d54ea](https://git.meli-email.org/meli/meli/commit/b87d54ea3f3f077b6330e798263be6a3d33b3b9c) Impl Into<BTreeSet<EnvelopeHash>> for EnvelopeHashBatch
|
||||
- [e450ad0f](https://git.meli-email.org/meli/meli/commit/e450ad0f9cbc2d215a8f03d2d39260abe19fb5af) Remove unused struct
|
||||
- [c54a31f7](https://git.meli-email.org/meli/meli/commit/c54a31f7cca728eec87f7cd670a4baec37dc919a) Break line for error messages
|
||||
- [7935e49a](https://git.meli-email.org/meli/meli/commit/7935e49a00190cc7f2057abe353739c8dad4f74d) Check properly if mailbox request is an error
|
||||
- [117d7fbe](https://git.meli-email.org/meli/meli/commit/117d7fbe046fe23c400a925ccba7317d8a1d3f08) Make private fields public
|
||||
- [ffb12c6d](https://git.meli-email.org/meli/meli/commit/ffb12c6d1ae9a774de22a25d38bc6714a435c7ad) Make all public struct fields public
|
||||
- [46a038dc](https://git.meli-email.org/meli/meli/commit/46a038dc68093b28b69c3af38de4dd09431efae2) Remove interactive messages when #[cfg(test)]
|
||||
- [803d3414](https://git.meli-email.org/meli/meli/commit/803d3414fd73743ff5bfc0fefe5e3d76d88e58cb) Implement some rfc5804 commands
|
||||
- [b776409d](https://git.meli-email.org/meli/meli/commit/b776409d6c9caec3732bada9e25637c2676af3b8) Add thread, env hash index fields
|
||||
- [cc439b23](https://git.meli-email.org/meli/meli/commit/cc439b239ae27ae84fbcf50fbd82ec591c147c94) Add RowsState struct
|
||||
- [db227dea](https://git.meli-email.org/meli/meli/commit/db227dea34caa747e136500356fddf95a91002e6) Add error messages if `mandoc`,`man` binaries are missing
|
||||
- [ee9d458b](https://git.meli-email.org/meli/meli/commit/ee9d458b05ffa0214a4526daf1423916830526bc) Implement mailbox {un,}sub actions
|
||||
- [7af89359](https://git.meli-email.org/meli/meli/commit/7af893597f5a3f3261bfff47dae0723bf1b17e53) Replace use of Self::DESCRIPTION with Shortcuts struct consts
|
||||
- [eaecc5ea](https://git.meli-email.org/meli/meli/commit/eaecc5ea12f4a5ebe309d5654509c0771bbdc2f1) Remove hardcoded major .so version for non linux/macos target_os
|
||||
- [f63ce388](https://git.meli-email.org/meli/meli/commit/f63ce388f7774ea015fdaa2362202c33f3ddacd4) Move ManageMailboxes to Tab Actions
|
||||
- [3c847ad2](https://git.meli-email.org/meli/meli/commit/3c847ad26afcc4a4cdcfbdbf70f35be57d0da1ab) Add beginning of sieve parser
|
||||
- [5443b7e8](https://git.meli-email.org/meli/meli/commit/5443b7e8f300a0084abde7354360ecbe909178bb) Remove literal_map() parse combinator
|
||||
- [12cb717b](https://git.meli-email.org/meli/meli/commit/12cb717bda186b0ebdda18e2215e30b1426fb08a) Add server_password_command to jmap
|
||||
- [428f752b](https://git.meli-email.org/meli/meli/commit/428f752b20cdb1c8ab01e7f3119001cfafca8ef1) Remove obsolete crate::components::mail::get_display_name()
|
||||
- [91557c2c](https://git.meli-email.org/meli/meli/commit/91557c2c4366b481e80943e94f661c8b47150571) Prevent list blank when refreshing account
|
||||
- [d332e457](https://git.meli-email.org/meli/meli/commit/d332e4578d69c4371418fb2bb3c0d75e1960e01f) Add proper Display impl for HeaderName
|
||||
- [f537c249](https://git.meli-email.org/meli/meli/commit/f537c24909d13a53a95b43e265e4cb4c013334ac) Move text field to its own module
|
||||
- [d33f9d54](https://git.meli-email.org/meli/meli/commit/d33f9d54c708699386a3f32e4056ccab6c68528b) Remove unreachable!() in Key::serialize
|
||||
- [330887c4](https://git.meli-email.org/meli/meli/commit/330887c4f5bad5357508b9fa6f723e45ab307d2a) Introduce imap-codec.
|
||||
- [4da53669](https://git.meli-email.org/meli/meli/commit/4da5366959145e166c40297abfdf1876e5addc50) Remove bincode dep, use serde_json for sqlite3 values
|
||||
- [155fb41b](https://git.meli-email.org/meli/meli/commit/155fb41b93708ef8793250f9dea611bc317a86d5) Remove unused Component::set_id method
|
||||
- [575509f1](https://git.meli-email.org/meli/meli/commit/575509f1edc756ad218bb76cf74460d83009c851) Move mail view to listing parent component
|
||||
- [6858ee1f](https://git.meli-email.org/meli/meli/commit/6858ee1fab3bcddbda7335f49c30f36153e8d4b7) Move subcommand handling to its own module
|
||||
- [b0e867eb](https://git.meli-email.org/meli/meli/commit/b0e867eb68dc3dba96de79f7481989187fa12df4) Move src to meli/src
|
||||
- [48a10f72](https://git.meli-email.org/meli/meli/commit/48a10f724171bfae702b7b40438189adbbe75079) Remove unused BackendOp::fetch_flags() method
|
||||
- [073d43b9](https://git.meli-email.org/meli/meli/commit/073d43b9b869fc9d46c5195c31ad6e7806cf486c) Move data files to data subdir
|
||||
- [1e084c1d](https://git.meli-email.org/meli/meli/commit/1e084c1d854ed7efb2254f9e8d52ac13d8badffa) Move backends out of the backends module
|
||||
- [a5446975](https://git.meli-email.org/meli/meli/commit/a5446975c2423654dea9551474a880e94ebdc006) Move braille and screen to their own module files
|
||||
- [005bf388](https://git.meli-email.org/meli/meli/commit/005bf3881ec59d53e4f16473fb3b1857487dae23) Move components/utilities -> utilities
|
||||
- [64ab65dd](https://git.meli-email.org/meli/meli/commit/64ab65ddffe3341bca775acb2289ee00e771fdb0) Move components/contacts -> contacts
|
||||
- [7c9a4b4b](https://git.meli-email.org/meli/meli/commit/7c9a4b4b7c366c967a3378098d210124712fd293) Move components/mail -> mail
|
||||
- [df638cce](https://git.meli-email.org/meli/meli/commit/df638cceec6016760037b650a77143a07cd1e738) Remove stale failing doc code example
|
||||
- [da8e8104](https://git.meli-email.org/meli/meli/commit/da8e81044833975cadb08db836795a389c142e9c) Remove leftover debug prints
|
||||
- [a1e70061](https://git.meli-email.org/meli/meli/commit/a1e7006186474f55cf4a14f53dbd32bdf8ca5993) Move Sort{Order,Field} to utils mod
|
||||
- [66c21ab1](https://git.meli-email.org/meli/meli/commit/66c21ab1734bfbf4e604da505f6b6109008fd7c2) Move StandardHeader to its own module
|
||||
- [946309c6](https://git.meli-email.org/meli/meli/commit/946309c6f3bbc59b53dc2b05732b40f3d445fd9f) Do some small parser refactoring
|
||||
- [b95f7783](https://git.meli-email.org/meli/meli/commit/b95f778335bebd480f69fe66fabec4f8a6e2b587) Move JmapSession to its own module
|
||||
|
||||
### Documentation
|
||||
|
||||
- [a866b294](https://git.meli-email.org/meli/meli/commit/a866b29499b44032545df4941b6cfec4ee2db8bb) Update valid shortcut entries from src/conf/shortcuts.rs
|
||||
- [f76f4ea3](https://git.meli-email.org/meli/meli/commit/f76f4ea3f7416a4a641d5891f19927aa354a3247) Add meli.7, a general tutorial document
|
||||
- [5fa4b626](https://git.meli-email.org/meli/meli/commit/5fa4b6260c60409579fe964970719f9ab60482cc) Add more screenshots
|
||||
- [7c711542](https://git.meli-email.org/meli/meli/commit/7c7115427dd5f6320a4305df3dc88a8567829720) Complete guide document
|
||||
- [30cc5d3d](https://git.meli-email.org/meli/meli/commit/30cc5d3d0220452630780c3238f393b9e1f2b93a) Add edit-config in manpages
|
||||
- [24103f33](https://git.meli-email.org/meli/meli/commit/24103f3310ca533791bdd07643fdb23a10c6031d) Add external-tools.md document
|
||||
- [b6c93e49](https://git.meli-email.org/meli/meli/commit/b6c93e49f2af3001b206a288edea02c58e14aa5b) Add use_tls option in IMAP connection settings
|
||||
- [34a54d3c](https://git.meli-email.org/meli/meli/commit/34a54d3c05efc3b56154179111c3e39e0f3fd8b1) Add some `TODO([#222](https://git.meli-email.org/meli/meli/issues/222))`s.
|
||||
|
||||
### Packaging
|
||||
|
||||
- [671ce9f6](https://git.meli-email.org/meli/meli/commit/671ce9f694a8e941826472caad8051998540bb1f) Add missing build dependencies
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- [25805229](https://git.meli-email.org/meli/meli/commit/2580522931fb29442598ac8932a13eaeb577bace) Log vcard parsing failures
|
||||
- [5f003a31](https://git.meli-email.org/meli/meli/commit/5f003a31be95a3877d1006f8a22e424a1183163d) Parse vCards with just LF instead of CRLF line endings
|
||||
- [d8e9a005](https://git.meli-email.org/meli/meli/commit/d8e9a00563c023abb0ff75aaa4ba3fa92626c5ce) Add quoted REFERENCES field in parsing of responses
|
||||
- [81d12656](https://git.meli-email.org/meli/meli/commit/81d1265601c299dee6405f3f9b4e81f89d3cfe29) Escape IMAP passwords properly
|
||||
- [0d8bedd2](https://git.meli-email.org/meli/meli/commit/0d8bedd2d5d3eb8eee831e75d1e14d45beefb847) Make is_online() await for connection
|
||||
- [d4b690d5](https://git.meli-email.org/meli/meli/commit/d4b690d5d3a7f6a6b57afd7a6177db0db20a9c94) Send password as byte literal on LOGIN
|
||||
- [2eb22a29](https://git.meli-email.org/meli/meli/commit/2eb22a290abb3f37bc77c3bc2771edfb60a1c314) Stop hardcoding certain component colors
|
||||
- [2c23ca34](https://git.meli-email.org/meli/meli/commit/2c23ca34cdee769a0f78a0b0ef934e5f20dd9567) Update most Cargo dependencies
|
||||
- [721891c2](https://git.meli-email.org/meli/meli/commit/721891c2955e9f5e223949bde2dd43604cec8390) Update nom dependency
|
||||
- [4fdc90b3](https://git.meli-email.org/meli/meli/commit/4fdc90b31ea56c046dfe5bf9bee0a118f9c03db1) Use `open` instead of `xdg-open` in macos
|
||||
- [9558b2ae](https://git.meli-email.org/meli/meli/commit/9558b2ae921aa35076f58d68b5898334a2797685) Parse Cp1253 as windows1253 encoding
|
||||
- [6a843d49](https://git.meli-email.org/meli/meli/commit/6a843d49830f8c70f510c4232ea63eb204d35319) Export list_mail_in_maildir_fs() function
|
||||
- [d6355a30](https://git.meli-email.org/meli/meli/commit/d6355a3043ec0b4b2a3e1c3fbb0ed66d2e87e7f4) Impl Debug for ParsingError
|
||||
- [dc5afa13](https://git.meli-email.org/meli/meli/commit/dc5afa13dbea4da042c35e12291c5b5a2846c3ff) Use osascript/applescript for notifications on macos
|
||||
- [e6d6e1f5](https://git.meli-email.org/meli/meli/commit/e6d6e1f588db9793e822cdbb1ce2edb2959170c6) Don't unwrap if pseudoterminal creation fails
|
||||
- [ca84906d](https://git.meli-email.org/meli/meli/commit/ca84906d7ddb1351643998efaa56086e3ba9cf8e) Escape all quotes in applescript on macos
|
||||
- [4a79b202](https://git.meli-email.org/meli/meli/commit/4a79b2021d2fb3edd046197b44b702bdb468fc5e) Update dependency versions
|
||||
- [e29041f7](https://git.meli-email.org/meli/meli/commit/e29041f73354c59ef95916edd75e6ca7876e3c3a) Rename src/bin.rs to src/main.rs
|
||||
- [7650805c](https://git.meli-email.org/meli/meli/commit/7650805c60cec2fe09cd2a59cb665731f5cca140) Bring stripped binary size down to 7MiB
|
||||
- [ca488968](https://git.meli-email.org/meli/meli/commit/ca48896865778df2c79bc1d13f03b5f56136304c) Add strip option to profile.release
|
||||
- [10497952](https://git.meli-email.org/meli/meli/commit/10497952f718b49f3a247741a64361f855b2d4f7) Wrap stdout in BufWriter
|
||||
- [29042aba](https://git.meli-email.org/meli/meli/commit/29042aba593210f3be73010908d5092951b3b1a1) Add mbox date format parse
|
||||
- [480000eb](https://git.meli-email.org/meli/meli/commit/480000ebbb67a80181fd27762ca649acf13df0f3) Show error if account directory does not contain ".notmuch" subdirectory
|
||||
- [a484b397](https://git.meli-email.org/meli/meli/commit/a484b397c68fd126c17073ac9c9f02432c413341) Show informative error messages if libloading fails
|
||||
- [4a20fc42](https://git.meli-email.org/meli/meli/commit/4a20fc42e1f5cad325d5aa439d1baab210aceed8) Update CHANGELOG.md
|
||||
- [a72c96a2](https://git.meli-email.org/meli/meli/commit/a72c96a26afe9e54a0fcadb8c43448f1fdc09ce9) Add 8BITMIME support to smtp client
|
||||
- [3c0f5d82](https://git.meli-email.org/meli/meli/commit/3c0f5d8274d8039b1a2c928f99194835bca7b83a) Add BINARYMIME support to smtp client
|
||||
- [36883692](https://git.meli-email.org/meli/meli/commit/36883692782ed2355a0ec12ccf9f82aa2edcc8c1) Add smtp test
|
||||
- [9cbbf71e](https://git.meli-email.org/meli/meli/commit/9cbbf71e0f8f9115e9e043982f20045cfc550eb7) Add DecodeOptions struct for decoding
|
||||
- [0df46a63](https://git.meli-email.org/meli/meli/commit/0df46a63ec6e30983480f0eb50c8da3f74b4f0b3) Show error if sqlite3 search backend is set but doesn't exist
|
||||
- [a7a50d30](https://git.meli-email.org/meli/meli/commit/a7a50d3078cb7466ab341ddfc30a80c7b1f8dfdb) Box<_> some large fields in biggest types
|
||||
- [d8d43a16](https://git.meli-email.org/meli/meli/commit/d8d43a16fef045a2116ff126e7b6e27817b526fc) Add html_open config setting
|
||||
- [0ed10711](https://git.meli-email.org/meli/meli/commit/0ed10711ef542cc13eaaef809fa557468b3d6696) Add new_mail_script option
|
||||
- [c3fdafde](https://git.meli-email.org/meli/meli/commit/c3fdafde3b69c0abc78a62926e0c32fc3dd602d6) Documentation touchups
|
||||
- [347be543](https://git.meli-email.org/meli/meli/commit/347be54305c60350b055a1da3a1abfa4d33d3f22) Add NetworkErrorKind enum
|
||||
- [0c08cb73](https://git.meli-email.org/meli/meli/commit/0c08cb737ceaa5c738712905c7d57f956d449ed0) Mark mailboxes as subscribed on personal accounts
|
||||
- [129573e0](https://git.meli-email.org/meli/meli/commit/129573e0fd9b42ebf14c2de176e65b92bf8479bd) Rename root_path to root_mailbox
|
||||
- [7e09b180](https://git.meli-email.org/meli/meli/commit/7e09b1807ffa9bae54da35b02c83b5aaee455819) Replace _Ref deref unwraps with expect()
|
||||
- [55ed9624](https://git.meli-email.org/meli/meli/commit/55ed962425ba25d2317946705ff6861a77eb770f) Use server_url instead of server_hostname + server_port in config
|
||||
- [0ef4dde9](https://git.meli-email.org/meli/meli/commit/0ef4dde9392452f7cf7f18294f747fc6e0babb8d) Wrap serde_json deserialize errors in human readable errors
|
||||
- [dd0baa82](https://git.meli-email.org/meli/meli/commit/dd0baa82e9789da23c8f9b06925776c7f80e2568) Spawn user-given command strings with sh -c ".."
|
||||
- [3697b7d9](https://git.meli-email.org/meli/meli/commit/3697b7d960cc9dbe602fa84f861cea854b600b73) Don't use LC_ category in place of LC_ masks in libc calls
|
||||
- [6d20abdd](https://git.meli-email.org/meli/meli/commit/6d20abdde7b4cec6ec1af7c097f01042ea05cfbb) Add #[allow(deref_nullptr)] in bindgen tests
|
||||
- [17b42b1a](https://git.meli-email.org/meli/meli/commit/17b42b1a6c721fb2e369c2a300867c8db2beb959) Add json deserialization tests
|
||||
- [64346dd3](https://git.meli-email.org/meli/meli/commit/64346dd3fe0ef40025ec6fdb01d18eb38f7e7f65) Add map_res, quoted_slice, is_a, alt, take, take_literal
|
||||
- [56fc43bc](https://git.meli-email.org/meli/meli/commit/56fc43bcf869a867455b44d007b9d3d17422bc8d) Add As{Ref,Mut} impls for RwRef{,Mut}
|
||||
- [63179841](https://git.meli-email.org/meli/meli/commit/631798413659a320dcd9574e0bca7b7d75cc8d6c) Add --bin flag to meli cargo build target
|
||||
- [ded9adde](https://git.meli-email.org/meli/meli/commit/ded9adde614ac3d38045fa97a0f5144b80855fe7) More descriptive "Unimplemented" messages
|
||||
- [2224a710](https://git.meli-email.org/meli/meli/commit/2224a7100f9bc6c44bc66117a88556003e74186e) Reset imap cache on init error
|
||||
- [252d2bdf](https://git.meli-email.org/meli/meli/commit/252d2bdf2f12c8954f8b299000bbde6219d25335) Replace hardcoded /bin/false with 'false'
|
||||
- [2427b097](https://git.meli-email.org/meli/meli/commit/2427b097c5c40f3212a105cb40f913c9860ae2a8) Make tag_default background lighter on light theme
|
||||
- [7382e301](https://git.meli-email.org/meli/meli/commit/7382e30160a934ce97dd73c1be44640d5b4a4c75) Convert EnvelopeHash from typedef to wrapper struct
|
||||
- [259aeb00](https://git.meli-email.org/meli/meli/commit/259aeb00877557ee85b5cc555d50e605b85b3109) Convert {Account,Mailbox}Hash from typedef to wrapper struct
|
||||
- [5634f955](https://git.meli-email.org/meli/meli/commit/5634f9555315deb2d39ed8fce577a35f4d535ac1) Rename MeliError struct to Error
|
||||
- [7606317f](https://git.meli-email.org/meli/meli/commit/7606317f24d076bdc7db873c2b15811728ed946a) Add support for virtual mailbox hierarchy
|
||||
- [2878bbb8](https://git.meli-email.org/meli/meli/commit/2878bbb8c887275d26264bf7201a632161c4048a) Add parser for mutt alias file
|
||||
- [de2f46fe](https://git.meli-email.org/meli/meli/commit/de2f46fe611726a445c1e06cbc35343e716aa335) Rustfmt changes
|
||||
- [f9ac9b60](https://git.meli-email.org/meli/meli/commit/f9ac9b607a2bd01e42c81cfab3c933df28ff1676) Temporarily disable libgpgme functions because of a bug
|
||||
- [256a3e25](https://git.meli-email.org/meli/meli/commit/256a3e252e2e4db9af9a04c7df1a52eeaf2bbfc9) Update minimum supported rust version
|
||||
- [fbc1007f](https://git.meli-email.org/meli/meli/commit/fbc1007ff4f41bac888a1b53c156feec4f795403) Deserialize `null` to empty vec for messageId
|
||||
- [d7ec97f0](https://git.meli-email.org/meli/meli/commit/d7ec97f03bc0e815e160a142f871dc764d416af1) Small rustfmt change
|
||||
- [2447a2cb](https://git.meli-email.org/meli/meli/commit/2447a2cbfeaa8d6f7ec11a2a8a6f3be1ff2fea58) Avoid relying on hardcoded hash values
|
||||
- [d679a744](https://git.meli-email.org/meli/meli/commit/d679a74450b35724301c81da1644bcedb1c54045) Implement Bearer token authentication
|
||||
- [47e6d5d9](https://git.meli-email.org/meli/meli/commit/47e6d5d935a2b5124efbe847dac885b859200469) Add edit-config CLI subcommand that opens config files on EDITOR
|
||||
- [3a02b6fb](https://git.meli-email.org/meli/meli/commit/3a02b6fb8024e6bb046fc167e7527aad1b192202) Mention how to override w3m with html_filter
|
||||
- [85d4316a](https://git.meli-email.org/meli/meli/commit/85d4316a6a8703ac3e4923cf99ce8c4bb22bb4ae) Replace old logging module with the `log` create
|
||||
- [1f1ea307](https://git.meli-email.org/meli/meli/commit/1f1ea307698a5a7f62f5ab2ea1594aef4d8f48a8) On draw() set dirty on return
|
||||
- [77020e0c](https://git.meli-email.org/meli/meli/commit/77020e0c19873b8053321132ff5b58181c567fcd) Update CHANGELOG.md
|
||||
- [682ea554](https://git.meli-email.org/meli/meli/commit/682ea5547e380deeb215503b39c8aa66c65b3cac) Add `.idea` (CLion) to `.gitignore`.
|
||||
- [f63f6445](https://git.meli-email.org/meli/meli/commit/f63f6445addeccee1a6b830f1c101a043612ea4e) Improve error message when `m4` executable is missing.
|
||||
- [cc27639f](https://git.meli-email.org/meli/meli/commit/cc27639fca0dcb3a5ff9fceef8666dbbf047adaa) Use Envelope attachments when editing and don't add already existing headers
|
||||
- [30866f75](https://git.meli-email.org/meli/meli/commit/30866f752b21802b64ce7d2e02c9962c1091c9d8) Bypass rustfmt bug.
|
||||
- [235fceaf](https://git.meli-email.org/meli/meli/commit/235fceaf2168af50c3804cecfbf69e64ff42598c) Add standard heeder constants in email::headers
|
||||
- [aebff3d3](https://git.meli-email.org/meli/meli/commit/aebff3d3d9864b8854aba5e7f43a61d515e8057f) Implement mailto RFC properly
|
||||
- [954329d8](https://git.meli-email.org/meli/meli/commit/954329d848a5b3e73fca50ed1db9859118bed6dd) Set file extensions to temp files, use `open` in macos
|
||||
- [58889bca](https://git.meli-email.org/meli/meli/commit/58889bcadd44d6aec2eddd17cf5ecb1e07531cbe) Add show_extra_headers option
|
||||
- [23d95973](https://git.meli-email.org/meli/meli/commit/23d95973d4f574fe431441df97ceaef0e3e4762f) Add search.rs module
|
||||
- [6bf1756d](https://git.meli-email.org/meli/meli/commit/6bf1756de844386ba312d15109ae29951896147b) Implement more search criteria in Query type
|
||||
- [299c8e0f](https://git.meli-email.org/meli/meli/commit/299c8e0f993c4ac88005a5c9e708d9e214b20ac1) Restructure pub use melib::* imports
|
||||
- [f8623d4b](https://git.meli-email.org/meli/meli/commit/f8623d4b2c386f51f1d11a23900503d8165ac9f3) Implement more ResponseCode cases
|
||||
- [b92a80a2](https://git.meli-email.org/meli/meli/commit/b92a80a23afb96fbd63031704e4656cc8a00526c) Resync even if UIDVALIDITY is missing from cache
|
||||
- [bf615e7d](https://git.meli-email.org/meli/meli/commit/bf615e7d933b474942d421eafc1015aeb28f8516) Check for case when envelope has its own message id in References and In-Reply-To
|
||||
- [e0257c9d](https://git.meli-email.org/meli/meli/commit/e0257c9d8d6f234f71852a0080d443b063d5e6d7) Run cargo-sort
|
||||
- [d7e6b40b](https://git.meli-email.org/meli/meli/commit/d7e6b40b7e1f501fdaaba54880e9c7a4b0e01288) Auto re-index sqlite3 database if it's missing
|
||||
- [cd85d833](https://git.meli-email.org/meli/meli/commit/cd85d83324a009ea4b86ac22af395145a9e999ab) Replace timestamp with Date value in message/rfc822 Display
|
||||
- [579372b4](https://git.meli-email.org/meli/meli/commit/579372b4a75e39c9e84010de16d7d46294bed04a) Improve readability of `Envelope`.
|
||||
- [6c6d9f4b](https://git.meli-email.org/meli/meli/commit/6c6d9f4b4e0d16b5a73ae8e2a2fb2a6f124df7e6) Improve ordering of `flag_impl!`s.
|
||||
- [8f14a237](https://git.meli-email.org/meli/meli/commit/8f14a2373e16b9b4af22f9388fae84235dd08123) Put imap-codec logic under the imap_backend feature
|
||||
- [fd0faade](https://git.meli-email.org/meli/meli/commit/fd0faade066a18466e683361211bba569956bf63) Add connection instance id string for debugging in logs
|
||||
- [5c9b3fb0](https://git.meli-email.org/meli/meli/commit/5c9b3fb0448fa3689ff33faba3dde03c49347f61) Impl Component for Box<dyn Component>
|
||||
- [45bac6eb](https://git.meli-email.org/meli/meli/commit/45bac6eb16a5a093193d5beb4d80040ce161304a) Tidy up use of debug!
|
||||
- [5699baec](https://git.meli-email.org/meli/meli/commit/5699baecfba9cb15aac04a6b400cfb6bc881e2c5) Add utils::{futures, random}
|
||||
- [b05d9299](https://git.meli-email.org/meli/meli/commit/b05d92997546e438b202d336fc581c2514c63b9f) Impl exponential backoff when retrying connection
|
||||
- [f5cfbd32](https://git.meli-email.org/meli/meli/commit/f5cfbd32e6ebbe83ad7e84d048f1fbf2e51ca605) On set_flags, update {un,}seen sets in all mailboxes
|
||||
- [f0d88005](https://git.meli-email.org/meli/meli/commit/f0d88005fbabcd552593ba0fe785e89a3560ac1c) Change message/rfc822 Display repr
|
||||
- [f98e36ce](https://git.meli-email.org/meli/meli/commit/f98e36cee514f643e0fe256857cf31e2e0f24080) Replace old-style /*! module doc comments with //!
|
||||
- [1bcc0bbe](https://git.meli-email.org/meli/meli/commit/1bcc0bbece2f479950e8811261befedc0199dab9) Add mbox parsing test
|
||||
- [619fbef1](https://git.meli-email.org/meli/meli/commit/619fbef129e249489e64a26e1d0dfbd02db2516a) Recursively calculate update_show_subject()
|
||||
- [957abf4e](https://git.meli-email.org/meli/meli/commit/957abf4e7238ec74b2194a21533b69dd1a58c0a8) Update cargo dependencies
|
||||
- [9d51b6bd](https://git.meli-email.org/meli/meli/commit/9d51b6bd525784bc108959519c8dd21d30a8b020) Update `imap-codec`.
|
||||
- [7c33f899](https://git.meli-email.org/meli/meli/commit/7c33f8999b6a5efd911680f2b83a3ff3a682a715) Use published imap-codec 0.10.0.
|
||||
- [3803d788](https://git.meli-email.org/meli/meli/commit/3803d788abc5157b9cc6368da7e54aced9604aec) If auth is false checks if config has password entry
|
||||
- [866166eb](https://git.meli-email.org/meli/meli/commit/866166eb8e8b994c8c87aad92a3303f9f6449b2d) Don't print parsing error for empty bytes
|
||||
- [5b5869a2](https://git.meli-email.org/meli/meli/commit/5b5869a2ec3fce2fc69aa5c83fbda7a767f2a402) Re-enable print to stderr ifdef MELI_DEBUG_STDERR
|
||||
- [13fe64a0](https://git.meli-email.org/meli/meli/commit/13fe64a027895780efdb6bfee246d562741a4be1) Cache pgp signature verification results
|
||||
- [5ceddf41](https://git.meli-email.org/meli/meli/commit/5ceddf412e3b215b712e55aea8e18887d2d39f1a) Update CHANGELOG.md
|
||||
- [4e55fbc9](https://git.meli-email.org/meli/meli/commit/4e55fbc90d8b105788c7c5998cb26b2829ac87a2) Add SEEN flag to all envs, since NNTP has no flags
|
||||
- [e9cd800f](https://git.meli-email.org/meli/meli/commit/e9cd800f49e2d0e155d434ff8e91462e20b9d4f5) Add support for storing read status locally
|
||||
- [53cba4be](https://git.meli-email.org/meli/meli/commit/53cba4beee4f774b548881c1a3f207ca391d3df3) Update README.md relative file paths
|
||||
- [c4c245ee](https://git.meli-email.org/meli/meli/commit/c4c245ee19137f64d836401f7c1de17c9eb42b6e) Respect danger_accept_invalid_certs setting
|
||||
- [29b43e2c](https://git.meli-email.org/meli/meli/commit/29b43e2c88edcfdecffd076fbb773c8547425f12) Replace mktime with timegm
|
||||
- [4874e30f](https://git.meli-email.org/meli/meli/commit/4874e30f3ce9b186ac7cd427cba4a8542bd5048e) Add smtp-trace feature
|
||||
- [51e9fbe8](https://git.meli-email.org/meli/meli/commit/51e9fbe8f2c380f3c9ee6a9ee65e638c169b43ef) Add account_name identifier to sqlite3 index database name
|
||||
- [129f1091](https://git.meli-email.org/meli/meli/commit/129f10911b01641940801586bfa5286307e4342f) Rename `imap_backend` feature to `imap`
|
||||
- [fe027fa3](https://git.meli-email.org/meli/meli/commit/fe027fa300a9882730a558fffe6000527ef08ff8) Rename `maildir_backend` feature to `maildir`
|
||||
- [fe7dcc50](https://git.meli-email.org/meli/meli/commit/fe7dcc508ee51f492df2de3884147531fada6f4e) Rename `notmuch_backend` feature to `notmuch`
|
||||
- [e9f09a15](https://git.meli-email.org/meli/meli/commit/e9f09a153ca0a1a023efe924b314ea977ccc3c25) Rename `mbox_backend` feature to `mbox`
|
||||
- [7db930ca](https://git.meli-email.org/meli/meli/commit/7db930cabd295e888f4f106d5e7ea411521340ff) Rename `jmap_backend` feature to `jmap`
|
||||
- [89c90f22](https://git.meli-email.org/meli/meli/commit/89c90f224a68ec524f7dc7033955ce7b8196f493) Add `nntp` feature
|
||||
- [b65934fa](https://git.meli-email.org/meli/meli/commit/b65934facc7aeeb8ab30603e16cef2b747f9a0e5) Add nntp-trace feature
|
||||
- [8ecdb6df](https://git.meli-email.org/meli/meli/commit/8ecdb6df3189cae4b6fa21a177bde756cc4407cf) Add imap-trace feature
|
||||
- [9216e7bc](https://git.meli-email.org/meli/meli/commit/9216e7bc657738ae9861583a837c1326398197e4) Add opt id string for tracing
|
||||
- [ae25ffba](https://git.meli-email.org/meli/meli/commit/ae25ffba430572efe73fde05eaf8111453f814cf) Don't do plain EHLO before starting Tls connection
|
||||
- [8cb2a515](https://git.meli-email.org/meli/meli/commit/8cb2a515e1ba31efe914db67504993bc081ed7f3) Use localhost in lieu of 127.0.0.1 for CI
|
||||
- [0ee1b6e0](https://git.meli-email.org/meli/meli/commit/0ee1b6e01830c01871e93e27d735a39792202325) Start background watch job in init
|
||||
- [448e0635](https://git.meli-email.org/meli/meli/commit/448e0635e00b533a4d9dc15ba65982097649b397) Log error when command length exceeds 512 octets
|
||||
- [bf543855](https://git.meli-email.org/meli/meli/commit/bf543855dc143b25344b79303f017380c9773793) Add PartialEq<str> for MessageID
|
||||
- [7c7f6e19](https://git.meli-email.org/meli/meli/commit/7c7f6e1923e8b3127cf7cbd4b18f1db3ed9c6583) Don't increase Thread length for duplicates
|
||||
- [5c2b0471](https://git.meli-email.org/meli/meli/commit/5c2b04719b953373c6a657f22db295d08b94685e) Normalize std::fmt::* imports
|
||||
- [0f60009e](https://git.meli-email.org/meli/meli/commit/0f60009ea909adfb8f4e85d942decb8bc60f7539) Add RUSTFLAGS with -D warnings
|
||||
- [6578a566](https://git.meli-email.org/meli/meli/commit/6578a5666889434ed6ca2f276e365633956fe3d3) Update cargo install directions
|
||||
- [4f6081b6](https://git.meli-email.org/meli/meli/commit/4f6081b6633aed1eeafd99c24aa2dc64397043ca) Update to `imap-codec 1.0.0-beta`.
|
||||
- [dc2b0044](https://git.meli-email.org/meli/meli/commit/dc2b00442b04c21455a6fda59b4729d0cbd04eff) Run rustfmt and cargo-sort
|
||||
- [b3858de2](https://git.meli-email.org/meli/meli/commit/b3858de2f4e12723ee922174c79cc36062bed54e) Impl From<io::ErrorKind> for ErrorKind
|
||||
- [f93adb68](https://git.meli-email.org/meli/meli/commit/f93adb683a562f25e40ffa03f80d04d5ad8ca34f) Replace change_color uses with change_theme
|
||||
- [f193bdf6](https://git.meli-email.org/meli/meli/commit/f193bdf685e06652ab5b2da2a9a01fa56620cda6) Add column headers and sorting
|
||||
- [095d24f9](https://git.meli-email.org/meli/meli/commit/095d24f91447a2ecab6d6bc78e1705ea4394e9bd) Add PULL_REQUEST_TEMPLATE.md
|
||||
- [ab57e942](https://git.meli-email.org/meli/meli/commit/ab57e9420db29efd42773e970f33751b7b3f6f26) Add delete_contact shortcut
|
||||
- [3963103d](https://git.meli-email.org/meli/meli/commit/3963103d55db28f789fe39f0dd80cd0d57792b5d) Prevent duplicate contact creation
|
||||
- [f162239f](https://git.meli-email.org/meli/meli/commit/f162239fcc87d9c4f8aba8c33a9812a5e691c8d9) Change `on:` conditions for test.yaml
|
||||
- [974b3a53](https://git.meli-email.org/meli/meli/commit/974b3a53058181e3df992a2105abcbf1c392fc19) Update bitflags, rusqlite dependencies
|
||||
- [4d22b669](https://git.meli-email.org/meli/meli/commit/4d22b669bf330f8f3168fc2f704ad63c21c5e821) Update dependencies
|
||||
- [ffba203a](https://git.meli-email.org/meli/meli/commit/ffba203a3b7070cc9e71d9444556e108ff0e18ea) Add support for Home and End key navigation
|
||||
- [3433f7c4](https://git.meli-email.org/meli/meli/commit/3433f7c41e0d0cbb48af821280537da41b9e53d0) Update PULL_REQUEST_TEMPLATE.md
|
||||
- [f7a4741b](https://git.meli-email.org/meli/meli/commit/f7a4741bf1622ae60042fb6ab0a906fe50fb1e06) Add jmap-trace feature
|
||||
- [c875dda4](https://git.meli-email.org/meli/meli/commit/c875dda4960e5688b17176ba82ad1e5da38b883b) Add last_method_response field to Connection
|
||||
- [37a787e6](https://git.meli-email.org/meli/meli/commit/37a787e6bb5abd34fae2888944537dec1ee3842f) Use IndexMap instead of HashMap
|
||||
- [6ebdc7f9](https://git.meli-email.org/meli/meli/commit/6ebdc7f9aec5531c2b562a4e0cfd320ead6a4c01) Add Id<_>::empty() contructor
|
||||
- [4f9b9773](https://git.meli-email.org/meli/meli/commit/4f9b97736a4af8b8b4ba0017ad1175a1c2352db6) Rename EmailImport to EmailImportObject
|
||||
- [11432ba2](https://git.meli-email.org/meli/meli/commit/11432ba2c381b07bb540f7f92664b3c351e3cf62) Make `null` fields into Option<_>s
|
||||
- [d9467d5f](https://git.meli-email.org/meli/meli/commit/d9467d5fcd9543611ec8a034eb7e25d12a3dcc45) Save all core capabilities to session store
|
||||
- [31982931](https://git.meli-email.org/meli/meli/commit/31982931f5f472717b4c3d900f16c0588682f48e) Use Argument<OBJ> (value or resultreference) where appropriate
|
||||
- [29fd8522](https://git.meli-email.org/meli/meli/commit/29fd8522e6bc2b0b6196cb97c8868dc34c2ba2f0) Implement Backend::create_mailbox()
|
||||
- [5d8f07c8](https://git.meli-email.org/meli/meli/commit/5d8f07c8058261c7c251b3fb010ad866110e91df) Rename some objects better
|
||||
- [38bc1369](https://git.meli-email.org/meli/meli/commit/38bc1369cc136c482f48d1ed3172b7f510ff7762) Add an Identity type.
|
||||
- [59513b26](https://git.meli-email.org/meli/meli/commit/59513b267097cac8fe757c6198f26e0179014604) Implement Backend::submit(), server-side submission
|
||||
- [5459a84f](https://git.meli-email.org/meli/meli/commit/5459a84f3d2b4c91a89252fba63f4ef12d965b9b) Update to imap-codec 1.0.0 (w/o `-beta`)
|
||||
- [290cfb86](https://git.meli-email.org/meli/meli/commit/290cfb86c0c942690c48a0d3298e9d2de3ec4d94) Add a highlighted_selected theme key
|
||||
- [46636d87](https://git.meli-email.org/meli/meli/commit/46636d8748f2779f38a10c6bf38c4e07acf16f8a) Bump version to 0.8.0
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- [1d0405ed](https://git.meli-email.org/meli/meli/commit/1d0405ed5b5cd76f4fe79e73fb30f4d4dce1d441) Add env vars
|
||||
- [6e27edcb](https://git.meli-email.org/meli/meli/commit/6e27edcb775ce831b784d2040672f2d2af2c020f) Use cargo-nextest
|
||||
- [67d2da0f](https://git.meli-email.org/meli/meli/commit/67d2da0f88b0e7b9b74c5d05c6c17a45057b094a) Disable smtp::test::test_smtp in test.yaml
|
||||
|
||||
## [alpha-0.7.2] - 2021-10-15
|
||||
|
||||
### Added
|
||||
|
||||
- Add forward mail option
|
||||
- Add url_launcher config setting
|
||||
- Add add_addresses_to_contacts command
|
||||
- Add show_date_in_my_timezone pager config flag
|
||||
- docs: add pager filter documentation
|
||||
- mail/view: respect per-folder/account pager filter override
|
||||
- pager: add filter command, esc to clear filter
|
||||
- Show compile time features in with command argument
|
||||
|
||||
### Fixed
|
||||
|
||||
- melib/email/address: quote display_name if it contains ","
|
||||
- melib/smtp: fix Cc and Bcc ignored when sending mail
|
||||
- melib/email/address: quote display_name if it contains "."
|
||||
|
||||
## [alpha-0.7.1] - 2021-09-08
|
||||
|
||||
### Added
|
||||
|
||||
- Change all Down/Up shortcuts to j/k
|
||||
- add 'GB18030' charset
|
||||
- melib/nntp: implement refresh
|
||||
- melib/nntp: update total/new counters on new articles
|
||||
- melib/nntp: implement NNTP posting
|
||||
- configs: throw error on extra unused conf flags in some imap/nntp
|
||||
- configs: throw error on missing `composing` section with explanation
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix compilation for netbsd-9.2
|
||||
- conf: fixed some boolean flag values requiring to be string e.g. "true"
|
||||
|
||||
## [alpha-0.7.0] - 2021-09-03
|
||||
|
||||
### Added
|
||||
|
||||
Notable changes:
|
||||
|
||||
- add import command to import email from files into accounts
|
||||
- add add-attachment-file-picker command and `file_picker_command` setting to
|
||||
use external commands to choose files when composing new mail
|
||||
- ask confirm for delete
|
||||
- add export-mbox command
|
||||
- add export-mail command
|
||||
- add TLS support with nntp
|
||||
- add JMAP watch with polling
|
||||
- add reload-config command
|
||||
- add import-mail command
|
||||
- imap: implement gmail XOAUTH2 authentication method
|
||||
- imap: implement OAUTH2 authentication
|
||||
- compose: treat inline message/rfc822 as attachments
|
||||
- add gpg support via libgpgme
|
||||
|
||||
### Fixed
|
||||
|
||||
- Loading notmuch library on macos
|
||||
- Limit dbus dependency to target_os = "linux"
|
||||
- IMAP, notmuch, mbox backends: various performance fixes
|
||||
|
||||
## [alpha-0.6.2] - 2020-09-24
|
||||
|
||||
|
@ -109,3 +723,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[alpha-0.6.0]: https://github.com/meli/meli/releases/tag/alpha-0.6.0
|
||||
[alpha-0.6.1]: https://github.com/meli/meli/releases/tag/alpha-0.6.1
|
||||
[alpha-0.6.2]: https://github.com/meli/meli/releases/tag/alpha-0.6.2
|
||||
[alpha-0.7.0]: https://github.com/meli/meli/releases/tag/alpha-0.7.0
|
||||
[alpha-0.7.1]: https://github.com/meli/meli/releases/tag/alpha-0.7.1
|
||||
[alpha-0.7.2]: https://github.com/meli/meli/releases/tag/alpha-0.7.2
|
||||
[v0.8.0]: https://git.meli-email.org/meli/meli/releases/tag/v0.8.0
|
||||
[v0.8.1]: https://git.meli-email.org/meli/meli/releases/tag/v0.8.1
|
||||
[v0.8.2]: https://git.meli-email.org/meli/meli/releases/tag/v0.8.2
|
||||
[v0.8.3]: https://git.meli-email.org/meli/meli/releases/tag/v0.8.3
|
||||
[v0.8.4]: https://git.meli-email.org/meli/meli/releases/tag/v0.8.4
|
||||
|
|
90
Cargo.toml
|
@ -1,90 +1,14 @@
|
|||
[package]
|
||||
name = "meli"
|
||||
version = "0.6.2"
|
||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||
edition = "2018"
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
license = "GPL-3.0-or-later"
|
||||
readme = "README.md"
|
||||
description = "terminal mail client"
|
||||
homepage = "https://meli.delivery"
|
||||
repository = "https://git.meli.delivery/meli/meli.git"
|
||||
keywords = ["mail", "mua", "maildir", "terminal", "imap"]
|
||||
categories = ["command-line-utilities", "email"]
|
||||
default-run = "meli"
|
||||
|
||||
[[bin]]
|
||||
name = "meli"
|
||||
path = "src/bin.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "managesieve-meli"
|
||||
#path = "src/managesieve.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "async"
|
||||
#path = "src/async.rs"
|
||||
|
||||
[dependencies]
|
||||
xdg = "2.1.0"
|
||||
crossbeam = "0.7.2"
|
||||
signal-hook = "0.1.12"
|
||||
signal-hook-registry = "1.2.0"
|
||||
nix = "0.17.0"
|
||||
melib = { path = "melib", version = "0.6.2" }
|
||||
|
||||
serde = "1.0.71"
|
||||
serde_derive = "1.0.71"
|
||||
serde_json = "1.0"
|
||||
toml = { version = "0.5.6", features = ["preserve_order", ] }
|
||||
indexmap = { version = "^1.6", features = ["serde-1", ] }
|
||||
linkify = "0.4.0"
|
||||
notify = "4.0.1" # >:c
|
||||
termion = "1.5.1"
|
||||
bincode = "^1.3.0"
|
||||
uuid = { version = "0.8.1", features = ["serde", "v4"] }
|
||||
unicode-segmentation = "1.2.1" # >:c
|
||||
libc = {version = "0.2.59", features = ["extra_traits",]}
|
||||
smallvec = { version = "^1.5.0", features = ["serde", ] }
|
||||
bitflags = "1.0"
|
||||
pcre2 = { version = "0.2.3", optional = true }
|
||||
structopt = { version = "0.3.14", default-features = false }
|
||||
svg_crate = { version = "0.8.0", optional = true, package = "svg" }
|
||||
futures = "0.3.5"
|
||||
async-task = "3.0.0"
|
||||
num_cpus = "1.12.0"
|
||||
flate2 = { version = "1.0.16", optional = true }
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
notify-rust = { version = "^4", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
syn = { version = "1.0.31", features = [] }
|
||||
quote = "^1.0"
|
||||
proc-macro2 = "1.0.18"
|
||||
flate2 = { version = "1.0.16", optional = true }
|
||||
members = [
|
||||
"meli",
|
||||
"melib",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
opt-level = "s"
|
||||
debug = false
|
||||
|
||||
[workspace]
|
||||
members = ["melib", "tools", ]
|
||||
|
||||
[features]
|
||||
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications", "gpgme"]
|
||||
notmuch = ["melib/notmuch_backend", ]
|
||||
jmap = ["melib/jmap_backend",]
|
||||
sqlite3 = ["melib/sqlite3"]
|
||||
smtp = ["melib/smtp"]
|
||||
regexp = ["pcre2"]
|
||||
dbus-notifications = ["notify-rust",]
|
||||
cli-docs = ["flate2"]
|
||||
svgscreenshot = ["svg_crate"]
|
||||
gpgme = ["melib/gpgme"]
|
||||
|
||||
# Print tracing logs as meli runs in stderr
|
||||
# enable for debug tracing logs: build with --features=debug-tracing
|
||||
debug-tracing = ["melib/debug-tracing", ]
|
||||
strip = true
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
[target.aarch64-unknown-linux-gnu]
|
||||
# Build with -static features.
|
||||
pre-build = [
|
||||
"export DEBIAN_FRONTEND=noninteractive ",
|
||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
"apt-get update -y",
|
||||
"""
|
||||
apt-get install --assume-yes \
|
||||
pkg-config \
|
||||
libdbus-1-dev \
|
||||
libdbus-1-dev:$CROSS_DEB_ARCH \
|
||||
librust-libdbus-sys-dev \
|
||||
librust-libdbus-sys-dev:$CROSS_DEB_ARCH \
|
||||
librust-openssl-sys-dev \
|
||||
librust-openssl-sys-dev:$CROSS_DEB_ARCH \
|
||||
libsqlite3-dev:$CROSS_DEB_ARCH \
|
||||
libssl-dev \
|
||||
libssl-dev:$CROSS_DEB_ARCH \
|
||||
sqlite3:$CROSS_DEB_ARCH
|
||||
""",
|
||||
]
|
|
@ -0,0 +1,67 @@
|
|||
# Development
|
||||
|
||||
Code style follows the `rustfmt.toml` file.
|
||||
|
||||
## Trace logs
|
||||
|
||||
Enable trace logs to `stderr` with:
|
||||
|
||||
```sh
|
||||
export MELI_DEBUG_STDERR=yes
|
||||
```
|
||||
|
||||
This means you will have to to redirect `stderr` to a file like `meli 2> trace.log`.
|
||||
|
||||
Tracing is opt-in by build features:
|
||||
|
||||
```sh
|
||||
cargo build --features=debug-tracing,imap-trace,smtp-trace
|
||||
```
|
||||
|
||||
## use `.git-blame-ignore-revs` file _optional_
|
||||
|
||||
Use this file to ignore formatting commits from `git-blame`.
|
||||
It needs to be set up per project because `git-blame` will fail if it's missing.
|
||||
|
||||
```sh
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
```
|
||||
|
||||
## Formatting with `rustfmt`
|
||||
|
||||
```sh
|
||||
make fmt
|
||||
```
|
||||
|
||||
## Linting with `clippy`
|
||||
|
||||
```sh
|
||||
make lint
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
make test
|
||||
```
|
||||
|
||||
How to run specific tests:
|
||||
|
||||
```sh
|
||||
cargo test -p {melib, meli} (-- --nocapture) (--test test_name)
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
```sh
|
||||
perf record -g target/debug/meli
|
||||
perf script | stackcollapse-perf | rust-unmangle | flamegraph > perf.svg
|
||||
```
|
||||
<!-- -->
|
||||
<!-- ## Running fuzz targets -->
|
||||
<!-- -->
|
||||
<!-- Note: `cargo-fuzz` requires the nightly toolchain. -->
|
||||
<!-- -->
|
||||
<!-- ```sh -->
|
||||
<!-- cargo +nightly fuzz run envelope_parse -- -dict=fuzz/envelope_tokens.dict -->
|
||||
<!-- ``` -->
|
157
Makefile
|
@ -16,6 +16,16 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
CARGO_TARGET_DIR ?= target
|
||||
CARGO_BIN ?= cargo
|
||||
TAGREF_BIN ?= tagref
|
||||
CARGO_ARGS ?=
|
||||
RUSTFLAGS ?= -D warnings -W unreachable-pub -W rust-2021-compatibility
|
||||
CARGO_SORT_BIN = cargo-sort
|
||||
CARGO_HACK_BIN = cargo-hack
|
||||
PRINTF = /usr/bin/printf
|
||||
|
||||
# Options
|
||||
PREFIX ?= /usr/local
|
||||
|
@ -23,20 +33,16 @@ EXPANDED_PREFIX := `cd ${PREFIX} && pwd -P`
|
|||
BINDIR ?= ${EXPANDED_PREFIX}/bin
|
||||
MANDIR ?= ${EXPANDED_PREFIX}/share/man
|
||||
|
||||
CARGO_TARGET_DIR ?= target
|
||||
MIN_RUSTC ?= 1.39.0
|
||||
CARGO_BIN ?= cargo
|
||||
CARGO_ARGS ?=
|
||||
|
||||
# Installation parameters
|
||||
DOCS_SUBDIR ?= docs/
|
||||
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5
|
||||
DOCS_SUBDIR ?= meli/docs/
|
||||
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5 meli.7
|
||||
FEATURES ?= --features "${MELI_FEATURES}"
|
||||
|
||||
MANPATHS != ACCUM="";for m in `manpath 2> /dev/null | tr ':' ' '`; do if [ -d "$${m}" ]; then REAL_PATH=`cd $${m} && pwd` ACCUM="$${ACCUM}:$${REAL_PATH}";fi;done;echo -n $${ACCUM} | sed 's/^://'
|
||||
VERSION != sed -n "s/^version\s*=\s*\"\(.*\)\"/\1/p" Cargo.toml
|
||||
GIT_COMMIT != git show-ref -s --abbrev HEAD
|
||||
DATE != date -I
|
||||
MANPATHS != ACCUM="";for m in `manpath 2> /dev/null | tr ':' ' '`; do if [ -d "$${m}" ]; then REAL_PATH=`cd $${m} && pwd` ACCUM="$${ACCUM}:$${REAL_PATH}";fi;done;echo $${ACCUM}'\c' | sed 's/^://'
|
||||
VERSION = `grep -m1 version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1`
|
||||
MIN_RUSTC = `grep -m1 rust-version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1`
|
||||
GIT_COMMIT = `git show-ref -s --abbrev HEAD`
|
||||
DATE = `date -I`
|
||||
|
||||
# Output parameters
|
||||
BOLD ?= `[ -z $${TERM} ] && echo "" || tput bold`
|
||||
|
@ -45,19 +51,20 @@ ANSI_RESET ?= `[ -z $${TERM} ] && echo "" || tput sgr0`
|
|||
CARGO_COLOR ?= `[ -z $${NO_COLOR+x} ] && echo "" || echo "--color=never "`
|
||||
RED ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 1) || echo ""`
|
||||
GREEN ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 2) || echo ""`
|
||||
YELLOW ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 3) || echo ""`
|
||||
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
.PHONY: meli
|
||||
meli: check-deps
|
||||
@${CARGO_BIN} build ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --release
|
||||
@${CARGO_BIN} build ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --release --bin meli
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "For a quick start, build and install locally:\n ${BOLD}${GREEN}PREFIX=~/.local make install${ANSI_RESET}\n"
|
||||
@echo "For a quick start, build and install locally:\n\n${BOLD}${GREEN}make PREFIX=~/.local install${ANSI_RESET}\n"
|
||||
@echo "Available subcommands:"
|
||||
@echo " - ${BOLD}meli${ANSI_RESET} (builds meli with optimizations in \$$CARGO_TARGET_DIR)"
|
||||
@echo " - ${BOLD}install${ANSI_RESET} (installs binary in \$$BINDIR and documentation to \$$MANDIR)"
|
||||
@echo " - ${BOLD}uninstall${ANSI_RESET}"
|
||||
@echo "Secondary subcommands:"
|
||||
@echo "\nSecondary subcommands:"
|
||||
@echo " - ${BOLD}clean${ANSI_RESET} (cleans build artifacts)"
|
||||
@echo " - ${BOLD}check-deps${ANSI_RESET} (checks dependencies)"
|
||||
@echo " - ${BOLD}install-bin${ANSI_RESET} (installs binary to \$$BINDIR)"
|
||||
|
@ -70,29 +77,52 @@ help:
|
|||
@echo " - ${BOLD}build-rustdoc${ANSI_RESET} (builds rustdoc documentation for all packages in \$$CARGO_TARGET_DIR)"
|
||||
@echo "\nENVIRONMENT variables of interest:"
|
||||
@echo "* PREFIX = ${UNDERLINE}${EXPANDED_PREFIX}${ANSI_RESET}"
|
||||
@echo -n "* MELI_FEATURES = ${UNDERLINE}"
|
||||
@[ -z $${MELI_FEATURES+x} ] && echo -n "unset" || echo -n ${MELI_FEATURES}
|
||||
@echo "* MELI_FEATURES = ${UNDERLINE}\n"
|
||||
@[ -z $${MELI_FEATURES+x} ] && echo "unset\c" || echo ${MELI_FEATURES}'\c'
|
||||
@echo ${ANSI_RESET}
|
||||
@echo "* BINDIR = ${UNDERLINE}${BINDIR}${ANSI_RESET}"
|
||||
@echo "* MANDIR = ${UNDERLINE}${MANDIR}${ANSI_RESET}"
|
||||
@echo -n "* MANPATH = ${UNDERLINE}"
|
||||
@[ $${MANPATH+x} ] && echo -n $${MANPATH} || echo -n "unset"
|
||||
@echo "* MANPATH = ${UNDERLINE}\c"
|
||||
@[ $${MANPATH+x} ] && echo $${MANPATH}'\c' || echo "unset\c"
|
||||
@echo ${ANSI_RESET}
|
||||
@echo "* (cleaned) output of manpath(1) = ${UNDERLINE}${MANPATHS}${ANSI_RESET}"
|
||||
@echo -n "* NO_MAN ${UNDERLINE}"
|
||||
@[ $${NO_MAN+x} ] && echo -n "set" || echo -n "unset"
|
||||
@echo "* NO_MAN ${UNDERLINE}\c"
|
||||
@[ $${NO_MAN+x} ] && echo "set\c" || echo "unset\c"
|
||||
@echo ${ANSI_RESET}
|
||||
@echo -n "* NO_COLOR ${UNDERLINE}"
|
||||
@[ $${NO_COLOR+x} ] && echo -n "set" || echo -n "unset"
|
||||
@echo "* NO_COLOR ${UNDERLINE}\c"
|
||||
@[ $${NO_COLOR+x} ] && echo "set\c" || echo "unset\c"
|
||||
@echo ${ANSI_RESET}
|
||||
@echo "* CARGO_BIN = ${UNDERLINE}${CARGO_BIN}${ANSI_RESET}"
|
||||
@echo "* CARGO_ARGS = ${UNDERLINE}${CARGO_ARGS}${ANSI_RESET}"
|
||||
@echo "* MIN_RUSTC = ${UNDERLINE}${MIN_RUSTC}${ANSI_RESET}"
|
||||
@echo "* VERSION = ${UNDERLINE}${VERSION}${ANSI_RESET}"
|
||||
@echo "* GIT_COMMIT = ${UNDERLINE}${GIT_COMMIT}${ANSI_RESET}"
|
||||
@#@echo "* CARGO_COLOR = ${CARGO_COLOR}"
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
@${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --workspace
|
||||
check: check-tagrefs
|
||||
@RUSTFLAGS='${RUSTFLAGS}' ${CARGO_BIN} check ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@$(CARGO_BIN) +nightly fmt --all || $(CARGO_BIN) fmt --all
|
||||
@OUT=$$($(CARGO_SORT_BIN) -w 2>&1) || $(PRINTF) "WARN: %s cargo-sort failed or binary not found in PATH.\n" "$$OUT"
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@RUSTFLAGS='${RUSTFLAGS}' $(CARGO_BIN) clippy --no-deps --all-features --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: test
|
||||
test: test-docs
|
||||
@RUSTFLAGS='${RUSTFLAGS}' ${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: test-docs
|
||||
test-docs:
|
||||
@RUSTFLAGS='${RUSTFLAGS}' ${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --all --doc
|
||||
|
||||
.PHONY: test-feature-permutations
|
||||
test-feature-permutations:
|
||||
$(CARGO_HACK_BIN) hack --feature-powerset
|
||||
|
||||
.PHONY: check-deps
|
||||
check-deps:
|
||||
|
@ -105,30 +135,32 @@ clean:
|
|||
-rm -rf ./${CARGO_TARGET_DIR}/
|
||||
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
||||
distclean:
|
||||
@rm -f meli-${VERSION}.tar.gz
|
||||
@rm -rf .pc # rm debian stuff
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)${BINDIR}/meli
|
||||
-rm $(DESTDIR)${MANDIR}/man1/meli.1.gz
|
||||
-rm $(DESTDIR)${MANDIR}/man5/meli.conf.5.gz
|
||||
-rm $(DESTDIR)${MANDIR}/man5/meli-themes.5.gz
|
||||
for MANPAGE in ${MANPAGES}; do \
|
||||
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
||||
MANPAGEPATH="${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz"; \
|
||||
rm -f "$${MANAGEPATH}"
|
||||
; done
|
||||
|
||||
.PHONY: install-doc
|
||||
install-doc:
|
||||
@(if [ -z $${NO_MAN+x} ]; then \
|
||||
mkdir -p $(DESTDIR)${MANDIR}/man1 ; \
|
||||
mkdir -p $(DESTDIR)${MANDIR}/man5 ; \
|
||||
echo " - ${BOLD}Installing manpages to ${ANSI_RESET}${DESTDIR}${MANDIR}:" ; \
|
||||
for MANPAGE in ${MANPAGES}; do \
|
||||
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
||||
mkdir -p $(DESTDIR)${MANDIR}/man$${SECTION} ; \
|
||||
MANPAGEPATH=${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz; \
|
||||
echo " * installing $${MANPAGE} → ${GREEN}$${MANPAGEPATH}${ANSI_RESET}"; \
|
||||
gzip -n < ${DOCS_SUBDIR}$${MANPAGE} > $${MANPAGEPATH} \
|
||||
; done ; \
|
||||
; done ; \
|
||||
(case ":${MANPATHS}:" in \
|
||||
*:${DESTDIR}${MANDIR}:*) echo -n "";; \
|
||||
*:${DESTDIR}${MANDIR}:*) echo "\c";; \
|
||||
*) echo "\n${RED}${BOLD}WARNING${ANSI_RESET}: ${UNDERLINE}Path ${DESTDIR}${MANDIR} is not contained in your MANPATH variable or the output of \`manpath\` command.${ANSI_RESET} \`man\` might fail finding the installed manpages. Consider adding it if necessary.\nMANPATH variable / output of \`manpath\`: ${MANPATHS}" ;; \
|
||||
esac) ; \
|
||||
else echo "NO_MAN is defined, so no documentation is going to be installed." ; fi)
|
||||
|
@ -138,7 +170,7 @@ install-bin: meli
|
|||
@mkdir -p $(DESTDIR)${BINDIR}
|
||||
@echo " - ${BOLD}Installing binary to ${ANSI_RESET}${GREEN}${DESTDIR}${BINDIR}/meli${ANSI_RESET}"
|
||||
@case ":${PATH}:" in \
|
||||
*:${DESTDIR}${BINDIR}:*) echo -n "";; \
|
||||
*:${DESTDIR}${BINDIR}:*) echo "\n";; \
|
||||
*) echo "\n${RED}${BOLD}WARNING${ANSI_RESET}: ${UNDERLINE}Path ${DESTDIR}${BINDIR} is not contained in your PATH variable.${ANSI_RESET} Consider adding it if necessary.\nPATH variable: ${PATH}";; \
|
||||
esac
|
||||
@mkdir -p $(DESTDIR)${BINDIR}
|
||||
|
@ -151,10 +183,11 @@ install-bin: meli
|
|||
.NOTPARALLEL: yes
|
||||
install: meli install-bin install-doc
|
||||
@(if [ -z $${NO_MAN+x} ]; then \
|
||||
echo "\n You're ready to go. You might want to read the \"STARTING WITH meli\" section in the manpage (\`man meli\`)" ;\
|
||||
$(PRINTF) "\n You're ready to go. You might want to read the \"STARTING WITH meli\" section in the manpage (\`man meli\`)" ;\
|
||||
$(PRINTF) "\n or the tutorial in meli(7) (\`man 7 meli\`).\n" ;\
|
||||
fi)
|
||||
@echo " - Report bugs in the mailing list or git issue tracker ${UNDERLINE}https://git.meli.delivery${ANSI_RESET}"
|
||||
@echo " - If you have a specific feature or workflow you want to use, you can post in the mailing list or git issue tracker."
|
||||
@$(PRINTF) " - Report bugs in the mailing list or git issue tracker ${UNDERLINE}https://git.meli-email.org${ANSI_RESET}\n"
|
||||
@$(PRINTF) " - If you have a specific feature or workflow you want to use, you can post in the mailing list or git issue tracker.\n"
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
|
@ -163,9 +196,53 @@ dist:
|
|||
|
||||
.PHONY: deb-dist
|
||||
deb-dist:
|
||||
@dpkg-buildpackage -b -rfakeroot -us -uc
|
||||
@echo ${BOLD}${GREEN}Generated${ANSI_RESET} ../meli_${VERSION}-1_amd64.deb
|
||||
@author=$(grep -m1 authors meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1)
|
||||
@dpkg-buildpackage -b -rfakeroot -us -uc --build-by="${author}" --release-by="${author}"
|
||||
@echo ${BOLD}${GREEN}Generated${ANSI_RESET} ../meli_${VERSION}-1_`dpkg --print-architecture`.deb
|
||||
|
||||
.PHONY: build-rustdoc
|
||||
build-rustdoc:
|
||||
@RUSTDOCFLAGS="--crate-version ${VERSION}_${GIT_COMMIT}_${DATE}" ${CARGO_BIN} doc ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --all-features --no-deps --workspace --document-private-items --open
|
||||
|
||||
.PHONY: check-tagrefs
|
||||
check-tagrefs:
|
||||
@(if ! command -v "$(TAGREF_BIN)" > /dev/null;\
|
||||
then \
|
||||
$(PRINTF) "Warning: tagref binary not in PATH.\n" 1>&2;\
|
||||
exit;\
|
||||
else \
|
||||
$(TAGREF_BIN);\
|
||||
fi)
|
||||
|
||||
.PHONY: test-makefile
|
||||
test-makefile:
|
||||
@$(PRINTF) "Checking that current version is detected. "
|
||||
@([ ! -z "${VERSION}" ] && $(PRINTF) "${GREEN}OK${ANSI_RESET}\n") || $(PRINTF) "${RED}ERROR${ANSI_RESET}\nVERSION env var is empty, check its definition.\n" 1>&2
|
||||
@$(PRINTF) "Checking that 'date -I' works on this platform. "
|
||||
@export DATEVAL=$$(printf "%s" ${DATE} | wc -c | tr -d "[:blank:]" 2>&1); ([ "$${DATEVAL}" = "10" ] && $(PRINTF) "${GREEN}OK${ANSI_RESET}\n") || $(PRINTF) "${RED}ERROR${ANSI_RESET}\n'date -I' does not produce a YYYY-MM-DD output on this platform.\n" 1>&2
|
||||
@$(PRINTF) "Checking that the git commit SHA can be detected. "
|
||||
@([ ! -z "$(GIT_COMMIT)" ] && $(PRINTF) "${GREEN}OK${ANSI_RESET}\n") || $(PRINTF) "${YELLOW}WARN${ANSI_RESET}\nGIT_COMMIT env var is empty.\n" 1>&2
|
||||
|
||||
# Checking if mdoc changes produce new lint warnings from mandoc(1) compared to HEAD version:
|
||||
#
|
||||
# example invocation: `mandoc_lint meli.1`
|
||||
#
|
||||
# with diff(1)
|
||||
# ============
|
||||
#function mandoc_lint () {
|
||||
#diff <(mandoc -T lint <(git show HEAD:./meli/docs/$1) 2> /dev/null | cut -d':' -f 3-) <(mandoc -T lint ./meli/docs/$1 2> /dev/null | cut -d':' -f 3-)
|
||||
#}
|
||||
#
|
||||
# with sdiff(1) (side by side)
|
||||
# ============================
|
||||
#
|
||||
#function mandoc_lint () {
|
||||
#sdiff <(mandoc -T lint <(git show HEAD:./meli/docs/$1) 2> /dev/null | cut -d':' -f 3-) <(mandoc -T lint ./meli/docs/$1 2> /dev/null | cut -d':' -f 3-)
|
||||
#}
|
||||
#
|
||||
# with delta(1)
|
||||
# =============
|
||||
#
|
||||
#function mandoc_lint () {
|
||||
#delta --side-by-side <(mandoc -T lint <(git show HEAD:./meli/docs/$1) 2> /dev/null | cut -d':' -f 3-) <(mandoc -T lint ./meli/docs/$1 2> /dev/null | cut -d':' -f 3-)
|
||||
#}
|
||||
|
|
235
README.md
|
@ -1,130 +1,153 @@
|
|||
# meli [![GitHub license](https://img.shields.io/github/license/meli/meli)](https://github.com/meli/meli/blob/master/COPYING) [![Crates.io](https://img.shields.io/crates/v/meli)](https://crates.io/crates/meli)
|
||||
# meli ![Established, created in 2017](https://img.shields.io/badge/Est.-2017-blue) ![Minimum Supported Rust Version](https://img.shields.io/badge/MSRV-1.68.2-blue) [![GitHub license](https://img.shields.io/github/license/meli/meli)](https://github.com/meli/meli/blob/master/COPYING) [![Crates.io](https://img.shields.io/crates/v/meli)](https://crates.io/crates/meli) [![IRC channel](https://img.shields.io/badge/irc.oftc.net-%23meli-blue)](ircs://irc.oftc.net:6697/%23meli)
|
||||
|
||||
**BSD/Linux terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP.**
|
||||
**BSD/Linux/macos terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP / NNTP (Usenet).**
|
||||
|
||||
Community links:
|
||||
[mailing lists](https://lists.meli.delivery/) | `#meli` on OFTC IRC | Report bugs and/or feature requests in [meli's issue tracker](https://git.meli.delivery/meli/meli/issues "meli gitea issue tracker")
|
||||
Try an [old online interactive web demo](https://meli-email.org/wasm2.html "online interactive web demo") powered by WebAssembly!
|
||||
|
||||
| | | |
|
||||
:---:|:---:|:---:
|
||||
![Main view screenshot](./docs/screenshots/main.webp "mail meli view screenshot") | ![Compact main view screenshot](./docs/screenshots/compact.webp "compact main view screenshot") | ![Compose with embed terminal editor screenshot](./docs/screenshots/compose.webp "composing view screenshot")
|
||||
Main view | Compact main view | Compose with embed terminal editor
|
||||
* `#meli` on OFTC IRC | [mailing lists](https://lists.meli-email.org/)
|
||||
* Repository:
|
||||
- Main <https://git.meli-email.org/meli/meli> Report bugs and/or feature requests in [meli's issue tracker](https://git.meli-email.org/meli/meli/issues "meli gitea issue tracker")
|
||||
- Official mirror <https://codeberg.org/meli/meli>
|
||||
- Official mirror <https://github.com/meli/meli>
|
||||
|
||||
Main repository:
|
||||
* https://git.meli.delivery/meli/meli
|
||||
**Table of contents**:
|
||||
|
||||
Official mirrors:
|
||||
* https://github.com/meli/meli
|
||||
- [Install](#install)
|
||||
- [Build](#build)
|
||||
- [Quick start](#quick-start)
|
||||
- [Supported E-mail backends](#supported-e-mail-backends)
|
||||
- [E-mail submission backends](#e-mail-submission-backends)
|
||||
- [Non-exhaustive list of features](#non-exhaustive-list-of-features)
|
||||
- [HTML Rendering](#html-rendering)
|
||||
- [Documentation](#documentation)
|
||||
|
||||
## Install
|
||||
- Try an [online interactive web demo](https://meli.delivery/wasm2.html "online interactive web demo") powered by WebAssembly
|
||||
- [`cargo install meli`](https://crates.io/crates/meli "crates.io meli package")
|
||||
- [Download and install pre-built debian package, static linux binary](https://github.com/meli/meli/releases/ "github releases for meli"), or
|
||||
- Install with [Nix](https://search.nixos.org/packages?show=meli&query=meli&from=0&size=30&sort=relevance&channel=unstable#disabled "nixos package search results for 'meli'")
|
||||
|
||||
## Documentation
|
||||
- [pkgsrc](https://pkgsrc.se/mail/meli)
|
||||
- [openbsd ports](https://openports.pl/path/mail/meli)
|
||||
- `cargo install meli` or `cargo install --git https://git.meli-email.org/meli/meli.git meli`
|
||||
- [Pre-built debian package, static binaries](https://github.com/meli/meli/releases/ "github releases for meli")
|
||||
- [Nix](https://search.nixos.org/packages?show=meli&query=meli&from=0&size=30&sort=relevance&channel=unstable#disabled "nixos package search results for 'meli'")
|
||||
|
||||
See also [Quickstart tutorial](https://meli.delivery/documentation.html#quick-start).
|
||||
## Build
|
||||
|
||||
After installing meli, see `meli(1)`, `meli.conf(5)` and `meli-themes(5)` for documentation. Sample configuration and theme files can be found in the `docs/samples/` subdirectory. Manual pages are also [hosted online](https://meli.delivery/documentation.html "meli documentation").
|
||||
Run `cargo build --release --bin meli` or `make`.
|
||||
|
||||
meli by default looks for a configuration file in this location: `$XDG_CONFIG_HOME/meli/config.toml`
|
||||
For detailed building instructions, see [`BUILD.md`](./BUILD.md)
|
||||
|
||||
You can run meli with arbitrary configuration files by setting the `$MELI_CONFIG`
|
||||
environment variable to their locations, i.e.:
|
||||
## Quick start
|
||||
|
||||
<table>
|
||||
<tr><td>
|
||||
|
||||
```sh
|
||||
# Create configuration file in ${XDG_CONFIG_HOME}/meli/config.toml:
|
||||
$ meli create-config
|
||||
# Edit configuration in ${EDITOR} or ${VISUAL}:
|
||||
$ meli edit-config
|
||||
# Optionally, install manual pages if installed via cargo:
|
||||
$ meli install-man
|
||||
# Ready to go.
|
||||
$ meli
|
||||
```
|
||||
|
||||
</td><td>
|
||||
|
||||
See a comprehensive tour of `meli` in the manual page [`meli(7)`](./meli/docs/meli.7).
|
||||
|
||||
See also the [Quickstart tutorial](https://meli-email.org/documentation.html#quick-start) online.
|
||||
|
||||
After installing `meli`, see `meli(1)`, `meli.conf(5)`, `meli(7)` and `meli-themes(5)` for documentation.
|
||||
Sample configuration and theme files can be found in the `meli/docs/samples/` subdirectory.
|
||||
Manual pages are also [hosted online](https://meli-email.org/documentation.html "meli documentation").
|
||||
`meli` by default looks for a configuration file in this location: `${XDG_CONFIG_HOME}/meli/config.toml`.
|
||||
|
||||
You can run meli with arbitrary configuration files by setting the `${MELI_CONFIG}` environment variable to their locations, i.e.:
|
||||
|
||||
```sh
|
||||
MELI_CONFIG=./test_config cargo run
|
||||
```
|
||||
|
||||
## Build
|
||||
For a quick start, build and install locally:
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
See [`meli(7)`](./meli/docs/meli.7) for an extensive tutorial and [`meli.conf(5)`](./meli/docs/meli.conf.5) for all configuration values.
|
||||
|
||||
| | | |
|
||||
:---:|:---:|:---:
|
||||
![Main view screenshot](./meli/docs/screenshots/main.webp "mail meli view screenshot") | ![Compact main view screenshot](./meli/docs/screenshots/compact.webp "compact main view screenshot") | ![Compose with embed terminal editor screenshot](./meli/docs/screenshots/compose.webp "composing view screenshot")
|
||||
Main view | Compact main view | Compose with embed terminal editor
|
||||
|
||||
### Supported E-mail backends
|
||||
|
||||
| Protocol | Support |
|
||||
|:------------:|:----------------|
|
||||
| IMAP | full |
|
||||
| Maildir | full |
|
||||
| notmuch | full[^0] |
|
||||
| mbox | read-only |
|
||||
| JMAP | functional |
|
||||
| NNTP / Usenet| functional |
|
||||
|
||||
[^0]: there's no support for searching through all email directly, you'd have to
|
||||
create a mailbox with a notmuch query that returns everything and search
|
||||
inside that mailbox.
|
||||
|
||||
### E-mail submission backends
|
||||
|
||||
- SMTP
|
||||
- Pipe to shell script
|
||||
- Server-side submission when supported
|
||||
|
||||
### Non-exhaustive list of features
|
||||
|
||||
- TLS
|
||||
- email threading support
|
||||
- multithreaded, async operation
|
||||
- optionally run your editor of choice inside meli, with an embedded
|
||||
xterm-compatible terminal emulator
|
||||
- plain text configuration in TOML
|
||||
- ability to open emails in UI tabs and switch to them
|
||||
- optional sqlite3 index search
|
||||
- override almost any setting per mailbox, per account
|
||||
- contact list (+read-only vCard and mutt alias file support)
|
||||
- forced UTF-8 (other encodings are read-only)
|
||||
- configurable shortcuts
|
||||
- theming
|
||||
- `NO_COLOR` support
|
||||
- ascii-only drawing characters option
|
||||
- view text/html attachments through an html filter command (w3m by default)
|
||||
- pipe attachments/mail to stuff
|
||||
- use external attachment file picker instead of typing in an attachment's full path
|
||||
- GPG signing, encryption, signing + encryption
|
||||
- GPG signature verification
|
||||
|
||||
## HTML Rendering
|
||||
|
||||
HTML rendering is achieved using [w3m](https://github.com/tats/w3m) by default.
|
||||
You can use the `pager.html_filter` setting to override this (for more details you can consult [`meli.conf(5)`](./meli/docs/meli.conf.5)).
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
See a comprehensive tour of `meli` in the manual page [`meli(7)`](./meli/docs/meli.7).
|
||||
|
||||
See also the [Quickstart tutorial](https://meli-email.org/documentation.html#quick-start) online.
|
||||
|
||||
After installing `meli`, see `meli(1)`, `meli.conf(5)`, `meli(7)` and `meli-themes(5)` for documentation.
|
||||
Sample configuration and theme files can be found in the `meli/docs/samples/` subdirectory.
|
||||
Manual pages are also [hosted online](https://meli-email.org/documentation.html "meli documentation").
|
||||
|
||||
`meli` by default looks for a configuration file in this location: `${XDG_CONFIG_HOME}/meli/config.toml`
|
||||
|
||||
You can run meli with arbitrary configuration files by setting the `${MELI_CONFIG}` environment variable to their locations, or use the `[-c, --config]` argument:
|
||||
|
||||
```sh
|
||||
PREFIX=~/.local make install
|
||||
MELI_CONFIG=./test_config meli
|
||||
```
|
||||
|
||||
Available subcommands for `make` are listed with `make help`. The Makefile *should* be POSIX portable and not require a specific `make` version.
|
||||
|
||||
meli requires rust 1.39 and rust's package manager, Cargo. Information on how
|
||||
to get it on your system can be found here: <https://doc.rust-lang.org/cargo/getting-started/installation.html>
|
||||
|
||||
With Cargo available, the project can be built with `make` and the resulting binary will then be found under `target/release/meli`. Run `make install` to install the binary and man pages. This requires root, so I suggest you override the default paths and install it in your `$HOME`: `make PREFIX=$HOME/.local install`.
|
||||
|
||||
You can build and run meli with one command: `cargo run --release`.
|
||||
|
||||
### Build features
|
||||
|
||||
Some functionality is held behind "feature gates", or compile-time flags. The following list explains each feature's purpose:
|
||||
|
||||
- `gpgme` enables GPG support via `libgpgme` (on by default)
|
||||
- `dbus-notifications` enables showing notifications using `dbus` (on by default)
|
||||
- `notmuch` provides support for using a notmuch database as a mail backend (on by default)
|
||||
- `jmap` provides support for connecting to a jmap server and use it as a mail backend (off by default)
|
||||
- `sqlite3` provides support for builting fast search indexes in local sqlite3 databases (on by default)
|
||||
- `cli-docs` includes the manpage documentation compiled by either `mandoc` or `man` binary to plain text in `meli`'s command line. Embedded documentation can be viewed with the subcommand `meli man [PAGE]`
|
||||
- `svgscreenshot` provides support for taking screenshots of the current view of meli and saving it as SVG files. Its only purpose is taking screenshots for the official meli webpage. (off by default)
|
||||
- `debug-tracing` enables various trace debug logs from various places around the meli code base. The trace log is printed in `stderr`. (off by default)
|
||||
|
||||
### Build Debian package (*deb*)
|
||||
|
||||
Building with Debian's packaged cargo might require the installation of these
|
||||
two packages: `librust-openssl-sys-dev librust-libdbus-sys-dev`
|
||||
|
||||
A `*.deb` package can be built with `make deb-dist`
|
||||
|
||||
### Using notmuch
|
||||
|
||||
To use the optional notmuch backend feature, you must have `libnotmuch5` installed in your system. In Debian-like systems, install the `libnotmuch5` packages. meli detects the library's presence on runtime.
|
||||
|
||||
### Using GPG
|
||||
|
||||
To use the optional gpg feature, you must have `libgpgme` installed in your system. In Debian-like systems, install the `libgpgme11` package. meli detects the library's presence on runtime.
|
||||
|
||||
### Building with JMAP
|
||||
|
||||
To build with JMAP support, prepend the environment variable `MELI_FEATURES='jmap'` to your make invocation:
|
||||
or
|
||||
|
||||
```sh
|
||||
MELI_FEATURES="jmap" make
|
||||
```
|
||||
|
||||
or if building directly with cargo, use the flag `--features="jmap"'.
|
||||
|
||||
# Development
|
||||
|
||||
Development builds can be built and/or run with
|
||||
|
||||
```
|
||||
cargo build
|
||||
cargo run
|
||||
```
|
||||
|
||||
There is a debug/tracing log feature that can be enabled by using the flag
|
||||
`--feature debug-tracing` after uncommenting the features in `Cargo.toml`. The logs
|
||||
are printed in stderr, thus you can run meli with a redirection (i.e `2> log`)
|
||||
|
||||
Code style follows the default rustfmt profile.
|
||||
|
||||
## Testing
|
||||
|
||||
How to run specific tests:
|
||||
|
||||
```sh
|
||||
cargo test -p {melib, meli} (-- --nocapture) (--test test_name)
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
```sh
|
||||
perf record -g target/debug/bin
|
||||
perf script | stackcollapse-perf | rust-unmangle | flamegraph > perf.svg
|
||||
```
|
||||
|
||||
## Running fuzz targets
|
||||
|
||||
Note: `cargo-fuzz` requires the nightly toolchain.
|
||||
|
||||
```sh
|
||||
cargo +nightly fuzz run envelope_parse -- -dict=fuzz/envelope_tokens.dict
|
||||
meli -c ./test_config
|
||||
```
|
||||
|
|
106
build.rs
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* meli - build.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
mod config_macros;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
config_macros::override_derive(&[
|
||||
("src/conf/pager.rs", "PagerSettings"),
|
||||
("src/conf/listing.rs", "ListingSettings"),
|
||||
("src/conf/notifications.rs", "NotificationsSettings"),
|
||||
("src/conf/shortcuts.rs", "Shortcuts"),
|
||||
("src/conf/composing.rs", "ComposingSettings"),
|
||||
("src/conf/tags.rs", "TagsSettings"),
|
||||
("src/conf/pgp.rs", "PGPSettings"),
|
||||
]);
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
use flate2::Compression;
|
||||
use flate2::GzBuilder;
|
||||
const MANDOC_OPTS: &[&'static str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let mut out_dir_path = Path::new(&out_dir).to_path_buf();
|
||||
out_dir_path.push("meli.txt.gz");
|
||||
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("docs/meli.1")
|
||||
.output()
|
||||
.or_else(|_| Command::new("man").arg("-l").arg("docs/meli.1").output())
|
||||
.unwrap();
|
||||
|
||||
let file = File::create(&out_dir_path).unwrap();
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.stdout.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output.stdout).unwrap();
|
||||
gz.finish().unwrap();
|
||||
out_dir_path.pop();
|
||||
|
||||
out_dir_path.push("meli.conf.txt.gz");
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("docs/meli.conf.5")
|
||||
.output()
|
||||
.or_else(|_| {
|
||||
Command::new("man")
|
||||
.arg("-l")
|
||||
.arg("docs/meli.conf.5")
|
||||
.output()
|
||||
})
|
||||
.unwrap();
|
||||
let file = File::create(&out_dir_path).unwrap();
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.stdout.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output.stdout).unwrap();
|
||||
gz.finish().unwrap();
|
||||
out_dir_path.pop();
|
||||
|
||||
out_dir_path.push("meli-themes.txt.gz");
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("docs/meli-themes.5")
|
||||
.output()
|
||||
.or_else(|_| {
|
||||
Command::new("man")
|
||||
.arg("-l")
|
||||
.arg("docs/meli-themes.5")
|
||||
.output()
|
||||
})
|
||||
.unwrap();
|
||||
let file = File::create(&out_dir_path).unwrap();
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.stdout.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output.stdout).unwrap();
|
||||
gz.finish().unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
# configuration for https://github.com/orhun/git-cliff
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
# note that the - before / after the % controls whether whitespace is rendered between each line.
|
||||
# Getting this right so that the markdown renders with the correct number of lines between headings
|
||||
# code fences and list items is pretty finicky. Note also that the 4 backticks in the commit macro
|
||||
# is intentional as this escapes any backticks in the commit body.
|
||||
body = """
|
||||
|
||||
{% if not version %}
|
||||
## [Unreleased]
|
||||
{% else %}
|
||||
## [{{ version }}](https://git.meli-email.org/meli/meli/releases/tag/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% endif %}
|
||||
{% macro commit(commit) -%}
|
||||
- [{{ commit.id | truncate(length=8, end="") }}]({{ "https://git.meli-email.org/meli/meli/commit/" ~ commit.id }}) {% if commit.scope %}*({{commit.scope | lower }})* {% endif %}{{ commit.message | split(pat="\n")| first | upper_first }}{% endmacro -%}
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
|
||||
### {{ group | striptags | trim | upper_first }}
|
||||
{% for commit in commits | filter(attribute="scope") | sort(attribute="scope") %}
|
||||
{{ self::commit(commit=commit) }}
|
||||
{%- endfor -%}
|
||||
{% for commit in commits %}
|
||||
{%- if not commit.scope %}
|
||||
{{ self::commit(commit=commit) }}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor %}
|
||||
"""
|
||||
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
|
||||
|
||||
<!-- generated by git-cliff <https://git-cliff.org> -->
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = false
|
||||
# process each line of a commit as an individual commit
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://git.meli-email.org/meli/meli/issues/${2}))" },
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "<!-- 00 -->Added" },
|
||||
{ message = "^[aA]dd", group = "<!-- 00 -->Added" },
|
||||
{ message = "[fF]ix", group = "<!-- 01 -->Bug Fixes" },
|
||||
{ message = "[rR]efactor", group = "<!-- 02 -->Refactoring" },
|
||||
{ message = "[mM]ove", group = "<!-- 02 -->Refactoring" },
|
||||
{ message = "[rR]emove", group = "<!-- 02 -->Refactoring" },
|
||||
{ message = "^refactor", group = "<!-- 02 -->Refactoring" },
|
||||
{ message = "^[^.]*.rs:", group = "<!-- 02 -->Refactoring" },
|
||||
{ message = "^meli", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^melib", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^imap", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^jmap", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^notmuch", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^mbox", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^smtp", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^mbox", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^doc", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "[mM]anual", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "[mM]anpage", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "[rR]eadme", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "^perf", group = "<!-- 04 -->Performance" },
|
||||
{ message = "^style", group = "<!-- 05 -->Styling" },
|
||||
{ message = "^test", group = "<!-- 06 -->Testing" },
|
||||
{ message = "^debian", group = "<!-- 06 -->Packaging" },
|
||||
{ message = "^mail/view", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^view", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^utilities", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^mail", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^listing", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^terminal", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^types", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^conf", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||
{ message = "^chore\\(pr\\)", skip = true },
|
||||
{ message = "^chore\\(pull\\)", skip = true },
|
||||
{ message = "^chore\\(deps\\)", skip = true },
|
||||
{ message = "^chore\\(changelog\\)", skip = true },
|
||||
{ message = "^[cC]hore", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^scripts", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ body = ".*security", group = "<!-- 08 -->Security" },
|
||||
{ message = "^build", group = "<!-- 09 -->Build" },
|
||||
{ message = "^ci", group = "<!-- 10 -->Continuous Integration" },
|
||||
{ message = "^revert", group = "<!-- 11 -->Reverted Commits" },
|
||||
{ message = ".*", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = false
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]+|alpha-[0-9]+"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = "v[^-]+-rc[.]?[0-9]+"
|
||||
# regex for skipping tags
|
||||
#skip_tags = "alpha"
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"@context": ["https://doi.org/10.5063/schema/codemeta-2.0", "http://schema.org/"],
|
||||
"@type": "SoftwareSourceCode",
|
||||
"applicationCategory": "E-mail client",
|
||||
"author": [
|
||||
{
|
||||
"@id": "https://pitsidianak.is/",
|
||||
"@type": "Person",
|
||||
"name": "epilys",
|
||||
"email": "manos@pitsidianak.is",
|
||||
"familyName": "Pitsidianakis",
|
||||
"givenName": "Manos",
|
||||
"url": "https://pitsidianak.is/"
|
||||
}
|
||||
],
|
||||
"codeRepository": "https://git.meli-email.org/meli/meli.git",
|
||||
"dateCreated": "2016-04-25",
|
||||
"dateModified": "2023-12-11",
|
||||
"datePublished": "2017-07-23",
|
||||
"description": "BSD/Linux/macos terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP / NNTP (Usenet).",
|
||||
"downloadUrl": "https://git.meli-email.org/meli/meli/archive/v0.8.5-rc.3.tar.gz",
|
||||
"identifier": "https://meli-email.org/",
|
||||
"isPartOf": "https://meli-email.org/",
|
||||
"keywords": [
|
||||
"e-mail",
|
||||
"email",
|
||||
"mail",
|
||||
"terminal user interface",
|
||||
"client",
|
||||
"mua",
|
||||
"mail user agent",
|
||||
"smtp",
|
||||
"imap",
|
||||
"jmap",
|
||||
"mbox",
|
||||
"maildir",
|
||||
"nntp"
|
||||
],
|
||||
"license": [
|
||||
"https://spdx.org/licenses/EUPL-1.2",
|
||||
"https://spdx.org/licenses/GPL-3.0-or-later"
|
||||
],
|
||||
"name": "meli",
|
||||
"operatingSystem": [
|
||||
"Linux",
|
||||
"macOS",
|
||||
"OpenBSD",
|
||||
"NetBSD"
|
||||
],
|
||||
"programmingLanguage": "Rust",
|
||||
"relatedLink": [
|
||||
"https://codeberg.org/meli/meli",
|
||||
"https://github.com/meli/meli",
|
||||
"https://lists.meli-email.org/"
|
||||
],
|
||||
"version": "0.8.5-rc3",
|
||||
"contIntegration": "https://git.meli-email.org/meli/meli/actions",
|
||||
"developmentStatus": "active",
|
||||
"issueTracker": "https://git.meli-email.org/meli/meli/issues",
|
||||
"readme": "https://git.meli-email.org/meli/meli/raw/commit/dedee908d1e0b42773bade8e0604e94b14810e2d/README.md",
|
||||
"buildInstructions": "https://git.meli-email.org/meli/meli/raw/commit/dedee908d1e0b42773bade8e0604e94b14810e2d/BUILD.md"
|
||||
}
|
|
@ -1,3 +1,58 @@
|
|||
meli (0.8.5-rc.3-1) bookworm; urgency=low
|
||||
|
||||
* Update to 0.8.5-rc.3
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Sun, 10 Dec 2023 15:22:18 +0000
|
||||
|
||||
meli (0.8.5-rc.2-1) bookworm; urgency=low
|
||||
|
||||
* Update to 0.8.5-rc.2
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Mon, 4 Dec 2023 19:34:00 +0200
|
||||
|
||||
meli (0.8.4-1) bookworm; urgency=low
|
||||
|
||||
* Update to 0.8.4
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Mon, 27 Nov 2023 19:34:00 +0200
|
||||
|
||||
meli (0.7.2-1) bullseye; urgency=low
|
||||
Added
|
||||
|
||||
- Add forward mail option
|
||||
- Add url_launcher config setting
|
||||
- Add add_addresses_to_contacts command
|
||||
- Add show_date_in_my_timezone pager config flag
|
||||
- docs: add pager filter documentation
|
||||
- mail/view: respect per-folder/account pager filter override
|
||||
- pager: add filter command, esc to clear filter
|
||||
- Show compile time features in with command argument
|
||||
|
||||
Fixed
|
||||
|
||||
- melib/email/address: quote display_name if it contains ","
|
||||
- melib/smtp: fix Cc and Bcc ignored when sending mail
|
||||
- melib/email/address: quote display_name if it contains "."
|
||||
|
||||
-- Manos Pitsidianakis <epilys@nessuent.xyz> Fri, 15 Oct 2021 12:34:00 +0200
|
||||
meli (0.7.1-1) bullseye; urgency=low
|
||||
|
||||
Added
|
||||
- Change all Down/Up shortcuts to j/k
|
||||
- add 'GB18030' charset
|
||||
- melib/nntp: implement refresh
|
||||
- melib/nntp: update total/new counters on new articles
|
||||
- melib/nntp: implement NNTP posting
|
||||
- configs: throw error on extra unused conf flags in some imap/nntp
|
||||
- configs: throw error on missing `composing` section with explanation
|
||||
|
||||
Fixed
|
||||
- Fix compilation for netbsd-9.2
|
||||
- conf: fixed some boolean flag values requiring to be string e.g. "true"
|
||||
-- Manos Pitsidianakis <epilys@nessuent.xyz> Wed, 08 Sep 2021 18:14:00 +0200
|
||||
meli (0.7.0-1) buster; urgency=low
|
||||
|
||||
-- Manos Pitsidianakis <epilys@nessuent.xyz> Fri, 03 Sep 2021 18:14:00 +0200
|
||||
meli (0.6.2-1) buster; urgency=low
|
||||
|
||||
Added
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
11
|
|
@ -1,14 +1,20 @@
|
|||
Source: meli
|
||||
Section: mail
|
||||
Priority: optional
|
||||
Maintainer: Manos Pitsidianakis <epilys@nessuent.xyz>
|
||||
Build-Depends: debhelper (>=11~), mandoc (>=1.14.4-1)
|
||||
Maintainer: Manos Pitsidianakis <manos@pitsidianak.is>
|
||||
Build-Depends: debhelper-compat (=13), mandoc (>=1.14.4-1), quilt, libsqlite3-dev
|
||||
Standards-Version: 4.1.4
|
||||
Homepage: https://meli.delivery
|
||||
Rules-Requires-Root: no
|
||||
Vcs-Git: https://git.meli-email.org/meli/meli.git
|
||||
Vcs-Browser: https://git.meli-email.org/meli/meli
|
||||
Homepage: https://meli-email.org
|
||||
|
||||
Package: meli
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Recommends: libnotmuch, xdg-utils (>=1.1.3-1)
|
||||
Description: terminal mail client
|
||||
Recommends: xdg-utils (>=1.1.3-1), w3m, mailcap
|
||||
Suggests: libnotmuch5, notmuch, rss2email, xterm, neovim, msmtp
|
||||
Provides: mail-reader, imap-client
|
||||
Description: terminal mail client.
|
||||
meli supports mbox, maildir, IMAP, JMAP, notmuch and NNTP (Usernet) with
|
||||
TLS/SSL, SASL, GPG features.
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: meli
|
||||
Source: <https://git.meli.delivery/meli/meli>
|
||||
Source: https://git.meli-email.org/meli/meli
|
||||
#
|
||||
# Please double check copyright with the licensecheck(1) command.
|
||||
|
||||
Files: *
|
||||
Copyright: 2017-2020 Manos Pitsidianakis
|
||||
Copyright: 2017-2023 Manos Pitsidianakis
|
||||
License: GPL-3.0+
|
||||
#----------------------------------------------------------------------------
|
||||
# License file: COPYING
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
[Desktop Entry]
|
||||
Name=meli
|
||||
Exec=meli
|
||||
Categories=Office;Network;Email;
|
||||
Comment=Terminal mail client
|
||||
NoDisplay=false
|
||||
Terminal=true
|
||||
Type=Application
|
|
@ -0,0 +1,7 @@
|
|||
WARNING: This package is not distributed by debian, it was generated from the source repository of meli.
|
||||
|
||||
Please do not report bugs to debian, but to the appropriate issue tracker for meli:
|
||||
|
||||
- https://git.meli-email.org/meli/meli/issues
|
||||
- Send e-mail to the mailing list, "meli general" <meli-general@meli-email.org>
|
||||
https://lists.meli-email.org/list/meli-general/
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Including output of \`meli -v\` and \`meli compiled-with\`..."
|
||||
|
||||
LC_ALL=C meli -v >&3
|
||||
|
||||
echo "\nEnabled compile-time features"
|
||||
echo "-----------------------------"
|
||||
LC_ALL=C meli compiled-with >&3 || true
|
|
@ -0,0 +1,5 @@
|
|||
Document: meli
|
||||
Title: meli E-mail Client Manual
|
||||
Author: Various
|
||||
Abstract: Manual for meli the terminal e-mail client.
|
||||
Section: Network/Communication
|
|
@ -1,3 +1,4 @@
|
|||
docs/meli.1
|
||||
docs/meli.conf.5
|
||||
docs/meli-themes.5
|
||||
meli/docs/meli.1
|
||||
meli/docs/meli.7
|
||||
meli/docs/meli.conf.5
|
||||
meli/docs/meli-themes.5
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
meli/docs/samples/sample-config.toml
|
||||
meli/docs/samples/themes
|
|
@ -1,10 +1,12 @@
|
|||
Description: Fix PREFIX env var in Makefile for use in Debian
|
||||
Author: Manos Pitsidianakis <epilys@nessuent.xyz>
|
||||
Last-Update: 2020-01-30
|
||||
--- a/Makefile
|
||||
+++ b/Makefile
|
||||
@@ -18,7 +18,7 @@
|
||||
# along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
Last-Update: 2023-03-06
|
||||
Index: meli/Makefile
|
||||
===================================================================
|
||||
--- meli.orig/Makefile
|
||||
+++ meli/Makefile
|
||||
@@ -20,7 +20,7 @@
|
||||
.SUFFIXES:
|
||||
|
||||
# Options
|
||||
-PREFIX ?= /usr/local
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
fix-prefix-for-debian.patch
|
||||
usr_bin_editor.patch
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
From: Manos Pitsidianakis <manos@pitsidianak.is>
|
||||
Date: Thu, 27 Feb 2014 16:06:15 +0100
|
||||
Subject: usr_bin_editor
|
||||
|
||||
If EDITOR or VISUAL is not set, fall back to /usr/bin/editor,
|
||||
which is set by update-alternatives.
|
||||
---
|
||||
meli/src/subcommands.rs | 1 +---
|
||||
1 file changed, 1 insertion(+), 3 deletions(-)
|
||||
|
||||
--- a/meli/src/subcommands.rs
|
||||
+++ b/meli/src/subcommands.rs
|
||||
@@ -52,9 +52,7 @@
|
||||
pub fn edit_config() -> Result<()> {
|
||||
let editor = std::env::var("EDITOR")
|
||||
.or_else(|_| std::env::var("VISUAL"))
|
||||
- .map_err(|err| {
|
||||
- format!("Could not find any value in environment variables EDITOR and VISUAL. {err}")
|
||||
- })?;
|
||||
+ .unwrap_or_else(|_| "/usr/bin/editor".into());
|
||||
let config_path = crate::conf::get_config_file()?;
|
||||
|
||||
let mut cmd = Command::new(editor);
|
|
@ -1,14 +1,21 @@
|
|||
#!/usr/bin/make -f
|
||||
# You must remove unused comment lines for the released package.
|
||||
#export DH_VERBOSE = 1
|
||||
export RUSTUP_HOME=${HOME}/.rustup
|
||||
export DH_VERBOSE = 1
|
||||
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
export MELI_FEATURES = cli-docs sqlite3
|
||||
#export MELI_FEATURES = cli-docs sqlite3
|
||||
|
||||
%:
|
||||
dh $@ --with quilt
|
||||
|
||||
override_dh_auto_configure:
|
||||
true
|
||||
|
||||
override_dh_auto_test:
|
||||
true
|
||||
|
||||
#override_dh_auto_install:
|
||||
# dh_auto_install -- prefix=/usr
|
||||
|
||||
|
|
1276
docs/meli.conf.5
|
@ -8,17 +8,14 @@ edition = "2018"
|
|||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[[bin]]
|
||||
name = "envelope_parse"
|
||||
path = "fuzz_targets/envelope_parse.rs"
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
|
||||
[dependencies.melib]
|
||||
path = "../melib"
|
||||
features = ["unicode_algorithms"]
|
||||
melib = { path = "../melib" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "envelope_parse"
|
||||
path = "fuzz_targets/envelope_parse.rs"
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
[package]
|
||||
name = "meli"
|
||||
version = "0.8.5-rc.3"
|
||||
authors = ["Manos Pitsidianakis <manos@pitsidianak.is>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.68.2"
|
||||
license = "GPL-3.0-or-later"
|
||||
readme = "README.md"
|
||||
description = "terminal e-mail client"
|
||||
homepage = "https://meli-email.org"
|
||||
repository = "https://git.meli-email.org/meli/meli.git"
|
||||
keywords = ["mail", "mua", "maildir", "terminal", "imap"]
|
||||
categories = ["command-line-utilities", "email"]
|
||||
default-run = "meli"
|
||||
|
||||
[[bin]]
|
||||
name = "meli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "meli"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-task = "^4.2.0"
|
||||
bitflags = { version = "2.4", features = ["serde"] }
|
||||
crossbeam = { version = "^0.8" }
|
||||
flate2 = { version = "1", optional = true }
|
||||
futures = "0.3.5"
|
||||
indexmap = { version = "^1.6", features = ["serde-1"] }
|
||||
libc = { version = "0.2.125", default-features = false, features = ["extra_traits"] }
|
||||
libz-sys = { version = "1.1", features = ["static"], optional = true }
|
||||
linkify = { version = "^0.10", default-features = false }
|
||||
melib = { path = "../melib", version = "0.8.5-rc.3", features = [] }
|
||||
nix = { version = "0.27", default-features = false, features = ["signal", "poll", "term", "ioctl", "process"] }
|
||||
serde = "1.0.71"
|
||||
serde_derive = "1.0.71"
|
||||
serde_json = "1.0"
|
||||
signal-hook = { version = "^0.3", default-features = false, features = ["iterator"] }
|
||||
signal-hook-registry = { version = "1.2.0", default-features = false }
|
||||
smallvec = { version = "^1.5.0", features = ["serde"] }
|
||||
structopt = { version = "0.3.14", default-features = false }
|
||||
svg_crate = { version = "^0.13", optional = true, package = "svg" }
|
||||
termion = { version = "1.5.1", default-features = false }
|
||||
toml = { version = "0.8", default-features = false, features = ["display","preserve_order","parse"] }
|
||||
xdg = "2.1.0"
|
||||
|
||||
[dependencies.pcre2]
|
||||
# An [env] entry in .cargo/config.toml should force a static build instead of
|
||||
# looking for a system library.
|
||||
version = "0.2.3"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["sqlite3", "notmuch", "smtp", "dbus-notifications", "gpgme", "cli-docs", "jmap", "static"]
|
||||
notmuch = ["melib/notmuch"]
|
||||
jmap = ["melib/jmap"]
|
||||
sqlite3 = ["melib/sqlite3"]
|
||||
smtp = ["melib/smtp"]
|
||||
smtp-trace = ["smtp", "melib/smtp-trace"]
|
||||
regexp = ["dep:pcre2"]
|
||||
dbus-notifications = ["dep:notify-rust"]
|
||||
cli-docs = ["dep:flate2"]
|
||||
svgscreenshot = ["dep:svg_crate"]
|
||||
gpgme = ["melib/gpgme"]
|
||||
# Static / vendoring features.
|
||||
tls-static = ["melib/tls-static"]
|
||||
http-static = ["melib/http-static"]
|
||||
sqlite3-static = ["melib/sqlite3-static"]
|
||||
dbus-static = ["dep:notify-rust", "notify-rust?/d_vendored"]
|
||||
libz-static = ["dep:libz-sys", "libz-sys?/static"]
|
||||
static = ["tls-static", "http-static", "sqlite3-static", "dbus-static", "libz-static"]
|
||||
|
||||
# Print tracing logs as meli runs in stderr
|
||||
# enable for debug tracing logs: build with --features=debug-tracing and export MELI_DEBUG_STDERR
|
||||
debug-tracing = ["melib/debug-tracing"]
|
||||
|
||||
[build-dependencies]
|
||||
flate2 = { version = "1", optional = true }
|
||||
proc-macro2 = "1.0.37"
|
||||
quote = "^1.0"
|
||||
regex = "1"
|
||||
syn = { version = "1", features = [] }
|
||||
|
||||
[dev-dependencies]
|
||||
flate2 = { version = "1" }
|
||||
regex = "1"
|
||||
tempfile = "3.3"
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
notify-rust = { version = "^4", default-features = false, features = ["dbus"], optional = true }
|
|
@ -0,0 +1 @@
|
|||
../README.md
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* meli - build.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
include!("config_macros.rs");
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/conf/.rebuild.overrides.rs");
|
||||
override_derive(&[
|
||||
("src/conf/pager.rs", "PagerSettings"),
|
||||
("src/conf/listing.rs", "ListingSettings"),
|
||||
("src/conf/notifications.rs", "NotificationsSettings"),
|
||||
("src/conf/shortcuts.rs", "Shortcuts"),
|
||||
("src/conf/composing.rs", "ComposingSettings"),
|
||||
("src/conf/tags.rs", "TagsSettings"),
|
||||
("src/conf/pgp.rs", "PGPSettings"),
|
||||
]);
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
use flate2::{Compression, GzBuilder};
|
||||
const MANDOC_OPTS: &[&str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
|
||||
use std::{env, io::prelude::*, path::Path};
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let mut out_dir_path = Path::new(&out_dir).to_path_buf();
|
||||
|
||||
let mut cl = |filepath: &str, output: &str, source: bool| {
|
||||
out_dir_path.push(output);
|
||||
let output = if source {
|
||||
std::fs::read_to_string(filepath).unwrap().into_bytes()
|
||||
} else {
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg(filepath)
|
||||
.output()
|
||||
.or_else(|_| Command::new("man").arg("-l").arg(filepath).output())
|
||||
.expect(
|
||||
"could not execute `mandoc` or `man`. If the binaries are not available \
|
||||
in the PATH, disable `cli-docs` feature to be able to continue \
|
||||
compilation.",
|
||||
);
|
||||
output.stdout
|
||||
};
|
||||
|
||||
let file = File::create(&out_dir_path).unwrap_or_else(|err| {
|
||||
panic!("Could not create file {}: {}", out_dir_path.display(), err)
|
||||
});
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output).unwrap();
|
||||
gz.finish().unwrap();
|
||||
out_dir_path.pop();
|
||||
};
|
||||
|
||||
cl("docs/meli.1", "meli.txt.gz", false);
|
||||
cl("docs/meli.conf.5", "meli.conf.txt.gz", false);
|
||||
cl("docs/meli-themes.5", "meli-themes.txt.gz", false);
|
||||
cl("docs/meli.7", "meli.7.txt.gz", false);
|
||||
cl("docs/meli.1", "meli.mdoc.gz", true);
|
||||
cl("docs/meli.conf.5", "meli.conf.mdoc.gz", true);
|
||||
cl("docs/meli-themes.5", "meli-themes.mdoc.gz", true);
|
||||
cl("docs/meli.7", "meli.7.mdoc.gz", true);
|
||||
}
|
||||
}
|
|
@ -19,17 +19,21 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::prelude::*,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use quote::{format_ident, quote};
|
||||
use regex::Regex;
|
||||
|
||||
// Write ConfigStructOverride to overrides.rs
|
||||
pub fn override_derive(filenames: &[(&str, &str)]) {
|
||||
pub(crate) fn override_derive(filenames: &[(&str, &str)]) {
|
||||
let mut output_file =
|
||||
File::create("src/conf/overrides.rs").expect("Unable to open output file");
|
||||
let mut output_string = r##"/*
|
||||
let mut output_string = r##"// @generated
|
||||
/*
|
||||
* meli - conf/overrides.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
|
@ -50,15 +54,23 @@ pub fn override_derive(filenames: &[(&str, &str)]) {
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! This module is automatically generated by build.rs.
|
||||
#![allow(clippy::derivable_impls)]
|
||||
|
||||
//! This module is automatically generated by `config_macros.rs`.
|
||||
|
||||
use super::*;
|
||||
use melib::HeaderName;
|
||||
|
||||
"##
|
||||
.to_string();
|
||||
|
||||
let cfg_attr_default_attr_regex = Regex::new(r"\s*default\s*[,]").unwrap();
|
||||
let cfg_attr_default_val_attr_regex = Regex::new(r#"\s*default\s*=\s*"[^"]*"\s*,\s*"#).unwrap();
|
||||
let cfg_attr_feature_regex = Regex::new(r"[(](?:not[(]\s*)?feature").unwrap();
|
||||
|
||||
'file_loop: for (filename, ident) in filenames {
|
||||
println!("cargo:rerun-if-changed={}", filename);
|
||||
let mut file = File::open(&filename)
|
||||
let mut file = File::open(filename)
|
||||
.unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err));
|
||||
|
||||
let mut src = String::new();
|
||||
|
@ -104,10 +116,23 @@ use super::*;
|
|||
.iter()
|
||||
.filter_map(|f| {
|
||||
let mut new_attr = f.clone();
|
||||
if let quote::__private::TokenTree::Group(g) =
|
||||
if let proc_macro2::TokenTree::Group(g) =
|
||||
f.tokens.clone().into_iter().next().unwrap()
|
||||
{
|
||||
let attr_inner_value = f.tokens.to_string();
|
||||
let mut attr_inner_value = f.tokens.to_string();
|
||||
if cfg_attr_feature_regex.is_match(&attr_inner_value) {
|
||||
attr_inner_value = cfg_attr_default_val_attr_regex
|
||||
.replace_all(&attr_inner_value, "")
|
||||
.to_string();
|
||||
if attr_inner_value.contains("default") {
|
||||
attr_inner_value = cfg_attr_default_attr_regex
|
||||
.replace_all(&attr_inner_value, "")
|
||||
.to_string();
|
||||
}
|
||||
let new_toks: proc_macro2::TokenStream =
|
||||
attr_inner_value.parse().unwrap();
|
||||
new_attr.tokens = quote! { #new_toks };
|
||||
}
|
||||
if !attr_inner_value.starts_with("( default")
|
||||
&& !attr_inner_value.starts_with("( default =")
|
||||
&& !attr_inner_value.starts_with("(default")
|
||||
|
@ -148,7 +173,9 @@ use super::*;
|
|||
#[serde(default)]
|
||||
pub #ident : Option<#ty>
|
||||
};
|
||||
field_idents.push(ident);
|
||||
if !field_idents.contains(&ident) {
|
||||
field_idents.push(ident);
|
||||
}
|
||||
field_tokentrees.push(t);
|
||||
}
|
||||
//let fields = &s.fields;
|
||||
|
@ -165,7 +192,7 @@ use super::*;
|
|||
#(#attrs_tokens)*
|
||||
impl Default for #override_ident {
|
||||
fn default() -> Self {
|
||||
#override_ident {
|
||||
Self {
|
||||
#(#field_idents: None),*
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
# Using other apps with `meli`
|
||||
|
||||
## Sending mail with a command line tool
|
||||
|
||||
`composing.send_mail` can use either settings for an SMTP server or a shell
|
||||
command to which it pipes new mail to.
|
||||
|
||||
### `msmtp` and `send_mail`
|
||||
|
||||
[`msmtp`][msmtp] is a command line SMTP client that can be configured to work
|
||||
with many SMTP servers. It supports queuing and other small useful features.
|
||||
See [the documentation](https://marlam.de/msmtp/msmtp.html).
|
||||
|
||||
```toml
|
||||
[composing]
|
||||
send_mail = 'msmtp --logfile=/home/user/.mail/msmtp.log --read-recipients
|
||||
--read-envelope-from'
|
||||
```
|
||||
[msmtp]: https://marlam.de/msmtp/
|
||||
|
||||
## Editor
|
||||
|
||||
Any editor you specify in `composing.editor_cmd` will be invoked with the
|
||||
e-mail draft file path appended as an argument to it. For example, if your
|
||||
setting is `editor_cmd = 'nano'`, `meli` will execute `nano /tmp/meli/...`.
|
||||
|
||||
### Configuration
|
||||
|
||||
#### `vim` / `neovim` command
|
||||
|
||||
The following command setting in your `meli` configuration file makes editing
|
||||
start at the first empty line, that is, after the e-mail headers. This allows
|
||||
you to start writing the e-mail body right away after opening the editor from
|
||||
`meli`.
|
||||
|
||||
```toml
|
||||
[composing]
|
||||
editor_cmd = '~/.local/bin/vim +/^$'
|
||||
```
|
||||
|
||||
In `vim`, the `+` argument positions the cursor at the first file argument. `/`
|
||||
specifies a pattern position instead of a line number. `^` specifies the start
|
||||
of a line, and `$` the end of the line. The pattern altogether matches an empty
|
||||
line, which will be after the e-mail headers.
|
||||
|
||||
### Composing with `format=flowed`
|
||||
|
||||
`format=flowed` is a proposed IETF standard[^formatflowed] that lets you
|
||||
preserve the structure of paragraphs by disambiguating a *hard* and a *soft*
|
||||
line break. A line break that is preceded by a space character is *soft* and
|
||||
does not terminate the paragraph, while a line break without a space is a
|
||||
*hard* one and creates a new paragraph. This allows text to be re-flowed in
|
||||
e-mail clients at different display widths and font sizes without messing up
|
||||
the author's formatting.
|
||||
|
||||
#### `vim` / `neovim` and `format=flowed`
|
||||
|
||||
Create a `mail.vim` file type plugin in:
|
||||
|
||||
- `$HOME/.vim/after/ftplugin/mail.vim` for vim
|
||||
- `$HOME/.config/nvim/after/ftplugin/mail.vim` for neovim
|
||||
|
||||
```vim
|
||||
setlocal nomodeline
|
||||
setlocal textwidth=72
|
||||
setlocal formatoptions=aqtw2r
|
||||
setlocal nojoinspaces
|
||||
setlocal nosmartindent
|
||||
setlocal comments+=nb:>
|
||||
match ErrorMsg '\s\+$'
|
||||
```
|
||||
|
||||
Also, don't forget that you can easily quote stuff with `MailQuote`.
|
||||
From `:help ft-mail-plugin`:
|
||||
|
||||
> Local mappings:
|
||||
> `<LocalLeader>q` or `\\MailQuote`
|
||||
> Quotes the text selected in Visual mode, or from the cursor position
|
||||
> to the end of the file in Normal mode.
|
||||
> This means "> " is inserted in each line.
|
||||
|
||||
See the accompanying [`mail.vim`](./mail.vim) for comments for each setting.
|
||||
|
||||
## `xbiff`
|
||||
|
||||
[`xbiff(1)`][xbiff] manual page says:[^xbiffmanpage]
|
||||
|
||||
> The `xbiff` program displays a little image of a mailbox. When there is no
|
||||
> mail, the flag on the mailbox is down. When mail arrives, the flag goes up
|
||||
> and the mailbox beeps.
|
||||
|
||||
This tool is very outdated, but some users might still have use for it.
|
||||
Therefore `meli` provides support (also, it's easy to support this feature).
|
||||
|
||||
Specify a file path in `notifications.xbiff_file_path` and `meli` will write to
|
||||
it when new mail arrives. This file can the be used as input to `xbiff`.
|
||||
|
||||
```toml
|
||||
[notifications]
|
||||
xbiff_file_path = "/tmp/xbiff"
|
||||
```
|
||||
|
||||
[xbiff]: https://en.wikipedia.org/wiki/Xbiff
|
||||
[^xbiffmanpage]: https://www.x.org/releases/X11R7.0/doc/html/xbiff.1.html
|
||||
|
||||
## Viewing HTML e-mail
|
||||
|
||||
By default `meli` tries to render HTML e-mail with `w3m`. You can override this
|
||||
by setting the `pager.html_filter` setting. The default setting corresponds to:
|
||||
|
||||
```toml
|
||||
[pager]
|
||||
html_filter = "w3m -I utf-8 -T text/html"
|
||||
```
|
||||
|
||||
The HTML of the e-mail is piped into `html_filter`'s standard input.
|
||||
|
||||
## Externally refreshing e-mail accounts
|
||||
|
||||
If your account's syncing is handled by an external tool, you can use the
|
||||
refresh shortcuts within `meli` to call this tool with
|
||||
`accounts.refresh_command`.
|
|
@ -0,0 +1,354 @@
|
|||
'\" t
|
||||
.\"<!-- Copyright 1998 - 2007 Double Precision, Inc. See COPYING for -->
|
||||
.\"<!-- distribution information. -->
|
||||
.\" Title: maildir
|
||||
.\" Author: Sam Varshavchik
|
||||
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
|
||||
.\" Date: 07/24/2017
|
||||
.\" Manual: Double Precision, Inc.
|
||||
.\" Source: Courier Mail Server
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "MAILDIR" "5" "07/24/2017" "Courier Mail Server" "Double Precision, Inc\&."
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
maildir \- E\-mail directory
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
$HOME/Maildir
|
||||
.SH "DESCRIPTION"
|
||||
.PP
|
||||
A
|
||||
\(lqMaildir\(rq
|
||||
is a structured directory that holds E\-mail messages\&. Maildirs were first implemented by the
|
||||
Qmail
|
||||
mail server\&. Qmail\*(Aqs maildirs were a simple data structure, nothing more than a single collection of E\-mail messages\&. The
|
||||
Courier
|
||||
mail server builds upon
|
||||
Qmail\*(Aqs maildirs to provide extended functionality, such as folders and quotas\&. This document describes the
|
||||
Courier
|
||||
mail server\*(Aqs extended maildirs, without explicitly identifying The
|
||||
Courier
|
||||
mail server\-specific extensions\&. See
|
||||
\fBmaildir\fR(5)
|
||||
in Qmail\*(Aqs documentation for the original definition of maildirs\&.
|
||||
.PP
|
||||
Traditionally, E\-mail folders were saved as plain text files, called
|
||||
\(lqmboxes\(rq\&. Mboxes have known limitations\&. Only one application can use an mbox at the same time\&. Locking is required in order to allow simultaneous concurrent access by different applications\&. Locking is often problematic, and not very reliable in network\-based filesystem requirements\&. Some network\-based filesystems don\*(Aqt offer any reliable locking mechanism at all\&. Furthermore, even bulletproof locking won\*(Aqt prevent occasional mbox corruption\&. A process can be killed or terminated in the middle of updating an mbox\&. This will likely result in corruption, and a loss of most messages in the mbox\&.
|
||||
.PP
|
||||
Maildirs allow multiple concurrent access by different applications\&. Maildirs do not require locking\&. Multiple applications can update a maildir at the same time, without stepping on each other\*(Aqs feet\&.
|
||||
.SS "Maildir contents"
|
||||
.PP
|
||||
A
|
||||
\(lqmaildir\(rq
|
||||
is a directory that\*(Aqs created by
|
||||
\m[blue]\fB\fBmaildirmake\fR(1)\fR\m[]\&\s-2\u[1]\d\s+2\&. Naturally, maildirs should not have any group or world permissions, unless you want other people to read your mail\&. A maildir contains three subdirectories:
|
||||
tmp,
|
||||
new, and
|
||||
cur\&. These three subdirectories comprise the primary folder, where new mail is delivered by the system\&.
|
||||
.PP
|
||||
Folders are additional subdirectories in the maildir whose names begin with a period: such as
|
||||
\&.Drafts
|
||||
or
|
||||
\&.Sent\&. Each folder itself contains the same three subdirectories,
|
||||
tmp,
|
||||
new, and
|
||||
cur, and an additional zero\-length file named
|
||||
maildirfolder, whose purpose is to inform any mail delivery agent that it\*(Aqs really delivering to a folder, and that the mail delivery agent should look in the parent directory for any maildir\-related information\&.
|
||||
.PP
|
||||
Folders are not physically nested\&. A folder subdirectory, such as
|
||||
\&.Sent
|
||||
does not itself contain any subfolders\&. The main maildir contains a single, flat list of subfolders\&. These folders are logically nested, and periods serve to separate folder hierarchies\&. For example,
|
||||
\&.Sent\&.2002
|
||||
is considered to be a subfolder called
|
||||
\(lq2002\(rq
|
||||
which is a subfolder of
|
||||
\(lqSent\(rq\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
.nr an-break-flag 1
|
||||
.br
|
||||
.ps +1
|
||||
\fBFolder name encoding\fR
|
||||
.RS 4
|
||||
.PP
|
||||
Folder names can contain any Unicode character, except for control characters\&. US\-ASCII characters, U+0x0020 \- U+0x007F, except for the period, forward\-slash, and ampersand characters (U+0x002E, U+0x002F, and U+0x0026) represent themselves\&. The ampersand is represent by the two character sequence
|
||||
\(lq&\-\(rq\&. The period, forward slash, and non US\-ASCII Unicode characters are represented using the UTF\-7 character set, and encoded with a modified form of base64\-encoding\&.
|
||||
.PP
|
||||
The
|
||||
\(lq&\(rq
|
||||
character starts the modified base64\-encoded sequence; the sequence is terminated by the
|
||||
\(lq\-\(rq
|
||||
character\&. The sequence of 16\-bit Unicode characters is written in big\-endian order, and encoded using the base64\-encoding method described in section 5\&.2 of
|
||||
\m[blue]\fBRFC 1521\fR\m[]\&\s-2\u[2]\d\s+2, with the following modifications:
|
||||
.sp
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
The
|
||||
\(lq=\(rq
|
||||
padding character is omitted\&. When decoding, an incomplete 16\-bit character is discarded\&.
|
||||
.RE
|
||||
.sp
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
The comma character,
|
||||
\(lq,\(rq
|
||||
is used in place of the
|
||||
\(lq/\(rq
|
||||
character in the base64 alphabet\&.
|
||||
.RE
|
||||
.PP
|
||||
For example, the word
|
||||
\(lqResume\(rq
|
||||
with both "e"s being the e\-acute character, U+0x00e9, is encoded as
|
||||
\(lqR&AOk\-sum&AOk\-\(rq
|
||||
(so a folder of that name would be a maildir subdirectory called
|
||||
\(lq\&.R&AOk\-sum&AOk\-\(rq)\&.
|
||||
.RE
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
.nr an-break-flag 1
|
||||
.br
|
||||
.ps +1
|
||||
\fBOther maildir contents\fR
|
||||
.RS 4
|
||||
.PP
|
||||
Software that uses maildirs may also create additional files besides the
|
||||
tmp,
|
||||
new, and
|
||||
cur
|
||||
subdirectories \-\- in the main maildir or a subfolder \-\- for its own purposes\&.
|
||||
.RE
|
||||
.SS "Messages"
|
||||
.PP
|
||||
E\-mail messages are stored in separate, individual files, one E\-mail message per file\&. The
|
||||
tmp
|
||||
subdirectory temporarily stores E\-mail messages that are in the process of being delivered to this maildir\&.
|
||||
tmp
|
||||
may also store other kinds of temporary files, as long as they are created in the same way that message files are created in
|
||||
tmp\&. The
|
||||
new
|
||||
subdirectory stores messages that have been delivered to this maildir, but have not yet been seen by any mail application\&. The
|
||||
cur
|
||||
subdirectory stores messages that have already been seen by mail applications\&.
|
||||
.SS "Adding new mail to maildirs"
|
||||
.PP
|
||||
The following process delivers a new message to the maildir:
|
||||
.PP
|
||||
A new unique filename is created using one of two possible forms:
|
||||
\(lqtime\&.MusecPpid\&.host\(rq, or
|
||||
\(lqtime\&.MusecPpid_unique\&.host\(rq\&.
|
||||
\(lqtime\(rq
|
||||
and
|
||||
\(lqusec\(rq
|
||||
is the current system time, obtained from
|
||||
\fBgettimeofday\fR(2)\&.
|
||||
\(lqpid\(rq
|
||||
is the process number of the process that is delivering this message to the maildir\&.
|
||||
\(lqhost\(rq
|
||||
is the name of the machine where the mail is being delivered\&. In the event that the same process creates multiple messages, a suffix unique to each message is appended to the process id; preferrably an underscore, followed by an increasing counter\&. This applies whether messages created by a process are all added to the same, or different, maildirs\&. This protocol allows multiple processes running on multiple machines on the same network to simultaneously create new messages without stomping on each other\&.
|
||||
.PP
|
||||
The filename created in the previous step is checked for existence by executing the
|
||||
\fBstat\fR(2)
|
||||
system call\&. If
|
||||
\fBstat\fR(2)
|
||||
results in ANYTHING OTHER than the system error
|
||||
ENOENT, the process must sleep for two seconds, then go back and create another unique filename\&. This is an extra step to insure that each new message has a completely unique filename\&.
|
||||
.PP
|
||||
Other applications that wish to use
|
||||
tmp
|
||||
for temporary storage should observe the same protocol (but see READING MAIL FROM MAILDIRS below, because old files in
|
||||
tmp
|
||||
will be eventually deleted)\&.
|
||||
.PP
|
||||
If the
|
||||
\fBstat\fR(2)
|
||||
system call returned
|
||||
ENOENT, the process may proceed to create the file in the
|
||||
tmp
|
||||
subdirectory, and save the entire message in the new file\&. The message saved MUST NOT have the
|
||||
\(lqFrom_\(rq
|
||||
header that is used to mboxes\&. The message also MUST NOT have any
|
||||
\(lqFrom_\(rq
|
||||
lines in the contents of the message prefixed by the
|
||||
\(lq>\(rq
|
||||
character\&.
|
||||
.PP
|
||||
When saving the message, the number of bytes returned by the
|
||||
\fBwrite\fR(2)
|
||||
system call must be checked, in order to make sure that the complete message has been written out\&.
|
||||
.PP
|
||||
After the message is saved, the file descriptor is
|
||||
\fBfstat\fR(2)\-ed\&. The file\*(Aqs device number, inode number, and the its byte size, are saved\&. The file is closed and is then immediately moved/renamed into the
|
||||
new
|
||||
subdirectory\&. The name of the file in
|
||||
new
|
||||
should be
|
||||
\(lqtime\&.MusecPpidVdevIino\&.host,S=\fIcnt\fR\(rq, or
|
||||
\(lqtime\&.MusecPpidVdevIino_unique\&.host,S=\fIcnt\fR\(rq\&.
|
||||
\(lqdev\(rq
|
||||
is the message\*(Aqs device number,
|
||||
\(lqino\(rq
|
||||
is the message\*(Aqs inode number (from the previous
|
||||
\fBfstat\fR(2)
|
||||
call); and
|
||||
\(lqcnt\(rq
|
||||
is the message\*(Aqs size, in bytes\&.
|
||||
.PP
|
||||
The
|
||||
\(lq,S=\fIcnt\fR\(rq
|
||||
part optimizes the
|
||||
\m[blue]\fBCourier\fR\m[]\&\s-2\u[3]\d\s+2
|
||||
mail server\*(Aqs maildir quota enhancement; it allows the size of all the mail stored in the maildir to be added up without issuing the
|
||||
\fBstat\fR(2)
|
||||
system call for each individual message (this can be quite a performance drain with certain network filesystems)\&.
|
||||
.SS "READING MAIL FROM MAILDIRS"
|
||||
.PP
|
||||
Applications that read mail from maildirs should do it in the following order:
|
||||
.PP
|
||||
When opening a maildir or a maildir folder, read the
|
||||
tmp
|
||||
subdirectory and delete any files in there that are at least 36 hours old\&.
|
||||
.PP
|
||||
Look for new messages in the
|
||||
new
|
||||
subdirectory\&. Rename
|
||||
\fInew/filename\fR, as
|
||||
\fIcur/filename:2,info\fR\&. Here,
|
||||
\fIinfo\fR
|
||||
represents the state of the message, and it consists of zero or more boolean flags chosen from the following:
|
||||
\(lqD\(rq
|
||||
\- this is a \*(Aqdraft\*(Aq message,
|
||||
\(lqR\(rq
|
||||
\- this message has been replied to,
|
||||
\(lqS\(rq
|
||||
\- this message has been viewed (seen),
|
||||
\(lqT\(rq
|
||||
\- this message has been marked to be deleted (trashed), but is not yet removed (messages are removed from maildirs simply by deleting their file),
|
||||
\(lqF\(rq
|
||||
\- this message has been marked by the user, for some purpose\&. These flags must be stored in alphabetical order\&. New messages contain only the
|
||||
:2,
|
||||
suffix, with no flags, indicating that the messages were not seen, replied, marked, or deleted\&.
|
||||
.PP
|
||||
Maildirs may have maximum size quotas defined, but these quotas are purely voluntary\&. If you need to implement mandatory quotas, you should use any quota facilities provided by the underlying filesystem that is used to store the maildirs\&. The maildir quota enhancement is designed to be used in certain situations where filesystem\-based quotas cannot be used for some reason\&. The implementation is designed to avoid the use of any locking\&. As such, at certain times the calculated quota may be imprecise, and certain anomalous situations may result in the maildir actually going over the stated quota\&. One such situation would be when applications create messages without updating the quota estimate for the maildir\&. Eventually it will be precisely recalculated, but wherever possible new messages should be created in compliance with the voluntary quota protocol\&.
|
||||
.PP
|
||||
The voluntary quota protocol involves some additional procedures that must be followed when creating or deleting messages within a given maildir or its subfolders\&. The
|
||||
\m[blue]\fB\fBdeliverquota\fR(8)\fR\m[]\&\s-2\u[4]\d\s+2
|
||||
command is a tiny application that delivers a single message to a maildir using the voluntary quota protocol, and hopefully it can be used as a measure of last resort\&. Alternatively, applications can use the
|
||||
libmaildir\&.a
|
||||
library to handle all the low\-level dirty details for them\&. The voluntary quota enhancement is described in the
|
||||
\m[blue]\fB\fBmaildirquota\fR(7)\fR\m[]\&\s-2\u[5]\d\s+2
|
||||
man page\&.
|
||||
.SS "Maildir Quotas"
|
||||
.PP
|
||||
This is a voluntary mechanism for enforcing "loose" quotas on the maximum sizes of maildirs\&. This mechanism is enforced in software, and not by the operating system\&. Therefore it is only effective as long as the maildirs themselves are not directly accessible by their users, since this mechanism is trivially disabled\&.
|
||||
.PP
|
||||
If possible, operating system\-enforced quotas are preferrable\&. Where operating system quota enforcement is not available, or not possible, this voluntary quota enforcement mechanism might be an acceptable compromise\&. Since it\*(Aqs enforced in software, all software that modifies or accesses the maildirs is required to voluntary obey and enforce a quota\&. The voluntary quota implementation is flexible enough to allow non quota\-aware applications to also access the maildirs, without any drastic consequences\&. There will be some non\-drastic consequences, though\&. Of course, non quota\-aware applications will not enforce any defined quotas\&. Furthermore, this voluntary maildir quota mechanism works by estimating the current size of the maildir, with periodic exact recalculation\&. Obviously non quota\-aware maildir applications will not update the maildir size estimation, so the estimate will be thrown off for some period of time, until the next recalculation\&.
|
||||
.PP
|
||||
This voluntary quota mechanism is designed to be a reasonable compromise between effectiveness, and performance\&. The entire purpose of using maildir\-based mail storage is to avoid any kind of locking, and to permit parallel access to mail by multiple applications\&. In order to compute the exact size of a maildir, the maildir must be locked somehow to prevent any modifications while its contents are added up\&. Obviously something like that defeats the original purpose of using maildirs, therefore the voluntary quota mechanism does not use locking, and that\*(Aqs why the current recorded maildir size is always considered to be an estimate\&. Regular size recalculations will compensate for any occasional race conditions that result in the estimate to be thrown off\&.
|
||||
.PP
|
||||
A quota for an existing maildir is installed by running maildirmake with the
|
||||
\-q
|
||||
option, and naming an existing maildir\&. The
|
||||
\-q
|
||||
option takes a parameter,
|
||||
\fIquota\fR, which is a comma\-separated list of quota specifications\&. A quota specification consists of a number followed by either \*(AqS\*(Aq, indicating the maximum message size in bytes, or \*(AqC\*(Aq, maximum number of messages\&. For example:
|
||||
.sp
|
||||
.if n \{\
|
||||
.RS 4
|
||||
.\}
|
||||
.nf
|
||||
\fBmaildirmake \-q 5000000S,1000C \&./Maildir\fR
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.PP
|
||||
This sets the quota to 5,000,000 bytes or 1000 messages, whichever comes first\&.
|
||||
.sp
|
||||
.if n \{\
|
||||
.RS 4
|
||||
.\}
|
||||
.nf
|
||||
\fBmaildirmake \-q 1000000S \&./Maildir\fR
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.PP
|
||||
This sets the quota to 1,000,000 bytes, without limiting the number of messages\&.
|
||||
.PP
|
||||
A quota of an existing maildir can be changed by rerunning the
|
||||
\fBmaildirmake\fR
|
||||
command with a new
|
||||
\-q
|
||||
option\&. To delete a quota entirely, delete the
|
||||
\fIMaildir\fR/maildirsize
|
||||
file\&.
|
||||
.SH "SEE ALSO"
|
||||
.PP
|
||||
\m[blue]\fB\fBmaildirmake\fR(1)\fR\m[]\&\s-2\u[1]\d\s+2\&.
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fBSam Varshavchik\fR
|
||||
.RS 4
|
||||
Author
|
||||
.RE
|
||||
.SH "NOTES"
|
||||
.IP " 1." 4
|
||||
\fBmaildirmake\fR(1)
|
||||
.RS 4
|
||||
\%http://www.courier-mta.org/maildirmake.html
|
||||
.RE
|
||||
.IP " 2." 4
|
||||
RFC 1521
|
||||
.RS 4
|
||||
\%http://www.rfc-editor.org/rfc/rfc1521.txt
|
||||
.RE
|
||||
.IP " 3." 4
|
||||
Courier
|
||||
.RS 4
|
||||
\%http://www.courier-mta.org
|
||||
.RE
|
||||
.IP " 4." 4
|
||||
\fBdeliverquota\fR(8)
|
||||
.RS 4
|
||||
\%http://www.courier-mta.org/deliverquota.html
|
||||
.RE
|
||||
.IP " 5." 4
|
||||
\fBmaildirquota\fR(7)
|
||||
.RS 4
|
||||
\%http://www.courier-mta.org/maildirquota.html
|
||||
.RE
|
|
@ -0,0 +1,187 @@
|
|||
'\" t
|
||||
.\" -*-nroff-*-
|
||||
.\"
|
||||
.\" Copyright (C) 2000 Thomas Roessler <roessler@does-not-exist.org>
|
||||
.\"
|
||||
.\" This document is in the public domain and may be distributed and
|
||||
.\" changed arbitrarily.
|
||||
.\"
|
||||
.TH mbox 5 "February 19th, 2002" Unix "User Manuals"
|
||||
.\"
|
||||
.SH NAME
|
||||
mbox \- Format for mail message storage.
|
||||
.\"
|
||||
.SH DESCRIPTION
|
||||
This document describes the format traditionally used by Unix hosts
|
||||
to store mail messages locally.
|
||||
.B mbox
|
||||
files typically reside in the system's mail spool, under various
|
||||
names in users' Mail directories, and under the name
|
||||
.B mbox
|
||||
in users' home directories.
|
||||
.PP
|
||||
An
|
||||
.B mbox
|
||||
is a text file containing an arbitrary number of e-mail messages.
|
||||
Each message consists of a postmark, followed by an e-mail message
|
||||
formatted according to \fBRFC822\fP, \fBRFC2822\fP. The file format
|
||||
is line-oriented. Lines are separated by line feed characters (ASCII 10).
|
||||
.PP
|
||||
A postmark line consists of the four characters "From", followed by
|
||||
a space character, followed by the message's envelope sender
|
||||
address, followed by whitespace, and followed by a time stamp. This
|
||||
line is often called From_ line.
|
||||
.PP
|
||||
The sender address is expected to be
|
||||
.B addr-spec
|
||||
as defined in \fBRFC2822\fP 3.4.1. The date is expected to be
|
||||
.B date-time
|
||||
as output by
|
||||
.BR asctime (3).
|
||||
For compatibility reasons with legacy software, two-digit years
|
||||
greater than or equal to 70 should be interpreted as the years
|
||||
1970+, while two-digit years less than 70 should be interpreted as
|
||||
the years 2000-2069. Software reading files in this format should
|
||||
also be prepared to accept non-numeric timezone information such as
|
||||
"CET DST" for Central European Time, daylight saving time.
|
||||
.PP
|
||||
Example:
|
||||
.IP "" 1
|
||||
>From example@example.com Fri Jun 23 02:56:55 2000
|
||||
.PP
|
||||
In order to avoid misinterpretation of lines in message bodies
|
||||
which begin with the four characters "From", followed by a space
|
||||
character, the mail delivery agent must quote any occurrence
|
||||
of "From " at the start of a body line.
|
||||
.sp
|
||||
There are two different quoting schemes, the first (\fBMBOXO\fP) only
|
||||
quotes plain "From " lines in the body by prepending a '>' to the
|
||||
line; the second (\fBMBOXRD\fP) also quotes already quoted "From "
|
||||
lines by prepending a '>' (i.e. ">From ", ">>From ", ...). The later
|
||||
has the advantage that lines like
|
||||
.IP "" 1
|
||||
>From the command line you can use the '\-p' option
|
||||
.PP
|
||||
aren't dequoted wrongly as a \fBMBOXRD\fP-MDA would turn the line
|
||||
into
|
||||
.IP "" 1
|
||||
>>From the command line you can use the '\-p' option
|
||||
.PP
|
||||
before storing it. Besides \fBMBOXO\fP and \fBMBOXRD\fP there is also
|
||||
\fBMBOXCL\fP which is \fBMBOXO\fP with a "Content-Length:"\-field with the
|
||||
number of bytes in the message body; some MUAs (like
|
||||
.BR mutt (1))
|
||||
do automatically transform \fBMBOXO\fP mailboxes into \fBMBOXCL\fP ones when
|
||||
ever they write them back as \fBMBOXCL\fP can be read by any \fBMBOXO\fP-MUA
|
||||
without any problems.
|
||||
.PP
|
||||
If the modification-time (usually determined via
|
||||
.BR stat (2))
|
||||
of a nonempty
|
||||
.B mbox
|
||||
file is greater than the access-time the file has new mail. Many MUAs
|
||||
place a Status: header in each message to indicate which messages have
|
||||
already been read.
|
||||
.\"
|
||||
.SH LOCKING
|
||||
Since
|
||||
.B mbox
|
||||
files are frequently accessed by multiple programs in parallel,
|
||||
.B mbox
|
||||
files should generally not be accessed without locking.
|
||||
.PP
|
||||
Three different locking mechanisms (and combinations thereof) are in
|
||||
general use:
|
||||
.IP "\(bu"
|
||||
.BR fcntl (2)
|
||||
locking is mostly used on recent, POSIX-compliant systems. Use of
|
||||
this locking method is, in particular, advisable if
|
||||
.B mbox
|
||||
files are accessed through the Network File System (NFS), since it
|
||||
seems the only way to reliably invalidate NFS clients' caches.
|
||||
.IP "\(bu"
|
||||
.BR flock (2)
|
||||
locking is mostly used on BSD-based systems.
|
||||
.IP "\(bu"
|
||||
Dotlocking is used on all kinds of systems. In order to lock an
|
||||
.B mbox
|
||||
file named \fIfolder\fR, an application first creates a temporary file
|
||||
with a unique name in the directory in which the
|
||||
\fIfolder\fR resides. The application then tries to use the
|
||||
.BR link (2)
|
||||
system call to create a hard link named \fIfolder.lock\fR
|
||||
to the temporary file. The success of the
|
||||
.BR link (2)
|
||||
system call should be additionally verified using
|
||||
.BR stat (2)
|
||||
calls. If the link has succeeded, the mail folder is considered
|
||||
dotlocked. The temporary file can then safely be unlinked.
|
||||
.IP ""
|
||||
In order to release the lock, an application just unlinks the
|
||||
\fIfolder.lock\fR file.
|
||||
.PP
|
||||
If multiple methods are combined, implementors should make sure to
|
||||
use the non-blocking variants of the
|
||||
.BR fcntl (2)
|
||||
and
|
||||
.BR flock (2)
|
||||
system calls in order to avoid deadlocks.
|
||||
.PP
|
||||
If multiple methods are combined, an
|
||||
.B mbox
|
||||
file must not be considered to have been successfully locked before
|
||||
all individual locks were obtained. When one of the individual
|
||||
locking methods fails, an application should release all locks it
|
||||
acquired successfully, and restart the entire locking procedure from
|
||||
the beginning, after a suitable delay.
|
||||
.PP
|
||||
The locking mechanism used on a particular system is a matter of
|
||||
local policy, and should be consistently used by all applications
|
||||
installed on the system which access
|
||||
.B mbox
|
||||
files. Failure to do so may result in loss of e-mail data, and in
|
||||
corrupted
|
||||
.B mbox
|
||||
files.
|
||||
.\"
|
||||
.SH FILES
|
||||
.IR /var/spool/mail/$LOGNAME
|
||||
.RS
|
||||
\fB$LOGNAME\fP's incoming mail folder.
|
||||
.RE
|
||||
.PP
|
||||
.IR $HOME/mbox
|
||||
.RS
|
||||
user's archived mail messages, in his \fB$HOME\fP directory.
|
||||
.RE
|
||||
.PP
|
||||
.IR $HOME/Mail/
|
||||
.RS
|
||||
A directory in user's \fB$HOME\fP directory which is commonly used to hold
|
||||
.B mbox
|
||||
format folders.
|
||||
.RE
|
||||
.PP
|
||||
.\"
|
||||
.SH "SEE ALSO"
|
||||
.BR mutt (1),
|
||||
.BR fcntl (2),
|
||||
.BR flock (2),
|
||||
.BR link (2),
|
||||
.BR stat (2),
|
||||
.BR asctime (3),
|
||||
.BR maildir (5),
|
||||
.BR mmdf (5),
|
||||
.BR RFC822 ,
|
||||
.BR RFC976 ,
|
||||
.BR RFC2822
|
||||
.\"
|
||||
.SH AUTHOR
|
||||
Thomas Roessler <roessler@does-not-exist.org>, Urs Janssen <urs@tin.org>
|
||||
.\"
|
||||
.SH HISTORY
|
||||
The
|
||||
.B mbox
|
||||
format occurred in Version 6 AT&T Unix.
|
||||
.br
|
||||
A variant of this format was documented in \fBRFC976\fP.
|
|
@ -0,0 +1,235 @@
|
|||
.TH mbox 5
|
||||
.SH "NAME"
|
||||
mbox \- file containing mail messages
|
||||
.SH "INTRODUCTION"
|
||||
The most common format for storage of mail messages is
|
||||
.I mbox
|
||||
format.
|
||||
An
|
||||
.I mbox
|
||||
is a single file containing zero or more mail messages.
|
||||
.SH "MESSAGE FORMAT"
|
||||
A message encoded in
|
||||
.I mbox
|
||||
format begins with a
|
||||
.B From_
|
||||
line, continues with a series of
|
||||
.B \fRnon-\fBFrom_
|
||||
lines,
|
||||
and ends with a blank line.
|
||||
A
|
||||
.B From_
|
||||
line means any line that begins with the characters
|
||||
F, r, o, m, space:
|
||||
|
||||
.EX
|
||||
From god@heaven.af.mil Sat Jan 3 01:05:34 1996
|
||||
.br
|
||||
Return-Path: <god@heaven.af.mil>
|
||||
.br
|
||||
Delivered-To: djb@silverton.berkeley.edu
|
||||
.br
|
||||
Date: 3 Jan 1996 01:05:34 -0000
|
||||
.br
|
||||
From: God <god@heaven.af.mil>
|
||||
.br
|
||||
To: djb@silverton.berkeley.edu (D. J. Bernstein)
|
||||
.br
|
||||
|
||||
.br
|
||||
How's that mail system project coming along?
|
||||
.br
|
||||
|
||||
.EE
|
||||
|
||||
The final line is a completely blank line (no spaces or tabs).
|
||||
Notice that blank lines may also appear elsewhere in the message.
|
||||
|
||||
The
|
||||
.B From_
|
||||
line always looks like
|
||||
.B From
|
||||
.I envsender
|
||||
.I date
|
||||
.IR moreinfo .
|
||||
.I envsender
|
||||
is one word, without spaces or tabs;
|
||||
it is usually the envelope sender of the message.
|
||||
.I date
|
||||
is the delivery date of the message.
|
||||
It always contains exactly 24 characters in
|
||||
.B asctime
|
||||
format.
|
||||
.I moreinfo
|
||||
is optional; it may contain arbitrary information.
|
||||
|
||||
Between the
|
||||
.B From_
|
||||
line and the blank line is a message in RFC 822 format,
|
||||
as described in
|
||||
.BR qmail-header(5) ,
|
||||
subject to
|
||||
.B >From quoting
|
||||
as described below.
|
||||
.SH "HOW A MESSAGE IS DELIVERED"
|
||||
Here is how a program appends a message to an
|
||||
.I mbox
|
||||
file.
|
||||
|
||||
It first creates a
|
||||
.B From_
|
||||
line given the message's envelope sender and the current date.
|
||||
If the envelope sender is empty (i.e., if this is a bounce message),
|
||||
the program uses
|
||||
.B MAILER-DAEMON
|
||||
instead.
|
||||
If the envelope sender contains spaces, tabs, or newlines,
|
||||
the program replaces them with hyphens.
|
||||
|
||||
The program then copies the message, applying
|
||||
.B >From quoting
|
||||
to each line.
|
||||
.B >From quoting
|
||||
ensures that the resulting lines are not
|
||||
.B From_
|
||||
lines:
|
||||
the program prepends a
|
||||
.B >
|
||||
to any
|
||||
.B From_
|
||||
line,
|
||||
.B >From_
|
||||
line,
|
||||
.B >>From_
|
||||
line,
|
||||
.B >>>From_
|
||||
line,
|
||||
etc.
|
||||
|
||||
Finally the program appends a blank line to the message.
|
||||
If the last line of the message was a partial line,
|
||||
it writes two newlines;
|
||||
otherwise it writes one.
|
||||
.SH "HOW A MESSAGE IS READ"
|
||||
A reader scans through an
|
||||
.I mbox
|
||||
file looking for
|
||||
.B From_
|
||||
lines.
|
||||
Any
|
||||
.B From_
|
||||
line marks the beginning of a message.
|
||||
The reader should not attempt to take advantage of the fact that every
|
||||
.B From_
|
||||
line (past the beginning of the file)
|
||||
is preceded by a blank line.
|
||||
|
||||
Once the reader finds a message,
|
||||
it extracts a (possibly corrupted) envelope sender
|
||||
and delivery date out of the
|
||||
.B From_
|
||||
line.
|
||||
It then reads until the next
|
||||
.B From_
|
||||
line or end of file, whichever comes first.
|
||||
It strips off the final blank line
|
||||
and
|
||||
deletes the
|
||||
quoting of
|
||||
.B >From_
|
||||
lines and
|
||||
.B >>From_
|
||||
lines and so on.
|
||||
The result is an RFC 822 message.
|
||||
.SH "COMMON MBOX VARIANTS"
|
||||
There are many variants of
|
||||
.I mbox
|
||||
format.
|
||||
The variant described above is
|
||||
.I mboxrd
|
||||
format, popularized by Rahul Dhesi in June 1995.
|
||||
|
||||
The original
|
||||
.I mboxo
|
||||
format quotes only
|
||||
.B From_
|
||||
lines, not
|
||||
.B >From_
|
||||
lines.
|
||||
As a result it is impossible to tell whether
|
||||
|
||||
.EX
|
||||
From: djb@silverton.berkeley.edu (D. J. Bernstein)
|
||||
.br
|
||||
To: god@heaven.af.mil
|
||||
.br
|
||||
|
||||
.br
|
||||
>From now through August I'll be doing beta testing.
|
||||
.br
|
||||
Thanks for your interest.
|
||||
.EE
|
||||
|
||||
was quoted in the original message.
|
||||
An
|
||||
.I mboxrd
|
||||
reader will always strip off the quoting.
|
||||
|
||||
.I mboxcl
|
||||
format is like
|
||||
.I mboxo
|
||||
format, but includes a Content-Length field with the
|
||||
number of bytes in the message.
|
||||
.I mboxcl2
|
||||
format is like
|
||||
.I mboxcl
|
||||
but has no
|
||||
.B >From
|
||||
quoting.
|
||||
These formats are used by SVR4 mailers.
|
||||
.I mboxcl2
|
||||
cannot be read safely by
|
||||
.I mboxrd
|
||||
readers.
|
||||
.SH "UNSPECIFIED DETAILS"
|
||||
There are many locking mechanisms for
|
||||
.I mbox
|
||||
files.
|
||||
.B qmail-local
|
||||
always uses
|
||||
.B flock
|
||||
on systems that have it, otherwise
|
||||
.BR lockf .
|
||||
|
||||
The delivery date in a
|
||||
.B From_
|
||||
line does not specify a time zone.
|
||||
.B qmail-local
|
||||
always creates the delivery date in GMT
|
||||
so that
|
||||
.I mbox
|
||||
files can be safely transported from one time zone to another.
|
||||
|
||||
If the mtime on a nonempty
|
||||
.I mbox
|
||||
file is greater than the atime,
|
||||
the file has new mail.
|
||||
If the mtime is smaller than the atime,
|
||||
the new mail has been read.
|
||||
If the atime equals the mtime,
|
||||
there is no way to tell whether the file has new mail,
|
||||
since
|
||||
.B qmail-local
|
||||
takes much less than a second to run.
|
||||
One solution is for a mail reader to artificially set the
|
||||
atime to the mtime plus 1.
|
||||
Then the file has new mail if and only if the atime is
|
||||
less than or equal to the mtime.
|
||||
|
||||
Some mail readers place
|
||||
.B Status
|
||||
fields in each message to indicate which messages have been read.
|
||||
.SH "SEE ALSO"
|
||||
maildir(5),
|
||||
qmail-header(5),
|
||||
qmail-local(8)
|
|
@ -0,0 +1,239 @@
|
|||
.TH maildir 5
|
||||
.SH "NAME"
|
||||
maildir \- directory for incoming mail messages
|
||||
.SH "INTRODUCTION"
|
||||
.I maildir
|
||||
is a structure for
|
||||
directories of incoming mail messages.
|
||||
It solves the reliability problems that plague
|
||||
.I mbox
|
||||
files and
|
||||
.I mh
|
||||
folders.
|
||||
.SH "RELIABILITY ISSUES"
|
||||
A machine may crash while it is delivering a message.
|
||||
For both
|
||||
.I mbox
|
||||
files and
|
||||
.I mh
|
||||
folders this means that the message will be silently truncated.
|
||||
Even worse: for
|
||||
.I mbox
|
||||
format, if the message is truncated in the middle of a line,
|
||||
it will be silently joined to the next message.
|
||||
The mail transport agent will try again later to deliver the message,
|
||||
but it is unacceptable that a corrupted message should show up at all.
|
||||
In
|
||||
.IR maildir ,
|
||||
every message is guaranteed complete upon delivery.
|
||||
|
||||
A machine may have two programs simultaneously delivering mail
|
||||
to the same user.
|
||||
The
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
formats require the programs to update a single central file.
|
||||
If the programs do not use some locking mechanism,
|
||||
the central file will be corrupted.
|
||||
There are several
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
locking mechanisms,
|
||||
none of which work portably and reliably.
|
||||
In contrast, in
|
||||
.IR maildir ,
|
||||
no locks are ever necessary.
|
||||
Different delivery processes never touch the same file.
|
||||
|
||||
A user may try to delete messages from his mailbox at the same
|
||||
moment that the machine delivers a new message.
|
||||
For
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
formats, the user's mail-reading program must know
|
||||
what locking mechanism the mail-delivery programs use.
|
||||
In contrast, in
|
||||
.IR maildir ,
|
||||
any delivered message
|
||||
can be safely updated or deleted by a mail-reading program.
|
||||
|
||||
Many sites use Sun's
|
||||
.B Network F\fPa\fBil\fPur\fBe System
|
||||
(NFS),
|
||||
presumably because the operating system vendor does not offer
|
||||
anything else.
|
||||
NFS exacerbates all of the above problems.
|
||||
Some NFS implementations don't provide
|
||||
.B any
|
||||
reliable locking mechanism.
|
||||
With
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
formats,
|
||||
if two machines deliver mail to the same user,
|
||||
or if a user reads mail anywhere except the delivery machine,
|
||||
the user's mail is at risk.
|
||||
.I maildir
|
||||
works without trouble over NFS.
|
||||
.SH "THE MAILDIR STRUCTURE"
|
||||
A directory in
|
||||
.I maildir
|
||||
format has three subdirectories,
|
||||
all on the same filesystem:
|
||||
.BR tmp ,
|
||||
.BR new ,
|
||||
and
|
||||
.BR cur .
|
||||
|
||||
Each file in
|
||||
.B new
|
||||
is a newly delivered mail message.
|
||||
The modification time of the file is the delivery date of the message.
|
||||
The message is delivered
|
||||
.I without
|
||||
an extra UUCP-style
|
||||
.B From_
|
||||
line,
|
||||
.I without
|
||||
any
|
||||
.B >From
|
||||
quoting,
|
||||
and
|
||||
.I without
|
||||
an extra blank line at the end.
|
||||
The message is normally in RFC 822 format,
|
||||
starting with a
|
||||
.B Return-Path
|
||||
line and a
|
||||
.B Delivered-To
|
||||
line,
|
||||
but it could contain arbitrary binary data.
|
||||
It might not even end with a newline.
|
||||
|
||||
Files in
|
||||
.B cur
|
||||
are just like files in
|
||||
.BR new .
|
||||
The big difference is that files in
|
||||
.B cur
|
||||
are no longer new mail:
|
||||
they have been seen by the user's mail-reading program.
|
||||
.SH "HOW A MESSAGE IS DELIVERED"
|
||||
The
|
||||
.B tmp
|
||||
directory is used to ensure reliable delivery,
|
||||
as discussed here.
|
||||
|
||||
A program delivers a mail message in six steps.
|
||||
First, it
|
||||
.B chdir()\fPs
|
||||
to the
|
||||
.I maildir
|
||||
directory.
|
||||
Second, it
|
||||
.B stat()s
|
||||
the name
|
||||
.BR tmp/\fItime.pid.host ,
|
||||
where
|
||||
.I time
|
||||
is the number of seconds since the beginning of 1970 GMT,
|
||||
.I pid
|
||||
is the program's process ID,
|
||||
and
|
||||
.I host
|
||||
is the host name.
|
||||
Third, if
|
||||
.B stat()
|
||||
returned anything other than ENOENT,
|
||||
the program sleeps for two seconds, updates
|
||||
.IR time ,
|
||||
and tries the
|
||||
.B stat()
|
||||
again, a limited number of times.
|
||||
Fourth, the program
|
||||
creates
|
||||
.BR tmp/\fItime.pid.host .
|
||||
Fifth, the program
|
||||
.I NFS-writes
|
||||
the message to the file.
|
||||
Sixth, the program
|
||||
.BR link() s
|
||||
the file to
|
||||
.BR new/\fItime.pid.host .
|
||||
At that instant the message has been successfully delivered.
|
||||
|
||||
The delivery program is required to start a 24-hour timer before
|
||||
creating
|
||||
.BR tmp/\fItime.pid.host ,
|
||||
and to abort the delivery
|
||||
if the timer expires.
|
||||
Upon error, timeout, or normal completion,
|
||||
the delivery program may attempt to
|
||||
.B unlink()
|
||||
.BR tmp/\fItime.pid.host .
|
||||
|
||||
.I NFS-writing
|
||||
means
|
||||
(1) as usual, checking the number of bytes returned from each
|
||||
.B write()
|
||||
call;
|
||||
(2) calling
|
||||
.B fsync()
|
||||
and checking its return value;
|
||||
(3) calling
|
||||
.B close()
|
||||
and checking its return value.
|
||||
(Standard NFS implementations handle
|
||||
.B fsync()
|
||||
incorrectly
|
||||
but make up for it by abusing
|
||||
.BR close() .)
|
||||
.SH "HOW A MESSAGE IS READ"
|
||||
A mail reader operates as follows.
|
||||
|
||||
It looks through the
|
||||
.B new
|
||||
directory for new messages.
|
||||
Say there is a new message,
|
||||
.BR new/\fIunique .
|
||||
The reader may freely display the contents of
|
||||
.BR new/\fIunique ,
|
||||
delete
|
||||
.BR new/\fIunique ,
|
||||
or rename
|
||||
.B new/\fIunique
|
||||
as
|
||||
.BR cur/\fIunique:info .
|
||||
See
|
||||
.B http://pobox.com/~djb/proto/maildir.html
|
||||
for the meaning of
|
||||
.IR info .
|
||||
|
||||
The reader is also expected to look through the
|
||||
.B tmp
|
||||
directory and to clean up any old files found there.
|
||||
A file in
|
||||
.B tmp
|
||||
may be safely removed if it
|
||||
has not been accessed in 36 hours.
|
||||
|
||||
It is a good idea for readers to skip all filenames in
|
||||
.B new
|
||||
and
|
||||
.B cur
|
||||
starting with a dot.
|
||||
Other than this, readers should not attempt to parse filenames.
|
||||
.SH "ENVIRONMENT VARIABLES"
|
||||
Mail readers supporting
|
||||
.I maildir
|
||||
use the
|
||||
.B MAILDIR
|
||||
environment variable
|
||||
as the name of the user's primary mail directory.
|
||||
.SH "SEE ALSO"
|
||||
mbox(5),
|
||||
qmail-local(8)
|
|
@ -0,0 +1,87 @@
|
|||
" Place this plugin in
|
||||
"
|
||||
" `$HOME/.vim/after/ftplugin/mail.vim` for vim
|
||||
" `$HOME/.config/nvim/after/ftplugin/mail.vim` for neovim
|
||||
|
||||
" Don't use modelines in e-mail messages
|
||||
setlocal nomodeline
|
||||
setlocal textwidth=72
|
||||
|
||||
" *fo-a*
|
||||
" a Automatic formatting of paragraphs.
|
||||
" Every time text is inserted or deleted the paragraph will be reformatted.
|
||||
" *fo-w*
|
||||
" w Trailing white space indicates a paragraph continues in the next line.
|
||||
" A line that ends in a non-white character ends a paragraph.
|
||||
" *fo-q*
|
||||
" q Allow formatting of comments with "gq".
|
||||
" *fo-t*
|
||||
" t Auto-wrap text using textwidth
|
||||
" *fo-r*
|
||||
" r Automatically insert the current comment leader after hitting <Enter> in
|
||||
" Insert mode.
|
||||
" *fo-c*
|
||||
" c Auto-wrap comments using textwidth, inserting the current comment leader
|
||||
" automatically.
|
||||
" *fo-2*
|
||||
" 2 When formatting text, use the indent of the second line of a paragraph for
|
||||
" the rest of the paragraph, instead of the indent of the first line.
|
||||
" This supports paragraphs in which the first line has a different indent than
|
||||
" the rest.
|
||||
" Note that 'autoindent' must be set too.
|
||||
" Example:
|
||||
" first line of a paragraph
|
||||
" second line of the same paragraph
|
||||
" third line.
|
||||
" This also works inside comments, ignoring the comment leader.
|
||||
setlocal formatoptions=aqtw2r
|
||||
|
||||
" Disable adding two spaces after '.', '?' and '!' with a join command.
|
||||
setlocal nojoinspaces
|
||||
|
||||
" Disable smartident (meant for source code)
|
||||
setlocal nosmartindent
|
||||
|
||||
" *'comments'* *'com'* *E524* *E525*
|
||||
" A comma-separated list of strings that can start a comment line.
|
||||
" See |format-comments|.
|
||||
" See |option-backslash| about using backslashes to insert a space.
|
||||
"
|
||||
"
|
||||
" The 'comments' option is a comma-separated list of parts.
|
||||
" Each part defines a type of comment string.
|
||||
" A part consists of: {flags}:{string}
|
||||
"
|
||||
" {string} is the literal text that must appear.
|
||||
"
|
||||
" {flags}:
|
||||
" n Nested comment.
|
||||
" Nesting with mixed parts is allowed.
|
||||
" If 'comments' is "n:),n:>" a line starting with "> ) >" is a comment.
|
||||
"
|
||||
" b Blank (<Space>, <Tab> or <EOL>) required after {string}.
|
||||
setlocal comments+=nb:>
|
||||
|
||||
" Highlight trailing whitespace as errors.
|
||||
match ErrorMsg '\s\+$'
|
||||
|
||||
" MAIL *mail.vim* *ft-mail.vim*
|
||||
" By default mail.vim synchronises syntax to 100 lines before the first
|
||||
" displayed line.
|
||||
" If you have a slow machine, and generally deal with emails with short
|
||||
" headers, you can change this to a smaller value:
|
||||
|
||||
let mail_minlines = 30
|
||||
|
||||
|
||||
" *no_mail_maps* *g:no_mail_maps*
|
||||
" Disable defining mappings for a specific filetype by setting a variable,
|
||||
" which contains the name of the filetype.
|
||||
" For the "mail" filetype this would be:
|
||||
let no_mail_maps = 1
|
||||
|
||||
" Local mappings:
|
||||
" <LocalLeader>q or \\MailQuote
|
||||
" Quotes the text selected in Visual mode, or from the cursor position
|
||||
" to the end of the file in Normal mode.
|
||||
" This means "> " is inserted in each line.
|
|
@ -17,29 +17,30 @@
|
|||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.Dd January 23, 2020
|
||||
.\".Dd November 11, 2022
|
||||
.Dd March 10, 2024
|
||||
.Dt MELI-THEMES 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli-themes
|
||||
.Nd themes for the
|
||||
.Nm meli
|
||||
mail client
|
||||
.Xr meli 1
|
||||
terminal e-mail client
|
||||
.Sh SYNOPSIS
|
||||
.Nm meli
|
||||
comes with two themes,
|
||||
.Ic dark
|
||||
(default) and
|
||||
.Ic light .
|
||||
.sp
|
||||
.Pp
|
||||
Custom themes are defined as lists of key-values in the configuration files:
|
||||
.Bl -bullet -compact
|
||||
.Bl -item -compact -offset 2
|
||||
.It
|
||||
.Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
.It
|
||||
.Pa $XDG_CONFIG_HOME/meli/themes/*.toml
|
||||
.El
|
||||
.sp
|
||||
.Pp
|
||||
The application theme is defined in the configuration as follows:
|
||||
.Bd -literal
|
||||
[terminal]
|
||||
|
@ -56,9 +57,9 @@ keys are settings for the
|
|||
.Ic compact
|
||||
mail listing style.
|
||||
A setting contains three fields: fg for foreground color, bg for background color, and attrs for text attribute.
|
||||
.sp
|
||||
.Pp
|
||||
.Dl \&"widget.key.label\&" = { fg = \&"Default\&", bg = \&"Default\&", attrs = \&"Default\&" }
|
||||
.sp
|
||||
.Pp
|
||||
Each field contains a value, which may be either a color/attribute, a link (key name) or a valid alias.
|
||||
An alias is a string starting with the \&"\&$\&" character and must be declared in advance in the
|
||||
.Ic color_aliases
|
||||
|
@ -69,10 +70,14 @@ An alias' value can be any valid value, including links and other aliases, as lo
|
|||
In the case of a link the setting's real value depends on the value of the referred key.
|
||||
This allows for defaults within a group of associated values.
|
||||
Cyclic references in a theme results in an error:
|
||||
.sp
|
||||
.Pp
|
||||
.Dl spooky theme contains a cycle: fg: mail.listing.compact.even -> mail.listing.compact.highlighted -> mail.listing.compact.odd -> mail.listing.compact.even
|
||||
.Pp
|
||||
Two themes are included by default, `light` and `dark`.
|
||||
Two themes are included by default,
|
||||
.Ql light
|
||||
and
|
||||
.Ql dark Ns
|
||||
\&.
|
||||
.Sh EXAMPLES
|
||||
Specific settings from already defined themes can be overwritten:
|
||||
.Bd -literal
|
||||
|
@ -100,18 +105,18 @@ Custom themes can be included in your configuration files or be saved independen
|
|||
.Pa $XDG_CONFIG_HOME/meli/themes/
|
||||
directory as TOML files.
|
||||
To start creating a theme right away, you can begin by editing the default theme keys and values:
|
||||
.sp
|
||||
.Dl meli --print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||
.sp
|
||||
.Pp
|
||||
.Dl meli print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||
.Pp
|
||||
.Pa new_theme.toml
|
||||
will now include all keys and values of the "dark" theme.
|
||||
.sp
|
||||
.Dl meli --print-loaded-themes
|
||||
.sp
|
||||
.Pp
|
||||
.Dl meli print-loaded-themes
|
||||
.Pp
|
||||
will print all loaded themes with the links resolved.
|
||||
.Sh VALID ATTRIBUTE VALUES
|
||||
Case-sensitive.
|
||||
.Bl -bullet -compact
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
"Default"
|
||||
.It
|
||||
|
@ -133,7 +138,7 @@ Any combo of the above separated by a bitwise XOR "\&|" eg "Dim | Italics"
|
|||
.El
|
||||
.Sh VALID COLOR VALUES
|
||||
Color values are of type String with the following valid contents:
|
||||
.Bl -bullet -compact
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
"Default" is the terminal default. (Case-sensitive)
|
||||
.It
|
||||
|
@ -146,8 +151,10 @@ Three character shorthand is also valid, e.g. #09c → #0099cc (Case-insensitive
|
|||
name but with some modifications (for a full table see COLOR NAMES addendum) (Case-sensitive)
|
||||
.El
|
||||
.Sh NO COLOR
|
||||
To completely disable ANSI colors, there are two options:
|
||||
.Bl -bullet -compact
|
||||
To completely disable
|
||||
.Tn ANSI
|
||||
colors, there are two options:
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
Set the
|
||||
.Ic use_color
|
||||
|
@ -157,22 +164,37 @@ option (section
|
|||
.It
|
||||
The
|
||||
.Ev NO_COLOR
|
||||
environmental variable, when present (regardless of its value), prevents the addition of ANSI color.
|
||||
environmental variable, when present (regardless of its value), prevents the addition of
|
||||
.Tn ANSI
|
||||
color.
|
||||
When the configuration value
|
||||
.Ic use_color
|
||||
is explicitly set to true by the user,
|
||||
.Ev NO_COLOR
|
||||
is ignored.
|
||||
.El
|
||||
.sp
|
||||
In this mode, cursor locations (i.e., currently selected entries/items) will use the "reverse video" ANSI attribute to invert the terminal's default foreground/background colors.
|
||||
.Pp
|
||||
In this mode, cursor locations (i.e., currently selected entries/items) will use the
|
||||
.Ql reverse video
|
||||
.Tn ANSI
|
||||
attribute to invert the terminal's default foreground/background colors.
|
||||
.Sh VALID KEYS
|
||||
.Bl -bullet -compact
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
theme_default
|
||||
.It
|
||||
error_message
|
||||
.It
|
||||
highlight
|
||||
.It
|
||||
status.bar
|
||||
.It
|
||||
status.command_bar
|
||||
.It
|
||||
status.history
|
||||
.It
|
||||
status.history.hints
|
||||
.It
|
||||
status.notification
|
||||
.It
|
||||
tab.focused
|
||||
|
@ -227,6 +249,10 @@ mail.listing.compact.even_highlighted
|
|||
.It
|
||||
mail.listing.compact.odd_highlighted
|
||||
.It
|
||||
mail.listing.compact.even_highlighted_selected
|
||||
.It
|
||||
mail.listing.compact.odd_highlighted_selected
|
||||
.It
|
||||
mail.listing.plain.even
|
||||
.It
|
||||
mail.listing.plain.odd
|
||||
|
@ -243,6 +269,10 @@ mail.listing.plain.even_highlighted
|
|||
.It
|
||||
mail.listing.plain.odd_highlighted
|
||||
.It
|
||||
mail.listing.plain.even_highlighted_selected
|
||||
.It
|
||||
mail.listing.plain.odd_highlighted_selected
|
||||
.It
|
||||
mail.listing.conversations
|
||||
.It
|
||||
mail.listing.conversations.subject
|
||||
|
@ -251,16 +281,14 @@ mail.listing.conversations.from
|
|||
.It
|
||||
mail.listing.conversations.date
|
||||
.It
|
||||
mail.listing.conversations.padding
|
||||
.It
|
||||
mail.listing.conversations.unseen
|
||||
.It
|
||||
mail.listing.conversations.unseen_padding
|
||||
.It
|
||||
mail.listing.conversations.highlighted
|
||||
.It
|
||||
mail.listing.conversations.selected
|
||||
.It
|
||||
mail.listing.conversations.highlighted_selected
|
||||
.It
|
||||
mail.view.headers
|
||||
.It
|
||||
mail.view.headers_names
|
||||
|
@ -296,7 +324,7 @@ pager.highlight_search_current
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
Aqua:14:_:Black:0
|
||||
Aquamarine1:122:_:Maroon:1
|
||||
Aquamarine2:86:_:Green:2
|
||||
|
@ -332,7 +360,7 @@ DarkMagenta1:91:_:SpringGreen6:29
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
DarkOliveGreen1:192:_:Turquoise4:30
|
||||
DarkOliveGreen2:155:_:DeepSkyBlue3:31
|
||||
DarkOliveGreen3:191:_:DeepSkyBlue4:32
|
||||
|
@ -368,7 +396,7 @@ DeepPink4:125:_:Grey37:59
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
DeepPink6:162:_:MediumPurple6:60
|
||||
DeepPink7:89:_:SlateBlue2:61
|
||||
DeepPink8:53:_:SlateBlue3:62
|
||||
|
@ -404,7 +432,7 @@ Grey19:236:_:DeepPink7:89
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
Grey23:237:_:DarkMagenta:90
|
||||
Grey27:238:_:DarkMagenta1:91
|
||||
Grey3:232:_:DarkViolet1:92
|
||||
|
@ -440,7 +468,7 @@ HotPink2:169:_:LightGreen:119
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
HotPink3:132:_:LightGreen1:120
|
||||
HotPink4:168:_:PaleGreen1:121
|
||||
IndianRed:131:_:Aquamarine1:122
|
||||
|
@ -476,7 +504,7 @@ LightSlateGrey:103:_:DarkOliveGreen6:149
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
LightSteelBlue:147:_:DarkSeaGreen6:150
|
||||
LightSteelBlue1:189:_:DarkSeaGreen3:151
|
||||
LightSteelBlue3:146:_:LightCyan3:152
|
||||
|
@ -512,7 +540,7 @@ NavajoWhite3:144:_:LightGoldenrod3:179
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
Navy:4:_:Tan:180
|
||||
NavyBlue:17:_:MistyRose3:181
|
||||
Olive:3:_:Thistle3:182
|
||||
|
@ -548,7 +576,7 @@ Purple5:55:_:Salmon1:209
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
Red:9:_:LightCoral:210
|
||||
Red1:196:_:PaleVioletRed1:211
|
||||
Red2:124:_:Orchid2:212
|
||||
|
@ -584,7 +612,7 @@ Tan:180:_:Grey30:239
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name ↓:byte:_:name:byte ↓
|
||||
name \(da:byte:_:name:byte \(da
|
||||
Teal:6:_:Grey35:240
|
||||
Thistle1:225:_:Grey39:241
|
||||
Thistle3:182:_:Grey42:242
|
||||
|
@ -605,15 +633,34 @@ Yellow6:148:_:Grey93:255
|
|||
.Sh SEE ALSO
|
||||
.Xr meli 1 ,
|
||||
.Xr meli.conf 5
|
||||
.Sh CONFORMING TO
|
||||
TOML Standard v.0.5.0 https://toml.io/en/v0.5.0
|
||||
.sp
|
||||
https://no-color.org/
|
||||
.Sh STANDARDS
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://toml.io/en/v0.5.0 "TOML Standard v.0.5.0"
|
||||
.It
|
||||
.Lk https://no\-color.org/ "NO_COLOR: disabling ANSI color output by default"
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
Copyright 2017-2019
|
||||
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
|
||||
Copyright 2017\(en2024
|
||||
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||
.Pp
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
(See COPYING for full copyright and warranty notices.)
|
||||
.Pp
|
||||
.Aq https://meli.delivery
|
||||
.Po
|
||||
See
|
||||
.Pa COPYING
|
||||
for full copyright and warranty notices.
|
||||
.Pc
|
||||
.Ss Links
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://meli\-email.org "Website"
|
||||
.It
|
||||
.Lk https://git.meli\-email.org/meli/meli "Main\ git\ repository\ and\ issue\ tracker"
|
||||
.It
|
||||
.Lk https://codeberg.org/meli/meli "Official\ read-only\ git\ mirror\ on\ codeberg.org"
|
||||
.It
|
||||
.Lk https://github.com/meli/meli "Official\ read-only\ git\ mirror\ on\ github.com"
|
||||
.It
|
||||
.Lk https://crates.io/crates/meli "meli\ crate\ on\ crates.io"
|
||||
.El
|
|
@ -17,12 +17,42 @@
|
|||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.Dd July 29, 2019
|
||||
.de HorizontalRule
|
||||
.\"\l'\n(.l\(ru1.25'
|
||||
.sp
|
||||
..
|
||||
.de Shortcut
|
||||
.Sm
|
||||
.Aq \\$1
|
||||
\
|
||||
.Po
|
||||
.Em shortcuts.\\$2\&. Ns
|
||||
.Em \\$3
|
||||
.Pc
|
||||
.Sm
|
||||
..
|
||||
.de ShortcutPeriod
|
||||
.Aq \\$1
|
||||
.Po
|
||||
.Em shortcuts.\\$2\&. Ns
|
||||
.Em \\$3
|
||||
.Pc Ns
|
||||
..
|
||||
.de Command
|
||||
.Bd -ragged
|
||||
.Cm \\$*
|
||||
.Ed
|
||||
.sp
|
||||
..
|
||||
.\".Dd November 11, 2022
|
||||
.Dd March 10, 2024
|
||||
.Dt MELI 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli
|
||||
.Nd Meli Mail User Agent. meli is the Greek word for honey
|
||||
.Nd terminal e\-mail client
|
||||
.Em μέλι
|
||||
is the Greek word for honey
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -help | h
|
||||
|
@ -43,17 +73,41 @@ if given, or at
|
|||
.It Cm test-config Op Ar path
|
||||
Test a configuration file for syntax issues or missing options.
|
||||
.It Cm man Op Ar page
|
||||
Print documentation page and exit (Piping to a pager is recommended.)
|
||||
Print documentation page and exit (Piping to a pager is recommended).
|
||||
.It Cm install-man Op Ar path
|
||||
Install manual pages to the first location provided by
|
||||
.Ev MANPATH
|
||||
or
|
||||
.Xr manpath 1 ,
|
||||
unless you specify the directory as an argument.
|
||||
.It Cm compiled-with
|
||||
Print compile time feature flags of this binary.
|
||||
.It Cm edit-config
|
||||
Edit configuration files with
|
||||
.Ev EDITOR
|
||||
or
|
||||
.Ev VISUAL Ns
|
||||
\&.
|
||||
.It Cm help
|
||||
Prints help information or the help of the given subcommand(s).
|
||||
.It Cm print-app-directories
|
||||
Print all directories that
|
||||
.Ns Nm
|
||||
creates and uses.
|
||||
.It Cm print-config-path
|
||||
Print location of configuration file that will be loaded on normal app startup.
|
||||
.It Cm print-default-theme
|
||||
Print default theme keys and values in TOML syntax, to be used as a blueprint.
|
||||
.It Cm print-loaded-themes
|
||||
Print all loaded themes in TOML syntax.
|
||||
.It Cm print-log-path
|
||||
Print log file location.
|
||||
.It Cm view
|
||||
View mail from input file.
|
||||
.El
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a terminal mail client aiming for extensive and user-frendly configurability.
|
||||
is a terminal mail client aiming for extensive and user-friendly configurability.
|
||||
.Bd -literal
|
||||
^^ .-=-=-=-. ^^
|
||||
^^ (`-=-=-=-=-`) ^^
|
||||
|
@ -83,17 +137,32 @@ See
|
|||
for the available configuration options.
|
||||
.Pp
|
||||
At any time, you may press
|
||||
.Cm \&?
|
||||
.Shortcut \&? general toggle_help
|
||||
for a searchable list of all available actions and shortcuts, along with every possible setting and command that your version supports.
|
||||
.Pp
|
||||
The main visual navigation tool, the left-side sidebar may be toggled with
|
||||
.Cm `
|
||||
(shortcuts.listing:
|
||||
.Ic toggle_menu_visibility Ns
|
||||
).
|
||||
.ShortcutPeriod \(ga listing toggle_menu_visibility
|
||||
\&.
|
||||
.Pp
|
||||
Each mailbox may be viewed in 4 modes:
|
||||
Plain views each mail individually, Threaded shows their thread relationship visually, Conversations collapses each thread of emails into a single entry, Compact shows one row per thread.
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
.Tg index-style-plain
|
||||
.Em Plain
|
||||
views each mail individually,
|
||||
.It
|
||||
.Tg index-style-threaded
|
||||
.Em Threaded
|
||||
shows their thread relationship visually,
|
||||
.It
|
||||
.Tg index-style-conversations
|
||||
.Em Conversations
|
||||
collapses each thread of e\-mails into a single entry,
|
||||
.It
|
||||
.Tg index-style-compact
|
||||
.Em Compact
|
||||
shows one row per thread.
|
||||
.El
|
||||
.Pp
|
||||
If you're using a light color palette in your terminal, you should set
|
||||
.Em theme = "light"
|
||||
|
@ -103,23 +172,26 @@ section of your configuration.
|
|||
See
|
||||
.Xr meli-themes 5
|
||||
for complete documentation on user themes.
|
||||
.Pp
|
||||
See
|
||||
.Xr meli 7
|
||||
for a more detailed tutorial on using
|
||||
.Nm Ns
|
||||
\&.
|
||||
.Sh SHORTCUTS
|
||||
See
|
||||
.Xr meli.conf 5 SHORTCUTS
|
||||
for shortcuts and their default values.
|
||||
.Sh VIEWING MAIL
|
||||
Open attachments by typing their index in the attachments list and then
|
||||
.Cm a
|
||||
.Po
|
||||
shortcut
|
||||
.Ic open_attachment
|
||||
.Pc .
|
||||
.ShortcutPeriod a envelope_view open_attachment
|
||||
\&.
|
||||
.Nm
|
||||
will attempt to open text inside its pager, and other content via
|
||||
.Cm xdg-open Ns
|
||||
\&.
|
||||
Press
|
||||
.Cm m
|
||||
.Po
|
||||
shortcut
|
||||
.Ic open_mailcap
|
||||
.Pc
|
||||
.Shortcut m envelope_view open_mailcap
|
||||
instead to use the mailcap entry for the MIME type of the attachment, if any.
|
||||
See
|
||||
.Sx FILES
|
||||
|
@ -127,15 +199,15 @@ for the location of the mailcap files and
|
|||
.Xr mailcap 5
|
||||
for their syntax.
|
||||
You can save individual attachments with the
|
||||
.Em COMMAND
|
||||
.Cm save-attachment Ar INDEX Ar path-to-file
|
||||
where
|
||||
.Command save-attachment Ar INDEX Ar path-to-file
|
||||
command.
|
||||
.Ar INDEX
|
||||
is the attachment's index in the listing.
|
||||
If the zeroth index is provided, the entire message is saved.
|
||||
If the path provided is a directory, the attachment is saved with its filename set to the filename in the attachment, if any.
|
||||
If the 0th index is provided, the entire message is saved.
|
||||
If the path provided is a directory, the message is saved as an eml file with its filename set to the messages message-id.
|
||||
.Sh SEARCH
|
||||
Each e-mail storage backend has a default search method assigned.
|
||||
Each e\-mail storage backend has a default search method assigned.
|
||||
.Em IMAP
|
||||
uses the SEARCH command,
|
||||
.Em notmuch
|
||||
|
@ -161,9 +233,8 @@ To enable sqlite3 indexing for an account set
|
|||
.Em search_backend
|
||||
to
|
||||
.Em sqlite3
|
||||
in the configuration file and to create the sqlite3 index issue command
|
||||
.Cm index Ar ACCOUNT_NAME Ns \&.
|
||||
.sp
|
||||
in the configuration file and to create the sqlite3 index issue command:
|
||||
.Command index Ar ACCOUNT_NAME Ns
|
||||
To search in the message body type your keywords without any special formatting.
|
||||
To search in specific fields, prepend your search keyword with "field:" like so:
|
||||
.Pp
|
||||
|
@ -185,9 +256,8 @@ alias:
|
|||
.Pc
|
||||
String keywords with spaces must be quoted.
|
||||
Quotes should always be escaped.
|
||||
.sp
|
||||
.Sy Important Notice about IMAP/JMAP
|
||||
.sp
|
||||
.Ss Important Notice about IMAP/JMAP
|
||||
.HorizontalRule
|
||||
To prevent downloading all your messages from your IMAP/JMAP server, don't set
|
||||
.Em search_backend
|
||||
to
|
||||
|
@ -196,9 +266,10 @@ to
|
|||
.Nm
|
||||
will relay your queries to the IMAP server.
|
||||
Expect a delay between query and response.
|
||||
Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticable delay.
|
||||
Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticeable delay.
|
||||
.Ss QUERY ABNF SYNTAX
|
||||
.Bl -bullet
|
||||
.HorizontalRule
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
.Li query = \&"(\&" query \&")\&" | from | to | cc | bcc | alladdresses | subject | flags | has_attachments | query \&"or\&" query | query \&"and\&" query | not query
|
||||
.It
|
||||
|
@ -228,39 +299,48 @@ Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticable
|
|||
.It
|
||||
.Li flags = \&"flags:\&" flag | \&"tags:\&" flag | \&"is:\&" flag
|
||||
.El
|
||||
.Sh FLAGS
|
||||
.Nm
|
||||
supports the basic maildir flags: passed, replied, seen, trashed, draft and flagged.
|
||||
Flags can be searched with the
|
||||
.Ns Ql flags:
|
||||
prefix in a search query, and can be modified by
|
||||
.Command flag set FLAG
|
||||
and
|
||||
.Command flag unset FLAG
|
||||
.Sh TAGS
|
||||
.Nm
|
||||
supports tagging in notmuch and IMAP/JMAP backends.
|
||||
Tags can be searched with the `tags:` or `flags:` prefix in a search query, and can be modified by
|
||||
.Cm tag add TAG
|
||||
Tags can be searched with the
|
||||
.Ns Ql tags:
|
||||
or
|
||||
.Ns Ql flags:
|
||||
prefix in a search query, and can be modified by
|
||||
.Command tag add TAG
|
||||
and
|
||||
.Cm tag remove TAG
|
||||
.Command tag remove TAG
|
||||
(see
|
||||
.Xr meli.conf 5 TAGS Ns
|
||||
, settings
|
||||
.Ic colors
|
||||
and
|
||||
.Ic ignore_tags
|
||||
for how to set tag colors and tag visiblity)
|
||||
for how to set tag colors and tag visibility)
|
||||
.Sh COMPOSING
|
||||
.Ss Opening the message Composer tab
|
||||
To create a new mail message, press
|
||||
.Cm m
|
||||
(shortcut
|
||||
.Ic new_mail Ns
|
||||
) while viewing a mailbox.
|
||||
.Shortcut m listing new_mail
|
||||
while viewing a mailbox.
|
||||
To reply to a mail, press
|
||||
.Cm R
|
||||
.Po
|
||||
shortcut
|
||||
.Ic reply
|
||||
.Pc .
|
||||
.ShortcutPeriod R envelope_view reply
|
||||
\&.
|
||||
Both these actions open the mail composer view in a new tab.
|
||||
.Ss Editing text
|
||||
.Bl -bullet -compact
|
||||
.HorizontalRule
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
Edit the header fields by selecting with the arrow keys and pressing
|
||||
.Cm enter
|
||||
.Shortcut Enter general focus_in_text_field
|
||||
to enter
|
||||
.Em INSERT
|
||||
mode and
|
||||
|
@ -268,10 +348,8 @@ mode and
|
|||
key to exit.
|
||||
.It
|
||||
At any time you may press
|
||||
.Cm e
|
||||
(shortcut
|
||||
.Ic edit_mail Ns
|
||||
) to launch your editor (see
|
||||
.Shortcut e composing edit Ns
|
||||
to launch your editor (see
|
||||
.Xr meli.conf 5 COMPOSING Ns
|
||||
, setting
|
||||
.Ic editor_command
|
||||
|
@ -283,36 +361,40 @@ Your editor can be used in
|
|||
.Ic embed
|
||||
to
|
||||
.Em true
|
||||
in your composing settings.
|
||||
in your composing settings
|
||||
.Po
|
||||
You can return to
|
||||
.Nm
|
||||
at any time by pressing
|
||||
.Aq Ctrl-Z
|
||||
.Pc
|
||||
.It
|
||||
When launched, your editor captures all input until it exits or stops.
|
||||
.It
|
||||
To stop your editor and return to
|
||||
.Nm
|
||||
press Ctrl-z and to resume editing press the
|
||||
.Ic edit_mail
|
||||
command again
|
||||
.Po
|
||||
default
|
||||
.Em e
|
||||
.Pc .
|
||||
press
|
||||
.Aq Ctrl-z
|
||||
and to resume editing press the
|
||||
.Ic edit
|
||||
command again.
|
||||
.El
|
||||
.Ss Attachments
|
||||
.HorizontalRule
|
||||
Attachments may be handled with the
|
||||
.Cm add-attachment Ns
|
||||
,
|
||||
.Cm remove-attachment
|
||||
commands (see below).
|
||||
.Ss Sending
|
||||
.HorizontalRule
|
||||
Finally, pressing
|
||||
.Cm s
|
||||
(shortcut
|
||||
.Ic send_mail Ns
|
||||
) will send your message according to your settings
|
||||
.Shortcut s composing send_mail
|
||||
will send your message according to your settings
|
||||
.Po
|
||||
see
|
||||
.Xr meli.conf 5 COMPOSING Ns
|
||||
, setting
|
||||
, setting name
|
||||
.Ic send_mail
|
||||
.Pc Ns
|
||||
\&.
|
||||
|
@ -323,19 +405,19 @@ On complete failure to save your draft or sent message it will be saved in your
|
|||
.Em tmp
|
||||
directory instead and you will be notified of its location.
|
||||
.Ss Drafts
|
||||
.HorizontalRule
|
||||
To save your draft without sending it, issue
|
||||
.Em COMMAND
|
||||
.Cm close
|
||||
and select 'save as draft'.
|
||||
.sp
|
||||
To open a draft for further editing, select your draft in the mail listing and press
|
||||
.Ic edit_mail Ns
|
||||
.Ic edit Ns
|
||||
\&.
|
||||
.Sh CONTACTS
|
||||
.Nm
|
||||
supports two kinds of contact backends:
|
||||
.sp
|
||||
.Bl -enum -compact -offset indent
|
||||
supports three kinds of contact backends:
|
||||
.Bl -enum -compact
|
||||
.It
|
||||
an internal format that gets saved under
|
||||
.Pa $XDG_DATA_HOME/meli/account_name/addressbook Ns
|
||||
|
@ -348,11 +430,16 @@ The path defined as
|
|||
.Ic vcard_folder
|
||||
can hold multiple vCards per file.
|
||||
They are loaded read only.
|
||||
.It
|
||||
a
|
||||
.Xr mutt 1
|
||||
compatible alias file in the option
|
||||
.Ic mutt_alias_file
|
||||
.El
|
||||
.sp
|
||||
See
|
||||
.Xr meli.conf 5 ACCOUNTS
|
||||
for the complete account configuration values.
|
||||
for the complete account contact configuration values.
|
||||
.Sh MODES
|
||||
.Bl -tag -compact -width 8n
|
||||
.It NORMAL
|
||||
|
@ -361,9 +448,9 @@ is the default mode
|
|||
commands are issued in
|
||||
.Em COMMAND
|
||||
mode, by default started with
|
||||
.Cm \&:
|
||||
.Shortcut \&: general enter_command_mode
|
||||
and exited with
|
||||
.Cm Esc
|
||||
.Aq Esc
|
||||
key.
|
||||
.It EMBED
|
||||
is the mode of the embed terminal emulator
|
||||
|
@ -372,8 +459,9 @@ captures all input as text input, and is exited with
|
|||
.Cm Esc
|
||||
key.
|
||||
.El
|
||||
.Ss COMMAND Mode
|
||||
.Sh COMMAND
|
||||
.Ss Mail listing commands
|
||||
.HorizontalRule
|
||||
.Bl -tag -width 36n
|
||||
.It Cm set Ar plain | threaded | compact | conversations
|
||||
set the way mailboxes are displayed
|
||||
|
@ -408,6 +496,8 @@ Escape exits search results.
|
|||
select threads matching
|
||||
.Ar STRING
|
||||
query.
|
||||
.It Cm clear-selection
|
||||
Clear current selection.
|
||||
.It Cm set seen, set unseen
|
||||
Set seen status of message.
|
||||
.It Cm import Ar FILEPATH Ar MAILBOX_PATH
|
||||
|
@ -415,28 +505,31 @@ Import mail from file into given mailbox.
|
|||
.It Cm copyto, moveto Ar MAILBOX_PATH
|
||||
Copy or move to other mailbox.
|
||||
.It Cm copyto, moveto Ar ACCOUNT Ar MAILBOX_PATH
|
||||
Copy or move to another account's mailbox.
|
||||
Copy or move to another account's mailbox.
|
||||
.It Cm delete
|
||||
Delete selected threads.
|
||||
.It Cm export-mbox Ar FILEPATH
|
||||
Export selected threads to mboxcl2 file.
|
||||
.It Cm create-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
.It Cm create\-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
create mailbox with given path.
|
||||
Be careful with backends and separator sensitivity (eg IMAP)
|
||||
.It Cm subscribe-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
.It Cm subscribe\-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
subscribe to mailbox with given path
|
||||
.It Cm unsubscribe-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
.It Cm unsubscribe\-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
unsubscribe to mailbox with given path
|
||||
.It Cm rename-mailbox Ar ACCOUNT Ar MAILBOX_PATH_SRC Ar MAILBOX_PATH_DEST
|
||||
.It Cm rename\-mailbox Ar ACCOUNT Ar MAILBOX_PATH_SRC Ar MAILBOX_PATH_DEST
|
||||
rename mailbox
|
||||
.It Cm delete-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
.It Cm delete\-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
deletes mailbox in the mail backend.
|
||||
This action is unreversible.
|
||||
This action is irreversible.
|
||||
.El
|
||||
.Ss Mail view commands
|
||||
.HorizontalRule
|
||||
.Bl -tag -width 36n
|
||||
.It Cm pipe Ar EXECUTABLE Ar ARGS
|
||||
pipe pager contents to binary
|
||||
.It Cm filter Ar EXECUTABLE Ar ARGS
|
||||
filter and display pager contents through command
|
||||
.It Cm list-post
|
||||
post in list of viewed envelope
|
||||
.It Cm list-unsubscribe
|
||||
|
@ -445,8 +538,13 @@ unsubscribe automatically from list of viewed envelope
|
|||
open list archive with
|
||||
.Cm xdg-open
|
||||
.El
|
||||
.Ss composing mail commands
|
||||
.Ss Composing mail commands
|
||||
.HorizontalRule
|
||||
.Bl -tag -width 36n
|
||||
.It Cm mailto Ar MAILTO_ADDRESS
|
||||
Opens a composer tab with initial values parsed from the
|
||||
.Li mailto:
|
||||
address.
|
||||
.It Cm add-attachment Ar PATH
|
||||
in composer, add
|
||||
.Ar PATH
|
||||
|
@ -476,7 +574,8 @@ for PGP configuration.
|
|||
.It Cm save-draft
|
||||
saves a copy of the draft in the Draft folder
|
||||
.El
|
||||
.Ss generic commands
|
||||
.Ss Generic commands
|
||||
.HorizontalRule
|
||||
.Bl -tag -width 36n
|
||||
.It Cm open-in-tab
|
||||
opens envelope view in new tab
|
||||
|
@ -500,10 +599,6 @@ Useful if you want to reload some settings without restarting
|
|||
.Nm Ns
|
||||
\&.
|
||||
.El
|
||||
.Sh SHORTCUTS
|
||||
See
|
||||
.Xr meli.conf 5 SHORTCUTS
|
||||
for shortcuts and their default values.
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with 0 on a successful run.
|
||||
|
@ -521,7 +616,9 @@ Specifies the editor to use
|
|||
.It Ev MELI_CONFIG
|
||||
Override the configuration file
|
||||
.It Ev NO_COLOR
|
||||
When present (regardless of its value), prevents the addition of ANSI color.
|
||||
When defined (regardless of its value), prevents the addition of
|
||||
.Tn ANSI
|
||||
color.
|
||||
The configuration value
|
||||
.Ic use_color
|
||||
overrides this.
|
||||
|
@ -578,22 +675,265 @@ Mailcap entries are searched for in the following files, in this order:
|
|||
.It
|
||||
.Pa /usr/local/etc/mailcap
|
||||
.El
|
||||
.Sh STANDARDS
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
.Rs
|
||||
.%B XDG Base Directory Specification
|
||||
.%O Version 0.8
|
||||
.%A Waldo Bastian
|
||||
.%A Allison Karlitskaya
|
||||
.%A Lennart Poettering
|
||||
.%A Johannes Löthberg
|
||||
.%U https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
.%D May 08, 2021
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B maildir
|
||||
.%A Daniel J. Bernstein
|
||||
.%U https://cr.yp.to/proto/maildir.html
|
||||
.%D 1995
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC1524 A User Agent Configuration Mechanism For Multimedia Mail Format Information
|
||||
.%O mailcap file
|
||||
.%I Legacy
|
||||
.%D September 01, 1993
|
||||
.%A Dr. Nathaniel S. Borenstein
|
||||
.%U https://datatracker.ietf.org/doc/rfc1524/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC2047 MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text
|
||||
.%I IETF
|
||||
.%D November 01, 1996
|
||||
.%A Keith Moore
|
||||
.%U https://datatracker.ietf.org/doc/rfc2047/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC2183 Communicating Presentation Information in Internet Messages: The Content-Disposition Header Field
|
||||
.%I Legacy
|
||||
.%D August 01, 1997
|
||||
.%A Rens Troost
|
||||
.%A Steve Dorner
|
||||
.%A Keith Moore
|
||||
.%U https://datatracker.ietf.org/doc/rfc2183/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC2369 The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields
|
||||
.%I Legacy
|
||||
.%D July 01, 1998
|
||||
.%A Joshua D. Baer
|
||||
.%A Grant Neufeld
|
||||
.%U https://datatracker.ietf.org/doc/rfc2369/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC2426 vCard MIME Directory Profile
|
||||
.%O vCard Version 3
|
||||
.%I IETF
|
||||
.%D September 01, 1998
|
||||
.%A Frank Dawson
|
||||
.%A Tim Howes
|
||||
.%U https://datatracker.ietf.org/doc/rfc2426/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC3156 MIME Security with OpenPGP
|
||||
.%I IETF
|
||||
.%D August 01, 2001
|
||||
.%A Thomas Roessler
|
||||
.%A Michael Elkins
|
||||
.%A Raph Levien
|
||||
.%A Dave Del Torto
|
||||
.%U https://datatracker.ietf.org/doc/rfc3156/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC3461 Simple Mail Transfer Protocol (SMTP) Service Extension for Delivery Status Notifications (DSNs)
|
||||
.%I IETF
|
||||
.%D January 23, 2003
|
||||
.%A Keith Moore
|
||||
.%U https://datatracker.ietf.org/doc/rfc3461/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC3501 INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
|
||||
.%I IETF
|
||||
.%D March 18, 2003
|
||||
.%A Mark Crispin
|
||||
.%U https://datatracker.ietf.org/doc/rfc3501/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC3676 The Text/Plain Format and DelSp Parameters
|
||||
.%I IETF
|
||||
.%D February 19, 2004
|
||||
.%A Randall Gellens
|
||||
.%U https://datatracker.ietf.org/doc/rfc3676/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC3691 Internet Message Access Protocol (IMAP) UNSELECT command
|
||||
.%I IETF
|
||||
.%D February 20, 2004
|
||||
.%A Alexey Melnikov
|
||||
.%U https://datatracker.ietf.org/doc/rfc3691/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC3977 Network News Transfer Protocol (NNTP)
|
||||
.%I IETF
|
||||
.%D October 26, 2006
|
||||
.%A Clive Feather
|
||||
.%U https://datatracker.ietf.org/doc/rfc3977/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC4549 Synchronization Operations for Disconnected IMAP4 Clients
|
||||
.%I IETF
|
||||
.%D June 16, 2006
|
||||
.%A Alexey Melnikov
|
||||
.%U https://datatracker.ietf.org/doc/rfc4549/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC4616 The PLAIN Simple Authentication and Security Layer (SASL) Mechanism
|
||||
.%I IETF
|
||||
.%D August 31, 2006
|
||||
.%A Kurt Zeilenga
|
||||
.%U https://datatracker.ietf.org/doc/rfc4616/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC4954 SMTP Service Extension for Authentication
|
||||
.%I IETF
|
||||
.%D July 23, 2007
|
||||
.%A Rob Siemborski
|
||||
.%A Alexey Melnikov
|
||||
.%U https://datatracker.ietf.org/doc/rfc4954/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC5321 Simple Mail Transfer Protocol
|
||||
.%I IETF
|
||||
.%D October 01, 2008
|
||||
.%A Dr. John C. Klensin
|
||||
.%U https://datatracker.ietf.org/doc/rfc5321/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC5322 Internet Message Format
|
||||
.%I IETF
|
||||
.%D October 01, 2008
|
||||
.%A Pete Resnick
|
||||
.%U https://datatracker.ietf.org/doc/rfc5322/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC6048 Network News Transfer Protocol (NNTP) Additions to LIST Command
|
||||
.%I IETF
|
||||
.%D November 22, 2010
|
||||
.%A Julien ÉLIE
|
||||
.%U https://datatracker.ietf.org/doc/rfc6048/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC6152 SMTP Service Extension for 8-bit MIME Transport
|
||||
.%I IETF
|
||||
.%D March 07, 2011
|
||||
.%A Dave Crocker
|
||||
.%A Dr. John C. Klensin
|
||||
.%A Dr. Marshall T. Rose
|
||||
.%A Ned Freed
|
||||
.%U https://datatracker.ietf.org/doc/rfc6152/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC6350 vCard Format Specification
|
||||
.%O vCard Version 4
|
||||
.%I IETF
|
||||
.%D August 31, 2011
|
||||
.%A Simon Perreault
|
||||
.%U https://datatracker.ietf.org/doc/rfc6350/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC6532 Internationalized Email Headers
|
||||
.%I IETF
|
||||
.%D February 17, 2012
|
||||
.%A Abel Yang
|
||||
.%A Shawn Steele
|
||||
.%A Ned Freed
|
||||
.%U https://datatracker.ietf.org/doc/rfc6532/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC6868 Parameter Value Encoding in iCalendar and vCard
|
||||
.%I IETF
|
||||
.%D February 14, 2013
|
||||
.%A Cyrus Daboo
|
||||
.%U https://datatracker.ietf.org/doc/rfc6868/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC7162 IMAP Extensions: Quick Flag Changes Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization (QRESYNC)
|
||||
.%I IETF
|
||||
.%D May 23, 2014
|
||||
.%A Alexey Melnikov
|
||||
.%A Dave Cridland
|
||||
.%U https://datatracker.ietf.org/doc/rfc7162/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC8620 The JSON Meta Application Protocol (JMAP)
|
||||
.%I IETF
|
||||
.%D July 18, 2019
|
||||
.%A Neil Jenkins
|
||||
.%A Chris Newman
|
||||
.%U https://datatracker.ietf.org/doc/rfc8620/
|
||||
.Re
|
||||
.It
|
||||
.Rs
|
||||
.%B RFC8621 The JSON Meta Application Protocol (JMAP) for Mail
|
||||
.%I IETF
|
||||
.%D August 08, 2019
|
||||
.%A Neil Jenkins
|
||||
.%A Chris Newman
|
||||
.%U https://datatracker.ietf.org/doc/rfc8621/
|
||||
.Re
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr meli.conf 5 ,
|
||||
.Xr meli-themes 5 ,
|
||||
.Xr meli 7 ,
|
||||
.Xr xdg-open 1 ,
|
||||
.Xr mailcap 5
|
||||
.Sh CONFORMING TO
|
||||
XDG Standard
|
||||
.Aq https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html Ns
|
||||
, maildir
|
||||
.Aq https://cr.yp.to/proto/maildir.html Ns
|
||||
, IMAPv4rev1 RFC3501, The JSON Meta Application Protocol (JMAP) RFC8620, The JSON Meta Application Protocol (JMAP) for Mail RFC8621.
|
||||
.Sh AUTHORS
|
||||
Copyright 2017-2019
|
||||
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
|
||||
Copyright 2017\(en2024
|
||||
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||
.Pp
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
(See COPYING for full copyright and warranty notices.)
|
||||
.Pp
|
||||
.Aq https://meli.delivery
|
||||
.Po
|
||||
See
|
||||
.Pa COPYING
|
||||
for full copyright and warranty notices.
|
||||
.Pc
|
||||
.Ss Links
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://meli\-email.org "Website"
|
||||
.It
|
||||
.Lk https://git.meli\-email.org/meli/meli "Main\ git\ repository\ and\ issue\ tracker"
|
||||
.It
|
||||
.Lk https://codeberg.org/meli/meli "Official\ read-only\ git\ mirror\ on\ codeberg.org"
|
||||
.It
|
||||
.Lk https://github.com/meli/meli "Official\ read-only\ git\ mirror\ on\ github.com"
|
||||
.It
|
||||
.Lk https://crates.io/crates/meli "meli\ crate\ on\ crates.io"
|
||||
.El
|
|
@ -0,0 +1,764 @@
|
|||
.\" meli - meli.7
|
||||
.\"
|
||||
.\" Copyright 2017-2022 Manos Pitsidianakis
|
||||
.\"
|
||||
.\" This file is part of meli.
|
||||
.\"
|
||||
.\" meli is free software: you can redistribute it and/or modify
|
||||
.\" it under the terms of the GNU General Public License as published by
|
||||
.\" the Free Software Foundation, either version 3 of the License, or
|
||||
.\" (at your option) any later version.
|
||||
.\"
|
||||
.\" meli is distributed in the hope that it will be useful,
|
||||
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
.\" GNU General Public License for more details.
|
||||
.\"
|
||||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.\".de Hr
|
||||
.\".Bd -literal -offset center
|
||||
.\"╌╍─────────────────────────────────────────────────────────╍╌
|
||||
.\".Ed
|
||||
.\"..
|
||||
.de Shortcut
|
||||
.Sm
|
||||
.Aq \\$1
|
||||
\
|
||||
.Po
|
||||
.Em shortcuts.\\$2\&. Ns
|
||||
.Em \\$3
|
||||
.Pc
|
||||
.Sm
|
||||
..
|
||||
.de ShortcutPeriod
|
||||
.Aq \\$1
|
||||
.Po
|
||||
.Em shortcuts.\\$2\&. Ns
|
||||
.Em \\$3
|
||||
.Pc Ns
|
||||
..
|
||||
.de Command
|
||||
.Bd -ragged -offset 1n
|
||||
.Cm \\$*
|
||||
.Ed
|
||||
..
|
||||
.\".Dd November 11, 2022
|
||||
.Dd March 10, 2024
|
||||
.Dt MELI 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli
|
||||
.Nd Tutorial for the meli terminal e\-mail client
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op ...
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a terminal mail client aiming for extensive and user\-friendly configurability.
|
||||
.Bd -literal -offset center
|
||||
^^ .-=-=-=-. ^^
|
||||
^^ (`-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-`) ^^ ^^
|
||||
^^ (`-=-=-=-=-=-=-=-`) ^^
|
||||
( `-=-=-=-(@)-=-=-` ) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`)
|
||||
^^ (`-=-=-=-=-=-=-=-=-`) ^^
|
||||
^^ (`-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-`) ^^
|
||||
^^ (`-=-=-=-=-`)
|
||||
`-=-=-=-=-` ^^
|
||||
.Ed
|
||||
.Sh INTRODUCTION
|
||||
To quit
|
||||
.Nm
|
||||
press
|
||||
.Shortcut q general quit
|
||||
at any time.
|
||||
When launched for the first time,
|
||||
.Nm
|
||||
will search for its configuration directory,
|
||||
.Pa $XDG_CONFIG_HOME/meli/ Ns
|
||||
\&.
|
||||
If it doesn't exist, you will be asked if you want to create one and presented with a sample configuration file
|
||||
.Pq Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
that includes the basic settings required for setting up accounts allowing you to copy and edit right away.
|
||||
See
|
||||
.Xr meli.conf 5
|
||||
for the available configuration options.
|
||||
.Pp
|
||||
At any time, you may press
|
||||
.Shortcut \&? general toggle_help
|
||||
for a searchable list of all available actions and shortcuts, along with every possible setting and command that your version supports.
|
||||
.Pp
|
||||
Each time a shortcut is mentioned in this document, you will find a parenthesis next to it with the name of the shortcut setting along with its section in the configuration settings so that you can modify it if you wish.
|
||||
.Pp
|
||||
For example, to set the
|
||||
.Em toggle_help
|
||||
shortcut mentioned in the previous paragraph, add the following to your configuration:
|
||||
.Bd -literal -offset center
|
||||
[shortcuts]
|
||||
general.toggle_help = 'F1'
|
||||
.Ed
|
||||
.sp
|
||||
Or alternatively:
|
||||
.Bd -literal -offset center
|
||||
[shortcuts.general]
|
||||
toggle_help = 'F1'
|
||||
.Ed
|
||||
.Pp
|
||||
To go to the next tab on the right, press
|
||||
.ShortcutPeriod T general next_tab
|
||||
\&.
|
||||
.Sh INTERACTING WITH Nm
|
||||
You will be interacting with
|
||||
.Nm
|
||||
in four primary ways:
|
||||
.Bl -column
|
||||
.It 1.
|
||||
keyboard shortcuts in
|
||||
.Sy NORMAL
|
||||
mode.
|
||||
.It 2.
|
||||
commands with arguments in
|
||||
.Sy COMMAND
|
||||
mode.
|
||||
.It 3.
|
||||
regular text input in text input widgets in
|
||||
.Sy INSERT
|
||||
mode.
|
||||
.It 4.
|
||||
any kind of input that gets passed directly into an embedded terminal in
|
||||
.Sy EMBED
|
||||
mode.
|
||||
.El
|
||||
.Sh MODES
|
||||
.Nm
|
||||
is a modal application, just like
|
||||
.Xr vi 1 Ns
|
||||
\&.
|
||||
This means that pressing the same keys in different modes would yield different results.
|
||||
This allows you to separate how the input is interpreted without the need to focus your input with a mouse.
|
||||
.Bl -tag -width 8n
|
||||
.It NORMAL
|
||||
This is the default mode of
|
||||
.Nm Ns
|
||||
\&.
|
||||
All keyboard shortcuts work in this mode.
|
||||
.It COMMAND
|
||||
Commands are issued in
|
||||
.Sy COMMAND
|
||||
mode, by default started with
|
||||
.Shortcut \&: general enter_command_mode
|
||||
and exited with
|
||||
.Aq Esc
|
||||
key.
|
||||
.It EMBED
|
||||
This is the mode of the embed terminal emulator.
|
||||
To exit an embedded application, issue
|
||||
.Aq Ctrl\-C
|
||||
to kill it or
|
||||
.Aq Ctrl\-Z
|
||||
to stop the program and follow the instructions on
|
||||
.Nm
|
||||
to exit.
|
||||
.It INSERT
|
||||
This mode is entered when pressing
|
||||
.Aq Enter
|
||||
on a cursor selected text input field, and it captures all input as text input.
|
||||
It is exited with the
|
||||
.Aq Esc
|
||||
key.
|
||||
.El
|
||||
.Sh ACTIVE SHORTCUTS POPUP
|
||||
By pressing
|
||||
.Shortcut \&? general toggle_help
|
||||
at any time, the shortcuts popup display status gets toggled.
|
||||
You can find all valid shortcuts for the current UI state you are in.
|
||||
.Bd -literal -offset center
|
||||
┌─shortcuts──Press ? to close────────────────────────────────┐
|
||||
│ ▀│
|
||||
│ use COMMAND "search" to find shortcuts █│
|
||||
│ Use Up, Down, Left, Right to scroll. █│
|
||||
│ █│
|
||||
│ pager █│
|
||||
│ █│
|
||||
│ PageDown page_down █│
|
||||
│ PageUp page_up │
|
||||
│ j scroll_down │
|
||||
│ k scroll_up │
|
||||
│ │
|
||||
│ view mail │
|
||||
│ │
|
||||
│ c add_addresses_to_contacts │
|
||||
│ e edit │
|
||||
│ u toggle_url_mode │
|
||||
│ a open_attachment │
|
||||
│ m open_mailcap │
|
||||
│ R reply │
|
||||
│ C-r reply_to_author │
|
||||
│ C-g reply_to_all │
|
||||
│ C-f forward │
|
||||
│ M-r view_raw_source │
|
||||
│ h toggle_expand_headers ▄│
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em Shows\ active\ shortcuts\ in\ order\ of\ the\ widget\ hierarchy\&.
|
||||
.Ed
|
||||
.Sh MAIN VIEW
|
||||
.Bd -literal -offset center
|
||||
┌───────────────────────┐
|
||||
├────┼──────────────────┤
|
||||
│___ │ ___________ │
|
||||
│ _ │ _______________ │
|
||||
│ _ │__________________│
|
||||
│ _ │ ___________ │
|
||||
│ │ _____ │
|
||||
│ │ │
|
||||
└────┴──────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em The\ main\ view's\ layout\&.
|
||||
.Ed
|
||||
.sp
|
||||
This is the view you will spend more time with in
|
||||
.Nm Ns
|
||||
\&.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut \(ga listing toggle_menu_visibility
|
||||
to toggle the sidebars visibility.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut Left listing focus_right
|
||||
to switch focus on the sidebar menu.
|
||||
Press
|
||||
.Shortcut Right listing focus_left
|
||||
to switch focus on the e\-mail list.
|
||||
.Pp
|
||||
On the e\-mail list, press
|
||||
.Shortcut k listing scroll_up
|
||||
to scroll up, and
|
||||
.Shortcut j listing scroll_down
|
||||
to scroll down.
|
||||
Press
|
||||
.Shortcut Enter listing open_entry
|
||||
to open an e\-mail entry and
|
||||
.Shortcut i listing exit_entry
|
||||
to exit it.
|
||||
.Bd -ragged
|
||||
.Sy The sidebar\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
┌─────────────┉┉┉┉┉✂
|
||||
│ mail▐ contact li✂
|
||||
│personal account ✂
|
||||
│ 0 INBOX ✂
|
||||
│ 1 ┣━Sent ✂
|
||||
│ 2 ┣━Lists ✂
|
||||
│ 3 ┃ ┣━meli-dev ✂
|
||||
│ 4 ┃ ┗━meli ✂
|
||||
│ 5 ┣━Drafts ✂
|
||||
│ 6 ┣━Trash ✂
|
||||
│ 7 ┗━foobar ✂
|
||||
┇ 8 Trash ✂
|
||||
✂ ✂ ✂ ✂ ✂ ✂ ✂ ✂ ✂ ✂
|
||||
.Ed
|
||||
.sp
|
||||
Press
|
||||
.Shortcut k listing scroll_up
|
||||
to scroll up, and
|
||||
.Shortcut j listing scroll_down
|
||||
to scroll down.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut Enter listing open_mailbox
|
||||
to open an entry (either a mailbox or an account name).
|
||||
Entering an account name will show you a page with details about the account and its network connection, depending on the backend.
|
||||
.Pp
|
||||
While focused in the sidebar, you can
|
||||
.Dq collapse
|
||||
a mailbox tree, if it has children, and you can open it with
|
||||
.ShortcutPeriod Space listing toggle_mailbox_collapse
|
||||
\&.
|
||||
You can have mailbox trees collapsed on startup by default by setting a mailbox's
|
||||
.Ic collapsed
|
||||
setting to
|
||||
.Em true Ns
|
||||
\&.
|
||||
See
|
||||
.Xr meli.conf 5 section MAILBOXES
|
||||
for details.
|
||||
.Pp
|
||||
You can increase the sidebar's width with
|
||||
.Shortcut Ctrl\-p listing increase_sidebar
|
||||
and decrease with
|
||||
.ShortcutPeriod Ctrl\-o listing decrease_sidebar
|
||||
\&.
|
||||
.Bd -ragged
|
||||
.Sy The status bar.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────┈┈
|
||||
│NORMAL | Mailbox: Inbox, Messages: 25772, New: 3006
|
||||
└────────────────────────────────────────────────────┈┈
|
||||
.Ed
|
||||
.Pp
|
||||
The status bar shows which mode you are, and the status message of the current view.
|
||||
In the pictured example, it shows the status of a mailbox called
|
||||
.Dq Inbox
|
||||
with lots of e\-mails.
|
||||
.Bd -ragged
|
||||
.Sy The number modifier buffer.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
┈┈────────────┐
|
||||
12 │
|
||||
┈┈────────────┘
|
||||
.Ed
|
||||
.Pp
|
||||
Some commands may accept a number modifier.
|
||||
.Tg number-modifier
|
||||
For example, scroll down commands can receive a multiplier
|
||||
.Em n
|
||||
to scroll down
|
||||
.Em n
|
||||
entries.
|
||||
Another use of the number buffer is opening URLs inside the pager.
|
||||
See
|
||||
.Sx PAGER
|
||||
for an explanation of interacting with URLs in e\-mails.
|
||||
.Pp
|
||||
Pressing numbers in
|
||||
.Sy NORMAL
|
||||
mode will populate this buffer.
|
||||
To erase it, press the
|
||||
.Aq Esc
|
||||
key.
|
||||
.Sh MAIL LIST
|
||||
There are four different list styles:
|
||||
.Bl -hyphen -compact
|
||||
.It
|
||||
.Qq plain
|
||||
which shows one line per e\-mail.
|
||||
.It
|
||||
.Qq threaded
|
||||
which shows a threaded view with drawn tree structure.
|
||||
.It
|
||||
.Qq compact
|
||||
which shows one line per thread which can include multiple e\-mails.
|
||||
.It
|
||||
.Qq conversations
|
||||
which shows more than one line per thread which can include multiple e\-mails with more details about the thread.
|
||||
.El
|
||||
.Bd -ragged
|
||||
.Sy Plain view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│42 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 3/8] │
|
||||
│43 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 2/8] │
|
||||
│44 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 1/8] │
|
||||
|45 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 0/8] |
|
||||
│46 Fri, 02 Sep 2022 18:18 xxxxxxxx <xxxxx Re: [PATCH 3│
|
||||
.Ed
|
||||
.Bd -ragged
|
||||
.Sy Threaded view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│12 9 hours ago xxxxxxxxxxxxxxx [PATCH v3 0│
|
||||
│13 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH │
|
||||
│14 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH │
|
||||
|15 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH |
|
||||
│16 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH │
|
||||
│17 9 hours ago xxxxxxxxxxxxxxx └─>[PATCH │
|
||||
│18 2022-08-23 01:23:51 xxxxxxxxxxxxxxx [RFC v4 00/│
|
||||
│19 2022-08-23 01:23:52 xxxxxxxxxxxxxxx ├─>[RFC v4│
|
||||
|20 2022-08-30 10:30:16 xxxxxxxxxxxxxxx │ └─> |
|
||||
│21 6 days ago xxxxxxxxxxxxxxx │ └─> │
|
||||
│22 2022-08-23 01:23:53 xxxxxxxxxxxxxxx ├─>[RFC v4│
|
||||
.Ed
|
||||
.Bd -ragged
|
||||
.Sy Compact view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│18 2022-…:38 xxxxxxxxxxxxxxx [PATCH v3 3/3] u…_l() (2) │
|
||||
|19 2022-…:49 xxxxxxxxxxxxxxx [PATCH v8 0/7] A…e (3) |
|
||||
│20 2022-…:10 xxxxxxxxxxxxxxx [PATCH v8 2/7] f…s (2) │
|
||||
│21 2022-…:38 xxxxxxxxxxxxxxx [PATCH v8 3/7] b…s (2) │
|
||||
│22 2022-…:53 xxxxxxxxxxxxxxx [PATCH v6 00/10] p…g (31) │
|
||||
.Ed
|
||||
.Bd -ragged
|
||||
.Sy Conversations view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│[PATCH v2] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (5) │
|
||||
|1 day ago▁▁▁▁xxxxxxxxxxxxx <xxxxxxxxxxxxx@xxxxxxxxxx>, xxxxx│
|
||||
│ |
|
||||
│[PATCH v2 0/8] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│
|
||||
│1 day ago▁▁▁▁xxxxxxxxxxxxxxx <xxxxxxxxxx@xxxxxxxxxxxxxx>, xx│
|
||||
| │
|
||||
│[PATCH 0/2] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (4) |
|
||||
│2 days ago▁▁▁▁xxxxxxxxxxxxxxxx <xxxxxxxx@xxxxxxxxxxx>, xxxxx│
|
||||
│ │
|
||||
│[PATCH 0/8] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (12) │
|
||||
│2 days ago▁▁▁▁xxxxxxxxxxxxx <xxxxxxxx@xxxxxxxxxx>, xxxxxxxxx│
|
||||
.Ed
|
||||
.sp
|
||||
.sp
|
||||
.Sy Performing actions on entries and/or selections\&.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut v listing select_entry
|
||||
to toggle the selection of a single entry.
|
||||
.Qq select_entry
|
||||
can be prefixed by a number modifier and affixed by a scrolling motion (up or down) to select multiple entries.
|
||||
.Tg number-modifier
|
||||
Simple set operations can be performed on a selection with these shortcut modifiers:
|
||||
.sp
|
||||
.Bl -hyphen -compact
|
||||
.It
|
||||
Union modifier:
|
||||
.Shortcut Ctrl\-u listing union_modifier
|
||||
.It
|
||||
Difference modifier:
|
||||
.Shortcut Ctrl\-d listing diff_modifier
|
||||
.It
|
||||
Intersection modifier:
|
||||
.Shortcut Ctrl\-i listing intersection_modifier
|
||||
.El
|
||||
.Pp
|
||||
To set an entry as
|
||||
.Qq read
|
||||
\&, use the
|
||||
.Shortcut n listing set_seen
|
||||
shortcut.
|
||||
To set an entry as
|
||||
.Qq unread
|
||||
\&, use the command
|
||||
.Command set unseen
|
||||
.sp
|
||||
which also has its complement
|
||||
.Command set seen
|
||||
.sp
|
||||
action.
|
||||
.Pp
|
||||
For e\-mail backends that support flags you can use the following commands on entries and selections to modify them:
|
||||
.Command flag set FLAG
|
||||
.Command flag unset FLAG
|
||||
.Pp
|
||||
For e\-mail backends that support tags
|
||||
.Po
|
||||
like
|
||||
.Qq IMAP
|
||||
or
|
||||
.Qq notmuch Ns
|
||||
.Pc
|
||||
you can use the following commands on entries and selections to modify them:
|
||||
.Command tag add TAG
|
||||
.Command tag remove TAG
|
||||
.sp
|
||||
(see
|
||||
.Xr meli.conf 5 TAGS Ns
|
||||
, settings
|
||||
.Ic colors
|
||||
and
|
||||
.Ic ignore_tags
|
||||
for how to set tag colors and tag visibility)
|
||||
You can clear the selection with the
|
||||
.Aq Esc
|
||||
key.
|
||||
.Sh PAGER
|
||||
You can open an e\-mail entry by pressing
|
||||
.ShortcutPeriod Enter listing open_entry
|
||||
\&. This brings up the e\-mail view with the e\-mail content inside a pager.
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│Date: Sat, 21 May 2022 16:16:11 +0300 ▀│
|
||||
│From: Narrator <narrator@example.com> █│
|
||||
│To: Stanley <427@example.com> █│
|
||||
│Subject: The e-mail ending █│
|
||||
│Message-ID: <gambheerata@example.com> █│
|
||||
│ █│
|
||||
│The story, and the choices, or what have you, and therefore█│
|
||||
│by becoming it is! So on and so forth, until inevitably, we │
|
||||
│all until the end of time. At which time, everything all at │
|
||||
│once, so now you see? Blah, blah, blah, rah, rah, rah... │
|
||||
│We've eaten too much and it can't be just yet. No, no! │
|
||||
│Until two-hundred and forty-five! But the logic of │
|
||||
│elimination, working backwards, the deduction therefore │
|
||||
│becomes impossible to manufacture. It went on for nearly │
|
||||
│ten thousand years, until just yesterday. Here and there, │
|
||||
│forward and back, and never a moment before lunchtime. It │
|
||||
│can't be! It's the only thing there is! How many billions │
|
||||
│left until so much more than forever ago! Which is why I │
|
||||
│say: │
|
||||
│ │
|
||||
│The story, and the choices, or what have you, and therefore │
|
||||
│by becoming it is! So on and so forth, until inevitably, we▄│
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em The\ pager\ displaying\ an\ e\-mail\&.
|
||||
.Ed
|
||||
.Pp
|
||||
The pager is simple to use.
|
||||
Scroll with the following:
|
||||
.Bl -hang -width 27n
|
||||
.It Go to next pager page
|
||||
.Shortcut PageDown pager page_down
|
||||
.It Go to previous pager page
|
||||
.Shortcut PageUp pager page_up
|
||||
.It Scroll down pager.
|
||||
.Shortcut j pager scroll_down
|
||||
.It Scroll up pager.
|
||||
.Shortcut k pager scroll_up
|
||||
.El
|
||||
.sp
|
||||
All scrolling shortcuts can be prefixed with a number modifier
|
||||
.Tg number-modifier
|
||||
which will act as a multiplier.
|
||||
.Pp
|
||||
The pager can enter a special
|
||||
.Em url
|
||||
mode which will prefix all detected hyperlinks and e\-mail addresses with a number inside square brackets
|
||||
.ShortcutPeriod u pager toggle_url_mode
|
||||
\&.
|
||||
Writing down a chosen number as a number modifier
|
||||
.Tg number-modifier
|
||||
and pressing
|
||||
.Shortcut g envelope_view go_to_url
|
||||
will attempt to open the link with the system's default open command
|
||||
.Po
|
||||
.Xr xdg-open 1
|
||||
in supported OSes,
|
||||
and
|
||||
.Xr open 1
|
||||
on MacOS
|
||||
.Pc Ns
|
||||
\&.
|
||||
To override with a custom launcher, see
|
||||
.Qo
|
||||
.Li pager
|
||||
.Qc
|
||||
configuration setting
|
||||
.Qo
|
||||
.Li url_launcher
|
||||
.Qc
|
||||
.Po
|
||||
see
|
||||
.Xr meli.conf 5 PAGER
|
||||
for more details
|
||||
.Pc Ns
|
||||
\&.
|
||||
.Sh MAIL VIEW
|
||||
Other things you can do when viewing e\-mail:
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
Most importantly, you can exit the mail view with:
|
||||
.Shortcut i listing exit_entry
|
||||
.It
|
||||
Add addresses from the e\-mail headers to contacts:
|
||||
.Shortcut c envelope_view add_addresses_to_contacts
|
||||
.It
|
||||
Open an attachment by entering its index as a number modifier and pressing:
|
||||
.Tg number-modifier
|
||||
.Shortcut a envelope_view open_attachment
|
||||
.It
|
||||
Open an attachment by its
|
||||
.Xr mailcap 4
|
||||
entry by entering its index as a number modifier and pressing:
|
||||
.Shortcut m envelope_view open_mailcap
|
||||
.It
|
||||
Reply to envelope:
|
||||
.Shortcut R envelope_view reply
|
||||
.It
|
||||
Reply to author:
|
||||
.Shortcut Ctrl\-r envelope_view reply_to_author
|
||||
.It
|
||||
Reply to all/Reply to list/Follow up:
|
||||
.Shortcut Ctrl\-g envelope_view reply_to_all
|
||||
.It
|
||||
Forward e\-mail:
|
||||
.Shortcut Ctrl\-f envelope_view forward
|
||||
.It
|
||||
Expand extra headers: (References and others)
|
||||
.Shortcut h envelope_view toggle_expand_headerk
|
||||
.It
|
||||
View envelope source in a pager: (toggles between raw and decoded source)
|
||||
.Shortcut M\-r envelope_view view_raw_source
|
||||
.It
|
||||
Return to envelope_view if viewing raw source or attachment:
|
||||
.Shortcut r envelope_view return_to_normal_view
|
||||
.El
|
||||
.Sh COMPOSING
|
||||
To compose an e\-mail, you can either start with an empty draft by pressing
|
||||
.Shortcut m listing new_mail
|
||||
which opens a composer view in a new tab.
|
||||
To reply to a specific e\-mail, when in envelope view you can select the specific action you want to take:
|
||||
.sp
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
Reply to envelope.
|
||||
.Shortcut R envelope_view reply
|
||||
.It
|
||||
Reply to author.
|
||||
.Shortcut Ctrl\-r envelope_view reply_to_author
|
||||
.It
|
||||
Reply to all.
|
||||
.Shortcut Ctrl\-g envelope_view reply_to_all
|
||||
.El
|
||||
.sp
|
||||
To launch your editor, press
|
||||
.ShortcutPeriod e composing edit
|
||||
\&.
|
||||
To send your draft, press
|
||||
.ShortcutPeriod s composing send_mail
|
||||
\&.
|
||||
To save the draft without submission, enter the command
|
||||
.Command close
|
||||
.sp
|
||||
and select
|
||||
.Qq save as draft Ns
|
||||
\&.
|
||||
You can return to the draft by going to your
|
||||
.Qq Drafts
|
||||
mailbox and selecting
|
||||
.ShortcutPeriod e envelope_view edit
|
||||
\&.
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ mail▐ contact list ▐ composing ▍███████████████████████│
|
||||
│ COMPOSING MESSAGE │
|
||||
│ Date Mon, 05 Sep 2022 17:49:19 +0300 │
|
||||
│ From myself <myself@example.com>░░░░ │
|
||||
│ To friend <myfriend@example.com>░░ │
|
||||
│ Cc ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ Bcc ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ Subject This is my subject!░░░░░░░░░░░░ │
|
||||
│ │
|
||||
│ Hello friend!░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ │
|
||||
│ ☐ don't sign │
|
||||
│ ☐ don't encrypt │
|
||||
│ no attachments │
|
||||
│ │
|
||||
│NORMAL | Mailbox: Inbox, Messages: 25772, New: 3006 │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em The\ lightly\ highlighted\ cells\ represent\ text\ input\ fields\&.
|
||||
.Ed
|
||||
.sp
|
||||
If you enable the embed terminal option, you can launch your terminal editor of choice when you press
|
||||
.Ic edit Ns
|
||||
\&.
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ mail▐ contact list ▐ composing ▍███████████████████████│
|
||||
│ ╓COMPOSING MESSAGE┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╖ │
|
||||
│ ║ p/v/f/h/5/T/m/07f56b6e-ec09-49d9-b8d8-f0c5a81e7826 ║ │
|
||||
│ ║ 7 Date: Mon, 05 Sep 2022 18:43:10 +0300 ║ │
|
||||
│ ║ 6 From: Mister Cardholder <mrholder@example.com> ║ │
|
||||
│ ║ 5 To: ║ │
|
||||
│ ║ 4 Cc: ║ │
|
||||
│ ║ 3 Bcc: ║ │
|
||||
│ ║ 2 Subject: ║ │
|
||||
│ ║ 1 User-Agent: meli 0.7.2 ║ │
|
||||
│ ║8 █ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║ N… <6e-ec09-49d9-b8d8-f0c5a81e7826 100% ㏑:8 ℅:1║ │
|
||||
│ ╚════════════════════════════════════════════════════╝ │
|
||||
│ │
|
||||
│ │
|
||||
│ ☐ don't sign │
|
||||
│ ☐ don't encrypt │
|
||||
│ no attachments │
|
||||
│ │
|
||||
│EMBED | Mailbox: Inbox, Messages: 25772, New: 3006 │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Bf -emphasis
|
||||
.Xr nvim 1 Ns
|
||||
\ running\ inside\ the\ composing\ tab\&.
|
||||
.Ef
|
||||
The\ double\ line\ border\ annotates\ the\ area\ of\ the\ embedded\ terminal,
|
||||
the\ actual\ embedding\ is\ seamless\&.
|
||||
.Ed
|
||||
.Ss composing mail commands
|
||||
.Bl -tag -width 36n
|
||||
.It Cm add\-attachment Ar PATH
|
||||
in composer, add
|
||||
.Ar PATH
|
||||
as an attachment
|
||||
.It Cm add\-attachment < Ar CMD Ar ARGS
|
||||
in composer, pipe
|
||||
.Ar CMD Ar ARGS
|
||||
output into an attachment
|
||||
.It Cm add\-attachment\-file\-picker
|
||||
Launch command defined in the configuration value
|
||||
.Ic file_picker_command
|
||||
in
|
||||
.Xr meli.conf 5 TERMINAL
|
||||
.It Cm add\-attachment\-file\-picker < Ar CMD Ar ARGS
|
||||
Launch command
|
||||
.Ar CMD Ar ARGS Ns
|
||||
\&.
|
||||
The command should print file paths in stderr, separated by NULL bytes.
|
||||
.It Cm remove\-attachment Ar INDEX
|
||||
remove attachment with given index
|
||||
.It Cm toggle sign
|
||||
toggle between signing and not signing this message.
|
||||
If the gpg invocation fails then the mail won't be sent.
|
||||
See
|
||||
.Xr meli.conf 5 PGP
|
||||
for PGP configuration.
|
||||
.It Cm save\-draft
|
||||
saves a copy of the draft in the Draft folder
|
||||
.El
|
||||
.\" [ref:TODO]: add contacts section
|
||||
.Sh THEMES
|
||||
See
|
||||
.Xr meli-themes 5
|
||||
for documentation on how to theme
|
||||
.Nm Ns
|
||||
\&.
|
||||
.Sh SEE ALSO
|
||||
.Xr meli 1 ,
|
||||
.Xr meli.conf 5 ,
|
||||
.Xr meli-themes 5 ,
|
||||
.Xr xdg-open 1 ,
|
||||
.Xr mailcap 5
|
||||
.Sh AUTHORS
|
||||
Copyright 2017\(en2024
|
||||
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||
.Pp
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
.Po
|
||||
See
|
||||
.Pa COPYING
|
||||
for full copyright and warranty notices.
|
||||
.Pc
|
||||
.Ss Links
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://meli\-email.org "Website"
|
||||
.It
|
||||
.Lk https://git.meli\-email.org/meli/meli "Main\ git\ repository\ and\ issue\ tracker"
|
||||
.It
|
||||
.Lk https://codeberg.org/meli/meli "Official\ read-only\ git\ mirror\ on\ codeberg.org"
|
||||
.It
|
||||
.Lk https://github.com/meli/meli "Official\ read-only\ git\ mirror\ on\ github.com"
|
||||
.It
|
||||
.Lk https://crates.io/crates/meli "meli\ crate\ on\ crates.io"
|
||||
.El
|
|
@ -11,7 +11,7 @@
|
|||
#[accounts.account-name]
|
||||
#root_mailbox = "/path/to/root/mailbox"
|
||||
#format = "Maildir"
|
||||
#index_style = "Conversations" # or [plain, threaded, compact]
|
||||
#listing.index_style = "Conversations" # or [plain, threaded, compact]
|
||||
#identity="email@example.com"
|
||||
#display_name = "Name"
|
||||
#subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||
|
@ -26,7 +26,7 @@
|
|||
#[accounts.mbox]
|
||||
#root_mailbox = "/var/mail/username"
|
||||
#format = "mbox"
|
||||
#index_style = "Compact"
|
||||
#listing.index_style = "Compact"
|
||||
#identity="username@hostname.local"
|
||||
#
|
||||
## Setting up an IMAP account
|
||||
|
@ -36,10 +36,10 @@
|
|||
#server_hostname="mail.example.com"
|
||||
#server_password="pha2hiLohs2eeeish2phaii1We3ood4chakaiv0hien2ahie3m"
|
||||
#server_username="username@example.com"
|
||||
#server_port="993" # imaps
|
||||
##server_port="993" # imaps
|
||||
#server_port="143" # STARTTLS
|
||||
#use_starttls=true #optional
|
||||
#index_style = "Conversations"
|
||||
#listing.index_style = "Conversations"
|
||||
#identity = "username@example.com"
|
||||
#display_name = "Name Name"
|
||||
### match every mailbox:
|
||||
|
@ -48,18 +48,18 @@
|
|||
##subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||
#
|
||||
## Setting up an account for an already existing notmuch database
|
||||
#[accounts.notmuch]
|
||||
#root_mailbox = "/path/to/folder" # where .notmuch/ directory is located
|
||||
#format = "notmuch"
|
||||
#index_style = "conversations"
|
||||
#identity="username@example.com"
|
||||
#display_name = "Name Name"
|
||||
# # notmuch mailboxes are virtual, they are defined by their alias and the notmuch query that corresponds to their content.
|
||||
# [accounts.notmuch.mailboxes]
|
||||
# "INBOX" = { query="tag:inbox", subscribe = true }
|
||||
# "Drafts" = { query="tag:draft", subscribe = true }
|
||||
# "Sent" = { query="from:username@example.com from:username2@example.com", subscribe = true }
|
||||
#
|
||||
##[accounts.notmuch]
|
||||
##root_mailbox = "/path/to/folder" # where .notmuch/ directory is located
|
||||
##format = "notmuch"
|
||||
##listing.index_style = "conversations"
|
||||
##identity="username@example.com"
|
||||
##display_name = "Name Name"
|
||||
## # notmuch mailboxes are virtual, they are defined by their alias and the notmuch query that corresponds to their content.
|
||||
## [accounts.notmuch.mailboxes]
|
||||
## "INBOX" = { query="tag:inbox", subscribe = true }
|
||||
## "Drafts" = { query="tag:draft", subscribe = true }
|
||||
## "Sent" = { query="from:username@example.com from:username2@example.com", subscribe = true }
|
||||
##
|
||||
## Setting up a Gmail account
|
||||
#[accounts."gmail"]
|
||||
#root_mailbox = '[Gmail]'
|
||||
|
@ -68,7 +68,7 @@
|
|||
#server_password="password"
|
||||
#server_username="username@gmail.com"
|
||||
#server_port="993"
|
||||
#index_style = "Conversations"
|
||||
#listing.index_style = "Conversations"
|
||||
#identity = "username@gmail.com"
|
||||
#display_name = "Name Name"
|
||||
### match every mailbox:
|
||||
|
@ -77,11 +77,21 @@
|
|||
### Gmail auto saves sent mail to Sent folder, so don't duplicate the effort:
|
||||
#composing.store_sent_mail = false
|
||||
#
|
||||
##[accounts."jmap account"]
|
||||
##root_mailbox = "INBOX"
|
||||
##format = "jmap"
|
||||
##server_url="http://localhost:8080"
|
||||
##server_username="user@hostname.local"
|
||||
##server_password="changeme"
|
||||
##listing.index_style = "Conversations"
|
||||
##identity = "user@hostname.local"
|
||||
##subscribed_mailboxes = ["*", ]
|
||||
##composing.send_mail = 'server_submission'
|
||||
#
|
||||
#[pager]
|
||||
#filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
||||
#pager_context = 0 # default, optional
|
||||
#headers_sticky = true # default, optional
|
||||
#sticky_headers = true # default, optional
|
||||
#
|
||||
#[notifications]
|
||||
#script = "notify-send"
|
||||
|
@ -91,11 +101,7 @@
|
|||
#
|
||||
###shortcuts
|
||||
#[shortcuts.composing]
|
||||
#edit_mail = 'e'
|
||||
#
|
||||
##Thread view defaults:
|
||||
#[shortcuts.compact-listing]
|
||||
#exit_thread = 'i'
|
||||
#edit = 'e'
|
||||
#
|
||||
#[shortcuts.contact-list]
|
||||
#create_contact = 'c'
|
||||
|
@ -111,6 +117,7 @@
|
|||
#next_account = 'h'
|
||||
#new_mail = 'm'
|
||||
#set_seen = 'n'
|
||||
#exit_entry = 'i'
|
||||
#
|
||||
##Pager defaults
|
||||
#
|
|
@ -14,11 +14,9 @@ color_aliases = { "neon_green" = "#6ef9d4", "darkgrey" = "#4a4a4a", "neon_purp
|
|||
"mail.listing.conversations.date" = { fg = "$neon_purple", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.from" = { fg = "$darkgrey", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.highlighted" = { fg = "theme_default", bg = "Grey58", attrs = "theme_default" }
|
||||
"mail.listing.conversations.padding" = { fg = "Black", bg = "Black", attrs = "theme_default" }
|
||||
"mail.listing.conversations.selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||
"mail.listing.conversations.subject" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen" = { fg = "Black", bg = "Grey78", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen_padding" = { fg = "Black", bg = "Black", attrs = "theme_default" }
|
||||
"mail.listing.plain.even" = { fg = "theme_default", bg = "Grey19", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_highlighted" = { fg = "theme_default", bg = "Grey58", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
|
@ -13,11 +13,9 @@
|
|||
"mail.listing.conversations" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.from" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.highlighted" = { fg = "theme_default", bg = "Grey58", attrs = "theme_default" }
|
||||
"mail.listing.conversations.padding" = { fg = "Grey15", bg = "Grey15", attrs = "theme_default" }
|
||||
"mail.listing.conversations.selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||
"mail.listing.conversations.subject" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen" = { fg = "Black", bg = "Grey78", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen_padding" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.plain.even" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.plain.odd" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_unseen" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
|
@ -16,11 +16,9 @@ color_aliases = { "JewelGreen" = "#157241", "PinkLace" = "#FFD5FD", "TorchRed" =
|
|||
"mail.listing.conversations" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.from" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.highlighted" = { fg = "$JewelGreen", bg = "$SunsetOrange", attrs = "theme_default" }
|
||||
"mail.listing.conversations.padding" = { fg = "$TorchRed", bg = "Grey15", attrs = "theme_default" }
|
||||
"mail.listing.conversations.selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||
"mail.listing.conversations.subject" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen" = { fg = "Black", bg = "mail.listing.compact.even_unseen", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen_padding" = { fg = "$BlueStone", bg = "mail.listing.conversations.unseen", attrs = "theme_default" }
|
||||
"mail.listing.plain.even" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.plain.odd" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_unseen" = { fg = "theme_default", bg = "mail.listing.compact.even_unseen", attrs = "theme_default" }
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* meli - accounts module.
|
||||
*
|
||||
* Copyright 2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Account mail backend operations.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Account {
|
||||
pub fn set_flags(
|
||||
&mut self,
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
) -> Result<JobId> {
|
||||
let fut = self.backend.write().unwrap().set_flags(
|
||||
env_hashes.clone(),
|
||||
mailbox_hash,
|
||||
flags.clone(),
|
||||
)?;
|
||||
let handle = self
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized("set_flags".into(), fut);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
job_id,
|
||||
JobRequest::SetFlags {
|
||||
env_hashes,
|
||||
mailbox_hash,
|
||||
flags,
|
||||
handle,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(job_id)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sqlite3"))]
|
||||
pub(super) fn update_cached_env(&mut self, _: Envelope, _: Option<EnvelopeHash>) {}
|
||||
|
||||
#[cfg(feature = "sqlite3")]
|
||||
pub(super) fn update_cached_env(&mut self, env: Envelope, old_hash: Option<EnvelopeHash>) {
|
||||
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
|
||||
let msg_id = env.message_id_display().to_string();
|
||||
let name = self.name.clone();
|
||||
let backend = self.backend.clone();
|
||||
let fut = async move {
|
||||
crate::sqlite3::AccountCache::remove(
|
||||
name.clone(),
|
||||
old_hash.unwrap_or_else(|| env.hash()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
crate::sqlite3::AccountCache::insert(env, backend, name).await?;
|
||||
Ok(())
|
||||
};
|
||||
let handle = self
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized("sqlite3::remove".into(), fut);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
name: format!("Update envelope {} in sqlite3 cache", msg_id).into(),
|
||||
handle,
|
||||
log_level: LogLevel::TRACE,
|
||||
on_finish: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
//
|
||||
// meli - accounts module.
|
||||
//
|
||||
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap, pin::Pin};
|
||||
|
||||
use futures::stream::Stream;
|
||||
use melib::{backends::*, email::*, error::Result, LogLevel};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{is_variant, jobs::JoinHandle};
|
||||
|
||||
pub enum JobRequest {
|
||||
Mailboxes {
|
||||
handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
|
||||
},
|
||||
Fetch {
|
||||
mailbox_hash: MailboxHash,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handle: JoinHandle<(
|
||||
Option<Result<Vec<Envelope>>>,
|
||||
Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>,
|
||||
)>,
|
||||
},
|
||||
Generic {
|
||||
name: Cow<'static, str>,
|
||||
log_level: LogLevel,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
on_finish: Option<crate::types::CallbackFn>,
|
||||
},
|
||||
IsOnline {
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
Refresh {
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SetFlags {
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SaveMessage {
|
||||
bytes: Vec<u8>,
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SendMessage,
|
||||
SendMessageBackground {
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
DeleteMessages {
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
CreateMailbox {
|
||||
path: String,
|
||||
handle: JoinHandle<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
|
||||
},
|
||||
DeleteMailbox {
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
|
||||
},
|
||||
//RenameMailbox,
|
||||
SetMailboxPermissions {
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SetMailboxSubscription {
|
||||
mailbox_hash: MailboxHash,
|
||||
new_value: bool,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
Watch {
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Drop for JobRequest {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
Self::Generic { handle, .. } |
|
||||
Self::IsOnline { handle, .. } |
|
||||
Self::Refresh { handle, .. } |
|
||||
Self::SetFlags { handle, .. } |
|
||||
Self::SaveMessage { handle, .. } |
|
||||
//JobRequest::RenameMailbox,
|
||||
Self::SetMailboxPermissions { handle, .. } |
|
||||
Self::SetMailboxSubscription { handle, .. } |
|
||||
Self::Watch { handle, .. } |
|
||||
Self::SendMessageBackground { handle, .. } => {
|
||||
handle.cancel();
|
||||
}
|
||||
Self::DeleteMessages { handle, .. } => {
|
||||
handle.cancel();
|
||||
}
|
||||
Self::CreateMailbox { handle, .. } => {
|
||||
handle.cancel();
|
||||
}
|
||||
Self::DeleteMailbox { handle, .. } => {
|
||||
handle.cancel();
|
||||
}
|
||||
Self::Fetch { handle, .. } => {
|
||||
handle.cancel();
|
||||
}
|
||||
Self::Mailboxes { handle, .. } => {
|
||||
handle.cancel();
|
||||
}
|
||||
Self::SendMessage => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JobRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Generic { name, .. } => write!(f, "JobRequest::Generic({})", name),
|
||||
Self::Mailboxes { .. } => write!(f, "JobRequest::Mailboxes"),
|
||||
Self::Fetch { mailbox_hash, .. } => {
|
||||
write!(f, "JobRequest::Fetch({})", mailbox_hash)
|
||||
}
|
||||
Self::IsOnline { .. } => write!(f, "JobRequest::IsOnline"),
|
||||
Self::Refresh { .. } => write!(f, "JobRequest::Refresh"),
|
||||
Self::SetFlags {
|
||||
env_hashes,
|
||||
mailbox_hash,
|
||||
flags,
|
||||
..
|
||||
} => f
|
||||
.debug_struct(stringify!(JobRequest::SetFlags))
|
||||
.field("env_hashes", &env_hashes)
|
||||
.field("mailbox_hash", &mailbox_hash)
|
||||
.field("flags", &flags)
|
||||
.finish(),
|
||||
Self::SaveMessage { .. } => write!(f, "JobRequest::SaveMessage"),
|
||||
Self::DeleteMessages { .. } => write!(f, "JobRequest::DeleteMessages"),
|
||||
Self::CreateMailbox { .. } => write!(f, "JobRequest::CreateMailbox"),
|
||||
Self::DeleteMailbox { mailbox_hash, .. } => {
|
||||
write!(f, "JobRequest::DeleteMailbox({})", mailbox_hash)
|
||||
}
|
||||
//JobRequest::RenameMailbox,
|
||||
Self::SetMailboxPermissions { .. } => {
|
||||
write!(f, "JobRequest::SetMailboxPermissions")
|
||||
}
|
||||
Self::SetMailboxSubscription { .. } => {
|
||||
write!(f, "JobRequest::SetMailboxSubscription")
|
||||
}
|
||||
Self::Watch { .. } => write!(f, "JobRequest::Watch"),
|
||||
Self::SendMessage => write!(f, "JobRequest::SendMessage"),
|
||||
Self::SendMessageBackground { .. } => {
|
||||
write!(f, "JobRequest::SendMessageBackground")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for JobRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Generic { name, .. } => write!(f, "{}", name),
|
||||
Self::Mailboxes { .. } => write!(f, "Get mailbox list"),
|
||||
Self::Fetch { .. } => write!(f, "Mailbox fetch"),
|
||||
Self::IsOnline { .. } => write!(f, "Online status check"),
|
||||
Self::Refresh { .. } => write!(f, "Refresh mailbox"),
|
||||
Self::SetFlags {
|
||||
env_hashes, flags, ..
|
||||
} => write!(
|
||||
f,
|
||||
"Set flags for {} message{}: {:?}",
|
||||
env_hashes.len(),
|
||||
if env_hashes.len() == 1 { "" } else { "s" },
|
||||
flags
|
||||
),
|
||||
Self::SaveMessage { .. } => write!(f, "Save message"),
|
||||
Self::DeleteMessages { env_hashes, .. } => write!(
|
||||
f,
|
||||
"Delete {} message{}",
|
||||
env_hashes.len(),
|
||||
if env_hashes.len() == 1 { "" } else { "s" }
|
||||
),
|
||||
Self::CreateMailbox { path, .. } => write!(f, "Create mailbox {}", path),
|
||||
Self::DeleteMailbox { .. } => write!(f, "Delete mailbox"),
|
||||
//JobRequest::RenameMailbox,
|
||||
Self::SetMailboxPermissions { .. } => write!(f, "Set mailbox permissions"),
|
||||
Self::SetMailboxSubscription { .. } => write!(f, "Set mailbox subscription"),
|
||||
Self::Watch { .. } => write!(f, "Background watch"),
|
||||
Self::SendMessageBackground { .. } | Self::SendMessage => {
|
||||
write!(f, "Sending message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JobRequest {
|
||||
is_variant! { is_watch, Watch { .. } }
|
||||
is_variant! { is_online, IsOnline { .. } }
|
||||
|
||||
pub fn is_fetch(&self, mailbox_hash: MailboxHash) -> bool {
|
||||
matches!(self, Self::Fetch {
|
||||
mailbox_hash: h, ..
|
||||
} if *h == mailbox_hash)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,347 @@
|
|||
//
|
||||
// meli - accounts module.
|
||||
//
|
||||
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use melib::{
|
||||
backends::{Mailbox, MailboxHash},
|
||||
error::Error,
|
||||
log,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{conf::FileMailboxConf, is_variant};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum MailboxStatus {
|
||||
Available,
|
||||
Failed(Error),
|
||||
/// first argument is done work, and second is total work
|
||||
Parsing(usize, usize),
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl MailboxStatus {
|
||||
is_variant! { is_available, Available }
|
||||
is_variant! { is_parsing, Parsing(_, _) }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MailboxEntry {
|
||||
pub status: MailboxStatus,
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub ref_mailbox: Mailbox,
|
||||
pub conf: FileMailboxConf,
|
||||
}
|
||||
|
||||
impl MailboxEntry {
|
||||
pub fn new(
|
||||
status: MailboxStatus,
|
||||
name: String,
|
||||
ref_mailbox: Mailbox,
|
||||
conf: FileMailboxConf,
|
||||
) -> Self {
|
||||
let mut ret = Self {
|
||||
status,
|
||||
name,
|
||||
path: ref_mailbox.path().into(),
|
||||
ref_mailbox,
|
||||
conf,
|
||||
};
|
||||
match ret.conf.mailbox_conf.extra.get("encoding") {
|
||||
None => {}
|
||||
Some(v) if ["utf-8", "utf8"].iter().any(|e| v.eq_ignore_ascii_case(e)) => {}
|
||||
Some(v) if ["utf-7", "utf7"].iter().any(|e| v.eq_ignore_ascii_case(e)) => {
|
||||
ret.name = melib::backends::utf7::decode_utf7_imap(&ret.name);
|
||||
ret.path = melib::backends::utf7::decode_utf7_imap(&ret.path);
|
||||
}
|
||||
Some(other) => {
|
||||
log::warn!(
|
||||
"mailbox `{}`: unrecognized mailbox name charset: {}",
|
||||
&ret.name,
|
||||
other
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn status(&self) -> String {
|
||||
match self.status {
|
||||
MailboxStatus::Available => format!(
|
||||
"{} [{} messages]",
|
||||
self.name(),
|
||||
self.ref_mailbox.count().ok().unwrap_or((0, 0)).1
|
||||
),
|
||||
MailboxStatus::Failed(ref e) => e.to_string(),
|
||||
MailboxStatus::None => "Retrieving mailbox.".to_string(),
|
||||
MailboxStatus::Parsing(done, total) => {
|
||||
format!("Parsing messages. [{}/{}]", done, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
if let Some(name) = self.conf.mailbox_conf.alias.as_ref() {
|
||||
name
|
||||
} else {
|
||||
self.ref_mailbox.name()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct MailboxNode {
|
||||
pub hash: MailboxHash,
|
||||
pub depth: usize,
|
||||
pub indentation: u32,
|
||||
pub has_sibling: bool,
|
||||
pub children: Vec<MailboxNode>,
|
||||
}
|
||||
|
||||
pub fn build_mailboxes_order(
|
||||
tree: &mut Vec<MailboxNode>,
|
||||
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
||||
mailboxes_order: &mut Vec<MailboxHash>,
|
||||
) {
|
||||
tree.clear();
|
||||
mailboxes_order.clear();
|
||||
for (h, f) in mailbox_entries.iter() {
|
||||
if f.ref_mailbox.parent().is_none() {
|
||||
fn rec(
|
||||
h: MailboxHash,
|
||||
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
||||
depth: usize,
|
||||
) -> MailboxNode {
|
||||
let mut node = MailboxNode {
|
||||
hash: h,
|
||||
children: Vec::new(),
|
||||
depth,
|
||||
indentation: 0,
|
||||
has_sibling: false,
|
||||
};
|
||||
for &c in mailbox_entries[&h].ref_mailbox.children() {
|
||||
if mailbox_entries.contains_key(&c) {
|
||||
node.children.push(rec(c, mailbox_entries, depth + 1));
|
||||
}
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
tree.push(rec(*h, mailbox_entries, 0));
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mailbox_eq_key {
|
||||
($mailbox:expr) => {{
|
||||
if let Some(sort_order) = $mailbox.conf.mailbox_conf.sort_order {
|
||||
(0, sort_order, $mailbox.ref_mailbox.path())
|
||||
} else {
|
||||
(1, 0, $mailbox.ref_mailbox.path())
|
||||
}
|
||||
}};
|
||||
}
|
||||
tree.sort_unstable_by(|a, b| {
|
||||
if mailbox_entries[&b.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&b.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Greater
|
||||
} else if mailbox_entries[&a.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&a.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
mailbox_eq_key!(mailbox_entries[&a.hash])
|
||||
.cmp(&mailbox_eq_key!(mailbox_entries[&b.hash]))
|
||||
}
|
||||
});
|
||||
|
||||
let mut stack: SmallVec<[Option<&MailboxNode>; 16]> = SmallVec::new();
|
||||
for n in tree.iter_mut() {
|
||||
mailboxes_order.push(n.hash);
|
||||
n.children.sort_unstable_by(|a, b| {
|
||||
if mailbox_entries[&b.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&b.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Greater
|
||||
} else if mailbox_entries[&a.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&a.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
mailbox_eq_key!(mailbox_entries[&a.hash])
|
||||
.cmp(&mailbox_eq_key!(mailbox_entries[&b.hash]))
|
||||
}
|
||||
});
|
||||
stack.extend(n.children.iter().rev().map(Some));
|
||||
while let Some(Some(next)) = stack.pop() {
|
||||
mailboxes_order.push(next.hash);
|
||||
stack.extend(next.children.iter().rev().map(Some));
|
||||
}
|
||||
}
|
||||
drop(stack);
|
||||
for node in tree.iter_mut() {
|
||||
fn rec(
|
||||
node: &mut MailboxNode,
|
||||
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
||||
mut indentation: u32,
|
||||
has_sibling: bool,
|
||||
) {
|
||||
node.indentation = indentation;
|
||||
node.has_sibling = has_sibling;
|
||||
let mut iter = (0..node.children.len())
|
||||
.filter(|i| {
|
||||
mailbox_entries[&node.children[*i].hash]
|
||||
.ref_mailbox
|
||||
.is_subscribed()
|
||||
})
|
||||
.collect::<SmallVec<[_; 8]>>()
|
||||
.into_iter()
|
||||
.peekable();
|
||||
indentation <<= 1;
|
||||
if has_sibling {
|
||||
indentation |= 1;
|
||||
}
|
||||
while let Some(i) = iter.next() {
|
||||
let c = &mut node.children[i];
|
||||
rec(c, mailbox_entries, indentation, iter.peek().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
rec(node, mailbox_entries, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use melib::{
|
||||
backends::{Mailbox, MailboxHash},
|
||||
error::Result,
|
||||
MailboxPermissions, SpecialUsageMailbox,
|
||||
};
|
||||
|
||||
use crate::accounts::{FileMailboxConf, MailboxEntry, MailboxStatus};
|
||||
|
||||
#[test]
|
||||
fn test_mailbox_utf7() {
|
||||
#[derive(Debug)]
|
||||
struct TestMailbox(String);
|
||||
|
||||
impl melib::BackendMailbox for TestMailbox {
|
||||
fn hash(&self) -> MailboxHash {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn children(&self) -> &[MailboxHash] {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn clone(&self) -> Mailbox {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn special_usage(&self) -> SpecialUsageMailbox {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<MailboxHash> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn permissions(&self) -> MailboxPermissions {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_subscribed(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_is_subscribed(&mut self, _: bool) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_special_usage(&mut self, _: SpecialUsageMailbox) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn count(&self) -> Result<(usize, usize)> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
for (n, d) in [
|
||||
("~peter/mail/&U,BTFw-/&ZeVnLIqe-", "~peter/mail/台北/日本語"),
|
||||
("&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-", "Отправленные"),
|
||||
] {
|
||||
let ref_mbox = TestMailbox(n.to_string());
|
||||
let mut conf: melib::MailboxConf = Default::default();
|
||||
conf.extra.insert("encoding".to_string(), "utf7".into());
|
||||
|
||||
let entry = MailboxEntry::new(
|
||||
MailboxStatus::None,
|
||||
n.to_string(),
|
||||
Box::new(ref_mbox),
|
||||
FileMailboxConf {
|
||||
mailbox_conf: conf,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(&entry.path, d);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* meli - args.rs
|
||||
*
|
||||
* Copyright 2017-2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Command line arguments.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "meli", about = "terminal mail client", version_short = "v")]
|
||||
pub struct Opt {
|
||||
/// use specified configuration file
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub subcommand: Option<SubCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum SubCommand {
|
||||
/// print default theme in full to stdout and exit.
|
||||
PrintDefaultTheme,
|
||||
/// print loaded themes in full to stdout and exit.
|
||||
PrintLoadedThemes,
|
||||
/// print all directories that meli creates/uses.
|
||||
PrintAppDirectories,
|
||||
/// print location of configuration file that will be loaded on normal app
|
||||
/// startup.
|
||||
PrintConfigPath,
|
||||
/// edit configuration files with `$EDITOR`/`$VISUAL`.
|
||||
EditConfig,
|
||||
/// create a sample configuration file with available configuration options.
|
||||
/// If PATH is not specified, meli will try to create it in
|
||||
/// $XDG_CONFIG_HOME/meli/config.toml
|
||||
#[structopt(display_order = 1)]
|
||||
CreateConfig {
|
||||
#[structopt(value_name = "NEW_CONFIG_PATH", parse(from_os_str))]
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
/// test a configuration file for syntax issues or missing options.
|
||||
#[structopt(display_order = 2)]
|
||||
TestConfig {
|
||||
#[structopt(value_name = "CONFIG_PATH", parse(from_os_str))]
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
#[structopt(visible_alias="docs", aliases=&["docs", "manpage", "manpages"])]
|
||||
#[structopt(display_order = 3)]
|
||||
/// print documentation page and exit (Piping to a pager is recommended.).
|
||||
Man(ManOpt),
|
||||
|
||||
#[structopt(display_order = 4)]
|
||||
/// Install manual pages to the first location provided by $MANPATH /
|
||||
/// manpath(1), unless you specify the directory as an argument.
|
||||
InstallMan {
|
||||
#[structopt(value_name = "DESTINATION_PATH", parse(from_os_str))]
|
||||
destination_path: Option<PathBuf>,
|
||||
},
|
||||
#[structopt(display_order = 5)]
|
||||
/// Print compile time feature flags of this binary
|
||||
CompiledWith,
|
||||
/// Print log file location.
|
||||
PrintLogPath,
|
||||
/// View mail from input file.
|
||||
View {
|
||||
#[structopt(value_name = "INPUT", parse(from_os_str))]
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct ManOpt {
|
||||
#[cfg(feature = "cli-docs")]
|
||||
#[cfg_attr(feature = "cli-docs", structopt(default_value = "meli", possible_values=manpages::POSSIBLE_VALUES, value_name="PAGE", parse(try_from_str = manpages::parse_manpage)))]
|
||||
/// Name of manual page.
|
||||
pub page: manpages::ManPages,
|
||||
/// If true, output text in stdout instead of spawning $PAGER.
|
||||
#[cfg(feature = "cli-docs")]
|
||||
#[cfg_attr(
|
||||
feature = "cli-docs",
|
||||
structopt(long = "no-raw", alias = "no-raw", value_name = "bool")
|
||||
)]
|
||||
pub no_raw: Option<Option<bool>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli-docs")]
|
||||
pub mod manpages {
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use melib::log;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub const POSSIBLE_VALUES: &[&str] = &[
|
||||
"meli",
|
||||
"meli.1",
|
||||
"conf",
|
||||
"meli.conf",
|
||||
"meli.conf.5",
|
||||
"themes",
|
||||
"meli-themes",
|
||||
"meli-themes.5",
|
||||
"guide",
|
||||
"meli.7",
|
||||
];
|
||||
|
||||
pub fn parse_manpage(src: &str) -> Result<ManPages> {
|
||||
match src {
|
||||
"" | "meli" | "meli.1" | "main" => Ok(ManPages::Main),
|
||||
"meli.7" | "guide" => Ok(ManPages::Guide),
|
||||
"meli.conf" | "meli.conf.5" | "conf" | "config" | "configuration" => Ok(ManPages::Conf),
|
||||
"meli-themes" | "meli-themes.5" | "themes" | "theming" | "theme" => {
|
||||
Ok(ManPages::Themes)
|
||||
}
|
||||
_ => Err(Error::new(format!("Invalid documentation page: {src}",))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
/// Choose manpage
|
||||
pub enum ManPages {
|
||||
/// meli(1)
|
||||
Main = 0,
|
||||
/// meli.conf(5)
|
||||
Conf = 1,
|
||||
/// meli-themes(5)
|
||||
Themes = 2,
|
||||
/// meli(7)
|
||||
Guide = 3,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ManPages {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Main => "meli.1",
|
||||
Self::Conf => "meli.conf.5",
|
||||
Self::Themes => "meli-themes.5",
|
||||
Self::Guide => "meli.7",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ManPages {
|
||||
pub fn install(destination: Option<PathBuf>) -> Result<PathBuf> {
|
||||
fn path_valid(p: &Path, tries: &mut Vec<PathBuf>) -> bool {
|
||||
tries.push(p.into());
|
||||
p.exists()
|
||||
&& p.is_dir()
|
||||
&& fs::metadata(p)
|
||||
.ok()
|
||||
.map(|m| !m.permissions().readonly())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
let mut tries = vec![];
|
||||
let Some(mut path) = destination
|
||||
.filter(|p| path_valid(p, &mut tries))
|
||||
.or_else(|| {
|
||||
if let Some(paths) = env::var_os("MANPATH") {
|
||||
if let Some(path) =
|
||||
env::split_paths(&paths).find(|p| path_valid(p, &mut tries))
|
||||
{
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.or_else(|| {
|
||||
#[allow(deprecated)]
|
||||
env::home_dir()
|
||||
.map(|p| p.join(".local").join("share").join("man"))
|
||||
.filter(|p| path_valid(p, &mut tries))
|
||||
})
|
||||
else {
|
||||
return Err(format!("Could not write to any of these paths: {:?}", tries).into());
|
||||
};
|
||||
|
||||
for (p, dir) in [
|
||||
(Self::Main, "man1"),
|
||||
(Self::Conf, "man5"),
|
||||
(Self::Themes, "man5"),
|
||||
(Self::Guide, "man7"),
|
||||
] {
|
||||
let text = crate::subcommands::man(p, true)?;
|
||||
path.push(dir);
|
||||
std::fs::create_dir_all(&path).map_err(|err| {
|
||||
Error::new(format!("Could not create {} directory.", path.display()))
|
||||
.set_source(Some(Arc::new(err)))
|
||||
})?;
|
||||
path.push(&p.to_string());
|
||||
|
||||
fs::write(&path, text.as_bytes()).map_err(|err| {
|
||||
Error::new(format!("Could not write to {}", path.display()))
|
||||
.set_source(Some(Arc::new(err)))
|
||||
})?;
|
||||
log::trace!("Installed {} to {}", p, path.display());
|
||||
path.pop();
|
||||
path.pop();
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,688 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! A parser module for user commands passed through
|
||||
//! [`Command`](crate::types::UIMode::Command) mode.
|
||||
|
||||
use std::{borrow::Cow, collections::HashSet, str::FromStr};
|
||||
|
||||
use melib::{
|
||||
nom::{
|
||||
self,
|
||||
branch::alt,
|
||||
bytes::complete::{is_a, is_not, tag, take_until},
|
||||
character::complete::{digit1, not_line_ending},
|
||||
combinator::{map, map_res},
|
||||
error::Error as NomError,
|
||||
multi::separated_list1,
|
||||
sequence::{pair, preceded, separated_pair},
|
||||
IResult,
|
||||
},
|
||||
parser::BytesExt,
|
||||
SortField, SortOrder,
|
||||
};
|
||||
|
||||
pub mod actions;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
#[macro_use]
|
||||
pub mod argcheck;
|
||||
pub mod history;
|
||||
pub mod parser;
|
||||
use actions::MailboxOperation;
|
||||
use error::CommandError;
|
||||
pub use parser::parse_command;
|
||||
|
||||
pub use crate::actions::{
|
||||
AccountAction::{self, *},
|
||||
Action::{self, *},
|
||||
ComposeAction::{self, *},
|
||||
FlagAction,
|
||||
ListingAction::{self, *},
|
||||
MailingListAction::{self, *},
|
||||
TabAction::{self, *},
|
||||
TagAction,
|
||||
ViewAction::{self, *},
|
||||
};
|
||||
|
||||
/// Helper macro to convert an array of tokens into a `TokenStream`
|
||||
macro_rules! to_stream {
|
||||
($token: expr) => {
|
||||
TokenStream {
|
||||
tokens: &[$token],
|
||||
}
|
||||
};
|
||||
($($tokens:expr),*) => {
|
||||
TokenStream {
|
||||
tokens: &[$($tokens),*],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to create a const table with every command part that can be
|
||||
/// auto-completed and its description
|
||||
macro_rules! define_commands {
|
||||
( [$({ tags: [$( $tags:literal),*], desc: $desc:literal, tokens: $tokens:expr, parser: $parser:path}),*]) => {
|
||||
pub const COMMAND_COMPLETION: &[(&str, &str, TokenStream, fn(&[u8]) -> IResult<&[u8], Result<Action, CommandError>>)] = &[$($( ($tags, $desc, TokenStream { tokens: $tokens }, $parser) ),*),* ];
|
||||
};
|
||||
}
|
||||
|
||||
pub fn quoted_argument(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
if input.is_empty() {
|
||||
return Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: nom::error::ErrorKind::Tag,
|
||||
}));
|
||||
}
|
||||
|
||||
if input[0] == b'"' {
|
||||
let mut i = 1;
|
||||
while i < input.len() {
|
||||
if input[i] == b'\"' && input[i - 1] != b'\\' {
|
||||
return Ok((&input[i + 1..], unsafe {
|
||||
std::str::from_utf8_unchecked(&input[1..i])
|
||||
}));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: nom::error::ErrorKind::Tag,
|
||||
}))
|
||||
} else {
|
||||
map_res(is_not(" "), std::str::from_utf8)(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TokenStream {
|
||||
tokens: &'static [TokenAdicity],
|
||||
}
|
||||
|
||||
use Token::*;
|
||||
use TokenAdicity::*;
|
||||
|
||||
impl TokenStream {
|
||||
fn matches<'s>(&self, s: &mut &'s str, sugg: &mut HashSet<String>) -> Vec<(&'s str, Token)> {
|
||||
let mut tokens = vec![];
|
||||
for t in self.tokens.iter() {
|
||||
let mut ptr = 0;
|
||||
while ptr + 1 < s.len() && s.as_bytes()[ptr].is_ascii_whitespace() {
|
||||
ptr += 1;
|
||||
}
|
||||
*s = &s[ptr..];
|
||||
//println!("\t before s.is_empty() {:?} {:?}", t, s);
|
||||
if s.is_empty() || *s == " " {
|
||||
match t.inner() {
|
||||
Literal(lit) => {
|
||||
sugg.insert(format!("{}{}", if s.is_empty() { " " } else { "" }, lit));
|
||||
}
|
||||
Alternatives(v) => {
|
||||
for t in v.iter() {
|
||||
//println!("adding empty suggestions for {:?}", t);
|
||||
let mut _s = *s;
|
||||
let mut m = t.matches(&mut _s, sugg);
|
||||
tokens.append(&mut m);
|
||||
}
|
||||
}
|
||||
Seq(_s) => {}
|
||||
RestOfStringValue => {
|
||||
sugg.insert(String::new());
|
||||
}
|
||||
t @ AttachmentIndexValue
|
||||
| t @ MailboxIndexValue
|
||||
| t @ IndexValue
|
||||
| t @ Filepath
|
||||
| t @ AccountName
|
||||
| t @ MailboxPath
|
||||
| t @ QuotedStringValue
|
||||
| t @ AlphanumericStringValue => {
|
||||
let _t = t;
|
||||
//sugg.insert(format!("{}{:?}", if s.is_empty() { " " }
|
||||
// else { "" }, t));
|
||||
}
|
||||
}
|
||||
tokens.push((*s, *t.inner()));
|
||||
return tokens;
|
||||
}
|
||||
match t.inner() {
|
||||
Literal(lit) => {
|
||||
if lit.starts_with(*s) && lit.len() != s.len() {
|
||||
sugg.insert(lit[s.len()..].to_string());
|
||||
tokens.push((s, *t.inner()));
|
||||
return tokens;
|
||||
} else if s.starts_with(lit) {
|
||||
tokens.push((&s[..lit.len()], *t.inner()));
|
||||
*s = &s[lit.len()..];
|
||||
} else {
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
Alternatives(v) => {
|
||||
let mut cont = true;
|
||||
for t in v.iter() {
|
||||
let mut _s = *s;
|
||||
let mut m = t.matches(&mut _s, sugg);
|
||||
if !m.is_empty() {
|
||||
tokens.append(&mut m);
|
||||
//println!("_s is empty {}", _s.is_empty());
|
||||
cont = !_s.is_empty();
|
||||
*s = _s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if tokens.is_empty() {
|
||||
return tokens;
|
||||
}
|
||||
if !cont {
|
||||
*s = "";
|
||||
}
|
||||
}
|
||||
Seq(_s) => {
|
||||
return vec![];
|
||||
}
|
||||
RestOfStringValue => {
|
||||
tokens.push((*s, *t.inner()));
|
||||
return tokens;
|
||||
}
|
||||
AttachmentIndexValue
|
||||
| MailboxIndexValue
|
||||
| IndexValue
|
||||
| Filepath
|
||||
| AccountName
|
||||
| MailboxPath
|
||||
| QuotedStringValue
|
||||
| AlphanumericStringValue => {
|
||||
let mut ptr = 0;
|
||||
while ptr + 1 < s.len() && !s.as_bytes()[ptr].is_ascii_whitespace() {
|
||||
ptr += 1;
|
||||
}
|
||||
tokens.push((&s[..ptr + 1], *t.inner()));
|
||||
*s = &s[ptr + 1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
/// `Token` wrapper that defines how many times a token is expected to be
|
||||
/// repeated
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TokenAdicity {
|
||||
ZeroOrOne(Token),
|
||||
ZeroOrMore(Token),
|
||||
One(Token),
|
||||
OneOrMore(Token),
|
||||
}
|
||||
|
||||
impl TokenAdicity {
|
||||
fn inner(&self) -> &Token {
|
||||
match self {
|
||||
ZeroOrOne(ref t) => t,
|
||||
ZeroOrMore(ref t) => t,
|
||||
One(ref t) => t,
|
||||
OneOrMore(ref t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A token encountered in the UI's command execution bar
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Token {
|
||||
Literal(&'static str),
|
||||
Filepath,
|
||||
Alternatives(&'static [TokenStream]),
|
||||
Seq(&'static [TokenAdicity]),
|
||||
AccountName,
|
||||
MailboxPath,
|
||||
QuotedStringValue,
|
||||
RestOfStringValue,
|
||||
AlphanumericStringValue,
|
||||
AttachmentIndexValue,
|
||||
MailboxIndexValue,
|
||||
IndexValue,
|
||||
}
|
||||
|
||||
fn eof(input: &[u8]) -> IResult<&[u8], ()> {
|
||||
if input.is_empty() {
|
||||
Ok((input, ()))
|
||||
} else {
|
||||
Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: nom::error::ErrorKind::Tag,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
define_commands!([
|
||||
{ tags: ["set", "set seen", "set unseen", "set plain", "set threaded", "set compact"],
|
||||
desc: "set [seen/unseen], toggles message's Seen flag. set [plain/threaded/compact/conversations] changes the mail listing view",
|
||||
tokens: &[One(Literal("set")),
|
||||
One(
|
||||
Alternatives(&[
|
||||
to_stream!(One(Literal("seen"))),
|
||||
to_stream!(One(Literal("unseen"))),
|
||||
to_stream!(One(Literal("plain"))),
|
||||
to_stream!(One(Literal("threaded"))),
|
||||
to_stream!(One(Literal("compact"))),
|
||||
to_stream!(One(Literal("conversations")))
|
||||
])
|
||||
)
|
||||
],
|
||||
parser: parser::set
|
||||
},
|
||||
{ tags: ["delete"],
|
||||
desc: "delete message",
|
||||
tokens: &[One(Literal("delete"))],
|
||||
parser: parser::delete_message
|
||||
},
|
||||
{ tags: ["copyto", "moveto"],
|
||||
desc: "copy/move message",
|
||||
tokens: &[One(Alternatives(&[to_stream!(One(Literal("copyto"))), to_stream!(One(Literal("moveto")))])), ZeroOrOne(AccountName), One(MailboxPath)],
|
||||
parser: parser::copymove
|
||||
},
|
||||
{ tags: ["import "],
|
||||
desc: "import FILESYSTEM_PATH MAILBOX_PATH",
|
||||
tokens: &[One(Literal("import")), One(Filepath), One(MailboxPath)],
|
||||
parser: parser::import
|
||||
},
|
||||
{ tags: ["close"],
|
||||
desc: "close non-sticky tabs",
|
||||
tokens: &[One(Literal("close"))],
|
||||
parser: parser::close
|
||||
},
|
||||
{ tags: ["go"],
|
||||
desc: "go <n>, switch to nth mailbox in this account",
|
||||
tokens: &[One(Literal("goto")), One(MailboxIndexValue)],
|
||||
parser: parser::goto
|
||||
},
|
||||
{ tags: ["subsort"],
|
||||
desc: "subsort [date/subject] [asc/desc], sorts first level replies in threads.",
|
||||
tokens: &[One(Literal("subsort")), One(Alternatives(&[to_stream!(One(Literal("date"))), to_stream!(One(Literal("subject")))])), One(Alternatives(&[to_stream!(One(Literal("asc"))), to_stream!(One(Literal("desc")))])) ],
|
||||
parser: parser::subsort
|
||||
},
|
||||
{ tags: ["sort"],
|
||||
desc: "sort [date/subject] [asc/desc], sorts threads.",
|
||||
tokens: &[One(Literal("sort")), One(Alternatives(&[to_stream!(One(Literal("date"))), to_stream!(One(Literal("subject")))])), One(Alternatives(&[to_stream!(One(Literal("asc"))), to_stream!(One(Literal("desc")))])) ],
|
||||
parser: parser::sort
|
||||
},
|
||||
{ tags: ["sort"],
|
||||
desc: "sort <column index> [asc/desc], sorts table columns.",
|
||||
tokens: &[One(Literal("sort")), One(IndexValue), ZeroOrOne(Alternatives(&[to_stream!(One(Literal("asc"))), to_stream!(One(Literal("desc")))])) ],
|
||||
parser: parser::sort_column
|
||||
},
|
||||
{ tags: ["toggle thread_snooze"],
|
||||
desc: "turn off new notifications for this thread",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("thread_snooze"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["search"],
|
||||
desc: "search <TERM>, searches list with given term",
|
||||
tokens: &[One(Literal("search")), One(RestOfStringValue)],
|
||||
parser: parser::search
|
||||
},
|
||||
{ tags: ["clear-selection"],
|
||||
desc: "clear-selection",
|
||||
tokens: &[One(Literal("clear-selection"))],
|
||||
parser: parser::select
|
||||
},
|
||||
{ tags: ["select"],
|
||||
desc: "select <TERM>, selects envelopes matching with given term",
|
||||
tokens: &[One(Literal("select")), One(RestOfStringValue)],
|
||||
parser: parser::select
|
||||
},
|
||||
{ tags: ["export-mbox "],
|
||||
desc: "export-mbox PATH",
|
||||
tokens: &[One(Literal("export-mbox")), One(Filepath)],
|
||||
parser: parser::export_mbox
|
||||
},
|
||||
{ tags: ["list-archive", "list-post", "list-unsubscribe", "list-"],
|
||||
desc: "list-[unsubscribe/post/archive]",
|
||||
tokens: &[One(Alternatives(&[to_stream!(One(Literal("list-archive"))), to_stream!(One(Literal("list-post"))), to_stream!(One(Literal("list-unsubscribe")))]))],
|
||||
parser: parser::mailinglist
|
||||
},
|
||||
{ tags: ["setenv "],
|
||||
desc: "setenv VAR=VALUE",
|
||||
tokens: &[One(Literal("setenv")), OneOrMore(Seq(&[One(AlphanumericStringValue), One(Literal("=")), One(QuotedStringValue)]))],
|
||||
parser: parser::setenv
|
||||
},
|
||||
{ tags: ["printenv "],
|
||||
desc: "printenv VAR",
|
||||
tokens: &[],
|
||||
parser: parser::printenv
|
||||
},
|
||||
{ tags: ["mailto "],
|
||||
desc: "mailto MAILTO_ADDRESS",
|
||||
tokens: &[One(Literal("mailto")), One(QuotedStringValue)],
|
||||
parser: parser::mailto
|
||||
},
|
||||
/* Pipe pager contents to binary */
|
||||
{ tags: ["pipe "],
|
||||
desc: "pipe EXECUTABLE ARGS",
|
||||
tokens: &[One(Literal("pipe")), One(Filepath), ZeroOrMore(QuotedStringValue)],
|
||||
parser: parser::pipe
|
||||
},
|
||||
/* Filter pager contents through binary */
|
||||
{ tags: ["filter "],
|
||||
desc: "filter EXECUTABLE ARGS",
|
||||
tokens: &[One(Literal("filter")), One(Filepath), ZeroOrMore(QuotedStringValue)],
|
||||
parser: parser::filter
|
||||
},
|
||||
{ tags: ["add-attachment ", "add-attachment-file-picker "],
|
||||
desc: "add-attachment PATH",
|
||||
tokens: &[One(
|
||||
Alternatives(&[to_stream!(One(Literal("add-attachment")), One(Filepath)), to_stream!(One(Literal("add-attachment-file-picker")))]))],
|
||||
parser: parser::add_attachment
|
||||
},
|
||||
{ tags: ["remove-attachment "],
|
||||
desc: "remove-attachment INDEX",
|
||||
tokens: &[One(Literal("remove-attachment")), One(IndexValue)],
|
||||
parser: parser::remove_attachment
|
||||
},
|
||||
{ tags: ["save-draft"],
|
||||
desc: "save draft",
|
||||
tokens: &[One(Literal("save-draft"))],
|
||||
parser: parser::save_draft
|
||||
},
|
||||
{ tags: ["toggle sign "],
|
||||
desc: "switch between sign/unsign for this draft",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("sign"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["toggle encrypt"],
|
||||
desc: "toggle encryption for this draft",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("encrypt"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["create-mailbox "],
|
||||
desc: "create-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("create-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::create_mailbox
|
||||
},
|
||||
{ tags: ["subscribe-mailbox "],
|
||||
desc: "subscribe-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("subscribe-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::sub_mailbox
|
||||
},
|
||||
{ tags: ["unsubscribe-mailbox "],
|
||||
desc: "unsubscribe-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("unsubscribe-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::unsub_mailbox
|
||||
},
|
||||
{ tags: ["rename-mailbox "],
|
||||
desc: "rename-mailbox ACCOUNT MAILBOX_PATH_SRC MAILBOX_PATH_DEST",
|
||||
tokens: &[One(Literal("rename-mailbox")), One(AccountName), One(MailboxPath), One(MailboxPath)],
|
||||
parser: parser::rename_mailbox
|
||||
},
|
||||
{ tags: ["delete-mailbox "],
|
||||
desc: "delete-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("delete-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::delete_mailbox
|
||||
},
|
||||
{ tags: ["reindex "],
|
||||
desc: "reindex ACCOUNT, rebuild account cache in the background",
|
||||
tokens: &[One(Literal("reindex")), One(AccountName)],
|
||||
parser: parser::reindex
|
||||
},
|
||||
{ tags: ["open-in-tab"],
|
||||
desc: "opens envelope view in new tab",
|
||||
tokens: &[One(Literal("open-in-tab"))],
|
||||
parser: parser::open_in_new_tab
|
||||
},
|
||||
{ tags: ["save-attachment "],
|
||||
desc: "save-attachment INDEX PATH",
|
||||
tokens: &[One(Literal("save-attachment")), One(AttachmentIndexValue), One(Filepath)],
|
||||
parser: parser::save_attachment
|
||||
},
|
||||
{ tags: ["export-mail "],
|
||||
desc: "export-mail PATH",
|
||||
tokens: &[One(Literal("export-mail")), One(Filepath)],
|
||||
parser: parser::export_mail
|
||||
},
|
||||
{ tags: ["add-addresses-to-contacts "],
|
||||
desc: "add-addresses-to-contacts",
|
||||
tokens: &[One(Literal("add-addresses-to-contacts"))],
|
||||
parser: parser::add_addresses_to_contacts
|
||||
},
|
||||
{ tags: ["tag", "tag add", "tag remove"],
|
||||
desc: "tag [add/remove], edits message's tags.",
|
||||
tokens: &[One(Literal("tag")), One(Alternatives(&[to_stream!(One(Literal("add"))), to_stream!(One(Literal("remove")))]))],
|
||||
parser: parser::_tag
|
||||
},
|
||||
{ tags: ["print "],
|
||||
desc: "print ACCOUNT SETTING",
|
||||
tokens: &[One(Literal("print")), One(AccountName), One(QuotedStringValue)],
|
||||
parser: parser::print_account_setting
|
||||
},
|
||||
{ tags: ["print "],
|
||||
desc: "print SETTING",
|
||||
tokens: &[One(Literal("print")), One(QuotedStringValue)],
|
||||
parser: parser::print_setting
|
||||
},
|
||||
{ tags: ["toggle mouse"],
|
||||
desc: "toggle mouse support",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("mouse"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["manage-mailboxes"],
|
||||
desc: "view and manage mailbox preferences",
|
||||
tokens: &[One(Literal("manage-mailboxes"))],
|
||||
parser: parser::manage_mailboxes
|
||||
},
|
||||
{ tags: ["manage-jobs"],
|
||||
desc: "view and manage jobs",
|
||||
tokens: &[One(Literal("manage-jobs"))],
|
||||
parser: parser::manage_jobs
|
||||
},
|
||||
{ tags: ["quit"],
|
||||
desc: "quit meli",
|
||||
tokens: &[One(Literal("quit"))],
|
||||
parser: parser::quit
|
||||
},
|
||||
{ tags: ["reload-config"],
|
||||
desc: "reload configuration file",
|
||||
tokens: &[One(Literal("reload-config"))],
|
||||
parser: parser::reload_config
|
||||
}
|
||||
]);
|
||||
|
||||
/// Get command suggestions for input
|
||||
pub fn command_completion_suggestions(input: &str) -> Vec<String> {
|
||||
use crate::melib::ShellExpandTrait;
|
||||
let mut sugg: HashSet<String> = Default::default();
|
||||
for (_tags, _desc, tokens, _) in COMMAND_COMPLETION.iter() {
|
||||
let _m = tokens.matches(&mut &(*input), &mut sugg);
|
||||
if _m.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some((s, Filepath)) = _m.last() {
|
||||
let p = std::path::Path::new(s);
|
||||
sugg.extend(p.complete(true).into_iter());
|
||||
}
|
||||
}
|
||||
sugg.into_iter()
|
||||
.map(|s| format!("{}{}", input, s.as_str()))
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_command_parser() {
|
||||
let mut input = "sort".to_string();
|
||||
macro_rules! match_input {
|
||||
($input:expr) => {{
|
||||
let mut sugg: HashSet<String> = Default::default();
|
||||
//print!("{}", $input);
|
||||
for (_tags, _desc, tokens, _) in COMMAND_COMPLETION.iter() {
|
||||
// //println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let _ = tokens.matches(&mut $input.as_str(), &mut sugg);
|
||||
// if !m.is_empty() {
|
||||
// //print!("{:?} ", desc);
|
||||
// //println!(" result = {:#?}\n\n", m);
|
||||
// }
|
||||
}
|
||||
//println!("suggestions = {:#?}", sugg);
|
||||
sugg.into_iter()
|
||||
.map(|s| format!("{}{}", $input.as_str(), s.as_str()))
|
||||
.collect::<HashSet<String>>()
|
||||
}};
|
||||
}
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort date".to_string(), "sort subject".to_string()])
|
||||
.collect(),
|
||||
);
|
||||
input = "so".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort".to_string()]).collect(),
|
||||
);
|
||||
input = "so ".to_string();
|
||||
assert_eq!(&match_input!(input), &HashSet::default(),);
|
||||
input = "to".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["toggle".to_string()]).collect(),
|
||||
);
|
||||
input = "toggle ".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter([
|
||||
"toggle mouse".to_string(),
|
||||
"toggle sign".to_string(),
|
||||
"toggle encrypt".to_string(),
|
||||
"toggle thread_snooze".to_string()
|
||||
])
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_parser_interactive() {
|
||||
use std::io;
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
input.clear();
|
||||
print!("> ");
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_n) => {
|
||||
println!("Input is {:?}", input.as_str().trim());
|
||||
let mut sugg: HashSet<String> = Default::default();
|
||||
let mut vec = vec![];
|
||||
//print!("{}", input);
|
||||
for (_tags, _desc, tokens, _) in COMMAND_COMPLETION.iter() {
|
||||
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let m = tokens.matches(&mut input.as_str().trim(), &mut sugg);
|
||||
if !m.is_empty() {
|
||||
vec.push(tokens);
|
||||
//print!("{:?} ", desc);
|
||||
//println!(" result = {:#?}\n\n", m);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"suggestions = {:#?}",
|
||||
sugg.into_iter()
|
||||
.zip(vec.into_iter())
|
||||
.map(|(s, v)| format!(
|
||||
"{}{} {:?}",
|
||||
input.as_str().trim(),
|
||||
if input.trim().is_empty() {
|
||||
s.trim()
|
||||
} else {
|
||||
s.as_str()
|
||||
},
|
||||
v
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
if input.trim() == "quit" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(error) => println!("error: {}", error),
|
||||
}
|
||||
}
|
||||
println!("alright");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_parser_all() {
|
||||
use CommandError::*;
|
||||
|
||||
for cmd in [
|
||||
"set unseen",
|
||||
"set seen",
|
||||
"delete",
|
||||
"copyto somewhere",
|
||||
"moveto somewhere",
|
||||
"import fpath mpath",
|
||||
"close ",
|
||||
"go 5",
|
||||
] {
|
||||
parse_command(cmd.as_bytes()).unwrap_or_else(|err| panic!("{} failed {}", cmd, err));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_command(b"setfafsfoo").unwrap_err().to_string(),
|
||||
Parsing {
|
||||
inner: "setfafsfoo".into(),
|
||||
kind: "".into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_command(b"set foo").unwrap_err().to_string(),
|
||||
BadValue {
|
||||
inner: "Bad argument for `set`. Accepted arguments are [seen, unseen, plain, \
|
||||
threaded, compact, conversations]."
|
||||
.into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_command(b"moveto ").unwrap_err().to_string(),
|
||||
WrongNumberOfArguments {
|
||||
too_many: false,
|
||||
takes: (1, Some(1)),
|
||||
given: 0,
|
||||
__func__: "moveto",
|
||||
inner: "".into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_command(b"reindex 1 2 3").unwrap_err().to_string(),
|
||||
WrongNumberOfArguments {
|
||||
too_many: true,
|
||||
takes: (1, Some(1)),
|
||||
given: 2,
|
||||
__func__: "reindex",
|
||||
inner: "".into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -19,16 +19,19 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* User actions that need to be handled by the UI
|
||||
*/
|
||||
//! User actions that need to be handled by the UI
|
||||
|
||||
use crate::components::Component;
|
||||
pub use melib::thread::{SortField, SortOrder};
|
||||
use std::path::PathBuf;
|
||||
|
||||
extern crate uuid;
|
||||
use uuid::Uuid;
|
||||
use melib::{email::mailto::Mailto, Flag, SortField, SortOrder};
|
||||
|
||||
use crate::components::{Component, ComponentId};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FlagAction {
|
||||
Set(Flag),
|
||||
Unset(Flag),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TagAction {
|
||||
|
@ -51,18 +54,22 @@ pub enum ListingAction {
|
|||
MoveTo(MailboxPath),
|
||||
MoveToOtherAccount(AccountName, MailboxPath),
|
||||
Import(PathBuf, MailboxPath),
|
||||
ExportMbox(Option<melib::backends::mbox::MboxFormat>, PathBuf),
|
||||
ExportMbox(Option<melib::mbox::MboxFormat>, PathBuf),
|
||||
Delete,
|
||||
OpenInNewTab,
|
||||
Tag(TagAction),
|
||||
Flag(FlagAction),
|
||||
ClearSelection,
|
||||
ToggleThreadSnooze,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TabAction {
|
||||
Close,
|
||||
Kill(Uuid),
|
||||
Kill(ComponentId),
|
||||
New(Option<Box<dyn Component>>),
|
||||
ManageMailboxes,
|
||||
ManageJobs,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -75,8 +82,10 @@ pub enum MailingListAction {
|
|||
#[derive(Debug)]
|
||||
pub enum ViewAction {
|
||||
Pipe(String, Vec<String>),
|
||||
Filter(String),
|
||||
SaveAttachment(usize, String),
|
||||
ExportMail(String),
|
||||
AddAddressesToContacts,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -88,6 +97,7 @@ pub enum ComposeAction {
|
|||
SaveDraft,
|
||||
ToggleSign,
|
||||
ToggleEncrypt,
|
||||
Mailto(Mailto),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -112,12 +122,15 @@ pub enum Action {
|
|||
Listing(ListingAction),
|
||||
ViewMailbox(usize),
|
||||
Sort(SortField, SortOrder),
|
||||
SortColumn(usize, SortOrder),
|
||||
SubSort(SortField, SortOrder),
|
||||
Tab(TabAction),
|
||||
MailingListAction(MailingListAction),
|
||||
View(ViewAction),
|
||||
SetEnv(String, String),
|
||||
PrintEnv(String),
|
||||
CurrentDirectory,
|
||||
ChangeCurrentDirectory(PathBuf),
|
||||
Compose(ComposeAction),
|
||||
Mailbox(AccountName, MailboxOperation),
|
||||
AccountAction(AccountName, AccountAction),
|
||||
|
@ -129,27 +142,51 @@ pub enum Action {
|
|||
|
||||
impl Action {
|
||||
pub fn needs_confirmation(&self) -> bool {
|
||||
match self {
|
||||
Action::Listing(_) => false,
|
||||
Action::ViewMailbox(_) => false,
|
||||
Action::Sort(_, _) => false,
|
||||
Action::SubSort(_, _) => false,
|
||||
Action::Tab(_) => false,
|
||||
Action::MailingListAction(_) => true,
|
||||
Action::View(_) => false,
|
||||
Action::SetEnv(_, _) => false,
|
||||
Action::PrintEnv(_) => false,
|
||||
Action::Compose(_) => false,
|
||||
Action::Mailbox(_, _) => true,
|
||||
Action::AccountAction(_, _) => false,
|
||||
Action::PrintSetting(_) => false,
|
||||
Action::ToggleMouse => false,
|
||||
Action::Quit => true,
|
||||
Action::ReloadConfiguration => false,
|
||||
}
|
||||
matches!(
|
||||
self,
|
||||
Self::Listing(ListingAction::Delete)
|
||||
| Self::MailingListAction(_)
|
||||
| Self::Mailbox(_, _)
|
||||
| Self::Quit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type AccountName = String;
|
||||
type MailboxPath = String;
|
||||
type NewMailboxPath = String;
|
||||
|
||||
macro_rules! impl_into_action {
|
||||
($({$t:ty => $var:tt}),*) => {
|
||||
$(
|
||||
impl From<$t> for Action {
|
||||
fn from(v: $t) -> Self {
|
||||
Self::$var(v)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
macro_rules! impl_tuple_into_action {
|
||||
($({$a:ty,$b:ty => $var:tt}),*) => {
|
||||
$(
|
||||
impl From<($a,$b)> for Action {
|
||||
fn from((a, b): ($a,$b)) -> Self {
|
||||
Self::$var(a, b)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_action!(
|
||||
{ ListingAction => Listing },
|
||||
{ TabAction => Tab },
|
||||
{ MailingListAction => MailingListAction },
|
||||
{ ViewAction => View },
|
||||
{ ComposeAction => Compose }
|
||||
);
|
||||
impl_tuple_into_action!(
|
||||
{ AccountName, MailboxOperation => Mailbox },
|
||||
{ AccountName, AccountAction => AccountAction }
|
||||
);
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Helper type for showing the exact reason why a command was invalid.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub enum ArgCheck<const MIN: u8, const MAX: u8> {
|
||||
Start { __func__: &'static str },
|
||||
BeforeArgument { so_far: u8, __func__: &'static str },
|
||||
Eof { so_far: u8, __func__: &'static str },
|
||||
}
|
||||
|
||||
impl<const MIN: u8, const MAX: u8> ArgCheck<MIN, MAX> {
|
||||
#[inline]
|
||||
pub fn new(__func__: &'static str) -> Self {
|
||||
Self::Start { __func__ }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start(&mut self, input: &[u8]) -> Result<(), CommandError> {
|
||||
let Self::Start { __func__ } = *self else {
|
||||
unreachable!(
|
||||
"ArgCheck::start called with invalid variant: {}",
|
||||
if matches!(self, Self::BeforeArgument { .. }) {
|
||||
"BeforeArgument"
|
||||
} else {
|
||||
"Eof"
|
||||
}
|
||||
);
|
||||
};
|
||||
let is_empty = input.trim().is_empty();
|
||||
if is_empty && MIN > 0 {
|
||||
return Err(CommandError::WrongNumberOfArguments {
|
||||
too_many: false,
|
||||
takes: (MIN, MAX.into()),
|
||||
given: 0,
|
||||
__func__,
|
||||
inner: format!(
|
||||
"needs {}{} arguments.",
|
||||
if MIN == MAX { "at least " } else { "" },
|
||||
MIN
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
*self = Self::BeforeArgument {
|
||||
so_far: 0,
|
||||
__func__,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inc(&mut self, input: &[u8]) -> Result<(), CommandError> {
|
||||
let Self::BeforeArgument { __func__, so_far } = *self else {
|
||||
unreachable!(
|
||||
"ArgCheck::inc called with invalid variant: {}",
|
||||
if matches!(self, Self::Start { .. }) {
|
||||
"Start"
|
||||
} else {
|
||||
"Eof"
|
||||
}
|
||||
);
|
||||
};
|
||||
let is_empty = input.trim().is_empty();
|
||||
let new_value = so_far + 1;
|
||||
if is_empty && new_value > MAX {
|
||||
return Err(CommandError::WrongNumberOfArguments {
|
||||
too_many: true,
|
||||
takes: (MIN, MAX.into()),
|
||||
given: new_value,
|
||||
__func__,
|
||||
inner: format!(
|
||||
"needs {}{} arguments.",
|
||||
if MIN == MAX { "at least " } else { "" },
|
||||
MIN
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
*self = Self::BeforeArgument {
|
||||
so_far: new_value,
|
||||
__func__,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn finish(&mut self, input: &[u8]) -> Result<(), CommandError> {
|
||||
let Self::BeforeArgument { __func__, so_far } = *self else {
|
||||
unreachable!(
|
||||
"ArgCheck::finish called with invalid variant: {}",
|
||||
if matches!(self, Self::Start { .. }) {
|
||||
"Start"
|
||||
} else {
|
||||
"Eof"
|
||||
}
|
||||
);
|
||||
};
|
||||
let is_empty = input.trim().is_empty();
|
||||
if !is_empty {
|
||||
assert!(so_far <= MAX);
|
||||
assert!(so_far >= MIN);
|
||||
return Err(CommandError::WrongNumberOfArguments {
|
||||
too_many: true,
|
||||
takes: (MIN, MAX.into()),
|
||||
given: so_far + 1,
|
||||
__func__,
|
||||
inner: format!(
|
||||
"needs {}{} arguments.",
|
||||
if MIN == MAX { "at least " } else { "" },
|
||||
MIN
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
*self = Self::Eof { so_far, __func__ };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! arg_init {
|
||||
(min_arg: $n:expr, max_arg: $x:expr, $func:tt) => {{
|
||||
ArgCheck::<$n, $x>::new(stringify!($func))
|
||||
}};
|
||||
}
|
||||
|
||||
//macro_rules! arg_value_check {
|
||||
// ($tag:literal, $input:expr) => {{
|
||||
// if tag::<&'_ str, &'_ [u8],
|
||||
// melib::nom::error::Error<&[u8]>>($tag)($input).is_err() { return
|
||||
// Ok(( $input,
|
||||
// Err(CommandError::BadValue {
|
||||
// inner: $tag.to_string().into(),
|
||||
// }),
|
||||
// ));
|
||||
// }
|
||||
// tag($tag)($input)
|
||||
// }};
|
||||
//}
|
||||
|
||||
macro_rules! arg_chk {
|
||||
(start $check:ident, $input:expr) => {{
|
||||
if let Err(err) = $check.start($input) {
|
||||
return Ok(($input, Err(err)));
|
||||
};
|
||||
}};
|
||||
(inc $check:ident, $input:expr) => {{
|
||||
if let Err(err) = $check.inc($input) {
|
||||
return Ok(($input, Err(err)));
|
||||
};
|
||||
}};
|
||||
(finish $check:ident, $input:expr) => {{
|
||||
if let Err(err) = $check.finish($input) {
|
||||
return Ok(($input, Err(err)));
|
||||
};
|
||||
}};
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CommandError {
|
||||
Parsing {
|
||||
inner: Cow<'static, str>,
|
||||
kind: Cow<'static, str>,
|
||||
},
|
||||
BadValue {
|
||||
inner: Cow<'static, str>,
|
||||
},
|
||||
WrongNumberOfArguments {
|
||||
too_many: bool,
|
||||
takes: (u8, Option<u8>),
|
||||
given: u8,
|
||||
__func__: &'static str,
|
||||
inner: Cow<'static, str>,
|
||||
},
|
||||
Other {
|
||||
inner: Cow<'static, str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<nom::Err<melib::nom::error::Error<&'a [u8]>>> for CommandError {
|
||||
fn from(res: nom::Err<melib::nom::error::Error<&'a [u8]>>) -> Self {
|
||||
match res {
|
||||
nom::Err::Incomplete(_) => Self::Parsing {
|
||||
inner: res.to_string().into(),
|
||||
kind: "".into(),
|
||||
},
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => Self::Parsing {
|
||||
inner: String::from_utf8_lossy(e.input).to_string().into(),
|
||||
kind: format!("{:?}", e.code).into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CommandError {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Parsing { inner, kind: _ } => {
|
||||
write!(fmt, "Could not parse command: {}", inner)
|
||||
}
|
||||
Self::BadValue { inner } => {
|
||||
write!(fmt, "Bad value/argument: {}", inner)
|
||||
}
|
||||
Self::WrongNumberOfArguments {
|
||||
too_many,
|
||||
takes,
|
||||
given,
|
||||
__func__,
|
||||
inner: _,
|
||||
} => {
|
||||
if *too_many {
|
||||
match takes {
|
||||
(min, None) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Too many arguments. Command takes {} arguments, but {} were \
|
||||
given.",
|
||||
__func__, min, given
|
||||
)
|
||||
}
|
||||
(min, Some(max)) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Too many arguments. Command takes from {} to {} arguments, \
|
||||
but {} were given.",
|
||||
__func__, min, max, given
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match takes {
|
||||
(min, None) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Not enough arguments. Command takes {} arguments, but {} \
|
||||
were given.",
|
||||
__func__, min, given
|
||||
)
|
||||
}
|
||||
(min, Some(max)) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Not enough arguments. Command takes from {} to {} arguments, \
|
||||
but {} were given.",
|
||||
__func__, min, max, given
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Other { inner } => {
|
||||
write!(fmt, "Error: {}", inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CommandError {}
|
|
@ -19,16 +19,20 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Read, Write},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
thread_local!(static CMD_HISTORY_FILE: Arc<Mutex<std::fs::File>> = Arc::new(Mutex::new({
|
||||
let data_dir = xdg::BaseDirectories::with_prefix("meli").unwrap();
|
||||
OpenOptions::new().append(true) /* writes will append to a file instead of overwriting previous contents */
|
||||
.create(true) /* a new file will be created if the file does not yet already exist.*/
|
||||
.read(true)
|
||||
.open(data_dir.place_data_file("cmd_history").unwrap()).unwrap()
|
||||
OpenOptions::new()
|
||||
.append(true) /* writes will append to a file instead of overwriting previous contents */
|
||||
.create(true) /* a new file will be created if the file does not yet already exist. */
|
||||
.read(true)
|
||||
.open(data_dir.place_data_file("cmd_history").unwrap())
|
||||
.unwrap()
|
||||
})));
|
||||
|
||||
pub fn log_cmd(mut cmd: String) {
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Components visual and logical separations of application interfaces.
|
||||
use indexmap::IndexMap;
|
||||
///
|
||||
/// They can draw on the terminal and receive events, but also do other stuff
|
||||
/// as well.
|
||||
/// For an example, see the [`notifications`] module.
|
||||
/// See also the [`Component`] trait for more details.
|
||||
use smallvec::SmallVec;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[repr(transparent)]
|
||||
pub struct ComponentId(Uuid);
|
||||
|
||||
impl AsRef<Uuid> for ComponentId {
|
||||
fn as_ref(&self) -> &Uuid {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ComponentId {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0.as_simple(), fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ComponentId {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0.as_simple(), fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentId {
|
||||
fn default() -> Self {
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::LowerHex for ComponentId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::LowerHex::fmt(self.0.as_hyphenated(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::UpperHex for ComponentId {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::UpperHex::fmt(self.0.as_hyphenated(), f)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ShortcutMap = IndexMap<&'static str, Key>;
|
||||
pub type ShortcutMaps = IndexMap<&'static str, ShortcutMap>;
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
impl private::Sealed for ShortcutMaps {}
|
||||
|
||||
pub trait ExtendShortcutsMaps: private::Sealed {
|
||||
fn extend_shortcuts(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl ExtendShortcutsMaps for ShortcutMaps {
|
||||
fn extend_shortcuts(&mut self, mut other: Self) {
|
||||
other.retain(|k, v| {
|
||||
if let Some(m) = self.get_mut(k) {
|
||||
m.extend(v.iter().map(|(k, v)| (*k, v.clone())));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
self.extend(other);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PageMovement {
|
||||
Up(usize),
|
||||
Right(usize),
|
||||
Left(usize),
|
||||
Down(usize),
|
||||
PageUp(usize),
|
||||
PageDown(usize),
|
||||
Home,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ScrollContext {
|
||||
pub shown_lines: usize,
|
||||
pub total_lines: usize,
|
||||
pub has_more_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ScrollUpdate {
|
||||
End(ComponentId),
|
||||
Update {
|
||||
id: ComponentId,
|
||||
context: ScrollContext,
|
||||
},
|
||||
}
|
||||
|
||||
/// Types implementing this Trait can draw on the terminal and receive events.
|
||||
/// If a type wants to skip drawing if it has not changed anything, it can hold
|
||||
/// some flag in its fields (eg `self.dirty = false`) and act upon that in their
|
||||
/// [`draw`](Component::draw) implementation.
|
||||
pub trait Component: std::fmt::Display + std::fmt::Debug + Send + Sync {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool;
|
||||
fn is_dirty(&self) -> bool;
|
||||
/// If the component is meant to be currently visible to the user.
|
||||
fn is_visible(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// If the component can quit right away without any unsaved or ongoing
|
||||
/// operations.
|
||||
fn can_quit_cleanly(&mut self, _context: &Context) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool);
|
||||
|
||||
fn kill(&mut self, _id: ComponentId, _context: &mut Context) {}
|
||||
|
||||
fn id(&self) -> ComponentId;
|
||||
|
||||
fn shortcuts(&self, _context: &Context) -> ShortcutMaps {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Get status message for the status line.
|
||||
fn status(&self, _context: &Context) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
&ComponentAttr::DEFAULT
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
// log::trace!("Realizing id {} w/ parent {:?}", self.id(), &parent);
|
||||
context.realized.insert(self.id(), parent);
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
// log::trace!("Unrealizing id {}", self.id());
|
||||
context.unrealized.insert(self.id());
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::ComponentUnrealize(self.id()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Box<dyn Component> {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
(**self).draw(grid, area, context)
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
(**self).process_event(event, context)
|
||||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
(**self).is_dirty()
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
(**self).is_visible()
|
||||
}
|
||||
|
||||
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||
(**self).can_quit_cleanly(context)
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
(**self).set_dirty(value)
|
||||
}
|
||||
|
||||
fn kill(&mut self, id: ComponentId, context: &mut Context) {
|
||||
(**self).kill(id, context)
|
||||
}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
(**self).id()
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
(**self).shortcuts(context)
|
||||
}
|
||||
|
||||
fn status(&self, context: &Context) -> String {
|
||||
(**self).status(context)
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
(**self).attributes()
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
(**self).children()
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
(**self).children_mut()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
(**self).realize(parent, context)
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
(**self).unrealize(context)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Attributes of a [`Component`] widget.
|
||||
///
|
||||
/// `ComponentAttr::DEFAULT` represents no attribute.
|
||||
pub struct ComponentAttr: u8 {
|
||||
/// Nothing special going on.
|
||||
const DEFAULT = 0;
|
||||
const HAS_ANIMATIONS = 1;
|
||||
const CONTAINER = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentAttr {
|
||||
fn default() -> Self {
|
||||
Self::DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ComponentPath {
|
||||
pub id: ComponentId,
|
||||
pub tail: SmallVec<[ComponentId; 8]>,
|
||||
}
|
||||
|
||||
impl ComponentPath {
|
||||
pub fn new(id: ComponentId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
tail: SmallVec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_front(&mut self, id: ComponentId) {
|
||||
self.tail.insert(0, self.id);
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
pub fn push_back(&mut self, id: ComponentId) {
|
||||
self.tail.push(id);
|
||||
}
|
||||
|
||||
pub fn resolve<'c>(&self, root: &'c dyn Component) -> Option<&'c dyn Component> {
|
||||
let mut cursor = root;
|
||||
for id in self.tail.iter().rev().chain(std::iter::once(&self.id)) {
|
||||
// log::trace!("resolve cursor = {} next id is {}", cursor.id(), &id);
|
||||
if *id == cursor.id() {
|
||||
// log::trace!("continue;");
|
||||
continue;
|
||||
}
|
||||
cursor = cursor.children().remove(id)?;
|
||||
}
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parent(&self) -> Option<&ComponentId> {
|
||||
self.tail.first()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root(&self) -> Option<&ComponentId> {
|
||||
self.tail.last()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* meli - conf module
|
||||
*
|
||||
* Copyright 2019 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Configuration for composing email.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use melib::{conf::ActionFlag, email::HeaderName};
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
use super::{
|
||||
default_vals::{ask, false_val, none, true_val},
|
||||
deserializers::non_empty_string,
|
||||
};
|
||||
|
||||
/// Settings for writing and sending new e-mail
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ComposingSettings {
|
||||
/// A command to pipe new emails to
|
||||
/// Required
|
||||
pub send_mail: SendMail,
|
||||
/// Command to launch editor. Can have arguments. Draft filename is given as
|
||||
/// the last argument. If it's missing, the environment variable $EDITOR is
|
||||
/// looked up.
|
||||
#[serde(
|
||||
default = "none",
|
||||
alias = "editor-command",
|
||||
alias = "editor-cmd",
|
||||
alias = "editor_cmd"
|
||||
)]
|
||||
pub editor_command: Option<String>,
|
||||
/// Embedded editor (for terminal interfaces) instead of forking and
|
||||
/// waiting.
|
||||
#[serde(default = "false_val", alias = "embed")]
|
||||
pub embedded_pty: bool,
|
||||
/// Set "format=flowed" in plain text attachments.
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "format-flowed")]
|
||||
pub format_flowed: bool,
|
||||
/// Set User-Agent
|
||||
/// Default: empty
|
||||
#[serde(default = "true_val", alias = "insert_user_agent")]
|
||||
pub insert_user_agent: bool,
|
||||
/// Set default header values for new drafts
|
||||
/// Default: empty
|
||||
#[serde(default, alias = "default-header-values")]
|
||||
pub default_header_values: HashMap<HeaderName, String>,
|
||||
/// Wrap header preamble when editing a draft in an editor. This allows you
|
||||
/// to write non-plain text email without the preamble creating syntax
|
||||
/// errors. They are stripped when you return from the editor. The
|
||||
/// values should be a two element array of strings, a prefix and suffix.
|
||||
/// Default: None
|
||||
#[serde(default, alias = "wrap-header-preamble")]
|
||||
pub wrap_header_preamble: Option<(String, String)>,
|
||||
/// Store sent mail after successful submission. This setting is meant to be
|
||||
/// disabled for non-standard behaviour in gmail, which auto-saves sent
|
||||
/// mail on its own. Default: true
|
||||
#[serde(default = "true_val")]
|
||||
pub store_sent_mail: bool,
|
||||
/// The attribution line appears above the quoted reply text.
|
||||
/// The format specifiers for the replied address are:
|
||||
/// - `%+f` — the sender's name and email address.
|
||||
/// - `%+n` — the sender's name (or email address, if no name is included).
|
||||
/// - `%+a` — the sender's email address.
|
||||
/// The format string is passed to strftime(3) with the replied envelope's
|
||||
/// date. Default: "On %a, %0e %b %Y %H:%M, %+f wrote:%n"
|
||||
#[serde(default = "none")]
|
||||
pub attribution_format_string: Option<String>,
|
||||
/// Whether the strftime call for the attribution string uses the POSIX
|
||||
/// locale instead of the user's active locale
|
||||
/// Default: true
|
||||
#[serde(default = "true_val")]
|
||||
pub attribution_use_posix_locale: bool,
|
||||
/// Forward emails as attachment? (Alternative is inline)
|
||||
/// Default: ask
|
||||
#[serde(default = "ask", alias = "forward-as-attachment")]
|
||||
pub forward_as_attachment: ActionFlag,
|
||||
/// Alternative lists of reply prefixes (etc. ["Re:", "RE:", ...]) to strip
|
||||
/// Default: `["Re:", "RE:", "Fwd:", "Fw:", "回复:", "回覆:", "SV:", "Sv:",
|
||||
/// "VS:", "Antw:", "Doorst:", "VS:", "VL:", "REF:", "TR:", "TR:", "AW:",
|
||||
/// "WG:", "ΑΠ:", "Απ:", "απ:", "ΠΡΘ:", "Πρθ:", "πρθ:", "ΣΧΕΤ:", "Σχετ:",
|
||||
/// "σχετ:", "ΠΡΘ:", "Πρθ:", "πρθ:", "Vá:", "Továbbítás:", "R:", "I:",
|
||||
/// "RIF:", "FS:", "BLS:", "TRS:", "VS:", "VB:", "RV:", "RES:", "Res",
|
||||
/// "ENC:", "Odp:", "PD:", "YNT:", "İLT:", "ATB:", "YML:"]`
|
||||
#[serde(default, alias = "reply-prefix-list-to-strip")]
|
||||
pub reply_prefix_list_to_strip: Option<Vec<String>>,
|
||||
/// The prefix to use in reply subjects. The de facto prefix is "Re:".
|
||||
#[serde(default = "res", alias = "reply-prefix")]
|
||||
pub reply_prefix: String,
|
||||
/// Custom `compose-hooks`.
|
||||
#[serde(default, alias = "custom-compose-hooks")]
|
||||
pub custom_compose_hooks: Vec<ComposeHook>,
|
||||
/// Disabled `compose-hooks`.
|
||||
#[serde(default, alias = "disabled-compose-hooks")]
|
||||
pub disabled_compose_hooks: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for ComposingSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
send_mail: SendMail::ShellCommand("false".into()),
|
||||
editor_command: None,
|
||||
embedded_pty: false,
|
||||
format_flowed: true,
|
||||
insert_user_agent: true,
|
||||
default_header_values: HashMap::default(),
|
||||
store_sent_mail: true,
|
||||
wrap_header_preamble: None,
|
||||
attribution_format_string: None,
|
||||
attribution_use_posix_locale: true,
|
||||
forward_as_attachment: ActionFlag::Ask,
|
||||
reply_prefix_list_to_strip: None,
|
||||
reply_prefix: res(),
|
||||
custom_compose_hooks: vec![],
|
||||
disabled_compose_hooks: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn res() -> String {
|
||||
"Re:".to_string()
|
||||
}
|
||||
|
||||
macro_rules! named_unit_variant {
|
||||
($variant:ident) => {
|
||||
pub mod $variant {
|
||||
pub fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(stringify!($variant))
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct V;
|
||||
impl<'de> serde::de::Visitor<'de> for V {
|
||||
type Value = ();
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str(concat!("\"", stringify!($variant), "\""))
|
||||
}
|
||||
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
if value == stringify!($variant) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(V)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod strings {
|
||||
named_unit_variant!(server_submission);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SendMail {
|
||||
#[cfg(feature = "smtp")]
|
||||
Smtp(melib::smtp::SmtpServerConf),
|
||||
#[serde(with = "strings::server_submission")]
|
||||
ServerSubmission,
|
||||
ShellCommand(String),
|
||||
}
|
||||
|
||||
/// Shell command compose hooks (See
|
||||
/// [`crate::mail::compose::hooks::Hook`])
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ComposeHook {
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
name: String,
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
command: String,
|
||||
}
|
||||
|
||||
impl From<ComposeHook> for crate::mail::hooks::Hook {
|
||||
fn from(c: ComposeHook) -> Self {
|
||||
Self::new_shell_command(c.name.into(), c.command)
|
||||
}
|
||||
}
|
||||
const SENDMAIL_ERR_HELP: &str = r#"Invalid `send_mail` value.
|
||||
|
||||
Here are some valid examples:
|
||||
|
||||
Use server submission in protocols that support it (JMAP, NNTP)
|
||||
===============================================================
|
||||
|
||||
send_mail = "server_submission"
|
||||
|
||||
Using a shell script
|
||||
====================
|
||||
|
||||
send_mail = "msmtp --read-recipients --read-envelope-from"
|
||||
|
||||
Direct SMTP connection
|
||||
======================
|
||||
|
||||
send_mail = { hostname = "mail.example.com", port = 587, auth = { type = "auto", password = { type = "raw", value = "hunter2" } }, security = { type = "STARTTLS" } }
|
||||
|
||||
[composing.send_mail]
|
||||
hostname = "mail.example.com"
|
||||
port = 587
|
||||
auth = { type = "auto", password = { type = "command_eval", value = "/path/to/password_script.sh" } }
|
||||
security = { type = "TLS", danger_accept_invalid_certs = true } }
|
||||
|
||||
|
||||
`send_mail` direct SMTP connection fields:
|
||||
- hostname: text
|
||||
- port: valid port number
|
||||
- envelope_from: text (optional, default is empty),
|
||||
- auth: ...
|
||||
- security: ... (optional, default is "auto")
|
||||
- extensions: ... (optional, default is PIPELINING, CHUNKING, PRDR, 8BITMIME, BINARYMIME, SMTPUTF8, AUTH and DSN_NOTIFY)
|
||||
|
||||
Possible values for `send_mail.auth`:
|
||||
|
||||
No authentication:
|
||||
|
||||
auth = { type = "none" }
|
||||
|
||||
Regular authentication:
|
||||
Note: `require_auth` and `auth_type` are optional and can be skipped.
|
||||
|
||||
auth = { type = "auto", username = "...", password = "...", require_auth = true, auth_type = ... }
|
||||
|
||||
password can be:
|
||||
password = { type = "raw", value = "..." }
|
||||
password = { type = "command_eval", value = "/path/to/password_script.sh" }
|
||||
|
||||
XOAuth2 authentication:
|
||||
Note: `require_auth` is optional and can be skipped.
|
||||
auth = { type = "xoauth2", token_command = "...", require_auth = true }
|
||||
|
||||
Possible values for `send_mail.auth.auth_type` when `auth.type` is "auto":
|
||||
|
||||
auth_type = { plain = false, login = true }
|
||||
|
||||
Possible values for `send_mail.security`:
|
||||
Note that in all cases field `danger_accept_invalid_certs` is optional and its default value is false.
|
||||
|
||||
security = "none"
|
||||
security = { type = "auto", danger_accept_invalid_certs = false }
|
||||
security = { type = "STARTTLS", danger_accept_invalid_certs = false }
|
||||
security = { type = "TLS", danger_accept_invalid_certs = false }
|
||||
|
||||
Possible values for `send_mail.extensions` (All optional and have default values `true`:
|
||||
pipelining
|
||||
chunking
|
||||
8bitmime
|
||||
prdr
|
||||
binarymime
|
||||
smtputf8
|
||||
auth
|
||||
dsn_notify: Array of options e.g. ["FAILURE"]
|
||||
"#;
|
||||
|
||||
impl<'de> Deserialize<'de> for SendMail {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum SendMailInner {
|
||||
#[cfg(feature = "smtp")]
|
||||
Smtp(melib::smtp::SmtpServerConf),
|
||||
#[serde(with = "strings::server_submission")]
|
||||
ServerSubmission,
|
||||
ShellCommand(String),
|
||||
}
|
||||
|
||||
match <SendMailInner>::deserialize(deserializer) {
|
||||
#[cfg(feature = "smtp")]
|
||||
Ok(SendMailInner::Smtp(v)) => Ok(Self::Smtp(v)),
|
||||
Ok(SendMailInner::ServerSubmission) => Ok(Self::ServerSubmission),
|
||||
Ok(SendMailInner::ShellCommand(v)) => Ok(Self::ShellCommand(v)),
|
||||
Err(_err) => Err(de::Error::custom(SENDMAIL_ERR_HELP)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* meli - listing conf module
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use melib::{search::Query, Error, Result, ToggleFlag};
|
||||
|
||||
use super::{default_vals::*, DotAddressable, IndexStyle};
|
||||
|
||||
/// Settings for mail listings
|
||||
///
|
||||
///
|
||||
/// Tree decoration examples:
|
||||
///
|
||||
///```no_run
|
||||
/// const HAS_SIBLING: &str = " ┃";
|
||||
/// const NO_SIBLING: &str = " ";
|
||||
/// const HAS_SIBLING_LEAF: &str = " ┣━";
|
||||
/// const NO_SIBLING_LEAF: &str = " ┗━";
|
||||
/// ```
|
||||
///
|
||||
///```no_run
|
||||
/// const HAS_SIBLING: &str = " |";
|
||||
/// const NO_SIBLING: &str = " ";
|
||||
/// const HAS_SIBLING_LEAF: &str = " |\\_";
|
||||
/// const NO_SIBLING_LEAF: &str = " \\_";
|
||||
/// ```
|
||||
///
|
||||
///```no_run
|
||||
/// const HAS_SIBLING: &str = " ";
|
||||
/// const NO_SIBLING: &str = " ";
|
||||
/// const HAS_SIBLING_LEAF: &str = " ";
|
||||
/// const NO_SIBLING_LEAF: &str = " ";
|
||||
/// ```
|
||||
///
|
||||
///```no_run
|
||||
/// const HAS_SIBLING: &str = " │";
|
||||
/// const NO_SIBLING: &str = " ";
|
||||
/// const HAS_SIBLING_LEAF: &str = " ├─";
|
||||
/// const NO_SIBLING_LEAF: &str = " ╰─";
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ListingSettings {
|
||||
/// Number of context lines when going to next page.
|
||||
/// Default: 0
|
||||
#[serde(default = "zero_val", alias = "context-lines")]
|
||||
pub context_lines: usize,
|
||||
|
||||
/// Show auto-hiding scrollbar in accounts sidebar menu.
|
||||
/// Default: True
|
||||
#[serde(default = "true_val")]
|
||||
pub show_menu_scrollbar: bool,
|
||||
|
||||
/// Datetime formatting passed verbatim to strftime(3).
|
||||
/// Default: %Y-%m-%d %T
|
||||
#[serde(default = "none", alias = "datetime-fmt")]
|
||||
pub datetime_fmt: Option<String>,
|
||||
|
||||
/// Show recent dates as `X {minutes,hours,days} ago`, up to 7 days.
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "recent-dates")]
|
||||
pub recent_dates: bool,
|
||||
|
||||
/// Show only envelopes that match this query
|
||||
/// Default: None
|
||||
#[serde(default = "none")]
|
||||
pub filter: Option<Query>,
|
||||
|
||||
#[serde(default, alias = "index-style")]
|
||||
pub index_style: IndexStyle,
|
||||
|
||||
/// Default: " "
|
||||
#[serde(default = "none")]
|
||||
pub sidebar_mailbox_tree_has_sibling: Option<String>,
|
||||
|
||||
/// Default: " "
|
||||
#[serde(default)]
|
||||
pub sidebar_mailbox_tree_no_sibling: Option<String>,
|
||||
|
||||
/// Default: " "
|
||||
#[serde(default)]
|
||||
pub sidebar_mailbox_tree_has_sibling_leaf: Option<String>,
|
||||
|
||||
/// Default: " "
|
||||
#[serde(default)]
|
||||
pub sidebar_mailbox_tree_no_sibling_leaf: Option<String>,
|
||||
|
||||
/// Default: ' '
|
||||
#[serde(default = "default_divider")]
|
||||
pub sidebar_divider: char,
|
||||
|
||||
/// Default: 90
|
||||
#[serde(default = "default_ratio")]
|
||||
pub sidebar_ratio: usize,
|
||||
|
||||
/// Flag to show if thread entry contains unseen mail.
|
||||
/// Default: "●"
|
||||
#[serde(default)]
|
||||
pub unseen_flag: Option<String>,
|
||||
|
||||
/// Flag to show if thread has been snoozed.
|
||||
/// Default: "💤"
|
||||
#[serde(default)]
|
||||
pub thread_snoozed_flag: Option<String>,
|
||||
|
||||
/// Flag to show if thread entry has been selected.
|
||||
/// Default: "☑️"
|
||||
#[serde(default)]
|
||||
pub selected_flag: Option<String>,
|
||||
|
||||
/// Flag to show if thread entry contains attachments.
|
||||
/// Default: "📎"
|
||||
#[serde(default)]
|
||||
pub attachment_flag: Option<String>,
|
||||
|
||||
/// Flag to show if any thread entry contains your address as a receiver.
|
||||
/// Useful to make mailing list threads that CC you stand out.
|
||||
/// Default: "✸"
|
||||
#[serde(default)]
|
||||
pub highlight_self_flag: Option<String>,
|
||||
|
||||
/// Show `highlight_self_flag` or not.
|
||||
/// Default: false
|
||||
#[serde(default)]
|
||||
pub highlight_self: ToggleFlag,
|
||||
|
||||
/// Should threads with different Subjects show a list of those
|
||||
/// subjects on the entry title?
|
||||
/// Default: "true"
|
||||
#[serde(default = "true_val")]
|
||||
pub thread_subject_pack: bool,
|
||||
|
||||
/// In threaded listing style, repeat identical From column values within a
|
||||
/// thread. Not repeating adds empty space in the From column which
|
||||
/// might result in less visual clutter.
|
||||
/// Default: "false"
|
||||
#[serde(default = "false_val")]
|
||||
pub threaded_repeat_identical_from_values: bool,
|
||||
|
||||
/// Show relative indices in menu mailboxes to quickly help with jumping to
|
||||
/// them. Default: "true"
|
||||
#[serde(default = "true_val", alias = "relative-menu-indices")]
|
||||
pub relative_menu_indices: bool,
|
||||
|
||||
/// Show relative indices in listings to quickly help with jumping to
|
||||
/// them. Default: "true"
|
||||
#[serde(default = "true_val", alias = "relative-list-indices")]
|
||||
pub relative_list_indices: bool,
|
||||
|
||||
/// Hide sidebar on launch. Default: "false"
|
||||
#[serde(default = "false_val", alias = "hide-sidebar-on-launch")]
|
||||
pub hide_sidebar_on_launch: bool,
|
||||
}
|
||||
|
||||
const fn default_divider() -> char {
|
||||
' '
|
||||
}
|
||||
|
||||
const fn default_ratio() -> usize {
|
||||
90
|
||||
}
|
||||
|
||||
impl Default for ListingSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
context_lines: 0,
|
||||
show_menu_scrollbar: true,
|
||||
datetime_fmt: None,
|
||||
recent_dates: true,
|
||||
filter: None,
|
||||
index_style: IndexStyle::default(),
|
||||
sidebar_mailbox_tree_has_sibling: None,
|
||||
sidebar_mailbox_tree_no_sibling: None,
|
||||
sidebar_mailbox_tree_has_sibling_leaf: None,
|
||||
sidebar_mailbox_tree_no_sibling_leaf: None,
|
||||
sidebar_divider: default_divider(),
|
||||
sidebar_ratio: 90,
|
||||
unseen_flag: None,
|
||||
thread_snoozed_flag: None,
|
||||
selected_flag: None,
|
||||
attachment_flag: None,
|
||||
highlight_self_flag: None,
|
||||
highlight_self: ToggleFlag::Unset,
|
||||
thread_subject_pack: true,
|
||||
threaded_repeat_identical_from_values: false,
|
||||
relative_menu_indices: true,
|
||||
relative_list_indices: true,
|
||||
hide_sidebar_on_launch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for ListingSettings {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"context_lines" => self.context_lines.lookup(field, tail),
|
||||
"show_menu_scrollbar" => self.show_menu_scrollbar.lookup(field, tail),
|
||||
"datetime_fmt" => self.datetime_fmt.lookup(field, tail),
|
||||
"recent_dates" => self.recent_dates.lookup(field, tail),
|
||||
"filter" => self.filter.lookup(field, tail),
|
||||
"index_style" => self.index_style.lookup(field, tail),
|
||||
"sidebar_mailbox_tree_has_sibling" => {
|
||||
self.sidebar_mailbox_tree_has_sibling.lookup(field, tail)
|
||||
}
|
||||
"sidebar_mailbox_tree_no_sibling" => {
|
||||
self.sidebar_mailbox_tree_no_sibling.lookup(field, tail)
|
||||
}
|
||||
"sidebar_mailbox_tree_has_sibling_leaf" => self
|
||||
.sidebar_mailbox_tree_has_sibling_leaf
|
||||
.lookup(field, tail),
|
||||
"sidebar_mailbox_tree_no_sibling_leaf" => self
|
||||
.sidebar_mailbox_tree_no_sibling_leaf
|
||||
.lookup(field, tail),
|
||||
"sidebar_divider" => self.sidebar_divider.lookup(field, tail),
|
||||
"sidebar_ratio" => self.sidebar_ratio.lookup(field, tail),
|
||||
"unseen_flag" => self.unseen_flag.lookup(field, tail),
|
||||
"thread_snoozed_flag" => self.thread_snoozed_flag.lookup(field, tail),
|
||||
"selected_flag" => self.selected_flag.lookup(field, tail),
|
||||
"attachment_flag" => self.attachment_flag.lookup(field, tail),
|
||||
"highlight_self_flag" => self.highlight_self_flag.lookup(field, tail),
|
||||
"highlight_self" => self.highlight_self.lookup(field, tail),
|
||||
"thread_subject_pack" => self.thread_subject_pack.lookup(field, tail),
|
||||
"threaded_repeat_identical_from_values" => self
|
||||
.threaded_repeat_identical_from_values
|
||||
.lookup(field, tail),
|
||||
"relative_menu_indices" => self.relative_menu_indices.lookup(field, tail),
|
||||
"relative_list_indices" => self.relative_list_indices.lookup(field, tail),
|
||||
"hide_sidebar_on_launch" => self.hide_sidebar_on_launch.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,29 +19,41 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::default_vals::{internal_value_false, none, true_val};
|
||||
use super::DotAddressable;
|
||||
use melib::{MeliError, Result, ToggleFlag};
|
||||
use melib::{Error, Result, ToggleFlag};
|
||||
|
||||
use super::{
|
||||
default_vals::{internal_value_false, none, true_val},
|
||||
DotAddressable,
|
||||
};
|
||||
|
||||
/// Settings for the notifications function.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NotificationsSettings {
|
||||
/// Enable notifications.
|
||||
/// Default: True
|
||||
#[serde(default = "true_val")]
|
||||
pub enable: bool,
|
||||
/// A command to pipe notifications through
|
||||
|
||||
/// A command to pipe notifications through.
|
||||
/// Default: None
|
||||
#[serde(default = "none")]
|
||||
pub script: Option<String>,
|
||||
/// A file location which has its size changed when new mail arrives (max 128 bytes). Can be
|
||||
/// used to trigger new mail notifications eg with `xbiff(1)`
|
||||
/// Default: None
|
||||
|
||||
/// A command to pipe new mail notifications through (preferred over
|
||||
/// `script`). Default: None
|
||||
#[serde(default = "none")]
|
||||
pub new_mail_script: Option<String>,
|
||||
|
||||
/// A file location which has its size changed when new mail arrives (max
|
||||
/// 128 bytes). Can be used to trigger new mail notifications eg with
|
||||
/// `xbiff(1)`. Default: None
|
||||
#[serde(default = "none", alias = "xbiff-file-path")]
|
||||
pub xbiff_file_path: Option<String>,
|
||||
|
||||
#[serde(default = "internal_value_false", alias = "play-sound")]
|
||||
pub play_sound: ToggleFlag,
|
||||
|
||||
#[serde(default = "none", alias = "sound-file")]
|
||||
pub sound_file: Option<String>,
|
||||
}
|
||||
|
@ -51,6 +63,7 @@ impl Default for NotificationsSettings {
|
|||
Self {
|
||||
enable: true,
|
||||
script: None,
|
||||
new_mail_script: None,
|
||||
xbiff_file_path: None,
|
||||
play_sound: ToggleFlag::InternalVal(false),
|
||||
sound_file: None,
|
||||
|
@ -65,10 +78,11 @@ impl DotAddressable for NotificationsSettings {
|
|||
match *field {
|
||||
"enable" => self.enable.lookup(field, tail),
|
||||
"script" => self.script.lookup(field, tail),
|
||||
"new_mail_script" => self.new_mail_script.lookup(field, tail),
|
||||
"xbiff_file_path" => self.xbiff_file_path.lookup(field, tail),
|
||||
"play_sound" => self.play_sound.lookup(field, tail),
|
||||
"sound_file" => self.sound_file.lookup(field, tail),
|
||||
other => Err(MeliError::new(format!(
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
|
@ -0,0 +1,43 @@
|
|||
// @generated
|
||||
/*
|
||||
* meli - conf/overrides.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![allow(clippy::derivable_impls)]
|
||||
|
||||
//! This module is automatically generated by `config_macros.rs`.
|
||||
|
||||
use super::*;
|
||||
use melib::HeaderName;
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "sticky-headers" , alias = "headers-sticky" , alias = "headers_sticky")] # [serde (default)] pub sticky_headers : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > , # [doc = " Extra headers to display, if present, in the default header preamble."] # [doc = " Default: []"] # [serde (alias = "show-extra-headers")] # [serde (default)] pub show_extra_headers : Option < Vec < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { Self { pager_context : None , pager_stop : None , sticky_headers : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None , show_extra_headers : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ListingSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "context-lines")] # [serde (default)] pub context_lines : Option < usize > , # [doc = " Show auto-hiding scrollbar in accounts sidebar menu."] # [doc = " Default: True"] # [serde (default)] pub show_menu_scrollbar : Option < bool > , # [doc = " Datetime formatting passed verbatim to strftime(3)."] # [doc = " Default: %Y-%m-%d %T"] # [serde (alias = "datetime-fmt")] # [serde (default)] pub datetime_fmt : Option < Option < String > > , # [doc = " Show recent dates as `X {minutes,hours,days} ago`, up to 7 days."] # [doc = " Default: true"] # [serde (alias = "recent-dates")] # [serde (default)] pub recent_dates : Option < bool > , # [doc = " Show only envelopes that match this query"] # [doc = " Default: None"] # [serde (default)] pub filter : Option < Option < Query > > , # [serde (alias = "index-style")] # [serde (default)] pub index_style : Option < IndexStyle > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_has_sibling_leaf : Option < Option < String > > , # [doc = " Default: \" \""] # [serde (default)] pub sidebar_mailbox_tree_no_sibling_leaf : Option < Option < String > > , # [doc = " Default: ' '"] # [serde (default)] pub sidebar_divider : Option < char > , # [doc = " Default: 90"] # [serde (default)] pub sidebar_ratio : Option < usize > , # [doc = " Flag to show if thread entry contains unseen mail."] # [doc = " Default: \"●\""] # [serde (default)] pub unseen_flag : Option < Option < String > > , # [doc = " Flag to show if thread has been snoozed."] # [doc = " Default: \"💤\""] # [serde (default)] pub thread_snoozed_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry has been selected."] # [doc = " Default: \"☑\u{fe0f}\""] # [serde (default)] pub selected_flag : Option < Option < String > > , # [doc = " Flag to show if thread entry contains attachments."] # [doc = " Default: \"📎\""] # [serde (default)] pub attachment_flag : Option < Option < String > > , # [doc = " Flag to show if any thread entry contains your address as a receiver."] # [doc = " Useful to make mailing list threads that CC you stand out."] # [doc = " Default: \"✸\""] # [serde (default)] pub highlight_self_flag : Option < Option < String > > , # [doc = " Show `highlight_self_flag` or not."] # [doc = " Default: false"] # [serde (default)] pub highlight_self : Option < ToggleFlag > , # [doc = " Should threads with different Subjects show a list of those"] # [doc = " subjects on the entry title?"] # [doc = " Default: \"true\""] # [serde (default)] pub thread_subject_pack : Option < bool > , # [doc = " In threaded listing style, repeat identical From column values within a"] # [doc = " thread. Not repeating adds empty space in the From column which"] # [doc = " might result in less visual clutter."] # [doc = " Default: \"false\""] # [serde (default)] pub threaded_repeat_identical_from_values : Option < bool > , # [doc = " Show relative indices in menu mailboxes to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-menu-indices")] # [serde (default)] pub relative_menu_indices : Option < bool > , # [doc = " Show relative indices in listings to quickly help with jumping to"] # [doc = " them. Default: \"true\""] # [serde (alias = "relative-list-indices")] # [serde (default)] pub relative_list_indices : Option < bool > , # [doc = " Hide sidebar on launch. Default: \"false\""] # [serde (alias = "hide-sidebar-on-launch")] # [serde (default)] pub hide_sidebar_on_launch : Option < bool > } impl Default for ListingSettingsOverride { fn default () -> Self { Self { context_lines : None , show_menu_scrollbar : None , datetime_fmt : None , recent_dates : None , filter : None , index_style : None , sidebar_mailbox_tree_has_sibling : None , sidebar_mailbox_tree_no_sibling : None , sidebar_mailbox_tree_has_sibling_leaf : None , sidebar_mailbox_tree_no_sibling_leaf : None , sidebar_divider : None , sidebar_ratio : None , unseen_flag : None , thread_snoozed_flag : None , selected_flag : None , attachment_flag : None , highlight_self_flag : None , highlight_self : None , thread_subject_pack : None , threaded_repeat_identical_from_values : None , relative_menu_indices : None , relative_list_indices : None , hide_sidebar_on_launch : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct NotificationsSettingsOverride { # [doc = " Enable notifications."] # [doc = " Default: True"] # [serde (default)] pub enable : Option < bool > , # [doc = " A command to pipe notifications through."] # [doc = " Default: None"] # [serde (default)] pub script : Option < Option < String > > , # [doc = " A command to pipe new mail notifications through (preferred over"] # [doc = " `script`). Default: None"] # [serde (default)] pub new_mail_script : Option < Option < String > > , # [doc = " A file location which has its size changed when new mail arrives (max"] # [doc = " 128 bytes). Can be used to trigger new mail notifications eg with"] # [doc = " `xbiff(1)`. Default: None"] # [serde (alias = "xbiff-file-path")] # [serde (default)] pub xbiff_file_path : Option < Option < String > > , # [serde (alias = "play-sound")] # [serde (default)] pub play_sound : Option < ToggleFlag > , # [serde (alias = "sound-file")] # [serde (default)] pub sound_file : Option < Option < String > > } impl Default for NotificationsSettingsOverride { fn default () -> Self { Self { enable : None , script : None , new_mail_script : None , xbiff_file_path : None , play_sound : None , sound_file : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ShortcutsOverride { # [serde (default)] pub general : Option < GeneralShortcuts > , # [serde (default)] pub listing : Option < ListingShortcuts > , # [serde (default)] pub composing : Option < ComposingShortcuts > , # [serde (alias = "contact-list")] # [serde (default)] pub contact_list : Option < ContactListShortcuts > , # [serde (alias = "envelope-view")] # [serde (default)] pub envelope_view : Option < EnvelopeViewShortcuts > , # [serde (alias = "thread-view")] # [serde (default)] pub thread_view : Option < ThreadViewShortcuts > , # [serde (default)] pub pager : Option < PagerShortcuts > } impl Default for ShortcutsOverride { fn default () -> Self { Self { general : None , listing : None , composing : None , contact_list : None , envelope_view : None , thread_view : None , pager : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " A command to pipe new emails to"] # [doc = " Required"] # [serde (default)] pub send_mail : Option < SendMail > , # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embedded editor (for terminal interfaces) instead of forking and"] # [doc = " waiting."] # [serde (alias = "embed")] # [serde (default)] pub embedded_pty : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Set User-Agent"] # [doc = " Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < HeaderName , String > > , # [doc = " Wrap header preamble when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preamble")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ActionFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { Self { send_mail : None , editor_command : None , embedded_pty : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct TagsSettingsOverride { # [serde (deserialize_with = "tag_color_de")] # [serde (default)] pub colors : Option < HashMap < TagHash , Color > > , # [serde (deserialize_with = "tag_set_de" , alias = "ignore-tags")] # [serde (default)] pub ignore_tags : Option < HashSet < TagHash > > } impl Default for TagsSettingsOverride { fn default () -> Self { Self { colors : None , ignore_tags : None } } }
|
||||
|
||||
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PGPSettingsOverride { # [doc = " auto verify signed e-mail according to RFC3156"] # [doc = " Default: true"] # [serde (alias = "auto-verify-signatures")] # [serde (default)] pub auto_verify_signatures : Option < ActionFlag > , # [doc = " auto decrypt encrypted e-mail"] # [doc = " Default: true"] # [serde (alias = "auto-decrypt")] # [serde (default)] pub auto_decrypt : Option < ActionFlag > , # [doc = " always sign sent e-mail"] # [doc = " Default: false"] # [serde (alias = "auto-sign")] # [serde (default)] pub auto_sign : Option < ActionFlag > , # [doc = " Auto encrypt sent e-mail"] # [doc = " Default: false"] # [serde (alias = "auto-encrypt")] # [serde (default)] pub auto_encrypt : Option < ActionFlag > , # [doc = " Default: None"] # [serde (alias = "sign-key")] # [serde (default)] pub sign_key : Option < Option < String > > , # [doc = " Default: None"] # [serde (alias = "decrypt-key")] # [serde (default)] pub decrypt_key : Option < Option < String > > , # [doc = " Default: None"] # [serde (alias = "encrypt-key")] # [serde (default)] pub encrypt_key : Option < Option < String > > , # [doc = " Allow remote lookups"] # [doc = " Default: False"] # [serde (alias = "allow-remote-lookups")] # [serde (default)] pub allow_remote_lookup : Option < ActionFlag > , # [doc = " Remote lookup mechanisms."] # [doc = " Default: \"local,wkd\""] # [cfg_attr (feature = "gpgme" , serde (alias = "remote-lookup-mechanisms"))] # [cfg (feature = "gpgme")] # [serde (default)] pub remote_lookup_mechanisms : Option < melib :: gpgme :: LocateKey > , # [cfg (not (feature = "gpgme"))] # [cfg_attr (not (feature = "gpgme") , serde (alias = "remote-lookup-mechanisms"))] # [serde (default)] pub remote_lookup_mechanisms : Option < String > } impl Default for PGPSettingsOverride { fn default () -> Self { Self { auto_verify_signatures : None , auto_decrypt : None , auto_sign : None , auto_encrypt : None , sign_key : None , decrypt_key : None , encrypt_key : None , allow_remote_lookup : None , remote_lookup_mechanisms : None } } }
|
||||
|
|
@ -21,13 +21,12 @@
|
|||
|
||||
//! Settings for the pager function.
|
||||
|
||||
use super::default_vals::*;
|
||||
use super::deserializers::*;
|
||||
use super::DotAddressable;
|
||||
use melib::{MeliError, Result, ToggleFlag};
|
||||
use melib::{Error, Result, ToggleFlag};
|
||||
|
||||
use super::{default_vals::*, deserializers::*, DotAddressable};
|
||||
|
||||
/// Settings for the pager function.
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PagerSettings {
|
||||
/// Number of context lines when going to next page.
|
||||
|
@ -42,8 +41,14 @@ pub struct PagerSettings {
|
|||
|
||||
/// Always show headers when scrolling.
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "headers-sticky")]
|
||||
pub headers_sticky: bool,
|
||||
#[serde(
|
||||
default = "false_val",
|
||||
alias = "sticky-headers",
|
||||
/* deprecated names */
|
||||
alias = "headers-sticky",
|
||||
alias = "headers_sticky"
|
||||
)]
|
||||
pub sticky_headers: bool,
|
||||
|
||||
/// The height of the pager in mail view, in percent.
|
||||
/// Default: 80
|
||||
|
@ -52,14 +57,14 @@ pub struct PagerSettings {
|
|||
|
||||
/// A command to pipe mail output through for viewing in pager.
|
||||
/// Default: None
|
||||
#[serde(default = "none", deserialize_with = "non_empty_string")]
|
||||
#[serde(default = "none", deserialize_with = "non_empty_opt_string")]
|
||||
pub filter: Option<String>,
|
||||
|
||||
/// A command to pipe html output before displaying it in a pager
|
||||
/// Default: None
|
||||
#[serde(
|
||||
default = "none",
|
||||
deserialize_with = "non_empty_string",
|
||||
deserialize_with = "non_empty_opt_string",
|
||||
alias = "html-filter"
|
||||
)]
|
||||
pub html_filter: Option<String>,
|
||||
|
@ -79,14 +84,38 @@ pub struct PagerSettings {
|
|||
#[serde(default = "eighty_val", alias = "minimum-width")]
|
||||
pub minimum_width: usize,
|
||||
|
||||
/// Choose `text/html` alternative if `text/plain` is empty in `multipart/alternative`
|
||||
/// attachments.
|
||||
/// Choose `text/html` alternative if `text/plain` is empty in
|
||||
/// `multipart/alternative` attachments.
|
||||
/// Default: true
|
||||
#[serde(
|
||||
default = "internal_value_true",
|
||||
alias = "auto-choose-multipart-alternative"
|
||||
)]
|
||||
pub auto_choose_multipart_alternative: ToggleFlag,
|
||||
|
||||
/// Show Date: in my timezone
|
||||
/// Default: true
|
||||
#[serde(default = "internal_value_true", alias = "show-date-in-my-timezone")]
|
||||
pub show_date_in_my_timezone: ToggleFlag,
|
||||
|
||||
/// A command to launch URLs with. The URL will be given as the first
|
||||
/// argument of the command. Default: None
|
||||
#[serde(default = "none", deserialize_with = "non_empty_opt_string")]
|
||||
pub url_launcher: Option<String>,
|
||||
|
||||
/// A command to open html files.
|
||||
/// Default: None
|
||||
#[serde(
|
||||
default = "none",
|
||||
deserialize_with = "non_empty_opt_string",
|
||||
alias = "html-open"
|
||||
)]
|
||||
pub html_open: Option<String>,
|
||||
|
||||
/// Extra headers to display, if present, in the default header preamble.
|
||||
/// Default: []
|
||||
#[serde(default = "Vec::new", alias = "show-extra-headers")]
|
||||
pub show_extra_headers: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for PagerSettings {
|
||||
|
@ -94,14 +123,18 @@ impl Default for PagerSettings {
|
|||
Self {
|
||||
pager_context: 0,
|
||||
pager_stop: false,
|
||||
headers_sticky: true,
|
||||
sticky_headers: false,
|
||||
pager_ratio: 80,
|
||||
filter: None,
|
||||
html_filter: None,
|
||||
html_open: None,
|
||||
format_flowed: true,
|
||||
split_long_lines: true,
|
||||
minimum_width: 80,
|
||||
auto_choose_multipart_alternative: ToggleFlag::InternalVal(true),
|
||||
show_date_in_my_timezone: ToggleFlag::InternalVal(true),
|
||||
url_launcher: None,
|
||||
show_extra_headers: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,17 +147,21 @@ impl DotAddressable for PagerSettings {
|
|||
match *field {
|
||||
"pager_context" => self.pager_context.lookup(field, tail),
|
||||
"pager_stop" => self.pager_stop.lookup(field, tail),
|
||||
"headers_sticky" => self.headers_sticky.lookup(field, tail),
|
||||
"sticky_headers" => self.sticky_headers.lookup(field, tail),
|
||||
"pager_ratio" => self.pager_ratio.lookup(field, tail),
|
||||
"filter" => self.filter.lookup(field, tail),
|
||||
"html_filter" => self.html_filter.lookup(field, tail),
|
||||
"html_open" => self.html_open.lookup(field, tail),
|
||||
"format_flowed" => self.format_flowed.lookup(field, tail),
|
||||
"split_long_lines" => self.split_long_lines.lookup(field, tail),
|
||||
"minimum_width" => self.minimum_width.lookup(field, tail),
|
||||
"auto_choose_multipart_alternative" => {
|
||||
self.auto_choose_multipart_alternative.lookup(field, tail)
|
||||
}
|
||||
other => Err(MeliError::new(format!(
|
||||
"show_date_in_my_timezone" => self.show_date_in_my_timezone.lookup(field, tail),
|
||||
"url_launcher" => self.html_filter.lookup(field, tail),
|
||||
"show_extra_headers" => self.show_extra_headers.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
|
@ -19,35 +19,33 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
use super::default_vals::*;
|
||||
#[cfg(feature = "gpgme")]
|
||||
use melib::conf::ToggleFlag;
|
||||
use melib::conf::ActionFlag;
|
||||
|
||||
use super::default_vals::*;
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
/// Settings for digital signing and encryption
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PGPSettings {
|
||||
/// auto verify signed e-mail according to RFC3156
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "auto-verify-signatures")]
|
||||
pub auto_verify_signatures: bool,
|
||||
pub auto_verify_signatures: ActionFlag,
|
||||
|
||||
/// auto decrypt encrypted e-mail
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "auto-decrypt")]
|
||||
pub auto_decrypt: bool,
|
||||
pub auto_decrypt: ActionFlag,
|
||||
|
||||
/// always sign sent e-mail
|
||||
/// Default: false
|
||||
#[serde(default = "false_val", alias = "auto-sign")]
|
||||
pub auto_sign: bool,
|
||||
pub auto_sign: ActionFlag,
|
||||
|
||||
/// Auto encrypt sent e-mail
|
||||
/// Default: false
|
||||
#[serde(default = "false_val", alias = "auto-encrypt")]
|
||||
pub auto_encrypt: bool,
|
||||
pub auto_encrypt: ActionFlag,
|
||||
|
||||
// https://tools.ietf.org/html/rfc4880#section-12.2
|
||||
/// Default: None
|
||||
|
@ -63,17 +61,30 @@ pub struct PGPSettings {
|
|||
pub encrypt_key: Option<String>,
|
||||
|
||||
/// Allow remote lookups
|
||||
/// Default: None
|
||||
#[serde(default = "internal_value_false", alias = "allow-remote-lookups")]
|
||||
pub allow_remote_lookup: ToggleFlag,
|
||||
/// Default: False
|
||||
#[serde(
|
||||
default = "action_internal_value_false",
|
||||
alias = "allow-remote-lookups"
|
||||
)]
|
||||
pub allow_remote_lookup: ActionFlag,
|
||||
|
||||
/// Remote lookup mechanisms.
|
||||
/// Default: "local,wkd"
|
||||
#[serde(
|
||||
default = "default_lookup_mechanism",
|
||||
alias = "remote-lookup-mechanisms"
|
||||
#[cfg_attr(
|
||||
feature = "gpgme",
|
||||
serde(
|
||||
default = "default_lookup_mechanism",
|
||||
alias = "remote-lookup-mechanisms"
|
||||
)
|
||||
)]
|
||||
#[cfg(feature = "gpgme")]
|
||||
pub remote_lookup_mechanisms: melib::gpgme::LocateKey,
|
||||
#[cfg(not(feature = "gpgme"))]
|
||||
#[cfg_attr(
|
||||
not(feature = "gpgme"),
|
||||
serde(default, alias = "remote-lookup-mechanisms")
|
||||
)]
|
||||
pub remote_lookup_mechanisms: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
|
@ -81,24 +92,21 @@ fn default_lookup_mechanism() -> melib::gpgme::LocateKey {
|
|||
melib::gpgme::LocateKey::LOCAL | melib::gpgme::LocateKey::WKD
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
impl Default for PGPSettings {
|
||||
fn default() -> Self {
|
||||
PGPSettings {
|
||||
auto_verify_signatures: true,
|
||||
auto_decrypt: true,
|
||||
auto_sign: false,
|
||||
auto_encrypt: false,
|
||||
Self {
|
||||
auto_verify_signatures: true.into(),
|
||||
auto_decrypt: true.into(),
|
||||
auto_sign: false.into(),
|
||||
auto_encrypt: false.into(),
|
||||
sign_key: None,
|
||||
decrypt_key: None,
|
||||
encrypt_key: None,
|
||||
allow_remote_lookup: internal_value_false::<ToggleFlag>(),
|
||||
allow_remote_lookup: action_internal_value_false::<ActionFlag>(),
|
||||
#[cfg(feature = "gpgme")]
|
||||
remote_lookup_mechanisms: default_lookup_mechanism(),
|
||||
#[cfg(not(feature = "gpgme"))]
|
||||
remote_lookup_mechanisms: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "gpgme"))]
|
||||
#[derive(Debug, Default, Deserialize, Clone, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PGPSettings;
|
|
@ -19,10 +19,11 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use melib::{Error, Result};
|
||||
|
||||
use super::DotAddressable;
|
||||
use crate::terminal::Key;
|
||||
use indexmap::IndexMap;
|
||||
use melib::{MeliError, Result};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! shortcut {
|
||||
|
@ -34,7 +35,7 @@ macro_rules! shortcut {
|
|||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Shortcuts {
|
||||
#[serde(default)]
|
||||
|
@ -43,8 +44,6 @@ pub struct Shortcuts {
|
|||
pub listing: ListingShortcuts,
|
||||
#[serde(default)]
|
||||
pub composing: ComposingShortcuts,
|
||||
#[serde(default, alias = "compact-listing")]
|
||||
pub compact_listing: CompactListingShortcuts,
|
||||
#[serde(default, alias = "contact-list")]
|
||||
pub contact_list: ContactListShortcuts,
|
||||
#[serde(default, alias = "envelope-view")]
|
||||
|
@ -55,19 +54,14 @@ pub struct Shortcuts {
|
|||
pub pager: PagerShortcuts,
|
||||
}
|
||||
|
||||
impl Default for Shortcuts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
general: GeneralShortcuts::default(),
|
||||
listing: ListingShortcuts::default(),
|
||||
composing: ComposingShortcuts::default(),
|
||||
compact_listing: CompactListingShortcuts::default(),
|
||||
contact_list: ContactListShortcuts::default(),
|
||||
envelope_view: EnvelopeViewShortcuts::default(),
|
||||
thread_view: ThreadViewShortcuts::default(),
|
||||
pager: PagerShortcuts::default(),
|
||||
}
|
||||
}
|
||||
impl Shortcuts {
|
||||
pub const GENERAL: &'static str = "general";
|
||||
pub const LISTING: &'static str = "listing";
|
||||
pub const COMPOSING: &'static str = "composing";
|
||||
pub const CONTACT_LIST: &'static str = "contact_list";
|
||||
pub const ENVELOPE_VIEW: &'static str = "envelope_view";
|
||||
pub const THREAD_VIEW: &'static str = "thread_view";
|
||||
pub const PAGER: &'static str = "pager";
|
||||
}
|
||||
|
||||
impl DotAddressable for Shortcuts {
|
||||
|
@ -79,14 +73,11 @@ impl DotAddressable for Shortcuts {
|
|||
"general" => self.general.lookup(field, tail),
|
||||
"listing" => self.listing.lookup(field, tail),
|
||||
"composing" => self.composing.lookup(field, tail),
|
||||
"compact_listing" | "compact-listing" => {
|
||||
self.compact_listing.lookup(field, tail)
|
||||
}
|
||||
"contact_list" | "contact-list" => self.contact_list.lookup(field, tail),
|
||||
"envelope_view" | "envelope-view" => self.envelope_view.lookup(field, tail),
|
||||
"thread_view" | "thread-view" => self.thread_view.lookup(field, tail),
|
||||
"pager" => self.pager.lookup(field, tail),
|
||||
other => Err(MeliError::new(format!(
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
|
@ -97,6 +88,12 @@ impl DotAddressable for Shortcuts {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommandShortcut {
|
||||
pub shortcut: Key,
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
/// Create a struct holding all of a Component's shortcuts.
|
||||
#[macro_export]
|
||||
macro_rules! shortcut_key_values {
|
||||
|
@ -109,6 +106,7 @@ macro_rules! shortcut_key_values {
|
|||
#[serde(default)]
|
||||
#[serde(rename = $cname)]
|
||||
pub struct $name {
|
||||
pub commands: Vec<CommandShortcut>,
|
||||
$(pub $fname : Key),*
|
||||
}
|
||||
|
||||
|
@ -131,6 +129,7 @@ macro_rules! shortcut_key_values {
|
|||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
commands : vec![],
|
||||
$($fname: $default),*
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +142,7 @@ macro_rules! shortcut_key_values {
|
|||
let tail = &path[1..];
|
||||
match *field {
|
||||
$(stringify!($fname) => self.$fname.lookup(field, tail),)*
|
||||
other => Err(MeliError::new(format!(
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
|
@ -156,26 +155,19 @@ macro_rules! shortcut_key_values {
|
|||
}
|
||||
}
|
||||
|
||||
shortcut_key_values! { "compact-listing",
|
||||
/// Shortcut listing for a mail listing in compact mode.
|
||||
pub struct CompactListingShortcuts {
|
||||
exit_thread |> "Exit thread view." |> Key::Char('i'),
|
||||
open_thread |> "Open thread." |> Key::Char('\n')
|
||||
}
|
||||
}
|
||||
|
||||
shortcut_key_values! { "listing",
|
||||
/// Shortcut listing for a mail listing.
|
||||
pub struct ListingShortcuts {
|
||||
scroll_up |> "Scroll up list." |> Key::Up,
|
||||
scroll_down |> "Scroll down list." |> Key::Down,
|
||||
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||
new_mail |> "Start new mail draft in new tab." |> Key::Char('m'),
|
||||
next_account |> "Go to next account." |> Key::Char('h'),
|
||||
next_account |> "Go to next account." |> Key::Char('H'),
|
||||
next_mailbox |> "Go to next mailbox." |> Key::Char('J'),
|
||||
next_page |> "Go to next page." |> Key::PageDown,
|
||||
prev_account |> "Go to previous account." |> Key::Char('l'),
|
||||
prev_account |> "Go to previous account." |> Key::Char('L'),
|
||||
prev_mailbox |> "Go to previous mailbox." |> Key::Char('K'),
|
||||
open_mailbox |> "Open selected mailbox" |> Key::Char('\n'),
|
||||
open_mailbox |> "Open selected mailbox." |> Key::Char('\n'),
|
||||
toggle_mailbox_collapse |> "Toggle mailbox collapse in menu." |> Key::Char(' '),
|
||||
prev_page |> "Go to previous page." |> Key::PageUp,
|
||||
search |> "Search within list of e-mails." |> Key::Char('/'),
|
||||
refresh |> "Manually request a mailbox refresh." |> Key::F(5),
|
||||
|
@ -184,20 +176,29 @@ shortcut_key_values! { "listing",
|
|||
diff_modifier |> "Difference modifier." |> Key::Ctrl('d'),
|
||||
intersection_modifier |> "Intersection modifier." |> Key::Ctrl('i'),
|
||||
select_entry |> "Select thread entry." |> Key::Char('v'),
|
||||
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`')
|
||||
increase_sidebar |> "Increase sidebar width." |> Key::Ctrl('f'),
|
||||
decrease_sidebar |> "Decrease sidebar width." |> Key::Ctrl('d'),
|
||||
next_entry |> "Focus on next entry." |> Key::Ctrl('n'),
|
||||
previous_entry |> "Focus on previous entry." |> Key::Ctrl('p'),
|
||||
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`'),
|
||||
focus_left |> "Switch focus on the left." |> Key::Left,
|
||||
focus_right |> "Switch focus on the right." |> Key::Right,
|
||||
exit_entry |> "Exit e-mail entry." |> Key::Char('i'),
|
||||
open_entry |> "Open e-mail entry." |> Key::Char('\n')
|
||||
}
|
||||
}
|
||||
|
||||
shortcut_key_values! { "contact-list",
|
||||
/// Shortcut listing for the contact list view
|
||||
pub struct ContactListShortcuts {
|
||||
scroll_up |> "Scroll up list." |> Key::Up,
|
||||
scroll_down |> "Scroll down list." |> Key::Down,
|
||||
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||
create_contact |> "Create new contact." |> Key::Char('c'),
|
||||
edit_contact |> "Edit contact under cursor." |> Key::Char('e'),
|
||||
delete_contact |> "Delete contact under cursor." |> Key::Char('d'),
|
||||
mail_contact |> "Mail contact under cursor." |> Key::Char('m'),
|
||||
next_account |> "Go to next account." |> Key::Char('h'),
|
||||
prev_account |> "Go to previous account." |> Key::Char('l'),
|
||||
next_account |> "Go to next account." |> Key::Char('H'),
|
||||
prev_account |> "Go to previous account." |> Key::Char('L'),
|
||||
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`')
|
||||
}
|
||||
}
|
||||
|
@ -205,8 +206,8 @@ shortcut_key_values! { "contact-list",
|
|||
shortcut_key_values! { "pager",
|
||||
/// Shortcut listing for the text pager
|
||||
pub struct PagerShortcuts {
|
||||
page_down |> "Go to next pager page" |> Key::PageDown,
|
||||
page_up |> "Go to previous pager page" |> Key::PageUp,
|
||||
page_down |> "Go to next pager page." |> Key::PageDown,
|
||||
page_up |> "Go to previous pager page." |> Key::PageUp,
|
||||
scroll_down |> "Scroll down pager." |> Key::Char('j'),
|
||||
scroll_up |> "Scroll up pager." |> Key::Char('k')
|
||||
}
|
||||
|
@ -217,21 +218,31 @@ shortcut_key_values! { "general",
|
|||
toggle_help |> "Toggle help and shortcuts view." |> Key::Char('?'),
|
||||
enter_command_mode |> "Enter COMMAND mode." |> Key::Char(':'),
|
||||
quit |> "Quit meli." |> Key::Char('q'),
|
||||
go_to_tab |> "Go to the nth tab" |> Key::Alt('n'),
|
||||
next_tab |> "Next tab." |> Key::Char('T'),
|
||||
scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Right,
|
||||
scroll_left |> "Generic scroll left (catch-all setting)" |> Key::Left,
|
||||
scroll_up |> "Generic scroll up (catch-all setting)" |> Key::Up,
|
||||
scroll_down |> "Generic scroll down (catch-all setting)" |> Key::Down
|
||||
go_to_tab |> "Go to the nth tab." |> Key::Alt('n'),
|
||||
next_tab |> "Go to the next tab." |> Key::Char('T'),
|
||||
scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Char('l'),
|
||||
scroll_left |> "Generic scroll left (catch-all setting)" |>Key::Char('h'),
|
||||
scroll_up |> "Generic scroll up (catch-all setting)" |> Key::Char('k'),
|
||||
scroll_down |> "Generic scroll down (catch-all setting)" |> Key::Char('j'),
|
||||
next_page |> "Go to next page. (catch-all setting)" |> Key::PageDown,
|
||||
prev_page |> "Go to previous page. (catch-all setting)" |> Key::PageUp,
|
||||
home_page |> "Go to first page. (catch-all setting)" |> Key::Home,
|
||||
end_page |> "Go to last page. (catch-all setting)" |> Key::End,
|
||||
open_entry |> "Open list entry. (catch-all setting)" |> Key::Char('\n'),
|
||||
info_message_next |> "Show next info message, if any." |> Key::Alt('>'),
|
||||
info_message_previous |> "Show previous info message, if any." |> Key::Alt('<'),
|
||||
focus_in_text_field |> "Focus on a text field." |> Key::Char('\n'),
|
||||
next_search_result |> "Scroll to next search result." |> Key::Char('n'),
|
||||
previous_search_result |> "Scroll to previous search result." |> Key::Char('N')
|
||||
}
|
||||
}
|
||||
|
||||
shortcut_key_values! { "composing",
|
||||
pub struct ComposingShortcuts {
|
||||
edit_mail |> "Edit mail." |> Key::Char('e'),
|
||||
send_mail |> "Deliver draft to mailer" |> Key::Char('s'),
|
||||
scroll_up |> "Change field focus." |> Key::Up,
|
||||
scroll_down |> "Change field focus." |> Key::Down
|
||||
edit |> "Edit." |> Key::Char('e'),
|
||||
send_mail |> "Deliver draft to mailer." |> Key::Char('s'),
|
||||
scroll_up |> "Change field focus." |> Key::Char('k'),
|
||||
scroll_down |> "Change field focus." |> Key::Char('j')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,28 +250,32 @@ shortcut_key_values! { "envelope-view",
|
|||
pub struct EnvelopeViewShortcuts {
|
||||
add_addresses_to_contacts |> "Select addresses from envelope to add to contacts." |> Key::Char('c'),
|
||||
edit |> "Open envelope in composer." |> Key::Char('e'),
|
||||
go_to_url |> "Go to url of given index" |> Key::Char('g'),
|
||||
go_to_url |> "Go to url of given index." |> Key::Char('g'),
|
||||
open_attachment |> "Opens selected attachment with xdg-open." |> Key::Char('a'),
|
||||
open_mailcap |> "Opens selected attachment according to its mailcap entry." |> Key::Char('m'),
|
||||
open_html |> "Opens html attachment in the default browser." |> Key::Char('v'),
|
||||
reply |> "Reply to envelope." |> Key::Char('R'),
|
||||
reply_to_author |> "Reply to author." |> Key::Ctrl('r'),
|
||||
reply_to_all |> "Reply to all/Reply to list/Follow up." |> Key::Ctrl('g'),
|
||||
forward |> "Forward email." |> Key::Ctrl('f'),
|
||||
return_to_normal_view |> "Return to envelope if viewing raw source or attachment." |> Key::Char('r'),
|
||||
toggle_expand_headers |> "Expand extra headers (References and others)." |> Key::Char('h'),
|
||||
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),
|
||||
view_raw_source |> "View envelope source in a pager. (toggles between raw and decoded source)" |> Key::Alt('r')
|
||||
view_raw_source |> "View envelope source in a pager. (toggles between raw and decoded source)" |> Key::Alt('r'),
|
||||
change_charset |> "Force attachment charset for decoding." |> Key::Char('d')
|
||||
}
|
||||
}
|
||||
|
||||
shortcut_key_values! { "thread-view",
|
||||
pub struct ThreadViewShortcuts {
|
||||
scroll_up |> "Scroll up list." |> Key::Up,
|
||||
scroll_down |> "Scroll down list." |> Key::Down,
|
||||
collapse_subtree |> "collapse thread branches" |> Key::Char('h'),
|
||||
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||
collapse_subtree |> "collapse thread branches." |> Key::Char('h'),
|
||||
next_page |> "Go to next page." |> Key::PageDown,
|
||||
prev_page |> "Go to previous page." |> Key::PageUp,
|
||||
reverse_thread_order |> "reverse thread order" |> Key::Ctrl('r'),
|
||||
toggle_mailview |> "toggle mail view visibility" |> Key::Char('p'),
|
||||
toggle_threadview |> "toggle thread view visibility" |> Key::Char('t')
|
||||
reverse_thread_order |> "reverse thread order." |> Key::Ctrl('r'),
|
||||
toggle_mailview |> "toggle mail view visibility." |> Key::Char('p'),
|
||||
toggle_threadview |> "toggle thread view visibility." |> Key::Char('t'),
|
||||
toggle_layout |> "Toggle between horizontal and vertical layout." |> Key::Char(' ')
|
||||
}
|
||||
}
|
|
@ -21,32 +21,24 @@
|
|||
|
||||
//! E-mail tag configuration and {de,}serializing.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use melib::{Error, Result, TagHash};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use super::DotAddressable;
|
||||
use crate::terminal::Color;
|
||||
use melib::{MeliError, Result};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
|
||||
use std::hash::Hasher;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TagsSettings {
|
||||
#[serde(default, deserialize_with = "tag_color_de")]
|
||||
pub colors: HashMap<u64, Color>,
|
||||
pub colors: HashMap<TagHash, Color>,
|
||||
#[serde(default, deserialize_with = "tag_set_de", alias = "ignore-tags")]
|
||||
pub ignore_tags: HashSet<u64>,
|
||||
pub ignore_tags: HashSet<TagHash>,
|
||||
}
|
||||
|
||||
impl Default for TagsSettings {
|
||||
fn default() -> Self {
|
||||
TagsSettings {
|
||||
colors: Default::default(),
|
||||
ignore_tags: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tag_set_de<'de, D, T: std::convert::From<HashSet<u64>>>(
|
||||
pub fn tag_set_de<'de, D, T: std::convert::From<HashSet<TagHash>>>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<T, D::Error>
|
||||
where
|
||||
|
@ -54,16 +46,12 @@ where
|
|||
{
|
||||
Ok(<Vec<String>>::deserialize(deserializer)?
|
||||
.into_iter()
|
||||
.map(|tag| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(tag.as_bytes());
|
||||
hasher.finish()
|
||||
})
|
||||
.collect::<HashSet<u64>>()
|
||||
.map(|tag| TagHash::from_bytes(tag.as_bytes()))
|
||||
.collect::<HashSet<TagHash>>()
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn tag_color_de<'de, D, T: std::convert::From<HashMap<u64, Color>>>(
|
||||
pub fn tag_color_de<'de, D, T: std::convert::From<HashMap<TagHash, Color>>>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<T, D::Error>
|
||||
where
|
||||
|
@ -79,17 +67,15 @@ where
|
|||
Ok(<HashMap<String, _Color>>::deserialize(deserializer)?
|
||||
.into_iter()
|
||||
.map(|(tag, color)| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(tag.as_bytes());
|
||||
(
|
||||
hasher.finish(),
|
||||
TagHash::from_bytes(tag.as_bytes()),
|
||||
match color {
|
||||
_Color::B(b) => Color::Byte(b),
|
||||
_Color::C(c) => c,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<u64, Color>>()
|
||||
.collect::<HashMap<TagHash, Color>>()
|
||||
.into())
|
||||
}
|
||||
|
||||
|
@ -101,7 +87,7 @@ impl DotAddressable for TagsSettings {
|
|||
match *field {
|
||||
"colors" => self.colors.lookup(field, tail),
|
||||
"ignore_tags" => self.ignore_tags.lookup(field, tail),
|
||||
other => Err(MeliError::new(format!(
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
|
@ -21,13 +21,12 @@
|
|||
|
||||
//! Settings for terminal display
|
||||
|
||||
use super::deserializers::non_empty_string;
|
||||
use super::DotAddressable;
|
||||
use super::Themes;
|
||||
use melib::{MeliError, Result, ToggleFlag};
|
||||
use melib::{Error, Result, ToggleFlag};
|
||||
|
||||
use super::{deserializers::non_empty_opt_string, DotAddressable, Themes};
|
||||
|
||||
/// Settings for terminal display
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct TerminalSettings {
|
||||
/// light, dark
|
||||
|
@ -35,28 +34,28 @@ pub struct TerminalSettings {
|
|||
pub themes: Themes,
|
||||
pub ascii_drawing: bool,
|
||||
pub use_color: ToggleFlag,
|
||||
/// Use mouse events. This will disable text selection, but you will be able to resize some
|
||||
/// widgets.
|
||||
/// Use mouse events. This will disable text selection, but you will be able
|
||||
/// to resize some widgets.
|
||||
/// Default: False
|
||||
pub use_mouse: ToggleFlag,
|
||||
/// String to show in status bar if mouse is active.
|
||||
/// Default: "🖱️ "
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||
pub mouse_flag: Option<String>,
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||
pub window_title: Option<String>,
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||
pub file_picker_command: Option<String>,
|
||||
/// Choose between 30-something built in sequences (integers between 0-30) or define your own
|
||||
/// list of strings for the progress spinner animation.
|
||||
/// Default: 0
|
||||
/// Choose between 30-something built in sequences (integers between 0-30)
|
||||
/// or define your own list of strings for the progress spinner
|
||||
/// animation. Default: 0
|
||||
#[serde(default)]
|
||||
pub progress_spinner_sequence: Option<ProgressSpinnerSequence>,
|
||||
}
|
||||
|
||||
impl Default for TerminalSettings {
|
||||
fn default() -> Self {
|
||||
TerminalSettings {
|
||||
Self {
|
||||
theme: "dark".to_string(),
|
||||
themes: Themes::default(),
|
||||
ascii_drawing: false,
|
||||
|
@ -89,7 +88,7 @@ impl DotAddressable for TerminalSettings {
|
|||
let tail = &path[1..];
|
||||
match *field {
|
||||
"theme" => self.theme.lookup(field, tail),
|
||||
"themes" => Err(MeliError::new("unimplemented")),
|
||||
"themes" => Err(Error::new("unimplemented")),
|
||||
"ascii_drawing" => self.ascii_drawing.lookup(field, tail),
|
||||
"use_color" => self.use_color.lookup(field, tail),
|
||||
"use_mouse" => self.use_mouse.lookup(field, tail),
|
||||
|
@ -99,7 +98,7 @@ impl DotAddressable for TerminalSettings {
|
|||
"progress_spinner_sequence" => {
|
||||
self.progress_spinner_sequence.lookup(field, tail)
|
||||
}
|
||||
other => Err(MeliError::new(format!(
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
|
@ -110,7 +109,7 @@ impl DotAddressable for TerminalSettings {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ProgressSpinnerSequence {
|
||||
Integer(usize),
|
||||
|
@ -122,7 +121,7 @@ pub enum ProgressSpinnerSequence {
|
|||
}
|
||||
|
||||
const fn interval_ms_val() -> u64 {
|
||||
crate::components::utilities::ProgressSpinner::INTERVAL_MS
|
||||
crate::utilities::ProgressSpinner::INTERVAL_MS
|
||||
}
|
||||
|
||||
impl DotAddressable for ProgressSpinnerSequence {}
|
|
@ -19,54 +19,53 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use indexmap::IndexMap;
|
||||
use melib::Card;
|
||||
|
||||
mod contact_list;
|
||||
|
||||
pub use self::contact_list::*;
|
||||
use crate::{
|
||||
terminal::*, CellBuffer, Component, ComponentId, Context, Field, FormWidget, Key, StatusEvent,
|
||||
ThemeAttribute, UIDialog, UIEvent,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ViewMode {
|
||||
ReadOnly,
|
||||
Discard(UIDialog<char>),
|
||||
Discard(Box<UIDialog<char>>),
|
||||
Edit,
|
||||
//New,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContactManager {
|
||||
id: ComponentId,
|
||||
parent_id: ComponentId,
|
||||
parent_id: Option<ComponentId>,
|
||||
pub card: Card,
|
||||
mode: ViewMode,
|
||||
form: FormWidget<bool>,
|
||||
account_pos: usize,
|
||||
content: CellBuffer,
|
||||
pub account_pos: usize,
|
||||
content: Screen<Virtual>,
|
||||
theme_default: ThemeAttribute,
|
||||
dirty: bool,
|
||||
has_changes: bool,
|
||||
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for ContactManager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "contacts")
|
||||
impl std::fmt::Display for ContactManager {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.card)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContactManager {
|
||||
fn new(context: &Context) -> Self {
|
||||
pub fn new(context: &Context) -> Self {
|
||||
let theme_default: ThemeAttribute = crate::conf::value(context, "theme_default");
|
||||
ContactManager {
|
||||
id: Uuid::nil(),
|
||||
parent_id: Uuid::nil(),
|
||||
Self {
|
||||
id: ComponentId::default(),
|
||||
parent_id: None,
|
||||
card: Card::new(),
|
||||
mode: ViewMode::Edit,
|
||||
form: FormWidget::default(),
|
||||
account_pos: 0,
|
||||
content: CellBuffer::new_with_context(100, 1, None, context),
|
||||
content: Screen::<Virtual>::new(),
|
||||
theme_default,
|
||||
dirty: true,
|
||||
has_changes: false,
|
||||
|
@ -74,134 +73,124 @@ impl ContactManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self) {
|
||||
let (width, _) = self.content.size();
|
||||
fn initialize(&mut self, context: &Context) {
|
||||
if !self.content.resize_with_context(100, 1, context) {
|
||||
return;
|
||||
}
|
||||
let mut area = self.content.area();
|
||||
|
||||
let (x, _) = write_string_to_grid(
|
||||
let (x, _) = self.content.grid_mut().write_string(
|
||||
"Last edited: ",
|
||||
&mut self.content,
|
||||
Color::Byte(250),
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, 0), (width - 1, 0)),
|
||||
area,
|
||||
None,
|
||||
);
|
||||
let (x, y) = write_string_to_grid(
|
||||
area = area.skip_cols(x);
|
||||
let (x, y) = self.content.grid_mut().write_string(
|
||||
&self.card.last_edited(),
|
||||
&mut self.content,
|
||||
Color::Byte(250),
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((x, 0), (width - 1, 0)),
|
||||
area,
|
||||
None,
|
||||
);
|
||||
area = area.skip(x, y);
|
||||
|
||||
if self.card.external_resource() {
|
||||
self.mode = ViewMode::ReadOnly;
|
||||
let _ = self.content.resize(self.content.size().0, 2, None);
|
||||
write_string_to_grid(
|
||||
self.content.grid_mut().write_string(
|
||||
"This contact's origin is external and cannot be edited within meli.",
|
||||
&mut self.content,
|
||||
Color::Byte(250),
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((x, y), (width - 1, y)),
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
self.form = FormWidget::new(("Save".into(), true));
|
||||
self.form = FormWidget::new(
|
||||
("Save".into(), true),
|
||||
/* cursor_up_shortcut */ context.settings.shortcuts.general.scroll_up.clone(),
|
||||
/* cursor_down_shortcut */
|
||||
context.settings.shortcuts.general.scroll_down.clone(),
|
||||
);
|
||||
self.form.add_button(("Cancel(Esc)".into(), false));
|
||||
self.form
|
||||
.push(("NAME".into(), self.card.name().to_string().into()));
|
||||
.push(("NAME".into(), self.card.name().to_string()));
|
||||
self.form.push((
|
||||
"ADDITIONAL NAME".into(),
|
||||
self.card.additionalname().to_string().into(),
|
||||
));
|
||||
self.form.push((
|
||||
"NAME PREFIX".into(),
|
||||
self.card.name_prefix().to_string().into(),
|
||||
));
|
||||
self.form.push((
|
||||
"NAME SUFFIX".into(),
|
||||
self.card.name_suffix().to_string().into(),
|
||||
self.card.additionalname().to_string(),
|
||||
));
|
||||
self.form
|
||||
.push(("E-MAIL".into(), self.card.email().to_string().into()));
|
||||
.push(("NAME PREFIX".into(), self.card.name_prefix().to_string()));
|
||||
self.form
|
||||
.push(("URL".into(), self.card.url().to_string().into()));
|
||||
.push(("NAME SUFFIX".into(), self.card.name_suffix().to_string()));
|
||||
self.form
|
||||
.push(("KEY".into(), self.card.key().to_string().into()));
|
||||
.push(("E-MAIL".into(), self.card.email().to_string()));
|
||||
self.form.push(("URL".into(), self.card.url().to_string()));
|
||||
self.form.push(("KEY".into(), self.card.key().to_string()));
|
||||
for (k, v) in self.card.extra_properties() {
|
||||
self.form.push((k.to_string().into(), v.to_string().into()));
|
||||
self.form.push((k.to_string().into(), v.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_parent_id(&mut self, new_val: ComponentId) {
|
||||
self.parent_id = new_val;
|
||||
self.parent_id = Some(new_val);
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ContactManager {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.initialized {
|
||||
self.initialize();
|
||||
self.initialize(context);
|
||||
self.initialized = true;
|
||||
}
|
||||
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
if self.dirty {
|
||||
let (width, _height) = self.content.size();
|
||||
clear_area(
|
||||
grid,
|
||||
(upper_left, set_y(bottom_right, get_y(upper_left) + 1)),
|
||||
self.theme_default,
|
||||
);
|
||||
copy_area_with_break(grid, &self.content, area, ((0, 0), (width - 1, 0)));
|
||||
if self.is_dirty() {
|
||||
grid.clear_area(area, self.theme_default);
|
||||
grid.copy_area(self.content.grid(), area.skip_rows(2), self.content.area());
|
||||
self.dirty = false;
|
||||
}
|
||||
|
||||
self.form.draw(
|
||||
grid,
|
||||
(set_y(upper_left, get_y(upper_left) + 2), bottom_right),
|
||||
area.skip_rows(2 + self.content.area().height()),
|
||||
context,
|
||||
);
|
||||
match self.mode {
|
||||
ViewMode::Discard(ref mut selector) => {
|
||||
/* Let user choose whether to quit with/without saving or cancel */
|
||||
selector.draw(grid, area, context);
|
||||
}
|
||||
_ => {}
|
||||
if let ViewMode::Discard(ref mut selector) = self.mode {
|
||||
/* Let user choose whether to quit with/without saving or cancel */
|
||||
selector.draw(grid, area, context);
|
||||
}
|
||||
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
match event {
|
||||
UIEvent::ConfigReload { old_settings: _ } => {
|
||||
self.theme_default = crate::conf::value(context, "theme_default");
|
||||
self.content = CellBuffer::new_with_context(100, 1, None, context);
|
||||
self.initialized = false;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
_ => {}
|
||||
if let UIEvent::ConfigReload { old_settings: _ } = event {
|
||||
self.theme_default = crate::conf::value(context, "theme_default");
|
||||
self.content.grid_mut().empty();
|
||||
self.initialized = false;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
match self.mode {
|
||||
ViewMode::Discard(ref mut selector) => {
|
||||
if matches!(event, UIEvent::ComponentUnrealize(ref id) if *id == selector.id()) {
|
||||
selector.unrealize(context);
|
||||
self.mode = ViewMode::Edit;
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
if selector.process_event(event, context) {
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ViewMode::Edit => {
|
||||
if let &mut UIEvent::Input(Key::Esc) = event {
|
||||
if matches!(event, UIEvent::Input(Key::Esc)) {
|
||||
if self.can_quit_cleanly(context) {
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::Action(Tab(Kill(self.parent_id))));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -209,17 +198,15 @@ impl Component for ContactManager {
|
|||
match self.form.buttons_result() {
|
||||
None => {}
|
||||
Some(true) => {
|
||||
let fields = std::mem::replace(&mut self.form, FormWidget::default())
|
||||
.collect()
|
||||
.unwrap();
|
||||
let fields: HashMap<String, String> = fields
|
||||
let fields = std::mem::take(&mut self.form).collect().unwrap();
|
||||
let fields: IndexMap<String, String> = fields
|
||||
.into_iter()
|
||||
.map(|(s, v)| {
|
||||
(
|
||||
s.to_string(),
|
||||
match v {
|
||||
Field::Text(v, _) => v.as_str().to_string(),
|
||||
Field::Choice(mut v, c) => v.remove(c).to_string(),
|
||||
Field::Text(v) => v.as_str().to_string(),
|
||||
Field::Choice(mut v, c, _) => v.remove(c).to_string(),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -232,23 +219,23 @@ impl Component for ContactManager {
|
|||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage("Saved.".into()),
|
||||
));
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
Some(false) => {
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
}
|
||||
self.set_dirty(true);
|
||||
if let UIEvent::InsertInput(_) = event {
|
||||
if matches!(event, UIEvent::InsertInput(_)) {
|
||||
self.has_changes = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ViewMode::ReadOnly => {
|
||||
if let &mut UIEvent::Input(Key::Esc) = event {
|
||||
if matches!(event, UIEvent::Input(Key::Esc)) {
|
||||
if self.can_quit_cleanly(context) {
|
||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
||||
self.unrealize(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -260,11 +247,7 @@ impl Component for ContactManager {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
|| self.form.is_dirty()
|
||||
|| if let ViewMode::Discard(ref selector) = self.mode {
|
||||
selector.is_dirty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|| matches!(self.mode, ViewMode::Discard(ref selector) if selector.is_dirty())
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
|
@ -279,34 +262,36 @@ impl Component for ContactManager {
|
|||
self.id
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: ComponentId) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||
if !self.has_changes {
|
||||
return true;
|
||||
}
|
||||
|
||||
let parent_id = self.parent_id;
|
||||
/* Play it safe and ask user for confirmation */
|
||||
self.mode = ViewMode::Discard(UIDialog::new(
|
||||
"this contact has unsaved changes",
|
||||
vec![
|
||||
('x', "quit without saving".to_string()),
|
||||
('y', "save draft and quit".to_string()),
|
||||
('n', "cancel".to_string()),
|
||||
],
|
||||
true,
|
||||
Some(Box::new(move |_, results: &[char]| match results[0] {
|
||||
'x' => Some(UIEvent::Action(Tab(Kill(parent_id)))),
|
||||
'n' => None,
|
||||
'y' => None,
|
||||
_ => None,
|
||||
})),
|
||||
context,
|
||||
));
|
||||
self.set_dirty(true);
|
||||
false
|
||||
if matches!(self.mode, ViewMode::Discard(_)) {
|
||||
true
|
||||
} else {
|
||||
let Some(parent_id) = self.parent_id else {
|
||||
return true;
|
||||
};
|
||||
/* Play it safe and ask user for confirmation */
|
||||
self.mode = ViewMode::Discard(Box::new(UIDialog::new(
|
||||
"this contact has unsaved changes",
|
||||
vec![
|
||||
('y', "quit without saving".to_string()),
|
||||
('n', "cancel".to_string()),
|
||||
],
|
||||
true,
|
||||
Some(Box::new(move |id, results: &[char]| {
|
||||
if matches!(results.first(), Some(&'y')) {
|
||||
Some(UIEvent::ComponentUnrealize(parent_id))
|
||||
} else {
|
||||
Some(UIEvent::ComponentUnrealize(id))
|
||||
}
|
||||
})),
|
||||
context,
|
||||
)));
|
||||
self.set_dirty(true);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2023 - Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pub mod editor;
|
||||
pub mod list;
|
|
@ -20,32 +20,35 @@
|
|||
*/
|
||||
|
||||
//! Async job executor thread pool
|
||||
//!
|
||||
//! ## Usage
|
||||
//! ```no_run
|
||||
//! let (channel, handle, job_id) = job_executor.spawn(job);
|
||||
//! ```
|
||||
|
||||
use melib::error::Result;
|
||||
use melib::smol;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::panic::catch_unwind;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
future::Future,
|
||||
iter,
|
||||
panic::catch_unwind,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::types::{ThreadEvent, UIEvent};
|
||||
use crossbeam::deque::{Injector, Stealer, Worker};
|
||||
use crossbeam::sync::{Parker, Unparker};
|
||||
use crossbeam::Sender;
|
||||
use crossbeam::{
|
||||
channel::Sender,
|
||||
deque::{Injector, Stealer, Worker},
|
||||
sync::{Parker, Unparker},
|
||||
};
|
||||
pub use futures::channel::oneshot;
|
||||
use std::iter;
|
||||
use indexmap::IndexMap;
|
||||
use melib::{log, smol, utils::datetime, uuid::Uuid, UnixTimestamp};
|
||||
|
||||
type AsyncTask = async_task::Task<()>;
|
||||
use crate::types::{StatusEvent, ThreadEvent, UIEvent};
|
||||
|
||||
fn find_task<T>(local: &Worker<T>, global: &Injector<T>, stealers: &[Stealer<T>]) -> Option<T> {
|
||||
type AsyncTask = async_task::Runnable;
|
||||
|
||||
fn find_task(
|
||||
local: &Worker<MeliTask>,
|
||||
global: &Injector<MeliTask>,
|
||||
stealers: &[Stealer<MeliTask>],
|
||||
) -> Option<MeliTask> {
|
||||
// Pop a task from the local queue, if not empty.
|
||||
local.pop().or_else(|| {
|
||||
// Otherwise, we need to look for a task elsewhere.
|
||||
|
@ -65,49 +68,67 @@ fn find_task<T>(local: &Worker<T>, global: &Injector<T>, stealers: &[Stealer<T>]
|
|||
|
||||
macro_rules! uuid_hash_type {
|
||||
($n:ident) => {
|
||||
#[derive(
|
||||
PartialEq, Hash, Eq, Copy, Clone, Ord, PartialOrd, Serialize, Deserialize, Default,
|
||||
)]
|
||||
#[derive(PartialEq, Hash, Eq, Copy, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct $n(Uuid);
|
||||
|
||||
impl core::fmt::Debug for $n {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
impl std::fmt::Debug for $n {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for $n {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
impl std::fmt::Display for $n {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for $n {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl $n {
|
||||
pub fn new() -> Self {
|
||||
$n(Uuid::new_v4())
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
pub fn null() -> Self {
|
||||
$n(Uuid::nil())
|
||||
Self(Uuid::nil())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
uuid_hash_type!(JobId);
|
||||
uuid_hash_type!(TimerId);
|
||||
|
||||
/// A spawned future and its current state.
|
||||
pub struct MeliTask {
|
||||
task: AsyncTask,
|
||||
id: JobId,
|
||||
desc: Cow<'static, str>,
|
||||
timer: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A spawned future's metadata for book-keeping.
|
||||
pub struct JobMetadata {
|
||||
pub id: JobId,
|
||||
pub desc: Cow<'static, str>,
|
||||
pub timer: bool,
|
||||
pub started: UnixTimestamp,
|
||||
pub finished: Option<UnixTimestamp>,
|
||||
pub succeeded: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JobExecutor {
|
||||
global_queue: Arc<Injector<MeliTask>>,
|
||||
workers: Vec<Stealer<MeliTask>>,
|
||||
sender: Sender<ThreadEvent>,
|
||||
parkers: Vec<Unparker>,
|
||||
timers: Arc<Mutex<HashMap<Uuid, TimerPrivate>>>,
|
||||
timers: Arc<Mutex<IndexMap<TimerId, TimerPrivate>>>,
|
||||
pub jobs: Arc<Mutex<IndexMap<JobId, JobMetadata>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -117,17 +138,18 @@ struct TimerPrivate {
|
|||
/// Time until next expiration.
|
||||
value: Duration,
|
||||
active: bool,
|
||||
handle: Option<async_task::JoinHandle<(), ()>>,
|
||||
handle: Option<async_task::Task<()>>,
|
||||
cancel: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Timer {
|
||||
id: Uuid,
|
||||
id: TimerId,
|
||||
job_executor: Arc<JobExecutor>,
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
pub fn id(&self) -> Uuid {
|
||||
pub fn id(&self) -> TimerId {
|
||||
self.id
|
||||
}
|
||||
|
||||
|
@ -154,15 +176,19 @@ impl JobExecutor {
|
|||
/// A queue that holds scheduled tasks.
|
||||
pub fn new(sender: Sender<ThreadEvent>) -> Self {
|
||||
// Create a queue.
|
||||
let mut ret = JobExecutor {
|
||||
let mut ret = Self {
|
||||
global_queue: Arc::new(Injector::new()),
|
||||
workers: vec![],
|
||||
parkers: vec![],
|
||||
sender,
|
||||
timers: Arc::new(Mutex::new(HashMap::default())),
|
||||
timers: Arc::new(Mutex::new(IndexMap::default())),
|
||||
jobs: Arc::new(Mutex::new(IndexMap::default())),
|
||||
};
|
||||
let mut workers = vec![];
|
||||
for _ in 0..num_cpus::get().max(1) {
|
||||
for _ in 0..std::thread::available_parallelism()
|
||||
.map(Into::into)
|
||||
.unwrap_or(1)
|
||||
{
|
||||
let new_worker = Worker::new_fifo();
|
||||
ret.workers.push(new_worker.stealer());
|
||||
let p = Parker::new();
|
||||
|
@ -190,13 +216,18 @@ impl JobExecutor {
|
|||
parker.park_timeout(Duration::from_millis(100));
|
||||
let task = find_task(&local, &global, stealers.as_slice());
|
||||
if let Some(meli_task) = task {
|
||||
let MeliTask { task, id, timer } = meli_task;
|
||||
let MeliTask {
|
||||
task,
|
||||
id,
|
||||
timer,
|
||||
desc,
|
||||
} = meli_task;
|
||||
if !timer {
|
||||
debug!("Worker {} got task {:?}", i, id);
|
||||
log::trace!("Worker {} got task {:?} {:?}", i, desc, id);
|
||||
}
|
||||
let _ = catch_unwind(|| task.run());
|
||||
if !timer {
|
||||
debug!("Worker {} returned after {:?}", i, id);
|
||||
log::trace!("Worker {} returned after {:?} {:?}", i, desc, id);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -206,7 +237,8 @@ impl JobExecutor {
|
|||
}
|
||||
|
||||
/// Spawns a future with a generic return value `R`
|
||||
pub fn spawn_specialized<F, R>(&self, future: F) -> JoinHandle<R>
|
||||
#[inline(always)]
|
||||
pub fn spawn_specialized<F, R>(&self, desc: Cow<'static, str>, future: F) -> JoinHandle<R>
|
||||
where
|
||||
F: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
|
@ -215,54 +247,79 @@ impl JobExecutor {
|
|||
let finished_sender = self.sender.clone();
|
||||
let job_id = JobId::new();
|
||||
let injector = self.global_queue.clone();
|
||||
let cancel = Arc::new(Mutex::new(false));
|
||||
let cancel2 = cancel.clone();
|
||||
|
||||
self.jobs.lock().unwrap().insert(
|
||||
job_id,
|
||||
JobMetadata {
|
||||
id: job_id,
|
||||
desc: desc.clone(),
|
||||
started: datetime::now(),
|
||||
finished: None,
|
||||
succeeded: true,
|
||||
timer: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Create a task and schedule it for execution.
|
||||
let (task, handle) = async_task::spawn(
|
||||
let (handle, task) = async_task::spawn(
|
||||
async move {
|
||||
let res = future.await;
|
||||
let _ = sender.send(res);
|
||||
finished_sender
|
||||
.send(ThreadEvent::JobFinished(job_id))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
},
|
||||
move |task| {
|
||||
if *cancel.lock().unwrap() {
|
||||
return;
|
||||
}
|
||||
let desc = desc.clone();
|
||||
injector.push(MeliTask {
|
||||
task,
|
||||
id: job_id,
|
||||
desc,
|
||||
timer: false,
|
||||
})
|
||||
},
|
||||
(),
|
||||
);
|
||||
task.schedule();
|
||||
handle.schedule();
|
||||
for unparker in self.parkers.iter() {
|
||||
unparker.unpark();
|
||||
}
|
||||
|
||||
JoinHandle {
|
||||
inner: handle,
|
||||
task: Arc::new(Mutex::new(Some(task))),
|
||||
cancel: cancel2,
|
||||
chan: receiver,
|
||||
job_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a future with a generic return value `R` that might block on a new thread
|
||||
pub fn spawn_blocking<F, R>(&self, future: F) -> JoinHandle<R>
|
||||
/// Spawns a future with a generic return value `R` that might block on a
|
||||
/// new thread
|
||||
#[inline(always)]
|
||||
pub fn spawn_blocking<F, R>(&self, desc: Cow<'static, str>, future: F) -> JoinHandle<R>
|
||||
where
|
||||
F: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.spawn_specialized(smol::unblock(move || futures::executor::block_on(future)))
|
||||
self.spawn_specialized(
|
||||
desc,
|
||||
smol::unblock(move || futures::executor::block_on(future)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_timer(self: Arc<JobExecutor>, interval: Duration, value: Duration) -> Timer {
|
||||
let id = Uuid::new_v4();
|
||||
pub fn create_timer(self: Arc<Self>, interval: Duration, value: Duration) -> Timer {
|
||||
let timer = TimerPrivate {
|
||||
interval,
|
||||
cancel: Arc::new(Mutex::new(false)),
|
||||
value,
|
||||
active: true,
|
||||
handle: None,
|
||||
};
|
||||
let id = TimerId::default();
|
||||
self.timers.lock().unwrap().insert(id, timer);
|
||||
self.arm_timer(id, value);
|
||||
Timer {
|
||||
|
@ -271,23 +328,22 @@ impl JobExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn rearm(&self, timer_id: Uuid) {
|
||||
pub fn rearm(&self, timer_id: TimerId) {
|
||||
let mut timers_lck = self.timers.lock().unwrap();
|
||||
if let Some(timer) = timers_lck.get_mut(&timer_id) {
|
||||
if let Some(handle) = timer.handle.take() {
|
||||
handle.cancel();
|
||||
}
|
||||
let value = timer.value;
|
||||
drop(timers_lck);
|
||||
self.arm_timer(timer_id, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn arm_timer(&self, id: Uuid, value: Duration) {
|
||||
fn arm_timer(&self, id: TimerId, value: Duration) {
|
||||
let job_id = JobId::new();
|
||||
let sender = self.sender.clone();
|
||||
let injector = self.global_queue.clone();
|
||||
let timers = self.timers.clone();
|
||||
let cancel = Arc::new(Mutex::new(false));
|
||||
let cancel2 = cancel.clone();
|
||||
let (task, handle) = async_task::spawn(
|
||||
async move {
|
||||
let mut value = value;
|
||||
|
@ -312,16 +368,20 @@ impl JobExecutor {
|
|||
}
|
||||
},
|
||||
move |task| {
|
||||
if *cancel.lock().unwrap() {
|
||||
return;
|
||||
}
|
||||
injector.push(MeliTask {
|
||||
task,
|
||||
id: job_id,
|
||||
desc: "timer".into(),
|
||||
timer: true,
|
||||
})
|
||||
},
|
||||
(),
|
||||
);
|
||||
self.timers.lock().unwrap().entry(id).and_modify(|timer| {
|
||||
timer.handle = Some(handle);
|
||||
timer.cancel = cancel2;
|
||||
timer.active = true;
|
||||
});
|
||||
task.schedule();
|
||||
|
@ -330,37 +390,54 @@ impl JobExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
fn disable_timer(&self, id: Uuid) {
|
||||
fn disable_timer(&self, id: TimerId) {
|
||||
let mut timers_lck = self.timers.lock().unwrap();
|
||||
if let Some(timer) = timers_lck.get_mut(&id) {
|
||||
if let Some(handle) = timer.handle.take() {
|
||||
handle.cancel();
|
||||
}
|
||||
timer.active = false;
|
||||
*timer.cancel.lock().unwrap() = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_interval(&self, id: Uuid, new_val: Duration) {
|
||||
fn set_interval(&self, id: TimerId, new_val: Duration) {
|
||||
let mut timers_lck = self.timers.lock().unwrap();
|
||||
if let Some(timer) = timers_lck.get_mut(&id) {
|
||||
timer.interval = new_val;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_job_finished(&self, id: JobId) {
|
||||
self.jobs.lock().unwrap().entry(id).and_modify(|entry| {
|
||||
entry.finished = Some(datetime::now());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_job_success(&self, id: JobId, value: bool) {
|
||||
self.jobs.lock().unwrap().entry(id).and_modify(|entry| {
|
||||
entry.succeeded = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub type JobChannel<T> = oneshot::Receiver<T>;
|
||||
|
||||
/// `JoinHandle` for the future that allows us to cancel the task.
|
||||
#[derive(Debug)]
|
||||
/// JoinHandle for the future that allows us to cancel the task.
|
||||
pub struct JoinHandle<T> {
|
||||
pub inner: async_task::JoinHandle<Result<()>, ()>,
|
||||
pub task: Arc<Mutex<Option<async_task::Task<()>>>>,
|
||||
pub chan: JobChannel<T>,
|
||||
pub cancel: Arc<Mutex<bool>>,
|
||||
pub job_id: JobId,
|
||||
}
|
||||
|
||||
impl<T> JoinHandle<T> {
|
||||
pub fn cancel(&self) {
|
||||
self.inner.cancel()
|
||||
pub fn cancel(&self) -> Option<StatusEvent> {
|
||||
let mut lck = self.cancel.lock().unwrap();
|
||||
if !*lck {
|
||||
*lck = true;
|
||||
Some(StatusEvent::JobCanceled(self.job_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,638 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2019 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
jobs::{JobId, JobMetadata},
|
||||
melib::{
|
||||
utils::datetime::{self, formats::RFC3339_DATETIME_AND_SPACE},
|
||||
SortOrder,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
enum Column {
|
||||
_0 = 0,
|
||||
_1,
|
||||
_2,
|
||||
_3,
|
||||
_4,
|
||||
}
|
||||
|
||||
const fn _assert_len() {
|
||||
if JobManager::HEADERS.len() != Column::_4 as usize + 1 {
|
||||
panic!("JobManager::HEADERS length changed, please update Column enum accordingly.");
|
||||
}
|
||||
}
|
||||
|
||||
const _: () = _assert_len();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JobManager {
|
||||
cursor_pos: usize,
|
||||
new_cursor_pos: usize,
|
||||
length: usize,
|
||||
data_columns: DataColumns<5>,
|
||||
min_width: [usize; 5],
|
||||
sort_col: Column,
|
||||
sort_order: SortOrder,
|
||||
entries: IndexMap<JobId, JobMetadata>,
|
||||
|
||||
initialized: bool,
|
||||
theme_default: ThemeAttribute,
|
||||
highlight_theme: ThemeAttribute,
|
||||
|
||||
dirty: bool,
|
||||
|
||||
movement: Option<PageMovement>,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for JobManager {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "jobs")
|
||||
}
|
||||
}
|
||||
|
||||
impl JobManager {
|
||||
const HEADERS: [&'static str; 5] = ["id", "desc", "started", "finished", "succeeded"];
|
||||
|
||||
pub fn new(context: &Context) -> Self {
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
let highlight_theme = if context.settings.terminal.use_color() {
|
||||
crate::conf::value(context, "highlight")
|
||||
} else {
|
||||
ThemeAttribute {
|
||||
attrs: Attr::REVERSE,
|
||||
..ThemeAttribute::default()
|
||||
}
|
||||
};
|
||||
let mut data_columns = DataColumns::default();
|
||||
data_columns.theme_config.set_single_theme(theme_default);
|
||||
Self {
|
||||
cursor_pos: 0,
|
||||
new_cursor_pos: 0,
|
||||
entries: IndexMap::default(),
|
||||
length: 0,
|
||||
data_columns,
|
||||
min_width: [0; 5],
|
||||
sort_col: Column::_2,
|
||||
sort_order: SortOrder::Desc,
|
||||
theme_default,
|
||||
highlight_theme,
|
||||
initialized: false,
|
||||
dirty: true,
|
||||
movement: None,
|
||||
id: ComponentId::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self, context: &Context) {
|
||||
self.set_dirty(true);
|
||||
|
||||
let mut entries = (*context.main_loop_handler.job_executor.jobs.lock().unwrap()).clone();
|
||||
|
||||
self.length = entries.len();
|
||||
entries.sort_by(|_, a, _, b| match (self.sort_col, self.sort_order) {
|
||||
(Column::_0, SortOrder::Asc) => a.id.cmp(&b.id),
|
||||
(Column::_0, SortOrder::Desc) => b.id.cmp(&b.id),
|
||||
(Column::_1, SortOrder::Asc) => a.desc.cmp(&b.desc),
|
||||
(Column::_1, SortOrder::Desc) => b.desc.cmp(&a.desc),
|
||||
(Column::_2, SortOrder::Asc) => a.started.cmp(&b.started),
|
||||
(Column::_2, SortOrder::Desc) => b.started.cmp(&a.started),
|
||||
(Column::_3, SortOrder::Asc) => a.finished.cmp(&b.finished),
|
||||
(Column::_3, SortOrder::Desc) => b.finished.cmp(&a.finished),
|
||||
(Column::_4, SortOrder::Asc) if a.finished.is_some() && b.finished.is_some() => {
|
||||
a.succeeded.cmp(&b.succeeded)
|
||||
}
|
||||
(Column::_4, SortOrder::Desc) if a.finished.is_some() && b.finished.is_some() => {
|
||||
b.succeeded.cmp(&a.succeeded)
|
||||
}
|
||||
(Column::_4, SortOrder::Asc) if a.finished.is_none() => std::cmp::Ordering::Less,
|
||||
(Column::_4, SortOrder::Asc) => std::cmp::Ordering::Greater,
|
||||
(Column::_4, SortOrder::Desc) if a.finished.is_none() => std::cmp::Ordering::Greater,
|
||||
(Column::_4, SortOrder::Desc) => std::cmp::Ordering::Less,
|
||||
});
|
||||
self.entries = entries;
|
||||
|
||||
macro_rules! hdr {
|
||||
($idx:literal) => {{
|
||||
Self::HEADERS[$idx].len() + if self.sort_col as u8 == $idx { 1 } else { 0 }
|
||||
}};
|
||||
}
|
||||
self.min_width = [hdr!(0), hdr!(1), hdr!(2), hdr!(3), hdr!(4)];
|
||||
|
||||
for c in self.entries.values() {
|
||||
// title
|
||||
self.min_width[0] = self.min_width[0].max(c.id.to_string().len());
|
||||
// desc
|
||||
self.min_width[1] = self.min_width[1].max(c.desc.len());
|
||||
}
|
||||
self.min_width[2] = "1970-01-01 00:00:00".len();
|
||||
self.min_width[3] = self.min_width[2];
|
||||
|
||||
// name column
|
||||
_ = self.data_columns.columns[0].resize_with_context(
|
||||
self.min_width[0],
|
||||
self.length,
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[0].grid_mut().clear(None);
|
||||
// path column
|
||||
_ = self.data_columns.columns[1].resize_with_context(
|
||||
self.min_width[1],
|
||||
self.length,
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[1].grid_mut().clear(None);
|
||||
// size column
|
||||
_ = self.data_columns.columns[2].resize_with_context(
|
||||
self.min_width[2],
|
||||
self.length,
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[2].grid_mut().clear(None);
|
||||
// subscribed column
|
||||
_ = self.data_columns.columns[3].resize_with_context(
|
||||
self.min_width[3],
|
||||
self.length,
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[3].grid_mut().clear(None);
|
||||
_ = self.data_columns.columns[4].resize_with_context(
|
||||
self.min_width[4],
|
||||
self.length,
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[4].grid_mut().clear(None);
|
||||
|
||||
for (idx, e) in self.entries.values().enumerate() {
|
||||
{
|
||||
let area = self.data_columns.columns[0].area().nth_row(idx);
|
||||
self.data_columns.columns[0].grid_mut().write_string(
|
||||
&e.id.to_string(),
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let area = self.data_columns.columns[1].area().nth_row(idx);
|
||||
self.data_columns.columns[1].grid_mut().write_string(
|
||||
&e.desc,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let area = self.data_columns.columns[2].area().nth_row(idx);
|
||||
self.data_columns.columns[2].grid_mut().write_string(
|
||||
&datetime::timestamp_to_string(
|
||||
e.started,
|
||||
Some(RFC3339_DATETIME_AND_SPACE),
|
||||
true,
|
||||
),
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let area = self.data_columns.columns[3].area().nth_row(idx);
|
||||
self.data_columns.columns[3].grid_mut().write_string(
|
||||
&if let Some(t) = e.finished {
|
||||
Cow::Owned(datetime::timestamp_to_string(
|
||||
t,
|
||||
Some(RFC3339_DATETIME_AND_SPACE),
|
||||
true,
|
||||
))
|
||||
} else {
|
||||
Cow::Borrowed("null")
|
||||
},
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let area = self.data_columns.columns[4].area().nth_row(idx);
|
||||
self.data_columns.columns[4].grid_mut().write_string(
|
||||
&if e.finished.is_some() {
|
||||
Cow::Owned(format!("{:?}", e.succeeded))
|
||||
} else {
|
||||
Cow::Borrowed("-")
|
||||
},
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.length == 0 {
|
||||
let message = "No jobs.".to_string();
|
||||
if self.data_columns.columns[0].resize_with_context(message.len(), self.length, context)
|
||||
{
|
||||
let area = self.data_columns.columns[0].area();
|
||||
self.data_columns.columns[0].grid_mut().write_string(
|
||||
&message,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let rows = area.height();
|
||||
if rows == 0 {
|
||||
return;
|
||||
}
|
||||
if self.length == 0 {
|
||||
grid.clear_area(area, self.theme_default);
|
||||
|
||||
grid.copy_area(
|
||||
self.data_columns.columns[0].grid(),
|
||||
area,
|
||||
self.data_columns.columns[0].area(),
|
||||
);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mvm) = self.movement.take() {
|
||||
match mvm {
|
||||
PageMovement::Up(amount) => {
|
||||
self.new_cursor_pos = self.new_cursor_pos.saturating_sub(amount);
|
||||
}
|
||||
PageMovement::PageUp(multiplier) => {
|
||||
self.new_cursor_pos = self.new_cursor_pos.saturating_sub(rows * multiplier);
|
||||
}
|
||||
PageMovement::Down(amount) => {
|
||||
if self.new_cursor_pos + amount < self.length {
|
||||
self.new_cursor_pos += amount;
|
||||
} else {
|
||||
self.new_cursor_pos = self.length - 1;
|
||||
}
|
||||
}
|
||||
PageMovement::PageDown(multiplier) => {
|
||||
#[allow(clippy::comparison_chain)]
|
||||
if self.new_cursor_pos + rows * multiplier < self.length {
|
||||
self.new_cursor_pos += rows * multiplier;
|
||||
} else if self.new_cursor_pos + rows * multiplier > self.length {
|
||||
self.new_cursor_pos = self.length - 1;
|
||||
} else {
|
||||
self.new_cursor_pos = (self.length / rows) * rows;
|
||||
}
|
||||
}
|
||||
PageMovement::Right(amount) => {
|
||||
self.data_columns.x_offset += amount;
|
||||
self.data_columns.x_offset = self.data_columns.x_offset.min(
|
||||
self.data_columns
|
||||
.widths
|
||||
.iter()
|
||||
.map(|w| w + 2)
|
||||
.sum::<usize>()
|
||||
.saturating_sub(2),
|
||||
);
|
||||
}
|
||||
PageMovement::Left(amount) => {
|
||||
self.data_columns.x_offset = self.data_columns.x_offset.saturating_sub(amount);
|
||||
}
|
||||
PageMovement::Home => {
|
||||
self.new_cursor_pos = 0;
|
||||
}
|
||||
PageMovement::End => {
|
||||
self.new_cursor_pos = self.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let prev_page_no = (self.cursor_pos).wrapping_div(rows);
|
||||
let page_no = (self.new_cursor_pos).wrapping_div(rows);
|
||||
|
||||
let top_idx = page_no * rows;
|
||||
|
||||
if self.length >= rows {
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
|
||||
ScrollUpdate::Update {
|
||||
id: self.id,
|
||||
context: ScrollContext {
|
||||
shown_lines: top_idx + rows,
|
||||
total_lines: self.length,
|
||||
has_more_lines: false,
|
||||
},
|
||||
},
|
||||
)));
|
||||
} else {
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
|
||||
ScrollUpdate::End(self.id),
|
||||
)));
|
||||
}
|
||||
|
||||
// If cursor position has changed, remove the highlight from the previous
|
||||
// position and apply it in the new one.
|
||||
if self.cursor_pos != self.new_cursor_pos && prev_page_no == page_no {
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
self.cursor_pos = self.new_cursor_pos;
|
||||
for &(idx, highlight) in &[(old_cursor_pos, false), (self.new_cursor_pos, true)] {
|
||||
if idx >= self.length {
|
||||
continue; //bounds check
|
||||
}
|
||||
let new_area = area.nth_row(idx % rows);
|
||||
self.data_columns
|
||||
.draw(grid, idx, self.cursor_pos, grid.bounds_iter(new_area));
|
||||
let row_attr = if highlight {
|
||||
self.highlight_theme
|
||||
} else {
|
||||
self.theme_default
|
||||
};
|
||||
grid.change_theme(new_area, row_attr);
|
||||
context.dirty_areas.push_back(new_area);
|
||||
}
|
||||
return;
|
||||
} else if self.cursor_pos != self.new_cursor_pos {
|
||||
self.cursor_pos = self.new_cursor_pos;
|
||||
}
|
||||
if self.new_cursor_pos >= self.length {
|
||||
self.new_cursor_pos = self.length - 1;
|
||||
self.cursor_pos = self.new_cursor_pos;
|
||||
}
|
||||
// Page_no has changed, so draw new page
|
||||
_ = self
|
||||
.data_columns
|
||||
.recalc_widths((area.width(), area.height()), top_idx);
|
||||
grid.clear_area(area, self.theme_default);
|
||||
// copy table columns
|
||||
self.data_columns
|
||||
.draw(grid, top_idx, self.cursor_pos, grid.bounds_iter(area));
|
||||
|
||||
// highlight cursor
|
||||
grid.change_theme(area.nth_row(self.cursor_pos % rows), self.highlight_theme);
|
||||
|
||||
// clear gap if available height is more than count of entries
|
||||
if top_idx + rows > self.length {
|
||||
grid.change_theme(area.skip_rows(self.length - top_idx), self.theme_default);
|
||||
}
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for JobManager {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
if !self.initialized {
|
||||
self.initialize(context);
|
||||
}
|
||||
if self.dirty {
|
||||
let area = area.nth_row(0);
|
||||
// Draw column headers.
|
||||
grid.clear_area(area, self.theme_default);
|
||||
let mut x_offset = 0;
|
||||
for (i, (h, w)) in Self::HEADERS.iter().zip(self.min_width).enumerate() {
|
||||
grid.write_string(
|
||||
h,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs | Attr::BOLD,
|
||||
area.skip_cols(x_offset),
|
||||
None,
|
||||
);
|
||||
if self.sort_col as usize == i {
|
||||
use SortOrder::*;
|
||||
let arrow = match (grid.ascii_drawing, self.sort_order) {
|
||||
(true, Asc) => DataColumns::<5>::ARROW_UP_ASCII,
|
||||
(true, Desc) => DataColumns::<5>::ARROW_DOWN_ASCII,
|
||||
(false, Asc) => DataColumns::<5>::ARROW_UP,
|
||||
(false, Desc) => DataColumns::<5>::ARROW_DOWN,
|
||||
};
|
||||
grid.write_string(
|
||||
arrow,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
area.skip_cols(x_offset + h.len()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
x_offset += w + 2;
|
||||
}
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
self.draw_list(grid, area.skip_rows(1), context);
|
||||
self.dirty = false;
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
if let UIEvent::ConfigReload { old_settings: _ } = event {
|
||||
self.theme_default = crate::conf::value(context, "theme_default");
|
||||
self.initialized = false;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
|
||||
let shortcuts = self.shortcuts(context);
|
||||
match event {
|
||||
UIEvent::StatusEvent(
|
||||
StatusEvent::JobFinished(_) | StatusEvent::JobCanceled(_) | StatusEvent::NewJob(_),
|
||||
) => {
|
||||
self.initialized = false;
|
||||
self.set_dirty(true);
|
||||
return false;
|
||||
}
|
||||
UIEvent::Action(Action::SortColumn(column, order)) => {
|
||||
let column = match *column {
|
||||
0 => Column::_0,
|
||||
1 => Column::_1,
|
||||
2 => Column::_2,
|
||||
3 => Column::_3,
|
||||
4 => Column::_4,
|
||||
other => {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"Invalid column index `{}`: there are {} columns.",
|
||||
other,
|
||||
Self::HEADERS.len()
|
||||
)),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
if (self.sort_col, self.sort_order) != (column, *order) {
|
||||
self.sort_col = column;
|
||||
self.sort_order = *order;
|
||||
self.initialized = false;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Char(ref c)) if c.is_ascii_digit() => {
|
||||
let n = *c as u8 - b'0'; // safe cast because of is_ascii_digit() check;
|
||||
let column = match n {
|
||||
1 => Column::_0,
|
||||
2 => Column::_1,
|
||||
3 => Column::_2,
|
||||
4 => Column::_3,
|
||||
5 => Column::_4,
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if self.sort_col == column {
|
||||
self.sort_order = !self.sort_order;
|
||||
} else {
|
||||
self.sort_col = column;
|
||||
self.sort_order = SortOrder::default();
|
||||
}
|
||||
self.initialized = false;
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) =>
|
||||
{
|
||||
let amount = 1;
|
||||
self.movement = Some(PageMovement::Up(amount));
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"])
|
||||
&& self.cursor_pos < self.length.saturating_sub(1) =>
|
||||
{
|
||||
let amount = 1;
|
||||
self.set_dirty(true);
|
||||
self.movement = Some(PageMovement::Down(amount));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["prev_page"]) =>
|
||||
{
|
||||
let mult = 1;
|
||||
self.set_dirty(true);
|
||||
self.movement = Some(PageMovement::PageUp(mult));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["next_page"]) =>
|
||||
{
|
||||
let mult = 1;
|
||||
self.set_dirty(true);
|
||||
self.movement = Some(PageMovement::PageDown(mult));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) =>
|
||||
{
|
||||
self.set_dirty(true);
|
||||
self.movement = Some(PageMovement::Home);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) =>
|
||||
{
|
||||
self.set_dirty(true);
|
||||
self.movement = Some(PageMovement::End);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Resize => {
|
||||
self.set_dirty(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
self.dirty = value;
|
||||
}
|
||||
|
||||
fn kill(&mut self, uuid: ComponentId, context: &mut Context) {
|
||||
debug_assert!(uuid == self.id);
|
||||
context.replies.push_back(UIEvent::Action(Tab(Kill(uuid))));
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = ShortcutMaps::default();
|
||||
|
||||
map.insert(
|
||||
Shortcuts::GENERAL,
|
||||
context.settings.shortcuts.general.key_values(),
|
||||
);
|
||||
map[Shortcuts::GENERAL].insert("sort by 1st column", Key::Char('1'));
|
||||
map[Shortcuts::GENERAL].insert("sort by 2nd column", Key::Char('2'));
|
||||
map[Shortcuts::GENERAL].insert("sort by 3rd column", Key::Char('3'));
|
||||
map[Shortcuts::GENERAL].insert("sort by 4th column", Key::Char('4'));
|
||||
map[Shortcuts::GENERAL].insert("sort by 5th column", Key::Char('5'));
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn can_quit_cleanly(&mut self, _context: &Context) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn status(&self, _context: &Context) -> String {
|
||||
format!(
|
||||
"{} entries. Use `sort <n> [asc/desc]` command or press column index number key \
|
||||
(twice to toggle asc/desc) to sort",
|
||||
self.entries.len()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* meli - lib.rs
|
||||
*
|
||||
* Copyright 2017-2022 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![deny(
|
||||
rustdoc::redundant_explicit_links,
|
||||
/* groups */
|
||||
clippy::correctness,
|
||||
clippy::suspicious,
|
||||
clippy::complexity,
|
||||
clippy::perf,
|
||||
clippy::cargo,
|
||||
clippy::nursery,
|
||||
clippy::style,
|
||||
/* restriction */
|
||||
clippy::dbg_macro,
|
||||
clippy::rc_buffer,
|
||||
clippy::as_underscore,
|
||||
clippy::assertions_on_result_states,
|
||||
/* rustdoc */
|
||||
rustdoc::broken_intra_doc_links,
|
||||
/* pedantic */
|
||||
//clippy::cast_lossless,
|
||||
//clippy::cast_possible_wrap,
|
||||
//clippy::ptr_as_ptr,
|
||||
clippy::doc_markdown,
|
||||
clippy::expect_fun_call,
|
||||
clippy::bool_to_int_with_if,
|
||||
clippy::borrow_as_ptr,
|
||||
clippy::cast_ptr_alignment,
|
||||
clippy::large_futures,
|
||||
clippy::waker_clone_wake,
|
||||
clippy::unused_enumerate_index,
|
||||
clippy::unnecessary_fallible_conversions,
|
||||
clippy::struct_field_names,
|
||||
clippy::manual_hash_one,
|
||||
clippy::into_iter_without_iter,
|
||||
)]
|
||||
#![allow(
|
||||
clippy::option_if_let_else,
|
||||
clippy::missing_const_for_fn,
|
||||
clippy::significant_drop_tightening,
|
||||
clippy::multiple_crate_versions,
|
||||
clippy::significant_drop_in_scrutinee,
|
||||
clippy::cognitive_complexity,
|
||||
clippy::manual_clamp
|
||||
)]
|
||||
/* Source Code Annotation Tags:
|
||||
*
|
||||
* Global tags (in tagref format <https://github.com/stepchowfun/tagref>) for source code
|
||||
* annotation:
|
||||
*
|
||||
* - tags from melib/src/lib.rs.
|
||||
* - [tag:hardcoded_color_value] Replace hardcoded color values with user configurable ones.
|
||||
*/
|
||||
|
||||
//!
|
||||
//! This crate contains the frontend stuff of the application. The application
|
||||
//! entry way on `src/bin.rs` creates an event loop and passes input to a
|
||||
//! thread.
|
||||
//!
|
||||
//! The mail handling stuff is done in the `melib` crate which includes all
|
||||
//! backend needs. The split is done to theoretically be able to create
|
||||
//! different frontends with the same innards.
|
||||
|
||||
use std::alloc::System;
|
||||
pub use std::{collections::VecDeque, path::PathBuf};
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate linkify;
|
||||
pub use melib::uuid;
|
||||
|
||||
pub extern crate bitflags;
|
||||
pub extern crate serde_json;
|
||||
pub extern crate smallvec;
|
||||
pub extern crate termion;
|
||||
|
||||
pub use structopt::StructOpt;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: System = System;
|
||||
|
||||
pub extern crate melib;
|
||||
pub use melib::{
|
||||
error::*, log, AccountHash, ActionFlag, Envelope, EnvelopeHash, EnvelopeRef, Flag, LogLevel,
|
||||
Mail, Mailbox, MailboxHash, ThreadHash, ToggleFlag,
|
||||
};
|
||||
|
||||
pub mod args;
|
||||
pub mod subcommands;
|
||||
|
||||
#[macro_use]
|
||||
pub mod types;
|
||||
pub use crate::types::*;
|
||||
|
||||
#[macro_use]
|
||||
pub mod terminal;
|
||||
pub use crate::terminal::*;
|
||||
|
||||
#[macro_use]
|
||||
pub mod command;
|
||||
pub use crate::command::*;
|
||||
|
||||
pub mod state;
|
||||
pub use crate::state::*;
|
||||
|
||||
pub mod components;
|
||||
pub use crate::components::*;
|
||||
|
||||
pub mod utilities;
|
||||
pub use crate::utilities::*;
|
||||
|
||||
pub mod contacts;
|
||||
pub use crate::contacts::*;
|
||||
|
||||
pub mod mail;
|
||||
pub use crate::mail::*;
|
||||
|
||||
pub mod notifications;
|
||||
|
||||
pub mod mailbox_management;
|
||||
pub use mailbox_management::*;
|
||||
|
||||
pub mod jobs_view;
|
||||
pub use jobs_view::*;
|
||||
|
||||
#[cfg(feature = "svgscreenshot")]
|
||||
pub mod svg;
|
||||
|
||||
#[macro_use]
|
||||
pub mod conf;
|
||||
pub use crate::conf::{
|
||||
DotAddressable, IndexStyle, SearchBackend, Settings, Shortcuts, ThemeAttribute,
|
||||
};
|
||||
|
||||
#[cfg(feature = "sqlite3")]
|
||||
pub mod sqlite3;
|
||||
|
||||
pub mod jobs;
|
||||
pub mod mailcap;
|
||||
|
||||
pub mod accounts;
|
||||
pub use self::accounts::Account;
|
|
@ -19,31 +19,30 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*! Entities that handle Mail specific functions.
|
||||
*/
|
||||
//! Entities that handle Mail specific functions.
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use melib::{
|
||||
backends::{AccountHash, Mailbox, MailboxHash},
|
||||
email::{attachment_types::*, attachments::*},
|
||||
thread::ThreadNodeHash,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use melib::backends::{AccountHash, Mailbox, MailboxHash};
|
||||
use melib::email::{attachment_types::*, attachments::*};
|
||||
use melib::thread::ThreadNodeHash;
|
||||
use crate::{
|
||||
melib::text::{TextProcessing, Truncate},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
pub mod listing;
|
||||
pub use crate::listing::*;
|
||||
pub mod view;
|
||||
pub use crate::view::*;
|
||||
mod compose;
|
||||
pub mod compose;
|
||||
pub use self::compose::*;
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
pub mod pgp;
|
||||
|
||||
mod status;
|
||||
pub mod status;
|
||||
pub use self::status::*;
|
||||
|
||||
fn get_display_name(context: &Context, account_hash: AccountHash) -> String {
|
||||
let settings = context.accounts[&account_hash].settings.account();
|
||||
if let Some(d) = settings.display_name.as_ref() {
|
||||
format!("{} <{}>", d, settings.identity)
|
||||
} else {
|
||||
settings.identity.to_string()
|
||||
}
|
||||
}
|
|
@ -21,29 +21,26 @@
|
|||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum EditAttachmentCursor {
|
||||
AttachmentNo(usize),
|
||||
#[default]
|
||||
Buttons,
|
||||
}
|
||||
|
||||
impl Default for EditAttachmentCursor {
|
||||
fn default() -> Self {
|
||||
EditAttachmentCursor::Buttons
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EditAttachmentMode {
|
||||
Overview,
|
||||
Edit {
|
||||
inner: FormWidget<FormButtonActions>,
|
||||
inner: Box<FormWidget<FormButtonActions>>,
|
||||
no: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EditAttachments {
|
||||
/// For shortcut setting retrieval.
|
||||
pub account_hash: Option<AccountHash>,
|
||||
pub mode: EditAttachmentMode,
|
||||
pub buttons: ButtonWidget<FormButtonActions>,
|
||||
pub cursor: EditAttachmentCursor,
|
||||
|
@ -52,35 +49,54 @@ pub struct EditAttachments {
|
|||
}
|
||||
|
||||
impl EditAttachments {
|
||||
pub fn new() -> Self {
|
||||
let mut buttons = ButtonWidget::new(("Add".into(), FormButtonActions::Other("add")));
|
||||
buttons.push(("Go Back".into(), FormButtonActions::Cancel));
|
||||
pub fn new(account_hash: Option<AccountHash>) -> Self {
|
||||
//ButtonWidget::new(("Add".into(), FormButtonActions::Other("add")));
|
||||
let mut buttons = ButtonWidget::new(("Go Back".into(), FormButtonActions::Cancel));
|
||||
buttons.set_focus(true);
|
||||
buttons.set_cursor(1);
|
||||
EditAttachments {
|
||||
Self {
|
||||
account_hash,
|
||||
mode: EditAttachmentMode::Overview,
|
||||
buttons,
|
||||
cursor: EditAttachmentCursor::Buttons,
|
||||
dirty: true,
|
||||
id: ComponentId::new_v4(),
|
||||
id: ComponentId::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditAttachmentsRefMut<'_, '_> {
|
||||
fn new_edit_widget(&self, no: usize) -> Option<FormWidget<FormButtonActions>> {
|
||||
fn new_edit_widget(
|
||||
&self,
|
||||
no: usize,
|
||||
context: &Context,
|
||||
) -> Option<Box<FormWidget<FormButtonActions>>> {
|
||||
if no >= self.draft.attachments().len() {
|
||||
return None;
|
||||
}
|
||||
let filename = self.draft.attachments()[no].content_type().name();
|
||||
let mime_type = self.draft.attachments()[no].content_type();
|
||||
let mut ret = FormWidget::new(("Save".into(), FormButtonActions::Accept));
|
||||
let shortcuts = self.shortcuts(context);
|
||||
|
||||
let mut ret = FormWidget::new(
|
||||
("Save".into(), FormButtonActions::Accept),
|
||||
/* cursor_up_shortcut */
|
||||
shortcuts
|
||||
.get(Shortcuts::COMPOSING)
|
||||
.and_then(|c| c.get("scroll_up").cloned())
|
||||
.unwrap_or_else(|| context.settings.shortcuts.composing.scroll_up.clone()),
|
||||
/* cursor_down_shortcut */
|
||||
shortcuts
|
||||
.get(Shortcuts::COMPOSING)
|
||||
.and_then(|c| c.get("scroll_down").cloned())
|
||||
.unwrap_or_else(|| context.settings.shortcuts.composing.scroll_down.clone()),
|
||||
);
|
||||
|
||||
ret.add_button(("Reset".into(), FormButtonActions::Reset));
|
||||
ret.add_button(("Cancel".into(), FormButtonActions::Cancel));
|
||||
ret.push(("Filename".into(), filename.unwrap_or_default().to_string()));
|
||||
ret.push(("Mime type".into(), mime_type.to_string()));
|
||||
Some(ret)
|
||||
Some(Box::new(ret))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,11 +123,10 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
} else if self.is_dirty() {
|
||||
let attachments_no = self.draft.attachments().len();
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
clear_area(grid, area, theme_default);
|
||||
grid.clear_area(area, theme_default);
|
||||
if attachments_no == 0 {
|
||||
write_string_to_grid(
|
||||
grid.write_string(
|
||||
"no attachments",
|
||||
grid,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
|
@ -119,9 +134,8 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
None,
|
||||
);
|
||||
} else {
|
||||
write_string_to_grid(
|
||||
grid.write_string(
|
||||
&format!("{} attachments ", attachments_no),
|
||||
grid,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
|
@ -131,48 +145,41 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
for (i, a) in self.draft.attachments().iter().enumerate() {
|
||||
let bg = if let EditAttachmentCursor::AttachmentNo(u) = self.inner.cursor {
|
||||
if u == i {
|
||||
Color::Byte(237)
|
||||
crate::conf::value(context, "highlight").bg
|
||||
} else {
|
||||
theme_default.bg
|
||||
}
|
||||
} else {
|
||||
theme_default.bg
|
||||
};
|
||||
if let Some(name) = a.content_type().name() {
|
||||
write_string_to_grid(
|
||||
&format!(
|
||||
grid.write_string(
|
||||
&if let Some(name) = a.content_type().name() {
|
||||
format!(
|
||||
"[{}] \"{}\", {} {}",
|
||||
i,
|
||||
name,
|
||||
a.content_type(),
|
||||
melib::Bytes(a.raw.len())
|
||||
),
|
||||
grid,
|
||||
theme_default.fg,
|
||||
bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 1 + i)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
write_string_to_grid(
|
||||
&format!("[{}] {} {}", i, a.content_type(), melib::Bytes(a.raw.len())),
|
||||
grid,
|
||||
theme_default.fg,
|
||||
bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 1 + i)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
}
|
||||
melib::BytesDisplay(a.raw.len())
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"[{}] {} {}",
|
||||
i,
|
||||
a.content_type(),
|
||||
melib::BytesDisplay(a.raw.len())
|
||||
)
|
||||
},
|
||||
theme_default.fg,
|
||||
bg,
|
||||
theme_default.attrs,
|
||||
area.skip(2, 2 + i),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.inner.buttons.draw(
|
||||
grid,
|
||||
(
|
||||
pos_inc(upper_left!(area), (0, 1 + self.draft.attachments().len())),
|
||||
bottom_right!(area),
|
||||
),
|
||||
area.skip_rows(3 + self.draft.attachments().len()),
|
||||
context,
|
||||
);
|
||||
self.set_dirty(false);
|
||||
|
@ -193,7 +200,7 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
}
|
||||
Some(FormButtonActions::Reset) => {
|
||||
let no = *no;
|
||||
if let Some(inner) = self.new_edit_widget(no) {
|
||||
if let Some(inner) = self.new_edit_widget(no, context) {
|
||||
self.inner.mode = EditAttachmentMode::Edit { inner, no };
|
||||
}
|
||||
}
|
||||
|
@ -202,8 +209,12 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
return true;
|
||||
}
|
||||
} else {
|
||||
let shortcuts = self.shortcuts(context);
|
||||
|
||||
match event {
|
||||
UIEvent::Input(Key::Up) => {
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_up"]) =>
|
||||
{
|
||||
self.set_dirty(true);
|
||||
match self.inner.cursor {
|
||||
EditAttachmentCursor::AttachmentNo(ref mut n) => {
|
||||
|
@ -229,7 +240,9 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Down) => {
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_down"]) =>
|
||||
{
|
||||
self.set_dirty(true);
|
||||
match self.inner.cursor {
|
||||
EditAttachmentCursor::AttachmentNo(ref mut n) => {
|
||||
|
@ -251,7 +264,7 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
UIEvent::Input(Key::Char('\n')) => {
|
||||
match self.inner.cursor {
|
||||
EditAttachmentCursor::AttachmentNo(ref no) => {
|
||||
if let Some(inner) = self.new_edit_widget(*no) {
|
||||
if let Some(inner) = self.new_edit_widget(*no, context) {
|
||||
self.inner.mode = EditAttachmentMode::Edit { inner, no: *no };
|
||||
}
|
||||
self.set_dirty(true);
|
||||
|
@ -296,17 +309,22 @@ impl Component for EditAttachmentsRefMut<'_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn kill(&mut self, _uuid: Uuid, _context: &mut Context) {}
|
||||
fn kill(&mut self, _uuid: ComponentId, _context: &mut Context) {}
|
||||
|
||||
fn get_shortcuts(&self, _context: &Context) -> ShortcutMaps {
|
||||
ShortcutMaps::default()
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
let mut map = ShortcutMaps::default();
|
||||
|
||||
let our_map: ShortcutMap = self
|
||||
.inner
|
||||
.account_hash
|
||||
.map(|acc| account_settings!(context[acc].shortcuts.composing).key_values())
|
||||
.unwrap_or_else(|| context.settings.shortcuts.composing.key_values());
|
||||
map.insert(Shortcuts::COMPOSING, our_map);
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
self.inner.id
|
||||
}
|
||||
|
||||
fn set_id(&mut self, new_id: ComponentId) {
|
||||
self.inner.id = new_id;
|
||||
}
|
||||
}
|
|
@ -29,14 +29,14 @@ pub enum KeySelection {
|
|||
secret: bool,
|
||||
local: bool,
|
||||
pattern: String,
|
||||
allow_remote_lookup: ToggleFlag,
|
||||
allow_remote_lookup: ActionFlag,
|
||||
},
|
||||
Error {
|
||||
id: ComponentId,
|
||||
err: MeliError,
|
||||
err: Error,
|
||||
},
|
||||
Loaded {
|
||||
widget: UIDialog<melib::gpgme::Key>,
|
||||
widget: Box<UIDialog<melib::gpgme::Key>>,
|
||||
keys: Vec<melib::gpgme::Key>,
|
||||
},
|
||||
}
|
||||
|
@ -52,15 +52,10 @@ impl KeySelection {
|
|||
secret: bool,
|
||||
local: bool,
|
||||
pattern: String,
|
||||
allow_remote_lookup: ToggleFlag,
|
||||
context: &mut Context,
|
||||
allow_remote_lookup: ActionFlag,
|
||||
context: &Context,
|
||||
) -> Result<Self> {
|
||||
use melib::gpgme::*;
|
||||
debug!("KeySelection::new");
|
||||
debug!(&secret);
|
||||
debug!(&local);
|
||||
debug!(&pattern);
|
||||
debug!(&allow_remote_lookup);
|
||||
let mut ctx = Context::new()?;
|
||||
if local {
|
||||
ctx.set_auto_key_locate(LocateKey::LOCAL)?;
|
||||
|
@ -68,10 +63,13 @@ impl KeySelection {
|
|||
ctx.set_auto_key_locate(LocateKey::WKD | LocateKey::LOCAL)?;
|
||||
}
|
||||
let job = ctx.keylist(secret, Some(pattern.clone()))?;
|
||||
let handle = context.job_executor.spawn_specialized(job);
|
||||
let handle = context
|
||||
.main_loop_handler
|
||||
.job_executor
|
||||
.spawn_specialized("gpg::keylist".into(), job);
|
||||
let mut progress_spinner = ProgressSpinner::new(8, context);
|
||||
progress_spinner.start();
|
||||
Ok(KeySelection::LoadingKeys {
|
||||
Ok(Self::LoadingKeys {
|
||||
handle,
|
||||
secret,
|
||||
local,
|
||||
|
@ -85,31 +83,28 @@ impl KeySelection {
|
|||
impl Component for KeySelection {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
match self {
|
||||
KeySelection::LoadingKeys {
|
||||
Self::LoadingKeys {
|
||||
ref mut progress_spinner,
|
||||
..
|
||||
} => progress_spinner.draw(grid, center_area(area, (2, 2)), context),
|
||||
KeySelection::Error { ref err, .. } => {
|
||||
} => progress_spinner.draw(grid, area.center_inside((2, 2)), context),
|
||||
Self::Error { ref err, .. } => {
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
write_string_to_grid(
|
||||
grid.write_string(
|
||||
&err.to_string(),
|
||||
grid,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
center_area(area, (15, 2)),
|
||||
area.center_inside((15, 2)),
|
||||
Some(0),
|
||||
);
|
||||
}
|
||||
KeySelection::Loaded { ref mut widget, .. } => widget.draw(grid, area, context),
|
||||
Self::Loaded { ref mut widget, .. } => widget.draw(grid, area, context),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
debug!(&self);
|
||||
debug!(&event);
|
||||
match self {
|
||||
KeySelection::LoadingKeys {
|
||||
Self::LoadingKeys {
|
||||
ref mut progress_spinner,
|
||||
ref mut handle,
|
||||
secret,
|
||||
|
@ -129,33 +124,30 @@ impl Component for KeySelection {
|
|||
match Self::new(
|
||||
*secret,
|
||||
*local,
|
||||
std::mem::replace(pattern, String::new()),
|
||||
std::mem::take(pattern),
|
||||
*allow_remote_lookup,
|
||||
context,
|
||||
) {
|
||||
Ok(w) => {
|
||||
*self = w;
|
||||
}
|
||||
Err(err) => *self = KeySelection::Error { err, id },
|
||||
Err(err) => *self = Self::Error { err, id },
|
||||
}
|
||||
} else if !*local && allow_remote_lookup.is_ask() {
|
||||
*self = KeySelection::Error {
|
||||
err: MeliError::new(format!(
|
||||
*self = Self::Error {
|
||||
err: Error::new(format!(
|
||||
"No keys found for {}, perform remote lookup?",
|
||||
pattern
|
||||
)),
|
||||
id,
|
||||
}
|
||||
} else {
|
||||
*self = KeySelection::Error {
|
||||
err: MeliError::new(format!(
|
||||
"No keys found for {}.",
|
||||
pattern
|
||||
)),
|
||||
*self = Self::Error {
|
||||
err: Error::new(format!("No keys found for {}.", pattern)),
|
||||
id,
|
||||
}
|
||||
}
|
||||
if let KeySelection::Error { ref err, .. } = self {
|
||||
if let Self::Error { ref err, .. } = self {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(err.to_string()),
|
||||
));
|
||||
|
@ -166,7 +158,7 @@ impl Component for KeySelection {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
let mut widget = UIDialog::new(
|
||||
let mut widget = Box::new(UIDialog::new(
|
||||
"select key",
|
||||
keys.iter()
|
||||
.map(|k| {
|
||||
|
@ -185,19 +177,19 @@ impl Component for KeySelection {
|
|||
move |id: ComponentId, results: &[melib::gpgme::Key]| {
|
||||
Some(UIEvent::FinishedUIDialog(
|
||||
id,
|
||||
Box::new(results.get(0).map(|k| k.clone())),
|
||||
Box::new(results.first().cloned()),
|
||||
))
|
||||
},
|
||||
)),
|
||||
context,
|
||||
);
|
||||
));
|
||||
widget.set_dirty(true);
|
||||
*self = KeySelection::Loaded { widget, keys };
|
||||
*self = Self::Loaded { widget, keys };
|
||||
}
|
||||
Ok(Some(Err(err))) => {
|
||||
*self = KeySelection::Error {
|
||||
*self = Self::Error {
|
||||
err,
|
||||
id: ComponentId::new_v4(),
|
||||
id: ComponentId::default(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -205,81 +197,68 @@ impl Component for KeySelection {
|
|||
}
|
||||
_ => progress_spinner.process_event(event, context),
|
||||
},
|
||||
KeySelection::Error { .. } => false,
|
||||
KeySelection::Loaded { ref mut widget, .. } => widget.process_event(event, context),
|
||||
Self::Error { .. } => false,
|
||||
Self::Loaded { ref mut widget, .. } => widget.process_event(event, context),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
match self {
|
||||
KeySelection::LoadingKeys {
|
||||
Self::LoadingKeys {
|
||||
ref progress_spinner,
|
||||
..
|
||||
} => progress_spinner.is_dirty(),
|
||||
KeySelection::Error { .. } => true,
|
||||
KeySelection::Loaded { ref widget, .. } => widget.is_dirty(),
|
||||
Self::Error { .. } => true,
|
||||
Self::Loaded { ref widget, .. } => widget.is_dirty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
match self {
|
||||
KeySelection::LoadingKeys {
|
||||
Self::LoadingKeys {
|
||||
ref mut progress_spinner,
|
||||
..
|
||||
} => progress_spinner.set_dirty(value),
|
||||
KeySelection::Error { .. } => {}
|
||||
KeySelection::Loaded { ref mut widget, .. } => widget.set_dirty(value),
|
||||
Self::Error { .. } => {}
|
||||
Self::Loaded { ref mut widget, .. } => widget.set_dirty(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(&mut self, _uuid: Uuid, _context: &mut Context) {}
|
||||
fn kill(&mut self, _uuid: ComponentId, _context: &mut Context) {}
|
||||
|
||||
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
match self {
|
||||
KeySelection::LoadingKeys { .. } | KeySelection::Error { .. } => {
|
||||
ShortcutMaps::default()
|
||||
}
|
||||
KeySelection::Loaded { ref widget, .. } => widget.get_shortcuts(context),
|
||||
Self::LoadingKeys { .. } | Self::Error { .. } => ShortcutMaps::default(),
|
||||
Self::Loaded { ref widget, .. } => widget.shortcuts(context),
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
match self {
|
||||
KeySelection::LoadingKeys {
|
||||
Self::LoadingKeys {
|
||||
ref progress_spinner,
|
||||
..
|
||||
} => progress_spinner.id(),
|
||||
KeySelection::Error { ref id, .. } => *id,
|
||||
KeySelection::Loaded { ref widget, .. } => widget.id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_id(&mut self, new_id: ComponentId) {
|
||||
match self {
|
||||
KeySelection::LoadingKeys {
|
||||
ref mut progress_spinner,
|
||||
..
|
||||
} => progress_spinner.set_id(new_id),
|
||||
KeySelection::Error { ref mut id, .. } => *id = new_id,
|
||||
KeySelection::Loaded { ref mut widget, .. } => widget.set_id(new_id),
|
||||
Self::Error { ref id, .. } => *id,
|
||||
Self::Loaded { ref widget, .. } => widget.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GpgComposeState {
|
||||
pub sign_mail: ToggleFlag,
|
||||
pub encrypt_mail: ToggleFlag,
|
||||
pub sign_mail: Option<ActionFlag>,
|
||||
pub encrypt_mail: Option<ActionFlag>,
|
||||
pub encrypt_keys: Vec<melib::gpgme::Key>,
|
||||
pub encrypt_for_self: bool,
|
||||
pub sign_keys: Vec<melib::gpgme::Key>,
|
||||
}
|
||||
|
||||
impl GpgComposeState {
|
||||
pub fn new() -> Self {
|
||||
GpgComposeState {
|
||||
sign_mail: ToggleFlag::Unset,
|
||||
encrypt_mail: ToggleFlag::Unset,
|
||||
impl Default for GpgComposeState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sign_mail: None,
|
||||
encrypt_mail: None,
|
||||
encrypt_keys: vec![],
|
||||
encrypt_for_self: true,
|
||||
sign_keys: vec![],
|
|
@ -0,0 +1,436 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Pre-submission hooks for draft validation and/or transformations.
|
||||
pub use std::borrow::Cow;
|
||||
|
||||
use melib::email::headers::HeaderName;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub enum HookFn {
|
||||
/// Stateful hook.
|
||||
Closure(Box<dyn FnMut(&mut Context, &mut Draft) -> Result<()> + Send + Sync>),
|
||||
|
||||
/// Static hook.
|
||||
Ptr(fn(&mut Context, &mut Draft) -> Result<()>),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for HookFn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct(stringify!(HookFn))
|
||||
.field(
|
||||
"kind",
|
||||
&match self {
|
||||
Self::Closure(_) => "closure",
|
||||
Self::Ptr(_) => "function ptr",
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for HookFn {
|
||||
type Target = dyn FnMut(&mut Context, &mut Draft) -> Result<()> + Send + Sync;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Ptr(ref v) => v,
|
||||
Self::Closure(ref v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for HookFn {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self {
|
||||
Self::Ptr(ref mut v) => v,
|
||||
Self::Closure(ref mut v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Pre-submission hook for draft validation and/or transformations.
|
||||
pub struct Hook {
|
||||
/// Hook name for enabling/disabling it from configuration.
|
||||
///
|
||||
/// See [`crate::conf::ComposingSettings::disabled_compose_hooks`].
|
||||
name: Cow<'static, str>,
|
||||
hook_fn: HookFn,
|
||||
}
|
||||
|
||||
impl Hook {
|
||||
/// Hook name for enabling/disabling it from configuration.
|
||||
///
|
||||
/// See [`crate::conf::ComposingSettings::disabled_compose_hooks`].
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_ref()
|
||||
}
|
||||
|
||||
pub fn new_shell_command(name: Cow<'static, str>, command: String) -> Self {
|
||||
let name_ = name.clone();
|
||||
Self {
|
||||
name,
|
||||
hook_fn: HookFn::Closure(Box::new(move |_, draft| -> Result<()> {
|
||||
use std::thread;
|
||||
|
||||
let mut child = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(&command)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|err| -> Error {
|
||||
format!(
|
||||
"could not execute `{command}`. Check if its binary is in PATH or if \
|
||||
the command is valid. Original error: {err}"
|
||||
)
|
||||
.into()
|
||||
})?;
|
||||
let mut stdin = child
|
||||
.stdin
|
||||
.take()
|
||||
.ok_or_else(|| Error::new("failed to get stdin"))?;
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(move || {
|
||||
stdin
|
||||
.write_all(draft.body.as_bytes())
|
||||
.expect("failed to write to stdin");
|
||||
});
|
||||
});
|
||||
let output = child.wait_with_output().map_err(|err| -> Error {
|
||||
format!("failed to wait on hook child {name_}: {err}").into()
|
||||
})?;
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if !output.status.success() || !stdout.is_empty() || !stderr.is_empty() {
|
||||
return Err(format!(
|
||||
"{name_}\n exit code: {:?}\n stdout:\n{}\n stderr:\n{}",
|
||||
output.status.code(),
|
||||
stdout,
|
||||
stderr,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Hook {
|
||||
type Target = dyn FnMut(&mut Context, &mut Draft) -> Result<()> + Send + Sync;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.hook_fn.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Hook {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.hook_fn.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
fn past_date_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
use melib::utils::datetime::*;
|
||||
if let Some(v) = draft
|
||||
.headers
|
||||
.get(HeaderName::DATE)
|
||||
.map(rfc822_to_timestamp)
|
||||
.and_then(Result::ok)
|
||||
{
|
||||
let now: UnixTimestamp = now();
|
||||
let diff = now.abs_diff(v);
|
||||
if diff >= 60 * 60 {
|
||||
return Err(format!(
|
||||
"Value of Date header is {} minutes in the {}.",
|
||||
diff / 60,
|
||||
if now > v { "past" } else { "future" }
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Warn if [`melib::Draft`] Date is far in the past/future.
|
||||
pub const PASTDATEWARN: Hook = Hook {
|
||||
name: Cow::Borrowed("past-date-warn"),
|
||||
hook_fn: HookFn::Ptr(past_date_warn),
|
||||
};
|
||||
|
||||
fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
for hdr in [HeaderName::FROM, HeaderName::TO] {
|
||||
match draft.headers.get(&hdr).map(melib::Address::list_try_from) {
|
||||
Some(Ok(_)) => {}
|
||||
Some(Err(err)) => return Err(format!("{hdr} header value is invalid ({err}).").into()),
|
||||
None => return Err(format!("{hdr} header is missing and should be present.").into()),
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
match draft
|
||||
.headers
|
||||
.get(HeaderName::DATE)
|
||||
.map(melib::utils::datetime::rfc822_to_timestamp)
|
||||
{
|
||||
Some(Err(err)) => return Err(format!("Date header value is invalid ({err}).").into()),
|
||||
Some(Ok(0)) => return Err("Date header value is invalid.".into()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for hdr in [HeaderName::CC, HeaderName::BCC] {
|
||||
if let Some(Err(err)) = draft
|
||||
.headers
|
||||
.get(&hdr)
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.map(melib::Address::list_try_from)
|
||||
{
|
||||
return Err(format!("{hdr} header value is invalid ({err}).").into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Warn if important [`melib::Draft`] header is missing or invalid.
|
||||
pub const HEADERWARN: Hook = Hook {
|
||||
name: Cow::Borrowed("important-header-warn"),
|
||||
hook_fn: HookFn::Ptr(important_header_warn),
|
||||
};
|
||||
|
||||
fn missing_attachment_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
if draft
|
||||
.headers
|
||||
.get(HeaderName::SUBJECT)
|
||||
.map(|s| s.to_lowercase().contains("attach"))
|
||||
.unwrap_or(false)
|
||||
&& draft.attachments.is_empty()
|
||||
{
|
||||
return Err("Subject mentions attachments but attachments are empty.".into());
|
||||
}
|
||||
|
||||
if draft.body.to_lowercase().contains("attach") && draft.attachments.is_empty() {
|
||||
return Err("Draft body mentions attachments but attachments are empty.".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Warn if Subject and/or draft body mentions attachments but they are missing.
|
||||
pub const MISSINGATTACHMENTWARN: Hook = Hook {
|
||||
name: Cow::Borrowed("missing-attachment-warn"),
|
||||
hook_fn: HookFn::Ptr(missing_attachment_warn),
|
||||
};
|
||||
|
||||
fn empty_draft_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
if draft
|
||||
.headers
|
||||
.get(HeaderName::SUBJECT)
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.is_none()
|
||||
&& draft.body.trim().is_empty()
|
||||
{
|
||||
return Err("Both Subject and body are empty.".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Warn if draft has no subject and no body.
|
||||
pub const EMPTYDRAFTWARN: Hook = Hook {
|
||||
name: Cow::Borrowed("empty-draft-warn"),
|
||||
hook_fn: HookFn::Ptr(empty_draft_warn),
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_draft_hook_datewarn() {
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let mut ctx = Context::new_mock(&tempdir);
|
||||
let mut draft = Draft::default();
|
||||
draft
|
||||
.set_body("αδφαφσαφασ".to_string())
|
||||
.set_header(HeaderName::SUBJECT, "test_update()".into())
|
||||
.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
|
||||
println!("Check that past Date header value produces a warning…");
|
||||
#[allow(const_item_mutation)]
|
||||
let err_msg = PASTDATEWARN(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert!(
|
||||
err_msg.starts_with("Value of Date header is "),
|
||||
"PASTDATEWARN should complain about Date value being in the past: {}",
|
||||
err_msg
|
||||
);
|
||||
assert!(
|
||||
err_msg.ends_with(" minutes in the past."),
|
||||
"PASTDATEWARN should complain about Date value being in the past: {}",
|
||||
err_msg
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_draft_hook_headerwarn() {
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let mut ctx = Context::new_mock(&tempdir);
|
||||
let mut draft = Draft::default();
|
||||
draft
|
||||
.set_body("αδφαφσαφασ".to_string())
|
||||
.set_header(HeaderName::SUBJECT, "test_update()".into())
|
||||
.set_header(HeaderName::DATE, "Sun sds16 Jun 2013 17:56:45 +0200".into());
|
||||
let mut hook = HEADERWARN;
|
||||
|
||||
println!("Check for missing/empty From header value…");
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg,
|
||||
"From header value is invalid (Parsing error. In input: \"...\",\nError: Alternative, \
|
||||
Many1, Alternative, atom(): starts with whitespace or empty).",
|
||||
"HEADERWARN should complain about From value being empty: {}",
|
||||
err_msg
|
||||
);
|
||||
draft.set_header(HeaderName::FROM, "user <user@example.com>".into());
|
||||
|
||||
println!("Check for missing/empty To header value…");
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg,
|
||||
"To header value is invalid (Parsing error. In input: \"...\",\nError: Alternative, \
|
||||
Many1, Alternative, atom(): starts with whitespace or empty).",
|
||||
"HEADERWARN should complain about To value being empty: {}",
|
||||
err_msg
|
||||
);
|
||||
draft.set_header(HeaderName::TO, "other user <user@example.com>".into());
|
||||
|
||||
println!("Check for invalid Date header value…");
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg, "Date header value is invalid.",
|
||||
"HEADERWARN should complain about Date value being invalid: {}",
|
||||
err_msg
|
||||
);
|
||||
|
||||
println!("Check that valid header values produces no errors…");
|
||||
draft = Draft::default();
|
||||
draft
|
||||
.set_body("αδφαφσαφασ".to_string())
|
||||
.set_header(HeaderName::FROM, "user <user@example.com>".into())
|
||||
.set_header(HeaderName::TO, "other user <user@example.com>".into())
|
||||
.set_header(HeaderName::SUBJECT, "test_update()".into());
|
||||
hook(&mut ctx, &mut draft).unwrap();
|
||||
draft.set_header(HeaderName::FROM, "user user@example.com>".into());
|
||||
|
||||
println!("Check for invalid From header value…");
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg,
|
||||
"From header value is invalid (Parsing error. In input: \"user \
|
||||
user@example.com>...\",\nError: Alternative, Tag).",
|
||||
"HEADERWARN should complain about From value being invalid: {}",
|
||||
err_msg
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_draft_hook_missingattachmentwarn() {
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let mut ctx = Context::new_mock(&tempdir);
|
||||
let mut draft = Draft::default();
|
||||
draft
|
||||
.set_body("αδφαφσαφασ".to_string())
|
||||
.set_header(HeaderName::SUBJECT, "Attachments included".into())
|
||||
.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
|
||||
|
||||
let mut hook = MISSINGATTACHMENTWARN;
|
||||
|
||||
println!(
|
||||
"Check that mentioning attachments in Subject produces a warning if draft has no \
|
||||
attachments…"
|
||||
);
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg, "Subject mentions attachments but attachments are empty.",
|
||||
"MISSINGATTACHMENTWARN should complain about missing attachments: {}",
|
||||
err_msg
|
||||
);
|
||||
|
||||
draft
|
||||
.set_header(HeaderName::SUBJECT, "Hello.".into())
|
||||
.set_body("Attachments included".to_string());
|
||||
println!(
|
||||
"Check that mentioning attachments in body produces a warning if draft has no \
|
||||
attachments…"
|
||||
);
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg, "Draft body mentions attachments but attachments are empty.",
|
||||
"MISSINGATTACHMENTWARN should complain about missing attachments: {}",
|
||||
err_msg
|
||||
);
|
||||
|
||||
println!(
|
||||
"Check that mentioning attachments produces no warnings if draft has attachments…"
|
||||
);
|
||||
draft.set_header(HeaderName::SUBJECT, "Attachments included".into());
|
||||
let mut attachment = AttachmentBuilder::new(b"");
|
||||
attachment
|
||||
.set_raw(b"foobar".to_vec())
|
||||
.set_content_type(ContentType::Other {
|
||||
name: Some("info.txt".to_string()),
|
||||
tag: b"text/plain".to_vec(),
|
||||
parameters: vec![],
|
||||
})
|
||||
.set_content_transfer_encoding(ContentTransferEncoding::Base64);
|
||||
draft.attachments_mut().push(attachment);
|
||||
|
||||
hook(&mut ctx, &mut draft).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_draft_hook_emptydraftwarn() {
|
||||
let tempdir = tempfile::tempdir().unwrap();
|
||||
let mut ctx = Context::new_mock(&tempdir);
|
||||
let mut draft = Draft::default();
|
||||
draft.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
|
||||
|
||||
let mut hook = EMPTYDRAFTWARN;
|
||||
|
||||
println!("Check that empty draft produces a warning…");
|
||||
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err_msg, "Both Subject and body are empty.",
|
||||
"EMPTYDRAFTWARN should complain about empty draft: {}",
|
||||
err_msg
|
||||
);
|
||||
|
||||
println!("Check that non-empty draft produces no warning…");
|
||||
draft.set_header(HeaderName::SUBJECT, "Ping".into());
|
||||
hook(&mut ctx, &mut draft).unwrap();
|
||||
}
|
||||
}
|