Compare commits
844 Commits
alpha-0.6.
...
master
Author | SHA1 | Date |
---|---|---|
Manos Pitsidianakis | 46e40856ba | |
Manos Pitsidianakis | 35408b1689 | |
Manos Pitsidianakis | 5d915baa81 | |
Manos Pitsidianakis | 684fae3ed8 | |
Manos Pitsidianakis | ab04189887 | |
Manos Pitsidianakis | 36b7c00b97 | |
Manos Pitsidianakis | 3a5306e9dd | |
Manos Pitsidianakis | 89c7972e12 | |
Manos Pitsidianakis | 8f3dee9b22 | |
Manos Pitsidianakis | 660022ce23 | |
Manos Pitsidianakis | 29cc1bce5b | |
Manos Pitsidianakis | bc1b65316d | |
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 | |
Ludovic LANGE | 66c6b62aa6 | |
Manos Pitsidianakis | eea9ac2b58 | |
Manos Pitsidianakis | d16866e0f0 | |
Manos Pitsidianakis | bcca9abe66 | |
Manos Pitsidianakis | 24b4c117e7 | |
Manos Pitsidianakis | b0fba401e6 | |
Manos Pitsidianakis | 48d4343082 | |
Manos Pitsidianakis | 2dfeb29b75 | |
Manos Pitsidianakis | 63d2fb93f4 | |
Manos Pitsidianakis | cf9457882a | |
Manos Pitsidianakis | 3fa9e355c2 | |
Manos Pitsidianakis | 3dae84182c | |
Manos Pitsidianakis | a4ae4da8b1 | |
Manos Pitsidianakis | 4050f6893f | |
Manos Pitsidianakis | dcccd303ac | |
Manos Pitsidianakis | 22a64e2d76 | |
Manos Pitsidianakis | 781a1d0e1b | |
Manos Pitsidianakis | eb8d29813c | |
Manos Pitsidianakis | 08af46f5ef | |
Manos Pitsidianakis | 2f47f1eebd | |
Manos Pitsidianakis | 622ded8021 | |
Manos Pitsidianakis | 6d63429ad3 | |
Manos Pitsidianakis | 5eb4342af8 | |
Manos Pitsidianakis | eca10a5660 | |
Manos Pitsidianakis | a697dfabbd | |
Manos Pitsidianakis | 23997bdec0 | |
Manos Pitsidianakis | 2e6a1e1ef8 | |
Manos Pitsidianakis | fe200a3218 | |
Manos Pitsidianakis | bf9143d8e4 | |
Manos Pitsidianakis | 441dcb62ca | |
Manos Pitsidianakis | 4cd3e28244 | |
Manos Pitsidianakis | 3dba6fdf60 | |
Manos Pitsidianakis | 50cd81772f | |
Manos Pitsidianakis | 613c3de3d2 | |
Manos Pitsidianakis | 62db7d7f32 | |
Manos Pitsidianakis | 1c25ae12eb | |
Manos Pitsidianakis | ccc083cf88 | |
Manos Pitsidianakis | db69349251 | |
Manos Pitsidianakis | 806254436b | |
Manos Pitsidianakis | 4f164dc700 | |
Manos Pitsidianakis | ab0ef1b63c | |
Manos Pitsidianakis | b966ee8fbd | |
Manos Pitsidianakis | 34e970d922 | |
Zisu Andrei | f7cbd9a64d | |
Manos Pitsidianakis | 829f1243fb | |
Manos Pitsidianakis | 1be30968ca | |
Manos Pitsidianakis | 92475c349a | |
Manos Pitsidianakis | 2d5f5e767c | |
Zisu Andrei | 0034f195e3 | |
Manos Pitsidianakis | 9124ad0ae7 | |
Manos Pitsidianakis | ed826357a3 | |
Manos Pitsidianakis | b2e853dd7b | |
matzipan@gmail.com | aa503deb76 | |
Manos Pitsidianakis | fee8f5b575 | |
Manos Pitsidianakis | 7e977fe627 | |
Manos Pitsidianakis | 09684e821d | |
Manos Pitsidianakis | 10b10e6267 | |
Manos Pitsidianakis | 48e7a493a9 | |
Manos Pitsidianakis | e5b0ff4fe2 | |
Manos Pitsidianakis | 68f9d1220b | |
Manos Pitsidianakis | 1408690a9a | |
Manos Pitsidianakis | 76814cea20 | |
Manos Pitsidianakis | 7e1e57a2df | |
Manos Pitsidianakis | f8a47586e9 | |
Manos Pitsidianakis | 7efbe6d692 | |
Manos Pitsidianakis | 0f86934e16 | |
Manos Pitsidianakis | c5a5c2666b | |
Manos Pitsidianakis | 7db32ff1b3 | |
Manos Pitsidianakis | 857d4d546f | |
Manos Pitsidianakis | 5327dae02d | |
Manos Pitsidianakis | c990687e5f | |
Manos Pitsidianakis | 453bb0b2b2 | |
Manos Pitsidianakis | 4914f29e20 | |
Manos Pitsidianakis | bedf181aff | |
Manos Pitsidianakis | 9dd21eea50 | |
Manos Pitsidianakis | 4939a1ad9e | |
Manos Pitsidianakis | 8e7583a32f | |
Manos Pitsidianakis | 5f6b4745b8 | |
Manos Pitsidianakis | 76c1c1a213 | |
Manos Pitsidianakis | ddfadc748d | |
Manos Pitsidianakis | 66dea9148b | |
Manos Pitsidianakis | 7b3fb86483 | |
Manos Pitsidianakis | d8c978ed2d | |
Manos Pitsidianakis | d076ff573f | |
Manos Pitsidianakis | 6cbb89a8e5 | |
Manos Pitsidianakis | aa89969dca | |
Manos Pitsidianakis | 6a67322570 | |
Manos Pitsidianakis | 3e109cabf0 | |
Manos Pitsidianakis | 1cbb6828f2 | |
Manos Pitsidianakis | de018294e4 | |
Manos Pitsidianakis | 6dd3b0bb4f | |
Manos Pitsidianakis | 714ccb5e16 | |
Manos Pitsidianakis | 8d9247e9a3 | |
Manos Pitsidianakis | b659749880 | |
Manos Pitsidianakis | b053aaa145 | |
Manos Pitsidianakis | 883b3e3a4f | |
Manos Pitsidianakis | 98c1ece28d | |
Manos Pitsidianakis | 54b2066f73 | |
Manos Pitsidianakis | 007e6320d5 | |
Manos Pitsidianakis | e01275cd93 | |
Manos Pitsidianakis | 879af75d88 | |
Manos Pitsidianakis | 6a5bb2e057 | |
Manos Pitsidianakis | 311c1a8a95 | |
Manos Pitsidianakis | ce5c7848e8 | |
Andrew Jeffery | daee4e46de | |
Manos Pitsidianakis | 92c12d3526 | |
Manos Pitsidianakis | 0a8a0c04c8 | |
Manos Pitsidianakis | ede5851baf | |
Manos Pitsidianakis | 79345b3e84 | |
Manos Pitsidianakis | b46cd09ca6 | |
Manos Pitsidianakis | bf56c88918 | |
Manos Pitsidianakis | 73372ff1e7 | |
Manos Pitsidianakis | d4f508642a | |
Manos Pitsidianakis | f69f623818 | |
Manos Pitsidianakis | 2ef2add67f | |
Manos Pitsidianakis | 458209b448 | |
Manos Pitsidianakis | b7c48a1ed0 | |
Manos Pitsidianakis | f25f93fccf | |
Manos Pitsidianakis | 31e4ed006d | |
Manos Pitsidianakis | 179ed52add | |
Manos Pitsidianakis | ebc290cc2a | |
Manos Pitsidianakis | f9ce5327c2 | |
Manos Pitsidianakis | 5b86c342fb | |
Manos Pitsidianakis | 0aa5cf273f | |
Manos Pitsidianakis | 041257f9a6 | |
Manos Pitsidianakis | 1da6d75b08 | |
Manos Pitsidianakis | a7c0bca8ce | |
Manos Pitsidianakis | 023afbaae3 | |
Manos Pitsidianakis | 1c62de57ae | |
Manos Pitsidianakis | 76f8bdc558 | |
Manos Pitsidianakis | d404910a0f | |
Manos Pitsidianakis | c0e3e78940 | |
Manos Pitsidianakis | aaee6d094c | |
Manos Pitsidianakis | 60350eaa88 | |
Manos Pitsidianakis | aa73bd71c3 | |
Manos Pitsidianakis | aa7ebf2918 | |
Manos Pitsidianakis | 2544f54107 | |
Manos Pitsidianakis | 72084da185 | |
Manos Pitsidianakis | 23777171f2 | |
Manos Pitsidianakis | cbaf21764c | |
Manos Pitsidianakis | da69eecafe | |
Manos Pitsidianakis | f0800f38a8 | |
Manos Pitsidianakis | a34f0aac5b | |
Manos Pitsidianakis | 353ac2d029 | |
Manos Pitsidianakis | 6c07046b66 | |
Manos Pitsidianakis | 8ac5558d65 | |
Manos Pitsidianakis | 43d3d3681e | |
Rudi Horn | f1bdae65ee | |
Manos Pitsidianakis | 6cc43540d6 | |
Manos Pitsidianakis | 6392904047 | |
Manos Pitsidianakis | 57e6cf3980 | |
Manos Pitsidianakis | 9a9c876f4a | |
Manos Pitsidianakis | afa74ccfb5 | |
Manos Pitsidianakis | 560771b32a | |
Manos Pitsidianakis | 7b1ab389fa | |
Manos Pitsidianakis | 594a2bd0dd | |
Manos Pitsidianakis | 05ef863a45 | |
Manos Pitsidianakis | d5aa2cb3ef | |
Manos Pitsidianakis | f7fc2e31e0 | |
Manos Pitsidianakis | 00f5c4b9c0 | |
Manos Pitsidianakis | 4b91de3d59 | |
Manos Pitsidianakis | eb36034740 | |
Manos Pitsidianakis | d4e347289c | |
Manos Pitsidianakis | 662706607b | |
Manos Pitsidianakis | b904f91f45 | |
Manos Pitsidianakis | 9f39a7c5a1 | |
Manos Pitsidianakis | 126ed8a189 | |
Manos Pitsidianakis | 91fe7435f7 | |
Manos Pitsidianakis | 7a9c150f33 | |
Manos Pitsidianakis | b9f4d718c7 | |
Manos Pitsidianakis | 54cb4ea623 | |
Manos Pitsidianakis | 7919e95ddd | |
Manos Pitsidianakis | 89940dd606 | |
Manos Pitsidianakis | b69bc219c3 | |
Manos Pitsidianakis | bb51d36579 | |
Manos Pitsidianakis | a2456fa3f5 | |
Manos Pitsidianakis | 3b97e66c10 | |
Manos Pitsidianakis | ddfec3e207 | |
Manos Pitsidianakis | a702a04043 | |
Manos Pitsidianakis | 6264ee011f | |
Manos Pitsidianakis | 5acd7dfe1c | |
Manos Pitsidianakis | 8090d614e2 | |
Manos Pitsidianakis | 3949cecb75 | |
Manos Pitsidianakis | 1e7b40e6b3 | |
Manos Pitsidianakis | d8d66641e2 | |
Manos Pitsidianakis | 393c5d0d53 | |
Manos Pitsidianakis | 4c1a9b2485 | |
Manos Pitsidianakis | 03a1d5a985 | |
Manos Pitsidianakis | 279c288a22 | |
Manos Pitsidianakis | e4cddbad25 | |
Manos Pitsidianakis | 67f50d95f4 | |
Manos Pitsidianakis | 0c68807814 | |
Manos Pitsidianakis | 4e72b6552a | |
Manos Pitsidianakis | 310d02042f | |
Manos Pitsidianakis | 188e020bd1 | |
Manos Pitsidianakis | 20840625d6 | |
Manos Pitsidianakis | d51d0187a6 | |
Manos Pitsidianakis | 2944fc992b | |
Manos Pitsidianakis | 535d04f4f0 | |
Manos Pitsidianakis | 6f31388b27 | |
Manos Pitsidianakis | 5337a54d96 | |
Manos Pitsidianakis | b343530f0c | |
Manos Pitsidianakis | cd68008e67 | |
Manos Pitsidianakis | 19891a3042 | |
Manos Pitsidianakis | 9ce62c735a | |
Manos Pitsidianakis | 39fab67523 | |
Manos Pitsidianakis | 0ca7b0042e | |
Manos Pitsidianakis | 406af1848f | |
Manos Pitsidianakis | a4b78532b7 | |
Manos Pitsidianakis | 4dd8474c30 | |
Manos Pitsidianakis | 0dd9e6a34b | |
Manos Pitsidianakis | eb1cb5cec6 | |
Manos Pitsidianakis | e42c9281fd | |
Manos Pitsidianakis | bc74379b27 | |
Manos Pitsidianakis | be45b0c02d | |
Manos Pitsidianakis | 3ec1ecb349 | |
Manos Pitsidianakis | afe7eed9ef | |
Manos Pitsidianakis | 59e60f8d28 | |
Manos Pitsidianakis | a2f11c341d | |
Manos Pitsidianakis | afee1e2be5 | |
Manos Pitsidianakis | 08df7f39b2 | |
Manos Pitsidianakis | 5d968b7c40 | |
Manos Pitsidianakis | 347b54e0f7 | |
Manos Pitsidianakis | 74f31875b8 | |
Manos Pitsidianakis | 23ca41e3e8 | |
Manos Pitsidianakis | b9c07bacef | |
Manos Pitsidianakis | 87443f156f | |
Manos Pitsidianakis | b0e50a29bd | |
Manos Pitsidianakis | 1ddde400ee |
|
@ -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/files
|
||||||
debian/meli.substvars
|
debian/meli.substvars
|
||||||
debian/meli/
|
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`
|
627
CHANGELOG.md
627
CHANGELOG.md
|
@ -7,6 +7,625 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- [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
|
## [alpha-0.6.2] - 2020-09-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -104,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.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.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.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
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
91
Cargo.toml
91
Cargo.toml
|
@ -1,87 +1,14 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "meli"
|
resolver = "2"
|
||||||
version = "0.6.2"
|
|
||||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
license = "GPL-3.0-or-later"
|
members = [
|
||||||
readme = "README.md"
|
"meli",
|
||||||
description = "terminal mail client"
|
"melib",
|
||||||
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.5", features = ["serde-1", ] }
|
|
||||||
linkify = "0.4.0"
|
|
||||||
notify = "4.0.1" # >:c
|
|
||||||
notify-rust = { version = "^4", optional = true }
|
|
||||||
termion = "1.5.1"
|
|
||||||
bincode = "1.2.0"
|
|
||||||
uuid = { version = "0.8.1", features = ["serde", "v4"] }
|
|
||||||
unicode-segmentation = "1.2.1" # >:c
|
|
||||||
libc = {version = "0.2.59", features = ["extra_traits",]}
|
|
||||||
rmp = "^0.8"
|
|
||||||
rmpv = { version = "^0.4.2", features=["with-serde",] }
|
|
||||||
rmp-serde = "^0.14.0"
|
|
||||||
smallvec = { version = "^1.4.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"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
syn = { version = "1.0.31", features = [] }
|
|
||||||
quote = "^1.0"
|
|
||||||
proc-macro2 = "1.0.18"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
opt-level = "z"
|
codegen-units = 1
|
||||||
|
opt-level = "s"
|
||||||
debug = false
|
debug = false
|
||||||
|
strip = true
|
||||||
[workspace]
|
|
||||||
members = ["melib", "tools", ]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications"]
|
|
||||||
notmuch = ["melib/notmuch_backend", ]
|
|
||||||
jmap = ["melib/jmap_backend",]
|
|
||||||
sqlite3 = ["melib/sqlite3"]
|
|
||||||
smtp = ["melib/smtp"]
|
|
||||||
regexp = ["pcre2"]
|
|
||||||
dbus-notifications = ["notify-rust",]
|
|
||||||
cli-docs = []
|
|
||||||
svgscreenshot = ["svg_crate"]
|
|
||||||
|
|
||||||
# Print tracing logs as meli runs in stderr
|
|
||||||
# enable for debug tracing logs: build with --features=debug-tracing
|
|
||||||
debug-tracing = ["melib/debug-tracing", ]
|
|
||||||
|
|
|
@ -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 -->
|
||||||
|
<!-- ``` -->
|
168
Makefile
168
Makefile
|
@ -16,6 +16,16 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with meli. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
# Options
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
@ -23,19 +33,16 @@ EXPANDED_PREFIX := `cd ${PREFIX} && pwd -P`
|
||||||
BINDIR ?= ${EXPANDED_PREFIX}/bin
|
BINDIR ?= ${EXPANDED_PREFIX}/bin
|
||||||
MANDIR ?= ${EXPANDED_PREFIX}/share/man
|
MANDIR ?= ${EXPANDED_PREFIX}/share/man
|
||||||
|
|
||||||
CARGO_TARGET_DIR ?= target
|
|
||||||
MIN_RUSTC ?= 1.39.0
|
|
||||||
CARGO_BIN ?= cargo
|
|
||||||
|
|
||||||
# Installation parameters
|
# Installation parameters
|
||||||
DOCS_SUBDIR ?= docs/
|
DOCS_SUBDIR ?= meli/docs/
|
||||||
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5
|
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5 meli.7
|
||||||
FEATURES ?= --features "${MELI_FEATURES}"
|
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/^://'
|
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 != sed -n "s/^version\s*=\s*\"\(.*\)\"/\1/p" Cargo.toml
|
VERSION = `grep -m1 version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1`
|
||||||
GIT_COMMIT != git show-ref -s --abbrev HEAD
|
MIN_RUSTC = `grep -m1 rust-version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1`
|
||||||
DATE != date -I
|
GIT_COMMIT = `git show-ref -s --abbrev HEAD`
|
||||||
|
DATE = `date -I`
|
||||||
|
|
||||||
# Output parameters
|
# Output parameters
|
||||||
BOLD ?= `[ -z $${TERM} ] && echo "" || tput bold`
|
BOLD ?= `[ -z $${TERM} ] && echo "" || tput bold`
|
||||||
|
@ -44,19 +51,20 @@ ANSI_RESET ?= `[ -z $${TERM} ] && echo "" || tput sgr0`
|
||||||
CARGO_COLOR ?= `[ -z $${NO_COLOR+x} ] && echo "" || echo "--color=never "`
|
CARGO_COLOR ?= `[ -z $${NO_COLOR+x} ] && echo "" || echo "--color=never "`
|
||||||
RED ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 1) || echo ""`
|
RED ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 1) || echo ""`
|
||||||
GREEN ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 2) || 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:
|
.PHONY: meli
|
||||||
.SUFFIXES:
|
|
||||||
meli: check-deps
|
meli: check-deps
|
||||||
@${CARGO_BIN} build ${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:
|
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 "Available subcommands:"
|
||||||
@echo " - ${BOLD}meli${ANSI_RESET} (builds meli with optimizations in \$$CARGO_TARGET_DIR)"
|
@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}install${ANSI_RESET} (installs binary in \$$BINDIR and documentation to \$$MANDIR)"
|
||||||
@echo " - ${BOLD}uninstall${ANSI_RESET}"
|
@echo " - ${BOLD}uninstall${ANSI_RESET}"
|
||||||
@echo "Secondary subcommands:"
|
@echo "\nSecondary subcommands:"
|
||||||
@echo " - ${BOLD}clean${ANSI_RESET} (cleans build artifacts)"
|
@echo " - ${BOLD}clean${ANSI_RESET} (cleans build artifacts)"
|
||||||
@echo " - ${BOLD}check-deps${ANSI_RESET} (checks dependencies)"
|
@echo " - ${BOLD}check-deps${ANSI_RESET} (checks dependencies)"
|
||||||
@echo " - ${BOLD}install-bin${ANSI_RESET} (installs binary to \$$BINDIR)"
|
@echo " - ${BOLD}install-bin${ANSI_RESET} (installs binary to \$$BINDIR)"
|
||||||
|
@ -69,30 +77,56 @@ help:
|
||||||
@echo " - ${BOLD}build-rustdoc${ANSI_RESET} (builds rustdoc documentation for all packages in \$$CARGO_TARGET_DIR)"
|
@echo " - ${BOLD}build-rustdoc${ANSI_RESET} (builds rustdoc documentation for all packages in \$$CARGO_TARGET_DIR)"
|
||||||
@echo "\nENVIRONMENT variables of interest:"
|
@echo "\nENVIRONMENT variables of interest:"
|
||||||
@echo "* PREFIX = ${UNDERLINE}${EXPANDED_PREFIX}${ANSI_RESET}"
|
@echo "* PREFIX = ${UNDERLINE}${EXPANDED_PREFIX}${ANSI_RESET}"
|
||||||
@echo -n "* MELI_FEATURES = ${UNDERLINE}"
|
@echo "* MELI_FEATURES = ${UNDERLINE}\n"
|
||||||
@[ -z $${MELI_FEATURES+x} ] && echo -n "unset" || echo -n ${MELI_FEATURES}
|
@[ -z $${MELI_FEATURES+x} ] && echo "unset\c" || echo ${MELI_FEATURES}'\c'
|
||||||
@echo ${ANSI_RESET}
|
@echo ${ANSI_RESET}
|
||||||
@echo "* BINDIR = ${UNDERLINE}${BINDIR}${ANSI_RESET}"
|
@echo "* BINDIR = ${UNDERLINE}${BINDIR}${ANSI_RESET}"
|
||||||
@echo "* MANDIR = ${UNDERLINE}${MANDIR}${ANSI_RESET}"
|
@echo "* MANDIR = ${UNDERLINE}${MANDIR}${ANSI_RESET}"
|
||||||
@echo -n "* MANPATH = ${UNDERLINE}"
|
@echo "* MANPATH = ${UNDERLINE}\c"
|
||||||
@[ $${MANPATH+x} ] && echo -n $${MANPATH} || echo -n "unset"
|
@[ $${MANPATH+x} ] && echo $${MANPATH}'\c' || echo "unset\c"
|
||||||
@echo ${ANSI_RESET}
|
@echo ${ANSI_RESET}
|
||||||
@echo "* (cleaned) output of manpath(1) = ${UNDERLINE}${MANPATHS}${ANSI_RESET}"
|
@echo "* (cleaned) output of manpath(1) = ${UNDERLINE}${MANPATHS}${ANSI_RESET}"
|
||||||
@echo -n "* NO_MAN ${UNDERLINE}"
|
@echo "* NO_MAN ${UNDERLINE}\c"
|
||||||
@[ $${NO_MAN+x} ] && echo -n "set" || echo -n "unset"
|
@[ $${NO_MAN+x} ] && echo "set\c" || echo "unset\c"
|
||||||
@echo ${ANSI_RESET}
|
@echo ${ANSI_RESET}
|
||||||
@echo -n "* NO_COLOR ${UNDERLINE}"
|
@echo "* NO_COLOR ${UNDERLINE}\c"
|
||||||
@[ $${NO_COLOR+x} ] && echo -n "set" || echo -n "unset"
|
@[ $${NO_COLOR+x} ] && echo "set\c" || echo "unset\c"
|
||||||
@echo ${ANSI_RESET}
|
@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}"
|
@#@echo "* CARGO_COLOR = ${CARGO_COLOR}"
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
check:
|
check: check-tagrefs
|
||||||
@${CARGO_BIN} test ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --workspace
|
@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
|
.PHONY: check-deps
|
||||||
check-deps:
|
check-deps:
|
||||||
@(if ! echo ${MIN_RUSTC}\\n`${CARGO_BIN} --version | cut -d ' ' -f 2` | sort -CV; then echo "rust version >= ${RED}${MIN_RUSTC}${ANSI_RESET} required, found: `which ${CARGO_BIN}` `${CARGO_BIN} --version | cut -d ' ' -f 2`" \
|
@(if ! echo ${MIN_RUSTC}\\n`${CARGO_BIN} --version | grep ^cargo | cut -d ' ' -f 2` | sort -CV; then echo "rust version >= ${RED}${MIN_RUSTC}${ANSI_RESET} required, found: `which ${CARGO_BIN}` `${CARGO_BIN} --version | cut -d ' ' -f 2`" \
|
||||||
"\nYour options:\n - Set CARGO_BIN to a supported version\n - Install a supported version from your distribution's package manager\n - Install a supported version from ${UNDERLINE}https://rustup.rs/${ANSI_RESET}" ; exit 1; fi)
|
"\nYour options:\n - Set CARGO_BIN to a supported version\n - Install a supported version from your distribution's package manager\n - Install a supported version from ${UNDERLINE}https://rustup.rs/${ANSI_RESET}" ; exit 1; fi)
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,30 +135,32 @@ clean:
|
||||||
-rm -rf ./${CARGO_TARGET_DIR}/
|
-rm -rf ./${CARGO_TARGET_DIR}/
|
||||||
|
|
||||||
.PHONY: distclean
|
.PHONY: distclean
|
||||||
distclean: clean
|
distclean:
|
||||||
@rm -f meli-${VERSION}.tar.gz
|
@rm -f meli-${VERSION}.tar.gz
|
||||||
|
@rm -rf .pc # rm debian stuff
|
||||||
|
|
||||||
.PHONY: uninstall
|
.PHONY: uninstall
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f $(DESTDIR)${BINDIR}/meli
|
rm -f $(DESTDIR)${BINDIR}/meli
|
||||||
-rm $(DESTDIR)${MANDIR}/man1/meli.1.gz
|
for MANPAGE in ${MANPAGES}; do \
|
||||||
-rm $(DESTDIR)${MANDIR}/man5/meli.conf.5.gz
|
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
||||||
-rm $(DESTDIR)${MANDIR}/man5/meli-themes.5.gz
|
MANPAGEPATH="${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz"; \
|
||||||
|
rm -f "$${MANAGEPATH}"
|
||||||
|
; done
|
||||||
|
|
||||||
.PHONY: install-doc
|
.PHONY: install-doc
|
||||||
install-doc:
|
install-doc:
|
||||||
@(if [ -z $${NO_MAN+x} ]; then \
|
@(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}:" ; \
|
echo " - ${BOLD}Installing manpages to ${ANSI_RESET}${DESTDIR}${MANDIR}:" ; \
|
||||||
for MANPAGE in ${MANPAGES}; do \
|
for MANPAGE in ${MANPAGES}; do \
|
||||||
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
||||||
|
mkdir -p $(DESTDIR)${MANDIR}/man$${SECTION} ; \
|
||||||
MANPAGEPATH=${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz; \
|
MANPAGEPATH=${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz; \
|
||||||
echo " * installing $${MANPAGE} → ${GREEN}$${MANPAGEPATH}${ANSI_RESET}"; \
|
echo " * installing $${MANPAGE} → ${GREEN}$${MANPAGEPATH}${ANSI_RESET}"; \
|
||||||
gzip -n < ${DOCS_SUBDIR}$${MANPAGE} > $${MANPAGEPATH} \
|
gzip -n < ${DOCS_SUBDIR}$${MANPAGE} > $${MANPAGEPATH} \
|
||||||
; done ; \
|
; done ; \
|
||||||
(case ":${MANPATHS}:" in \
|
(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}" ;; \
|
*) 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) ; \
|
esac) ; \
|
||||||
else echo "NO_MAN is defined, so no documentation is going to be installed." ; fi)
|
else echo "NO_MAN is defined, so no documentation is going to be installed." ; fi)
|
||||||
|
@ -134,20 +170,24 @@ install-bin: meli
|
||||||
@mkdir -p $(DESTDIR)${BINDIR}
|
@mkdir -p $(DESTDIR)${BINDIR}
|
||||||
@echo " - ${BOLD}Installing binary to ${ANSI_RESET}${GREEN}${DESTDIR}${BINDIR}/meli${ANSI_RESET}"
|
@echo " - ${BOLD}Installing binary to ${ANSI_RESET}${GREEN}${DESTDIR}${BINDIR}/meli${ANSI_RESET}"
|
||||||
@case ":${PATH}:" in \
|
@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}";; \
|
*) 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
|
esac
|
||||||
@install -D ./${CARGO_TARGET_DIR}/release/meli $(DESTDIR)${BINDIR}/meli
|
@mkdir -p $(DESTDIR)${BINDIR}
|
||||||
|
@rm -f $(DESTDIR)${BINDIR}/meli
|
||||||
|
@cp ./${CARGO_TARGET_DIR}/release/meli $(DESTDIR)${BINDIR}/meli
|
||||||
|
@chmod 755 $(DESTDIR)${BINDIR}/meli
|
||||||
|
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
.NOTPARALLEL: yes
|
.NOTPARALLEL: yes
|
||||||
install: meli install-bin install-doc
|
install: meli install-bin install-doc
|
||||||
@(if [ -z $${NO_MAN+x} ]; then \
|
@(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)
|
fi)
|
||||||
@echo " - Report bugs in the mailing list or git issue tracker ${UNDERLINE}https://git.meli.delivery${ANSI_RESET}"
|
@$(PRINTF) " - Report bugs in the mailing list or git issue tracker ${UNDERLINE}https://git.meli-email.org${ANSI_RESET}\n"
|
||||||
@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) " - 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
|
.PHONY: dist
|
||||||
dist:
|
dist:
|
||||||
|
@ -156,9 +196,53 @@ dist:
|
||||||
|
|
||||||
.PHONY: deb-dist
|
.PHONY: deb-dist
|
||||||
deb-dist:
|
deb-dist:
|
||||||
@dpkg-buildpackage -b -rfakeroot -us -uc
|
@author=$(grep -m1 authors meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1)
|
||||||
@echo ${BOLD}${GREEN}Generated${ANSI_RESET} ../meli_${VERSION}-1_amd64.deb
|
@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
|
.PHONY: build-rustdoc
|
||||||
build-rustdoc:
|
build-rustdoc:
|
||||||
@RUSTDOCFLAGS="--crate-version ${VERSION}_${GIT_COMMIT}_${DATE}" ${CARGO_BIN} doc ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --all-features --no-deps --workspace --document-private-items --open
|
@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-)
|
||||||
|
#}
|
||||||
|
|
257
README.md
257
README.md
|
@ -1,146 +1,153 @@
|
||||||
# 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)
|
||||||
For a quick start, build and install locally:
|
|
||||||
|
**BSD/Linux/macos terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP / NNTP (Usenet).**
|
||||||
|
|
||||||
|
Try an [old online interactive web demo](https://meli-email.org/wasm2.html "online interactive web demo") powered by WebAssembly!
|
||||||
|
|
||||||
|
* `#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>
|
||||||
|
|
||||||
|
**Table of contents**:
|
||||||
|
|
||||||
|
- [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
|
||||||
|
|
||||||
|
- [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'")
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `cargo build --release --bin meli` or `make`.
|
||||||
|
|
||||||
|
For detailed building instructions, see [`BUILD.md`](./BUILD.md)
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><td>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
PREFIX=~/.local make install
|
# 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
|
||||||
```
|
```
|
||||||
|
|
||||||
Available subcommands:
|
</td><td>
|
||||||
- meli (builds meli with optimizations in `$CARGO_TARGET_DIR`)
|
|
||||||
- install (installs binary in `$BINDIR` and documentation to `$MANDIR`)
|
|
||||||
- uninstall
|
|
||||||
Secondary subcommands:
|
|
||||||
- clean (cleans build artifacts)
|
|
||||||
- check-deps (checks dependencies)
|
|
||||||
- install-bin (installs binary to `$BINDIR`)
|
|
||||||
- install-doc (installs manpages to `$MANDIR`)
|
|
||||||
- help (prints this information)
|
|
||||||
- dist (creates release tarball named `meli-VERSION.tar.gz` in this directory)
|
|
||||||
- deb-dist (builds debian package in the parent directory)
|
|
||||||
- distclean (cleans distribution build artifacts)
|
|
||||||
- build-rustdoc (builds rustdoc documentation for all packages in `$CARGO_TARGET_DIR`)
|
|
||||||
|
|
||||||
The Makefile *should* be portable and not require a specific `make` version.
|
See a comprehensive tour of `meli` in the manual page [`meli(7)`](./meli/docs/meli.7).
|
||||||
|
|
||||||
# Documentation
|
See also the [Quickstart tutorial](https://meli-email.org/documentation.html#quick-start) online.
|
||||||
|
|
||||||
After installing meli, see `meli(1)` and `meli.conf(5)` for documentation. Sample configuration and theme files can be found in the `samples/` subdirectory.
|
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`.
|
||||||
|
|
||||||
# Building
|
You can run meli with arbitrary configuration files by setting the `${MELI_CONFIG}` environment variable to their locations, i.e.:
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
The resulting binary will then be found under `target/release/meli`
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
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`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make PREFIX=$HOME/.local install
|
|
||||||
```
|
|
||||||
|
|
||||||
See `meli(1)` and `meli.conf(5)` for documentation.
|
|
||||||
|
|
||||||
You can build and run meli with one command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo run --release
|
|
||||||
```
|
|
||||||
|
|
||||||
While the project is in early development, meli will only be developed for the
|
|
||||||
linux kernel and respected linux distributions. Support for more UNIX-like OSes
|
|
||||||
is on the roadmap.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
Some functionality is held behind "feature gates", or compile-time flags. The following list explains each feature's purpose:
|
|
||||||
|
|
||||||
- `dbus-notifications` enables showing notifications using `dbus`.
|
|
||||||
- `notmuch` provides support for using a notmuch database as a mail backend
|
|
||||||
- `jmap` provides support for connecting to a jmap server and use it as a mail backend
|
|
||||||
- `sqlite3` provides support for builting fast search indexes in local sqlite3 databases
|
|
||||||
- `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.
|
|
||||||
- `debug-tracing` enables various trace debug logs from various places around the meli code base. The trace log is printed in `stderr`.
|
|
||||||
|
|
||||||
## Building in Debian
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
# Building with JMAP
|
|
||||||
|
|
||||||
To build with JMAP support, prepend the environment variable `MELI_FEATURES='jmap'` to your make invocation:
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
|
|
||||||
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, ie:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
MELI_CONFIG=./test_config cargo run
|
MELI_CONFIG=./test_config cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
# Testing
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
How to run specific tests:
|
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
|
```sh
|
||||||
cargo test -p {melib, meli} (-- --nocapture) (--test test_name)
|
MELI_CONFIG=./test_config meli
|
||||||
```
|
```
|
||||||
|
|
||||||
# Profiling
|
or
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
perf record -g target/debug/bin
|
meli -c ./test_config
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
92
build.rs
92
build.rs
|
@ -1,92 +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")]
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
|
|
||||||
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 mut file = File::create(&out_dir_path).unwrap();
|
|
||||||
file.write_all(&output.stdout).unwrap();
|
|
||||||
out_dir_path.pop();
|
|
||||||
|
|
||||||
out_dir_path.push("meli.conf.txt");
|
|
||||||
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 mut file = File::create(&out_dir_path).unwrap();
|
|
||||||
file.write_all(&output.stdout).unwrap();
|
|
||||||
out_dir_path.pop();
|
|
||||||
|
|
||||||
out_dir_path.push("meli-themes.txt");
|
|
||||||
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 mut file = File::create(&out_dir_path).unwrap();
|
|
||||||
file.write_all(&output.stdout).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"
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2012 Google Inc.
|
||||||
|
# Copyright 2020 Manos Pitsidianakis
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Performs client tasks for testing IMAP OAuth2 authentication.
|
||||||
|
|
||||||
|
To use this script, you'll need to have registered with Google as an OAuth
|
||||||
|
application and obtained an OAuth client ID and client secret.
|
||||||
|
See https://developers.google.com/identity/protocols/OAuth2 for instructions on
|
||||||
|
registering and for documentation of the APIs invoked by this code.
|
||||||
|
|
||||||
|
This script has 3 modes of operation.
|
||||||
|
|
||||||
|
1. The first mode is used to generate and authorize an OAuth2 token, the
|
||||||
|
first step in logging in via OAuth2.
|
||||||
|
|
||||||
|
oauth2 --user=xxx@gmail.com \
|
||||||
|
--client_id=1038[...].apps.googleusercontent.com \
|
||||||
|
--client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \
|
||||||
|
--generate_oauth2_token
|
||||||
|
|
||||||
|
The script will converse with Google and generate an oauth request
|
||||||
|
token, then present you with a URL you should visit in your browser to
|
||||||
|
authorize the token. Once you get the verification code from the Google
|
||||||
|
website, enter it into the script to get your OAuth access token. The output
|
||||||
|
from this command will contain the access token, a refresh token, and some
|
||||||
|
metadata about the tokens. The access token can be used until it expires, and
|
||||||
|
the refresh token lasts indefinitely, so you should record these values for
|
||||||
|
reuse.
|
||||||
|
|
||||||
|
2. The script will generate new access tokens using a refresh token.
|
||||||
|
|
||||||
|
oauth2 --user=xxx@gmail.com \
|
||||||
|
--client_id=1038[...].apps.googleusercontent.com \
|
||||||
|
--client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \
|
||||||
|
--refresh_token=1/Yzm6MRy4q1xi7Dx2DuWXNgT6s37OrP_DW_IoyTum4YA
|
||||||
|
|
||||||
|
3. The script will generate an OAuth2 string that can be fed
|
||||||
|
directly to IMAP or SMTP. This is triggered with the --generate_oauth2_string
|
||||||
|
option.
|
||||||
|
|
||||||
|
oauth2 --generate_oauth2_string --user=xxx@gmail.com \
|
||||||
|
--access_token=ya29.AGy[...]ezLg
|
||||||
|
|
||||||
|
The output of this mode will be a base64-encoded string. To use it, connect to a
|
||||||
|
IMAPFE and pass it as the second argument to the AUTHENTICATE command.
|
||||||
|
|
||||||
|
a AUTHENTICATE XOAUTH2 a9sha9sfs[...]9dfja929dk==
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import imaplib
|
||||||
|
import json
|
||||||
|
from optparse import OptionParser
|
||||||
|
import smtplib
|
||||||
|
import sys
|
||||||
|
import urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
|
||||||
|
def SetupOptionParser():
|
||||||
|
# Usage message is the module's docstring.
|
||||||
|
parser = OptionParser(usage=__doc__)
|
||||||
|
parser.add_option('--generate_oauth2_token',
|
||||||
|
action='store_true',
|
||||||
|
dest='generate_oauth2_token',
|
||||||
|
help='generates an OAuth2 token for testing')
|
||||||
|
parser.add_option('--generate_oauth2_string',
|
||||||
|
action='store_true',
|
||||||
|
dest='generate_oauth2_string',
|
||||||
|
help='generates an initial client response string for '
|
||||||
|
'OAuth2')
|
||||||
|
parser.add_option('--client_id',
|
||||||
|
default=None,
|
||||||
|
help='Client ID of the application that is authenticating. '
|
||||||
|
'See OAuth2 documentation for details.')
|
||||||
|
parser.add_option('--client_secret',
|
||||||
|
default=None,
|
||||||
|
help='Client secret of the application that is '
|
||||||
|
'authenticating. See OAuth2 documentation for '
|
||||||
|
'details.')
|
||||||
|
parser.add_option('--access_token',
|
||||||
|
default=None,
|
||||||
|
help='OAuth2 access token')
|
||||||
|
parser.add_option('--refresh_token',
|
||||||
|
default=None,
|
||||||
|
help='OAuth2 refresh token')
|
||||||
|
parser.add_option('--scope',
|
||||||
|
default='https://mail.google.com/',
|
||||||
|
help='scope for the access token. Multiple scopes can be '
|
||||||
|
'listed separated by spaces with the whole argument '
|
||||||
|
'quoted.')
|
||||||
|
parser.add_option('--test_imap_authentication',
|
||||||
|
action='store_true',
|
||||||
|
dest='test_imap_authentication',
|
||||||
|
help='attempts to authenticate to IMAP')
|
||||||
|
parser.add_option('--test_smtp_authentication',
|
||||||
|
action='store_true',
|
||||||
|
dest='test_smtp_authentication',
|
||||||
|
help='attempts to authenticate to SMTP')
|
||||||
|
parser.add_option('--user',
|
||||||
|
default=None,
|
||||||
|
help='email address of user whose account is being '
|
||||||
|
'accessed')
|
||||||
|
parser.add_option('--quiet',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
dest='quiet',
|
||||||
|
help='Omit verbose descriptions and only print '
|
||||||
|
'machine-readable outputs.')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
# The URL root for accessing Google Accounts.
|
||||||
|
GOOGLE_ACCOUNTS_BASE_URL = 'https://accounts.google.com'
|
||||||
|
|
||||||
|
|
||||||
|
# Hardcoded dummy redirect URI for non-web apps.
|
||||||
|
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
|
||||||
|
|
||||||
|
def AccountsUrl(command):
|
||||||
|
"""Generates the Google Accounts URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: The command to execute.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A URL for the given command.
|
||||||
|
"""
|
||||||
|
return '%s/%s' % (GOOGLE_ACCOUNTS_BASE_URL, command)
|
||||||
|
|
||||||
|
|
||||||
|
def UrlEscape(text):
|
||||||
|
# See OAUTH 5.1 for a definition of which characters need to be escaped.
|
||||||
|
return urllib.parse.quote(text, safe='~-._')
|
||||||
|
|
||||||
|
|
||||||
|
def UrlUnescape(text):
|
||||||
|
# See OAUTH 5.1 for a definition of which characters need to be escaped.
|
||||||
|
return urllib.parse.unquote(text)
|
||||||
|
|
||||||
|
|
||||||
|
def FormatUrlParams(params):
|
||||||
|
"""Formats parameters into a URL query string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
params: A key-value map.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A URL query string version of the given parameters.
|
||||||
|
"""
|
||||||
|
param_fragments = []
|
||||||
|
for param in sorted(iter(params.items()), key=lambda x: x[0]):
|
||||||
|
param_fragments.append('%s=%s' % (param[0], UrlEscape(param[1])))
|
||||||
|
return '&'.join(param_fragments)
|
||||||
|
|
||||||
|
|
||||||
|
def GeneratePermissionUrl(client_id, scope='https://mail.google.com/'):
|
||||||
|
"""Generates the URL for authorizing access.
|
||||||
|
|
||||||
|
This uses the "OAuth2 for Installed Applications" flow described at
|
||||||
|
https://developers.google.com/accounts/docs/OAuth2InstalledApp
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client_id: Client ID obtained by registering your app.
|
||||||
|
scope: scope for access token, e.g. 'https://mail.google.com'
|
||||||
|
Returns:
|
||||||
|
A URL that the user should visit in their browser.
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
params['client_id'] = client_id
|
||||||
|
params['redirect_uri'] = REDIRECT_URI
|
||||||
|
params['scope'] = scope
|
||||||
|
params['response_type'] = 'code'
|
||||||
|
return '%s?%s' % (AccountsUrl('o/oauth2/auth'),
|
||||||
|
FormatUrlParams(params))
|
||||||
|
|
||||||
|
|
||||||
|
def AuthorizeTokens(client_id, client_secret, authorization_code):
|
||||||
|
"""Obtains OAuth access token and refresh token.
|
||||||
|
|
||||||
|
This uses the application portion of the "OAuth2 for Installed Applications"
|
||||||
|
flow at https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client_id: Client ID obtained by registering your app.
|
||||||
|
client_secret: Client secret obtained by registering your app.
|
||||||
|
authorization_code: code generated by Google Accounts after user grants
|
||||||
|
permission.
|
||||||
|
Returns:
|
||||||
|
The decoded response from the Google Accounts server, as a dict. Expected
|
||||||
|
fields include 'access_token', 'expires_in', and 'refresh_token'.
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
params['client_id'] = client_id
|
||||||
|
params['client_secret'] = client_secret
|
||||||
|
params['code'] = authorization_code
|
||||||
|
params['redirect_uri'] = REDIRECT_URI
|
||||||
|
params['grant_type'] = 'authorization_code'
|
||||||
|
request_url = AccountsUrl('o/oauth2/token')
|
||||||
|
|
||||||
|
response = urllib.request.urlopen(request_url, urllib.parse.urlencode(params).encode()).read()
|
||||||
|
return json.loads(response)
|
||||||
|
|
||||||
|
|
||||||
|
def RefreshToken(client_id, client_secret, refresh_token):
|
||||||
|
"""Obtains a new token given a refresh token.
|
||||||
|
|
||||||
|
See https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client_id: Client ID obtained by registering your app.
|
||||||
|
client_secret: Client secret obtained by registering your app.
|
||||||
|
refresh_token: A previously-obtained refresh token.
|
||||||
|
Returns:
|
||||||
|
The decoded response from the Google Accounts server, as a dict. Expected
|
||||||
|
fields include 'access_token', 'expires_in', and 'refresh_token'.
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
params['client_id'] = client_id
|
||||||
|
params['client_secret'] = client_secret
|
||||||
|
params['refresh_token'] = refresh_token
|
||||||
|
params['grant_type'] = 'refresh_token'
|
||||||
|
request_url = AccountsUrl('o/oauth2/token')
|
||||||
|
|
||||||
|
response = urllib.request.urlopen(request_url, urllib.parse.urlencode(params).encode()).read()
|
||||||
|
return json.loads(response)
|
||||||
|
|
||||||
|
|
||||||
|
def GenerateOAuth2String(username, access_token, base64_encode=True):
|
||||||
|
"""Generates an IMAP OAuth2 authentication string.
|
||||||
|
|
||||||
|
See https://developers.google.com/google-apps/gmail/oauth2_overview
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: the username (email address) of the account to authenticate
|
||||||
|
access_token: An OAuth2 access token.
|
||||||
|
base64_encode: Whether to base64-encode the output.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The SASL argument for the OAuth2 mechanism.
|
||||||
|
"""
|
||||||
|
auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
|
||||||
|
if base64_encode:
|
||||||
|
auth_string = base64.b64encode(bytes(auth_string, 'utf-8'))
|
||||||
|
return auth_string
|
||||||
|
|
||||||
|
|
||||||
|
def TestImapAuthentication(user, auth_string):
|
||||||
|
"""Authenticates to IMAP with the given auth_string.
|
||||||
|
|
||||||
|
Prints a debug trace of the attempted IMAP connection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user: The Gmail username (full email address)
|
||||||
|
auth_string: A valid OAuth2 string, as returned by GenerateOAuth2String.
|
||||||
|
Must not be base64-encoded, since imaplib does its own base64-encoding.
|
||||||
|
"""
|
||||||
|
print()
|
||||||
|
imap_conn = imaplib.IMAP4_SSL('imap.gmail.com')
|
||||||
|
imap_conn.debug = 4
|
||||||
|
imap_conn.authenticate('XOAUTH2', lambda x: auth_string)
|
||||||
|
imap_conn.select('INBOX')
|
||||||
|
|
||||||
|
|
||||||
|
def TestSmtpAuthentication(user, auth_string):
|
||||||
|
"""Authenticates to SMTP with the given auth_string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user: The Gmail username (full email address)
|
||||||
|
auth_string: A valid OAuth2 string, not base64-encoded, as returned by
|
||||||
|
GenerateOAuth2String.
|
||||||
|
"""
|
||||||
|
print()
|
||||||
|
smtp_conn = smtplib.SMTP('smtp.gmail.com', 587)
|
||||||
|
smtp_conn.set_debuglevel(True)
|
||||||
|
smtp_conn.ehlo('test')
|
||||||
|
smtp_conn.starttls()
|
||||||
|
smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string))
|
||||||
|
|
||||||
|
|
||||||
|
def RequireOptions(options, *args):
|
||||||
|
missing = [arg for arg in args if getattr(options, arg) is None]
|
||||||
|
if missing:
|
||||||
|
print('Missing options: %s' % ' '.join(missing), file=sys.stderr)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
options_parser = SetupOptionParser()
|
||||||
|
(options, args) = options_parser.parse_args()
|
||||||
|
if options.refresh_token:
|
||||||
|
RequireOptions(options, 'client_id', 'client_secret')
|
||||||
|
response = RefreshToken(options.client_id, options.client_secret,
|
||||||
|
options.refresh_token)
|
||||||
|
if options.quiet:
|
||||||
|
print(response['access_token'])
|
||||||
|
else:
|
||||||
|
print('Access Token: %s' % response['access_token'])
|
||||||
|
print('Access Token Expiration Seconds: %s' % response['expires_in'])
|
||||||
|
elif options.generate_oauth2_string:
|
||||||
|
RequireOptions(options, 'user', 'access_token')
|
||||||
|
oauth2_string = GenerateOAuth2String(options.user, options.access_token)
|
||||||
|
if options.quiet:
|
||||||
|
print(oauth2_string.decode('utf-8'))
|
||||||
|
else:
|
||||||
|
print('OAuth2 argument:\n' + oauth2_string.decode('utf-8'))
|
||||||
|
elif options.generate_oauth2_token:
|
||||||
|
RequireOptions(options, 'client_id', 'client_secret')
|
||||||
|
print('To authorize token, visit this url and follow the directions:')
|
||||||
|
print(' %s' % GeneratePermissionUrl(options.client_id, options.scope))
|
||||||
|
authorization_code = input('Enter verification code: ')
|
||||||
|
response = AuthorizeTokens(options.client_id, options.client_secret,
|
||||||
|
authorization_code)
|
||||||
|
print('Refresh Token: %s' % response['refresh_token'])
|
||||||
|
print('Access Token: %s' % response['access_token'])
|
||||||
|
print('Access Token Expiration Seconds: %s' % response['expires_in'])
|
||||||
|
elif options.test_imap_authentication:
|
||||||
|
RequireOptions(options, 'user', 'access_token')
|
||||||
|
TestImapAuthentication(options.user,
|
||||||
|
GenerateOAuth2String(options.user, options.access_token,
|
||||||
|
base64_encode=False))
|
||||||
|
elif options.test_smtp_authentication:
|
||||||
|
RequireOptions(options, 'user', 'access_token')
|
||||||
|
TestSmtpAuthentication(options.user,
|
||||||
|
GenerateOAuth2String(options.user, options.access_token,
|
||||||
|
base64_encode=False))
|
||||||
|
else:
|
||||||
|
options_parser.print_help()
|
||||||
|
print('Nothing to do, exiting.')
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
|
@ -1,3 +1,80 @@
|
||||||
|
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
|
||||||
|
- Add customizable mailbox tree in sidebar
|
||||||
|
- Make `dbus` dependency opt-out (feature is `dbus-notifications`)
|
||||||
|
- Implemented JMAP async, search, tagging, syncing
|
||||||
|
- Preserve account order from configuration file
|
||||||
|
- Implemented IMAP `CONDSTORE` support for IMAP cache
|
||||||
|
- Add `timeout` setting for IMAP
|
||||||
|
- Implement TCP keepalive for IMAP
|
||||||
|
- Rewrote email address parsers.
|
||||||
|
- Implement `copy_messages` for maildir
|
||||||
|
- Implement selection with motions
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
- Fixed various problems with IMAP cache
|
||||||
|
- Fixed various problems with IMAP message counts
|
||||||
|
- Fixed various problems with IMAP connection hanging
|
||||||
|
- Fixed IMAP not reconnecting on dropped IDLE connections
|
||||||
|
- Fixed various problems with notmuch backend
|
||||||
|
|
||||||
|
-- Manos Pitsidianakis <epilys@nessuent.xyz> Thu, 24 Sep 2020 18:14:00 +0200
|
||||||
meli (0.6.1-1) buster; urgency=low
|
meli (0.6.1-1) buster; urgency=low
|
||||||
|
|
||||||
* added experimental NNTP backend
|
* added experimental NNTP backend
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
11
|
|
|
@ -1,14 +1,20 @@
|
||||||
Source: meli
|
Source: meli
|
||||||
Section: mail
|
Section: mail
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Manos Pitsidianakis <epilys@nessuent.xyz>
|
Maintainer: Manos Pitsidianakis <manos@pitsidianak.is>
|
||||||
Build-Depends: debhelper (>=11~), mandoc (>=1.14.4-1)
|
Build-Depends: debhelper-compat (=13), mandoc (>=1.14.4-1), quilt, libsqlite3-dev
|
||||||
Standards-Version: 4.1.4
|
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
|
Package: meli
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Multi-Arch: foreign
|
|
||||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||||
Recommends: libnotmuch, xdg-utils (>=1.1.3-1)
|
Recommends: xdg-utils (>=1.1.3-1), w3m, mailcap
|
||||||
Description: terminal mail client
|
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/
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: meli
|
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.
|
# Please double check copyright with the licensecheck(1) command.
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2017-2020 Manos Pitsidianakis
|
Copyright: 2017-2023 Manos Pitsidianakis
|
||||||
License: GPL-3.0+
|
License: GPL-3.0+
|
||||||
#----------------------------------------------------------------------------
|
#----------------------------------------------------------------------------
|
||||||
# License file: COPYING
|
# 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
|
meli/docs/meli.1
|
||||||
docs/meli.conf.5
|
meli/docs/meli.7
|
||||||
docs/meli-themes.5
|
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
|
Description: Fix PREFIX env var in Makefile for use in Debian
|
||||||
Author: Manos Pitsidianakis <epilys@nessuent.xyz>
|
Author: Manos Pitsidianakis <epilys@nessuent.xyz>
|
||||||
Last-Update: 2020-01-30
|
Last-Update: 2023-03-06
|
||||||
--- a/Makefile
|
Index: meli/Makefile
|
||||||
+++ b/Makefile
|
===================================================================
|
||||||
@@ -18,7 +18,7 @@
|
--- meli.orig/Makefile
|
||||||
# along with meli. If not, see <http://www.gnu.org/licenses/>.
|
+++ meli/Makefile
|
||||||
|
@@ -20,7 +20,7 @@
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
-PREFIX ?= /usr/local
|
-PREFIX ?= /usr/local
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
fix-prefix-for-debian.patch
|
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
|
#!/usr/bin/make -f
|
||||||
# You must remove unused comment lines for the released package.
|
# 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_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||||
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||||
export MELI_FEATURES = cli-docs sqlite3
|
#export MELI_FEATURES = cli-docs sqlite3
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@ --with quilt
|
dh $@ --with quilt
|
||||||
|
|
||||||
|
override_dh_auto_configure:
|
||||||
|
true
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
true
|
||||||
|
|
||||||
#override_dh_auto_install:
|
#override_dh_auto_install:
|
||||||
# dh_auto_install -- prefix=/usr
|
# dh_auto_install -- prefix=/usr
|
||||||
|
|
||||||
|
|
1076
docs/meli.conf.5
1076
docs/meli.conf.5
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -8,17 +8,14 @@ edition = "2018"
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "envelope_parse"
|
||||||
|
path = "fuzz_targets/envelope_parse.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libfuzzer-sys = "0.3"
|
libfuzzer-sys = "0.3"
|
||||||
|
melib = { path = "../melib" }
|
||||||
[dependencies.melib]
|
|
||||||
path = "../melib"
|
|
||||||
features = ["unicode_algorithms"]
|
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["."]
|
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/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::fs::File;
|
use std::{
|
||||||
use std::io::prelude::*;
|
fs::File,
|
||||||
use std::process::{Command, Stdio};
|
io::prelude::*,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
// Write ConfigStructOverride to overrides.rs
|
// Write ConfigStructOverride to overrides.rs
|
||||||
pub fn override_derive(filenames: &[(&str, &str)]) {
|
pub(crate) fn override_derive(filenames: &[(&str, &str)]) {
|
||||||
let mut output_file =
|
let mut output_file =
|
||||||
File::create("src/conf/overrides.rs").expect("Unable to open 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
|
* meli - conf/overrides.rs
|
||||||
*
|
*
|
||||||
* Copyright 2020 Manos Pitsidianakis
|
* 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/>.
|
* 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 super::*;
|
||||||
|
use melib::HeaderName;
|
||||||
|
|
||||||
"##
|
"##
|
||||||
.to_string();
|
.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 {
|
'file_loop: for (filename, ident) in filenames {
|
||||||
println!("cargo:rerun-if-changed={}", filename);
|
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));
|
.unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err));
|
||||||
|
|
||||||
let mut src = String::new();
|
let mut src = String::new();
|
||||||
|
@ -87,6 +99,14 @@ use super::*;
|
||||||
}
|
}
|
||||||
let override_ident: syn::Ident = format_ident!("{}Override", s.ident);
|
let override_ident: syn::Ident = format_ident!("{}Override", s.ident);
|
||||||
let mut field_tokentrees = vec![];
|
let mut field_tokentrees = vec![];
|
||||||
|
let mut attrs_tokens = vec![];
|
||||||
|
for attr in &s.attrs {
|
||||||
|
if let Ok(syn::Meta::List(ml)) = attr.parse_meta() {
|
||||||
|
if ml.path.get_ident().is_some() && ml.path.get_ident().unwrap() == "cfg" {
|
||||||
|
attrs_tokens.push(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut field_idents = vec![];
|
let mut field_idents = vec![];
|
||||||
for f in &s.fields {
|
for f in &s.fields {
|
||||||
let ident = &f.ident;
|
let ident = &f.ident;
|
||||||
|
@ -96,10 +116,23 @@ use super::*;
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|f| {
|
.filter_map(|f| {
|
||||||
let mut new_attr = f.clone();
|
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()
|
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")
|
if !attr_inner_value.starts_with("( default")
|
||||||
&& !attr_inner_value.starts_with("( default =")
|
&& !attr_inner_value.starts_with("( default =")
|
||||||
&& !attr_inner_value.starts_with("(default")
|
&& !attr_inner_value.starts_with("(default")
|
||||||
|
@ -140,12 +173,15 @@ use super::*;
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub #ident : Option<#ty>
|
pub #ident : Option<#ty>
|
||||||
};
|
};
|
||||||
field_idents.push(ident);
|
if !field_idents.contains(&ident) {
|
||||||
|
field_idents.push(ident);
|
||||||
|
}
|
||||||
field_tokentrees.push(t);
|
field_tokentrees.push(t);
|
||||||
}
|
}
|
||||||
//let fields = &s.fields;
|
//let fields = &s.fields;
|
||||||
|
|
||||||
let literal_struct = quote! {
|
let literal_struct = quote! {
|
||||||
|
#(#attrs_tokens)*
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct #override_ident {
|
pub struct #override_ident {
|
||||||
|
@ -153,9 +189,10 @@ use super::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#(#attrs_tokens)*
|
||||||
impl Default for #override_ident {
|
impl Default for #override_ident {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#override_ident {
|
Self {
|
||||||
#(#field_idents: None),*
|
#(#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`.
|
Binary file not shown.
|
@ -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
|
.\" You should have received a copy of the GNU General Public License
|
||||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
.\" 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
|
.Dt MELI-THEMES 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm meli-themes
|
.Nm meli-themes
|
||||||
.Nd themes for the
|
.Nd themes for the
|
||||||
.Nm meli
|
.Xr meli 1
|
||||||
mail client
|
terminal e-mail client
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm meli
|
.Nm meli
|
||||||
comes with two themes,
|
comes with two themes,
|
||||||
.Ic dark
|
.Ic dark
|
||||||
(default) and
|
(default) and
|
||||||
.Ic light .
|
.Ic light .
|
||||||
.sp
|
.Pp
|
||||||
Custom themes are defined as lists of key-values in the configuration files:
|
Custom themes are defined as lists of key-values in the configuration files:
|
||||||
.Bl -bullet -compact
|
.Bl -item -compact -offset 2
|
||||||
.It
|
.It
|
||||||
.Pa $XDG_CONFIG_HOME/meli/config.toml
|
.Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||||
.It
|
.It
|
||||||
.Pa $XDG_CONFIG_HOME/meli/themes/*.toml
|
.Pa $XDG_CONFIG_HOME/meli/themes/*.toml
|
||||||
.El
|
.El
|
||||||
.sp
|
.Pp
|
||||||
The application theme is defined in the configuration as follows:
|
The application theme is defined in the configuration as follows:
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
[terminal]
|
[terminal]
|
||||||
|
@ -56,9 +57,9 @@ keys are settings for the
|
||||||
.Ic compact
|
.Ic compact
|
||||||
mail listing style.
|
mail listing style.
|
||||||
A setting contains three fields: fg for foreground color, bg for background color, and attrs for text attribute.
|
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\&" }
|
.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.
|
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
|
An alias is a string starting with the \&"\&$\&" character and must be declared in advance in the
|
||||||
.Ic color_aliases
|
.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.
|
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.
|
This allows for defaults within a group of associated values.
|
||||||
Cyclic references in a theme results in an error:
|
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
|
.Dl spooky theme contains a cycle: fg: mail.listing.compact.even -> mail.listing.compact.highlighted -> mail.listing.compact.odd -> mail.listing.compact.even
|
||||||
.Pp
|
.Pp
|
||||||
Two themes are included by default, `light` and `dark`.
|
Two themes are included by default,
|
||||||
|
.Ql light
|
||||||
|
and
|
||||||
|
.Ql dark Ns
|
||||||
|
\&.
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
Specific settings from already defined themes can be overwritten:
|
Specific settings from already defined themes can be overwritten:
|
||||||
.Bd -literal
|
.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/
|
.Pa $XDG_CONFIG_HOME/meli/themes/
|
||||||
directory as TOML files.
|
directory as TOML files.
|
||||||
To start creating a theme right away, you can begin by editing the default theme keys and values:
|
To start creating a theme right away, you can begin by editing the default theme keys and values:
|
||||||
.sp
|
.Pp
|
||||||
.Dl meli --print-default-theme > ~/.config/meli/themes/new_theme.toml
|
.Dl meli print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||||
.sp
|
.Pp
|
||||||
.Pa new_theme.toml
|
.Pa new_theme.toml
|
||||||
will now include all keys and values of the "dark" theme.
|
will now include all keys and values of the "dark" theme.
|
||||||
.sp
|
.Pp
|
||||||
.Dl meli --print-loaded-themes
|
.Dl meli print-loaded-themes
|
||||||
.sp
|
.Pp
|
||||||
will print all loaded themes with the links resolved.
|
will print all loaded themes with the links resolved.
|
||||||
.Sh VALID ATTRIBUTE VALUES
|
.Sh VALID ATTRIBUTE VALUES
|
||||||
Case-sensitive.
|
Case-sensitive.
|
||||||
.Bl -bullet -compact
|
.Bl -dash -compact
|
||||||
.It
|
.It
|
||||||
"Default"
|
"Default"
|
||||||
.It
|
.It
|
||||||
|
@ -133,7 +138,7 @@ Any combo of the above separated by a bitwise XOR "\&|" eg "Dim | Italics"
|
||||||
.El
|
.El
|
||||||
.Sh VALID COLOR VALUES
|
.Sh VALID COLOR VALUES
|
||||||
Color values are of type String with the following valid contents:
|
Color values are of type String with the following valid contents:
|
||||||
.Bl -bullet -compact
|
.Bl -dash -compact
|
||||||
.It
|
.It
|
||||||
"Default" is the terminal default. (Case-sensitive)
|
"Default" is the terminal default. (Case-sensitive)
|
||||||
.It
|
.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)
|
name but with some modifications (for a full table see COLOR NAMES addendum) (Case-sensitive)
|
||||||
.El
|
.El
|
||||||
.Sh NO COLOR
|
.Sh NO COLOR
|
||||||
To completely disable ANSI colors, there are two options:
|
To completely disable
|
||||||
.Bl -bullet -compact
|
.Tn ANSI
|
||||||
|
colors, there are two options:
|
||||||
|
.Bl -dash -compact
|
||||||
.It
|
.It
|
||||||
Set the
|
Set the
|
||||||
.Ic use_color
|
.Ic use_color
|
||||||
|
@ -157,22 +164,37 @@ option (section
|
||||||
.It
|
.It
|
||||||
The
|
The
|
||||||
.Ev NO_COLOR
|
.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
|
When the configuration value
|
||||||
.Ic use_color
|
.Ic use_color
|
||||||
is explicitly set to true by the user,
|
is explicitly set to true by the user,
|
||||||
.Ev NO_COLOR
|
.Ev NO_COLOR
|
||||||
is ignored.
|
is ignored.
|
||||||
.El
|
.El
|
||||||
.sp
|
.Pp
|
||||||
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.
|
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
|
.Sh VALID KEYS
|
||||||
.Bl -bullet -compact
|
.Bl -dash -compact
|
||||||
.It
|
.It
|
||||||
theme_default
|
theme_default
|
||||||
.It
|
.It
|
||||||
|
error_message
|
||||||
|
.It
|
||||||
|
highlight
|
||||||
|
.It
|
||||||
status.bar
|
status.bar
|
||||||
.It
|
.It
|
||||||
|
status.command_bar
|
||||||
|
.It
|
||||||
|
status.history
|
||||||
|
.It
|
||||||
|
status.history.hints
|
||||||
|
.It
|
||||||
status.notification
|
status.notification
|
||||||
.It
|
.It
|
||||||
tab.focused
|
tab.focused
|
||||||
|
@ -193,6 +215,8 @@ widgets.options.highlighted
|
||||||
.It
|
.It
|
||||||
mail.sidebar
|
mail.sidebar
|
||||||
.It
|
.It
|
||||||
|
mail.sidebar_divider
|
||||||
|
.It
|
||||||
mail.sidebar_unread_count
|
mail.sidebar_unread_count
|
||||||
.It
|
.It
|
||||||
mail.sidebar_index
|
mail.sidebar_index
|
||||||
|
@ -225,6 +249,10 @@ mail.listing.compact.even_highlighted
|
||||||
.It
|
.It
|
||||||
mail.listing.compact.odd_highlighted
|
mail.listing.compact.odd_highlighted
|
||||||
.It
|
.It
|
||||||
|
mail.listing.compact.even_highlighted_selected
|
||||||
|
.It
|
||||||
|
mail.listing.compact.odd_highlighted_selected
|
||||||
|
.It
|
||||||
mail.listing.plain.even
|
mail.listing.plain.even
|
||||||
.It
|
.It
|
||||||
mail.listing.plain.odd
|
mail.listing.plain.odd
|
||||||
|
@ -241,6 +269,10 @@ mail.listing.plain.even_highlighted
|
||||||
.It
|
.It
|
||||||
mail.listing.plain.odd_highlighted
|
mail.listing.plain.odd_highlighted
|
||||||
.It
|
.It
|
||||||
|
mail.listing.plain.even_highlighted_selected
|
||||||
|
.It
|
||||||
|
mail.listing.plain.odd_highlighted_selected
|
||||||
|
.It
|
||||||
mail.listing.conversations
|
mail.listing.conversations
|
||||||
.It
|
.It
|
||||||
mail.listing.conversations.subject
|
mail.listing.conversations.subject
|
||||||
|
@ -249,18 +281,20 @@ mail.listing.conversations.from
|
||||||
.It
|
.It
|
||||||
mail.listing.conversations.date
|
mail.listing.conversations.date
|
||||||
.It
|
.It
|
||||||
mail.listing.conversations.padding
|
|
||||||
.It
|
|
||||||
mail.listing.conversations.unseen
|
mail.listing.conversations.unseen
|
||||||
.It
|
.It
|
||||||
mail.listing.conversations.unseen_padding
|
|
||||||
.It
|
|
||||||
mail.listing.conversations.highlighted
|
mail.listing.conversations.highlighted
|
||||||
.It
|
.It
|
||||||
mail.listing.conversations.selected
|
mail.listing.conversations.selected
|
||||||
.It
|
.It
|
||||||
|
mail.listing.conversations.highlighted_selected
|
||||||
|
.It
|
||||||
mail.view.headers
|
mail.view.headers
|
||||||
.It
|
.It
|
||||||
|
mail.view.headers_names
|
||||||
|
.It
|
||||||
|
mail.view.headers_area
|
||||||
|
.It
|
||||||
mail.view.body
|
mail.view.body
|
||||||
.It
|
.It
|
||||||
mail.view.thread.indentation.a
|
mail.view.thread.indentation.a
|
||||||
|
@ -290,7 +324,7 @@ pager.highlight_search_current
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
Aqua:14:_:Black:0
|
Aqua:14:_:Black:0
|
||||||
Aquamarine1:122:_:Maroon:1
|
Aquamarine1:122:_:Maroon:1
|
||||||
Aquamarine2:86:_:Green:2
|
Aquamarine2:86:_:Green:2
|
||||||
|
@ -326,7 +360,7 @@ DarkMagenta1:91:_:SpringGreen6:29
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
DarkOliveGreen1:192:_:Turquoise4:30
|
DarkOliveGreen1:192:_:Turquoise4:30
|
||||||
DarkOliveGreen2:155:_:DeepSkyBlue3:31
|
DarkOliveGreen2:155:_:DeepSkyBlue3:31
|
||||||
DarkOliveGreen3:191:_:DeepSkyBlue4:32
|
DarkOliveGreen3:191:_:DeepSkyBlue4:32
|
||||||
|
@ -362,7 +396,7 @@ DeepPink4:125:_:Grey37:59
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
DeepPink6:162:_:MediumPurple6:60
|
DeepPink6:162:_:MediumPurple6:60
|
||||||
DeepPink7:89:_:SlateBlue2:61
|
DeepPink7:89:_:SlateBlue2:61
|
||||||
DeepPink8:53:_:SlateBlue3:62
|
DeepPink8:53:_:SlateBlue3:62
|
||||||
|
@ -398,7 +432,7 @@ Grey19:236:_:DeepPink7:89
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
Grey23:237:_:DarkMagenta:90
|
Grey23:237:_:DarkMagenta:90
|
||||||
Grey27:238:_:DarkMagenta1:91
|
Grey27:238:_:DarkMagenta1:91
|
||||||
Grey3:232:_:DarkViolet1:92
|
Grey3:232:_:DarkViolet1:92
|
||||||
|
@ -434,7 +468,7 @@ HotPink2:169:_:LightGreen:119
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
HotPink3:132:_:LightGreen1:120
|
HotPink3:132:_:LightGreen1:120
|
||||||
HotPink4:168:_:PaleGreen1:121
|
HotPink4:168:_:PaleGreen1:121
|
||||||
IndianRed:131:_:Aquamarine1:122
|
IndianRed:131:_:Aquamarine1:122
|
||||||
|
@ -470,7 +504,7 @@ LightSlateGrey:103:_:DarkOliveGreen6:149
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
LightSteelBlue:147:_:DarkSeaGreen6:150
|
LightSteelBlue:147:_:DarkSeaGreen6:150
|
||||||
LightSteelBlue1:189:_:DarkSeaGreen3:151
|
LightSteelBlue1:189:_:DarkSeaGreen3:151
|
||||||
LightSteelBlue3:146:_:LightCyan3:152
|
LightSteelBlue3:146:_:LightCyan3:152
|
||||||
|
@ -506,7 +540,7 @@ NavajoWhite3:144:_:LightGoldenrod3:179
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
Navy:4:_:Tan:180
|
Navy:4:_:Tan:180
|
||||||
NavyBlue:17:_:MistyRose3:181
|
NavyBlue:17:_:MistyRose3:181
|
||||||
Olive:3:_:Thistle3:182
|
Olive:3:_:Thistle3:182
|
||||||
|
@ -542,7 +576,7 @@ Purple5:55:_:Salmon1:209
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
Red:9:_:LightCoral:210
|
Red:9:_:LightCoral:210
|
||||||
Red1:196:_:PaleVioletRed1:211
|
Red1:196:_:PaleVioletRed1:211
|
||||||
Red2:124:_:Orchid2:212
|
Red2:124:_:Orchid2:212
|
||||||
|
@ -578,7 +612,7 @@ Tan:180:_:Grey30:239
|
||||||
allbox tab(:);
|
allbox tab(:);
|
||||||
lb|lb|l|lb|lb
|
lb|lb|l|lb|lb
|
||||||
l l|l|l l.
|
l l|l|l l.
|
||||||
name ↓:byte:_:name:byte ↓
|
name \(da:byte:_:name:byte \(da
|
||||||
Teal:6:_:Grey35:240
|
Teal:6:_:Grey35:240
|
||||||
Thistle1:225:_:Grey39:241
|
Thistle1:225:_:Grey39:241
|
||||||
Thistle3:182:_:Grey42:242
|
Thistle3:182:_:Grey42:242
|
||||||
|
@ -599,15 +633,34 @@ Yellow6:148:_:Grey93:255
|
||||||
.Sh SEE ALSO
|
.Sh SEE ALSO
|
||||||
.Xr meli 1 ,
|
.Xr meli 1 ,
|
||||||
.Xr meli.conf 5
|
.Xr meli.conf 5
|
||||||
.Sh CONFORMING TO
|
.Sh STANDARDS
|
||||||
TOML Standard v.0.5.0 https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md
|
.Bl -item -compact
|
||||||
.sp
|
.It
|
||||||
https://no-color.org/
|
.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
|
.Sh AUTHORS
|
||||||
Copyright 2017-2019
|
Copyright 2017\(en2024
|
||||||
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
|
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||||
|
.Pp
|
||||||
Released under the GPL, version 3 or greater.
|
Released under the GPL, version 3 or greater.
|
||||||
This software carries no warranty of any kind.
|
This software carries no warranty of any kind.
|
||||||
(See COPYING for full copyright and warranty notices.)
|
.Po
|
||||||
.Pp
|
See
|
||||||
.Aq https://meli.delivery
|
.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
|
.\" You should have received a copy of the GNU General Public License
|
||||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
.\" 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
|
.Dt MELI 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm meli
|
.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
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl -help | h
|
.Op Fl -help | h
|
||||||
|
@ -43,17 +73,41 @@ if given, or at
|
||||||
.It Cm test-config Op Ar path
|
.It Cm test-config Op Ar path
|
||||||
Test a configuration file for syntax issues or missing options.
|
Test a configuration file for syntax issues or missing options.
|
||||||
.It Cm man Op Ar page
|
.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
|
.It Cm print-default-theme
|
||||||
Print default theme keys and values in TOML syntax, to be used as a blueprint.
|
Print default theme keys and values in TOML syntax, to be used as a blueprint.
|
||||||
.It Cm print-loaded-themes
|
.It Cm print-loaded-themes
|
||||||
Print all loaded themes in TOML syntax.
|
Print all loaded themes in TOML syntax.
|
||||||
|
.It Cm print-log-path
|
||||||
|
Print log file location.
|
||||||
.It Cm view
|
.It Cm view
|
||||||
View mail from input file.
|
View mail from input file.
|
||||||
.El
|
.El
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
.Nm
|
.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
|
.Bd -literal
|
||||||
^^ .-=-=-=-. ^^
|
^^ .-=-=-=-. ^^
|
||||||
^^ (`-=-=-=-=-`) ^^
|
^^ (`-=-=-=-=-`) ^^
|
||||||
|
@ -83,17 +137,32 @@ See
|
||||||
for the available configuration options.
|
for the available configuration options.
|
||||||
.Pp
|
.Pp
|
||||||
At any time, you may press
|
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.
|
for a searchable list of all available actions and shortcuts, along with every possible setting and command that your version supports.
|
||||||
.Pp
|
.Pp
|
||||||
The main visual navigation tool, the left-side sidebar may be toggled with
|
The main visual navigation tool, the left-side sidebar may be toggled with
|
||||||
.Cm `
|
.ShortcutPeriod \(ga listing toggle_menu_visibility
|
||||||
(shortcuts.listing:
|
\&.
|
||||||
.Ic toggle_menu_visibility Ns
|
|
||||||
).
|
|
||||||
.Pp
|
.Pp
|
||||||
Each mailbox may be viewed in 4 modes:
|
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
|
.Pp
|
||||||
If you're using a light color palette in your terminal, you should set
|
If you're using a light color palette in your terminal, you should set
|
||||||
.Em theme = "light"
|
.Em theme = "light"
|
||||||
|
@ -103,23 +172,26 @@ section of your configuration.
|
||||||
See
|
See
|
||||||
.Xr meli-themes 5
|
.Xr meli-themes 5
|
||||||
for complete documentation on user themes.
|
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
|
.Sh VIEWING MAIL
|
||||||
Open attachments by typing their index in the attachments list and then
|
Open attachments by typing their index in the attachments list and then
|
||||||
.Cm a
|
.ShortcutPeriod a envelope_view open_attachment
|
||||||
.Po
|
\&.
|
||||||
shortcut
|
|
||||||
.Ic open_attachment
|
|
||||||
.Pc .
|
|
||||||
.Nm
|
.Nm
|
||||||
will attempt to open text inside its pager, and other content via
|
will attempt to open text inside its pager, and other content via
|
||||||
.Cm xdg-open Ns
|
.Cm xdg-open Ns
|
||||||
\&.
|
\&.
|
||||||
Press
|
Press
|
||||||
.Cm m
|
.Shortcut m envelope_view open_mailcap
|
||||||
.Po
|
|
||||||
shortcut
|
|
||||||
.Ic open_mailcap
|
|
||||||
.Pc
|
|
||||||
instead to use the mailcap entry for the MIME type of the attachment, if any.
|
instead to use the mailcap entry for the MIME type of the attachment, if any.
|
||||||
See
|
See
|
||||||
.Sx FILES
|
.Sx FILES
|
||||||
|
@ -127,15 +199,15 @@ for the location of the mailcap files and
|
||||||
.Xr mailcap 5
|
.Xr mailcap 5
|
||||||
for their syntax.
|
for their syntax.
|
||||||
You can save individual attachments with the
|
You can save individual attachments with the
|
||||||
.Em COMMAND
|
.Command save-attachment Ar INDEX Ar path-to-file
|
||||||
.Cm save-attachment Ar INDEX Ar path-to-file
|
command.
|
||||||
where
|
|
||||||
.Ar INDEX
|
.Ar INDEX
|
||||||
is the attachment's index in the listing.
|
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.
|
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
|
.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
|
.Em IMAP
|
||||||
uses the SEARCH command,
|
uses the SEARCH command,
|
||||||
.Em notmuch
|
.Em notmuch
|
||||||
|
@ -161,9 +233,8 @@ To enable sqlite3 indexing for an account set
|
||||||
.Em search_backend
|
.Em search_backend
|
||||||
to
|
to
|
||||||
.Em sqlite3
|
.Em sqlite3
|
||||||
in the configuration file and to create the sqlite3 index issue command
|
in the configuration file and to create the sqlite3 index issue command:
|
||||||
.Cm index Ar ACCOUNT_NAME Ns \&.
|
.Command index Ar ACCOUNT_NAME Ns
|
||||||
.sp
|
|
||||||
To search in the message body type your keywords without any special formatting.
|
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:
|
To search in specific fields, prepend your search keyword with "field:" like so:
|
||||||
.Pp
|
.Pp
|
||||||
|
@ -171,7 +242,7 @@ To search in specific fields, prepend your search keyword with "field:" like so:
|
||||||
.Pp
|
.Pp
|
||||||
.D1 not ((from:unrealistic and (to:complex or not "query")) or flags:seen,draft)
|
.D1 not ((from:unrealistic and (to:complex or not "query")) or flags:seen,draft)
|
||||||
.Pp
|
.Pp
|
||||||
.D1 alladdresses:mailing@list.tld and cc:me@domain.tld
|
.D1 alladdresses:mailing@example.com and cc:me@example.com
|
||||||
.Pp
|
.Pp
|
||||||
Boolean operators are
|
Boolean operators are
|
||||||
.Em or Ns
|
.Em or Ns
|
||||||
|
@ -185,9 +256,8 @@ alias:
|
||||||
.Pc
|
.Pc
|
||||||
String keywords with spaces must be quoted.
|
String keywords with spaces must be quoted.
|
||||||
Quotes should always be escaped.
|
Quotes should always be escaped.
|
||||||
.sp
|
.Ss Important Notice about IMAP/JMAP
|
||||||
.Sy Important Notice about IMAP/JMAP
|
.HorizontalRule
|
||||||
.sp
|
|
||||||
To prevent downloading all your messages from your IMAP/JMAP server, don't set
|
To prevent downloading all your messages from your IMAP/JMAP server, don't set
|
||||||
.Em search_backend
|
.Em search_backend
|
||||||
to
|
to
|
||||||
|
@ -196,9 +266,10 @@ to
|
||||||
.Nm
|
.Nm
|
||||||
will relay your queries to the IMAP server.
|
will relay your queries to the IMAP server.
|
||||||
Expect a delay between query and response.
|
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
|
.Ss QUERY ABNF SYNTAX
|
||||||
.Bl -bullet
|
.HorizontalRule
|
||||||
|
.Bl -dash -compact
|
||||||
.It
|
.It
|
||||||
.Li query = \&"(\&" query \&")\&" | from | to | cc | bcc | alladdresses | subject | flags | has_attachments | query \&"or\&" query | query \&"and\&" query | not query
|
.Li query = \&"(\&" query \&")\&" | from | to | cc | bcc | alladdresses | subject | flags | has_attachments | query \&"or\&" query | query \&"and\&" query | not query
|
||||||
.It
|
.It
|
||||||
|
@ -228,39 +299,48 @@ Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticable
|
||||||
.It
|
.It
|
||||||
.Li flags = \&"flags:\&" flag | \&"tags:\&" flag | \&"is:\&" flag
|
.Li flags = \&"flags:\&" flag | \&"tags:\&" flag | \&"is:\&" flag
|
||||||
.El
|
.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
|
.Sh TAGS
|
||||||
.Nm
|
.Nm
|
||||||
supports tagging in notmuch and IMAP/JMAP backends.
|
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
|
Tags can be searched with the
|
||||||
.Cm tag add TAG
|
.Ns Ql tags:
|
||||||
|
or
|
||||||
|
.Ns Ql flags:
|
||||||
|
prefix in a search query, and can be modified by
|
||||||
|
.Command tag add TAG
|
||||||
and
|
and
|
||||||
.Cm tag remove TAG
|
.Command tag remove TAG
|
||||||
(see
|
(see
|
||||||
.Xr meli.conf 5 TAGS Ns
|
.Xr meli.conf 5 TAGS Ns
|
||||||
, settings
|
, settings
|
||||||
.Ic colors
|
.Ic colors
|
||||||
and
|
and
|
||||||
.Ic ignore_tags
|
.Ic ignore_tags
|
||||||
for how to set tag colors and tag visiblity)
|
for how to set tag colors and tag visibility)
|
||||||
.Sh COMPOSING
|
.Sh COMPOSING
|
||||||
.Ss Opening the message Composer tab
|
.Ss Opening the message Composer tab
|
||||||
To create a new mail message, press
|
To create a new mail message, press
|
||||||
.Cm m
|
.Shortcut m listing new_mail
|
||||||
(shortcut
|
while viewing a mailbox.
|
||||||
.Ic new_mail Ns
|
|
||||||
) while viewing a mailbox.
|
|
||||||
To reply to a mail, press
|
To reply to a mail, press
|
||||||
.Cm R
|
.ShortcutPeriod R envelope_view reply
|
||||||
.Po
|
\&.
|
||||||
shortcut
|
|
||||||
.Ic reply
|
|
||||||
.Pc .
|
|
||||||
Both these actions open the mail composer view in a new tab.
|
Both these actions open the mail composer view in a new tab.
|
||||||
.Ss Editing text
|
.Ss Editing text
|
||||||
.Bl -bullet -compact
|
.HorizontalRule
|
||||||
|
.Bl -dash -compact
|
||||||
.It
|
.It
|
||||||
Edit the header fields by selecting with the arrow keys and pressing
|
Edit the header fields by selecting with the arrow keys and pressing
|
||||||
.Cm enter
|
.Shortcut Enter general focus_in_text_field
|
||||||
to enter
|
to enter
|
||||||
.Em INSERT
|
.Em INSERT
|
||||||
mode and
|
mode and
|
||||||
|
@ -268,10 +348,8 @@ mode and
|
||||||
key to exit.
|
key to exit.
|
||||||
.It
|
.It
|
||||||
At any time you may press
|
At any time you may press
|
||||||
.Cm e
|
.Shortcut e composing edit Ns
|
||||||
(shortcut
|
to launch your editor (see
|
||||||
.Ic edit_mail Ns
|
|
||||||
) to launch your editor (see
|
|
||||||
.Xr meli.conf 5 COMPOSING Ns
|
.Xr meli.conf 5 COMPOSING Ns
|
||||||
, setting
|
, setting
|
||||||
.Ic editor_command
|
.Ic editor_command
|
||||||
|
@ -283,36 +361,40 @@ Your editor can be used in
|
||||||
.Ic embed
|
.Ic embed
|
||||||
to
|
to
|
||||||
.Em true
|
.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
|
.It
|
||||||
When launched, your editor captures all input until it exits or stops.
|
When launched, your editor captures all input until it exits or stops.
|
||||||
.It
|
.It
|
||||||
To stop your editor and return to
|
To stop your editor and return to
|
||||||
.Nm
|
.Nm
|
||||||
press Ctrl-z and to resume editing press the
|
press
|
||||||
.Ic edit_mail
|
.Aq Ctrl-z
|
||||||
command again
|
and to resume editing press the
|
||||||
.Po
|
.Ic edit
|
||||||
default
|
command again.
|
||||||
.Em e
|
|
||||||
.Pc .
|
|
||||||
.El
|
.El
|
||||||
.Ss Attachments
|
.Ss Attachments
|
||||||
|
.HorizontalRule
|
||||||
Attachments may be handled with the
|
Attachments may be handled with the
|
||||||
.Cm add-attachment Ns
|
.Cm add-attachment Ns
|
||||||
,
|
,
|
||||||
.Cm remove-attachment
|
.Cm remove-attachment
|
||||||
commands (see below).
|
commands (see below).
|
||||||
.Ss Sending
|
.Ss Sending
|
||||||
|
.HorizontalRule
|
||||||
Finally, pressing
|
Finally, pressing
|
||||||
.Cm s
|
.Shortcut s composing send_mail
|
||||||
(shortcut
|
will send your message according to your settings
|
||||||
.Ic send_mail Ns
|
|
||||||
) will send your message according to your settings
|
|
||||||
.Po
|
.Po
|
||||||
see
|
see
|
||||||
.Xr meli.conf 5 COMPOSING Ns
|
.Xr meli.conf 5 COMPOSING Ns
|
||||||
, setting
|
, setting name
|
||||||
.Ic send_mail
|
.Ic send_mail
|
||||||
.Pc Ns
|
.Pc Ns
|
||||||
\&.
|
\&.
|
||||||
|
@ -323,19 +405,19 @@ On complete failure to save your draft or sent message it will be saved in your
|
||||||
.Em tmp
|
.Em tmp
|
||||||
directory instead and you will be notified of its location.
|
directory instead and you will be notified of its location.
|
||||||
.Ss Drafts
|
.Ss Drafts
|
||||||
|
.HorizontalRule
|
||||||
To save your draft without sending it, issue
|
To save your draft without sending it, issue
|
||||||
.Em COMMAND
|
.Em COMMAND
|
||||||
.Cm close
|
.Cm close
|
||||||
and select 'save as draft'.
|
and select 'save as draft'.
|
||||||
.sp
|
.sp
|
||||||
To open a draft for further editing, select your draft in the mail listing and press
|
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
|
.Sh CONTACTS
|
||||||
.Nm
|
.Nm
|
||||||
supports two kinds of contact backends:
|
supports three kinds of contact backends:
|
||||||
.sp
|
.Bl -enum -compact
|
||||||
.Bl -enum -compact -offset indent
|
|
||||||
.It
|
.It
|
||||||
an internal format that gets saved under
|
an internal format that gets saved under
|
||||||
.Pa $XDG_DATA_HOME/meli/account_name/addressbook Ns
|
.Pa $XDG_DATA_HOME/meli/account_name/addressbook Ns
|
||||||
|
@ -348,11 +430,16 @@ The path defined as
|
||||||
.Ic vcard_folder
|
.Ic vcard_folder
|
||||||
can hold multiple vCards per file.
|
can hold multiple vCards per file.
|
||||||
They are loaded read only.
|
They are loaded read only.
|
||||||
|
.It
|
||||||
|
a
|
||||||
|
.Xr mutt 1
|
||||||
|
compatible alias file in the option
|
||||||
|
.Ic mutt_alias_file
|
||||||
.El
|
.El
|
||||||
.sp
|
.sp
|
||||||
See
|
See
|
||||||
.Xr meli.conf 5 ACCOUNTS
|
.Xr meli.conf 5 ACCOUNTS
|
||||||
for the complete account configuration values.
|
for the complete account contact configuration values.
|
||||||
.Sh MODES
|
.Sh MODES
|
||||||
.Bl -tag -compact -width 8n
|
.Bl -tag -compact -width 8n
|
||||||
.It NORMAL
|
.It NORMAL
|
||||||
|
@ -360,8 +447,10 @@ is the default mode
|
||||||
.It COMMAND
|
.It COMMAND
|
||||||
commands are issued in
|
commands are issued in
|
||||||
.Em COMMAND
|
.Em COMMAND
|
||||||
mode, by default started with Space and exited with
|
mode, by default started with
|
||||||
.Cm Esc
|
.Shortcut \&: general enter_command_mode
|
||||||
|
and exited with
|
||||||
|
.Aq Esc
|
||||||
key.
|
key.
|
||||||
.It EMBED
|
.It EMBED
|
||||||
is the mode of the embed terminal emulator
|
is the mode of the embed terminal emulator
|
||||||
|
@ -370,8 +459,9 @@ captures all input as text input, and is exited with
|
||||||
.Cm Esc
|
.Cm Esc
|
||||||
key.
|
key.
|
||||||
.El
|
.El
|
||||||
.Ss COMMAND Mode
|
.Sh COMMAND
|
||||||
.Ss Mail listing commands
|
.Ss Mail listing commands
|
||||||
|
.HorizontalRule
|
||||||
.Bl -tag -width 36n
|
.Bl -tag -width 36n
|
||||||
.It Cm set Ar plain | threaded | compact | conversations
|
.It Cm set Ar plain | threaded | compact | conversations
|
||||||
set the way mailboxes are displayed
|
set the way mailboxes are displayed
|
||||||
|
@ -395,32 +485,51 @@ where
|
||||||
is a mailbox prefixed with the
|
is a mailbox prefixed with the
|
||||||
.Ar n
|
.Ar n
|
||||||
number in the side menu for the current account
|
number in the side menu for the current account
|
||||||
.It Cm toggle_thread_snooze
|
.It Cm toggle thread_snooze
|
||||||
don't issue notifications for thread under cursor in thread listing
|
don't issue notifications for thread under cursor in thread listing
|
||||||
.It Cm search Ar STRING
|
.It Cm search Ar STRING
|
||||||
search mailbox with
|
search mailbox with
|
||||||
.Ar STRING
|
.Ar STRING
|
||||||
key.
|
query.
|
||||||
Escape exits search results
|
Escape exits search results.
|
||||||
.It Cm set read, set unread
|
.It Cm select Ar STRING
|
||||||
Set read status of message.
|
select threads matching
|
||||||
.It Cm create-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
.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
|
||||||
|
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.
|
||||||
|
.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
|
||||||
create mailbox with given path.
|
create mailbox with given path.
|
||||||
Be careful with backends and separator sensitivity (eg IMAP)
|
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
|
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
|
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
|
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.
|
deletes mailbox in the mail backend.
|
||||||
This action is unreversible.
|
This action is irreversible.
|
||||||
.El
|
.El
|
||||||
.Ss Mail view commands
|
.Ss Mail view commands
|
||||||
|
.HorizontalRule
|
||||||
.Bl -tag -width 36n
|
.Bl -tag -width 36n
|
||||||
.It Cm pipe Ar EXECUTABLE Ar ARGS
|
.It Cm pipe Ar EXECUTABLE Ar ARGS
|
||||||
pipe pager contents to binary
|
pipe pager contents to binary
|
||||||
|
.It Cm filter Ar EXECUTABLE Ar ARGS
|
||||||
|
filter and display pager contents through command
|
||||||
.It Cm list-post
|
.It Cm list-post
|
||||||
post in list of viewed envelope
|
post in list of viewed envelope
|
||||||
.It Cm list-unsubscribe
|
.It Cm list-unsubscribe
|
||||||
|
@ -429,8 +538,13 @@ unsubscribe automatically from list of viewed envelope
|
||||||
open list archive with
|
open list archive with
|
||||||
.Cm xdg-open
|
.Cm xdg-open
|
||||||
.El
|
.El
|
||||||
.Ss composing mail commands
|
.Ss Composing mail commands
|
||||||
|
.HorizontalRule
|
||||||
.Bl -tag -width 36n
|
.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
|
.It Cm add-attachment Ar PATH
|
||||||
in composer, add
|
in composer, add
|
||||||
.Ar PATH
|
.Ar PATH
|
||||||
|
@ -439,6 +553,16 @@ as an attachment
|
||||||
in composer, pipe
|
in composer, pipe
|
||||||
.Ar CMD Ar ARGS
|
.Ar CMD Ar ARGS
|
||||||
output into an attachment
|
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
|
.It Cm remove-attachment Ar INDEX
|
||||||
remove attachment with given index
|
remove attachment with given index
|
||||||
.It Cm toggle sign
|
.It Cm toggle sign
|
||||||
|
@ -450,7 +574,8 @@ for PGP configuration.
|
||||||
.It Cm save-draft
|
.It Cm save-draft
|
||||||
saves a copy of the draft in the Draft folder
|
saves a copy of the draft in the Draft folder
|
||||||
.El
|
.El
|
||||||
.Ss generic commands
|
.Ss Generic commands
|
||||||
|
.HorizontalRule
|
||||||
.Bl -tag -width 36n
|
.Bl -tag -width 36n
|
||||||
.It Cm open-in-tab
|
.It Cm open-in-tab
|
||||||
opens envelope view in new tab
|
opens envelope view in new tab
|
||||||
|
@ -464,11 +589,16 @@ to
|
||||||
.It Cm printenv Ar KEY
|
.It Cm printenv Ar KEY
|
||||||
print environment variable
|
print environment variable
|
||||||
.Ar KEY
|
.Ar KEY
|
||||||
|
.It Cm quit
|
||||||
|
Quits
|
||||||
|
.Nm Ns
|
||||||
|
\&.
|
||||||
|
.It Cm reload-config
|
||||||
|
Reloads configuration but only if account configuration is unchanged.
|
||||||
|
Useful if you want to reload some settings without restarting
|
||||||
|
.Nm Ns
|
||||||
|
\&.
|
||||||
.El
|
.El
|
||||||
.Sh SHORTCUTS
|
|
||||||
See
|
|
||||||
.Xr meli.conf 5 SHORTCUTS
|
|
||||||
for shortcuts and their default values.
|
|
||||||
.Sh EXIT STATUS
|
.Sh EXIT STATUS
|
||||||
.Nm
|
.Nm
|
||||||
exits with 0 on a successful run.
|
exits with 0 on a successful run.
|
||||||
|
@ -486,7 +616,9 @@ Specifies the editor to use
|
||||||
.It Ev MELI_CONFIG
|
.It Ev MELI_CONFIG
|
||||||
Override the configuration file
|
Override the configuration file
|
||||||
.It Ev NO_COLOR
|
.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
|
The configuration value
|
||||||
.Ic use_color
|
.Ic use_color
|
||||||
overrides this.
|
overrides this.
|
||||||
|
@ -543,22 +675,265 @@ Mailcap entries are searched for in the following files, in this order:
|
||||||
.It
|
.It
|
||||||
.Pa /usr/local/etc/mailcap
|
.Pa /usr/local/etc/mailcap
|
||||||
.El
|
.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
|
.Sh SEE ALSO
|
||||||
.Xr meli.conf 5 ,
|
.Xr meli.conf 5 ,
|
||||||
.Xr meli-themes 5 ,
|
.Xr meli-themes 5 ,
|
||||||
|
.Xr meli 7 ,
|
||||||
.Xr xdg-open 1 ,
|
.Xr xdg-open 1 ,
|
||||||
.Xr mailcap 5
|
.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
|
.Sh AUTHORS
|
||||||
Copyright 2017-2019
|
Copyright 2017\(en2024
|
||||||
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
|
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||||
|
.Pp
|
||||||
Released under the GPL, version 3 or greater.
|
Released under the GPL, version 3 or greater.
|
||||||
This software carries no warranty of any kind.
|
This software carries no warranty of any kind.
|
||||||
(See COPYING for full copyright and warranty notices.)
|
.Po
|
||||||
.Pp
|
See
|
||||||
.Aq https://meli.delivery
|
.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
|
File diff suppressed because it is too large
Load Diff
|
@ -11,8 +11,8 @@
|
||||||
#[accounts.account-name]
|
#[accounts.account-name]
|
||||||
#root_mailbox = "/path/to/root/mailbox"
|
#root_mailbox = "/path/to/root/mailbox"
|
||||||
#format = "Maildir"
|
#format = "Maildir"
|
||||||
#index_style = "Conversations" # or [plain, threaded, compact]
|
#listing.index_style = "Conversations" # or [plain, threaded, compact]
|
||||||
#identity="email@address.tld"
|
#identity="email@example.com"
|
||||||
#display_name = "Name"
|
#display_name = "Name"
|
||||||
#subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
#subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||||
#
|
#
|
||||||
|
@ -26,21 +26,21 @@
|
||||||
#[accounts.mbox]
|
#[accounts.mbox]
|
||||||
#root_mailbox = "/var/mail/username"
|
#root_mailbox = "/var/mail/username"
|
||||||
#format = "mbox"
|
#format = "mbox"
|
||||||
#index_style = "Compact"
|
#listing.index_style = "Compact"
|
||||||
#identity="username@hostname.local"
|
#identity="username@hostname.local"
|
||||||
#
|
#
|
||||||
## Setting up an IMAP account
|
## Setting up an IMAP account
|
||||||
#[accounts."imap"]
|
#[accounts."imap"]
|
||||||
#root_mailbox = "INBOX"
|
#root_mailbox = "INBOX"
|
||||||
#format = "imap"
|
#format = "imap"
|
||||||
#server_hostname="mail.server.tld"
|
#server_hostname="mail.example.com"
|
||||||
#server_password="pha2hiLohs2eeeish2phaii1We3ood4chakaiv0hien2ahie3m"
|
#server_password="pha2hiLohs2eeeish2phaii1We3ood4chakaiv0hien2ahie3m"
|
||||||
#server_username="username@server.tld"
|
#server_username="username@example.com"
|
||||||
#server_port="993" # imaps
|
##server_port="993" # imaps
|
||||||
#server_port="143" # STARTTLS
|
#server_port="143" # STARTTLS
|
||||||
#use_starttls=true #optional
|
#use_starttls=true #optional
|
||||||
#index_style = "Conversations"
|
#listing.index_style = "Conversations"
|
||||||
#identity = "username@server.tld"
|
#identity = "username@example.com"
|
||||||
#display_name = "Name Name"
|
#display_name = "Name Name"
|
||||||
### match every mailbox:
|
### match every mailbox:
|
||||||
#subscribed_mailboxes = ["*" ]
|
#subscribed_mailboxes = ["*" ]
|
||||||
|
@ -48,22 +48,50 @@
|
||||||
##subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
##subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||||
#
|
#
|
||||||
## Setting up an account for an already existing notmuch database
|
## Setting up an account for an already existing notmuch database
|
||||||
#[accounts.notmuch]
|
##[accounts.notmuch]
|
||||||
#root_mailbox = "/path/to/folder" # where .notmuch/ directory is located
|
##root_mailbox = "/path/to/folder" # where .notmuch/ directory is located
|
||||||
#format = "notmuch"
|
##format = "notmuch"
|
||||||
#index_style = "conversations"
|
##listing.index_style = "conversations"
|
||||||
#identity="username@server.tld"
|
##identity="username@example.com"
|
||||||
#display_name = "Name Name"
|
##display_name = "Name Name"
|
||||||
# # notmuch mailboxes are virtual, they are defined by their alias and the notmuch query that corresponds to their content.
|
## # notmuch mailboxes are virtual, they are defined by their alias and the notmuch query that corresponds to their content.
|
||||||
# [accounts.notmuch.mailboxes]
|
## [accounts.notmuch.mailboxes]
|
||||||
# "INBOX" = { query="tag:inbox", subscribe = true }
|
## "INBOX" = { query="tag:inbox", subscribe = true }
|
||||||
# "Drafts" = { query="tag:draft", subscribe = true }
|
## "Drafts" = { query="tag:draft", subscribe = true }
|
||||||
# "Sent" = { query="from:username@server.tld from:username2@server.tld", subscribe = true }
|
## "Sent" = { query="from:username@example.com from:username2@example.com", subscribe = true }
|
||||||
##
|
##
|
||||||
|
## Setting up a Gmail account
|
||||||
|
#[accounts."gmail"]
|
||||||
|
#root_mailbox = '[Gmail]'
|
||||||
|
#format = "imap"
|
||||||
|
#server_hostname='imap.gmail.com'
|
||||||
|
#server_password="password"
|
||||||
|
#server_username="username@gmail.com"
|
||||||
|
#server_port="993"
|
||||||
|
#listing.index_style = "Conversations"
|
||||||
|
#identity = "username@gmail.com"
|
||||||
|
#display_name = "Name Name"
|
||||||
|
### match every mailbox:
|
||||||
|
#subscribed_mailboxes = ["*" ]
|
||||||
|
#composing.send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||||
|
### 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]
|
#[pager]
|
||||||
#filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
#filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
||||||
#pager_context = 0 # default, optional
|
#pager_context = 0 # default, optional
|
||||||
#headers_sticky = true # default, optional
|
#sticky_headers = true # default, optional
|
||||||
#
|
#
|
||||||
#[notifications]
|
#[notifications]
|
||||||
#script = "notify-send"
|
#script = "notify-send"
|
||||||
|
@ -73,11 +101,7 @@
|
||||||
#
|
#
|
||||||
###shortcuts
|
###shortcuts
|
||||||
#[shortcuts.composing]
|
#[shortcuts.composing]
|
||||||
#edit_mail = 'e'
|
#edit = 'e'
|
||||||
#
|
|
||||||
##Thread view defaults:
|
|
||||||
#[shortcuts.compact-listing]
|
|
||||||
#exit_thread = 'i'
|
|
||||||
#
|
#
|
||||||
#[shortcuts.contact-list]
|
#[shortcuts.contact-list]
|
||||||
#create_contact = 'c'
|
#create_contact = 'c'
|
||||||
|
@ -93,6 +117,7 @@
|
||||||
#next_account = 'h'
|
#next_account = 'h'
|
||||||
#new_mail = 'm'
|
#new_mail = 'm'
|
||||||
#set_seen = 'n'
|
#set_seen = 'n'
|
||||||
|
#exit_entry = 'i'
|
||||||
#
|
#
|
||||||
##Pager defaults
|
##Pager defaults
|
||||||
#
|
#
|
||||||
|
@ -105,14 +130,13 @@
|
||||||
#[composing]
|
#[composing]
|
||||||
##required for sending e-mail
|
##required for sending e-mail
|
||||||
#send_mail = 'msmtp --read-recipients --read-envelope-from'
|
#send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||||
##send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
##send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||||
#editor_command = 'vim +/^$' # optional, by default $EDITOR is used.
|
#editor_command = 'vim +/^$' # optional, by default $EDITOR is used.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#[pgp]
|
#[pgp]
|
||||||
#auto_sign = false # always sign sent messages
|
#auto_sign = false # always sign sent messages
|
||||||
#auto_verify_signatures = true # always verify signatures when reading signed e-mails
|
#auto_verify_signatures = true # always verify signatures when reading signed e-mails
|
||||||
#gpg_binary = "/usr/bin/gpg2" #optional
|
|
||||||
#
|
#
|
||||||
#[terminal]
|
#[terminal]
|
||||||
#theme = "dark" # or "light"
|
#theme = "dark" # or "light"
|
|
@ -0,0 +1,70 @@
|
||||||
|
[terminal.themes.nord]
|
||||||
|
"theme_default" = { fg = "$nord6", bg = "$nord0", attrs = "Default" }
|
||||||
|
"mail.listing.compact.even" = { fg = "theme_default", bg = "$nord1", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.odd" = { fg = "theme_default", bg = "$nord2", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.even" = { fg = "theme_default", bg = "$nord1", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.odd" = { fg = "theme_default", bg = "$nord2", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.even_highlighted" = { fg = "$nord0", bg = "$focused_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.odd_highlighted" = { fg = "$nord0", bg = "$focused_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.conversations.highlighted" = { fg = "$nord0", bg = "$focused_bg", 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 = "theme_default", bg = "$nord2", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.even_highlighted" = { fg = "$nord0", bg = "$focused_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.odd_highlighted" = { fg = "$nord0", bg = "$focused_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.tag_default" = { fg = "theme_default", bg = "$nord8", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted" = { fg = "$nord1", bg = "$focused_bg", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account" = { fg = "$nord5", bg = "$nord1", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account_name" = { fg = "mail.sidebar_highlighted_account", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account_index" = { fg = "mail.sidebar_highlighted_account", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account_unread_count" = { fg = "mail.sidebar_highlighted_account", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_index" = { fg = "mail.sidebar_index", bg = "mail.sidebar_highlighted", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_unread_count" = { fg = "mail.sidebar_highlighted", bg = "mail.sidebar_highlighted", attrs = "theme_default" }
|
||||||
|
"mail.sidebar" = { fg = "$nord5", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_account_name" = { fg = "$nord5", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_index" = { fg = "$nord1", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_unread_count" = { fg = "$nord1", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.view.body" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.view.headers" = { fg = "$nord9", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.a" = { fg = "theme_default", bg = "$nord11", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.b" = { fg = "theme_default", bg = "$nord12", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.c" = { fg = "theme_default", bg = "$nord13", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.d" = { fg = "theme_default", bg = "$nord14", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.e" = { fg = "theme_default", bg = "$nord15", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.f" = { fg = "theme_default", bg = "$nord13", attrs = "theme_default" }
|
||||||
|
"pager.highlight_search" = { fg = "$nord5", bg = "$nord7", attrs = "Bold" }
|
||||||
|
"pager.highlight_search_current" = { fg = "$nord7", bg = "$nord10", attrs = "Bold" }
|
||||||
|
"status.bar" = { fg = "$nord5", bg = "$nord3", attrs = "theme_default" }
|
||||||
|
"status.notification" = { fg = "$nord5", bg = "$nord3", attrs = "theme_default" }
|
||||||
|
"tab.bar" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"tab.focused" = { fg = "$nord1", bg = "$focused_bg", attrs = "theme_default" }
|
||||||
|
"tab.unfocused" = { fg = "$nord4", bg = "$unfocused_bg", attrs = "theme_default" }
|
||||||
|
"widgets.form.field" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"widgets.form.highlighted" = { fg = "theme_default", bg = "$nord2", attrs = "theme_default" }
|
||||||
|
"widgets.form.label" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||||
|
"widgets.list.header" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||||
|
"widgets.options.highlighted" = { fg = "theme_default", bg = "$nord2", attrs = "theme_default" }
|
||||||
|
|
||||||
|
[terminal.themes.nord.color_aliases]
|
||||||
|
nord0 = "#2e3440"
|
||||||
|
nord1 = "#3b4252"
|
||||||
|
nord2 = "#434c5e"
|
||||||
|
nord3 = "#4c566a"
|
||||||
|
# snow storm
|
||||||
|
nord4 = "#d8dee9"
|
||||||
|
nord5 = "#e5e9f0"
|
||||||
|
nord6 = "#eceff4"
|
||||||
|
# frost
|
||||||
|
nord7 = "#8fbcbb"
|
||||||
|
nord8 = "#88c0d0"
|
||||||
|
nord9 = "#81a1c1"
|
||||||
|
nord10 = "#5e81ac"
|
||||||
|
# aurora
|
||||||
|
nord11 = "#bf616a"
|
||||||
|
nord12 = "#d08770"
|
||||||
|
nord13 = "#ebcb8b"
|
||||||
|
nord14 = "#a3be8c"
|
||||||
|
nord15 = "#b48ead"
|
||||||
|
# semantics
|
||||||
|
focused_bg = "$nord8"
|
||||||
|
unfocused_bg = "$nord3"
|
|
@ -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.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.from" = { fg = "$darkgrey", bg = "theme_default", attrs = "theme_default" }
|
||||||
"mail.listing.conversations.highlighted" = { fg = "theme_default", bg = "Grey58", 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.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.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" = { 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" = { 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_highlighted" = { fg = "theme_default", bg = "Grey58", attrs = "theme_default" }
|
||||||
"mail.listing.plain.even_selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
"mail.listing.plain.even_selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||||
|
@ -51,7 +49,7 @@ color_aliases = { "neon_green" = "#6ef9d4", "darkgrey" = "#4a4a4a", "neon_purp
|
||||||
"pager.highlight_search" = { fg = "White", bg = "Teal", attrs = "Bold" }
|
"pager.highlight_search" = { fg = "White", bg = "Teal", attrs = "Bold" }
|
||||||
"pager.highlight_search_current" = { fg = "White", bg = "NavyBlue", attrs = "Bold" }
|
"pager.highlight_search_current" = { fg = "White", bg = "NavyBlue", attrs = "Bold" }
|
||||||
"status.bar" = { fg = "White", bg = "Black", attrs = "theme_default" }
|
"status.bar" = { fg = "White", bg = "Black", attrs = "theme_default" }
|
||||||
"status.notification" = { fg = "Plum1", bg = "DarkRed", attrs = "theme_default" }
|
"status.notification" = { fg = "Plum1", bg = "theme_default", attrs = "theme_default" }
|
||||||
"tab.bar" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
"tab.bar" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
"tab.focused" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
"tab.focused" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
"tab.unfocused" = { fg = "$darkgrey", bg = "Black", attrs = "theme_default" }
|
"tab.unfocused" = { fg = "$darkgrey", bg = "Black", attrs = "theme_default" }
|
|
@ -0,0 +1,69 @@
|
||||||
|
[terminal.themes.sail]
|
||||||
|
color_aliases = { "unseen_fg" = "theme_default", "unseen_bg" = "theme_default", "sea" = "#91C7FF", "dimmed_text" = "#afbec5", "dimmed_bg" = "Grey78", "header" = "#edeff1" }
|
||||||
|
"theme_default" = { fg = "#37474f", bg = "White", attrs = "Default" }
|
||||||
|
"mail.listing.attachment_flag" = { fg = "Blue", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.listing.compact.even" = { fg = "$dimmed_text", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.even_highlighted" = { fg = "theme_default", bg = "$dimmed_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.even_selected" = { fg = "$dimmed_text", bg = "LightCoral", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.even_unseen" = { fg = "$unseen_fg", bg = "$unseen_bg", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.listing.compact.odd" = { fg = "$dimmed_text", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.odd_highlighted" = { fg = "theme_default", bg = "$dimmed_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.odd_selected" = { fg = "$dimmed_text", bg = "LightCoral", attrs = "theme_default" }
|
||||||
|
"mail.listing.compact.odd_unseen" = { fg = "$unseen_fg", bg = "$unseen_bg", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.listing.plain.even" = { fg = "mail.listing.compact.even", bg = "mail.listing.compact.even", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.even_highlighted" = { fg = "mail.listing.compact.even_highlighted", bg = "mail.listing.compact.even_highlighted", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.even_selected" = { fg = "mail.listing.compact.even_selected", bg = "mail.listing.compact.even_selected", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.even_unseen" = { fg = "mail.listing.compact.even_unseen", bg = "mail.listing.compact.even_unseen", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.odd" = { fg = "mail.listing.compact.odd", bg = "mail.listing.compact.odd", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.odd_highlighted" = { fg = "mail.listing.compact.odd_highlighted", bg = "mail.listing.compact.odd_highlighted", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.odd_selected" = { fg = "mail.listing.compact.odd_selected", bg = "mail.listing.compact.odd_selected", attrs = "theme_default" }
|
||||||
|
"mail.listing.plain.odd_unseen" = { fg = "mail.listing.compact.odd_unseen", bg = "mail.listing.compact.odd_unseen", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.listing.conversations" = { fg = "$dimmed_text", bg = "theme_default", attrs = "Default" }
|
||||||
|
"mail.listing.conversations.date" = { fg = "mail.listing.conversations", bg = "mail.listing.conversations", attrs = "theme_default" }
|
||||||
|
"mail.listing.conversations.from" = { fg = "mail.listing.conversations", bg = "mail.listing.conversations", attrs = "theme_default" }
|
||||||
|
"mail.listing.conversations.subject" = { fg = "mail.listing.conversations", bg = "mail.listing.conversations", attrs = "theme_default" }
|
||||||
|
"mail.listing.conversations.highlighted" = { fg = "theme_default", bg = "$dimmed_bg", attrs = "theme_default" }
|
||||||
|
"mail.listing.conversations.selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||||
|
"mail.listing.conversations.unseen" = { fg = "$unseen_fg", bg = "$unseen_bg", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.listing.tag_default" = { fg = "Black", bg = "$dimmed_text", attrs = "theme_default" }
|
||||||
|
"mail.listing.thread_snooze_flag" = { fg = "Red", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.sidebar" = { fg = "$dimmed_text", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted" = { fg = "theme_default", bg = "$dimmed_bg", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account" = { fg = "$dimmed_text", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account_name" = { fg = "theme_default", bg = "$header", attrs = "Bold" }
|
||||||
|
"mail.sidebar_account_name" = { fg = "mail.sidebar", bg = "mail.sidebar", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account_index" = { fg = "mail.sidebar_index", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_account_unread_count" = { fg = "mail.sidebar_unread_count", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_index" = { fg = "mail.sidebar_index", bg = "mail.sidebar_highlighted", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_highlighted_unread_count" = { fg = "mail.sidebar_highlighted", bg = "mail.sidebar_highlighted", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_index" = { fg = "mail.sidebar", bg = "mail.sidebar", attrs = "theme_default" }
|
||||||
|
"mail.sidebar_unread_count" = { fg = "$dimmed_text", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"mail.view.body" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.view.headers" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"mail.view.headers_names" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||||
|
"mail.view.thread.indentation.a" = { fg = "theme_default", bg = "#EC633D", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.b" = { fg = "theme_default", bg = "#D347F9", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.c" = { fg = "theme_default", bg = "#317EFB", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.d" = { fg = "theme_default", bg = "#06B8CD", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.e" = { fg = "theme_default", bg = "#93DDB6", attrs = "theme_default" }
|
||||||
|
"mail.view.thread.indentation.f" = { fg = "theme_default", bg = "#68A033", attrs = "theme_default" }
|
||||||
|
|
||||||
|
"pager.highlight_search" = { fg = "White", bg = "Teal", attrs = "Bold" }
|
||||||
|
"pager.highlight_search_current" = { fg = "White", bg = "NavyBlue", attrs = "Bold" }
|
||||||
|
"status.bar" = { fg = "theme_default", bg = "$sea", attrs = "theme_default" }
|
||||||
|
"status.notification" = { fg = "Plum1", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"tab.bar" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"tab.focused" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||||
|
"tab.unfocused" = { fg = "White", bg = "$sea", attrs = "Bold" }
|
||||||
|
"widgets.form.field" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
|
"widgets.form.highlighted" = { fg = "theme_default", bg = "$dimmed_bg", attrs = "theme_default" }
|
||||||
|
"widgets.form.label" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||||
|
"widgets.list.header" = { fg = "Black", bg = "White", attrs = "Bold" }
|
||||||
|
"widgets.options.highlighted" = { fg = "theme_default", bg = "$dimmed_bg", attrs = "theme_default" }
|
|
@ -13,11 +13,9 @@
|
||||||
"mail.listing.conversations" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
"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.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.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.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.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" = { 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.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.odd" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
"mail.listing.plain.even_unseen" = { 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" = { 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.from" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||||
"mail.listing.conversations.highlighted" = { fg = "$JewelGreen", bg = "$SunsetOrange", 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.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.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" = { 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.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.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" }
|
"mail.listing.plain.even_unseen" = { fg = "theme_default", bg = "mail.listing.compact.even_unseen", attrs = "theme_default" }
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
After Width: | Height: | Size: 204 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -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,103 @@
|
||||||
|
/*
|
||||||
|
* 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::*;
|
||||||
|
#[cfg(feature = "cli-docs")]
|
||||||
|
use crate::manpages;
|
||||||
|
|
||||||
|
#[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>>,
|
||||||
|
}
|
|
@ -0,0 +1,726 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AlternativeStrings(v) => {
|
||||||
|
for t in v.iter() {
|
||||||
|
sugg.insert(format!("{}{}", if s.is_empty() { " " } else { "" }, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AlternativeStrings(v) => {
|
||||||
|
for lit in v.iter() {
|
||||||
|
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![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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]),
|
||||||
|
AlternativeStrings(&'static [&'static str]),
|
||||||
|
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: ["man"],
|
||||||
|
desc: "read documentation",
|
||||||
|
tokens: {
|
||||||
|
#[cfg(feature = "cli-docs")]
|
||||||
|
{
|
||||||
|
&[One(Literal("man")), One(AlternativeStrings(crate::manpages::POSSIBLE_VALUES))]
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "cli-docs"))]
|
||||||
|
{ &[] }
|
||||||
|
},
|
||||||
|
parser: parser::view_manpage
|
||||||
|
},
|
||||||
|
{ 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: "foo".into(),
|
||||||
|
suggestions: Some(&[
|
||||||
|
"seen",
|
||||||
|
"unseen",
|
||||||
|
"plain",
|
||||||
|
"threaded",
|
||||||
|
"compact",
|
||||||
|
"conversations"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
.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,15 +19,19 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* 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;
|
use std::path::PathBuf;
|
||||||
pub use melib::thread::{SortField, SortOrder};
|
|
||||||
|
|
||||||
extern crate uuid;
|
use melib::{email::mailto::Mailto, Flag, SortField, SortOrder};
|
||||||
use uuid::Uuid;
|
|
||||||
|
use crate::components::{Component, ComponentId};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FlagAction {
|
||||||
|
Set(Flag),
|
||||||
|
Unset(Flag),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TagAction {
|
pub enum TagAction {
|
||||||
|
@ -49,16 +53,25 @@ pub enum ListingAction {
|
||||||
CopyToOtherAccount(AccountName, MailboxPath),
|
CopyToOtherAccount(AccountName, MailboxPath),
|
||||||
MoveTo(MailboxPath),
|
MoveTo(MailboxPath),
|
||||||
MoveToOtherAccount(AccountName, MailboxPath),
|
MoveToOtherAccount(AccountName, MailboxPath),
|
||||||
|
Import(PathBuf, MailboxPath),
|
||||||
|
ExportMbox(Option<melib::mbox::MboxFormat>, PathBuf),
|
||||||
Delete,
|
Delete,
|
||||||
OpenInNewTab,
|
OpenInNewTab,
|
||||||
Tag(TagAction),
|
Tag(TagAction),
|
||||||
|
Flag(FlagAction),
|
||||||
|
ClearSelection,
|
||||||
|
ToggleThreadSnooze,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TabAction {
|
pub enum TabAction {
|
||||||
Close,
|
Close,
|
||||||
Kill(Uuid),
|
Kill(ComponentId),
|
||||||
New(Option<Box<dyn Component>>),
|
New(Option<Box<dyn Component>>),
|
||||||
|
ManageMailboxes,
|
||||||
|
ManageJobs,
|
||||||
|
#[cfg(feature = "cli-docs")]
|
||||||
|
Man(crate::manpages::ManPages),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -71,16 +84,22 @@ pub enum MailingListAction {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ViewAction {
|
pub enum ViewAction {
|
||||||
Pipe(String, Vec<String>),
|
Pipe(String, Vec<String>),
|
||||||
|
Filter(String),
|
||||||
SaveAttachment(usize, String),
|
SaveAttachment(usize, String),
|
||||||
|
ExportMail(String),
|
||||||
|
AddAddressesToContacts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ComposeAction {
|
pub enum ComposeAction {
|
||||||
AddAttachment(String),
|
AddAttachment(String),
|
||||||
|
AddAttachmentFilePicker(Option<String>),
|
||||||
AddAttachmentPipe(String),
|
AddAttachmentPipe(String),
|
||||||
RemoveAttachment(usize),
|
RemoveAttachment(usize),
|
||||||
SaveDraft,
|
SaveDraft,
|
||||||
ToggleSign,
|
ToggleSign,
|
||||||
|
ToggleEncrypt,
|
||||||
|
Mailto(Mailto),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -105,40 +124,71 @@ pub enum Action {
|
||||||
Listing(ListingAction),
|
Listing(ListingAction),
|
||||||
ViewMailbox(usize),
|
ViewMailbox(usize),
|
||||||
Sort(SortField, SortOrder),
|
Sort(SortField, SortOrder),
|
||||||
|
SortColumn(usize, SortOrder),
|
||||||
SubSort(SortField, SortOrder),
|
SubSort(SortField, SortOrder),
|
||||||
Tab(TabAction),
|
Tab(TabAction),
|
||||||
ToggleThreadSnooze,
|
|
||||||
MailingListAction(MailingListAction),
|
MailingListAction(MailingListAction),
|
||||||
View(ViewAction),
|
View(ViewAction),
|
||||||
SetEnv(String, String),
|
SetEnv(String, String),
|
||||||
PrintEnv(String),
|
PrintEnv(String),
|
||||||
|
CurrentDirectory,
|
||||||
|
ChangeCurrentDirectory(PathBuf),
|
||||||
Compose(ComposeAction),
|
Compose(ComposeAction),
|
||||||
Mailbox(AccountName, MailboxOperation),
|
Mailbox(AccountName, MailboxOperation),
|
||||||
AccountAction(AccountName, AccountAction),
|
AccountAction(AccountName, AccountAction),
|
||||||
PrintSetting(String),
|
PrintSetting(String),
|
||||||
|
ReloadConfiguration,
|
||||||
|
ToggleMouse,
|
||||||
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
pub fn needs_confirmation(&self) -> bool {
|
pub fn needs_confirmation(&self) -> bool {
|
||||||
match self {
|
matches!(
|
||||||
Action::Listing(_) => false,
|
self,
|
||||||
Action::ViewMailbox(_) => false,
|
Self::Listing(ListingAction::Delete)
|
||||||
Action::Sort(_, _) => false,
|
| Self::MailingListAction(_)
|
||||||
Action::SubSort(_, _) => false,
|
| Self::Mailbox(_, _)
|
||||||
Action::Tab(_) => false,
|
| Self::Quit
|
||||||
Action::ToggleThreadSnooze => 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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountName = String;
|
type AccountName = String;
|
||||||
type MailboxPath = String;
|
type MailboxPath = String;
|
||||||
type NewMailboxPath = 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,167 @@
|
||||||
|
/*
|
||||||
|
* 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>,
|
||||||
|
suggestions: Option<&'static [&'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,
|
||||||
|
suggestions: Some(suggs),
|
||||||
|
} => {
|
||||||
|
write!(fmt, "Bad value/argument: {}. Possible values are: ", inner)?;
|
||||||
|
let len = suggs.len();
|
||||||
|
for (i, val) in suggs.iter().enumerate() {
|
||||||
|
if i == len.saturating_sub(1) {
|
||||||
|
write!(fmt, "{}", val)?;
|
||||||
|
} else {
|
||||||
|
write!(fmt, "{}, ", val)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(fmt, "")
|
||||||
|
}
|
||||||
|
Self::BadValue {
|
||||||
|
inner,
|
||||||
|
suggestions: None,
|
||||||
|
} => {
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::CommandError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_command_error_display() {
|
||||||
|
assert_eq!(
|
||||||
|
&CommandError::BadValue {
|
||||||
|
inner: "foo".into(),
|
||||||
|
suggestions: Some(&[
|
||||||
|
"seen",
|
||||||
|
"unseen",
|
||||||
|
"plain",
|
||||||
|
"threaded",
|
||||||
|
"compact",
|
||||||
|
"conversations"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"Bad value/argument: foo. Possible values are: seen, unseen, plain, threaded, \
|
||||||
|
compact, conversations"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,16 +19,20 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::{
|
||||||
use std::io::{Read, Write};
|
fs::OpenOptions,
|
||||||
use std::sync::{Arc, Mutex};
|
io::{Read, Write},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
thread_local!(static CMD_HISTORY_FILE: Arc<Mutex<std::fs::File>> = Arc::new(Mutex::new({
|
thread_local!(static CMD_HISTORY_FILE: Arc<Mutex<std::fs::File>> = Arc::new(Mutex::new({
|
||||||
let data_dir = xdg::BaseDirectories::with_prefix("meli").unwrap();
|
let data_dir = xdg::BaseDirectories::with_prefix("meli").unwrap();
|
||||||
OpenOptions::new().append(true) /* writes will append to a file instead of overwriting previous contents */
|
OpenOptions::new()
|
||||||
.create(true) /* a new file will be created if the file does not yet already exist.*/
|
.append(true) /* writes will append to a file instead of overwriting previous contents */
|
||||||
.read(true)
|
.create(true) /* a new file will be created if the file does not yet already exist. */
|
||||||
.open(data_dir.place_data_file("cmd_history").unwrap()).unwrap()
|
.read(true)
|
||||||
|
.open(data_dir.place_data_file("cmd_history").unwrap())
|
||||||
|
.unwrap()
|
||||||
})));
|
})));
|
||||||
|
|
||||||
pub fn log_cmd(mut cmd: String) {
|
pub fn log_cmd(mut cmd: String) {
|
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::default_vals::{internal_value_false, none, true_val};
|
use melib::{Error, Result, ToggleFlag};
|
||||||
use super::DotAddressable;
|
|
||||||
use melib::{MeliError, Result, ToggleFlag};
|
use super::{
|
||||||
|
default_vals::{internal_value_false, none, true_val},
|
||||||
|
DotAddressable,
|
||||||
|
};
|
||||||
|
|
||||||
/// Settings for the notifications function.
|
/// Settings for the notifications function.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct NotificationsSettings {
|
pub struct NotificationsSettings {
|
||||||
/// Enable notifications.
|
/// Enable notifications.
|
||||||
/// Default: True
|
/// Default: True
|
||||||
#[serde(default = "true_val")]
|
#[serde(default = "true_val")]
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
/// A command to pipe notifications through
|
|
||||||
|
/// A command to pipe notifications through.
|
||||||
/// Default: None
|
/// Default: None
|
||||||
#[serde(default = "none")]
|
#[serde(default = "none")]
|
||||||
pub script: Option<String>,
|
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)`
|
/// A command to pipe new mail notifications through (preferred over
|
||||||
/// Default: None
|
/// `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")]
|
#[serde(default = "none", alias = "xbiff-file-path")]
|
||||||
pub xbiff_file_path: Option<String>,
|
pub xbiff_file_path: Option<String>,
|
||||||
|
|
||||||
#[serde(default = "internal_value_false", alias = "play-sound")]
|
#[serde(default = "internal_value_false", alias = "play-sound")]
|
||||||
pub play_sound: ToggleFlag,
|
pub play_sound: ToggleFlag,
|
||||||
|
|
||||||
#[serde(default = "none", alias = "sound-file")]
|
#[serde(default = "none", alias = "sound-file")]
|
||||||
pub sound_file: Option<String>,
|
pub sound_file: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -51,6 +63,7 @@ impl Default for NotificationsSettings {
|
||||||
Self {
|
Self {
|
||||||
enable: true,
|
enable: true,
|
||||||
script: None,
|
script: None,
|
||||||
|
new_mail_script: None,
|
||||||
xbiff_file_path: None,
|
xbiff_file_path: None,
|
||||||
play_sound: ToggleFlag::InternalVal(false),
|
play_sound: ToggleFlag::InternalVal(false),
|
||||||
sound_file: None,
|
sound_file: None,
|
||||||
|
@ -65,10 +78,11 @@ impl DotAddressable for NotificationsSettings {
|
||||||
match *field {
|
match *field {
|
||||||
"enable" => self.enable.lookup(field, tail),
|
"enable" => self.enable.lookup(field, tail),
|
||||||
"script" => self.script.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),
|
"xbiff_file_path" => self.xbiff_file_path.lookup(field, tail),
|
||||||
"play_sound" => self.play_sound.lookup(field, tail),
|
"play_sound" => self.play_sound.lookup(field, tail),
|
||||||
"sound_file" => self.sound_file.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 {}",
|
"{} has no field named {}",
|
||||||
parent_field, other
|
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.
|
//! Settings for the pager function.
|
||||||
|
|
||||||
use super::default_vals::*;
|
use melib::{Error, Result, ToggleFlag};
|
||||||
use super::deserializers::*;
|
|
||||||
use super::DotAddressable;
|
use super::{default_vals::*, deserializers::*, DotAddressable};
|
||||||
use melib::{MeliError, Result, ToggleFlag};
|
|
||||||
|
|
||||||
/// Settings for the pager function.
|
/// Settings for the pager function.
|
||||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct PagerSettings {
|
pub struct PagerSettings {
|
||||||
/// Number of context lines when going to next page.
|
/// Number of context lines when going to next page.
|
||||||
|
@ -42,8 +41,14 @@ pub struct PagerSettings {
|
||||||
|
|
||||||
/// Always show headers when scrolling.
|
/// Always show headers when scrolling.
|
||||||
/// Default: true
|
/// Default: true
|
||||||
#[serde(default = "true_val", alias = "headers-sticky")]
|
#[serde(
|
||||||
pub headers_sticky: bool,
|
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.
|
/// The height of the pager in mail view, in percent.
|
||||||
/// Default: 80
|
/// Default: 80
|
||||||
|
@ -52,14 +57,14 @@ pub struct PagerSettings {
|
||||||
|
|
||||||
/// A command to pipe mail output through for viewing in pager.
|
/// A command to pipe mail output through for viewing in pager.
|
||||||
/// Default: None
|
/// Default: None
|
||||||
#[serde(default = "none", deserialize_with = "non_empty_string")]
|
#[serde(default = "none", deserialize_with = "non_empty_opt_string")]
|
||||||
pub filter: Option<String>,
|
pub filter: Option<String>,
|
||||||
|
|
||||||
/// A command to pipe html output before displaying it in a pager
|
/// A command to pipe html output before displaying it in a pager
|
||||||
/// Default: None
|
/// Default: None
|
||||||
#[serde(
|
#[serde(
|
||||||
default = "none",
|
default = "none",
|
||||||
deserialize_with = "non_empty_string",
|
deserialize_with = "non_empty_opt_string",
|
||||||
alias = "html-filter"
|
alias = "html-filter"
|
||||||
)]
|
)]
|
||||||
pub html_filter: Option<String>,
|
pub html_filter: Option<String>,
|
||||||
|
@ -79,14 +84,38 @@ pub struct PagerSettings {
|
||||||
#[serde(default = "eighty_val", alias = "minimum-width")]
|
#[serde(default = "eighty_val", alias = "minimum-width")]
|
||||||
pub minimum_width: usize,
|
pub minimum_width: usize,
|
||||||
|
|
||||||
/// Choose `text/html` alternative if `text/plain` is empty in `multipart/alternative`
|
/// Choose `text/html` alternative if `text/plain` is empty in
|
||||||
/// attachments.
|
/// `multipart/alternative` attachments.
|
||||||
/// Default: true
|
/// Default: true
|
||||||
#[serde(
|
#[serde(
|
||||||
default = "internal_value_true",
|
default = "internal_value_true",
|
||||||
alias = "auto-choose-multipart-alternative"
|
alias = "auto-choose-multipart-alternative"
|
||||||
)]
|
)]
|
||||||
pub auto_choose_multipart_alternative: ToggleFlag,
|
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 {
|
impl Default for PagerSettings {
|
||||||
|
@ -94,14 +123,18 @@ impl Default for PagerSettings {
|
||||||
Self {
|
Self {
|
||||||
pager_context: 0,
|
pager_context: 0,
|
||||||
pager_stop: false,
|
pager_stop: false,
|
||||||
headers_sticky: true,
|
sticky_headers: false,
|
||||||
pager_ratio: 80,
|
pager_ratio: 80,
|
||||||
filter: None,
|
filter: None,
|
||||||
html_filter: None,
|
html_filter: None,
|
||||||
|
html_open: None,
|
||||||
format_flowed: true,
|
format_flowed: true,
|
||||||
split_long_lines: true,
|
split_long_lines: true,
|
||||||
minimum_width: 80,
|
minimum_width: 80,
|
||||||
auto_choose_multipart_alternative: ToggleFlag::InternalVal(true),
|
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 {
|
match *field {
|
||||||
"pager_context" => self.pager_context.lookup(field, tail),
|
"pager_context" => self.pager_context.lookup(field, tail),
|
||||||
"pager_stop" => self.pager_stop.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),
|
"pager_ratio" => self.pager_ratio.lookup(field, tail),
|
||||||
"filter" => self.filter.lookup(field, tail),
|
"filter" => self.filter.lookup(field, tail),
|
||||||
"html_filter" => self.html_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),
|
"format_flowed" => self.format_flowed.lookup(field, tail),
|
||||||
"split_long_lines" => self.split_long_lines.lookup(field, tail),
|
"split_long_lines" => self.split_long_lines.lookup(field, tail),
|
||||||
"minimum_width" => self.minimum_width.lookup(field, tail),
|
"minimum_width" => self.minimum_width.lookup(field, tail),
|
||||||
"auto_choose_multipart_alternative" => {
|
"auto_choose_multipart_alternative" => {
|
||||||
self.auto_choose_multipart_alternative.lookup(field, tail)
|
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 {}",
|
"{} has no field named {}",
|
||||||
parent_field, other
|
parent_field, other
|
||||||
))),
|
))),
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* meli - configuration 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use melib::conf::ActionFlag;
|
||||||
|
|
||||||
|
use super::default_vals::*;
|
||||||
|
|
||||||
|
/// Settings for digital signing and encryption
|
||||||
|
#[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: ActionFlag,
|
||||||
|
|
||||||
|
/// auto decrypt encrypted e-mail
|
||||||
|
/// Default: true
|
||||||
|
#[serde(default = "true_val", alias = "auto-decrypt")]
|
||||||
|
pub auto_decrypt: ActionFlag,
|
||||||
|
|
||||||
|
/// always sign sent e-mail
|
||||||
|
/// Default: false
|
||||||
|
#[serde(default = "false_val", alias = "auto-sign")]
|
||||||
|
pub auto_sign: ActionFlag,
|
||||||
|
|
||||||
|
/// Auto encrypt sent e-mail
|
||||||
|
/// Default: false
|
||||||
|
#[serde(default = "false_val", alias = "auto-encrypt")]
|
||||||
|
pub auto_encrypt: ActionFlag,
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc4880#section-12.2
|
||||||
|
/// Default: None
|
||||||
|
#[serde(default = "none", alias = "sign-key")]
|
||||||
|
pub sign_key: Option<String>,
|
||||||
|
|
||||||
|
/// Default: None
|
||||||
|
#[serde(default = "none", alias = "decrypt-key")]
|
||||||
|
pub decrypt_key: Option<String>,
|
||||||
|
|
||||||
|
/// Default: None
|
||||||
|
#[serde(default = "none", alias = "encrypt-key")]
|
||||||
|
pub encrypt_key: Option<String>,
|
||||||
|
|
||||||
|
/// Allow remote lookups
|
||||||
|
/// Default: False
|
||||||
|
#[serde(
|
||||||
|
default = "action_internal_value_false",
|
||||||
|
alias = "allow-remote-lookups"
|
||||||
|
)]
|
||||||
|
pub allow_remote_lookup: ActionFlag,
|
||||||
|
|
||||||
|
/// Remote lookup mechanisms.
|
||||||
|
/// Default: "local,wkd"
|
||||||
|
#[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")]
|
||||||
|
fn default_lookup_mechanism() -> melib::gpgme::LocateKey {
|
||||||
|
melib::gpgme::LocateKey::LOCAL | melib::gpgme::LocateKey::WKD
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PGPSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
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: action_internal_value_false::<ActionFlag>(),
|
||||||
|
#[cfg(feature = "gpgme")]
|
||||||
|
remote_lookup_mechanisms: default_lookup_mechanism(),
|
||||||
|
#[cfg(not(feature = "gpgme"))]
|
||||||
|
remote_lookup_mechanisms: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,10 +19,11 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use melib::{Error, Result};
|
||||||
|
|
||||||
use super::DotAddressable;
|
use super::DotAddressable;
|
||||||
use crate::terminal::Key;
|
use crate::terminal::Key;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use melib::{MeliError, Result};
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! shortcut {
|
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)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Shortcuts {
|
pub struct Shortcuts {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -43,8 +44,6 @@ pub struct Shortcuts {
|
||||||
pub listing: ListingShortcuts,
|
pub listing: ListingShortcuts,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub composing: ComposingShortcuts,
|
pub composing: ComposingShortcuts,
|
||||||
#[serde(default, alias = "compact-listing")]
|
|
||||||
pub compact_listing: CompactListingShortcuts,
|
|
||||||
#[serde(default, alias = "contact-list")]
|
#[serde(default, alias = "contact-list")]
|
||||||
pub contact_list: ContactListShortcuts,
|
pub contact_list: ContactListShortcuts,
|
||||||
#[serde(default, alias = "envelope-view")]
|
#[serde(default, alias = "envelope-view")]
|
||||||
|
@ -55,19 +54,14 @@ pub struct Shortcuts {
|
||||||
pub pager: PagerShortcuts,
|
pub pager: PagerShortcuts,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Shortcuts {
|
impl Shortcuts {
|
||||||
fn default() -> Self {
|
pub const GENERAL: &'static str = "general";
|
||||||
Self {
|
pub const LISTING: &'static str = "listing";
|
||||||
general: GeneralShortcuts::default(),
|
pub const COMPOSING: &'static str = "composing";
|
||||||
listing: ListingShortcuts::default(),
|
pub const CONTACT_LIST: &'static str = "contact_list";
|
||||||
composing: ComposingShortcuts::default(),
|
pub const ENVELOPE_VIEW: &'static str = "envelope_view";
|
||||||
compact_listing: CompactListingShortcuts::default(),
|
pub const THREAD_VIEW: &'static str = "thread_view";
|
||||||
contact_list: ContactListShortcuts::default(),
|
pub const PAGER: &'static str = "pager";
|
||||||
envelope_view: EnvelopeViewShortcuts::default(),
|
|
||||||
thread_view: ThreadViewShortcuts::default(),
|
|
||||||
pager: PagerShortcuts::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DotAddressable for Shortcuts {
|
impl DotAddressable for Shortcuts {
|
||||||
|
@ -79,14 +73,11 @@ impl DotAddressable for Shortcuts {
|
||||||
"general" => self.general.lookup(field, tail),
|
"general" => self.general.lookup(field, tail),
|
||||||
"listing" => self.listing.lookup(field, tail),
|
"listing" => self.listing.lookup(field, tail),
|
||||||
"composing" => self.composing.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),
|
"contact_list" | "contact-list" => self.contact_list.lookup(field, tail),
|
||||||
"envelope_view" | "envelope-view" => self.envelope_view.lookup(field, tail),
|
"envelope_view" | "envelope-view" => self.envelope_view.lookup(field, tail),
|
||||||
"thread_view" | "thread-view" => self.thread_view.lookup(field, tail),
|
"thread_view" | "thread-view" => self.thread_view.lookup(field, tail),
|
||||||
"pager" => self.pager.lookup(field, tail),
|
"pager" => self.pager.lookup(field, tail),
|
||||||
other => Err(MeliError::new(format!(
|
other => Err(Error::new(format!(
|
||||||
"{} has no field named {}",
|
"{} has no field named {}",
|
||||||
parent_field, other
|
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.
|
/// Create a struct holding all of a Component's shortcuts.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! shortcut_key_values {
|
macro_rules! shortcut_key_values {
|
||||||
|
@ -109,6 +106,7 @@ macro_rules! shortcut_key_values {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(rename = $cname)]
|
#[serde(rename = $cname)]
|
||||||
pub struct $name {
|
pub struct $name {
|
||||||
|
pub commands: Vec<CommandShortcut>,
|
||||||
$(pub $fname : Key),*
|
$(pub $fname : Key),*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +129,7 @@ macro_rules! shortcut_key_values {
|
||||||
impl Default for $name {
|
impl Default for $name {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
commands : vec![],
|
||||||
$($fname: $default),*
|
$($fname: $default),*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +142,7 @@ macro_rules! shortcut_key_values {
|
||||||
let tail = &path[1..];
|
let tail = &path[1..];
|
||||||
match *field {
|
match *field {
|
||||||
$(stringify!($fname) => self.$fname.lookup(field, tail),)*
|
$(stringify!($fname) => self.$fname.lookup(field, tail),)*
|
||||||
other => Err(MeliError::new(format!(
|
other => Err(Error::new(format!(
|
||||||
"{} has no field named {}",
|
"{} has no field named {}",
|
||||||
parent_field, other
|
parent_field, other
|
||||||
))),
|
))),
|
||||||
|
@ -156,45 +155,50 @@ 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'),
|
|
||||||
select_entry |> "Select thread entry." |> Key::Char('v')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut_key_values! { "listing",
|
shortcut_key_values! { "listing",
|
||||||
/// Shortcut listing for a mail listing.
|
/// Shortcut listing for a mail listing.
|
||||||
pub struct ListingShortcuts {
|
pub struct ListingShortcuts {
|
||||||
scroll_up |> "Scroll up list." |> Key::Up,
|
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||||
scroll_down |> "Scroll down list." |> Key::Down,
|
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||||
new_mail |> "Start new mail draft in new tab." |> Key::Char('m'),
|
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_mailbox |> "Go to next mailbox." |> Key::Char('J'),
|
||||||
next_page |> "Go to next page." |> Key::PageDown,
|
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'),
|
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,
|
prev_page |> "Go to previous page." |> Key::PageUp,
|
||||||
search |> "Search within list of e-mails." |> Key::Char('/'),
|
search |> "Search within list of e-mails." |> Key::Char('/'),
|
||||||
refresh |> "Manually request a mailbox refresh." |> Key::F(5),
|
refresh |> "Manually request a mailbox refresh." |> Key::F(5),
|
||||||
set_seen |> "Set thread as seen." |> Key::Char('n'),
|
set_seen |> "Set thread as seen." |> Key::Char('n'),
|
||||||
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`')
|
union_modifier |> "Union modifier." |> Key::Ctrl('u'),
|
||||||
|
diff_modifier |> "Difference modifier." |> Key::Ctrl('d'),
|
||||||
|
intersection_modifier |> "Intersection modifier." |> Key::Ctrl('i'),
|
||||||
|
select_entry |> "Select thread entry." |> Key::Char('v'),
|
||||||
|
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_key_values! { "contact-list",
|
||||||
/// Shortcut listing for the contact list view
|
/// Shortcut listing for the contact list view
|
||||||
pub struct ContactListShortcuts {
|
pub struct ContactListShortcuts {
|
||||||
scroll_up |> "Scroll up list." |> Key::Up,
|
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||||
scroll_down |> "Scroll down list." |> Key::Down,
|
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||||
create_contact |> "Create new contact." |> Key::Char('c'),
|
create_contact |> "Create new contact." |> Key::Char('c'),
|
||||||
edit_contact |> "Edit contact under cursor." |> Key::Char('e'),
|
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'),
|
mail_contact |> "Mail contact under cursor." |> Key::Char('m'),
|
||||||
next_account |> "Go to next account." |> Key::Char('h'),
|
next_account |> "Go to next account." |> Key::Char('H'),
|
||||||
prev_account |> "Go to previous account." |> Key::Char('l'),
|
prev_account |> "Go to previous account." |> Key::Char('L'),
|
||||||
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`')
|
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,8 +206,8 @@ shortcut_key_values! { "contact-list",
|
||||||
shortcut_key_values! { "pager",
|
shortcut_key_values! { "pager",
|
||||||
/// Shortcut listing for the text pager
|
/// Shortcut listing for the text pager
|
||||||
pub struct PagerShortcuts {
|
pub struct PagerShortcuts {
|
||||||
page_down |> "Go to next pager page" |> Key::PageDown,
|
page_down |> "Go to next pager page." |> Key::PageDown,
|
||||||
page_up |> "Go to previous pager page" |> Key::PageUp,
|
page_up |> "Go to previous pager page." |> Key::PageUp,
|
||||||
scroll_down |> "Scroll down pager." |> Key::Char('j'),
|
scroll_down |> "Scroll down pager." |> Key::Char('j'),
|
||||||
scroll_up |> "Scroll up pager." |> Key::Char('k')
|
scroll_up |> "Scroll up pager." |> Key::Char('k')
|
||||||
}
|
}
|
||||||
|
@ -212,22 +216,33 @@ shortcut_key_values! { "pager",
|
||||||
shortcut_key_values! { "general",
|
shortcut_key_values! { "general",
|
||||||
pub struct GeneralShortcuts {
|
pub struct GeneralShortcuts {
|
||||||
toggle_help |> "Toggle help and shortcuts view." |> Key::Char('?'),
|
toggle_help |> "Toggle help and shortcuts view." |> Key::Char('?'),
|
||||||
enter_command_mode |> "Enter COMMAND mode." |> Key::Char(' '),
|
enter_command_mode |> "Enter COMMAND mode." |> Key::Char(':'),
|
||||||
go_to_tab |> "Go to the nth tab" |> Key::Alt('n'),
|
quit |> "Quit meli." |> Key::Char('q'),
|
||||||
next_tab |> "Next tab." |> Key::Char('T'),
|
go_to_tab |> "Go to the nth tab." |> Key::Alt('n'),
|
||||||
scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Right,
|
next_tab |> "Go to the next tab." |> Key::Char('T'),
|
||||||
scroll_left |> "Generic scroll left (catch-all setting)" |> Key::Left,
|
scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Char('l'),
|
||||||
scroll_up |> "Generic scroll up (catch-all setting)" |> Key::Up,
|
scroll_left |> "Generic scroll left (catch-all setting)" |>Key::Char('h'),
|
||||||
scroll_down |> "Generic scroll down (catch-all setting)" |> Key::Down
|
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",
|
shortcut_key_values! { "composing",
|
||||||
pub struct ComposingShortcuts {
|
pub struct ComposingShortcuts {
|
||||||
edit_mail |> "Edit mail." |> Key::Char('e'),
|
edit |> "Edit." |> Key::Char('e'),
|
||||||
send_mail |> "Deliver draft to mailer" |> Key::Char('s'),
|
send_mail |> "Deliver draft to mailer." |> Key::Char('s'),
|
||||||
scroll_up |> "Change field focus." |> Key::Up,
|
scroll_up |> "Change field focus." |> Key::Char('k'),
|
||||||
scroll_down |> "Change field focus." |> Key::Down
|
scroll_down |> "Change field focus." |> Key::Char('j')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,28 +250,32 @@ shortcut_key_values! { "envelope-view",
|
||||||
pub struct EnvelopeViewShortcuts {
|
pub struct EnvelopeViewShortcuts {
|
||||||
add_addresses_to_contacts |> "Select addresses from envelope to add to contacts." |> Key::Char('c'),
|
add_addresses_to_contacts |> "Select addresses from envelope to add to contacts." |> Key::Char('c'),
|
||||||
edit |> "Open envelope in composer." |> Key::Char('e'),
|
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_attachment |> "Opens selected attachment with xdg-open." |> Key::Char('a'),
|
||||||
open_mailcap |> "Opens selected attachment according to its mailcap entry." |> Key::Char('m'),
|
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 |> "Reply to envelope." |> Key::Char('R'),
|
||||||
reply_to_author |> "Reply to author." |> Key::Ctrl('r'),
|
reply_to_author |> "Reply to author." |> Key::Ctrl('r'),
|
||||||
reply_to_all |> "Reply to all/Reply to list/Follow up." |> Key::Ctrl('g'),
|
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'),
|
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_expand_headers |> "Expand extra headers (References and others)." |> Key::Char('h'),
|
||||||
toggle_url_mode |> "Toggles url open mode." |> Key::Char('u'),
|
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",
|
shortcut_key_values! { "thread-view",
|
||||||
pub struct ThreadViewShortcuts {
|
pub struct ThreadViewShortcuts {
|
||||||
scroll_up |> "Scroll up list." |> Key::Up,
|
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||||
scroll_down |> "Scroll down list." |> Key::Down,
|
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||||
collapse_subtree |> "collapse thread branches" |> Key::Char('h'),
|
collapse_subtree |> "collapse thread branches." |> Key::Char('h'),
|
||||||
next_page |> "Go to next page." |> Key::PageDown,
|
next_page |> "Go to next page." |> Key::PageDown,
|
||||||
prev_page |> "Go to previous page." |> Key::PageUp,
|
prev_page |> "Go to previous page." |> Key::PageUp,
|
||||||
reverse_thread_order |> "reverse thread order" |> Key::Ctrl('r'),
|
reverse_thread_order |> "reverse thread order." |> Key::Ctrl('r'),
|
||||||
toggle_mailview |> "toggle mail view visibility" |> Key::Char('p'),
|
toggle_mailview |> "toggle mail view visibility." |> Key::Char('p'),
|
||||||
toggle_threadview |> "toggle thread view visibility" |> Key::Char('t')
|
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.
|
//! 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 super::DotAddressable;
|
||||||
use crate::terminal::Color;
|
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)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct TagsSettings {
|
pub struct TagsSettings {
|
||||||
#[serde(default, deserialize_with = "tag_color_de")]
|
#[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")]
|
#[serde(default, deserialize_with = "tag_set_de", alias = "ignore-tags")]
|
||||||
pub ignore_tags: HashSet<u64>,
|
pub ignore_tags: HashSet<TagHash>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TagsSettings {
|
pub fn tag_set_de<'de, D, T: std::convert::From<HashSet<TagHash>>>(
|
||||||
fn default() -> Self {
|
|
||||||
TagsSettings {
|
|
||||||
colors: Default::default(),
|
|
||||||
ignore_tags: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tag_set_de<'de, D, T: std::convert::From<HashSet<u64>>>(
|
|
||||||
deserializer: D,
|
deserializer: D,
|
||||||
) -> std::result::Result<T, D::Error>
|
) -> std::result::Result<T, D::Error>
|
||||||
where
|
where
|
||||||
|
@ -54,16 +46,12 @@ where
|
||||||
{
|
{
|
||||||
Ok(<Vec<String>>::deserialize(deserializer)?
|
Ok(<Vec<String>>::deserialize(deserializer)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tag| {
|
.map(|tag| TagHash::from_bytes(tag.as_bytes()))
|
||||||
let mut hasher = DefaultHasher::new();
|
.collect::<HashSet<TagHash>>()
|
||||||
hasher.write(tag.as_bytes());
|
|
||||||
hasher.finish()
|
|
||||||
})
|
|
||||||
.collect::<HashSet<u64>>()
|
|
||||||
.into())
|
.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,
|
deserializer: D,
|
||||||
) -> std::result::Result<T, D::Error>
|
) -> std::result::Result<T, D::Error>
|
||||||
where
|
where
|
||||||
|
@ -79,17 +67,15 @@ where
|
||||||
Ok(<HashMap<String, _Color>>::deserialize(deserializer)?
|
Ok(<HashMap<String, _Color>>::deserialize(deserializer)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(tag, color)| {
|
.map(|(tag, color)| {
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
hasher.write(tag.as_bytes());
|
|
||||||
(
|
(
|
||||||
hasher.finish(),
|
TagHash::from_bytes(tag.as_bytes()),
|
||||||
match color {
|
match color {
|
||||||
_Color::B(b) => Color::Byte(b),
|
_Color::B(b) => Color::Byte(b),
|
||||||
_Color::C(c) => c,
|
_Color::C(c) => c,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<HashMap<u64, Color>>()
|
.collect::<HashMap<TagHash, Color>>()
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +87,7 @@ impl DotAddressable for TagsSettings {
|
||||||
match *field {
|
match *field {
|
||||||
"colors" => self.colors.lookup(field, tail),
|
"colors" => self.colors.lookup(field, tail),
|
||||||
"ignore_tags" => self.ignore_tags.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 {}",
|
"{} has no field named {}",
|
||||||
parent_field, other
|
parent_field, other
|
||||||
))),
|
))),
|
|
@ -21,13 +21,12 @@
|
||||||
|
|
||||||
//! Settings for terminal display
|
//! Settings for terminal display
|
||||||
|
|
||||||
use super::deserializers::non_empty_string;
|
use melib::{Error, Result, ToggleFlag};
|
||||||
use super::DotAddressable;
|
|
||||||
use super::Themes;
|
use super::{deserializers::non_empty_opt_string, DotAddressable, Themes};
|
||||||
use melib::{MeliError, Result, ToggleFlag};
|
|
||||||
|
|
||||||
/// Settings for terminal display
|
/// Settings for terminal display
|
||||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub struct TerminalSettings {
|
pub struct TerminalSettings {
|
||||||
/// light, dark
|
/// light, dark
|
||||||
|
@ -35,18 +34,37 @@ pub struct TerminalSettings {
|
||||||
pub themes: Themes,
|
pub themes: Themes,
|
||||||
pub ascii_drawing: bool,
|
pub ascii_drawing: bool,
|
||||||
pub use_color: ToggleFlag,
|
pub use_color: ToggleFlag,
|
||||||
#[serde(deserialize_with = "non_empty_string")]
|
/// 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_opt_string")]
|
||||||
|
pub mouse_flag: Option<String>,
|
||||||
|
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||||
pub window_title: Option<String>,
|
pub window_title: Option<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
|
||||||
|
#[serde(default)]
|
||||||
|
pub progress_spinner_sequence: Option<ProgressSpinnerSequence>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TerminalSettings {
|
impl Default for TerminalSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TerminalSettings {
|
Self {
|
||||||
theme: "dark".to_string(),
|
theme: "dark".to_string(),
|
||||||
themes: Themes::default(),
|
themes: Themes::default(),
|
||||||
ascii_drawing: false,
|
ascii_drawing: false,
|
||||||
use_color: ToggleFlag::InternalVal(true),
|
use_color: ToggleFlag::InternalVal(true),
|
||||||
|
use_mouse: ToggleFlag::InternalVal(false),
|
||||||
|
mouse_flag: Some("🖱️ ".to_string()),
|
||||||
window_title: Some("meli".to_string()),
|
window_title: Some("meli".to_string()),
|
||||||
|
file_picker_command: None,
|
||||||
|
progress_spinner_sequence: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,11 +88,17 @@ impl DotAddressable for TerminalSettings {
|
||||||
let tail = &path[1..];
|
let tail = &path[1..];
|
||||||
match *field {
|
match *field {
|
||||||
"theme" => self.theme.lookup(field, tail),
|
"theme" => self.theme.lookup(field, tail),
|
||||||
"themes" => Err(MeliError::new("unimplemented")),
|
"themes" => Err(Error::new("unimplemented")),
|
||||||
"ascii_drawing" => self.ascii_drawing.lookup(field, tail),
|
"ascii_drawing" => self.ascii_drawing.lookup(field, tail),
|
||||||
"use_color" => self.use_color.lookup(field, tail),
|
"use_color" => self.use_color.lookup(field, tail),
|
||||||
|
"use_mouse" => self.use_mouse.lookup(field, tail),
|
||||||
|
"mouse_flag" => self.mouse_flag.lookup(field, tail),
|
||||||
"window_title" => self.window_title.lookup(field, tail),
|
"window_title" => self.window_title.lookup(field, tail),
|
||||||
other => Err(MeliError::new(format!(
|
"file_picker_command" => self.file_picker_command.lookup(field, tail),
|
||||||
|
"progress_spinner_sequence" => {
|
||||||
|
self.progress_spinner_sequence.lookup(field, tail)
|
||||||
|
}
|
||||||
|
other => Err(Error::new(format!(
|
||||||
"{} has no field named {}",
|
"{} has no field named {}",
|
||||||
parent_field, other
|
parent_field, other
|
||||||
))),
|
))),
|
||||||
|
@ -84,3 +108,20 @@ impl DotAddressable for TerminalSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ProgressSpinnerSequence {
|
||||||
|
Integer(usize),
|
||||||
|
Custom {
|
||||||
|
frames: Vec<String>,
|
||||||
|
#[serde(default = "interval_ms_val")]
|
||||||
|
interval_ms: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn interval_ms_val() -> u64 {
|
||||||
|
crate::utilities::ProgressSpinner::INTERVAL_MS
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DotAddressable for ProgressSpinnerSequence {}
|
File diff suppressed because it is too large
Load Diff
|
@ -19,61 +19,53 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use indexmap::IndexMap;
|
||||||
use std::collections::HashMap;
|
use melib::Card;
|
||||||
|
|
||||||
mod contact_list;
|
use crate::{
|
||||||
|
terminal::*, CellBuffer, Component, ComponentId, Context, Field, FormWidget, Key, StatusEvent,
|
||||||
pub use self::contact_list::*;
|
ThemeAttribute, UIDialog, UIEvent,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
ReadOnly,
|
ReadOnly,
|
||||||
Discard(UIDialog<char>),
|
Discard(Box<UIDialog<char>>),
|
||||||
Edit,
|
Edit,
|
||||||
//New,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContactManager {
|
pub struct ContactManager {
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
parent_id: ComponentId,
|
parent_id: Option<ComponentId>,
|
||||||
pub card: Card,
|
pub card: Card,
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
form: FormWidget,
|
form: FormWidget<bool>,
|
||||||
account_pos: usize,
|
pub account_pos: usize,
|
||||||
content: CellBuffer,
|
content: Screen<Virtual>,
|
||||||
theme_default: ThemeAttribute,
|
theme_default: ThemeAttribute,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
has_changes: bool,
|
has_changes: bool,
|
||||||
|
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ContactManager {
|
impl std::fmt::Display for ContactManager {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
write!(f, "contacts")
|
write!(f, "{}", self.card)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactManager {
|
impl ContactManager {
|
||||||
fn new(context: &Context) -> Self {
|
pub fn new(context: &Context) -> Self {
|
||||||
let theme_default: ThemeAttribute = crate::conf::value(context, "theme_default");
|
let theme_default: ThemeAttribute = crate::conf::value(context, "theme_default");
|
||||||
let default_cell = {
|
Self {
|
||||||
let mut ret = Cell::with_char(' ');
|
id: ComponentId::default(),
|
||||||
ret.set_fg(theme_default.fg)
|
parent_id: None,
|
||||||
.set_bg(theme_default.bg)
|
|
||||||
.set_attrs(theme_default.attrs);
|
|
||||||
ret
|
|
||||||
};
|
|
||||||
ContactManager {
|
|
||||||
id: Uuid::nil(),
|
|
||||||
parent_id: Uuid::nil(),
|
|
||||||
card: Card::new(),
|
card: Card::new(),
|
||||||
mode: ViewMode::Edit,
|
mode: ViewMode::Edit,
|
||||||
form: FormWidget::default(),
|
form: FormWidget::default(),
|
||||||
account_pos: 0,
|
account_pos: 0,
|
||||||
content: CellBuffer::new(100, 1, default_cell),
|
content: Screen::<Virtual>::new(),
|
||||||
theme_default,
|
theme_default,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
has_changes: false,
|
has_changes: false,
|
||||||
|
@ -81,44 +73,49 @@ impl ContactManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&mut self) {
|
fn initialize(&mut self, context: &Context) {
|
||||||
let (width, _) = self.content.size();
|
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: ",
|
"Last edited: ",
|
||||||
&mut self.content,
|
self.theme_default.fg,
|
||||||
Color::Byte(250),
|
|
||||||
self.theme_default.bg,
|
self.theme_default.bg,
|
||||||
self.theme_default.attrs,
|
self.theme_default.attrs,
|
||||||
((0, 0), (width - 1, 0)),
|
area,
|
||||||
None,
|
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(),
|
&self.card.last_edited(),
|
||||||
&mut self.content,
|
self.theme_default.fg,
|
||||||
Color::Byte(250),
|
|
||||||
self.theme_default.bg,
|
self.theme_default.bg,
|
||||||
self.theme_default.attrs,
|
self.theme_default.attrs,
|
||||||
((x, 0), (width - 1, 0)),
|
area,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
area = area.skip(x, y);
|
||||||
|
|
||||||
if self.card.external_resource() {
|
if self.card.external_resource() {
|
||||||
self.mode = ViewMode::ReadOnly;
|
self.mode = ViewMode::ReadOnly;
|
||||||
self.content
|
self.content.grid_mut().write_string(
|
||||||
.resize(self.content.size().0, 2, Cell::default());
|
|
||||||
write_string_to_grid(
|
|
||||||
"This contact's origin is external and cannot be edited within meli.",
|
"This contact's origin is external and cannot be edited within meli.",
|
||||||
&mut self.content,
|
self.theme_default.fg,
|
||||||
Color::Byte(250),
|
|
||||||
self.theme_default.bg,
|
self.theme_default.bg,
|
||||||
self.theme_default.attrs,
|
self.theme_default.attrs,
|
||||||
((x, y), (width - 1, y)),
|
area,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.form = FormWidget::new("Save".into());
|
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.add_button(("Cancel(Esc)".into(), false));
|
||||||
self.form
|
self.form
|
||||||
.push(("NAME".into(), self.card.name().to_string()));
|
.push(("NAME".into(), self.card.name().to_string()));
|
||||||
|
@ -135,66 +132,65 @@ impl ContactManager {
|
||||||
self.form.push(("URL".into(), self.card.url().to_string()));
|
self.form.push(("URL".into(), self.card.url().to_string()));
|
||||||
self.form.push(("KEY".into(), self.card.key().to_string()));
|
self.form.push(("KEY".into(), self.card.key().to_string()));
|
||||||
for (k, v) in self.card.extra_properties() {
|
for (k, v) in self.card.extra_properties() {
|
||||||
self.form.push((k.to_string(), v.to_string()));
|
self.form.push((k.to_string().into(), v.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_parent_id(&mut self, new_val: ComponentId) {
|
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 {
|
impl Component for ContactManager {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
if !self.initialized {
|
if !self.initialized {
|
||||||
self.initialize();
|
self.initialize(context);
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let upper_left = upper_left!(area);
|
if self.is_dirty() {
|
||||||
let bottom_right = bottom_right!(area);
|
grid.clear_area(area, self.theme_default);
|
||||||
|
grid.copy_area(self.content.grid(), area.skip_rows(2), self.content.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)));
|
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.form.draw(
|
self.form.draw(
|
||||||
grid,
|
grid,
|
||||||
(set_y(upper_left, get_y(upper_left) + 2), bottom_right),
|
area.skip_rows(2 + self.content.area().height()),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
match self.mode {
|
if let ViewMode::Discard(ref mut selector) = self.mode {
|
||||||
ViewMode::Discard(ref mut selector) => {
|
/* Let user choose whether to quit with/without saving or cancel */
|
||||||
/* Let user choose whether to quit with/without saving or cancel */
|
selector.draw(grid, area, context);
|
||||||
selector.draw(grid, center_area(area, selector.content.size()), context);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
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.content.grid_mut().empty();
|
||||||
|
self.initialized = false;
|
||||||
|
self.set_dirty(true);
|
||||||
|
}
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::Discard(ref mut selector) => {
|
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) {
|
if selector.process_event(event, context) {
|
||||||
self.set_dirty(true);
|
self.set_dirty(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewMode::Edit => {
|
ViewMode::Edit => {
|
||||||
if let &mut UIEvent::Input(Key::Esc) = event {
|
if matches!(event, UIEvent::Input(Key::Esc)) {
|
||||||
if self.can_quit_cleanly(context) {
|
if self.can_quit_cleanly(context) {
|
||||||
context
|
self.unrealize(context);
|
||||||
.replies
|
|
||||||
.push_back(UIEvent::Action(Tab(Kill(self.parent_id))));
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -202,17 +198,15 @@ impl Component for ContactManager {
|
||||||
match self.form.buttons_result() {
|
match self.form.buttons_result() {
|
||||||
None => {}
|
None => {}
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
let fields = std::mem::replace(&mut self.form, FormWidget::default())
|
let fields = std::mem::take(&mut self.form).collect().unwrap();
|
||||||
.collect()
|
let fields: IndexMap<String, String> = fields
|
||||||
.unwrap();
|
|
||||||
let fields: HashMap<String, String> = fields
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(s, v)| {
|
.map(|(s, v)| {
|
||||||
(
|
(
|
||||||
s,
|
s.to_string(),
|
||||||
match v {
|
match v {
|
||||||
Field::Text(v, _) => v.as_str().to_string(),
|
Field::Text(v) => v.as_str().to_string(),
|
||||||
Field::Choice(mut v, c) => v.remove(c),
|
Field::Choice(mut v, c, _) => v.remove(c).to_string(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -225,23 +219,23 @@ impl Component for ContactManager {
|
||||||
context.replies.push_back(UIEvent::StatusEvent(
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
StatusEvent::DisplayMessage("Saved.".into()),
|
StatusEvent::DisplayMessage("Saved.".into()),
|
||||||
));
|
));
|
||||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
self.unrealize(context);
|
||||||
}
|
}
|
||||||
Some(false) => {
|
Some(false) => {
|
||||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
self.unrealize(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.set_dirty(true);
|
self.set_dirty(true);
|
||||||
if let UIEvent::InsertInput(_) = event {
|
if matches!(event, UIEvent::InsertInput(_)) {
|
||||||
self.has_changes = true;
|
self.has_changes = true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewMode::ReadOnly => {
|
ViewMode::ReadOnly => {
|
||||||
if let &mut UIEvent::Input(Key::Esc) = event {
|
if matches!(event, UIEvent::Input(Key::Esc)) {
|
||||||
if self.can_quit_cleanly(context) {
|
if self.can_quit_cleanly(context) {
|
||||||
context.replies.push_back(UIEvent::ComponentKill(self.id));
|
self.unrealize(context);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -253,11 +247,7 @@ impl Component for ContactManager {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty
|
self.dirty
|
||||||
|| self.form.is_dirty()
|
|| self.form.is_dirty()
|
||||||
|| if let ViewMode::Discard(ref selector) = self.mode {
|
|| matches!(self.mode, ViewMode::Discard(ref selector) if selector.is_dirty())
|
||||||
selector.is_dirty()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_dirty(&mut self, value: bool) {
|
fn set_dirty(&mut self, value: bool) {
|
||||||
|
@ -272,34 +262,36 @@ impl Component for ContactManager {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_id(&mut self, id: ComponentId) {
|
|
||||||
self.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||||
if !self.has_changes {
|
if !self.has_changes {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent_id = self.parent_id;
|
if matches!(self.mode, ViewMode::Discard(_)) {
|
||||||
/* Play it safe and ask user for confirmation */
|
true
|
||||||
self.mode = ViewMode::Discard(UIDialog::new(
|
} else {
|
||||||
"this contact has unsaved changes",
|
let Some(parent_id) = self.parent_id else {
|
||||||
vec![
|
return true;
|
||||||
('x', "quit without saving".to_string()),
|
};
|
||||||
('y', "save draft and quit".to_string()),
|
/* Play it safe and ask user for confirmation */
|
||||||
('n', "cancel".to_string()),
|
self.mode = ViewMode::Discard(Box::new(UIDialog::new(
|
||||||
],
|
"this contact has unsaved changes",
|
||||||
true,
|
vec![
|
||||||
Some(Box::new(move |_, results: &[char]| match results[0] {
|
('y', "quit without saving".to_string()),
|
||||||
'x' => Some(UIEvent::Action(Tab(Kill(parent_id)))),
|
('n', "cancel".to_string()),
|
||||||
'n' => None,
|
],
|
||||||
'y' => None,
|
true,
|
||||||
_ => None,
|
Some(Box::new(move |id, results: &[char]| {
|
||||||
})),
|
if matches!(results.first(), Some(&'y')) {
|
||||||
context,
|
Some(UIEvent::ComponentUnrealize(parent_id))
|
||||||
));
|
} else {
|
||||||
self.set_dirty(true);
|
Some(UIEvent::ComponentUnrealize(id))
|
||||||
false
|
}
|
||||||
|
})),
|
||||||
|
context,
|
||||||
|
)));
|
||||||
|
self.set_dirty(true);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
|
@ -0,0 +1,463 @@
|
||||||
|
/*
|
||||||
|
* meli - jobs executor
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Async job executor thread pool
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
future::Future,
|
||||||
|
iter,
|
||||||
|
panic::catch_unwind,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crossbeam::{
|
||||||
|
channel::Sender,
|
||||||
|
deque::{Injector, Stealer, Worker},
|
||||||
|
sync::{Parker, Unparker},
|
||||||
|
};
|
||||||
|
pub use futures::channel::oneshot;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use melib::{log, smol, utils::datetime, uuid::Uuid, UnixTimestamp};
|
||||||
|
|
||||||
|
use crate::types::{StatusEvent, ThreadEvent, UIEvent};
|
||||||
|
|
||||||
|
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.
|
||||||
|
iter::repeat_with(|| {
|
||||||
|
// Try stealing a batch of tasks from the global queue.
|
||||||
|
global
|
||||||
|
.steal_batch_and_pop(local)
|
||||||
|
// Or try stealing a task from one of the other threads.
|
||||||
|
.or_else(|| stealers.iter().map(|s| s.steal()).collect())
|
||||||
|
})
|
||||||
|
// Loop while no task was stolen and any steal operation needs to be retried.
|
||||||
|
.find(|s| !s.is_retry())
|
||||||
|
// Extract the stolen task, if there is one.
|
||||||
|
.and_then(|s| s.success())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! uuid_hash_type {
|
||||||
|
($n:ident) => {
|
||||||
|
#[derive(PartialEq, Hash, Eq, Copy, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||||
|
pub struct $n(Uuid);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for $n {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
|
pub fn null() -> Self {
|
||||||
|
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<IndexMap<TimerId, TimerPrivate>>>,
|
||||||
|
pub jobs: Arc<Mutex<IndexMap<JobId, JobMetadata>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct TimerPrivate {
|
||||||
|
/// Interval for periodic timer.
|
||||||
|
interval: Duration,
|
||||||
|
/// Time until next expiration.
|
||||||
|
value: Duration,
|
||||||
|
active: bool,
|
||||||
|
handle: Option<async_task::Task<()>>,
|
||||||
|
cancel: Arc<Mutex<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Timer {
|
||||||
|
id: TimerId,
|
||||||
|
job_executor: Arc<JobExecutor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
pub fn id(&self) -> TimerId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rearm(&self) {
|
||||||
|
self.job_executor.rearm(self.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable(&self) {
|
||||||
|
self.job_executor.disable_timer(self.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_interval(&self, new_val: Duration) {
|
||||||
|
self.job_executor.set_interval(self.id, new_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Timer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobExecutor {
|
||||||
|
/// A queue that holds scheduled tasks.
|
||||||
|
pub fn new(sender: Sender<ThreadEvent>) -> Self {
|
||||||
|
// Create a queue.
|
||||||
|
let mut ret = Self {
|
||||||
|
global_queue: Arc::new(Injector::new()),
|
||||||
|
workers: vec![],
|
||||||
|
parkers: vec![],
|
||||||
|
sender,
|
||||||
|
timers: Arc::new(Mutex::new(IndexMap::default())),
|
||||||
|
jobs: Arc::new(Mutex::new(IndexMap::default())),
|
||||||
|
};
|
||||||
|
let mut workers = vec![];
|
||||||
|
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();
|
||||||
|
ret.parkers.push(p.unparker().clone());
|
||||||
|
workers.push((new_worker, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactor thread
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("meli-reactor".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let ex = smol::Executor::new();
|
||||||
|
|
||||||
|
futures::executor::block_on(ex.run(futures::future::pending::<()>()));
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Spawn executor threads the first time the queue is created.
|
||||||
|
for (i, (local, parker)) in workers.into_iter().enumerate() {
|
||||||
|
let global = ret.global_queue.clone();
|
||||||
|
let stealers = ret.workers.clone();
|
||||||
|
thread::Builder::new()
|
||||||
|
.name(format!("meli-executor-{}", i))
|
||||||
|
.spawn(move || loop {
|
||||||
|
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,
|
||||||
|
desc,
|
||||||
|
} = meli_task;
|
||||||
|
if !timer {
|
||||||
|
log::trace!("Worker {} got task {:?} {:?}", i, desc, id);
|
||||||
|
}
|
||||||
|
let _ = catch_unwind(|| task.run());
|
||||||
|
if !timer {
|
||||||
|
log::trace!("Worker {} returned after {:?} {:?}", i, desc, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a future with a generic return value `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,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = oneshot::channel();
|
||||||
|
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 (handle, task) = async_task::spawn(
|
||||||
|
async move {
|
||||||
|
let res = future.await;
|
||||||
|
let _ = sender.send(res);
|
||||||
|
finished_sender
|
||||||
|
.send(ThreadEvent::JobFinished(job_id))
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
move |task| {
|
||||||
|
if *cancel.lock().unwrap() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let desc = desc.clone();
|
||||||
|
injector.push(MeliTask {
|
||||||
|
task,
|
||||||
|
id: job_id,
|
||||||
|
desc,
|
||||||
|
timer: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
handle.schedule();
|
||||||
|
for unparker in self.parkers.iter() {
|
||||||
|
unparker.unpark();
|
||||||
|
}
|
||||||
|
|
||||||
|
JoinHandle {
|
||||||
|
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
|
||||||
|
#[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(
|
||||||
|
desc,
|
||||||
|
smol::unblock(move || futures::executor::block_on(future)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
id,
|
||||||
|
job_executor: self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let value = timer.value;
|
||||||
|
drop(timers_lck);
|
||||||
|
self.arm_timer(timer_id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
loop {
|
||||||
|
smol::Timer::after(value).await;
|
||||||
|
sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::Timer(id)))
|
||||||
|
.unwrap();
|
||||||
|
if let Some(interval) = timers.lock().unwrap().get(&id).and_then(|timer| {
|
||||||
|
if timer.interval.as_millis() == 0 && timer.interval.as_secs() == 0 {
|
||||||
|
None
|
||||||
|
} else if timer.active {
|
||||||
|
Some(timer.interval)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
value = interval;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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();
|
||||||
|
for unparker in self.parkers.iter() {
|
||||||
|
unparker.unpark();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable_timer(&self, id: TimerId) {
|
||||||
|
let mut timers_lck = self.timers.lock().unwrap();
|
||||||
|
if let Some(timer) = timers_lck.get_mut(&id) {
|
||||||
|
timer.active = false;
|
||||||
|
*timer.cancel.lock().unwrap() = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
||||||
|
pub struct JoinHandle<T> {
|
||||||
|
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) -> Option<StatusEvent> {
|
||||||
|
let mut lck = self.cancel.lock().unwrap();
|
||||||
|
if !*lck {
|
||||||
|
*lck = true;
|
||||||
|
Some(StatusEvent::JobCanceled(self.job_id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::cmp::PartialEq<JobId> for JoinHandle<T> {
|
||||||
|
fn eq(&self, other: &JobId) -> bool {
|
||||||
|
self.job_id == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
impl Future for JoinHandle {
|
||||||
|
type Output = Result<()>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
match Pin::new(&mut self.inner).poll(cx) {
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
Poll::Ready(output) => Poll::Ready(output.expect("task failed")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -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,163 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
#[cfg(feature = "cli-docs")]
|
||||||
|
pub mod manpages;
|
||||||
|
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,30 +19,30 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* 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 super::*;
|
||||||
use melib::backends::{AccountHash, Mailbox, MailboxHash};
|
use crate::{
|
||||||
use melib::email::{attachment_types::*, attachments::*};
|
melib::text::{TextProcessing, Truncate},
|
||||||
use melib::thread::ThreadNodeHash;
|
uuid::Uuid,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod listing;
|
pub mod listing;
|
||||||
pub use crate::listing::*;
|
pub use crate::listing::*;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub use crate::view::*;
|
pub use crate::view::*;
|
||||||
mod compose;
|
pub mod compose;
|
||||||
pub use self::compose::*;
|
pub use self::compose::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "gpgme")]
|
||||||
pub mod pgp;
|
pub mod pgp;
|
||||||
|
|
||||||
mod status;
|
pub mod status;
|
||||||
pub use self::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()
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,330 @@
|
||||||
|
/*
|
||||||
|
* meli -
|
||||||
|
*
|
||||||
|
* Copyright 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, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub enum EditAttachmentCursor {
|
||||||
|
AttachmentNo(usize),
|
||||||
|
#[default]
|
||||||
|
Buttons,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EditAttachmentMode {
|
||||||
|
Overview,
|
||||||
|
Edit {
|
||||||
|
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,
|
||||||
|
pub dirty: bool,
|
||||||
|
pub id: ComponentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditAttachments {
|
||||||
|
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);
|
||||||
|
Self {
|
||||||
|
account_hash,
|
||||||
|
mode: EditAttachmentMode::Overview,
|
||||||
|
buttons,
|
||||||
|
cursor: EditAttachmentCursor::Buttons,
|
||||||
|
dirty: true,
|
||||||
|
id: ComponentId::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditAttachmentsRefMut<'_, '_> {
|
||||||
|
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 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(Box::new(ret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EditAttachmentsRefMut<'a, 'b> {
|
||||||
|
pub inner: &'a mut EditAttachments,
|
||||||
|
pub draft: &'b mut Draft,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EditAttachmentsRefMut<'_, '_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "edit attachments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for EditAttachmentsRefMut<'_, '_> {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if let EditAttachmentMode::Edit {
|
||||||
|
ref mut inner,
|
||||||
|
no: _,
|
||||||
|
} = self.inner.mode
|
||||||
|
{
|
||||||
|
inner.draw(grid, area, context);
|
||||||
|
} else if self.is_dirty() {
|
||||||
|
let attachments_no = self.draft.attachments().len();
|
||||||
|
let theme_default = crate::conf::value(context, "theme_default");
|
||||||
|
grid.clear_area(area, theme_default);
|
||||||
|
if attachments_no == 0 {
|
||||||
|
grid.write_string(
|
||||||
|
"no attachments",
|
||||||
|
theme_default.fg,
|
||||||
|
theme_default.bg,
|
||||||
|
theme_default.attrs,
|
||||||
|
area,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
grid.write_string(
|
||||||
|
&format!("{} attachments ", attachments_no),
|
||||||
|
theme_default.fg,
|
||||||
|
theme_default.bg,
|
||||||
|
theme_default.attrs,
|
||||||
|
area,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
for (i, a) in self.draft.attachments().iter().enumerate() {
|
||||||
|
let bg = if let EditAttachmentCursor::AttachmentNo(u) = self.inner.cursor {
|
||||||
|
if u == i {
|
||||||
|
crate::conf::value(context, "highlight").bg
|
||||||
|
} else {
|
||||||
|
theme_default.bg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
theme_default.bg
|
||||||
|
};
|
||||||
|
grid.write_string(
|
||||||
|
&if let Some(name) = a.content_type().name() {
|
||||||
|
format!(
|
||||||
|
"[{}] \"{}\", {} {}",
|
||||||
|
i,
|
||||||
|
name,
|
||||||
|
a.content_type(),
|
||||||
|
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,
|
||||||
|
area.skip_rows(3 + self.draft.attachments().len()),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
self.set_dirty(false);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
|
if let EditAttachmentMode::Edit {
|
||||||
|
ref mut inner,
|
||||||
|
ref no,
|
||||||
|
} = self.inner.mode
|
||||||
|
{
|
||||||
|
if inner.process_event(event, context) {
|
||||||
|
match inner.buttons_result() {
|
||||||
|
Some(FormButtonActions::Accept) | Some(FormButtonActions::Cancel) => {
|
||||||
|
self.inner.mode = EditAttachmentMode::Overview;
|
||||||
|
}
|
||||||
|
Some(FormButtonActions::Reset) => {
|
||||||
|
let no = *no;
|
||||||
|
if let Some(inner) = self.new_edit_widget(no, context) {
|
||||||
|
self.inner.mode = EditAttachmentMode::Edit { inner, no };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) | None => {}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let shortcuts = self.shortcuts(context);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
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) => {
|
||||||
|
if self.draft.attachments().is_empty() {
|
||||||
|
self.inner.cursor = EditAttachmentCursor::Buttons;
|
||||||
|
self.inner.buttons.set_focus(true);
|
||||||
|
self.inner.buttons.process_event(event, context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*n = n.saturating_sub(1);
|
||||||
|
}
|
||||||
|
EditAttachmentCursor::Buttons => {
|
||||||
|
if !self.inner.buttons.process_event(event, context) {
|
||||||
|
self.inner.buttons.set_focus(false);
|
||||||
|
if self.draft.attachments().is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.inner.cursor = EditAttachmentCursor::AttachmentNo(
|
||||||
|
self.draft.attachments().len() - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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) => {
|
||||||
|
if *n + 1 == self.draft.attachments().len() {
|
||||||
|
self.inner.cursor = EditAttachmentCursor::Buttons;
|
||||||
|
self.inner.buttons.set_focus(true);
|
||||||
|
self.inner.buttons.process_event(event, context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*n += 1;
|
||||||
|
}
|
||||||
|
EditAttachmentCursor::Buttons => {
|
||||||
|
self.inner.buttons.set_focus(true);
|
||||||
|
self.inner.buttons.process_event(event, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
UIEvent::Input(Key::Char('\n')) => {
|
||||||
|
match self.inner.cursor {
|
||||||
|
EditAttachmentCursor::AttachmentNo(ref no) => {
|
||||||
|
if let Some(inner) = self.new_edit_widget(*no, context) {
|
||||||
|
self.inner.mode = EditAttachmentMode::Edit { inner, no: *no };
|
||||||
|
}
|
||||||
|
self.set_dirty(true);
|
||||||
|
}
|
||||||
|
EditAttachmentCursor::Buttons => {
|
||||||
|
self.inner.buttons.process_event(event, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if self.inner.cursor == EditAttachmentCursor::Buttons
|
||||||
|
&& self.inner.buttons.process_event(event, context)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.inner.dirty
|
||||||
|
|| self.inner.buttons.is_dirty()
|
||||||
|
|| if let EditAttachmentMode::Edit { ref inner, no: _ } = self.inner.mode {
|
||||||
|
inner.is_dirty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dirty(&mut self, value: bool) {
|
||||||
|
self.inner.dirty = value;
|
||||||
|
self.inner.buttons.set_dirty(value);
|
||||||
|
if let EditAttachmentMode::Edit {
|
||||||
|
ref mut inner,
|
||||||
|
no: _,
|
||||||
|
} = self.inner.mode
|
||||||
|
{
|
||||||
|
inner.set_dirty(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill(&mut self, _uuid: ComponentId, _context: &mut Context) {}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
* meli
|
||||||
|
*
|
||||||
|
* 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 super::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KeySelection {
|
||||||
|
LoadingKeys {
|
||||||
|
handle: JoinHandle<Result<Vec<melib::gpgme::Key>>>,
|
||||||
|
progress_spinner: ProgressSpinner,
|
||||||
|
secret: bool,
|
||||||
|
local: bool,
|
||||||
|
pattern: String,
|
||||||
|
allow_remote_lookup: ActionFlag,
|
||||||
|
},
|
||||||
|
Error {
|
||||||
|
id: ComponentId,
|
||||||
|
err: Error,
|
||||||
|
},
|
||||||
|
Loaded {
|
||||||
|
widget: Box<UIDialog<melib::gpgme::Key>>,
|
||||||
|
keys: Vec<melib::gpgme::Key>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for KeySelection {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "select pgp keys")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeySelection {
|
||||||
|
pub fn new(
|
||||||
|
secret: bool,
|
||||||
|
local: bool,
|
||||||
|
pattern: String,
|
||||||
|
allow_remote_lookup: ActionFlag,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<Self> {
|
||||||
|
use melib::gpgme::*;
|
||||||
|
let mut ctx = Context::new()?;
|
||||||
|
if local {
|
||||||
|
ctx.set_auto_key_locate(LocateKey::LOCAL)?;
|
||||||
|
} else {
|
||||||
|
ctx.set_auto_key_locate(LocateKey::WKD | LocateKey::LOCAL)?;
|
||||||
|
}
|
||||||
|
let job = ctx.keylist(secret, Some(pattern.clone()))?;
|
||||||
|
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(Self::LoadingKeys {
|
||||||
|
handle,
|
||||||
|
secret,
|
||||||
|
local,
|
||||||
|
pattern,
|
||||||
|
allow_remote_lookup,
|
||||||
|
progress_spinner,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for KeySelection {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
match self {
|
||||||
|
Self::LoadingKeys {
|
||||||
|
ref mut progress_spinner,
|
||||||
|
..
|
||||||
|
} => progress_spinner.draw(grid, area.center_inside((2, 2)), context),
|
||||||
|
Self::Error { ref err, .. } => {
|
||||||
|
let theme_default = crate::conf::value(context, "theme_default");
|
||||||
|
grid.write_string(
|
||||||
|
&err.to_string(),
|
||||||
|
theme_default.fg,
|
||||||
|
theme_default.bg,
|
||||||
|
theme_default.attrs,
|
||||||
|
area.center_inside((15, 2)),
|
||||||
|
Some(0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Self::Loaded { ref mut widget, .. } => widget.draw(grid, area, context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::LoadingKeys {
|
||||||
|
ref mut progress_spinner,
|
||||||
|
ref mut handle,
|
||||||
|
secret,
|
||||||
|
local,
|
||||||
|
ref mut pattern,
|
||||||
|
allow_remote_lookup,
|
||||||
|
..
|
||||||
|
} => match event {
|
||||||
|
UIEvent::StatusEvent(StatusEvent::JobFinished(ref id)) if *id == handle.job_id => {
|
||||||
|
match handle.chan.try_recv() {
|
||||||
|
Err(_) => { /* Job was canceled */ }
|
||||||
|
Ok(None) => { /* something happened, perhaps a worker thread panicked */ }
|
||||||
|
Ok(Some(Ok(keys))) => {
|
||||||
|
if keys.is_empty() {
|
||||||
|
let id = progress_spinner.id();
|
||||||
|
if allow_remote_lookup.is_true() {
|
||||||
|
match Self::new(
|
||||||
|
*secret,
|
||||||
|
*local,
|
||||||
|
std::mem::take(pattern),
|
||||||
|
*allow_remote_lookup,
|
||||||
|
context,
|
||||||
|
) {
|
||||||
|
Ok(w) => {
|
||||||
|
*self = w;
|
||||||
|
}
|
||||||
|
Err(err) => *self = Self::Error { err, id },
|
||||||
|
}
|
||||||
|
} else if !*local && allow_remote_lookup.is_ask() {
|
||||||
|
*self = Self::Error {
|
||||||
|
err: Error::new(format!(
|
||||||
|
"No keys found for {}, perform remote lookup?",
|
||||||
|
pattern
|
||||||
|
)),
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*self = Self::Error {
|
||||||
|
err: Error::new(format!("No keys found for {}.", pattern)),
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Self::Error { ref err, .. } = self {
|
||||||
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::DisplayMessage(err.to_string()),
|
||||||
|
));
|
||||||
|
let res: Option<melib::gpgme::Key> = None;
|
||||||
|
context
|
||||||
|
.replies
|
||||||
|
.push_back(UIEvent::FinishedUIDialog(id, Box::new(res)));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut widget = Box::new(UIDialog::new(
|
||||||
|
"select key",
|
||||||
|
keys.iter()
|
||||||
|
.map(|k| {
|
||||||
|
(
|
||||||
|
k.clone(),
|
||||||
|
if let Some(primary_uid) = k.primary_uid() {
|
||||||
|
format!("{} {}", k.fingerprint(), primary_uid)
|
||||||
|
} else {
|
||||||
|
k.fingerprint().to_string()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(melib::gpgme::Key, String)>>(),
|
||||||
|
true,
|
||||||
|
Some(Box::new(
|
||||||
|
move |id: ComponentId, results: &[melib::gpgme::Key]| {
|
||||||
|
Some(UIEvent::FinishedUIDialog(
|
||||||
|
id,
|
||||||
|
Box::new(results.first().cloned()),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
context,
|
||||||
|
));
|
||||||
|
widget.set_dirty(true);
|
||||||
|
*self = Self::Loaded { widget, keys };
|
||||||
|
}
|
||||||
|
Ok(Some(Err(err))) => {
|
||||||
|
*self = Self::Error {
|
||||||
|
err,
|
||||||
|
id: ComponentId::default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => progress_spinner.process_event(event, context),
|
||||||
|
},
|
||||||
|
Self::Error { .. } => false,
|
||||||
|
Self::Loaded { ref mut widget, .. } => widget.process_event(event, context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::LoadingKeys {
|
||||||
|
ref progress_spinner,
|
||||||
|
..
|
||||||
|
} => progress_spinner.is_dirty(),
|
||||||
|
Self::Error { .. } => true,
|
||||||
|
Self::Loaded { ref widget, .. } => widget.is_dirty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dirty(&mut self, value: bool) {
|
||||||
|
match self {
|
||||||
|
Self::LoadingKeys {
|
||||||
|
ref mut progress_spinner,
|
||||||
|
..
|
||||||
|
} => progress_spinner.set_dirty(value),
|
||||||
|
Self::Error { .. } => {}
|
||||||
|
Self::Loaded { ref mut widget, .. } => widget.set_dirty(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill(&mut self, _uuid: ComponentId, _context: &mut Context) {}
|
||||||
|
|
||||||
|
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||||
|
match self {
|
||||||
|
Self::LoadingKeys { .. } | Self::Error { .. } => ShortcutMaps::default(),
|
||||||
|
Self::Loaded { ref widget, .. } => widget.shortcuts(context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> ComponentId {
|
||||||
|
match self {
|
||||||
|
Self::LoadingKeys {
|
||||||
|
ref progress_spinner,
|
||||||
|
..
|
||||||
|
} => progress_spinner.id(),
|
||||||
|
Self::Error { ref id, .. } => *id,
|
||||||
|
Self::Loaded { ref widget, .. } => widget.id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GpgComposeState {
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue