mirror of https://github.com/djcb/mu.git
Revert "build: bump version to 1.7.0"
Let's keep this away from the release branch. This reverts commit
ec40b18ae4
.
This commit is contained in:
parent
42f2e73025
commit
6d555b3a6c
|
@ -1,39 +0,0 @@
|
|||
---
|
||||
AlignAfterOpenBracket: 'Align'
|
||||
AlignConsecutiveAssignments: 'false'
|
||||
AlignConsecutiveDeclarations: 'false'
|
||||
AlignConsecutiveMacros: 'true'
|
||||
AlignOperands: 'true'
|
||||
AlignTrailingComments: 'true'
|
||||
AllowAllArgumentsOnNextLine: 'false'
|
||||
AllowAllParametersOfDeclarationOnNextLine: 'false'
|
||||
AllowShortBlocksOnASingleLine: 'false'
|
||||
AllowShortCaseLabelsOnASingleLine: 'false'
|
||||
AllowShortFunctionsOnASingleLine: 'Inline'
|
||||
AllowShortIfStatementsOnASingleLine: 'false'
|
||||
AlwaysBreakAfterReturnType: 'All'
|
||||
BinPackParameters: 'false'
|
||||
BinPackArguments: 'false'
|
||||
BreakBeforeBraces: 'Linux'
|
||||
ColumnLimit: '100'
|
||||
DerivePointerAlignment: 'false'
|
||||
IndentCaseLabels: 'false'
|
||||
IndentWidth: '8'
|
||||
KeepEmptyLinesAtTheStartOfBlocks: 'false'
|
||||
Language: 'Cpp'
|
||||
MaxEmptyLinesToKeep: '1'
|
||||
PointerAlignment: 'Right'
|
||||
SortIncludes: 'false'
|
||||
SpaceAfterCStyleCast: 'false'
|
||||
SpaceBeforeAssignmentOperators : 'true'
|
||||
SpaceBeforeParens: 'ControlStatements'
|
||||
SpaceInEmptyParentheses: 'false'
|
||||
SpacesInSquareBrackets: 'false'
|
||||
TabWidth: '8'
|
||||
UseTab: 'AlignWithSpaces'
|
||||
PenaltyBreakAssignment: '3'
|
||||
PenaltyBreakBeforeFirstCallParameter: '15'
|
||||
|
||||
# Local Variables:
|
||||
# mode: yaml
|
||||
# End:
|
|
@ -0,0 +1,2 @@
|
|||
((emacs-lisp-mode
|
||||
(indent-tabs-mode . nil)))
|
|
@ -2,7 +2,6 @@
|
|||
# editorconfig file (see EditorConfig.org), with some
|
||||
# lowest-denominator settings that should work for many editors.
|
||||
|
||||
|
||||
root = true # this is the top-level
|
||||
|
||||
[*]
|
||||
|
@ -11,20 +10,23 @@ insert_final_newline = true
|
|||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# The "best" answer is "tabs-for-indentation; spaces for alignment".
|
||||
# The "best" answer is "tabs-for-indentation; spaces for alignment", but in
|
||||
# practice that's hard to accomplish in many editors.
|
||||
#
|
||||
# So we use spaces instead, at least that looks consistent for all
|
||||
|
||||
[*.{cc,cpp,hh,hpp}]
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 8
|
||||
max_line_length = 100
|
||||
|
||||
[*.{c,h}]
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 8
|
||||
max_line_length = 80
|
||||
|
||||
[configure.ac]
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 100
|
||||
|
||||
|
|
|
@ -22,12 +22,12 @@ jobs:
|
|||
name: ubuntu-deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install meson ninja-build libglib2.0-dev libxapian-dev libgmime-3.0-dev pkg-config
|
||||
sudo apt-get install automake autoconf-archive autotools-dev libglib2.0-dev libxapian-dev libgmime-3.0-dev m4 make libtool pkg-config
|
||||
|
||||
- if: contains(matrix.os, 'macos')
|
||||
name: macos-deps
|
||||
run: |
|
||||
brew install meson ninja libgpg-error libtool pkg-config glib gmime xapian
|
||||
brew install autoconf automake libgpg-error libtool pkg-config gettext glib gmime xapian
|
||||
|
||||
- name: configure
|
||||
run: ./autogen.sh --disable-guile
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
language: c
|
||||
sudo: required
|
||||
compiler:
|
||||
- gcc
|
||||
env:
|
||||
global:
|
||||
- BUILD_PKGS="libtool autoconf autoconf-archive automake texinfo"
|
||||
- BUILD_LIBS="libgmime-2.6-dev libxapian-dev guile-2.0-dev libwebkitgtk-dev"
|
||||
- TEST_PKGS="pmccabe"
|
||||
matrix:
|
||||
- EVM_EMACS=emacs-24.1-bin
|
||||
- EVM_EMACS=emacs-24.2-bin
|
||||
- EVM_EMACS=emacs-24.3-bin
|
||||
# - EVM_EMACS=emacs-24.5-travis
|
||||
# - EVM_EMACS=emacs-25.1-travis
|
||||
before_install:
|
||||
- git submodule update --init --recursive
|
||||
# The Ubuntu version on travis is way too old, need Autoconf 2.69
|
||||
- sudo add-apt-repository ppa:dns/gnu -y
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -qq ${BUILD_PKGS} ${BUILD_LIBS} ${TEST_PKGS}
|
||||
install:
|
||||
- sudo mkdir /usr/local/evm
|
||||
- sudo chown $(id -u):$(id -g) /usr/local/evm
|
||||
- curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash
|
||||
- export PATH="$HOME/.evm/bin:$PATH"
|
||||
- evm install $EVM_EMACS --use
|
||||
script:
|
||||
# Need recent version of autoconf-archive
|
||||
- curl http://nl.mirror.babylon.network/gnu/autoconf-archive/autoconf-archive-2016.09.16.tar.xz -o /tmp/aa.tar.xz && tar xf /tmp/aa.tar.xz
|
||||
- cp autoconf-archive-2016.09.16/m4/*.m4 m4/
|
||||
- ./autogen.sh
|
||||
- ./configure
|
||||
- make
|
||||
- make check
|
|
@ -0,0 +1,130 @@
|
|||
* HACKING
|
||||
|
||||
Here are some guidelines for hacking on the 'mu' source code.
|
||||
|
||||
This is a fairly long list -- this is not meant to discourage anyone from
|
||||
working on mu; I think most of the rules are common sense anyway, and some of
|
||||
the more stylistic-aesthetic rules are clearly visible in current source code,
|
||||
so as long as any new code 'fits in', it should go a long way in satisfying
|
||||
them.
|
||||
|
||||
I should add some notes for the Lisp/Scheme code as well...
|
||||
|
||||
** Coding style
|
||||
|
||||
For consistency and, more important, to keep things understandable, mu
|
||||
attempts to follow the following rules:
|
||||
|
||||
1. Basic code layout is like in the Linux kernel coding style. Keep the '{'
|
||||
on the same line as the statement, except for functions. We're slowly
|
||||
moving to use SPC for indentation: all new code should use that.
|
||||
|
||||
While TABs are techically better, it seems that using SPCs is harder to
|
||||
get wrong.
|
||||
|
||||
2. Lines should not exceed 100 characters
|
||||
|
||||
3. Functions should be kept short.
|
||||
|
||||
4. Source files should not exceed 1000 lines, with few exceptions.
|
||||
|
||||
5. Non-static C-functions have the prefix based on their module, e.g.,
|
||||
~mu-foo.h~ declares a function of 'mu_foo_bar (int a);', mu-foo.c implements
|
||||
this. C++ functions use the Mu namespace
|
||||
|
||||
6. Non-global functions *don't* have the module prefix, and are declared
|
||||
static.
|
||||
|
||||
7. Functions have their return type on a separate line before the function
|
||||
name, so:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
static int
|
||||
foo (const char *bar)
|
||||
{
|
||||
....
|
||||
}
|
||||
#+END_EXAMPLE
|
||||
|
||||
8. In C code, variable-declarations are at the beginning of a block.
|
||||
|
||||
In C code, the declaration does *not* initialize the variable. This will
|
||||
give the compiler a chance to warn us if the variable is not initialized
|
||||
in a certain code path. Exception: autoptr & friends.
|
||||
|
||||
9. Returned strings of type ~char*~ must be freed by the caller; if they are
|
||||
not to be freed, ~const char*~ should be used instead
|
||||
|
||||
10. Functions calls have a space between function name and arguments, unless
|
||||
there are none, so:
|
||||
|
||||
~foo (12, 3)~;
|
||||
|
||||
and
|
||||
|
||||
~bar();~
|
||||
|
||||
after a comma, a space should follow.
|
||||
|
||||
11. C-functions that do not take arguments are explicitly declared as f(void)
|
||||
and not f(). Reason: f() means that the arguments are /unspecified/ (in C)
|
||||
|
||||
12. C-code should not use ~//~ comments.
|
||||
|
||||
|
||||
** Logging
|
||||
|
||||
For logging, mu uses the GLib logging functions/macros as listed below,
|
||||
except when logging may not have been initialized.
|
||||
|
||||
The logging system redirects most logging to the log file (typically,
|
||||
=~/.cache/mu/mu.log, or to the systemd journal=). ~g_critical~ messages are
|
||||
written to stderr.
|
||||
|
||||
- ~g_message~ is for non-error messages the user will see (unless running with
|
||||
~--quiet~)
|
||||
- ~g_warning~ is for problems the user may be able to do something about (and
|
||||
they are written on ~stderr~)
|
||||
- ~g_critical~ is for mu bugs, serious, internal problems (~g_return_if_fail~ and
|
||||
friends use this). (and they are written on ~stderr~)
|
||||
- don't use ~g_error~
|
||||
|
||||
** Compiling from git
|
||||
|
||||
For hacking, you're strongly advised to use the latest git version.
|
||||
Compilation from git should be straightforward, if you have the right tools
|
||||
installed.
|
||||
|
||||
*** dependencies
|
||||
|
||||
You need to install a few dependencies; e.g. on Debian/Ubuntu:
|
||||
#+BEGIN_EXAMPLE
|
||||
sudo apt-get install \
|
||||
automake \
|
||||
autoconf-archive \
|
||||
autotools-dev \
|
||||
libglib2.0-dev \
|
||||
libxapian-dev \
|
||||
libgmime-3.0-dev \
|
||||
m4 \
|
||||
make \
|
||||
libtool \
|
||||
pkg-config
|
||||
#+END_EXAMPLE
|
||||
|
||||
Then, to compile straight from ~git~:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
$ git clone https://github.com/djcb/mu
|
||||
$ cd mu
|
||||
$ ./autogen.sh
|
||||
$ make
|
||||
#+END_EXAMPLE
|
||||
|
||||
You only need to run ~./autogen.sh~ the first time and after changes in the
|
||||
build system; otherwise you can use ~./configure~.
|
||||
|
||||
# Local Variables:
|
||||
# mode: org; org-startup-folded: nofold
|
||||
# fill-column: 80
|
||||
# End:
|
|
@ -44,6 +44,7 @@ tags:
|
|||
|
||||
EXTRA_DIST= \
|
||||
TODO \
|
||||
HACKING \
|
||||
README.org \
|
||||
gtest.mk \
|
||||
NEWS \
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
## Copyright (C) 2008-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
##
|
||||
## This program 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.
|
||||
##
|
||||
## This program 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 this program; if not, write to the Free Software Foundation,
|
||||
## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
# Makefile with some useful targets for meson/ninja
|
||||
|
||||
NINJA ?= ninja
|
||||
BUILDDIR ?= $(CURDIR)/build
|
||||
MESON ?= meson
|
||||
|
||||
all: $(BUILDDIR)
|
||||
$(NINJA) -C $(BUILDDIR)
|
||||
|
||||
$(BUILDDIR):
|
||||
$(MESON) $(BUILDDIR)
|
||||
|
||||
check: test
|
||||
|
||||
test: $(BUILDDIR)
|
||||
@cd $(BUILDDIR); $(MESON) test
|
||||
|
||||
dist: $(BUILDDIR)
|
||||
@cd $(BUILDDIR); $(MESON) dist
|
||||
|
||||
install: $(BUILDDIR)
|
||||
@cd $(BUILDDIR); $(MESON) install
|
||||
|
||||
clean:
|
||||
@test -d $(BUILDDIR) && $(NINJA) -C $(BUILDDIR) clean
|
||||
|
48
NEWS.org
48
NEWS.org
|
@ -1,54 +1,6 @@
|
|||
#+STARTUP:showall
|
||||
* NEWS (user visible changes & bigger non-visible ones)
|
||||
|
||||
* 1.7 (unreleased, development)
|
||||
|
||||
*** mu
|
||||
|
||||
- ~mu~ switched to use the [[https://mesonbuild.com][meson]] build system by default. The existing
|
||||
~autotools~ is still avaiable, but is to be removed after the 1.8 release.
|
||||
|
||||
Using ~meson~ (which you need to install), you can use something like in the
|
||||
mu top directory:
|
||||
#+begin example
|
||||
$ meson build && ninja -C build
|
||||
#+end example
|
||||
|
||||
After that, ~ninja -C build~ should be enough. ~autogen.sh~ has been updated
|
||||
to use ~meson~ instead, and and even delivers a ~Makefile~ with some useful
|
||||
targets that invoke the corresponding ~meson~ / ~ninja~ targets.
|
||||
|
||||
- ~mu~ now require C++17 support for building (this is in preparation for code
|
||||
that actually _uses_ C++17)
|
||||
|
||||
*** mu4e
|
||||
|
||||
- the old mu4e-view is gone; only the gnus-based one remains, this allowed
|
||||
for removing quite a bit of old code.
|
||||
|
||||
- a lot of the internals have been changed:
|
||||
|
||||
- ~mu4e~ is slowly moving from using the ~~~ to the more common ~--~ separator for
|
||||
private functions; i.e., ~mu4e~foo~ becomes ~mu4e--foo~.
|
||||
- ~mu4e-utils.el~ had become a bit of a dumping ground for bits of code;
|
||||
it's gone now, with the functionality move to topic-specific files --
|
||||
~mu4e-folders.el~, ~mu4e-bookmarks.el~, ~mu4e-update.el~, and included in
|
||||
existing files.
|
||||
- the remaining common functionality has ended up in ~mu4e-helpers.el~
|
||||
- ~mu4e-search.el~ takes the search-specific code from ~mu4e-headers.el~, and
|
||||
adds a minor-mode for the keybindings.
|
||||
- ~mu4e-context.el~ and ~mu4e-update.el~ also define minor modes with
|
||||
keybindings, which saves a lot of code in the various views, since they
|
||||
don't need explicitly bind all those function.
|
||||
- also ~mu4e-vars.el~ had become very big, we're refactoring the factories
|
||||
~defvar~ / ~defcustom~ declarations to the topic-specific files.
|
||||
|
||||
*** guile
|
||||
|
||||
- the guile interface has been deprecated. It may be revamped at some point,
|
||||
but will be different from the curren one. To be removed after 1.8.x
|
||||
|
||||
|
||||
* 1.6 (released, as of July 27 2021)
|
||||
|
||||
NOTE: After upgrading, you need to call ~mu init~, with your prefered parameters
|
||||
|
|
31
autogen.sh
31
autogen.sh
|
@ -1,29 +1,34 @@
|
|||
#!/bin/sh
|
||||
# Run this to generate all the initial makefiles, etc.
|
||||
|
||||
echo "*** meson build setup"
|
||||
|
||||
test -f mu/mu.cc || {
|
||||
echo "*** Run this script from the top-level mu source directory"
|
||||
exit 1
|
||||
}
|
||||
|
||||
BUILDDIR=build
|
||||
# opportunistically; usually not needed, but occasionally it'll
|
||||
# avoid build errors that would otherwise confuse users.
|
||||
test -f Makefile && {
|
||||
echo "*** clear out old things"
|
||||
make distclean 2> /dev/null
|
||||
}
|
||||
|
||||
command -v meson 2> /dev/null
|
||||
|
||||
command -V autoreconf > /dev/null
|
||||
if [ $? != 0 ]; then
|
||||
echo "*** No meson found, please install it ***"
|
||||
echo "*** No autoreconf found, please install it ***"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# we could remove build/ but let's avoid rm -rf risks...
|
||||
if test -d ${BUILDDIR}; then
|
||||
meson --reconfigure ${BUILDDIR}
|
||||
rm -f config.cache
|
||||
rm -rf autom4te.cache
|
||||
|
||||
autoreconf --force --install --verbose || exit $?
|
||||
|
||||
if test -z "$*"; then
|
||||
echo "# Configuring without parameters"
|
||||
else
|
||||
meson ${BUILDDIR} $@
|
||||
echo "# Configure with parameters $*"
|
||||
fi
|
||||
|
||||
# Add a Makefile with some useful target
|
||||
cp Makefile.meson Makefile
|
||||
|
||||
echo "*** Now run 'ninja -C ${BUILDDIR}' to build mu"
|
||||
./configure --config-cache $@
|
||||
|
|
12
configure.ac
12
configure.ac
|
@ -15,7 +15,7 @@
|
|||
## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
AC_PREREQ([2.68])
|
||||
AC_INIT([mu],[1.7.0],[https://github.com/djcb/mu/issues],[mu])
|
||||
AC_INIT([mu],[1.6.5],[https://github.com/djcb/mu/issues],[mu])
|
||||
AC_COPYRIGHT([Copyright (C) 2008-2021 Dirk-Jan C. Binnema])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_SRCDIR([mu/mu.cc])
|
||||
|
@ -42,15 +42,15 @@ AC_PROG_CC_C99
|
|||
AC_PROG_INSTALL
|
||||
AC_HEADER_STDC
|
||||
|
||||
extra_flags="-Wformat-security \
|
||||
extra_flags="-Wformat-security \
|
||||
-Wstack-protector \
|
||||
-Wstack-protector-all \
|
||||
-Wno-cast-function-type \
|
||||
-Wno-bad-function-cast"
|
||||
|
||||
AX_CXX_COMPILE_STDCXX_17
|
||||
AX_CXX_COMPILE_STDCXX_14
|
||||
AX_COMPILER_FLAGS_CXXFLAGS([],[],[${extra_cflags}])
|
||||
AX_APPEND_COMPILE_FLAGS([-Wno-inline],[CXXFLAGS])
|
||||
AX_APPEND_COMPILE_FLAGS([-Wno-inline ],[CXXFLAGS])
|
||||
AX_VALGRIND_CHECK
|
||||
|
||||
LT_INIT
|
||||
|
@ -363,10 +363,6 @@ AS_IF([test "x$ac_cv_header_wordexp_h" != "xyes"],[
|
|||
echo " --maildir=/home/user/Maildir"
|
||||
])
|
||||
|
||||
|
||||
echo "NOTE: autotools support has been deprecated and will be removed"
|
||||
echo " after the next stable release. use the meson build instead"
|
||||
|
||||
echo
|
||||
echo "Now, type 'make' (or 'gmake') to build mu"
|
||||
echo
|
||||
|
|
151
lib/mu-server.cc
151
lib/mu-server.cc
|
@ -95,6 +95,7 @@ struct Server::Private {
|
|||
void add_handler (const Parameters& params);
|
||||
void compose_handler (const Parameters& params);
|
||||
void contacts_handler (const Parameters& params);
|
||||
void extract_handler (const Parameters& params);
|
||||
void find_handler (const Parameters& params);
|
||||
void help_handler (const Parameters& params);
|
||||
void index_handler (const Parameters& params);
|
||||
|
@ -213,6 +214,23 @@ Server::Private::make_command_map ()
|
|||
"return changes since tstamp" }}},
|
||||
"get contact information",
|
||||
[&](const auto& params){contacts_handler(params);}});
|
||||
|
||||
cmap.emplace("extract",
|
||||
CommandInfo{
|
||||
ArgMap{{":docid", ArgInfo{Type::Number, true, "document for the message" }},
|
||||
{":index", ArgInfo{Type::Number, true,
|
||||
"index for the part to operate on" }},
|
||||
{":action", ArgInfo{Type::Symbol, true, "what to do with the part" }},
|
||||
{":decrypt", ArgInfo{Type::Symbol, false,
|
||||
"whether to decrypt encrypted parts (if any)" }},
|
||||
{":path", ArgInfo{Type::String, false,
|
||||
"part for saving (for action: save)" }},
|
||||
{":what", ArgInfo{Type::Symbol, false,
|
||||
"what to do with the part (feedback)" }},
|
||||
{":param", ArgInfo{Type::String, false, "parameter for 'what'" }}},
|
||||
"extract mime-parts from a message",
|
||||
[&](const auto& params){extract_handler(params);}});
|
||||
|
||||
cmap.emplace("find",
|
||||
CommandInfo{
|
||||
ArgMap{ {":query", ArgInfo{Type::String, true, "search expression" }},
|
||||
|
@ -303,6 +321,13 @@ Server::Private::make_command_map ()
|
|||
{":path", ArgInfo{Type::String, false, "message filesystem path"}},
|
||||
{":mark-as-read", ArgInfo{Type::Symbol, false,
|
||||
"mark message as read (if not already)"}},
|
||||
{":extract-images", ArgInfo{Type::Symbol, false,
|
||||
"whether to extract images for this messages (if any)"}},
|
||||
{":decrypt", ArgInfo{Type::Symbol, false,
|
||||
"whether to decrypt encrypted parts (if any)" }},
|
||||
{":verify", ArgInfo{Type::Symbol, false,
|
||||
"whether to verify signatures (if any)" }}
|
||||
|
||||
},
|
||||
"view a message. exactly one of docid/msgid/path must be specified",
|
||||
[&](const auto& params){view_handler(params);}});
|
||||
|
@ -354,10 +379,15 @@ Server::Private::invoke (const std::string& expr) noexcept
|
|||
static MuMsgOptions
|
||||
message_options (const Parameters& params)
|
||||
{
|
||||
const auto extract_images{get_bool_or(params, ":extract-images", false)};
|
||||
const auto decrypt{get_bool_or(params, ":decrypt", false)};
|
||||
const auto verify{get_bool_or(params, ":verify", false)};
|
||||
|
||||
int opts{MU_MSG_OPTION_NONE};
|
||||
|
||||
if (extract_images)
|
||||
opts |= MU_MSG_OPTION_EXTRACT_IMAGES;
|
||||
if (verify)
|
||||
opts |= MU_MSG_OPTION_VERIFY | MU_MSG_OPTION_USE_AGENT;
|
||||
if (decrypt)
|
||||
opts |= MU_MSG_OPTION_DECRYPT | MU_MSG_OPTION_USE_AGENT;
|
||||
|
||||
|
@ -513,6 +543,123 @@ Server::Private::contacts_handler (const Parameters& params)
|
|||
output_sexp(std::move(seq));
|
||||
}
|
||||
|
||||
|
||||
static Sexp::List
|
||||
save_part (MuMsg *msg, unsigned docid, unsigned index,
|
||||
MuMsgOptions opts, const Parameters& params)
|
||||
{
|
||||
const auto path{get_string_or(params, ":path")};
|
||||
if (path.empty())
|
||||
throw Error{Error::Code::Command, "missing path"};
|
||||
|
||||
GError *gerr{};
|
||||
if (!mu_msg_part_save (msg, (MuMsgOptions)(opts | (int)MU_MSG_OPTION_OVERWRITE),
|
||||
path.c_str(), index, &gerr))
|
||||
throw Error{Error::Code::File, &gerr, "failed to save part"};
|
||||
|
||||
Sexp::List seq;
|
||||
seq.add_prop(":info", Sexp::make_symbol("save"));
|
||||
seq.add_prop(":message", Sexp::make_string(format("%s has been saved", path.c_str())));
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
|
||||
static Sexp::List
|
||||
open_part (MuMsg *msg, unsigned docid, unsigned index, MuMsgOptions opts)
|
||||
{
|
||||
GError *gerr{};
|
||||
char *targetpath{mu_msg_part_get_cache_path (msg, opts, index, &gerr)};
|
||||
if (!targetpath)
|
||||
throw Error{Error::Code::File, &gerr, "failed to get cache-path"};
|
||||
|
||||
if (!mu_msg_part_save (msg, (MuMsgOptions)(opts | MU_MSG_OPTION_USE_EXISTING),
|
||||
targetpath, index, &gerr)) {
|
||||
g_free(targetpath);
|
||||
throw Error{Error::Code::File, &gerr, "failed to save to cache-path"};
|
||||
}
|
||||
|
||||
if (!mu_util_play (targetpath, TRUE,/*allow local*/
|
||||
FALSE/*allow remote*/, &gerr)) {
|
||||
g_free(targetpath);
|
||||
throw Error{Error::Code::File, &gerr, "failed to play"};
|
||||
}
|
||||
|
||||
Sexp::List seq;
|
||||
seq.add_prop(":info", Sexp::make_symbol("open"));
|
||||
seq.add_prop(":message", Sexp::make_string(format("%s has been opened", targetpath)));
|
||||
g_free (targetpath);
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
static Sexp::List
|
||||
temp_part (MuMsg *msg, unsigned docid, unsigned index,
|
||||
MuMsgOptions opts, const Parameters& params)
|
||||
{
|
||||
const auto what{get_symbol_or(params, ":what")};
|
||||
if (what.empty())
|
||||
throw Error{Error::Code::Command, "missing 'what'"};
|
||||
|
||||
const auto param{get_string_or(params, ":param")};
|
||||
|
||||
GError *gerr{};
|
||||
char *path{mu_msg_part_get_cache_path (msg, opts, index, &gerr)};
|
||||
if (!path)
|
||||
throw Error{Error::Code::File, &gerr, "could not get cache path"};
|
||||
|
||||
if (!mu_msg_part_save (msg, (MuMsgOptions)(opts | MU_MSG_OPTION_USE_EXISTING),
|
||||
path, index, &gerr)) {
|
||||
g_free(path);
|
||||
throw Error{Error::Code::File, &gerr, "saving failed"};
|
||||
}
|
||||
|
||||
Sexp::List lst;
|
||||
lst.add_prop(":temp", Sexp::make_string(path));
|
||||
lst.add_prop(":what", Sexp::make_string(what));
|
||||
lst.add_prop(":docid", Sexp::make_number(docid));
|
||||
|
||||
if (!param.empty())
|
||||
lst.add_prop(":param", Sexp::make_string(param));
|
||||
|
||||
g_free(path);
|
||||
return lst;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 'extract' extracts some mime part from a message */
|
||||
void
|
||||
Server::Private::extract_handler (const Parameters& params)
|
||||
{
|
||||
const auto docid{get_int_or(params, ":docid")};
|
||||
const auto index{get_int_or(params, ":index")};
|
||||
const auto opts{message_options(params)};
|
||||
|
||||
auto msg{store().find_message(docid)};
|
||||
if (!msg)
|
||||
throw Error{Error::Code::Store, "failed to get message"};
|
||||
|
||||
try {
|
||||
const auto action{get_symbol_or(params, ":action")};
|
||||
if (action == "save")
|
||||
output_sexp(save_part (msg, docid, index, opts, params));
|
||||
else if (action == "open")
|
||||
output_sexp(open_part (msg, docid, index, opts));
|
||||
else if (action == "temp")
|
||||
output_sexp(temp_part (msg, docid, index, opts, params));
|
||||
else {
|
||||
throw Error{Error::Code::InvalidArgument,
|
||||
"unknown action '%s'", action.c_str()};
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
mu_msg_unref (msg);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* get a *list* of all messages with the given message id */
|
||||
static std::vector<Store::Id>
|
||||
docids_for_msgid (const Query& q, const std::string& msgid, size_t max=100)
|
||||
|
@ -1073,7 +1220,7 @@ Server::Private::view_handler (const Parameters& params)
|
|||
|
||||
Sexp::List seq;
|
||||
seq.add_prop(":view", build_message_sexp(
|
||||
msg, docid, {}, MU_MSG_OPTION_NONE));
|
||||
msg, docid, {}, message_options(params)));
|
||||
mu_msg_unref(msg);
|
||||
output_sexp (std::move(seq));
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## Copyright (C) 2008-2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
## Copyright (C) 2008-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
|
@ -36,7 +36,6 @@ EXTRA_DIST= \
|
|||
ax_compiler_flags_ldflags.m4 \
|
||||
ax_cxx_compile_stdcxx.m4 \
|
||||
ax_cxx_compile_stdcxx_14.m4 \
|
||||
ax_cxx_compile_stdcxx_17.m4 \
|
||||
ax_file_escapes.m4 \
|
||||
ax_is_release.m4 \
|
||||
ax_lib_readline.m4 \
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
# =============================================================================
|
||||
# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_17.html
|
||||
# =============================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_CXX_COMPILE_STDCXX_17([ext|noext], [mandatory|optional])
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# Check for baseline language coverage in the compiler for the C++17
|
||||
# standard; if necessary, add switches to CXX and CXXCPP to enable
|
||||
# support.
|
||||
#
|
||||
# This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX
|
||||
# macro with the version set to C++17. The two optional arguments are
|
||||
# forwarded literally as the second and third argument respectively.
|
||||
# Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for
|
||||
# more information. If you want to use this macro, you also need to
|
||||
# download the ax_cxx_compile_stdcxx.m4 file.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu>
|
||||
# Copyright (c) 2016 Krzesimir Nowak <qdlacz@gmail.com>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty provided the copyright notice
|
||||
# and this notice are preserved. This file is offered as-is, without any
|
||||
# warranty.
|
||||
|
||||
#serial 2
|
||||
|
||||
AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX])
|
||||
AC_DEFUN([AX_CXX_COMPILE_STDCXX_17], [AX_CXX_COMPILE_STDCXX([17], [$1], [$2])])
|
|
@ -18,14 +18,14 @@
|
|||
# project setup
|
||||
#
|
||||
project('mu', ['c', 'cpp'],
|
||||
version: '1.7.0',
|
||||
version: '1.6.5',
|
||||
meson_version: '>= 0.52.0', # debian 10
|
||||
license: 'GPL-3.0-or-later',
|
||||
default_options : [
|
||||
'buildtype=debugoptimized',
|
||||
'warning_level=1',
|
||||
'c_std=c11',
|
||||
'cpp_std=c++17'
|
||||
'cpp_std=c++14'
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -51,6 +51,7 @@ extra_flags = [
|
|||
'-Winit-self',
|
||||
'-Wmissing-include-dirs',
|
||||
'-Wpointer-arith',
|
||||
#'-Wswitch-enum',
|
||||
'-Wswitch-default',
|
||||
]
|
||||
|
||||
|
|
|
@ -23,26 +23,26 @@ mu4e_TEXINFOS=fdl.texi
|
|||
|
||||
dist_lisp_LISP= \
|
||||
mu4e-actions.el \
|
||||
mu4e-bookmarks.el \
|
||||
mu4e-compose.el \
|
||||
mu4e-context.el \
|
||||
mu4e-contrib.el \
|
||||
mu4e-draft.el \
|
||||
mu4e-folders.el \
|
||||
mu4e-headers.el \
|
||||
mu4e-icalendar.el \
|
||||
mu4e-lists.el \
|
||||
mu4e-helpers.el \
|
||||
mu4e-main.el \
|
||||
mu4e-mark.el \
|
||||
mu4e-message.el \
|
||||
mu4e-meta.el \
|
||||
mu4e-org.el \
|
||||
mu4e-server.el \
|
||||
mu4e-proc.el \
|
||||
mu4e-speedbar.el \
|
||||
mu4e-update.el \
|
||||
mu4e-utils.el \
|
||||
mu4e-vars.el \
|
||||
mu4e-view.el \
|
||||
mu4e-view-common.el \
|
||||
mu4e-view-gnus.el \
|
||||
mu4e-view-old.el \
|
||||
mu4e.el \
|
||||
obsolete/org-mu4e.el
|
||||
|
||||
|
|
|
@ -23,21 +23,18 @@ mu4e_meta = configure_file(
|
|||
'VERSION' : meson.project_version(),
|
||||
# project_build_root() with meson >= 0.56
|
||||
'abs_top_builddir': join_paths(meson.build_root()),
|
||||
'MU_DOC_DIR' : join_paths(datadir, 'doc', 'mu'),
|
||||
'MU_DOC_DIR' : join_paths(datadir, 'doc', 'mu')
|
||||
})
|
||||
|
||||
|
||||
mu4e_srcs=[
|
||||
'mu4e-actions.el',
|
||||
'mu4e-bookmarks.el',
|
||||
'mu4e-compose.el',
|
||||
'mu4e-context.el',
|
||||
'mu4e-contrib.el',
|
||||
'mu4e-draft.el',
|
||||
'mu4e-folders.el',
|
||||
'mu4e.el',
|
||||
'mu4e-headers.el',
|
||||
'mu4e-helpers.el',
|
||||
'mu4e-icalendar.el',
|
||||
'mu4e-lists.el',
|
||||
'mu4e-main.el',
|
||||
|
@ -45,21 +42,17 @@ mu4e_srcs=[
|
|||
'mu4e-message.el',
|
||||
join_paths(meson.current_build_dir(), 'mu4e-meta.el'),
|
||||
'mu4e-org.el',
|
||||
'mu4e-server.el',
|
||||
'mu4e-proc.el',
|
||||
'mu4e-speedbar.el',
|
||||
'mu4e-update.el',
|
||||
'mu4e-utils.el',
|
||||
'mu4e-vars.el',
|
||||
'mu4e-view.el',
|
||||
'mu4e-view-common.el',
|
||||
'mu4e-view-gnus.el',
|
||||
'mu4e-view-old.el',
|
||||
'obsolete/org-mu4e.el',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# with (development) emacs 28.50 / native compilation this doesn't
|
||||
# _quite_ work; see: https://debbugs.gnu.org/db/47/47987.html
|
||||
#
|
||||
lispdir = join_paths(datadir, 'emacs', 'site-lisp', 'mu4e')
|
||||
|
||||
foreach src : mu4e_srcs
|
||||
target_name= '@BASENAME@.elc'
|
||||
target_path = join_paths(meson.current_build_dir(), target_name)
|
||||
|
@ -68,8 +61,6 @@ foreach src : mu4e_srcs
|
|||
build_by_default: true,
|
||||
input: src,
|
||||
output: target_name,
|
||||
install_dir: lispdir,
|
||||
install: true,
|
||||
command: [emacs,
|
||||
'--no-init-file',
|
||||
'--batch',
|
||||
|
@ -79,9 +70,6 @@ foreach src : mu4e_srcs
|
|||
'--funcall', 'batch-byte-compile', '@INPUT@'])
|
||||
endforeach
|
||||
|
||||
# also install the sources.
|
||||
install_data(mu4e_srcs, install_dir: lispdir)
|
||||
|
||||
|
||||
if makeinfo.found()
|
||||
custom_target('mu4e_info',
|
||||
|
|
|
@ -30,11 +30,18 @@
|
|||
(require 'cl-lib)
|
||||
(require 'ido)
|
||||
|
||||
(require 'mu4e-helpers)
|
||||
(require 'mu4e-utils)
|
||||
(require 'mu4e-message)
|
||||
(require 'mu4e-search)
|
||||
(require 'mu4e-meta)
|
||||
|
||||
(declare-function mu4e~proc-extract "mu4e-proc")
|
||||
(declare-function mu4e-headers-search "mu4e-headers")
|
||||
|
||||
(defvar mu4e-headers-include-related)
|
||||
(defvar mu4e-headers-show-threads)
|
||||
(defvar mu4e-view-show-addresses)
|
||||
(defvar mu4e-view-date-format)
|
||||
|
||||
|
||||
;;; Count lines
|
||||
|
||||
|
@ -170,14 +177,6 @@ Otherwise return nil."
|
|||
(if (re-search-forward regexp nil t)
|
||||
(replace-match to-string nil nil)))))
|
||||
|
||||
(declare-function mu4e--server-add "mu4e-server")
|
||||
(defun mu4e--refresh-message (path)
|
||||
"Re-parse message at PATH.
|
||||
if this works, we will
|
||||
receive (:info add :path <path> :docid <docid>) as well as (:update
|
||||
<msg-sexp>)."
|
||||
(mu4e--server-add path))
|
||||
|
||||
(defun mu4e-action-retag-message (msg &optional retag-arg)
|
||||
"Change tags of MSG with RETAG-ARG.
|
||||
|
||||
|
@ -232,7 +231,7 @@ would add 'tag' and 'long tag', and remove 'oldtag'."
|
|||
path))
|
||||
|
||||
(mu4e-message (concat "tagging: " (mapconcat 'identity taglist ", ")))
|
||||
(mu4e--refresh-message path)))
|
||||
(mu4e-refresh-message path)))
|
||||
|
||||
(defun mu4e-action-show-thread (msg)
|
||||
"Show thread for message at point with point remaining on MSG.
|
||||
|
@ -241,13 +240,14 @@ action was invoked. If invoked in view mode, continue to display
|
|||
the message."
|
||||
(let ((msgid (mu4e-message-field msg :message-id)))
|
||||
(when msgid
|
||||
(let ((mu4e-search-threads t)
|
||||
(let ((mu4e-headers-show-threads t)
|
||||
(mu4e-headers-include-related t))
|
||||
(mu4e-search
|
||||
(mu4e-headers-search
|
||||
(format "msgid:%s" msgid)
|
||||
nil nil nil
|
||||
msgid (and (eq major-mode 'mu4e-view-mode)
|
||||
(not (eq mu4e-split-view 'single-window))))))))
|
||||
|
||||
;;; _
|
||||
(provide 'mu4e-actions)
|
||||
;;; mu4e-actions.el ends here
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
;;; mu4e-bookmarks.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-helpers)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
|
||||
(defgroup mu4e-bookmarks nil
|
||||
"Settings for bookmarks."
|
||||
:group 'mu4e)
|
||||
|
||||
;; for backward compatibility, when a bookmark was defined with defstruct.
|
||||
(cl-defun make-mu4e-bookmark (&key name query key)
|
||||
"Create a mu4e plist.
|
||||
It has has with the following elements:
|
||||
- NAME: the user-visible name of the bookmark
|
||||
- KEY: a single key to search for this bookmark
|
||||
- QUERY: the query for this bookmark. Either a literal string or a function
|
||||
that evaluates to a string."
|
||||
`(:name ,name :query ,query :key ,key))
|
||||
(make-obsolete 'make-mu4e-bookmark "`unneeded; `mu4e-bookmarks'
|
||||
are plists" "1.3.7")
|
||||
|
||||
(defcustom mu4e-bookmarks
|
||||
'(( :name "Unread messages"
|
||||
:query "flag:unread AND NOT flag:trashed"
|
||||
:key ?u)
|
||||
( :name "Today's messages"
|
||||
:query "date:today..now"
|
||||
:key ?t)
|
||||
( :name "Last 7 days"
|
||||
:query "date:7d..now"
|
||||
:hide-unread t
|
||||
:key ?w)
|
||||
( :name "Messages with images"
|
||||
:query "mime:image/*"
|
||||
:key ?p))
|
||||
"List of pre-defined queries that are shown on the main screen.
|
||||
|
||||
Each of the list elements is a plist with at least:
|
||||
`:name' - the name of the query
|
||||
`:query' - the query expression or function
|
||||
`:key' - the shortcut key.
|
||||
|
||||
Note that the :query parameter can be a function/lambda.
|
||||
|
||||
Optionally, you can add the following:
|
||||
`:hide' - if t, the bookmark is hidden from the main-view and
|
||||
speedbar.
|
||||
`:hide-unread' - do not show the counts of unread/total number
|
||||
of matches for the query in the main-view. This can be useful
|
||||
if a bookmark uses a very slow query. :hide-unread
|
||||
is implied from :hide. Furthermore, it is implied if
|
||||
`:query' is a function.
|
||||
|
||||
Queries used to determine the unread/all counts do _not_ apply
|
||||
`mu4e-query-rewrite-function'; nor do they discard duplicate or
|
||||
unreadable messages (for efficiency). Thus, the numbers shown may
|
||||
differ from the number you get from a 'real' query."
|
||||
:type '(repeat (plist))
|
||||
:version "1.3.9"
|
||||
:group 'mu4e-bookmarks)
|
||||
|
||||
|
||||
(defun mu4e-ask-bookmark (prompt)
|
||||
"Ask the user for a bookmark (using PROMPT) as defined in
|
||||
`mu4e-bookmarks', then return the corresponding query."
|
||||
(unless (mu4e-bookmarks) (mu4e-error "No bookmarks defined"))
|
||||
(let* ((prompt (mu4e-format "%s" prompt))
|
||||
(bmarks
|
||||
(mapconcat
|
||||
(lambda (bm)
|
||||
(concat
|
||||
"[" (propertize (make-string 1 (plist-get bm :key))
|
||||
'face 'mu4e-highlight-face)
|
||||
"]"
|
||||
(plist-get bm :name))) (mu4e-bookmarks) ", "))
|
||||
(kar (read-char (concat prompt bmarks))))
|
||||
(mu4e-get-bookmark-query kar)))
|
||||
|
||||
(defun mu4e-get-bookmark-query (kar)
|
||||
"Get the corresponding bookmarked query for shortcut KAR.
|
||||
Raise an error if none is found."
|
||||
(let* ((chosen-bm
|
||||
(or (cl-find-if
|
||||
(lambda (bm)
|
||||
(= kar (plist-get bm :key)))
|
||||
(mu4e-bookmarks))
|
||||
(mu4e-warn "Unknown shortcut '%c'" kar)))
|
||||
(expr (plist-get chosen-bm :query))
|
||||
(expr (if (not (functionp expr)) expr
|
||||
(funcall expr)))
|
||||
(query (eval expr)))
|
||||
(if (stringp query)
|
||||
query
|
||||
(mu4e-warn "Expression must evaluate to query string ('%S')" expr))))
|
||||
|
||||
|
||||
(defun mu4e-bookmark-define (query name key)
|
||||
"Define a bookmark for QUERY with NAME and shortcut KEY.
|
||||
Append it to `mu4e-bookmarks'. Replaces any existing bookmark
|
||||
with KEY."
|
||||
(setq mu4e-bookmarks
|
||||
(cl-remove-if
|
||||
(lambda (bm)
|
||||
(= (plist-get bm :key) key))
|
||||
(mu4e-bookmarks)))
|
||||
(cl-pushnew `(:name ,name
|
||||
:query ,query
|
||||
:key ,key)
|
||||
mu4e-bookmarks :test 'equal))
|
||||
|
||||
(defun mu4e-bookmarks ()
|
||||
"Get `mu4e-bookmarks' in the (new) format.
|
||||
Convert from the old format if needed."
|
||||
(cl-map 'list
|
||||
(lambda (item)
|
||||
(if (and (listp item) (= (length item) 3))
|
||||
`(:name ,(nth 1 item)
|
||||
:query ,(nth 0 item)
|
||||
:key ,(nth 2 item))
|
||||
item))
|
||||
mu4e-bookmarks))
|
||||
|
||||
(provide 'mu4e-bookmarks)
|
||||
;;; mu4e-bookmarks.el ends here
|
|
@ -72,17 +72,192 @@
|
|||
(require 'smtpmail)
|
||||
(require 'rfc2368)
|
||||
|
||||
(require 'mu4e-server)
|
||||
(require 'mu4e-utils)
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-proc)
|
||||
(require 'mu4e-actions)
|
||||
(require 'mu4e-message)
|
||||
(require 'mu4e-draft)
|
||||
(require 'mu4e-context)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
;; see mu4e-drafts.el
|
||||
|
||||
;;; Composing / Sending messages
|
||||
|
||||
(defgroup mu4e-compose nil
|
||||
"Customizations for composing/sending messages."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-sent-messages-behavior 'sent
|
||||
"Determines what mu4e does with sent messages.
|
||||
|
||||
This is one of the symbols:
|
||||
* `sent' move the sent message to the Sent-folder (`mu4e-sent-folder')
|
||||
* `trash' move the sent message to the Trash-folder (`mu4e-trash-folder')
|
||||
* `delete' delete the sent message.
|
||||
|
||||
Note, when using GMail/IMAP, you should set this to either
|
||||
`trash' or `delete', since GMail already takes care of keeping
|
||||
copies in the sent folder.
|
||||
|
||||
Alternatively, `mu4e-sent-messages-behavior' can be a function
|
||||
which takes no arguments, and which should return one of the mentioned
|
||||
symbols, for example:
|
||||
|
||||
(setq mu4e-sent-messages-behavior (lambda ()
|
||||
(if (string= (message-sendmail-envelope-from) \"foo@example.com\")
|
||||
'delete 'sent)))
|
||||
|
||||
The various `message-' functions from `message-mode' are available
|
||||
for querying the message information."
|
||||
:type '(choice (const :tag "move message to mu4e-sent-folder" sent)
|
||||
(const :tag "move message to mu4e-trash-folder" trash)
|
||||
(const :tag "delete message" delete))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-context-policy 'ask
|
||||
"Policy for determining the context when composing a new message.
|
||||
|
||||
If the value is `always-ask', ask the user unconditionally.
|
||||
|
||||
In all other cases, if any context matches (using its match
|
||||
function), this context is used. Otherwise, if none of the
|
||||
contexts match, we have the following choices:
|
||||
|
||||
- `pick-first': pick the first of the contexts available (ie. the default)
|
||||
- `ask': ask the user
|
||||
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
|
||||
- nil: return nil; leaves the current context as is.
|
||||
|
||||
Also see `mu4e-context-policy'."
|
||||
:type '(choice
|
||||
(const :tag "Always ask what context to use" always-ask)
|
||||
(const :tag "Ask if none of the contexts match" ask)
|
||||
(const :tag "Ask when there's no context yet" ask-if-none)
|
||||
(const :tag "Pick the first context if none match" pick-first)
|
||||
(const :tag "Don't change the context when none match" nil))
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-crypto-policy
|
||||
'(encrypt-encrypted-replies sign-encrypted-replies)
|
||||
"Policy to control when messages will be signed/encrypted.
|
||||
|
||||
The value is a list, whose members determine the behaviour of
|
||||
`mu4e~compose-crypto-message'. Specifically, it might contain:
|
||||
|
||||
- `sign-all-messages': Always add a signature.
|
||||
- `sign-new-messages': Add a signature to new message, ie.
|
||||
messages that aren't responses to another message.
|
||||
- `sign-forwarded-messages': Add a signature when forwarding
|
||||
a message
|
||||
- `sign-edited-messages': Add a signature to drafts
|
||||
- `sign-all-replies': Add a signature when responding to
|
||||
another message.
|
||||
- `sign-plain-replies': Add a signature when responding to
|
||||
non-encrypted messages.
|
||||
- `sign-encrypted-replies': Add a signature when responding
|
||||
to encrypted messages.
|
||||
|
||||
It should be noted that certain symbols have priorities over one
|
||||
another. So `sign-all-messages' implies `sign-all-replies', which
|
||||
in turn implies `sign-plain-replies'. Adding both to the set, is
|
||||
not a contradiction, but a redundant configuration.
|
||||
|
||||
All `sign-*' options have a `encrypt-*' analogue."
|
||||
:type '(set :greedy t
|
||||
(const :tag "Sign all messages" sign-all-messages)
|
||||
(const :tag "Encrypt all messages" encrypt-all-messages)
|
||||
(const :tag "Sign new messages" sign-new-messages)
|
||||
(const :tag "Encrypt new messages" encrypt-new-messages)
|
||||
(const :tag "Sign forwarded messages" sign-forwarded-messages)
|
||||
(const :tag "Encrypt forwarded messages" encrypt-forwarded-messages)
|
||||
(const :tag "Sign edited messages" sign-edited-messages)
|
||||
(const :tag "Encrypt edited messages" edited-forwarded-messages)
|
||||
(const :tag "Sign all replies" sign-all-replies)
|
||||
(const :tag "Encrypt all replies" encrypt-all-replies)
|
||||
(const :tag "Sign replies to plain messages" sign-plain-replies)
|
||||
(const :tag "Encrypt replies to plain messages" encrypt-plain-replies)
|
||||
(const :tag "Sign replies to encrypted messages" sign-encrypted-replies)
|
||||
(const :tag "Encrypt replies to encrypted messages" encrypt-encrypted-replies))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-crypto-reply-encrypted-policy nil
|
||||
"Policy for signing/encrypting replies to encrypted messages.
|
||||
We have the following choices:
|
||||
|
||||
- `sign': sign the reply
|
||||
- `sign-and-encrypt': sign and encrypt the reply
|
||||
- `encrypt': encrypt the reply, but don't sign it.
|
||||
- anything else: do nothing."
|
||||
:type '(choice
|
||||
(const :tag "Sign the reply" sign)
|
||||
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
|
||||
(const :tag "Encrypt the reply" encrypt)
|
||||
(const :tag "Don't do anything" nil))
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(make-obsolete-variable 'mu4e-compose-crypto-reply-encrypted-policy "The use of the
|
||||
'mu4e-compose-crypto-reply-encrypted-policy' variable is deprecated.
|
||||
'mu4e-compose-crypto-policy' should be used instead"
|
||||
"2020-03-06")
|
||||
|
||||
(defcustom mu4e-compose-crypto-reply-plain-policy nil
|
||||
"Policy for signing/encrypting replies to messages received unencrypted.
|
||||
We have the following choices:
|
||||
|
||||
- `sign': sign the reply
|
||||
- `sign-and-encrypt': sign and encrypt the reply
|
||||
- `encrypt': encrypt the reply, but don't sign it.
|
||||
- anything else: do nothing."
|
||||
:type '(choice
|
||||
(const :tag "Sign the reply" sign)
|
||||
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
|
||||
(const :tag "Encrypt the reply" encrypt)
|
||||
(const :tag "Don't do anything" nil))
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(make-obsolete-variable 'mu4e-compose-crypto-reply-plain-policy "The use of the
|
||||
'mu4e-compose-crypto-reply-plain-policy' variable is deprecated.
|
||||
'mu4e-compose-crypto-policy' should be used instead"
|
||||
"2020-03-06")
|
||||
|
||||
(make-obsolete-variable 'mu4e-compose-crypto-reply-policy "The use of the
|
||||
'mu4e-compose-crypto-reply-policy' variable is deprecated.
|
||||
'mu4e-compose-crypto-reply-plain-policy' and
|
||||
'mu4e-compose-crypto-reply-encrypted-policy' should be used instead"
|
||||
"2017-09-02")
|
||||
|
||||
(defcustom mu4e-compose-format-flowed nil
|
||||
"Whether to compose messages to be sent as format=flowed.
|
||||
\(Or with long lines if variable `use-hard-newlines' is set to
|
||||
nil). The variable `fill-flowed-encode-column' lets you customize
|
||||
the width beyond which format=flowed lines are wrapped."
|
||||
:type 'boolean
|
||||
:safe 'booleanp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-pre-hook nil
|
||||
"Hook run just *before* message composition starts.
|
||||
If the compose-type is either 'reply' or 'forward', the variable
|
||||
`mu4e-compose-parent-message' points to the message replied to /
|
||||
being forwarded / edited, and `mu4e-compose-type' contains the
|
||||
type of message to be composed.
|
||||
|
||||
Note that there is no draft message yet when this hook runs, it
|
||||
is meant for influencing the how mu4e constructs the draft
|
||||
message. If you want to do something with the draft messages after
|
||||
it has been constructed, `mu4e-compose-mode-hook' would be the
|
||||
place to do that."
|
||||
:type 'hook
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defvar mu4e-compose-type nil
|
||||
"The compose-type for this buffer.
|
||||
This is a symbol, `new', `forward', `reply' or `edit'.")
|
||||
|
||||
;;; Attachments
|
||||
|
||||
(defun mu4e-compose-attach-message (msg)
|
||||
"Insert message MSG as an attachment."
|
||||
(let ((path (plist-get msg :path)))
|
||||
|
@ -153,12 +328,12 @@ If needed, set the Fcc header, and register the handler function."
|
|||
(setq message-fcc-handler-function old-handler) ;; reset the fcc handler
|
||||
(let ((mdir-path (concat (mu4e-root-maildir) maildir)))
|
||||
;; Create the full maildir structure for the sent folder if it doesn't exist.
|
||||
;; `mu4e--server-mkdir` runs asynchronously but no matter whether it runs before or after
|
||||
;; `mu4e~proc-mkdir` runs asynchronously but no matter whether it runs before or after
|
||||
;; `write-file`, the sent maildir ends up in the correct state.
|
||||
(unless (file-exists-p mdir-path)
|
||||
(mu4e--server-mkdir mdir-path)))
|
||||
(mu4e~proc-mkdir mdir-path)))
|
||||
(write-file file) ;; writing maildirs files is easy
|
||||
(mu4e--server-add file))))))) ;; update the database
|
||||
(mu4e~proc-add file))))))) ;; update the database
|
||||
|
||||
(defvar mu4e-compose-hidden-headers
|
||||
`("^References:" "^Face:" "^X-Face:"
|
||||
|
@ -206,7 +381,7 @@ Message-ID."
|
|||
(set-buffer-modified-p nil)
|
||||
(mu4e-message "Saved (%d lines)" (count-lines (point-min) (point-max)))
|
||||
;; update the file on disk -- ie., without the separator
|
||||
(mu4e--server-add (buffer-file-name))))
|
||||
(mu4e~proc-add (buffer-file-name))))
|
||||
|
||||
|
||||
;;; address completion
|
||||
|
@ -218,9 +393,9 @@ Message-ID."
|
|||
"Complete address STR with predication PRED for ACTION."
|
||||
(cond
|
||||
((eq action nil)
|
||||
(try-completion str mu4e--contacts-hash pred))
|
||||
(try-completion str mu4e~contacts-hash pred))
|
||||
((eq action t)
|
||||
(all-completions str mu4e--contacts-hash pred))
|
||||
(all-completions str mu4e~contacts-hash pred))
|
||||
((eq action 'metadata)
|
||||
;; our contacts are already sorted - just need to tell the
|
||||
;; completion machinery not to try to undo that...
|
||||
|
@ -272,6 +447,7 @@ removing the In-Reply-To header."
|
|||
(setq mu4e-compose-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
|
||||
(define-key map (kbd "C-c C-;") 'mu4e-compose-context-switch)
|
||||
(define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
|
||||
(define-key map (kbd "C-c C-k") 'mu4e-message-kill-buffer)
|
||||
(define-key map (kbd "M-q") 'mu4e-fill-paragraph)
|
||||
|
@ -332,12 +508,7 @@ buffers; lets remap its faces so it uses the ones for mu4e."
|
|||
\\{message-mode-map}."
|
||||
(progn
|
||||
(use-local-map mu4e-compose-mode-map)
|
||||
|
||||
(mu4e-context-minor-mode)
|
||||
(define-key mu4e-context-minor-mode-map (kbd ";") nil)
|
||||
(define-key mu4e-context-minor-mode-map (kbd "C-c C-;")
|
||||
#'mu4e-compose-context-switch)
|
||||
|
||||
(mu4e-context-in-modeline)
|
||||
(set (make-local-variable 'message-signature) mu4e-compose-signature)
|
||||
;; set this to allow mu4e to work when gnus-agent is unplugged in gnus
|
||||
(set (make-local-variable 'message-send-mail-real-function) nil)
|
||||
|
@ -354,9 +525,8 @@ buffers; lets remap its faces so it uses the ones for mu4e."
|
|||
(mu4e~compose-register-message-save-hooks)
|
||||
;; offer completion for e-mail addresses
|
||||
(when mu4e-compose-complete-addresses
|
||||
(unless mu4e--contacts-hash
|
||||
;; work-around for https://github.com/djcb/mu/issues/1016
|
||||
(mu4e--request-contacts-maybe))
|
||||
(unless mu4e~contacts-hash ;; work-around for https://github.com/djcb/mu/issues/1016
|
||||
(mu4e~request-contacts-maybe))
|
||||
(mu4e~compose-setup-completion))
|
||||
(if mu4e-compose-format-flowed
|
||||
(progn
|
||||
|
@ -432,7 +602,7 @@ buffers; lets remap its faces so it uses the ones for mu4e."
|
|||
(defun mu4e~set-sent-handler-message-sent-hook-fn ()
|
||||
;; mu4e~compose-mark-after-sending
|
||||
(setq mu4e-sent-func 'mu4e-sent-handler)
|
||||
(mu4e--server-sent (buffer-file-name)))
|
||||
(mu4e~proc-sent (buffer-file-name)))
|
||||
|
||||
(defun mu4e-send-harden-newlines ()
|
||||
"Set the hard property to all newlines."
|
||||
|
@ -540,8 +710,8 @@ are optional."
|
|||
(set (make-local-variable 'mu4e-compose-type) compose-type)
|
||||
(put 'mu4e-compose-type 'permanent-local t)
|
||||
;; maybe switch the context
|
||||
(mu4e--context-autoswitch mu4e-compose-parent-message
|
||||
mu4e-compose-context-policy)
|
||||
(mu4e~context-autoswitch mu4e-compose-parent-message
|
||||
mu4e-compose-context-policy)
|
||||
(run-hooks 'mu4e-compose-pre-hook)
|
||||
|
||||
;; this opens (or re-opens) a messages with all the basic headers set.
|
||||
|
@ -660,7 +830,7 @@ when the buffer is in `mu4e-compose-mode':
|
|||
;; Remove the <>
|
||||
(when (and msg-id (string-match "<\\(.*\\)>" msg-id))
|
||||
(save-window-excursion
|
||||
(mu4e--server-move (match-string 1 msg-id) mu4e-drafts-folder nil t)
|
||||
(mu4e~proc-move (match-string 1 msg-id) mu4e-drafts-folder nil t)
|
||||
(kill-buffer buf))))) ;; Kill previous buffer which points to wrong file
|
||||
;; No file, just change the buffer file name
|
||||
(setq buffer-file-name
|
||||
|
@ -676,7 +846,7 @@ For Forwarded ('Passed') and Replied messages, try to set the
|
|||
appropriate flag at the message forwarded or replied-to."
|
||||
(mu4e~compose-set-parent-flag path)
|
||||
(when (file-exists-p path) ;; maybe the draft was not saved at all
|
||||
(mu4e--server-remove docid))
|
||||
(mu4e~proc-remove docid))
|
||||
;; kill any remaining buffers for the draft file, or they will hang around...
|
||||
;; this seems a bit hamfisted...
|
||||
(when message-kill-buffer-on-exit
|
||||
|
@ -738,9 +908,9 @@ buffer."
|
|||
(setq forwarded-from (cl-first refs))))))
|
||||
;; remove the <>
|
||||
(when (and in-reply-to (string-match "<\\(.*\\)>" in-reply-to))
|
||||
(mu4e--server-move (match-string 1 in-reply-to) nil "+R-N"))
|
||||
(mu4e~proc-move (match-string 1 in-reply-to) nil "+R-N"))
|
||||
(when (and forwarded-from (string-match "<\\(.*\\)>" forwarded-from))
|
||||
(mu4e--server-move (match-string 1 forwarded-from) nil "+P-N")))))))
|
||||
(mu4e~proc-move (match-string 1 forwarded-from) nil "+P-N")))))))
|
||||
|
||||
(defun mu4e-compose (compose-type)
|
||||
"Start composing a message of COMPOSE-TYPE.
|
||||
|
@ -778,7 +948,7 @@ Symbol `edit' is only allowed for draft messages."
|
|||
(when (window-live-p viewwin)
|
||||
(select-window viewwin))))
|
||||
;; talk to the backend
|
||||
(mu4e--server-compose compose-type decrypt docid)))))
|
||||
(mu4e~proc-compose compose-type decrypt docid)))))
|
||||
|
||||
(defun mu4e-compose-reply ()
|
||||
"Compose a reply for the message at point in the headers buffer."
|
||||
|
@ -812,8 +982,6 @@ draft message."
|
|||
;; mu4e-compose-func and mu4e-send-func are wrappers so we can set ourselves
|
||||
;; as default emacs mailer (define-mail-user-agent etc.)
|
||||
|
||||
(declare-function mu4e "mu4e")
|
||||
|
||||
;;;###autoload
|
||||
(defun mu4e~compose-mail (&optional to subject other-headers _continue
|
||||
switch-function yank-action _send-actions _return-action)
|
||||
|
@ -850,7 +1018,7 @@ caller. It has the form (FUNCTION . ARGS). The function is
|
|||
called after the mail has been sent or put aside, and the mail
|
||||
buffer buried."
|
||||
(unless (mu4e-running-p)
|
||||
(mu4e))
|
||||
(mu4e~start))
|
||||
|
||||
;; create a new draft message 'resetting' (as below) is not actually needed in this case, but
|
||||
;; let's prepare for the re-edit case as well
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
;;; mu4e-contacts.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Utility functions used in the mu4e
|
||||
|
||||
;;; Code:
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-helpers)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
(defcustom mu4e-compose-complete-addresses t
|
||||
"Whether to do auto-completion of e-mail addresses."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-complete-only-personal nil
|
||||
"Whether to consider only 'personal' e-mail addresses for completion.
|
||||
That is, addresses from messages where user was explicitly in one
|
||||
of the address fields (this excludes mailing list messages).
|
||||
These addresses are the ones specified with `mu init'."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-complete-only-after "2014-01-01"
|
||||
"Consider only contacts last seen after this date.
|
||||
|
||||
Date must be a string of the form YYY-MM-DD.
|
||||
|
||||
This is useful for limiting a potentially enormous set of
|
||||
contacts for auto-completion to just those that are present in
|
||||
the e-mail corpus in recent timses. Set to nil to not have any
|
||||
time-based restriction."
|
||||
:type 'string
|
||||
:group 'mu4e-compose)
|
||||
|
||||
;; names and mail-addresses can be mapped onto their canonical
|
||||
;; counterpart. use the customizeable function
|
||||
;; mu4e-canonical-contact-function to do that. below the identity
|
||||
;; function for mapping a contact onto the canonical one.
|
||||
(defun mu4e-contact-identity (contact)
|
||||
"Return the name and the mail-address of a CONTACT.
|
||||
It is used as the identity function for converting contacts to
|
||||
their canonical counterpart; useful as an example."
|
||||
(let ((name (plist-get contact :name))
|
||||
(mail (plist-get contact :mail)))
|
||||
(list :name name :mail mail)))
|
||||
|
||||
(make-obsolete-variable 'mu4e-contact-rewrite-function
|
||||
"mu4e-contact-process-function (see docstring)"
|
||||
"mu4e 1.3.2")
|
||||
(make-obsolete-variable 'mu4e-compose-complete-ignore-address-regexp
|
||||
"mu4e-contact-process-function (see docstring)"
|
||||
"mu4e 1.3.2")
|
||||
|
||||
(defcustom mu4e-contact-process-function
|
||||
(lambda(addr) ;; filter-out no-reply addresses
|
||||
(unless (string-match-p "no[t]?[-\\.]?repl\\(y\\|ies\\)" addr)
|
||||
addr))
|
||||
"Function for processing contact information for use in auto-completion.
|
||||
|
||||
The function receives the contact as a string, e.g
|
||||
\"Foo Bar <foo.bar@example.com>\"
|
||||
\"cuux@example.com\"
|
||||
|
||||
The function should return either:
|
||||
- nil: do not use this contact for completion
|
||||
- the (possibly rewritten) address, which must be
|
||||
an RFC-2822-compatible e-mail address."
|
||||
:type 'function
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-reply-ignore-address
|
||||
'("no-?reply")
|
||||
"Addresses to prune when doing wide replies.
|
||||
|
||||
This can be a regexp matching the address, a list of regexps or a
|
||||
predicate function. A value of nil keeps all the addresses."
|
||||
:type '(choice
|
||||
(const nil)
|
||||
function
|
||||
string
|
||||
(repeat string))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
|
||||
;;; Internal variables
|
||||
(defvar mu4e--contacts-tstamp "0"
|
||||
"Timestamp for the most recent contacts update." )
|
||||
|
||||
(defvar mu4e--contacts-hash nil
|
||||
"Hash that maps contacts (ie. 'name <e-mail>') to an integer for sorting.
|
||||
We need to keep this information around to quickly re-sort
|
||||
subsets of the contacts in the completions function in
|
||||
mu4e-compose.")
|
||||
|
||||
;;; user mail address
|
||||
(defun mu4e-personal-addresses(&optional no-regexp)
|
||||
"Get the list user's personal addresses, as passed to mu init.
|
||||
The address are either plain e-mail address or /regular
|
||||
expressions/. When NO-REGEXP is non-nil, do not include regexp
|
||||
address patterns (if any)."
|
||||
(seq-remove
|
||||
(lambda(addr) (and no-regexp (string-match-p "^/.*/" addr)))
|
||||
(when (mu4e-server-properties)
|
||||
(plist-get (mu4e-server-properties) :personal-addresses))))
|
||||
|
||||
(defun mu4e-personal-address-p (addr)
|
||||
"Is ADDR a personal address?
|
||||
Evaluate to nil if ADDR matches any of the personal addresses.
|
||||
Uses (mu4e-personal-addresses) for the addresses with both the plain
|
||||
addresses and /regular expressions/."
|
||||
(when addr
|
||||
(seq-find
|
||||
(lambda (m)
|
||||
(if (string-match "/\\(.*\\)/" m)
|
||||
(let ((rx (match-string 1 m))
|
||||
(case-fold-search t))
|
||||
(if (string-match rx addr) t nil))
|
||||
(eq t (compare-strings addr nil nil m nil nil 'case-insensitive))))
|
||||
(mu4e-personal-addresses))))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-user-mail-address-p
|
||||
'mu4e-personal-address-p "1.5.5")
|
||||
|
||||
|
||||
;; don't use the older vars anymore
|
||||
(make-obsolete-variable 'mu4e-user-mail-address-regexp
|
||||
'mu4e-user-mail-address-list "0.9.9.x")
|
||||
(make-obsolete-variable 'mu4e-my-email-addresses
|
||||
'mu4e-user-mail-address-list "0.9.9.x")
|
||||
(make-obsolete-variable 'mu4e-user-mail-address-list
|
||||
"determined by server; see `mu4e-personal-addresses'."
|
||||
"1.3.8")
|
||||
|
||||
|
||||
(defun mu4e--update-contacts (contacts &optional tstamp)
|
||||
"Receive a sorted list of CONTACTS newer than TSTAMP.
|
||||
Each of the contacts has the form
|
||||
(FULL_EMAIL_ADDRESS . RANK) and fill `mu4e--contacts-hash' with
|
||||
it, with each contact mapped to an integer for their ranking.
|
||||
|
||||
This is used by the completion function in mu4e-compose."
|
||||
;; We have our nicely sorted list, map them to a list
|
||||
;; of increasing integers. We use that map in the composer
|
||||
;; to sort them there. It would have been so much easier if emacs
|
||||
;; allowed us to use the sorted-list as-is, but no such luck.
|
||||
(let ((n 0))
|
||||
(unless mu4e--contacts-hash
|
||||
(setq mu4e--contacts-hash (make-hash-table :test 'equal :weakness nil
|
||||
:size (length contacts))))
|
||||
(dolist (contact contacts)
|
||||
(cl-incf n)
|
||||
(let* ((address (plist-get contact :address))
|
||||
(address
|
||||
(if (functionp mu4e-contact-process-function)
|
||||
(funcall mu4e-contact-process-function address)
|
||||
address)))
|
||||
(when address ;; note the explicit deccode; the strings we get are
|
||||
;; utf-8, but emacs doesn't know yet.
|
||||
(puthash (decode-coding-string address 'utf-8)
|
||||
(plist-get contact :rank) mu4e--contacts-hash))))
|
||||
|
||||
(setq mu4e--contacts-tstamp (or tstamp "0"))
|
||||
|
||||
(unless (zerop n)
|
||||
(mu4e-index-message "Contacts updated: %d; total %d"
|
||||
n (hash-table-count mu4e--contacts-hash)))))
|
||||
|
||||
(defun mu4e-contacts-info ()
|
||||
"Display information about the contacts-cache.
|
||||
For testing/debugging."
|
||||
(interactive)
|
||||
(with-current-buffer (get-buffer-create "*mu4e-contacts-info*")
|
||||
(erase-buffer)
|
||||
(insert (format "complete addresses: %s\n"
|
||||
(if mu4e-compose-complete-addresses "yes" "no")))
|
||||
(insert (format "only personal addresses: %s\n"
|
||||
(if mu4e-compose-complete-only-personal "yes" "no")))
|
||||
(insert (format "only addresses seen after: %s\n"
|
||||
(or mu4e-compose-complete-only-after "no restrictions")))
|
||||
|
||||
(when mu4e--contacts-hash
|
||||
(insert (format "number of contacts cached: %d\n\n"
|
||||
(hash-table-count mu4e--contacts-hash)))
|
||||
(let ((contacts))
|
||||
(maphash (lambda (addr rank)
|
||||
(setq contacts (cons (cons rank addr) contacts)))
|
||||
mu4e--contacts-hash)
|
||||
(setq contacts (sort contacts
|
||||
(lambda(cell1 cell2) (< (car cell1) (car cell2)))))
|
||||
(dolist (contact contacts)
|
||||
(insert (format "%s\n" (cdr contact))))))
|
||||
|
||||
(pop-to-buffer "*mu4e-contacts-info*")))
|
||||
|
||||
(declare-function mu4e--server-contacts "mu4e-server")
|
||||
|
||||
(defun mu4e--request-contacts-maybe ()
|
||||
"If `mu4e-compose-complete-addresses' is non-nil, get/update
|
||||
the list of contacts we use for autocompletion; otherwise, do
|
||||
nothing."
|
||||
(when mu4e-compose-complete-addresses
|
||||
(mu4e--server-contacts
|
||||
mu4e-compose-complete-only-personal
|
||||
mu4e-compose-complete-only-after
|
||||
mu4e--contacts-tstamp)))
|
||||
|
||||
(provide 'mu4e-contacts)
|
||||
;;; mu4e-contacts.el ends here
|
|
@ -27,66 +27,38 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'mu4e-helpers)
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-utils)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
(defcustom mu4e-context-policy 'ask-if-none
|
||||
"The policy to determine the context when entering the mu4e main view.
|
||||
(defvar smtpmail-smtp-user)
|
||||
(defvar mu4e-view-date-format)
|
||||
|
||||
If the value is `always-ask', ask the user unconditionally.
|
||||
|
||||
In all other cases, if any context matches (using its match
|
||||
function), this context is used. Otherwise, if none of the
|
||||
contexts match, we have the following choices:
|
||||
|
||||
- `pick-first': pick the first of the contexts available (ie. the default)
|
||||
- `ask': ask the user
|
||||
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
|
||||
- nil: return nil; leaves the current context as is.
|
||||
|
||||
Also see `mu4e-compose-context-policy'."
|
||||
:type '(choice
|
||||
(const :tag "Always ask what context to use, even if one matches"
|
||||
always-ask)
|
||||
(const :tag "Ask if none of the contexts match" ask)
|
||||
(const :tag "Ask when there's no context yet" ask-if-none)
|
||||
(const :tag "Pick the first context if none match" pick-first)
|
||||
(const :tag "Don't change the context when none match" nil))
|
||||
:group 'mu4e)
|
||||
|
||||
|
||||
(defvar mu4e-contexts nil
|
||||
"The list of `mu4e-context' objects describing mu4e's contexts.")
|
||||
(defvar mu4e-contexts nil "The list of `mu4e-context' objects
|
||||
describing mu4e's contexts.")
|
||||
|
||||
(defvar mu4e-context-changed-hook nil
|
||||
"Hook run just *after* the context changed.")
|
||||
|
||||
(defface mu4e-context-face
|
||||
'((t :inherit mu4e-title-face :weight bold))
|
||||
"Face for displaying the context in the modeline."
|
||||
:group 'mu4e-faces)
|
||||
(defvar mu4e~context-current nil
|
||||
"The current context; for internal use. Use
|
||||
`mu4e-context-switch' to change it.")
|
||||
|
||||
(defvar mu4e--context-current nil
|
||||
"The current context.
|
||||
Internal; use `mu4e-context-switch' to change it.")
|
||||
|
||||
(defun mu4e-context-current (&optional output)
|
||||
"Get the currently active context, or nil if there is none.
|
||||
When OUTPUT is non-nil, echo the name of the current context or
|
||||
none."
|
||||
(interactive "p")
|
||||
(let ((ctx mu4e--context-current))
|
||||
(let ((ctx mu4e~context-current))
|
||||
(when output
|
||||
(mu4e-message "Current context: %s"
|
||||
(if ctx (mu4e-context-name ctx) "<none>")))
|
||||
ctx))
|
||||
|
||||
(defun mu4e-context-label ()
|
||||
"Propertized string with the current context name.
|
||||
An empty string \"\" if there is none."
|
||||
"Propertized string with the current context name, or \"\" if
|
||||
there is none."
|
||||
(if (mu4e-context-current)
|
||||
(concat "[" (propertize (mu4e-quote-for-modeline
|
||||
(concat "[" (propertize (mu4e~quote-for-modeline
|
||||
(mu4e-context-name (mu4e-context-current)))
|
||||
'face 'mu4e-context-face) "]") ""))
|
||||
|
||||
|
@ -111,8 +83,8 @@ An empty string \"\" if there is none."
|
|||
vars) ;; alist of variables.
|
||||
|
||||
|
||||
(defun mu4e--context-ask-user (prompt)
|
||||
"Let user choose some context based on its name with PROMPT."
|
||||
(defun mu4e~context-ask-user (prompt)
|
||||
"Let user choose some context based on its name."
|
||||
(when mu4e-contexts
|
||||
(let* ((names (cl-map 'list (lambda (context)
|
||||
(cons (mu4e-context-name context) context))
|
||||
|
@ -121,8 +93,8 @@ An empty string \"\" if there is none."
|
|||
(or context (mu4e-error "No such context")))))
|
||||
|
||||
(defun mu4e-context-switch (&optional force name)
|
||||
"Switch to a context with NAME.
|
||||
Context must be part of `mu4e-contexts'; if NAME is nil, query user.
|
||||
"Switch context to a context with NAME which is part of
|
||||
`mu4e-contexts'; if NAME is nil, query user.
|
||||
|
||||
If the new context is the same and the current context, only
|
||||
switch (run associated functions) when prefix argument FORCE is
|
||||
|
@ -136,13 +108,13 @@ non-nil."
|
|||
(context
|
||||
(if name
|
||||
(cdr-safe (assoc name names))
|
||||
(mu4e--context-ask-user "Switch to context: "))))
|
||||
(mu4e~context-ask-user "Switch to context: "))))
|
||||
(unless context (mu4e-error "No such context"))
|
||||
;; if new context is same as old one one switch with FORCE is set.
|
||||
(when (or force (not (eq context (mu4e-context-current))))
|
||||
(when (and (mu4e-context-current)
|
||||
(mu4e-context-leave-func mu4e--context-current))
|
||||
(funcall (mu4e-context-leave-func mu4e--context-current)))
|
||||
(mu4e-context-leave-func mu4e~context-current))
|
||||
(funcall (mu4e-context-leave-func mu4e~context-current)))
|
||||
;; enter the new context
|
||||
(when (mu4e-context-enter-func context)
|
||||
(funcall (mu4e-context-enter-func context)))
|
||||
|
@ -150,30 +122,26 @@ non-nil."
|
|||
(mapc (lambda (cell)
|
||||
(set (car cell) (cdr cell)))
|
||||
(mu4e-context-vars context)))
|
||||
(setq mu4e--context-current context)
|
||||
(setq mu4e~context-current context)
|
||||
|
||||
(run-hooks 'mu4e-context-changed-hook)
|
||||
(mu4e-message "Switched context to %s" (mu4e-context-name context))
|
||||
(force-mode-line-update))
|
||||
context))
|
||||
|
||||
(defun mu4e--context-autoswitch (&optional msg policy)
|
||||
"Automatically switch to some context.
|
||||
|
||||
When contexts are defined but there is no context yet, switch to
|
||||
the first whose :match-func return non-nil. If none of them
|
||||
match, return the first. For MSG and POLICY, see
|
||||
`mu4e-context-determine'."
|
||||
(defun mu4e~context-autoswitch (&optional msg policy)
|
||||
"When contexts are defined but there is no context yet, switch
|
||||
to the first whose :match-func return non-nil. If none of them
|
||||
match, return the first. For MSG and POLICY, see `mu4e-context-determine'."
|
||||
(when mu4e-contexts
|
||||
(let ((context (mu4e-context-determine msg policy)))
|
||||
(when context (mu4e-context-switch
|
||||
nil (mu4e-context-name context))))))
|
||||
|
||||
(defun mu4e-context-determine (msg &optional policy)
|
||||
"Return the first context where match-func evaluate to non-nil.
|
||||
|
||||
MSG points to the plist for the message replied to or forwarded,
|
||||
or nil if there is no such MSG; similar to what
|
||||
"Return the first context with a match-func that returns t. MSG
|
||||
points to the plist for the message replied to or forwarded, or
|
||||
nil if there is no such MSG; similar to what
|
||||
`mu4e-compose-pre-hook' does.
|
||||
|
||||
POLICY specifies how to do the determination. If POLICY is
|
||||
|
@ -189,7 +157,7 @@ match, POLICY determines what to do:
|
|||
- otherwise, return nil. Effectively, this leaves the current context as it is."
|
||||
(when mu4e-contexts
|
||||
(if (eq policy 'always-ask)
|
||||
(mu4e--context-ask-user "Select context: ")
|
||||
(mu4e~context-ask-user "Select context: ")
|
||||
(or ;; is there a matching one?
|
||||
(cl-find-if (lambda (context)
|
||||
(when (mu4e-context-match-func context)
|
||||
|
@ -198,9 +166,9 @@ match, POLICY determines what to do:
|
|||
;; no context found yet; consult policy
|
||||
(cl-case policy
|
||||
(pick-first (car mu4e-contexts))
|
||||
(ask (mu4e--context-ask-user "Select context: "))
|
||||
(ask (mu4e~context-ask-user "Select context: "))
|
||||
(ask-if-none (or (mu4e-context-current)
|
||||
(mu4e--context-ask-user "Select context: ")))
|
||||
(mu4e~context-ask-user "Select context: ")))
|
||||
(otherwise nil))))))
|
||||
|
||||
(defun mu4e-context-in-modeline ()
|
||||
|
@ -210,28 +178,6 @@ global-mode-line."
|
|||
(make-local-variable 'global-mode-string)
|
||||
'(:eval (mu4e-context-label))))
|
||||
|
||||
(defmacro with-mu4e-context-vars (context &rest body)
|
||||
"Evaluate BODY, with variables let-bound for CONTEXT (if any).
|
||||
`funcall'."
|
||||
(declare (indent 2))
|
||||
`(let* ((vars (and ,context (mu4e-context-vars ,context))))
|
||||
(cl-progv ;; XXX: perhaps use eval's lexical environment instead of progv?
|
||||
(mapcar (lambda(cell) (car cell)) vars)
|
||||
(mapcar (lambda(cell) (cdr cell)) vars)
|
||||
(eval ,@body))))
|
||||
|
||||
(define-minor-mode mu4e-context-minor-mode
|
||||
"Mode for switching the mu4e context."
|
||||
:global nil
|
||||
:init-value nil ;; disabled by default
|
||||
:group 'mu4e
|
||||
:lighter ""
|
||||
:keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd";") #'mu4e-context-switch)
|
||||
map)
|
||||
(mu4e-context-in-modeline))
|
||||
|
||||
;;;
|
||||
;;; _
|
||||
(provide 'mu4e-context)
|
||||
;;; mu4e-context.el ends here
|
||||
|
|
|
@ -96,7 +96,7 @@ BOOKMARK is a bookmark name or a bookmark record."
|
|||
(docid (cdr path))
|
||||
(query (car path)))
|
||||
(call-interactively 'mu4e)
|
||||
(mu4e-search query)
|
||||
(mu4e-headers-search query)
|
||||
(sit-for 0.5)
|
||||
(mu4e~headers-goto-docid docid)
|
||||
(mu4e~headers-highlight docid)
|
||||
|
@ -172,17 +172,6 @@ For example for bogofile, use \"/usr/bin/bogofilter -Sn < %s\"")
|
|||
;; allowing files to be attached to an email via mu4e using the
|
||||
;; eshell. Does not depend on gnus.
|
||||
|
||||
|
||||
(defun mu4e~active-composition-buffers ()
|
||||
"Return all active mu4e composition buffers"
|
||||
(let (buffers)
|
||||
(save-excursion
|
||||
(dolist (buffer (buffer-list t))
|
||||
(set-buffer buffer)
|
||||
(when (eq major-mode 'mu4e-compose-mode)
|
||||
(push (buffer-name buffer) buffers))))
|
||||
(nreverse buffers)))
|
||||
|
||||
(defun eshell/mu4e-attach (&rest args)
|
||||
"Attach files to a mu4e message using eshell. If no mu4e
|
||||
buffers found, compose a new message and then attach the file."
|
||||
|
|
|
@ -27,232 +27,12 @@
|
|||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-utils)
|
||||
(require 'mu4e-message)
|
||||
(require 'mu4e-contacts)
|
||||
(require 'mu4e-folders)
|
||||
(require 'message) ;; mail-header-separator
|
||||
|
||||
|
||||
;;; Configuration
|
||||
(defgroup mu4e-compose nil
|
||||
"Customizations for composing/sending messages."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-compose-reply-recipients 'ask
|
||||
"Which recipients to use when replying to a message.
|
||||
May be 'ask, 'all, 'sender. Note that that only applies to
|
||||
non-mailing-list message; for those, mu4e always asks."
|
||||
:type '(choice ask
|
||||
all
|
||||
sender)
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-reply-to-address nil
|
||||
"The Reply-To address.
|
||||
Useful when this is not equal to the From: address."
|
||||
:type 'string
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-forward-as-attachment nil
|
||||
"Whether to forward messages as attachments instead of inline."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
;; backward compatibility
|
||||
(make-obsolete-variable 'mu4e-reply-to-address
|
||||
'mu4e-compose-reply-to-address
|
||||
"v0.9.9")
|
||||
|
||||
(defcustom mu4e-compose-keep-self-cc nil
|
||||
"When non-nil. keep your e-mail address in Cc: when replying."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defvar mu4e-compose-parent-message nil
|
||||
"The parent message plist.
|
||||
This is the message being replied to, forwarded or edited; used
|
||||
in `mu4e-compose-pre-hook'. For new messages, it is nil.")
|
||||
|
||||
(make-obsolete-variable 'mu4e-auto-retrieve-keys "no longer used." "1.3.1")
|
||||
|
||||
(defcustom mu4e-decryption-policy t
|
||||
"Policy for dealing with replying/forwarding encrypted parts.
|
||||
The setting is a symbol:
|
||||
* t: try to decrypt automatically
|
||||
* `ask': ask before decrypting anything
|
||||
* nil: don't try to decrypt anything."
|
||||
:type '(choice (const :tag "Try to decrypt automatically" t)
|
||||
(const :tag "Ask before decrypting anything" ask)
|
||||
(const :tag "Don't try to decrypt anything" nil))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
;;; Composing / Sending messages
|
||||
|
||||
(defcustom mu4e-sent-messages-behavior 'sent
|
||||
"Determines what mu4e does with sent messages.
|
||||
|
||||
This is one of the symbols:
|
||||
* `sent' move the sent message to the Sent-folder (`mu4e-sent-folder')
|
||||
* `trash' move the sent message to the Trash-folder (`mu4e-trash-folder')
|
||||
* `delete' delete the sent message.
|
||||
|
||||
Note, when using GMail/IMAP, you should set this to either
|
||||
`trash' or `delete', since GMail already takes care of keeping
|
||||
copies in the sent folder.
|
||||
|
||||
Alternatively, `mu4e-sent-messages-behavior' can be a function
|
||||
which takes no arguments, and which should return one of the mentioned
|
||||
symbols, for example:
|
||||
|
||||
(setq mu4e-sent-messages-behavior (lambda ()
|
||||
(if (string= (message-sendmail-envelope-from) \"foo@example.com\")
|
||||
'delete 'sent)))
|
||||
|
||||
The various `message-' functions from `message-mode' are available
|
||||
for querying the message information."
|
||||
:type '(choice (const :tag "move message to mu4e-sent-folder" sent)
|
||||
(const :tag "move message to mu4e-trash-folder" trash)
|
||||
(const :tag "delete message" delete))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-context-policy 'ask
|
||||
"Policy for determining the context when composing a new message.
|
||||
|
||||
If the value is `always-ask', ask the user unconditionally.
|
||||
|
||||
In all other cases, if any context matches (using its match
|
||||
function), this context is used. Otherwise, if none of the
|
||||
contexts match, we have the following choices:
|
||||
|
||||
- `pick-first': pick the first of the contexts available (ie. the default)
|
||||
- `ask': ask the user
|
||||
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
|
||||
- nil: return nil; leaves the current context as is.
|
||||
|
||||
Also see `mu4e-context-policy'."
|
||||
:type '(choice
|
||||
(const :tag "Always ask what context to use" always-ask)
|
||||
(const :tag "Ask if none of the contexts match" ask)
|
||||
(const :tag "Ask when there's no context yet" ask-if-none)
|
||||
(const :tag "Pick the first context if none match" pick-first)
|
||||
(const :tag "Don't change the context when none match" nil))
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-crypto-policy
|
||||
'(encrypt-encrypted-replies sign-encrypted-replies)
|
||||
"Policy to control when messages will be signed/encrypted.
|
||||
|
||||
The value is a list, whose members determine the behaviour of
|
||||
`mu4e~compose-crypto-message'. Specifically, it might contain:
|
||||
|
||||
- `sign-all-messages': Always add a signature.
|
||||
- `sign-new-messages': Add a signature to new message, ie.
|
||||
messages that aren't responses to another message.
|
||||
- `sign-forwarded-messages': Add a signature when forwarding
|
||||
a message
|
||||
- `sign-edited-messages': Add a signature to drafts
|
||||
- `sign-all-replies': Add a signature when responding to
|
||||
another message.
|
||||
- `sign-plain-replies': Add a signature when responding to
|
||||
non-encrypted messages.
|
||||
- `sign-encrypted-replies': Add a signature when responding
|
||||
to encrypted messages.
|
||||
|
||||
It should be noted that certain symbols have priorities over one
|
||||
another. So `sign-all-messages' implies `sign-all-replies', which
|
||||
in turn implies `sign-plain-replies'. Adding both to the set, is
|
||||
not a contradiction, but a redundant configuration.
|
||||
|
||||
All `sign-*' options have a `encrypt-*' analogue."
|
||||
:type '(set :greedy t
|
||||
(const :tag "Sign all messages" sign-all-messages)
|
||||
(const :tag "Encrypt all messages" encrypt-all-messages)
|
||||
(const :tag "Sign new messages" sign-new-messages)
|
||||
(const :tag "Encrypt new messages" encrypt-new-messages)
|
||||
(const :tag "Sign forwarded messages" sign-forwarded-messages)
|
||||
(const :tag "Encrypt forwarded messages" encrypt-forwarded-messages)
|
||||
(const :tag "Sign edited messages" sign-edited-messages)
|
||||
(const :tag "Encrypt edited messages" edited-forwarded-messages)
|
||||
(const :tag "Sign all replies" sign-all-replies)
|
||||
(const :tag "Encrypt all replies" encrypt-all-replies)
|
||||
(const :tag "Sign replies to plain messages" sign-plain-replies)
|
||||
(const :tag "Encrypt replies to plain messages" encrypt-plain-replies)
|
||||
(const :tag "Sign replies to encrypted messages" sign-encrypted-replies)
|
||||
(const :tag "Encrypt replies to encrypted messages" encrypt-encrypted-replies))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-crypto-reply-encrypted-policy nil
|
||||
"Policy for signing/encrypting replies to encrypted messages.
|
||||
We have the following choices:
|
||||
|
||||
- `sign': sign the reply
|
||||
- `sign-and-encrypt': sign and encrypt the reply
|
||||
- `encrypt': encrypt the reply, but don't sign it.
|
||||
- anything else: do nothing."
|
||||
:type '(choice
|
||||
(const :tag "Sign the reply" sign)
|
||||
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
|
||||
(const :tag "Encrypt the reply" encrypt)
|
||||
(const :tag "Don't do anything" nil))
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(make-obsolete-variable 'mu4e-compose-crypto-reply-encrypted-policy "The use of the
|
||||
'mu4e-compose-crypto-reply-encrypted-policy' variable is deprecated.
|
||||
'mu4e-compose-crypto-policy' should be used instead"
|
||||
"2020-03-06")
|
||||
|
||||
(defcustom mu4e-compose-crypto-reply-plain-policy nil
|
||||
"Policy for signing/encrypting replies to messages received unencrypted.
|
||||
We have the following choices:
|
||||
|
||||
- `sign': sign the reply
|
||||
- `sign-and-encrypt': sign and encrypt the reply
|
||||
- `encrypt': encrypt the reply, but don't sign it.
|
||||
- anything else: do nothing."
|
||||
:type '(choice
|
||||
(const :tag "Sign the reply" sign)
|
||||
(const :tag "Sign and encrypt the reply" sign-and-encrypt)
|
||||
(const :tag "Encrypt the reply" encrypt)
|
||||
(const :tag "Don't do anything" nil))
|
||||
:safe 'symbolp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(make-obsolete-variable 'mu4e-compose-crypto-reply-plain-policy "The use of the
|
||||
'mu4e-compose-crypto-reply-plain-policy' variable is deprecated.
|
||||
'mu4e-compose-crypto-policy' should be used instead"
|
||||
"2020-03-06")
|
||||
|
||||
(make-obsolete-variable 'mu4e-compose-crypto-reply-policy "The use of the
|
||||
'mu4e-compose-crypto-reply-policy' variable is deprecated.
|
||||
'mu4e-compose-crypto-reply-plain-policy' and
|
||||
'mu4e-compose-crypto-reply-encrypted-policy' should be used instead"
|
||||
"2017-09-02")
|
||||
|
||||
(defcustom mu4e-compose-format-flowed nil
|
||||
"Whether to compose messages to be sent as format=flowed.
|
||||
\(Or with long lines if variable `use-hard-newlines' is set to
|
||||
nil). The variable `fill-flowed-encode-column' lets you customize
|
||||
the width beyond which format=flowed lines are wrapped."
|
||||
:type 'boolean
|
||||
:safe 'booleanp
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-pre-hook nil
|
||||
"Hook run just *before* message composition starts.
|
||||
If the compose-type is either 'reply' or 'forward', the variable
|
||||
`mu4e-compose-parent-message' points to the message replied to /
|
||||
being forwarded / edited, and `mu4e-compose-type' contains the
|
||||
type of message to be composed.
|
||||
|
||||
Note that there is no draft message yet when this hook runs, it
|
||||
is meant for influencing the how mu4e constructs the draft
|
||||
message. If you want to do something with the draft messages after
|
||||
it has been constructed, `mu4e-compose-mode-hook' would be the
|
||||
place to do that."
|
||||
:type 'hook
|
||||
:group 'mu4e-compose)
|
||||
;;; Options
|
||||
|
||||
(defcustom mu4e-compose-dont-reply-to-self nil
|
||||
"If non-nil, don't include self.
|
||||
|
@ -299,11 +79,6 @@ mu4e-specific version of `message-signature'."
|
|||
|
||||
(defvar mu4e-view-date-format)
|
||||
|
||||
(defvar mu4e-compose-type nil
|
||||
"The compose-type for this buffer.
|
||||
This is a symbol, `new', `forward', `reply' or `edit'.")
|
||||
|
||||
|
||||
(defun mu4e~draft-cite-original (msg)
|
||||
"Return a cited version of the original message MSG as a plist.
|
||||
This function uses `mu4e-compose-cite-function', and as such all
|
||||
|
@ -344,22 +119,6 @@ one. Code borrowed from `message-shorten-1'."
|
|||
(setcdr (nthcdr (- cut 2) list)
|
||||
(nthcdr (+ (- cut 2) surplus 1) list)))
|
||||
|
||||
|
||||
|
||||
(defun mu4e~fontify-signature ()
|
||||
"Give the message signatures a distinctive color. This is used in
|
||||
the view and compose modes and will color each signature in digest messages adhering to RFC 1153."
|
||||
(let ((inhibit-read-only t))
|
||||
(save-excursion
|
||||
;; give the footer a different color...
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^-- *$" nil t)
|
||||
(let ((p (point))
|
||||
(end (or
|
||||
(re-search-forward "\\(^-\\{30\\}.*$\\)" nil t) ;; 30 by RFC1153
|
||||
(point-max))))
|
||||
(add-text-properties p end '(face mu4e-footer-face)))))))
|
||||
|
||||
(defun mu4e~draft-references-construct (msg)
|
||||
"Construct the value of the References: header based on MSG.
|
||||
This assumes a comma-separated string. Normally, this the concatenation of the
|
||||
|
@ -799,13 +558,13 @@ will be created from either `mu4e~draft-reply-construct', or
|
|||
;; case-1: re-editing a draft messages. in this case, we do know the
|
||||
;; full path, but we cannot really know 'drafts folder'... we make a
|
||||
;; guess
|
||||
(setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path)))
|
||||
(setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path)))
|
||||
(mu4e~draft-open-file (mu4e-message-field msg :path) switch-function))
|
||||
|
||||
(resend
|
||||
;; case-2: copy some exisisting message to a draft message, then edit
|
||||
;; that.
|
||||
(setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path)))
|
||||
(setq draft-dir (mu4e~guess-maildir (mu4e-message-field msg :path)))
|
||||
(let ((draft-path (mu4e~draft-determine-path draft-dir)))
|
||||
(copy-file (mu4e-message-field msg :path) draft-path)
|
||||
(mu4e~draft-open-file draft-path switch-function)))
|
||||
|
|
|
@ -1,354 +0,0 @@
|
|||
;;; mu4e-folders.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Dealing with maildirs & folders
|
||||
|
||||
;;; Code:
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-helpers)
|
||||
(require 'mu4e-context)
|
||||
(require 'mu4e-server)
|
||||
|
||||
;;; Customization
|
||||
(defgroup mu4e-folders nil
|
||||
"Special folders."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-drafts-folder "/drafts"
|
||||
"Folder for draft messages, relative to the root maildir.
|
||||
For instance, \"/drafts\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. Note, the message
|
||||
parameter refers to the original message being replied to / being
|
||||
forwarded / re-edited and is nil otherwise. `mu4e-drafts-folder'
|
||||
is only evaluated once."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-refile-folder "/archive"
|
||||
"Folder for refiling messages, relative to the root maildir.
|
||||
For instance \"/Archive\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. Note that the
|
||||
message parameter refers to the message-at-point."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-sent-folder "/sent"
|
||||
"Folder for sent messages, relative to the root maildir.
|
||||
For instance, \"/Sent Items\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. Note that the
|
||||
message parameter refers to the original message being replied to
|
||||
/ being forwarded / re-edited, and is nil otherwise."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-trash-folder "/trash"
|
||||
"Folder for trashed messages, relative to the root maildir.
|
||||
For instance, \"/trash\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. When using
|
||||
`mu4e-trash-folder' in the headers view (when marking messages
|
||||
for trash). Note that the message parameter refers to the
|
||||
message-at-point. When using it when composing a message (see
|
||||
`mu4e-sent-messages-behavior'), this refers to the original
|
||||
message being replied to / being forwarded / re-edited, and is
|
||||
nil otherwise."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-maildir-shortcuts nil
|
||||
"A list of maildir shortcuts.
|
||||
This makes it possible to quickly go to a particular
|
||||
maildir (folder), or quickly moving messages to them (e.g., for
|
||||
archiving or refiling).
|
||||
|
||||
Each of the list elements is a plist with at least:
|
||||
`:maildir' - the maildir for the shortcut (e.g. \"/archive\")
|
||||
`:key' - the shortcut key.
|
||||
|
||||
Optionally, you can add the following:
|
||||
`:hide' - if t, the shortcut is hidden from the main-view and
|
||||
speedbar.
|
||||
`:hide-unread' - do not show the counts of unread/total number
|
||||
of matches for the maildir in the main-view, and is implied
|
||||
from `:hide'.
|
||||
|
||||
For backward compatibility, an older form is recognized as well:
|
||||
|
||||
(maildir . key), where MAILDIR is a maildir (such as
|
||||
\"/archive/\"), and key is a single character.
|
||||
|
||||
You can use these shortcuts in the headers and view buffers, for
|
||||
example with `mu4e-mark-for-move-quick' (or 'm', by default) or
|
||||
`mu4e-jump-to-maildir' (or 'j', by default), followed by the
|
||||
designated shortcut character for the maildir.
|
||||
|
||||
Unlike in search queries, folder names with spaces in them must
|
||||
NOT be quoted, since mu4e does this for you."
|
||||
:type '(repeat (cons (string :tag "Maildir") character))
|
||||
:version "1.3.9"
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-maildir-info-delimiter
|
||||
(if (member system-type '(ms-dos windows-nt cygwin))
|
||||
";" ":")
|
||||
"Separator character between message identifier and flags.
|
||||
It defaults to ':' on most platforms, except on Windows, where it
|
||||
is not allowed and we use ';' for compatibility with mbsync,
|
||||
offlineimap and other programs."
|
||||
:type 'string
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-attachment-dir (expand-file-name "~/")
|
||||
"Default directory for attaching and saving attachments.
|
||||
|
||||
This can be either a string (a file system path), or a function
|
||||
that takes a filename and the mime-type as arguments, and returns
|
||||
the attachment dir. See Info node `(mu4e) Attachments' for
|
||||
details.
|
||||
|
||||
When this called for composing a message, both filename and
|
||||
mime-type are nill."
|
||||
:type 'directory
|
||||
:group 'mu4e-folders
|
||||
:safe 'stringp)
|
||||
|
||||
|
||||
|
||||
(defun mu4e-maildir-shortcuts ()
|
||||
"Get `mu4e-maildir-shortcuts' in the (new) format.
|
||||
Converts from the old format if needed."
|
||||
(cl-map 'list
|
||||
(lambda (item) ;; convert from old format?
|
||||
(if (and (consp item) (not (consp (cdr item))))
|
||||
`(:maildir ,(car item) :key ,(cdr item))
|
||||
item))
|
||||
mu4e-maildir-shortcuts))
|
||||
|
||||
(defun mu4e--maildirs-with-query ()
|
||||
"Llike `mu4e-maildir-shortcuts', but with :query populated.
|
||||
|
||||
This is meant to be the exact same data structure as
|
||||
`mu4e-bookmarks'."
|
||||
(cl-mapcar
|
||||
(lambda (m)
|
||||
(append
|
||||
;; we want to change the :maildir key to :name, and add a :query key
|
||||
(list :name (plist-get m :maildir)
|
||||
:query (format "maildir:\"%s\"" (plist-get m :maildir)))
|
||||
;; next we want to append any other keys to our previous list (e.g. :hide,
|
||||
;; :key, etc) but skipping :maildir (since it's renamed to :name)
|
||||
(cl-loop for (key value) on m by 'cddr
|
||||
when (not (equal key :maildir))
|
||||
append (list key value))))
|
||||
(mu4e-maildir-shortcuts)))
|
||||
|
||||
|
||||
;; the standard folders can be functions too
|
||||
(defun mu4e--get-folder (foldervar msg)
|
||||
"Within the mu-context of MSG, get message folder FOLDERVAR.
|
||||
If FOLDER is a string, return it, if it is a function, evaluate
|
||||
this function with MSG as parameter which may be nil, and return
|
||||
the result."
|
||||
(unless (member foldervar
|
||||
'(mu4e-sent-folder mu4e-drafts-folder
|
||||
mu4e-trash-folder mu4e-refile-folder))
|
||||
(mu4e-error "Folder must be one of mu4e-(sent|drafts|trash|refile)-folder"))
|
||||
;; get the value with the vars for the relevants context let-bound
|
||||
(with-mu4e-context-vars (mu4e-context-determine msg nil)
|
||||
(let* ((folder (symbol-value foldervar))
|
||||
(val
|
||||
(cond
|
||||
((stringp folder) folder)
|
||||
((functionp folder) (funcall folder msg))
|
||||
(t (mu4e-error "Unsupported type for %S" folder)))))
|
||||
(or val (mu4e-error "%S evaluates to nil" foldervar)))))
|
||||
|
||||
(defun mu4e-get-drafts-folder (&optional msg)
|
||||
"Get the sent folder, optionallly based on MSG.
|
||||
See `mu4e-drafts-folder'." (mu4e--get-folder 'mu4e-drafts-folder msg))
|
||||
|
||||
(defun mu4e-get-refile-folder (&optional msg)
|
||||
"Get the folder for refiling, optionallly based on MSG.
|
||||
See `mu4e-refile-folder'." (mu4e--get-folder 'mu4e-refile-folder msg))
|
||||
|
||||
(defun mu4e-get-sent-folder (&optional msg)
|
||||
"Get the sent folder, optionallly based on MSG.
|
||||
See `mu4e-sent-folder'." (mu4e--get-folder 'mu4e-sent-folder msg))
|
||||
|
||||
(defun mu4e-get-trash-folder (&optional msg)
|
||||
"Get the sent folder, optionallly based on MSG.
|
||||
See `mu4e-trash-folder'." (mu4e--get-folder 'mu4e-trash-folder msg))
|
||||
|
||||
|
||||
;;; Maildirs
|
||||
(defun mu4e--guess-maildir (path)
|
||||
"Guess the maildir for PATH, or nil if cannot find it."
|
||||
(let ((idx (string-match (mu4e-root-maildir) path)))
|
||||
(when (and idx (zerop idx))
|
||||
(replace-regexp-in-string
|
||||
(mu4e-root-maildir)
|
||||
""
|
||||
(expand-file-name
|
||||
(concat path "/../.."))))))
|
||||
|
||||
(defun mu4e-create-maildir-maybe (dir)
|
||||
"Offer to create maildir DIR if it does not exist yet.
|
||||
Return t if the dir already existed, or an attempt has been made to
|
||||
create it -- we cannot be sure creation succeeded here, since this
|
||||
is done asynchronously. Otherwise, return nil. NOte, DIR has to be
|
||||
an absolute path."
|
||||
(if (and (file-exists-p dir) (not (file-directory-p dir)))
|
||||
(mu4e-error "File %s exists, but is not a directory" dir))
|
||||
(cond
|
||||
((file-directory-p dir) t)
|
||||
((yes-or-no-p (mu4e-format "%s does not exist yet. Create now?" dir))
|
||||
(mu4e--server-mkdir dir) t)
|
||||
(t nil)))
|
||||
|
||||
(defun mu4e~get-maildirs-1 (path mdir)
|
||||
"Get maildirs for MDIR under PATH.
|
||||
Do so recursively and produce a list of relative paths."
|
||||
(let ((dirs)
|
||||
(dentries
|
||||
(ignore-errors
|
||||
(directory-files-and-attributes
|
||||
(concat path mdir) nil
|
||||
"^[^.]\\|\\.[^.][^.]" t))))
|
||||
(dolist (dentry dentries)
|
||||
(when (and (booleanp (cadr dentry)) (cadr dentry))
|
||||
(if (file-accessible-directory-p
|
||||
(concat (mu4e-root-maildir) "/" mdir "/" (car dentry) "/cur"))
|
||||
(setq dirs (cons (concat mdir (car dentry)) dirs)))
|
||||
(unless (member (car dentry) '("cur" "new" "tmp"))
|
||||
(setq dirs
|
||||
(append dirs
|
||||
(mu4e~get-maildirs-1 path
|
||||
(concat mdir
|
||||
(car dentry) "/")))))))
|
||||
dirs))
|
||||
|
||||
(defvar mu4e-cache-maildir-list nil
|
||||
"Whether to cache the list of maildirs.
|
||||
Set it to t if you find
|
||||
that generating the list on the fly is too slow. If you do, you
|
||||
can set `(mu4e-root-maildir)-list' to nil to force regenerating the
|
||||
cache the next time `mu4e-get-maildirs' gets called.")
|
||||
|
||||
(defvar mu4e-maildir-list nil
|
||||
"Cached list of maildirs.")
|
||||
|
||||
(defun mu4e-get-maildirs ()
|
||||
"Get maildirs under `mu4e-maildir'.
|
||||
Do so recursively, and produce a list of relative paths (ie.,
|
||||
/archive, /sent etc.). Most of the work is done in
|
||||
`mu4e~get-maildirs-1'. Note, these results are /cached/ if
|
||||
`mu4e-cache-maildir-list' is customized to non-nil. In that case,
|
||||
the list of maildirs will not change until you restart mu4e."
|
||||
(unless (and mu4e-maildir-list mu4e-cache-maildir-list)
|
||||
(setq mu4e-maildir-list
|
||||
(sort
|
||||
(append
|
||||
(when (file-accessible-directory-p
|
||||
(concat (mu4e-root-maildir) "/cur")) '("/"))
|
||||
(mu4e~get-maildirs-1 (mu4e-root-maildir) "/"))
|
||||
(lambda (s1 s2) (string< (downcase s1) (downcase s2))))))
|
||||
mu4e-maildir-list)
|
||||
|
||||
(defun mu4e-ask-maildir (prompt)
|
||||
"Ask the user for a shortcut (using PROMPT).
|
||||
As per (mu4e-maildir-shortcuts), then return the corresponding folder
|
||||
name. If the special shortcut 'o' (for _o_ther) is used, or if
|
||||
`(mu4e-maildir-shortcuts)' evaluates to nil, let user choose from
|
||||
all maildirs under `mu4e-maildir'."
|
||||
(let ((prompt (mu4e-format "%s" prompt)))
|
||||
(if (not (mu4e-maildir-shortcuts))
|
||||
(substring-no-properties
|
||||
(funcall mu4e-completing-read-function prompt (mu4e-get-maildirs)))
|
||||
(let* ((mlist (append (mu4e-maildir-shortcuts)
|
||||
'((:maildir "ther" :key ?o))))
|
||||
(fnames
|
||||
(mapconcat
|
||||
(lambda (item)
|
||||
(concat
|
||||
"["
|
||||
(propertize (make-string 1 (plist-get item :key))
|
||||
'face 'mu4e-highlight-face)
|
||||
"]"
|
||||
(plist-get item :maildir)))
|
||||
mlist ", "))
|
||||
(kar (read-char (concat prompt fnames))))
|
||||
(if (member kar '(?/ ?o)) ;; user chose 'other'?
|
||||
(substring-no-properties
|
||||
(funcall mu4e-completing-read-function prompt
|
||||
(mu4e-get-maildirs) nil nil "/"))
|
||||
(or (plist-get
|
||||
(cl-find-if (lambda (item) (= kar (plist-get item :key)))
|
||||
(mu4e-maildir-shortcuts)) :maildir)
|
||||
(mu4e-warn "Unknown shortcut '%c'" kar)))))))
|
||||
|
||||
(defun mu4e-ask-maildir-check-exists (prompt)
|
||||
"Like `mu4e-ask-maildir', PROMPT for existence of the maildir.
|
||||
Offer to create it if it does not exist yet."
|
||||
(let* ((mdir (mu4e-ask-maildir prompt))
|
||||
(fullpath (concat (mu4e-root-maildir) mdir)))
|
||||
(unless (file-directory-p fullpath)
|
||||
(and (yes-or-no-p
|
||||
(mu4e-format "%s does not exist. Create now?" fullpath))
|
||||
(mu4e--server-mkdir fullpath)))
|
||||
mdir))
|
||||
|
||||
;; mu4e-attachment-dir is either a string or a function that takes a
|
||||
;; filename and the mime-type as argument, either (or both) which can
|
||||
;; be nil
|
||||
|
||||
(defun mu4e~get-attachment-dir (&optional fname mimetype)
|
||||
"Get the directory for saving attachments from
|
||||
`mu4e-attachment-dir' (which can be either a string or a function,
|
||||
see its docstring)."
|
||||
(let
|
||||
((dir
|
||||
(cond
|
||||
((stringp mu4e-attachment-dir)
|
||||
mu4e-attachment-dir)
|
||||
((functionp mu4e-attachment-dir)
|
||||
(funcall mu4e-attachment-dir fname mimetype))
|
||||
(t
|
||||
(mu4e-error "unsupported type for mu4e-attachment-dir" )))))
|
||||
(if dir
|
||||
(expand-file-name dir)
|
||||
(mu4e-error "mu4e-attachment-dir evaluates to nil"))))
|
||||
|
||||
(provide 'mu4e-folders)
|
||||
;;; mu4e-folders.el ends here
|
|
@ -34,25 +34,18 @@
|
|||
(require 'mailcap)
|
||||
(require 'mule-util) ;; seems _some_ people need this for truncate-string-ellipsis
|
||||
|
||||
(require 'mu4e-update)
|
||||
|
||||
(require 'mu4e-utils) ;; utility functions
|
||||
(require 'mu4e-server)
|
||||
(require 'mu4e-proc)
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-mark)
|
||||
(require 'mu4e-context)
|
||||
(require 'mu4e-search)
|
||||
(require 'mu4e-compose)
|
||||
(require 'mu4e-actions)
|
||||
(require 'mu4e-message)
|
||||
(require 'mu4e-folders)
|
||||
|
||||
(declare-function mu4e-view "mu4e-view")
|
||||
(declare-function mu4e~main-view "mu4e-main")
|
||||
|
||||
|
||||
|
||||
;;; Configuration
|
||||
;;; Options
|
||||
|
||||
(defgroup mu4e-headers nil
|
||||
"Settings for the headers view."
|
||||
|
@ -127,6 +120,27 @@ indexing operation showed changes."
|
|||
:type 'boolean
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(defcustom mu4e-headers-results-limit 500
|
||||
"Maximum number of results to show; this affects performance
|
||||
quite a bit, especially when `mu4e-headers-include-related' is
|
||||
non-nil. Set to -1 for no limits, and you temporarily (for one
|
||||
query) ignore the limit by pressing a C-u before invoking the
|
||||
search.
|
||||
|
||||
Note that there are a few complications when
|
||||
`mu4e-headers-include-related' is enabled: mu performs *two*
|
||||
queries; the first one with this limit set, and then a second
|
||||
(unlimited) query for all messages that are related to the first
|
||||
matches. We then limit this second result as well, favoring the
|
||||
messages that were found in the first set (the \"leaders\").
|
||||
"
|
||||
:type '(choice (const :tag "Unlimited" -1)
|
||||
(integer :tag "Limit"))
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(make-obsolete-variable 'mu4e-search-results-limit
|
||||
'mu4e-headers-results-limit "0.9.9.5-dev6")
|
||||
|
||||
(defcustom mu4e-headers-advance-after-mark t
|
||||
"With this option set to non-nil, automatically advance to the
|
||||
next mail after marking a message in header view."
|
||||
|
@ -172,6 +186,33 @@ query have been received and are displayed."
|
|||
:type 'hook
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(defcustom mu4e-headers-search-bookmark-hook nil
|
||||
"Hook run just after we invoke a bookmarked search. This
|
||||
function receives the query as its parameter, before any
|
||||
rewriting as per `mu4e-query-rewrite-function' has taken place.
|
||||
|
||||
The reason to use this instead of `mu4e-headers-search-hook' is
|
||||
if you only want to execute a hook when a search is entered via a
|
||||
bookmark, e.g. if you'd like to treat the bookmarks as a custom
|
||||
folder and change the options for the search, e.g.
|
||||
`mu4e-headers-show-threads', `mu4e-headers-include-related',
|
||||
`mu4e-headers-skip-duplicates` or `mu4e-headers-results-limit'.
|
||||
"
|
||||
:type 'hook
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(defcustom mu4e-headers-search-hook nil
|
||||
"Hook run just before executing a new search operation. This
|
||||
function receives the query as its parameter, before any
|
||||
rewriting as per `mu4e-query-rewrite-function' has taken place
|
||||
|
||||
This is a more general hook facility than the
|
||||
`mu4e-headers-search-bookmark-hook'. It gets called on every
|
||||
executed search, not just those that are invoked via bookmarks,
|
||||
but also manually invoked searches."
|
||||
:type 'hook
|
||||
:group 'mu4e-headers)
|
||||
|
||||
;;; Public variables
|
||||
|
||||
(defvar mu4e-headers-sort-field :date
|
||||
|
@ -180,7 +221,7 @@ one of: `:date', `:subject', `:size', `:prio', `:from', `:to.',
|
|||
`:list'.
|
||||
|
||||
Note that when threading is enabled (through
|
||||
`mu4e-search-threads'), the headers are exclusively sorted
|
||||
`mu4e-headers-show-threads'), the headers are exclusively sorted
|
||||
chronologically (`:date') by the newest message in the thread.")
|
||||
|
||||
(defvar mu4e-headers-sort-direction 'descending
|
||||
|
@ -267,6 +308,14 @@ and (optionally) PARAM, and should return non-nil when there's a
|
|||
match.
|
||||
* PARAM-FUNC is function that is evaluated once, and its value is then passed to
|
||||
PREDICATE-FUNC as PARAM. This is useful for getting user-input.")
|
||||
|
||||
(defvar mu4e-headers-show-threads t
|
||||
"Whether to show threads in the headers list.")
|
||||
|
||||
(defvar mu4e-headers-full-search nil
|
||||
"Whether to show all results.
|
||||
If this is nil show results up to `mu4e-headers-results-limit')")
|
||||
|
||||
;;; Internal variables/constants
|
||||
|
||||
;; docid cookies
|
||||
|
@ -289,7 +338,6 @@ followed by the docid, followed by `mu4e~headers-docid-post'.")
|
|||
"List of cells describing the various sort-options.
|
||||
In the format needed for `mu4e-read-option'.")
|
||||
|
||||
|
||||
;;; Clear
|
||||
|
||||
(defvar mu4e~headers-render-start nil)
|
||||
|
@ -299,11 +347,6 @@ In the format needed for `mu4e-read-option'.")
|
|||
"If non-nil, report on the time it took to render the messages.
|
||||
This is mostly useful for profiling.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(defun mu4e~headers-clear (&optional msg)
|
||||
"Clear the header buffer and related data structures."
|
||||
(when (buffer-live-p (mu4e-get-headers-buffer))
|
||||
|
@ -751,48 +794,10 @@ if provided, or at the end of the buffer otherwise."
|
|||
(mu4e~headers-add-header line (mu4e-message-field msg :docid)
|
||||
point msg))))))
|
||||
|
||||
|
||||
|
||||
|
||||
;;; Performing queries (internal)
|
||||
(defconst mu4e~search-message "Searching...")
|
||||
(defconst mu4e~no-matches "No matching messages found")
|
||||
(defconst mu4e~end-of-results "End of search results")
|
||||
|
||||
|
||||
(defun mu4e--search-execute (expr ignore-history)
|
||||
"Search for query EXPR.
|
||||
|
||||
Switch to the output buffer for the results. If IGNORE-HISTORY is
|
||||
true, do *not* update the query history stack."
|
||||
(let* ((buf (get-buffer-create mu4e-headers-buffer-name))
|
||||
(inhibit-read-only t)
|
||||
(rewritten-expr (funcall mu4e-search-query-rewrite-function expr))
|
||||
(maxnum (unless mu4e-search-full mu4e-search-results-limit)))
|
||||
(with-current-buffer buf
|
||||
(mu4e-headers-mode)
|
||||
(unless ignore-history
|
||||
;; save the old present query to the history list
|
||||
(when mu4e--search-last-query
|
||||
(mu4e--search-push-query mu4e--search-last-query 'past)))
|
||||
(setq mu4e--search-last-query rewritten-expr)
|
||||
(mu4e~headers-update-mode-line))
|
||||
|
||||
;; when the buffer is already visible, select it; otherwise,
|
||||
;; switch to it.
|
||||
(unless (get-buffer-window buf 0)
|
||||
(switch-to-buffer buf))
|
||||
(run-hook-with-args 'mu4e-search-hook expr)
|
||||
(mu4e~headers-clear mu4e~search-message)
|
||||
(mu4e--server-find
|
||||
rewritten-expr
|
||||
mu4e-search-threads
|
||||
mu4e-headers-sort-field
|
||||
mu4e-headers-sort-direction
|
||||
maxnum
|
||||
mu4e-headers-skip-duplicates
|
||||
mu4e-headers-include-related)))
|
||||
|
||||
(defvar mu4e~headers-view-target nil
|
||||
"Whether to automatically view (open) the target message (as
|
||||
per `mu4e~headers-msgid-target').")
|
||||
|
@ -878,8 +883,24 @@ after the end of the search results."
|
|||
(setq mu4e-headers-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
|
||||
(define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
|
||||
;; for terminal users
|
||||
(define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
|
||||
|
||||
(define-key map "s" 'mu4e-headers-search)
|
||||
(define-key map "S" 'mu4e-headers-search-edit)
|
||||
|
||||
(define-key map "/" 'mu4e-headers-search-narrow)
|
||||
|
||||
(define-key map "j" 'mu4e~headers-jump-to-maildir)
|
||||
|
||||
(define-key map (kbd "<M-left>") 'mu4e-headers-query-prev)
|
||||
(define-key map (kbd "\\") 'mu4e-headers-query-prev)
|
||||
(define-key map (kbd "<M-right>") 'mu4e-headers-query-next)
|
||||
|
||||
(define-key map "b" 'mu4e-headers-search-bookmark)
|
||||
(define-key map "B" 'mu4e-headers-search-bookmark-edit)
|
||||
|
||||
(define-key map "O" 'mu4e-headers-change-sorting)
|
||||
(define-key map "P" 'mu4e-headers-toggle-threading)
|
||||
(define-key map "Q" 'mu4e-headers-toggle-full-search)
|
||||
|
@ -908,6 +929,8 @@ after the end of the search results."
|
|||
(define-key map (kbd "<C-kp-add>") 'mu4e-headers-split-view-grow)
|
||||
(define-key map (kbd "<C-kp-subtract>") 'mu4e-headers-split-view-shrink)
|
||||
|
||||
(define-key map ";" 'mu4e-context-switch)
|
||||
|
||||
;; switching to view mode (if it's visible)
|
||||
(define-key map "y" 'mu4e-select-other-view)
|
||||
|
||||
|
@ -976,8 +999,8 @@ after the end of the search results."
|
|||
(define-key menumap [toggle-threading]
|
||||
'(menu-item "Toggle threading" mu4e-headers-toggle-threading
|
||||
:button (:toggle .
|
||||
(and (boundp 'mu4e-search-threads)
|
||||
mu4e-search-threads))))
|
||||
(and (boundp 'mu4e-headers-show-threads)
|
||||
mu4e-headers-show-threads))))
|
||||
|
||||
(define-key menumap "|" '("Pipe through shell" . mu4e-view-pipe))
|
||||
(define-key menumap [sepa1] '("--"))
|
||||
|
@ -1044,7 +1067,7 @@ after the end of the search results."
|
|||
(mapcar
|
||||
(lambda (item)
|
||||
(let* ( ;; with threading enabled, we're necessarily sorting by date.
|
||||
(sort-field (if mu4e-search-threads :date mu4e-headers-sort-field))
|
||||
(sort-field (if mu4e-headers-show-threads :date mu4e-headers-sort-field))
|
||||
(field (car item)) (width (cdr item))
|
||||
(info (cdr (assoc field
|
||||
(append mu4e-header-info mu4e-header-info-custom))))
|
||||
|
@ -1099,7 +1122,7 @@ no user-interaction ongoing."
|
|||
;; otherwise we'd trigger a headers view from out of nowhere.
|
||||
(when (and (buffer-live-p (mu4e-get-headers-buffer))
|
||||
(window-live-p (get-buffer-window (mu4e-get-headers-buffer) t)))
|
||||
(mu4e-search-rerun))))
|
||||
(mu4e-headers-rerun-search))))
|
||||
|
||||
(define-derived-mode mu4e-headers-mode special-mode
|
||||
"mu4e:headers"
|
||||
|
@ -1110,6 +1133,8 @@ no user-interaction ongoing."
|
|||
(make-local-variable 'mu4e~highlighted-docid)
|
||||
(set (make-local-variable 'hl-line-face) 'mu4e-header-highlight-face)
|
||||
|
||||
(mu4e-context-in-modeline)
|
||||
|
||||
;; maybe update the current headers upon indexing changes
|
||||
(add-hook 'mu4e-index-updated-hook 'mu4e~headers-maybe-auto-update)
|
||||
(add-hook 'mu4e-index-updated-hook
|
||||
|
@ -1122,9 +1147,6 @@ no user-interaction ongoing."
|
|||
header-line-format (mu4e~header-line-format))
|
||||
|
||||
(mu4e~mark-initialize) ;; initialize the marking subsystem
|
||||
(mu4e-context-minor-mode)
|
||||
(mu4e-update-minor-mode)
|
||||
(mu4e-search-minor-mode)
|
||||
(hl-line-mode 1))
|
||||
|
||||
(defun mu4e~headers-index-updated-hook-fn ()
|
||||
|
@ -1229,14 +1251,14 @@ docid is not found."
|
|||
(if mu4e-use-fancy-chars
|
||||
(cddr flag-cell) (cadr flag-cell) )
|
||||
""))
|
||||
`((,mu4e-search-full . ,mu4e-headers-full-label)
|
||||
`((,mu4e-headers-full-search . ,mu4e-headers-full-label)
|
||||
(,mu4e-headers-include-related . ,mu4e-headers-related-label)
|
||||
(,mu4e-search-threads . ,mu4e-headers-threaded-label))
|
||||
(,mu4e-headers-show-threads . ,mu4e-headers-threaded-label))
|
||||
""))
|
||||
(name "mu4e-headers"))
|
||||
|
||||
(setq mode-name name)
|
||||
(setq mu4e~headers-mode-line-label (concat flagstr " " mu4e--search-last-query))
|
||||
(setq mu4e~headers-mode-line-label (concat flagstr " " mu4e~headers-last-query))
|
||||
|
||||
(make-local-variable 'global-mode-string)
|
||||
|
||||
|
@ -1244,7 +1266,7 @@ docid is not found."
|
|||
`(:eval
|
||||
(concat
|
||||
(propertize
|
||||
(mu4e-quote-for-modeline ,mu4e~headers-mode-line-label)
|
||||
(mu4e~quote-for-modeline ,mu4e~headers-mode-line-label)
|
||||
'face 'mu4e-modeline-face)
|
||||
" "
|
||||
(if (and mu4e-display-update-status-in-modeline
|
||||
|
@ -1255,6 +1277,41 @@ docid is not found."
|
|||
""))))))
|
||||
|
||||
|
||||
(defun mu4e~headers-search-execute (expr ignore-history)
|
||||
"Search in the mu database for EXPR, and switch to the output
|
||||
buffer for the results. If IGNORE-HISTORY is true, do *not* update
|
||||
the query history stack."
|
||||
;; note: we don't want to update the history if this query comes from
|
||||
;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'.
|
||||
;;(mu4e-hide-other-mu4e-buffers)
|
||||
(let* ((buf (get-buffer-create mu4e~headers-buffer-name))
|
||||
(inhibit-read-only t)
|
||||
(rewritten-expr (funcall mu4e-query-rewrite-function expr))
|
||||
(maxnum (unless mu4e-headers-full-search mu4e-headers-results-limit)))
|
||||
(with-current-buffer buf
|
||||
(mu4e-headers-mode)
|
||||
(unless ignore-history
|
||||
;; save the old present query to the history list
|
||||
(when mu4e~headers-last-query
|
||||
(mu4e~headers-push-query mu4e~headers-last-query 'past)))
|
||||
(setq mu4e~headers-last-query rewritten-expr)
|
||||
(mu4e~headers-update-mode-line))
|
||||
|
||||
;; when the buffer is already visible, select it; otherwise,
|
||||
;; switch to it.
|
||||
(unless (get-buffer-window buf 0)
|
||||
(switch-to-buffer buf))
|
||||
(run-hook-with-args 'mu4e-headers-search-hook expr)
|
||||
(mu4e~headers-clear mu4e~search-message)
|
||||
(mu4e~proc-find
|
||||
rewritten-expr
|
||||
mu4e-headers-show-threads
|
||||
mu4e-headers-sort-field
|
||||
mu4e-headers-sort-direction
|
||||
maxnum
|
||||
mu4e-headers-skip-duplicates
|
||||
mu4e-headers-include-related)))
|
||||
|
||||
(defun mu4e~headers-redraw-get-view-window ()
|
||||
"Close all windows, redraw the headers buffer based on the value
|
||||
of `mu4e-split-view', and return a window for the message view."
|
||||
|
@ -1448,8 +1505,166 @@ descendants."
|
|||
(let ((current-prefix-arg t))
|
||||
(call-interactively 'mu4e-headers-mark-thread))))
|
||||
|
||||
|
||||
;;; The query past / present / future
|
||||
|
||||
(defvar mu4e~headers-query-past nil
|
||||
"Stack of queries before the present one.")
|
||||
(defvar mu4e~headers-query-future nil
|
||||
"Stack of queries after the present one.")
|
||||
(defvar mu4e~headers-query-stack-size 20
|
||||
"Maximum size for the query stacks.")
|
||||
|
||||
(defun mu4e~headers-push-query (query where)
|
||||
"Push QUERY to one of the query stacks.
|
||||
WHERE is a symbol telling us where to push; it's a symbol, either
|
||||
'future or 'past. Functional also removes duplicates, limits the
|
||||
stack size."
|
||||
(let ((stack
|
||||
(cl-case where
|
||||
(past mu4e~headers-query-past)
|
||||
(future mu4e~headers-query-future))))
|
||||
;; only add if not the same item
|
||||
(unless (and stack (string= (car stack) query))
|
||||
(push query stack)
|
||||
;; limit the stack to `mu4e~headers-query-stack-size' elements
|
||||
(when (> (length stack) mu4e~headers-query-stack-size)
|
||||
(setq stack (cl-subseq stack 0 mu4e~headers-query-stack-size)))
|
||||
;; remove all duplicates of the new element
|
||||
(cl-remove-if (lambda (elm) (string= elm (car stack))) (cdr stack))
|
||||
;; update the stacks
|
||||
(cl-case where
|
||||
(past (setq mu4e~headers-query-past stack))
|
||||
(future (setq mu4e~headers-query-future stack))))))
|
||||
|
||||
(defun mu4e~headers-pop-query (whence)
|
||||
"Pop a query from the stack.
|
||||
WHENCE is a symbol telling us where to get it from, either `future'
|
||||
or `past'."
|
||||
(cl-case whence
|
||||
(past
|
||||
(unless mu4e~headers-query-past
|
||||
(mu4e-warn "No more previous queries"))
|
||||
(pop mu4e~headers-query-past))
|
||||
(future
|
||||
(unless mu4e~headers-query-future
|
||||
(mu4e-warn "No more next queries"))
|
||||
(pop mu4e~headers-query-future))))
|
||||
|
||||
|
||||
;;; Reading queries with completion
|
||||
|
||||
(defvar mu4e-minibuffer-search-query-map
|
||||
(let ((map (copy-keymap minibuffer-local-map)))
|
||||
(define-key map (kbd "TAB") #'completion-at-point)
|
||||
map)
|
||||
|
||||
"The keymap when reading a search query.")
|
||||
(defun mu4e-read-query (prompt &optional initial-input)
|
||||
"Read a search query with completion using PROMPT and INITIAL-INPUT."
|
||||
(minibuffer-with-setup-hook
|
||||
(lambda ()
|
||||
(setq-local completion-at-point-functions
|
||||
#'mu4e~search-query-competion-at-point)
|
||||
(use-local-map mu4e-minibuffer-search-query-map))
|
||||
(read-string prompt initial-input 'mu4e~headers-search-hist)))
|
||||
|
||||
(defvar mu4e~headers-search-hist nil
|
||||
"History list of searches.")
|
||||
|
||||
(defconst mu4e~search-query-keywords
|
||||
'("and" "or" "not"
|
||||
"from:" "to:" "cc:" "bcc:" "contact:" "date:" "subject:" "body:"
|
||||
"list:" "maildir:" "flag:" "mime:" "file:" "prio:" "tag:" "msgid:"
|
||||
"size:" "embed:"))
|
||||
|
||||
(defun mu4e~search-query-competion-at-point ()
|
||||
(cond
|
||||
((not (looking-back "[:\"][^ \t]*" nil))
|
||||
(let ((bounds (bounds-of-thing-at-point 'word)))
|
||||
(list (or (car bounds) (point))
|
||||
(or (cdr bounds) (point))
|
||||
mu4e~search-query-keywords)))
|
||||
((looking-back "flag:\\(\\w*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
'("attach" "draft" "flagged" "list" "new" "passed" "replied"
|
||||
"seen" "trashed" "unread" "encrypted" "signed")))
|
||||
((looking-back "maildir:\\([a-zA-Z0-9/.]*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
(mu4e-get-maildirs)))
|
||||
((looking-back "prio:\\(\\w*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
(list "high" "normal" "low")))
|
||||
((looking-back "mime:\\([a-zA-Z0-9/-]*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
(mailcap-mime-types)))))
|
||||
|
||||
|
||||
;;; Interactive functions
|
||||
|
||||
(defun mu4e-headers-search (&optional expr prompt edit
|
||||
ignore-history msgid show)
|
||||
"Search in the mu database for EXPR, and switch to the output
|
||||
buffer for the results. This is an interactive function which ask
|
||||
user for EXPR. PROMPT, if non-nil, is the prompt used by this
|
||||
function (default is \"Search for:\"). If EDIT is non-nil,
|
||||
instead of executing the query for EXPR, let the user edit the
|
||||
query before executing it. If IGNORE-HISTORY is true, do *not*
|
||||
update the query history stack. If MSGID is non-nil, attempt to
|
||||
move point to the first message with that message-id after
|
||||
searching. If SHOW is non-nil, show the message with MSGID."
|
||||
;; note: we don't want to update the history if this query comes from
|
||||
;; `mu4e~headers-query-next' or `mu4e~headers-query-prev'."
|
||||
(interactive)
|
||||
(let* ((prompt (mu4e-format (or prompt "Search for: ")))
|
||||
(expr
|
||||
(if (or (null expr) edit)
|
||||
(mu4e-read-query prompt expr)
|
||||
expr)))
|
||||
(mu4e-mark-handle-when-leaving)
|
||||
(mu4e~headers-search-execute expr ignore-history)
|
||||
(setq mu4e~headers-msgid-target msgid
|
||||
mu4e~headers-view-target show)))
|
||||
|
||||
(defun mu4e-headers-search-edit ()
|
||||
"Edit the last search expression."
|
||||
(interactive)
|
||||
(mu4e-headers-search mu4e~headers-last-query nil t))
|
||||
|
||||
(defun mu4e-headers-search-bookmark (&optional expr edit)
|
||||
"Search using some bookmarked query EXPR.
|
||||
If EDIT is non-nil, let the user edit the bookmark before starting
|
||||
the search."
|
||||
(interactive)
|
||||
(let ((expr
|
||||
(or expr
|
||||
(mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))))
|
||||
(run-hook-with-args 'mu4e-headers-search-bookmark-hook expr)
|
||||
(mu4e-headers-search expr (when edit "Edit bookmark: ") edit)))
|
||||
|
||||
(defun mu4e-headers-search-bookmark-edit ()
|
||||
"Edit an existing bookmark before executing it."
|
||||
(interactive)
|
||||
(mu4e-headers-search-bookmark nil t))
|
||||
|
||||
(defun mu4e-headers-search-narrow (filter )
|
||||
"Narrow the last search by appending search expression FILTER to
|
||||
the last search expression. Note that you can go back to previous
|
||||
query (effectively, 'widen' it), with `mu4e-headers-query-prev'."
|
||||
(interactive
|
||||
(let ((filter
|
||||
(read-string (mu4e-format "Narrow down to: ")
|
||||
nil 'mu4e~headers-search-hist nil t)))
|
||||
(list filter)))
|
||||
(unless mu4e~headers-last-query
|
||||
(mu4e-warn "There's nothing to filter"))
|
||||
(mu4e-headers-search
|
||||
(format "(%s) AND (%s)" mu4e~headers-last-query filter)))
|
||||
|
||||
(defun mu4e-headers-change-sorting (&optional field dir)
|
||||
"Change the sorting/threading parameters.
|
||||
FIELD is the field to sort by; DIR is a symbol: either 'ascending,
|
||||
|
@ -1485,7 +1700,7 @@ sortfield, change the sort-order) or nil (ask the user)."
|
|||
(mu4e-message "Sorting by %s (%s)"
|
||||
(symbol-name sortfield)
|
||||
(symbol-name mu4e-headers-sort-direction))
|
||||
(mu4e-search-rerun)))
|
||||
(mu4e-headers-rerun-search)))
|
||||
|
||||
(defun mu4e~headers-toggle (name togglevar dont-refresh)
|
||||
"Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil,
|
||||
|
@ -1497,20 +1712,20 @@ re-run the last search."
|
|||
(if dont-refresh
|
||||
" (press 'g' to refresh)" ""))
|
||||
(unless dont-refresh
|
||||
(mu4e-search-rerun)))
|
||||
(mu4e-headers-rerun-search)))
|
||||
|
||||
(defun mu4e-headers-toggle-threading (&optional dont-refresh)
|
||||
"Toggle `mu4e-search-threads'. With prefix-argument, do
|
||||
"Toggle `mu4e-headers-show-threads'. With prefix-argument, do
|
||||
_not_ refresh the last search with the new setting for threading."
|
||||
(interactive "P")
|
||||
(mu4e~headers-toggle "Threading" 'mu4e-search-threads dont-refresh))
|
||||
(mu4e~headers-toggle "Threading" 'mu4e-headers-show-threads dont-refresh))
|
||||
|
||||
(defun mu4e-headers-toggle-full-search (&optional dont-refresh)
|
||||
"Toggle `mu4e-search-full'. With prefix-argument, do
|
||||
"Toggle `mu4e-headers-full-search'. With prefix-argument, do
|
||||
_not_ refresh the last search with the new setting for threading."
|
||||
(interactive "P")
|
||||
(mu4e~headers-toggle "Full-search"
|
||||
'mu4e-search-full dont-refresh))
|
||||
'mu4e-headers-full-search dont-refresh))
|
||||
|
||||
(defun mu4e-headers-toggle-include-related (&optional dont-refresh)
|
||||
"Toggle `mu4e-headers-include-related'. With prefix-argument, do
|
||||
|
@ -1529,6 +1744,14 @@ _not_ refresh the last search with the new setting for threading."
|
|||
(defvar mu4e~headers-loading-buf nil
|
||||
"A buffer for loading a message view.")
|
||||
|
||||
(defun mu4e~decrypt-p (msg)
|
||||
"Should we decrypt this message?"
|
||||
(when mu4e-view-use-old ;; we don't decrypt in the gnus-view case
|
||||
(and (member 'encrypted (mu4e-message-field msg :flags))
|
||||
(if (eq mu4e-decryption-policy 'ask)
|
||||
(yes-or-no-p (mu4e-format "Decrypt message?"))
|
||||
mu4e-decryption-policy))))
|
||||
|
||||
(defun mu4e-headers-view-message ()
|
||||
"View message at point .
|
||||
If there's an existing window for the view, re-use that one . If
|
||||
|
@ -1546,6 +1769,8 @@ window . "
|
|||
(if (functionp mu4e-view-auto-mark-as-read)
|
||||
(funcall mu4e-view-auto-mark-as-read msg)
|
||||
mu4e-view-auto-mark-as-read))
|
||||
(decrypt (mu4e~decrypt-p msg))
|
||||
(verify mu4e-view-use-old)
|
||||
(viewwin (mu4e~headers-redraw-get-view-window)))
|
||||
(unless (window-live-p viewwin)
|
||||
(mu4e-error "Cannot get a message view"))
|
||||
|
@ -1566,9 +1791,45 @@ window . "
|
|||
|
||||
;; (if mu4e-view-use-gnus
|
||||
;; (mu4e-view msg)
|
||||
;; (mu4e--server-view dowcid decrypt))
|
||||
(mu4e--server-view docid mark-as-read)))
|
||||
;; (mu4e~proc-view dowcid decrypt))
|
||||
(mu4e~proc-view docid mark-as-read decrypt verify)))
|
||||
|
||||
(defun mu4e-headers-rerun-search ()
|
||||
"Rerun the search for the last search expression."
|
||||
(interactive)
|
||||
;; if possible, try to return to the same message
|
||||
(let* ((msg (mu4e-message-at-point t))
|
||||
(msgid (and msg (mu4e-message-field msg :message-id))))
|
||||
(mu4e-headers-search mu4e~headers-last-query nil nil t msgid)))
|
||||
|
||||
(defun mu4e~headers-query-navigate (whence)
|
||||
"Execute the previous query from the query stacks.
|
||||
WHENCE determines where the query is taken from and is a symbol,
|
||||
either `future' or `past'."
|
||||
(let ((query (mu4e~headers-pop-query whence))
|
||||
(where (if (eq whence 'future) 'past 'future)))
|
||||
(when query
|
||||
(mu4e~headers-push-query mu4e~headers-last-query where)
|
||||
(mu4e-headers-search query nil nil t))))
|
||||
|
||||
(defun mu4e-headers-query-next ()
|
||||
"Execute the previous query from the query stacks."
|
||||
(interactive)
|
||||
(mu4e~headers-query-navigate 'future))
|
||||
|
||||
(defun mu4e-headers-query-prev ()
|
||||
"Execute the previous query from the query stacks."
|
||||
(interactive)
|
||||
(mu4e~headers-query-navigate 'past))
|
||||
|
||||
;; forget the past so we don't repeat it :/
|
||||
(defun mu4e-headers-forget-queries ()
|
||||
"Forget all the complete query history."
|
||||
(interactive)
|
||||
(setq ;; note: don't forget the present one
|
||||
mu4e~headers-query-past nil
|
||||
mu4e~headers-query-future nil)
|
||||
(mu4e-message "Query history cleared"))
|
||||
|
||||
(defun mu4e~headers-move (lines)
|
||||
"Move point LINES lines forward (if LINES is positive) or
|
||||
|
@ -1658,9 +1919,9 @@ given, offer to edit the search query before executing it."
|
|||
(list maildir current-prefix-arg)))
|
||||
(when maildir
|
||||
(let* ((query (format "maildir:\"%s\"" maildir))
|
||||
(query (if edit (mu4e-search-read-query "Refine query: " query) query)))
|
||||
(query (if edit (mu4e-read-query "Refine query: " query) query)))
|
||||
(mu4e-mark-handle-when-leaving)
|
||||
(mu4e-search query))))
|
||||
(mu4e-headers-search query))))
|
||||
|
||||
(defun mu4e-headers-split-view-grow (&optional n)
|
||||
"In split-view, grow the headers window.
|
||||
|
@ -1740,32 +2001,6 @@ other windows."
|
|||
(kill-buffer)
|
||||
(mu4e~main-view 'refresh))))
|
||||
|
||||
|
||||
;;; Loading messages
|
||||
;;
|
||||
(defvar mu4e-loading-mode-map nil "Keymap for *mu4e-loading* buffers.")
|
||||
(unless mu4e-loading-mode-map
|
||||
(setq mu4e-loading-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map "n" 'ignore)
|
||||
(define-key map "p" 'ignore)
|
||||
(define-key map "q"
|
||||
(lambda()(interactive)
|
||||
(if (eq mu4e-split-view 'single-window)
|
||||
'kill-buffer
|
||||
'kill-buffer-and-window)))
|
||||
map)))
|
||||
(fset 'mu4e-loading-mode-map mu4e-loading-mode-map)
|
||||
|
||||
(define-derived-mode mu4e-loading-mode special-mode
|
||||
"mu4e:loading"
|
||||
(use-local-map mu4e-loading-mode-map)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (propertize "Loading message..."
|
||||
'face 'mu4e-system-face 'intangible t))))
|
||||
|
||||
|
||||
;;; _
|
||||
(provide 'mu4e-headers)
|
||||
;;; mu4e-headers.el ends here
|
||||
|
|
|
@ -1,518 +0,0 @@
|
|||
;;; mu4e-helpers.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Helper functions used in the mu4e. This is slowly usurp all the code from
|
||||
;; mu4e-utils.el that does not depend on other parts of mu4e.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'seq)
|
||||
(require 'ido)
|
||||
(require 'cl-lib)
|
||||
|
||||
|
||||
;;; Customization
|
||||
|
||||
(defcustom mu4e-debug nil
|
||||
"When set to non-nil, log debug information to the mu4e log buffer."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-modeline-max-width 42
|
||||
"Determines the maximum length of the modeline string.
|
||||
If the string exceeds this limit, it will be truncated to fit."
|
||||
:type 'integer
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-completing-read-function 'ido-completing-read
|
||||
"Function to be used to receive user-input during completion.
|
||||
Suggested possible values are:
|
||||
* `completing-read': built-in completion method
|
||||
* `ido-completing-read': dynamic completion within the minibuffer."
|
||||
:type 'function
|
||||
:options '(completing-read ido-completing-read)
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-use-fancy-chars nil
|
||||
"When set, allow fancy (Unicode) characters for marks/threads.
|
||||
You can customize the exact fancy characters used with
|
||||
`mu4e-marks' and various `mu4e-headers-..-mark' and
|
||||
`mu4e-headers..-prefix' variables."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-display-update-status-in-modeline nil
|
||||
"Non-nil value will display the update status in the modeline."
|
||||
:group 'mu4e
|
||||
:type 'boolean)
|
||||
|
||||
;; maybe move the next ones... but they're convenient
|
||||
;; here because they're needed in multiple buffers.
|
||||
|
||||
(defcustom mu4e-view-auto-mark-as-read t
|
||||
"Automatically mark messages are 'read' when you read them.
|
||||
This is the default behavior, but can be turned off, for example
|
||||
when using a read-only file-system.
|
||||
|
||||
This can also be set to a function; if so, receives a message
|
||||
plist which should evaluate to nil if the message should *not* be
|
||||
marked as read-only, or non-nil otherwise."
|
||||
:type '(choice
|
||||
boolean
|
||||
function)
|
||||
:group 'mu4e-view)
|
||||
|
||||
|
||||
(defcustom mu4e-split-view 'horizontal
|
||||
"How to show messages / headers.
|
||||
A symbol which is either:
|
||||
* `horizontal': split horizontally (headers on top)
|
||||
* `vertical': split vertically (headers on the left).
|
||||
* `single-window': view and headers in one window (mu4e will try not to
|
||||
touch your window layout), main view in minibuffer
|
||||
* anything else: don't split (show either headers or messages,
|
||||
not both)
|
||||
Also see `mu4e-headers-visible-lines'
|
||||
and `mu4e-headers-visible-columns'."
|
||||
:type '(choice (const :tag "Split horizontally" horizontal)
|
||||
(const :tag "Split vertically" vertical)
|
||||
(const :tag "Single window" single-window)
|
||||
(const :tag "Don't split" nil))
|
||||
:group 'mu4e-headers)
|
||||
|
||||
;;; Buffers
|
||||
|
||||
(defconst mu4e-main-buffer-name " *mu4e-main*"
|
||||
"Name of the mu4e main buffer.
|
||||
The default name starts with SPC and therefore is not visible in
|
||||
buffer list.")
|
||||
(defconst mu4e-headers-buffer-name "*mu4e-headers*"
|
||||
"Name of the buffer for message headers.")
|
||||
(defconst mu4e-embedded-buffer-name " *mu4e-embedded*"
|
||||
"Name for the embedded message view buffer.")
|
||||
|
||||
(defun mu4e-get-headers-buffer()
|
||||
"Get the name of the headers buffer."
|
||||
(get-buffer mu4e-headers-buffer-name))
|
||||
|
||||
(defun mu4e-get-view-buffer()
|
||||
"Get the name of the view buffer."
|
||||
;; avoid a 'require.
|
||||
(when (boundp 'gnus-article-buffer) gnus-article-buffer))
|
||||
|
||||
(defun mu4e-select-other-view ()
|
||||
"Switch between headers view and message view."
|
||||
(interactive)
|
||||
(let* ((other-buf
|
||||
(cond
|
||||
((eq major-mode 'mu4e-headers-mode)
|
||||
(mu4e-get-view-buffer))
|
||||
((eq major-mode 'mu4e-view-mode)
|
||||
(mu4e-get-headers-buffer))))
|
||||
(other-win (and other-buf (get-buffer-window other-buf))))
|
||||
(if (window-live-p other-win)
|
||||
(select-window other-win)
|
||||
(mu4e-message "No window to switch to"))))
|
||||
|
||||
|
||||
;;; Windows
|
||||
(defun mu4e-hide-other-mu4e-buffers ()
|
||||
"Bury mu4e buffers.
|
||||
Hide (main, headers, view) (and delete all windows displaying
|
||||
it). Do _not_ bury the current buffer, though."
|
||||
(interactive)
|
||||
(unless (eq mu4e-split-view 'single-window)
|
||||
(let ((curbuf (current-buffer)))
|
||||
;; note: 'walk-windows' does not seem to work correctly when modifying
|
||||
;; windows; therefore, the doloops here
|
||||
(dolist (frame (frame-list))
|
||||
(dolist (win (window-list frame nil))
|
||||
(with-current-buffer (window-buffer win)
|
||||
(unless (eq curbuf (current-buffer))
|
||||
(when (member major-mode '(mu4e-headers-mode mu4e-view-mode))
|
||||
(when (eq t (window-deletable-p win))
|
||||
(delete-window win))))))) t)))
|
||||
|
||||
;;; Modeline
|
||||
|
||||
(defun mu4e-quote-for-modeline (str)
|
||||
"Quote STR to be used literally in the modeline.
|
||||
The string will be shortened to fit if its length exceeds
|
||||
`mu4e-modeline-max-width'."
|
||||
(replace-regexp-in-string
|
||||
"%" "%%"
|
||||
(truncate-string-to-width str mu4e-modeline-max-width 0 nil t)))
|
||||
|
||||
|
||||
|
||||
;;; Messages, warnings and errors
|
||||
(defun mu4e-format (frm &rest args)
|
||||
"Create [mu4e]-prefixed string based on format FRM and ARGS."
|
||||
(concat
|
||||
"[" (propertize "mu4e" 'face 'mu4e-title-face) "] "
|
||||
(apply 'format frm
|
||||
(mapcar (lambda (x)
|
||||
(if (stringp x)
|
||||
(decode-coding-string x 'utf-8)
|
||||
x))
|
||||
args))))
|
||||
|
||||
(defun mu4e-message (frm &rest args)
|
||||
"Display FRM with ARGS like `message' in mu4e style.
|
||||
If we're waiting for user-input or if there's some message in the
|
||||
echo area, don't show anything."
|
||||
(unless (or (active-minibuffer-window))
|
||||
(message "%s" (apply 'mu4e-format frm args))))
|
||||
|
||||
(defun mu4e-error (frm &rest args)
|
||||
"Display an error with FRM and ARGS like `mu4e-message'.
|
||||
|
||||
Create [mu4e]-prefixed error based on format FRM and ARGS. Does a
|
||||
local-exit and does not return, and raises a
|
||||
debuggable (backtrace) error."
|
||||
(mu4e-log 'error (apply 'mu4e-format frm args))
|
||||
(error "%s" (apply 'mu4e-format frm args)))
|
||||
|
||||
(defun mu4e-warn (frm &rest args)
|
||||
"Create [mu4e]-prefixed warning based on format FRM and ARGS.
|
||||
Does a local-exit and does not return."
|
||||
(mu4e-log 'error (apply 'mu4e-format frm args))
|
||||
(user-error "%s" (apply 'mu4e-format frm args)))
|
||||
|
||||
;;; Reading user input
|
||||
|
||||
(defun mu4e--read-char-choice (prompt choices)
|
||||
"Read and return one of CHOICES, prompting for PROMPT.
|
||||
Any input that is not one of CHOICES is ignored. This mu4e's
|
||||
version of `read-char-choice' which becomes case-insentive after
|
||||
trying an exact match."
|
||||
(let ((choice) (chosen) (inhibit-quit nil))
|
||||
(while (not chosen)
|
||||
(message nil);; this seems needed...
|
||||
(setq choice (read-char-exclusive prompt))
|
||||
(if (eq choice 27) (keyboard-quit)) ;; quit if ESC is pressed
|
||||
(setq chosen (or (member choice choices)
|
||||
(member (downcase choice) choices)
|
||||
(member (upcase choice) choices))))
|
||||
(car chosen)))
|
||||
|
||||
(defun mu4e-read-option (prompt options)
|
||||
"Ask user for an option from a list on the input area.
|
||||
PROMPT describes a multiple-choice question to the user.
|
||||
OPTIONS describe the options, and is a list of cells describing
|
||||
particular options. Cells have the following structure:
|
||||
|
||||
(OPTIONSTRING . RESULT)
|
||||
|
||||
where OPTIONSTRING is a non-empty string describing the
|
||||
option. The first character of OPTIONSTRING is used as the
|
||||
shortcut, and obviously all shortcuts must be different, so you
|
||||
can prefix the string with an uniquifying character.
|
||||
|
||||
The options are provided as a list for the user to choose from;
|
||||
user can then choose by typing CHAR. Example:
|
||||
(mu4e-read-option \"Choose an animal: \"
|
||||
'((\"Monkey\" . monkey) (\"Gnu\" . gnu) (\"xMoose\" . moose)))
|
||||
|
||||
User now will be presented with a list: \"Choose an animal:
|
||||
[M]onkey, [G]nu, [x]Moose\".
|
||||
|
||||
Function will return the cdr of the list element."
|
||||
(let* ((prompt (mu4e-format "%s" prompt))
|
||||
(optionsstr
|
||||
(mapconcat
|
||||
(lambda (option)
|
||||
;; try to detect old-style options, and warn
|
||||
(when (characterp (car-safe (cdr-safe option)))
|
||||
(mu4e-error
|
||||
(concat "Please use the new format for options/actions; "
|
||||
"see the manual")))
|
||||
(let ((kar (substring (car option) 0 1)))
|
||||
(concat
|
||||
"[" (propertize kar 'face 'mu4e-highlight-face) "]"
|
||||
(substring (car option) 1))))
|
||||
options ", "))
|
||||
(response
|
||||
(mu4e--read-char-choice
|
||||
(concat prompt optionsstr
|
||||
" [" (propertize "C-g" 'face 'mu4e-highlight-face)
|
||||
" to cancel]")
|
||||
;; the allowable chars
|
||||
(seq-map (lambda(elm) (string-to-char (car elm))) options)))
|
||||
(chosen
|
||||
(seq-find
|
||||
(lambda (option) (eq response (string-to-char (car option))))
|
||||
options)))
|
||||
(if chosen
|
||||
(cdr chosen)
|
||||
(mu4e-warn "Unknown shortcut '%c'" response))))
|
||||
|
||||
|
||||
|
||||
;;; Server properties
|
||||
(defvar mu4e--server-props nil
|
||||
"Metadata we receive from the mu4e server.")
|
||||
|
||||
(defun mu4e-server-properties ()
|
||||
"Get the server metadata plist."
|
||||
mu4e--server-props)
|
||||
|
||||
(defun mu4e-root-maildir()
|
||||
"Get the root maildir."
|
||||
(or (and mu4e--server-props
|
||||
(plist-get mu4e--server-props :root-maildir))
|
||||
(mu4e-error "Root maildir unknown; did you start mu4e?")))
|
||||
|
||||
(defun mu4e-database-path()
|
||||
"Get the root maildir."
|
||||
(or (and mu4e--server-props
|
||||
(plist-get mu4e--server-props :database-path))
|
||||
(mu4e-error "Root maildir unknown; did you start mu4e?")))
|
||||
|
||||
(defun mu4e-server-version()
|
||||
"Get the root maildir."
|
||||
(or (and mu4e--server-props
|
||||
(plist-get mu4e--server-props :version))
|
||||
(mu4e-error "Version unknown; did you start mu4e?")))
|
||||
|
||||
(defun mu4e-last-query-results ()
|
||||
"Get the results (counts) of the last cached queries.
|
||||
|
||||
The cached queries are the bookmark / maildir queries that are
|
||||
used to populated the read/unread counts in the main view. They
|
||||
are refreshed when calling `(mu4e)', i.e., when going to the main
|
||||
view.
|
||||
|
||||
The results are a list of elements of the form
|
||||
(:query \"query string\"
|
||||
:count <total number matching count>
|
||||
:unread <number of unread messages in count>)"
|
||||
(plist-get mu4e--server-props :queries))
|
||||
|
||||
(defun mu4e-last-query-result (query)
|
||||
"Get the last result for some QUERY or nil if not found."
|
||||
(seq-find
|
||||
(lambda (elm) (string= (plist-get elm :query) query))
|
||||
(mu4e-last-query-results)))
|
||||
|
||||
|
||||
;;; Logging / debugging
|
||||
|
||||
(defconst mu4e--log-max-size 1000000
|
||||
"Max number of characters to keep around in the log buffer.")
|
||||
(defconst mu4e--log-buffer-name "*mu4e-log*"
|
||||
"Name of the logging buffer.")
|
||||
|
||||
(defun mu4e--get-log-buffer ()
|
||||
"Fetch (and maybe create) the log buffer."
|
||||
(unless (get-buffer mu4e--log-buffer-name)
|
||||
(with-current-buffer (get-buffer-create mu4e--log-buffer-name)
|
||||
(view-mode)
|
||||
(when (fboundp 'so-long-mode)
|
||||
(unless (eq major-mode 'so-long-mode)
|
||||
(eval '(so-long-mode))))
|
||||
(setq buffer-undo-list t)))
|
||||
mu4e--log-buffer-name)
|
||||
|
||||
(defun mu4e-log (type frm &rest args)
|
||||
"Log a message of TYPE with format-string FRM and ARGS.
|
||||
Use the mu4e log buffer for this. If the variable mu4e-debug is
|
||||
non-nil. Type is either 'to-server, 'from-server or 'misc. This
|
||||
function is meant for debugging."
|
||||
(when mu4e-debug
|
||||
(with-current-buffer (mu4e--get-log-buffer)
|
||||
(let* ((inhibit-read-only t)
|
||||
(tstamp (propertize (format-time-string "%Y-%m-%d %T.%3N"
|
||||
(current-time))
|
||||
'face 'font-lock-string-face))
|
||||
(msg-face
|
||||
(cl-case type
|
||||
(from-server 'font-lock-type-face)
|
||||
(to-server 'font-lock-function-name-face)
|
||||
(misc 'font-lock-variable-name-face)
|
||||
(error 'font-lock-warning-face)
|
||||
(otherwise (mu4e-error "Unsupported log type"))))
|
||||
(msg (propertize (apply 'format frm args) 'face msg-face)))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(insert tstamp
|
||||
(cl-case type
|
||||
(from-server " <- ")
|
||||
(to-server " -> ")
|
||||
(error " !! ")
|
||||
(otherwise " "))
|
||||
msg "\n")
|
||||
;; if `mu4e-log-max-lines is specified and exceeded, clearest the
|
||||
;; oldest lines
|
||||
(when (> (buffer-size) mu4e--log-max-size)
|
||||
(goto-char (- (buffer-size) mu4e--log-max-size))
|
||||
(beginning-of-line)
|
||||
(delete-region (point-min) (point))))))))
|
||||
|
||||
(defun mu4e-toggle-logging ()
|
||||
"Toggle `mu4e-debug'.
|
||||
In debug-mode, mu4e logs some of its internal workings to a
|
||||
log-buffer. See `mu4e-show-log'."
|
||||
(interactive)
|
||||
(mu4e-log 'misc "logging disabled")
|
||||
(setq mu4e-debug (not mu4e-debug))
|
||||
(mu4e-message "debug logging has been %s"
|
||||
(if mu4e-debug "enabled" "disabled"))
|
||||
(mu4e-log 'misc "logging enabled"))
|
||||
|
||||
(defun mu4e-show-log ()
|
||||
"Visit the mu4e debug log."
|
||||
(interactive)
|
||||
(unless mu4e-debug (mu4e-toggle-logging))
|
||||
(let ((buf (get-buffer mu4e--log-buffer-name)))
|
||||
(unless (buffer-live-p buf)
|
||||
(mu4e-warn "No debug log available"))
|
||||
(switch-to-buffer buf)))
|
||||
|
||||
|
||||
|
||||
;;; Flags
|
||||
;; Converting flags->string and vice-versa
|
||||
|
||||
(defun mu4e-flags-to-string (flags)
|
||||
"Convert a list of Maildir[1] FLAGS into a string.
|
||||
|
||||
See `mu4e-string-to-flags'. \[1\]:
|
||||
http://cr.yp.to/proto/maildir.html."
|
||||
(seq-sort
|
||||
'<
|
||||
(seq-mapcat
|
||||
(lambda (flag)
|
||||
(pcase flag
|
||||
(`draft "D")
|
||||
(`flagged "F")
|
||||
(`new "N")
|
||||
(`passed "P")
|
||||
(`replied "R")
|
||||
(`seen "S")
|
||||
(`trashed "T")
|
||||
(`attach "a")
|
||||
(`encrypted "x")
|
||||
(`signed "s")
|
||||
(`unread "u")
|
||||
(_ "")))
|
||||
(seq-uniq flags) 'string)))
|
||||
|
||||
(defun mu4e-string-to-flags (str)
|
||||
"Convert a STR with Maildir[1] flags into a list of flags.
|
||||
|
||||
See `mu4e-string-to-flags'. \[1\]:
|
||||
http://cr.yp.to/proto/maildir.html."
|
||||
(seq-uniq
|
||||
(seq-filter
|
||||
'identity
|
||||
(seq-mapcat
|
||||
(lambda (kar)
|
||||
(list
|
||||
(pcase kar
|
||||
('?D 'draft)
|
||||
('?F 'flagged)
|
||||
('?P 'passed)
|
||||
('?R 'replied)
|
||||
('?S 'seen)
|
||||
('?T 'trashed)
|
||||
(_ nil))))
|
||||
str))))
|
||||
|
||||
|
||||
;;; Misc
|
||||
(defun mu4e-display-size (size)
|
||||
"Get a human-friendly string representation of SIZE (in bytes)."
|
||||
(cond
|
||||
((>= size 1000000)
|
||||
(format "%2.1fM" (/ size 1000000.0)))
|
||||
((and (>= size 1000) (< size 1000000))
|
||||
(format "%2.1fK" (/ size 1000.0)))
|
||||
((< size 1000)
|
||||
(format "%d" size))
|
||||
(t "?")))
|
||||
|
||||
|
||||
(defun mu4e-split-ranges-to-numbers (str n)
|
||||
"Convert STR containing attachment numbers into a list of numbers.
|
||||
|
||||
STR is a string; N is the highest possible number in the list.
|
||||
This includes expanding e.g. 3-5 into 3,4,5. If the letter
|
||||
\"a\" ('all')) is given, that is expanded to a list with numbers
|
||||
[1..n]."
|
||||
(let ((str-split (split-string str))
|
||||
beg end list)
|
||||
(dolist (elem str-split list)
|
||||
;; special number "a" converts into all attachments 1-N.
|
||||
(when (equal elem "a")
|
||||
(setq elem (concat "1-" (int-to-string n))))
|
||||
(if (string-match "\\([0-9]+\\)-\\([0-9]+\\)" elem)
|
||||
;; we have found a range A-B, which needs converting
|
||||
;; into the numbers A, A+1, A+2, ... B.
|
||||
(progn
|
||||
(setq beg (string-to-number (match-string 1 elem))
|
||||
end (string-to-number (match-string 2 elem)))
|
||||
(while (<= beg end)
|
||||
(cl-pushnew beg list :test 'equal)
|
||||
(setq beg (1+ beg))))
|
||||
;; else just a number
|
||||
(cl-pushnew (string-to-number elem) list :test 'equal)))
|
||||
;; Check that all numbers are valid.
|
||||
(mapc
|
||||
(lambda (x)
|
||||
(cond
|
||||
((> x n)
|
||||
(mu4e-warn "Attachment %d bigger than maximum (%d)" x n))
|
||||
((< x 1)
|
||||
(mu4e-warn "Attachment number must be greater than 0 (%d)" x))))
|
||||
list)))
|
||||
|
||||
(defun mu4e-make-temp-file (ext)
|
||||
"Create a self-destructing temporary file with extension EXT.
|
||||
The file will self-destruct in a short while, enough to open it
|
||||
in an external program."
|
||||
(let ((tmpfile (make-temp-file "mu4e-" nil (concat "." ext))))
|
||||
(run-at-time "30 sec" nil
|
||||
(lambda () (ignore-errors (delete-file tmpfile))))
|
||||
tmpfile))
|
||||
|
||||
(defun mu4e-display-manual ()
|
||||
"Display the mu4e manual page for the current mode.
|
||||
Or go to the top level if there is none."
|
||||
(interactive)
|
||||
(info (cl-case major-mode
|
||||
('mu4e-main-mode "(mu4e)Main view")
|
||||
('mu4e-headers-mode "(mu4e)Headers view")
|
||||
('mu4e-view-mode "(mu4e)Message view")
|
||||
(t "mu4e"))))
|
||||
|
||||
;;; Macros
|
||||
|
||||
(defmacro mu4e-setq-if-nil (var val)
|
||||
"Set VAR to VAL if VAR is nil."
|
||||
`(unless ,var (setq ,var ,val)))
|
||||
|
||||
(provide 'mu4e-helpers)
|
||||
;;; mu4e-helpers.el ends here
|
||||
|
|
@ -53,31 +53,14 @@
|
|||
(require 'cl-lib)
|
||||
|
||||
(require 'mu4e-mark)
|
||||
(require 'mu4e-helpers)
|
||||
(require 'mu4e-contacts)
|
||||
(require 'mu4e-utils)
|
||||
(require 'mu4e-headers)
|
||||
(require 'mu4e-view)
|
||||
(require 'mu4e-vars)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
;;;; Calendar
|
||||
(when mu4e-view-use-old
|
||||
(mu4e-error "iCalender support is not available with the old viewer"))
|
||||
|
||||
(defgroup mu4e-icalendar nil
|
||||
"Icalendar related settings."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-icalendar-trash-after-reply nil
|
||||
"If non-nil, trash the icalendar invitation after replying."
|
||||
:type 'boolean
|
||||
:group 'mu4e-icalendar)
|
||||
|
||||
(defcustom mu4e-icalendar-diary-file nil
|
||||
"If non-nil, the file in which to add events upon reply."
|
||||
:type '(choice (const :tag "Do not insert a diary entry" nil)
|
||||
(string :tag "Insert a diary entry in this file"))
|
||||
:group 'mu4e-icalendar)
|
||||
|
||||
|
||||
;;;###autoload
|
||||
(defun mu4e-icalendar-setup ()
|
||||
"Perform the necessary initialization to use mu4e-icalendar."
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
;;; mu4e-lists.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
;;; mu4e-lists.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
|
||||
;; Copyright (C) 2011-2016 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
@ -27,10 +27,7 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
;;; Configuration
|
||||
(defvar mu4e-mailing-lists
|
||||
(defvar mu4e~mailing-lists
|
||||
'( ("bbdb-info.lists.sourceforge.net" . "BBDB")
|
||||
("boost-announce.lists.boost.org" . "BoostA")
|
||||
("boost-interest.lists.boost.org" . "BoostI")
|
||||
|
@ -84,50 +81,22 @@
|
|||
("wl-en.ml.gentei.org" . "WdrLust")
|
||||
("xapian-devel.lists.xapian.org" . "Xapian")
|
||||
("zsh-users.zsh.org" . "ZshUsr"))
|
||||
"AList of cells (MAILING-LIST-ID . SHORTNAME).")
|
||||
"AList of cells (MAILING-LIST-ID . SHORTNAME)")
|
||||
|
||||
(defcustom mu4e-user-mailing-lists nil
|
||||
"An alist with cells (MAILING-LIST-ID . SHORTNAME).
|
||||
These are used in addition to the built-in list `mu4e~mailing-lists'."
|
||||
"An alist with cells (MAILING-LIST-ID . SHORTNAME); these are
|
||||
used in addition to the built-in list `mu4e~mailing-lists'."
|
||||
:group 'mu4e-headers
|
||||
:type '(repeat (cons string string)))
|
||||
|
||||
|
||||
(defcustom mu4e-mailing-list-patterns nil
|
||||
"A list of regexps to capture a shortname out of a list-id.
|
||||
For the first regex that matches, its first matchgroup will be
|
||||
used as the shortname."
|
||||
"A list of regex patterns to capture a shortname out of a list
|
||||
ID. For the first regex that matches, its first matchgroup will
|
||||
be used as the shortname."
|
||||
:group 'mu4e-headers
|
||||
:type '(repeat (regexp)))
|
||||
|
||||
|
||||
(defvar mu4e--lists-hash nil
|
||||
"Hashtable of mailing-list-id => shortname.
|
||||
Based on `mu4e-mailing-lists' and `mu4e-user-mailing-lists'.")
|
||||
|
||||
|
||||
(defun mu4e-get-mailing-list-shortname (list-id)
|
||||
"Get the shortname for a mailing-list with list-id LIST-ID.
|
||||
Based on `mu4e-mailing-lists', `mu4e-user-mailing-lists', and
|
||||
`mu4e-mailing-list-patterns'."
|
||||
(unless mu4e--lists-hash
|
||||
(setq mu4e--lists-hash (make-hash-table :test 'equal))
|
||||
(dolist (cell mu4e-mailing-lists)
|
||||
(puthash (car cell) (cdr cell) mu4e--lists-hash))
|
||||
(dolist (cell mu4e-user-mailing-lists)
|
||||
(puthash (car cell) (cdr cell) mu4e--lists-hash)))
|
||||
(or
|
||||
(gethash list-id mu4e--lists-hash)
|
||||
(and (boundp 'mu4e-mailing-list-patterns)
|
||||
(cl-member-if
|
||||
(lambda (pattern)
|
||||
(string-match pattern list-id))
|
||||
mu4e-mailing-list-patterns)
|
||||
(match-string 1 list-id))
|
||||
;; if it's not in the db, take the part until the first dot if there is one;
|
||||
;; otherwise just return the whole thing
|
||||
(if (string-match "\\([^.]*\\)\\." list-id)
|
||||
(match-string 1 list-id)
|
||||
list-id)))
|
||||
;;; _
|
||||
(provide 'mu4e-lists)
|
||||
;;; mu4e-lists.el ends here
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
;;; mu4e-main.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
|
||||
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
@ -25,20 +25,12 @@
|
|||
;;; Code:
|
||||
|
||||
(require 'smtpmail) ;; the queueing stuff (silence elint)
|
||||
(require 'mu4e-helpers) ;; utility functions
|
||||
(require 'mu4e-utils) ;; utility functions
|
||||
(require 'mu4e-context) ;; the context
|
||||
(require 'mu4e-bookmarks)
|
||||
(require 'mu4e-folders)
|
||||
(require 'mu4e-update)
|
||||
(require 'mu4e-contacts)
|
||||
(require 'mu4e-search)
|
||||
(require 'mu4e-vars) ;; mu-wide variables
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
|
||||
|
||||
;; Configuration
|
||||
;;; Mode
|
||||
|
||||
(define-obsolete-variable-alias
|
||||
'mu4e-main-buffer-hide-personal-addresses
|
||||
|
@ -51,44 +43,19 @@
|
|||
This also hides the warning if your `user-mail-address' is not
|
||||
part of the personal addresses.")
|
||||
|
||||
|
||||
|
||||
(defvar mu4e-main-hide-fully-read nil
|
||||
"When set to t, do not hide bookmarks or maildirs that have
|
||||
no unread messages.")
|
||||
|
||||
|
||||
;;; Mode
|
||||
(define-derived-mode mu4e-org-mode org-mode "mu4e:org"
|
||||
"Major mode for mu4e documents, derived from
|
||||
`org-mode'.")
|
||||
|
||||
(defun mu4e-info (path)
|
||||
"Show a buffer with the information (an org-file) at PATH."
|
||||
(unless (file-exists-p path)
|
||||
(mu4e-error "Cannot find %s" path))
|
||||
(let ((curbuf (current-buffer)))
|
||||
(find-file path)
|
||||
(mu4e-org-mode)
|
||||
(setq buffer-read-only t)
|
||||
(define-key mu4e-org-mode-map (kbd "q")
|
||||
`(lambda ()
|
||||
(interactive)
|
||||
(bury-buffer)
|
||||
(switch-to-buffer ,curbuf)))))
|
||||
|
||||
(defun mu4e-about ()
|
||||
"Show the mu4e 'about' page."
|
||||
(interactive)
|
||||
(mu4e-info (concat mu4e-doc-dir "/mu4e-about.org")))
|
||||
|
||||
(defun mu4e-news ()
|
||||
"Show the mu4e 'about' page."
|
||||
(interactive)
|
||||
(mu4e-info (concat mu4e-doc-dir "/NEWS.org")))
|
||||
|
||||
|
||||
(defvar mu4e-main-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
|
||||
(define-key map "b" 'mu4e-headers-search-bookmark)
|
||||
(define-key map "B" 'mu4e-headers-search-bookmark-edit)
|
||||
|
||||
(define-key map "s" 'mu4e-headers-search)
|
||||
(define-key map "q" 'mu4e-quit)
|
||||
(define-key map "j" 'mu4e~headers-jump-to-maildir)
|
||||
(define-key map "C" 'mu4e-compose-new)
|
||||
|
@ -120,9 +87,7 @@ no unread messages.")
|
|||
\\{mu4e-main-mode-map}."
|
||||
(setq truncate-lines t
|
||||
overwrite-mode 'overwrite-mode-binary)
|
||||
(mu4e-context-minor-mode)
|
||||
(mu4e-search-minor-mode)
|
||||
(mu4e-update-minor-mode)
|
||||
(mu4e-context-in-modeline)
|
||||
(set (make-local-variable 'revert-buffer-function) #'mu4e~main-view-real))
|
||||
|
||||
|
||||
|
@ -159,23 +124,15 @@ clicked."
|
|||
'mouse-face 'highlight newstr)
|
||||
newstr))
|
||||
|
||||
|
||||
|
||||
(defun mu4e--longest-of-maildirs-and-bookmarks ()
|
||||
"Return the length of longest name of bookmarks and maildirs."
|
||||
(cl-loop for b in (append (mu4e-bookmarks)
|
||||
(mu4e--maildirs-with-query))
|
||||
maximize (string-width (plist-get b :name))))
|
||||
|
||||
(defun mu4e~main-bookmarks ()
|
||||
;; TODO: it's a bit uncool to hard-code the "b" shortcut...
|
||||
(cl-loop with bmks = (mu4e-bookmarks)
|
||||
with longest = (mu4e--longest-of-maildirs-and-bookmarks)
|
||||
with longest = (mu4e~longest-of-maildirs-and-bookmarks)
|
||||
with queries = (mu4e-last-query-results)
|
||||
for bm in bmks
|
||||
for key = (string (plist-get bm :key))
|
||||
for name = (plist-get bm :name)
|
||||
for query = (funcall (or mu4e-search-query-rewrite-function #'identity)
|
||||
for query = (funcall (or mu4e-query-rewrite-function #'identity)
|
||||
(plist-get bm :query))
|
||||
for qcounts = (and (stringp query)
|
||||
(cl-loop for q in queries
|
||||
|
@ -208,9 +165,9 @@ clicked."
|
|||
|
||||
(defun mu4e~main-maildirs ()
|
||||
"Return a string of maildirs with their counts."
|
||||
(cl-loop with mds = (mu4e--maildirs-with-query)
|
||||
with longest = (mu4e--longest-of-maildirs-and-bookmarks)
|
||||
with queries = (plist-get mu4e--server-props :queries)
|
||||
(cl-loop with mds = (mu4e~maildirs-with-query)
|
||||
with longest = (mu4e~longest-of-maildirs-and-bookmarks)
|
||||
with queries = (plist-get mu4e~server-props :queries)
|
||||
for m in mds
|
||||
for key = (string (plist-get m :key))
|
||||
for name = (plist-get m :name)
|
||||
|
@ -263,15 +220,13 @@ clicked."
|
|||
"The revert buffer function for `mu4e-main-mode'."
|
||||
(mu4e~main-view-real-1 'refresh))
|
||||
|
||||
(declare-function mu4e--start "mu4e")
|
||||
|
||||
(defun mu4e~main-view-real-1 (&optional refresh)
|
||||
"Create `mu4e-main-buffer-name' and set it up.
|
||||
When REFRESH is non nil refresh infos from server."
|
||||
(let ((inhibit-read-only t))
|
||||
;; Maybe refresh infos from server.
|
||||
(if refresh
|
||||
(mu4e--start 'mu4e~main-redraw-buffer)
|
||||
(mu4e~start 'mu4e~main-redraw-buffer)
|
||||
(mu4e~main-redraw-buffer))))
|
||||
|
||||
(defun mu4e~main-redraw-buffer ()
|
||||
|
@ -323,7 +278,7 @@ When REFRESH is non nil refresh infos from server."
|
|||
(mu4e~key-val "database-path" (mu4e-database-path))
|
||||
(mu4e~key-val "maildir" (mu4e-root-maildir))
|
||||
(mu4e~key-val "in store"
|
||||
(format "%d" (plist-get mu4e--server-props :doccount)) "messages")
|
||||
(format "%d" (plist-get mu4e~server-props :doccount)) "messages")
|
||||
(if mu4e-main-hide-personal-addresses ""
|
||||
(mu4e~key-val "personal addresses" (if addrs (mapconcat #'identity addrs ", " ) "none"))))
|
||||
|
||||
|
|
|
@ -28,10 +28,9 @@
|
|||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-server)
|
||||
(require 'mu4e-proc)
|
||||
(require 'mu4e-utils)
|
||||
(require 'mu4e-message)
|
||||
(require 'mu4e-folders)
|
||||
|
||||
;; keep byte-compiler happy
|
||||
(declare-function mu4e~headers-mark "mu4e-headers")
|
||||
|
@ -133,50 +132,50 @@ The current buffer must be either a headers or view buffer."
|
|||
:prompt "refile"
|
||||
:dyn-target (lambda (target msg) (mu4e-get-refile-folder msg))
|
||||
:action (lambda (docid msg target)
|
||||
(mu4e--server-move docid (mu4e~mark-check-target target) "-N")))
|
||||
(mu4e~proc-move docid (mu4e~mark-check-target target) "-N")))
|
||||
(delete
|
||||
:char ("D" . "x")
|
||||
:prompt "Delete"
|
||||
:show-target (lambda (target) "delete")
|
||||
:action (lambda (docid msg target) (mu4e--server-remove docid)))
|
||||
:action (lambda (docid msg target) (mu4e~proc-remove docid)))
|
||||
(flag
|
||||
:char ("+" . "✚")
|
||||
:prompt "+flag"
|
||||
:show-target (lambda (target) "flag")
|
||||
:action (lambda (docid msg target)
|
||||
(mu4e--server-move docid nil "+F-u-N")))
|
||||
(mu4e~proc-move docid nil "+F-u-N")))
|
||||
(move
|
||||
:char ("m" . "▷")
|
||||
:prompt "move"
|
||||
:ask-target mu4e~mark-get-move-target
|
||||
:action (lambda (docid msg target)
|
||||
(mu4e--server-move docid (mu4e~mark-check-target target) "-N")))
|
||||
(mu4e~proc-move docid (mu4e~mark-check-target target) "-N")))
|
||||
(read
|
||||
:char ("!" . "◼")
|
||||
:prompt "!read"
|
||||
:show-target (lambda (target) "read")
|
||||
:action (lambda (docid msg target) (mu4e--server-move docid nil "+S-u-N")))
|
||||
:action (lambda (docid msg target) (mu4e~proc-move docid nil "+S-u-N")))
|
||||
(trash
|
||||
:char ("d" . "▼")
|
||||
:prompt "dtrash"
|
||||
:dyn-target (lambda (target msg) (mu4e-get-trash-folder msg))
|
||||
:action (lambda (docid msg target) (mu4e--server-move docid
|
||||
:action (lambda (docid msg target) (mu4e~proc-move docid
|
||||
(mu4e~mark-check-target target) "+T-N")))
|
||||
(unflag
|
||||
:char ("-" . "➖")
|
||||
:prompt "-unflag"
|
||||
:show-target (lambda (target) "unflag")
|
||||
:action (lambda (docid msg target) (mu4e--server-move docid nil "-F-N")))
|
||||
:action (lambda (docid msg target) (mu4e~proc-move docid nil "-F-N")))
|
||||
(untrash
|
||||
:char ("=" . "▲")
|
||||
:prompt "=untrash"
|
||||
:show-target (lambda (target) "untrash")
|
||||
:action (lambda (docid msg target) (mu4e--server-move docid nil "-T")))
|
||||
:action (lambda (docid msg target) (mu4e~proc-move docid nil "-T")))
|
||||
(unread
|
||||
:char ("?" . "◻")
|
||||
:prompt "?unread"
|
||||
:show-target (lambda (target) "unread")
|
||||
:action (lambda (docid msg target) (mu4e--server-move docid nil "-S+u-N")))
|
||||
:action (lambda (docid msg target) (mu4e~proc-move docid nil "-S+u-N")))
|
||||
(unmark
|
||||
:char " "
|
||||
:prompt "unmark"
|
||||
|
@ -299,7 +298,7 @@ The following marks are available, and the corresponding props:
|
|||
(when (or (file-directory-p fulltarget)
|
||||
(and (yes-or-no-p
|
||||
(format "%s does not exist. Create now?" fulltarget))
|
||||
(mu4e--server-mkdir fulltarget)))
|
||||
(mu4e~proc-mkdir fulltarget)))
|
||||
target)))
|
||||
|
||||
(defun mu4e~mark-ask-target (mark)
|
||||
|
|
|
@ -38,14 +38,52 @@
|
|||
|
||||
(defvar mu4e~view-message)
|
||||
(defvar shr-inhibit-images)
|
||||
|
||||
(make-obsolete-variable 'mu4e-html2text-command "No longer in use" "1.7.0")
|
||||
(make-obsolete-variable 'mu4e-view-prefer-html "No longer in use" "1.7.0")
|
||||
(make-obsolete-variable 'mu4e-view-html-plaintext-ratio-heuristic
|
||||
"No longer in use" "1.7.0")
|
||||
(make-obsolete-variable 'mu4e-message-body-rewrite-functions
|
||||
"No longer in use" "1.7.0")
|
||||
|
||||
|
||||
(defcustom mu4e-html2text-command 'mu4e-shr2text
|
||||
"Either a shell command or a function that converts from html to plain text.
|
||||
|
||||
If it is a shell command, the command reads html from standard
|
||||
input and outputs plain text on standard output. If you use the
|
||||
htmltext program, it's recommended you use \"html2text -utf8
|
||||
-width 72\". Alternatives are the python-based html2markdown, w3m
|
||||
and on MacOS you may want to use textutil.
|
||||
|
||||
It can also be a function, which takes a messsage-plist as
|
||||
argument and is expected to return the textified html as output.
|
||||
|
||||
For backward compatibility, it can also be a parameterless
|
||||
function which is run in the context of a buffer with the html
|
||||
and expected to transform this (like the `html2text' function).
|
||||
|
||||
In all cases, the output is expected to be in UTF-8 encoding.
|
||||
|
||||
The default is to use the shr renderer."
|
||||
:type '(choice string function)
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-prefer-html nil
|
||||
"Whether to base the body display on the html-version.
|
||||
If the e-mail message has no html-version the plain-text version
|
||||
is always used."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-html-plaintext-ratio-heuristic 5
|
||||
"Ratio between the length of the html and the plain text part.
|
||||
Below this ratio mu4e will consider the plain text part to be
|
||||
'This messages requires html' text bodies. You can neutralize
|
||||
it (always show the text version) by using
|
||||
`most-positive-fixnum'."
|
||||
:type 'integer
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defvar mu4e-message-body-rewrite-functions '(mu4e-message-outlook-cleanup)
|
||||
"List of functions to transform the message body text.
|
||||
The functions take two parameters, MSG and TXT, which are the
|
||||
message-plist and the text, which is the plain-text version,
|
||||
ossibly converted from html and/or transformed by earlier rewrite
|
||||
functions.")
|
||||
|
||||
;;; Message fields
|
||||
|
||||
(defsubst mu4e-message-field-raw (msg field)
|
||||
|
@ -131,11 +169,89 @@ This is equivalent to:
|
|||
(mu4e-message-field (mu4e-message-at-point) FIELD)."
|
||||
(mu4e-message-field (mu4e-message-at-point) field))
|
||||
|
||||
(defun mu4e-message-body-text (_msg &optional _prefer-html)
|
||||
"Get the body in text form for message MSG."
|
||||
"" ;; not implemented for Gnus mode.
|
||||
)
|
||||
|
||||
(defvar mu4e~message-body-html nil
|
||||
"Whether the body text uses HTML.")
|
||||
|
||||
(defun mu4e~message-use-html-p (msg prefer-html)
|
||||
"Do we want to PREFER-HTML for MSG?
|
||||
Determine whether we want
|
||||
to use html or text. The decision is based on PREFER-HTML and
|
||||
whether the message supports the given representation."
|
||||
(let* ((txt (mu4e-message-field msg :body-txt))
|
||||
(html (mu4e-message-field msg :body-html))
|
||||
(txt-len (length txt))
|
||||
(html-len (length html))
|
||||
(txt-limit (* mu4e-view-html-plaintext-ratio-heuristic txt-len))
|
||||
(txt-limit (if (>= txt-limit 0) txt-limit most-positive-fixnum)))
|
||||
(cond
|
||||
; user prefers html --> use html if there is
|
||||
(prefer-html (> html-len 0))
|
||||
;; otherwise (user prefers text) still use html if there is not enough
|
||||
;; text
|
||||
((< txt-limit html-len) t)
|
||||
;; otherwise, use text
|
||||
(t nil))))
|
||||
|
||||
(defun mu4e~message-body-has-content-type-param (msg param)
|
||||
"Does the MSG have a content-type parameter PARAM?"
|
||||
(cdr
|
||||
(assoc param (mu4e-message-field msg :body-txt-params))))
|
||||
|
||||
(defun mu4e~safe-iequal (a b)
|
||||
"Is string A equal to a downcased B?"
|
||||
(and b (equal (downcase b) a)))
|
||||
|
||||
(defun mu4e-message-body-text (msg &optional prefer-html)
|
||||
"Get the body in text form for message MSG.
|
||||
This is either :body-txt, or if not available, :body-html
|
||||
converted to text, using `mu4e-html2text-command' is non-nil, it
|
||||
will use that. Normally, this function prefers the text part,
|
||||
unless PREFER-HTML is non-nil."
|
||||
(setq mu4e~message-body-html (mu4e~message-use-html-p msg prefer-html))
|
||||
(let ((body
|
||||
(if mu4e~message-body-html
|
||||
;; use an htmml body
|
||||
(cond
|
||||
((stringp mu4e-html2text-command)
|
||||
(mu4e~html2text-shell msg mu4e-html2text-command))
|
||||
((functionp mu4e-html2text-command)
|
||||
(if (help-function-arglist mu4e-html2text-command)
|
||||
(funcall mu4e-html2text-command msg)
|
||||
;; oldskool parameterless mu4e-html2text-command
|
||||
(mu4e~html2text-wrapper mu4e-html2text-command msg)))
|
||||
(t (mu4e-error "Invalid `mu4e-html2text-command'")))
|
||||
;; use a text body
|
||||
(or (with-temp-buffer
|
||||
(insert (or (mu4e-message-field msg :body-txt) ""))
|
||||
(if (mu4e~safe-iequal "flowed"
|
||||
(mu4e~message-body-has-content-type-param
|
||||
msg "format"))
|
||||
(fill-flowed nil
|
||||
(mu4e~safe-iequal
|
||||
"yes"
|
||||
(mu4e~message-body-has-content-type-param
|
||||
msg "delsp"))))
|
||||
(buffer-string)) ""))))
|
||||
(dolist (func mu4e-message-body-rewrite-functions)
|
||||
(setq body (funcall func msg body)))
|
||||
body))
|
||||
|
||||
(defun mu4e-message-outlook-cleanup (_msg body)
|
||||
"Clean-up MSG's BODY.
|
||||
Esp. MS-Outlook-originating message may not advertise the correct
|
||||
encoding (e.g. 'iso-8859-1' instead of 'windows-1252'), thus
|
||||
giving us these funky chars. here, we either remove them, or
|
||||
replace with."
|
||||
(with-temp-buffer
|
||||
(insert body)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\015 ]" nil t)
|
||||
(replace-match
|
||||
(cond
|
||||
((string= (match-string 0) "") "'")
|
||||
((string= (match-string 0) " ") " ")
|
||||
(t ""))))
|
||||
(buffer-string)))
|
||||
|
||||
(defun mu4e-message-contact-field-matches (msg cfield rx)
|
||||
"Does MSG's contact-field CFIELD match rx?
|
||||
|
@ -168,8 +284,7 @@ expressions, in which case any of those are tried for a match."
|
|||
(mu4e-message-field msg cfield))))))
|
||||
|
||||
(defun mu4e-message-contact-field-matches-me (msg cfield)
|
||||
"Does contact-field CFIELD in MSG match me?
|
||||
Checks whether any
|
||||
"Does contact-field CFIELD in MSG match me? Checks whether any
|
||||
of the of the contacts in field CFIELD (either :to, :from, :cc or
|
||||
:bcc) of msg MSG matches *me*, that is, any of the addresses for
|
||||
which `mu4e-personal-address-p' return t. Returns the contact
|
||||
|
@ -178,20 +293,19 @@ cell that matched, or nil."
|
|||
(mu4e-message-field msg cfield)))
|
||||
|
||||
(defun mu4e-message-sent-by-me (msg)
|
||||
"Is this MSG (to be) sent by me?
|
||||
"Is this message (to be) sent by me?
|
||||
Checks if the from field matches user's personal addresses."
|
||||
(mu4e-message-contact-field-matches-me msg :from))
|
||||
|
||||
(defun mu4e-message-personal-p (msg)
|
||||
"Does MSG have user's personal address?
|
||||
In any of the contact
|
||||
fields?"
|
||||
"Does message have user's personal address in any of the
|
||||
contact fields?"
|
||||
(cl-some
|
||||
(lambda (field)
|
||||
(mu4e-message-contact-field-matches-me msg field))
|
||||
'(:from :to :cc :bcc)))
|
||||
|
||||
(defsubst mu4e-message-part-field (msgpart field)
|
||||
(defsubst mu4e-message-part-field (msgpart field)
|
||||
"Get some FIELD from MSGPART.
|
||||
A part would look something like:
|
||||
(:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)."
|
||||
|
@ -208,14 +322,41 @@ symbol, see `mu4e-header-info'."
|
|||
(plist-get (mu4e-message-at-point) field))
|
||||
|
||||
;;; Html2Text
|
||||
(make-obsolete 'mu4e-shr2text "No longer in use" "1.7.0")
|
||||
|
||||
(defun mu4e-copy-message-path ()
|
||||
"Copy the message-path of message at point to the kill ring."
|
||||
(interactive)
|
||||
(let ((path (mu4e-message-field-at-point :path)))
|
||||
(kill-new path)
|
||||
(mu4e-message "Saved '%s' to kill-ring" path)))
|
||||
(defun mu4e~html2text-wrapper (func msg)
|
||||
"Apply FUNC on a temporary buffer with html from MSG.
|
||||
Return the buffer contents."
|
||||
(with-temp-buffer
|
||||
(insert (or (mu4e-message-field msg :body-html) ""))
|
||||
(funcall func)
|
||||
(or (buffer-string) "")))
|
||||
|
||||
(defun mu4e-shr2text (msg)
|
||||
"Convert html in MSG to text using the shr engine.
|
||||
This can be used in `mu4e-html2text-command' in a new enough
|
||||
Emacs. Based on code by Titus von der Malsburg."
|
||||
(mu4e~html2text-wrapper
|
||||
(lambda ()
|
||||
(let (
|
||||
;; When HTML emails contain references to remote images,
|
||||
;; retrieving these images leaks information. For example,
|
||||
;; the sender can see when I opened the email and from which
|
||||
;; computer (IP address). For this reason, it is preferable
|
||||
;; to not retrieve images.
|
||||
;; See this discussion on mu-discuss:
|
||||
;; https://groups.google.com/forum/#!topic/mu-discuss/gr1cwNNZnXo
|
||||
(shr-inhibit-images t))
|
||||
(shr-render-region (point-min) (point-max)))) msg))
|
||||
|
||||
(defun mu4e~html2text-shell (msg _cmd)
|
||||
"Convert html2 text in MSG using a shell function CMD."
|
||||
(mu4e~html2text-wrapper
|
||||
(lambda ()
|
||||
(let* ((tmp-file (mu4e-make-temp-file "html")))
|
||||
(write-region (point-min) (point-max) tmp-file)
|
||||
(erase-buffer)
|
||||
(call-process-shell-command mu4e-html2text-command tmp-file t t)
|
||||
(delete-file tmp-file))) msg))
|
||||
|
||||
;;; _
|
||||
(provide 'mu4e-message)
|
||||
|
|
|
@ -122,7 +122,7 @@ the query (for links starting with 'query:')."
|
|||
((string-match "^msgid:\\(.+\\)" link)
|
||||
(mu4e-view-message-with-message-id (match-string 1 link)))
|
||||
((string-match "^query:\\(.+\\)" link)
|
||||
(mu4e-search (match-string 1 link) current-prefix-arg))
|
||||
(mu4e-headers-search (match-string 1 link) current-prefix-arg))
|
||||
(t (mu4e-error "Unrecognized link type '%s'" link))))
|
||||
|
||||
(make-obsolete 'org-mu4e-open 'mu4e-org-open "1.3.6")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
;;; mu4e-server.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
;;; mu4e-proc.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
|
||||
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
@ -24,175 +24,72 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'mu4e-helpers)
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-utils)
|
||||
(require 'mu4e-meta)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
(defcustom mu4e-mu-home nil
|
||||
"Location of an alternate mu home dir.
|
||||
If not set, use the defaults, based on the XDG Base Directory
|
||||
Specification."
|
||||
:group 'mu4e
|
||||
:type '(choice (const :tag "Default location" nil)
|
||||
(directory :tag "Specify location"))
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-mu-binary (executable-find "mu")
|
||||
"Name of the mu-binary to use.
|
||||
If it cannot be found in your PATH, you can specify the full
|
||||
path."
|
||||
:type 'file
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-mu-debug nil
|
||||
"Whether to run the mu binary in debug-mode.
|
||||
Setting this to t increases the amount of information in the log."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(make-obsolete-variable
|
||||
'mu4e-maildir
|
||||
"determined by server; see `mu4e-root-maildir'." "1.3.8")
|
||||
|
||||
(defcustom mu4e-change-filenames-when-moving nil
|
||||
"Change message file names when moving them.
|
||||
When moving messages to different folders, normally mu/mu4e keep
|
||||
the base filename the same (the flags-part of the filename may
|
||||
change still). With this option set to non-nil, mu4e instead
|
||||
changes the filename. This latter behavior works better with some
|
||||
IMAP-synchronization programs such as mbsync; the default works
|
||||
better with e.g. offlineimap."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
|
||||
;; Handlers are not strictly internal, but are not meant
|
||||
;; for overriding outside mu4e. The are mainly for breaking
|
||||
;; dependency cycles.
|
||||
|
||||
(defvar mu4e-error-func nil
|
||||
"Function called for each error received.
|
||||
The function is passed an error plist as argument. See
|
||||
`mu4e--server-filter' for the format.")
|
||||
|
||||
(defvar mu4e-update-func nil
|
||||
"Function called for each :update sexp returned.
|
||||
The function is passed a msg sexp as argument.
|
||||
See `mu4e--server-filter' for the format.")
|
||||
|
||||
(defvar mu4e-remove-func nil
|
||||
"Function called for each :remove sexp returned.
|
||||
This happens when some message has been deleted. The function is
|
||||
passed the docid of the removed message.")
|
||||
|
||||
(defvar mu4e-sent-func nil
|
||||
"Function called for each :sent sexp received.
|
||||
This happens when some message has been sent. The function is
|
||||
passed the docid and the draft-path of the sent message.")
|
||||
|
||||
(defvar mu4e-view-func nil
|
||||
"Function called for each single-message sexp.
|
||||
The function is passed a message sexp as argument. See
|
||||
`mu4e--server-filter' for the format.")
|
||||
|
||||
(defvar mu4e-header-func nil
|
||||
"Function called for each message-header received.
|
||||
The function is passed a msg plist as argument. See
|
||||
`mu4e--server-filter' for the format.")
|
||||
|
||||
(defvar mu4e-found-func nil
|
||||
"Function called for when we received a :found sexp.
|
||||
This happens after the headers have been returned, to report on
|
||||
the number of matches. See `mu4e--server-filter' for the format.")
|
||||
|
||||
(defvar mu4e-erase-func nil
|
||||
"Function called we receive an :erase sexp.
|
||||
This before new headers are displayed, to clear the current
|
||||
headers buffer. See `mu4e--server-filter' for the format.")
|
||||
|
||||
(defvar mu4e-compose-func nil
|
||||
"Function called for each compose message received.
|
||||
I.e., the original message that is used as basis for composing a
|
||||
new message (i.e., either a reply or a forward); the function is
|
||||
passed msg and a symbol (either reply or forward). See
|
||||
`mu4e--server-filter' for the format of <msg-plist>.")
|
||||
|
||||
(defvar mu4e-info-func nil
|
||||
"Function called for each (:info type ....) sexp received.
|
||||
from the server process.")
|
||||
|
||||
(defvar mu4e-pong-func nil
|
||||
"Function called for each (:pong type ....) sexp received.")
|
||||
|
||||
(defvar mu4e-contacts-func nil
|
||||
"A function called for each (:contacts (<list-of-contacts>)
|
||||
sexp received from the server process.")
|
||||
|
||||
(make-obsolete-variable 'mu4e-temp-func "No longer used" "1.7.0")
|
||||
|
||||
;;; Internal vars
|
||||
|
||||
(defvar mu4e--server-buf nil
|
||||
(defvar mu4e~proc-buf nil
|
||||
"Buffer (string) for data received from the backend.")
|
||||
(defconst mu4e--server-name " *mu4e-server*"
|
||||
(defconst mu4e~proc-name " *mu4e-proc*"
|
||||
"Name of the server process, buffer.")
|
||||
(defvar mu4e--server-process nil
|
||||
(defvar mu4e~proc-process nil
|
||||
"The mu-server process.")
|
||||
|
||||
;; dealing with the length cookie that precedes expressions
|
||||
(defconst mu4e--server-cookie-pre "\376"
|
||||
(defconst mu4e~cookie-pre "\376"
|
||||
"Each expression starts with a length cookie:
|
||||
<`mu4e--server-cookie-pre'><length-in-hex><`mu4e--server-cookie-post'>.")
|
||||
(defconst mu4e--server-cookie-post "\377"
|
||||
<`mu4e~cookie-pre'><length-in-hex><`mu4e~cookie-post'>.")
|
||||
(defconst mu4e~cookie-post "\377"
|
||||
"Each expression starts with a length cookie:
|
||||
<`mu4e--server-cookie-pre'><length-in-hex><`mu4e--server-cookie-post'>.")
|
||||
(defconst mu4e--server-cookie-matcher-rx
|
||||
(concat mu4e--server-cookie-pre "\\([[:xdigit:]]+\\)"
|
||||
mu4e--server-cookie-post)
|
||||
<`mu4e~cookie-pre'><length-in-hex><`mu4e~cookie-post'>.")
|
||||
(defconst mu4e~cookie-matcher-rx
|
||||
(concat mu4e~cookie-pre "\\([[:xdigit:]]+\\)" mu4e~cookie-post)
|
||||
"Regular expression matching the length cookie.
|
||||
Match 1 will be the length (in hex).")
|
||||
|
||||
(defun mu4e-running-p ()
|
||||
"Whether mu4e is running.
|
||||
Checks whether the server process is live."
|
||||
(and mu4e--server-process
|
||||
(memq (process-status mu4e--server-process)
|
||||
|
||||
;;; Functions
|
||||
|
||||
(defun mu4e~proc-running-p ()
|
||||
"Whether the mu process is running."
|
||||
(and mu4e~proc-process
|
||||
(memq (process-status mu4e~proc-process)
|
||||
'(run open listen connect stop))
|
||||
t))
|
||||
|
||||
(defsubst mu4e--server-eat-sexp-from-buf ()
|
||||
"'Eat' the next s-expression from `mu4e--server-buf'.
|
||||
Note: this is a string, not an emacs-buffer. `mu4e--server-buf gets
|
||||
(defsubst mu4e~proc-eat-sexp-from-buf ()
|
||||
"'Eat' the next s-expression from `mu4e~proc-buf'.
|
||||
Note: this is a string, not an emacs-buffer. `mu4e~proc-buf gets
|
||||
its contents from the mu-servers in the following form:
|
||||
<`mu4e--server-cookie-pre'><length-in-hex><`mu4e--server-cookie-post'>
|
||||
<`mu4e~cookie-pre'><length-in-hex><`mu4e~cookie-post'>
|
||||
Function returns this sexp, or nil if there was none.
|
||||
`mu4e--server-buf' is updated as well, with all processed sexp data
|
||||
`mu4e~proc-buf' is updated as well, with all processed sexp data
|
||||
removed."
|
||||
(ignore-errors ;; the server may die in the middle...
|
||||
(let ((b (string-match mu4e--server-cookie-matcher-rx mu4e--server-buf))
|
||||
;; mu4e~cookie-matcher-rx:
|
||||
;; (concat mu4e~cookie-pre "\\([[:xdigit:]]+\\)]" mu4e~cookie-post)
|
||||
(let ((b (string-match mu4e~cookie-matcher-rx mu4e~proc-buf))
|
||||
(sexp-len) (objcons))
|
||||
(when b
|
||||
(setq sexp-len (string-to-number (match-string 1 mu4e--server-buf) 16))
|
||||
;; does mu4e--server-buf contain the full sexp?
|
||||
(when (>= (length mu4e--server-buf) (+ sexp-len (match-end 0)))
|
||||
(setq sexp-len (string-to-number (match-string 1 mu4e~proc-buf) 16))
|
||||
;; does mu4e~proc-buf contain the full sexp?
|
||||
(when (>= (length mu4e~proc-buf) (+ sexp-len (match-end 0)))
|
||||
;; clear-up start
|
||||
(setq mu4e--server-buf (substring mu4e--server-buf (match-end 0)))
|
||||
(setq mu4e~proc-buf (substring mu4e~proc-buf (match-end 0)))
|
||||
;; note: we read the input in binary mode -- here, we take the part
|
||||
;; that is the sexp, and convert that to utf-8, before we interpret
|
||||
;; it.
|
||||
(setq objcons (read-from-string
|
||||
(decode-coding-string
|
||||
(substring mu4e--server-buf 0 sexp-len)
|
||||
(substring mu4e~proc-buf 0 sexp-len)
|
||||
'utf-8 t)))
|
||||
(when objcons
|
||||
(setq mu4e--server-buf (substring mu4e--server-buf sexp-len))
|
||||
(setq mu4e~proc-buf (substring mu4e~proc-buf sexp-len))
|
||||
(car objcons)))))))
|
||||
|
||||
|
||||
(defun mu4e--server-filter (_proc str)
|
||||
(defun mu4e~proc-filter (_proc str)
|
||||
"Filter string STR from PROC.
|
||||
This processes the 'mu server' output. It accumulates the
|
||||
strings into valid sexps by checking of the ';;eox' `end-of-sexp'
|
||||
|
@ -250,8 +147,8 @@ The server output is as follows:
|
|||
(:compose <reply|forward|edit|new> [:original<msg-sexp>] [:include <attach>])
|
||||
`mu4e-compose-func'."
|
||||
(mu4e-log 'misc "* Received %d byte(s)" (length str))
|
||||
(setq mu4e--server-buf (concat mu4e--server-buf str)) ;; update our buffer
|
||||
(let ((sexp (mu4e--server-eat-sexp-from-buf)))
|
||||
(setq mu4e~proc-buf (concat mu4e~proc-buf str)) ;; update our buffer
|
||||
(let ((sexp (mu4e~proc-eat-sexp-from-buf)))
|
||||
(with-local-quit
|
||||
(while sexp
|
||||
(mu4e-log 'from-server "%S" sexp)
|
||||
|
@ -280,7 +177,6 @@ The server output is as follows:
|
|||
|
||||
;; received a pong message
|
||||
((plist-get sexp :pong)
|
||||
(setq mu4e--server-props (plist-get sexp :props))
|
||||
(funcall mu4e-pong-func sexp))
|
||||
|
||||
;; received a contacts message
|
||||
|
@ -308,6 +204,15 @@ The server output is as follows:
|
|||
(plist-get sexp :original)
|
||||
(plist-get sexp :include)))
|
||||
|
||||
;; do something with a temporary file
|
||||
((plist-get sexp :temp)
|
||||
(funcall mu4e-temp-func
|
||||
(plist-get sexp :temp) ;; name of the temp file
|
||||
(plist-get sexp :what) ;; what to do with it
|
||||
;; (pipe|emacs|open-with...)
|
||||
(plist-get sexp :docid) ;; docid of the message
|
||||
(plist-get sexp :param)));; parameter for the action
|
||||
|
||||
;; get some info
|
||||
((plist-get sexp :info)
|
||||
(funcall mu4e-info-func sexp))
|
||||
|
@ -320,65 +225,71 @@ The server output is as follows:
|
|||
|
||||
(t (mu4e-message "Unexpected data from server [%S]" sexp)))
|
||||
|
||||
(setq sexp (mu4e--server-eat-sexp-from-buf))))))
|
||||
(setq sexp (mu4e~proc-eat-sexp-from-buf))))))
|
||||
|
||||
(defun mu4e--server-start ()
|
||||
(defun mu4e~escape (str)
|
||||
"Escape string STR for transport.
|
||||
Put it in quotes, and escape existing quotation. In particular,
|
||||
backslashes and double-quotes."
|
||||
(let ((esc (replace-regexp-in-string "\\\\" "\\\\\\\\" str)))
|
||||
(format "\"%s\"" (replace-regexp-in-string "\"" "\\\\\"" esc))))
|
||||
|
||||
(defun mu4e~proc-start ()
|
||||
"Start the mu server process."
|
||||
|
||||
;; sanity-check 1
|
||||
(unless (and mu4e-mu-binary (file-executable-p mu4e-mu-binary))
|
||||
(mu4e-error
|
||||
"Cannot find mu, please set `mu4e-mu-binary' to the mu executable path"))
|
||||
|
||||
;; sanity-check 2
|
||||
(let ((version (let ((s (shell-command-to-string
|
||||
(concat mu4e-mu-binary " --version"))))
|
||||
(let ((version (let ((s (shell-command-to-string (concat mu4e-mu-binary " --version"))))
|
||||
(and (string-match "version \\([.0-9]+\\)" s)
|
||||
(match-string 1 s)))))
|
||||
(unless (string= version mu4e-mu-version)
|
||||
(mu4e-error
|
||||
(concat
|
||||
"Found mu version %s, but mu4e needs version %s"
|
||||
"; please set `mu4e-mu-binary' "
|
||||
"Found mu version %s, but mu4e needs version %s; please set `mu4e-mu-binary' "
|
||||
"accordingly") version mu4e-mu-version)))
|
||||
|
||||
(let* ((process-connection-type nil) ;; use a pipe
|
||||
(args (when mu4e-mu-home `(,(format"--muhome=%s" mu4e-mu-home))))
|
||||
(args (if mu4e-mu-debug (cons "--debug" args) args))
|
||||
(args (cons "server" args)))
|
||||
(setq mu4e--server-buf "")
|
||||
(setq mu4e--server-process (apply 'start-process
|
||||
mu4e--server-name mu4e--server-name
|
||||
mu4e-mu-binary args))
|
||||
(setq mu4e~proc-buf "")
|
||||
(setq mu4e~proc-process (apply 'start-process
|
||||
mu4e~proc-name mu4e~proc-name
|
||||
mu4e-mu-binary args))
|
||||
;; register a function for (:info ...) sexps
|
||||
(unless mu4e--server-process
|
||||
(unless mu4e~proc-process
|
||||
(mu4e-error "Failed to start the mu4e backend"))
|
||||
(set-process-query-on-exit-flag mu4e--server-process nil)
|
||||
(set-process-coding-system mu4e--server-process 'binary 'utf-8-unix)
|
||||
(set-process-filter mu4e--server-process 'mu4e--server-filter)
|
||||
(set-process-sentinel mu4e--server-process 'mu4e--server-sentinel)))
|
||||
(set-process-query-on-exit-flag mu4e~proc-process nil)
|
||||
(set-process-coding-system mu4e~proc-process 'binary 'utf-8-unix)
|
||||
(set-process-filter mu4e~proc-process 'mu4e~proc-filter)
|
||||
(set-process-sentinel mu4e~proc-process 'mu4e~proc-sentinel)))
|
||||
|
||||
(defun mu4e--server-kill ()
|
||||
(defun mu4e~proc-kill ()
|
||||
"Kill the mu server process."
|
||||
(let* ((buf (get-buffer mu4e--server-name))
|
||||
(let* ((buf (get-buffer mu4e~proc-name))
|
||||
(proc (and (buffer-live-p buf) (get-buffer-process buf))))
|
||||
(when proc
|
||||
(let ((delete-exited-processes t))
|
||||
(mu4e--server-call-mu '(quit)))
|
||||
(mu4e~call-mu '(quit)))
|
||||
;; try sending SIGINT (C-c) to process, so it can exit gracefully
|
||||
(ignore-errors
|
||||
(signal-process proc 'SIGINT))))
|
||||
(setq
|
||||
mu4e--server-process nil
|
||||
mu4e--server-buf nil))
|
||||
mu4e~proc-process nil
|
||||
mu4e~proc-buf nil))
|
||||
|
||||
;; error codes are defined in src/mu-util
|
||||
;;(defconst mu4e-xapian-empty 19 "Error code: xapian is empty/non-existent")
|
||||
|
||||
(defun mu4e--server-sentinel (proc _msg)
|
||||
(defun mu4e~proc-sentinel (proc _msg)
|
||||
"Function called when the server process PROC terminates with MSG."
|
||||
(let ((status (process-status proc)) (code (process-exit-status proc)))
|
||||
(setq mu4e--server-process nil)
|
||||
(setq mu4e--server-buf "") ;; clear any half-received sexps
|
||||
(setq mu4e~proc-process nil)
|
||||
(setq mu4e~proc-buf "") ;; clear any half-received sexps
|
||||
(cond
|
||||
((eq status 'signal)
|
||||
(cond
|
||||
|
@ -395,21 +306,27 @@ The server output is as follows:
|
|||
(t
|
||||
(error "Something bad happened to the mu server process")))))
|
||||
|
||||
(defun mu4e--server-call-mu (form)
|
||||
"Call the mu server with some command FORM."
|
||||
(unless (mu4e-running-p) (mu4e--server-start))
|
||||
(defun mu4e~call-mu (form)
|
||||
"Call 'mu' with some command."
|
||||
(unless (mu4e~proc-running-p) (mu4e~proc-start))
|
||||
(let* ((print-length nil) (print-level nil)
|
||||
(cmd (format "%S" form)))
|
||||
(mu4e-log 'to-server "%s" cmd)
|
||||
(process-send-string mu4e--server-process (concat cmd "\n"))))
|
||||
(process-send-string mu4e~proc-process (concat cmd "\n"))))
|
||||
|
||||
(defun mu4e--server-add (path)
|
||||
(defun mu4e~docid-msgid-param (docid-or-msgid)
|
||||
"Construct a backend parameter based on DOCID-OR-MSGID."
|
||||
(if (stringp docid-or-msgid)
|
||||
`(:msgid ,(mu4e~escape docid-or-msgid))
|
||||
`(:docid ,docid-or-msgid)))
|
||||
|
||||
(defun mu4e~proc-add (path)
|
||||
"Add the message at PATH to the database.
|
||||
On success, we receive `'(:info add :path <path> :docid <docid>)'
|
||||
as well as `'(:update <msg-sexp>)`'; otherwise, we receive an error."
|
||||
(mu4e--server-call-mu `(add :path ,path)))
|
||||
(mu4e~call-mu `(add :path ,path)))
|
||||
|
||||
(defun mu4e--server-compose (type decrypt &optional docid)
|
||||
(defun mu4e~proc-compose (type decrypt &optional docid)
|
||||
"Compose a message of TYPE, DECRYPT it and use DOCID.
|
||||
TYPE is a symbol, either `forward', `reply', `edit', `resend' or
|
||||
`new', based on an original message (ie, replying to, forwarding,
|
||||
|
@ -417,26 +334,42 @@ editing, resending) with DOCID or nil for type `new'.
|
|||
|
||||
The result is delivered to the function registered as
|
||||
`mu4e-compose-func'."
|
||||
(mu4e--server-call-mu
|
||||
`(compose
|
||||
:type ,type
|
||||
:decrypt ,(and decrypt t)
|
||||
:docid ,docid)))
|
||||
(mu4e~call-mu `(compose
|
||||
:type ,type
|
||||
:decrypt ,(and decrypt t)
|
||||
:docid ,docid)))
|
||||
|
||||
(defun mu4e--server-contacts (personal after tstamp)
|
||||
(defun mu4e~proc-contacts (personal after tstamp)
|
||||
"Ask for contacts with PERSONAL AFTER TSTAMP.
|
||||
S-expression (:contacts (<list>) :tstamp \"<tstamp>\") is expected in
|
||||
response. If PERSONAL is non-nil, only get personal contacts, if
|
||||
AFTER is non-nil, get only contacts seen AFTER (the time_t
|
||||
value)."
|
||||
(mu4e--server-call-mu
|
||||
`(contacts
|
||||
:personal ,(and personal t)
|
||||
:after ,(or after nil)
|
||||
:tstamp ,(or tstamp nil))))
|
||||
(mu4e~call-mu `(contacts
|
||||
:personal ,(and personal t)
|
||||
:after ,(or after nil)
|
||||
:tstamp ,(or tstamp nil))))
|
||||
|
||||
(defun mu4e--server-find (query threads sortfield sortdir maxnum skip-dups
|
||||
include-related)
|
||||
(defun mu4e~proc-extract (action docid index decrypt
|
||||
&optional path what param)
|
||||
"Perform ACTION on part with DOCID INDEX DECRYPT PATH WHAT PARAM.
|
||||
Use a message with DOCID and perform ACTION on it (as symbol,
|
||||
either `save', `open', `temp') which mean:
|
||||
* save: save the part to PATH (a path) (non-optional for save)
|
||||
* open: open the part with the default application registered for doing so
|
||||
* temp: save to a temporary file, then respond with
|
||||
(:temp <path> :what <what> :param <param>)."
|
||||
(mu4e~call-mu `(extract
|
||||
:action ,action
|
||||
:docid ,docid
|
||||
:index ,index
|
||||
:decrypt ,(and decrypt t)
|
||||
:path ,path
|
||||
:what ,what
|
||||
:param ,param)))
|
||||
|
||||
(defun mu4e~proc-find (query threads sortfield sortdir maxnum skip-dups
|
||||
include-related)
|
||||
"Run QUERY with THREADS SORTFIELD SORTDIR MAXNUM SKIP-DUPS INCLUDE-RELATED.
|
||||
If THREADS is non-nil, show results in threaded fashion, SORTFIELD
|
||||
is a symbol describing the field to sort by (or nil); see
|
||||
|
@ -452,25 +385,26 @@ For each result found, a function is called, depending on the
|
|||
kind of result. The variables `mu4e-error-func' contain the
|
||||
function that will be called for, resp., a message (header row)
|
||||
or an error."
|
||||
(mu4e--server-call-mu
|
||||
`(find
|
||||
:query ,query
|
||||
:threads ,threads
|
||||
:sortfield ,sortfield
|
||||
:descending ,(if (eq sortdir 'descending) t nil)
|
||||
:maxnum ,maxnum
|
||||
:skip-dups ,skip-dups
|
||||
:include-related ,include-related)))
|
||||
(mu4e~call-mu `(find
|
||||
:query ,query
|
||||
:threads ,threads
|
||||
:sortfield ,sortfield
|
||||
:descending ,(if (eq sortdir 'descending) t nil)
|
||||
:maxnum ,maxnum
|
||||
:skip-dups ,skip-dups
|
||||
:include-related ,include-related)))
|
||||
|
||||
(defun mu4e--server-index (&optional cleanup lazy-check)
|
||||
(defun mu4e~proc-index (&optional cleanup lazy-check)
|
||||
"Index messages with possible CLEANUP and LAZY-CHECK."
|
||||
(mu4e--server-call-mu `(index :cleanup ,cleanup :lazy-check ,lazy-check)))
|
||||
(mu4e~call-mu `(index :cleanup ,cleanup :lazy-check ,lazy-check)))
|
||||
|
||||
(defun mu4e--server-mkdir (path)
|
||||
(defun mu4e~proc-mkdir (path)
|
||||
"Create a new maildir-directory at filesystem PATH."
|
||||
(mu4e--server-call-mu `(mkdir :path ,path)))
|
||||
;;(mu4e~proc-send-command "cmd:mkdir path:%s" (mu4e~escape path))
|
||||
(mu4e~call-mu `(mkdir :path ,path)))
|
||||
|
||||
(defun mu4e--server-move (docid-or-msgid &optional maildir flags no-view)
|
||||
|
||||
(defun mu4e~proc-move (docid-or-msgid &optional maildir flags no-view)
|
||||
"Move message identified by DOCID-OR-MSGID.
|
||||
Optionally to MAILDIR and optionally setting FLAGS. If MAILDIR is
|
||||
nil, message will be moved within the same maildir.
|
||||
|
@ -508,46 +442,60 @@ Returns either (:update ... ) or (:error ) sexp, which are handled my
|
|||
(unless (or (not maildir)
|
||||
(file-exists-p (concat (mu4e-root-maildir) "/" maildir "/")))
|
||||
(mu4e-error "Target dir does not exist"))
|
||||
(mu4e--server-call-mu
|
||||
`(move
|
||||
:docid ,(if (stringp docid-or-msgid) nil docid-or-msgid)
|
||||
:msgid ,(if (stringp docid-or-msgid) docid-or-msgid nil)
|
||||
:flags ,(or flags nil)
|
||||
:maildir ,(or maildir nil)
|
||||
:rename ,(and maildir mu4e-change-filenames-when-moving t)
|
||||
:no-view ,(and no-view t))))
|
||||
(mu4e~call-mu `(move
|
||||
:docid ,(if (stringp docid-or-msgid) nil docid-or-msgid)
|
||||
:msgid ,(if (stringp docid-or-msgid) docid-or-msgid nil)
|
||||
:flags ,(or flags nil)
|
||||
:maildir ,(or maildir nil)
|
||||
:rename ,(and maildir mu4e-change-filenames-when-moving t)
|
||||
:no-view ,(and no-view t))))
|
||||
|
||||
(defun mu4e--server-ping (&optional queries)
|
||||
(defun mu4e~proc-ping (&optional queries)
|
||||
"Sends a ping to the mu server, expecting a (:pong ...) in response.
|
||||
QUERIES is a list of queries for the number of results with read/unread status
|
||||
are returned in the 'pong' response."
|
||||
(mu4e--server-call-mu `(ping :queries ,queries)))
|
||||
(mu4e~call-mu `(ping :queries ,queries)))
|
||||
|
||||
(defun mu4e--server-remove (docid)
|
||||
(defun mu4e~proc-remove (docid)
|
||||
"Remove message with DOCID.
|
||||
The results are reporter through either (:update ... )
|
||||
or (:error) sexp, which are handled my `mu4e-error-func',
|
||||
respectively."
|
||||
(mu4e--server-call-mu `(remove :docid ,docid)))
|
||||
(mu4e~call-mu `(remove :docid ,docid)))
|
||||
|
||||
(defun mu4e--server-sent (path)
|
||||
"Tell the mu server we sent a message at PATH.
|
||||
If this works, we will receive (:info add :path <path> :docid
|
||||
(defun mu4e~proc-sent (path)
|
||||
"Add the message at PATH to the database.
|
||||
|
||||
if this works, we will receive (:info add :path <path> :docid
|
||||
<docid> :fcc <path>)."
|
||||
(mu4e--server-call-mu `(sent :path ,path)))
|
||||
(mu4e~call-mu `(sent :path ,path)))
|
||||
|
||||
(defun mu4e--server-view (docid-or-msgid &optional mark-as-read)
|
||||
(defun mu4e~proc-view (docid-or-msgid &optional mark-as-read decrypt verify)
|
||||
"Get a message DOCID-OR-MSGID.
|
||||
Optionally, if MARK-AS-READ is non-nil, the backend marks the
|
||||
message as read before returning, if it was not already unread.
|
||||
The result will be delivered to the function registered as
|
||||
`mu4e-view-func'."
|
||||
(mu4e--server-call-mu
|
||||
`(view
|
||||
:docid ,(if (stringp docid-or-msgid) nil docid-or-msgid)
|
||||
:msgid ,(if (stringp docid-or-msgid) docid-or-msgid nil)
|
||||
:mark-as-read ,mark-as-read)))
|
||||
Optionally, if MARK-AS-READ is non-nil, the backend marks the message as
|
||||
read before returning, if it was not already unread.
|
||||
DECRYPT and VERIFY if necessary. The result will be delivered to
|
||||
the function registered as `mu4e-view-func'."
|
||||
(mu4e~call-mu `(view
|
||||
:docid ,(if (stringp docid-or-msgid) nil docid-or-msgid)
|
||||
:msgid ,(if (stringp docid-or-msgid) docid-or-msgid nil)
|
||||
:mark-as-read ,mark-as-read
|
||||
:extract-images ,(if mu4e-view-show-images t nil)
|
||||
:decrypt ,(and decrypt t)
|
||||
:verify ,(and verify t))))
|
||||
|
||||
|
||||
(provide 'mu4e-server)
|
||||
;;; mu4e-server.el ends here
|
||||
(defun mu4e~proc-view-path (path &optional images decrypt verify)
|
||||
"View message at PATH..
|
||||
Optionally, if IMAGES is non-nil, backend will any images
|
||||
attached to the message, and return them as temp files. The
|
||||
result will be delivered to the function registered as
|
||||
`mu4e-view-func'. Optionally DECRYPT and VERIFY."
|
||||
(mu4e~call-mu `(view
|
||||
:path ,path
|
||||
:extract-images ,(if images t nil)
|
||||
:decrypt ,(and decrypt t)
|
||||
:verify ,(and verify t))))
|
||||
|
||||
;;; _
|
||||
(provide 'mu4e-proc)
|
||||
;;; mu4e-proc.el ends here
|
|
@ -1,446 +0,0 @@
|
|||
;;; mu4e-search.el -- part of mu4e -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Search-related functions and a minor-mode.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'seq)
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-helpers)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
(defgroup mu4e-search nil
|
||||
"Search-related settings."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-search-results-limit 500
|
||||
"Maximum number of results to show.
|
||||
This affects performance, especially when
|
||||
`mu4e-summary-include-related' is non-nil.
|
||||
Set to -1 for no limits."
|
||||
:type '(choice (const :tag "Unlimited" -1)
|
||||
(integer :tag "Limit"))
|
||||
:group 'mu4e-search)
|
||||
(define-obsolete-variable-alias 'mu4e-headers-results-limit
|
||||
'mu4e-search-results-limit "1.7.0")
|
||||
|
||||
(defvar mu4e-search-full nil
|
||||
"Whether to search for all results.
|
||||
If this is nil, search for up to `mu4e-search-results-limit')")
|
||||
|
||||
(define-obsolete-variable-alias 'mu4e-headers-full-search
|
||||
'mu4e-search-full "1.7.0")
|
||||
|
||||
(defvar mu4e-search-threads t
|
||||
"Whether to calculate threads for the search results.")
|
||||
(define-obsolete-variable-alias 'mu4e-headers-show-threads
|
||||
'mu4e-search-threads "1.7.0")
|
||||
|
||||
(defcustom mu4e-search-query-rewrite-function 'identity
|
||||
"Function to rewrite a query.
|
||||
|
||||
It takes a search expression string, and returns a possibly
|
||||
changed search expression string.
|
||||
|
||||
This function is applied on the search expression just before
|
||||
searching, and allows users to modify the query.
|
||||
|
||||
For instance, we could change and of workmail into
|
||||
\"maildir:/long-path-to-work-related-emails\", by setting the function
|
||||
|
||||
(setq mu4e-query-rewrite-function
|
||||
(lambda(expr)
|
||||
(replace-regexp-in-string \"workmail\"
|
||||
\"maildir:/long-path-to-work-related-emails\" expr)))
|
||||
|
||||
It is good to remember that the replacement does not understand
|
||||
anything about the query, it just does text replacement."
|
||||
:type 'function
|
||||
:group 'mu4e-search)
|
||||
|
||||
(define-obsolete-variable-alias 'mu4e-query-rewrite-function
|
||||
'mu4e-search-query-rewrite-function "1.7.0")
|
||||
|
||||
(defcustom mu4e-search-bookmark-hook nil
|
||||
"Hook run just after invoking a bookmarked search.
|
||||
|
||||
This function receives the query as its parameter, before any
|
||||
rewriting as per `mu4e-query-rewrite-function' has taken place.
|
||||
|
||||
The reason to use this instead of `mu4e-headers-search-hook' is
|
||||
if you only want to execute a hook when a search is entered via a
|
||||
bookmark, e.g. if you'd like to treat the bookmarks as a custom
|
||||
folder and change the options for the search."
|
||||
:type 'hook
|
||||
:group 'mu4e-search)
|
||||
|
||||
(define-obsolete-variable-alias
|
||||
'mu4e-headers-search-bookmark-hook
|
||||
'mu4e-search-bookmark-hook "1.7.0")
|
||||
|
||||
(defcustom mu4e-search-hook nil
|
||||
"Hook run just before executing a new search operation.
|
||||
This function receives the query as its parameter, before any
|
||||
rewriting as per `mu4e-query-rewrite-function' has taken place
|
||||
|
||||
This is a more general hook facility than the
|
||||
`mu4e-search-bookmark-hook'. It gets called on every
|
||||
executed search, not just those that are invoked via bookmarks,
|
||||
but also manually invoked searches."
|
||||
:type 'hook
|
||||
:group 'mu4e-search)
|
||||
|
||||
(define-obsolete-variable-alias 'mu4e-headers-search-hook
|
||||
'mu4e-search-hook "1.7.0")
|
||||
|
||||
;;; Interactive functions
|
||||
|
||||
(defun mu4e-search (&optional expr prompt edit ignore-history msgid show)
|
||||
"Search for query EXPR.
|
||||
|
||||
Switch to the output buffer for the results. This is an
|
||||
interactive function which ask user for EXPR. PROMPT, if non-nil,
|
||||
is the prompt used by this function (default is \"Search for:\").
|
||||
If EDIT is non-nil, instead of executing the query for EXPR, let
|
||||
the user edit the query before executing it.
|
||||
|
||||
If IGNORE-HISTORY is true, do *not* update the query history
|
||||
stack. If MSGID is non-nil, attempt to move point to the first
|
||||
message with that message-id after searching. If SHOW is non-nil,
|
||||
show the message with MSGID."
|
||||
(interactive)
|
||||
(let* ((prompt (mu4e-format (or prompt "Search for: ")))
|
||||
(expr
|
||||
(if (or (null expr) edit)
|
||||
(mu4e-read-query prompt expr)
|
||||
expr)))
|
||||
(mu4e-mark-handle-when-leaving)
|
||||
(mu4e--search-execute expr ignore-history)
|
||||
(setq mu4e~headers-msgid-target msgid
|
||||
mu4e~headers-view-target show)))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-search 'mu4e-search "1.7.0")
|
||||
|
||||
(defun mu4e-search-edit ()
|
||||
"Edit the last search expression."
|
||||
(interactive)
|
||||
(mu4e-search mu4e--search-last-query nil t))
|
||||
|
||||
(define-obsolete-variable-alias 'mu4e-headers-search-edit
|
||||
'mu4e-search-edit "1.7.0")
|
||||
|
||||
(defun mu4e-search-bookmark (&optional expr edit)
|
||||
"Search using some bookmarked query EXPR.
|
||||
If EDIT is non-nil, let the user edit the bookmark before starting
|
||||
the search."
|
||||
(interactive)
|
||||
(let ((expr
|
||||
(or expr
|
||||
(mu4e-ask-bookmark (if edit "Select bookmark: " "Bookmark: ")))))
|
||||
(run-hook-with-args 'mu4e-search-bookmark-hook expr)
|
||||
(mu4e-search expr (when edit "Edit bookmark: ") edit)))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-search-bookmark
|
||||
'mu4e-search-bookmark "1.7.0")
|
||||
|
||||
(defun mu4e-search-bookmark-edit ()
|
||||
"Edit an existing bookmark before executing it."
|
||||
(interactive)
|
||||
(mu4e-search-bookmark nil t))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-search-bookmark-edit
|
||||
'mu4e-search-bookmark-edit "1.7.0")
|
||||
|
||||
(defun mu4e-search-narrow(&optional filter)
|
||||
"Narrow the last search.
|
||||
Do so by appending search expression FILTER to the last search
|
||||
expression. Note that you can go back to previous
|
||||
query (effectively, 'widen' it), with `mu4e-search-prev'."
|
||||
(interactive
|
||||
(let ((filter
|
||||
(read-string (mu4e-format "Narrow down to: ")
|
||||
nil 'mu4e~headers-search-hist nil t)))
|
||||
(list filter)))
|
||||
(unless mu4e--search-last-query
|
||||
(mu4e-warn "There's nothing to filter"))
|
||||
(mu4e-headers-search
|
||||
(format "(%s) AND (%s)" mu4e--search-last-query filter)))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-search-narrow
|
||||
'mu4e-search-narrow "1.7.0")
|
||||
|
||||
;; (defun mu4e-headers-change-sorting (&optional field dir)
|
||||
;; "Change the sorting/threading parameters.
|
||||
;; FIELD is the field to sort by; DIR is a symbol: either 'ascending,
|
||||
;; 'descending, 't (meaning: if FIELD is the same as the current
|
||||
;; sortfield, change the sort-order) or nil (ask the user)."
|
||||
;; (interactive)
|
||||
;; (let* ((field
|
||||
;; (or field
|
||||
;; (mu4e-read-option "Sortfield: " mu4e~headers-sort-field-choices)))
|
||||
;; ;; note: 'sortable' is either a boolean (meaning: if non-nil, this is
|
||||
;; ;; sortable field), _or_ another field (meaning: sort by this other field).
|
||||
;; (sortable (plist-get (cdr (assoc field mu4e-header-info)) :sortable))
|
||||
;; ;; error check
|
||||
;; (sortable
|
||||
;; (if sortable
|
||||
;; sortable
|
||||
;; (mu4e-error "Not a sortable field")))
|
||||
;; (sortfield (if (booleanp sortable) field sortable))
|
||||
;; (dir
|
||||
;; (cl-case dir
|
||||
;; ((ascending descending) dir)
|
||||
;; ;; change the sort order if field = curfield
|
||||
;; (t
|
||||
;; (if (eq sortfield mu4e-headers-sort-field)
|
||||
;; (if (eq mu4e-headers-sort-direction 'ascending)
|
||||
;; 'descending 'ascending)
|
||||
;; 'descending))
|
||||
;; (mu4e-read-option "Direction: "
|
||||
;; '(("ascending" . 'ascending) ("descending" . 'descending))))))
|
||||
;; (setq
|
||||
;; mu4e-headers-sort-field sortfield
|
||||
;; mu4e-headers-sort-direction dir)
|
||||
;; (mu4e-message "Sorting by %s (%s)"
|
||||
;; (symbol-name sortfield)
|
||||
;; (symbol-name mu4e-headers-sort-direction))
|
||||
;; (mu4e-headers-rerun-search)))
|
||||
|
||||
;; (defun mu4e~headers-toggle (name togglevar dont-refresh)
|
||||
;; "Toggle variable TOGGLEVAR for feature NAME. Unless DONT-REFRESH is non-nil,
|
||||
;; re-run the last search."
|
||||
;; (set togglevar (not (symbol-value togglevar)))
|
||||
;; (mu4e-message "%s turned %s%s"
|
||||
;; name
|
||||
;; (if (symbol-value togglevar) "on" "off")
|
||||
;; (if dont-refresh
|
||||
;; " (press 'g' to refresh)" ""))
|
||||
;; (unless dont-refresh
|
||||
;; (mu4e-headers-rerun-search)))
|
||||
|
||||
;; (defun mu4e-headers-toggle-threading (&optional dont-refresh)
|
||||
;; "Toggle `mu4e-headers-show-threads'. With prefix-argument, do
|
||||
;; _not_ refresh the last search with the new setting for threading."
|
||||
;; (interactive "P")
|
||||
;; (mu4e~headers-toggle "Threading" 'mu4e-headers-show-threads dont-refresh))
|
||||
|
||||
;; (defun mu4e-headers-toggle-full-search (&optional dont-refresh)
|
||||
;; "Toggle `mu4e-headers-full-search'. With prefix-argument, do
|
||||
;; _not_ refresh the last search with the new setting for threading."
|
||||
;; (interactive "P")
|
||||
;; (mu4e~headers-toggle "Full-search"
|
||||
;; 'mu4e-headers-full-search dont-refresh))
|
||||
|
||||
;; (defun mu4e-headers-toggle-include-related (&optional dont-refresh)
|
||||
;; "Toggle `mu4e-headers-include-related'. With prefix-argument, do
|
||||
;; _not_ refresh the last search with the new setting for threading."
|
||||
;; (interactive "P")
|
||||
;; (mu4e~headers-toggle "Include-related"
|
||||
;; 'mu4e-headers-include-related dont-refresh))
|
||||
|
||||
;; (defun mu4e-headers-toggle-skip-duplicates (&optional dont-refresh)
|
||||
;; "Toggle `mu4e-headers-skip-duplicates'. With prefix-argument, do
|
||||
;; _not_ refresh the last search with the new setting for threading."
|
||||
;; (interactive "P")
|
||||
;; (mu4e~headers-toggle "Skip-duplicates"
|
||||
;; 'mu4e-headers-skip-duplicates dont-refresh))
|
||||
|
||||
|
||||
;;; History
|
||||
|
||||
(defvar mu4e--search-last-query nil
|
||||
"The present (most recent) query.")
|
||||
(defvar mu4e--search-query-past nil
|
||||
"Stack of queries before the present one.")
|
||||
(defvar mu4e--search-query-future nil
|
||||
"Stack of queries after the present one.")
|
||||
(defvar mu4e--search-query-stack-size 20
|
||||
"Maximum size for the query stacks.")
|
||||
|
||||
(defun mu4e--search-push-query (query where)
|
||||
"Push QUERY to one of the query stacks.
|
||||
WHERE is a symbol telling us where to push; it's a symbol, either
|
||||
'future or 'past. Functional also removes duplicates, limits the
|
||||
stack size."
|
||||
(let ((stack
|
||||
(cl-case where
|
||||
(past mu4e--search-query-past)
|
||||
(future mu4e--search-query-future))))
|
||||
;; only add if not the same item
|
||||
(unless (and stack (string= (car stack) query))
|
||||
(push query stack)
|
||||
;; limit the stack to `mu4e--search-query-stack-size' elements
|
||||
(when (> (length stack) mu4e--search-query-stack-size)
|
||||
(setq stack (cl-subseq stack 0 mu4e--search-query-stack-size)))
|
||||
;; remove all duplicates of the new element
|
||||
(cl-remove-if (lambda (elm) (string= elm (car stack))) (cdr stack))
|
||||
;; update the stacks
|
||||
(cl-case where
|
||||
(past (setq mu4e--search-query-past stack))
|
||||
(future (setq mu4e--search-query-future stack))))))
|
||||
|
||||
(defun mu4e--search-pop-query (whence)
|
||||
"Pop a query from the stack.
|
||||
WHENCE is a symbol telling us where to get it from, either `future'
|
||||
or `past'."
|
||||
(cl-case whence
|
||||
(past
|
||||
(unless mu4e--search-query-past
|
||||
(mu4e-warn "No more previous queries"))
|
||||
(pop mu4e--search-query-past))
|
||||
(future
|
||||
(unless mu4e--search-query-future
|
||||
(mu4e-warn "No more next queries"))
|
||||
(pop mu4e--search-query-future))))
|
||||
|
||||
|
||||
(defun mu4e-search-rerun ()
|
||||
"Re-run the search for the last search expression."
|
||||
(interactive)
|
||||
;; if possible, try to return to the same message
|
||||
(let* ((msg (mu4e-message-at-point t))
|
||||
(msgid (and msg (mu4e-message-field msg :message-id))))
|
||||
(mu4e-headers-search mu4e~headers-last-query nil nil t msgid)))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-rerun-search
|
||||
'mu4e-search-rerun "1.7.0")
|
||||
|
||||
(defun mu4e--search-query-navigate (whence)
|
||||
"Execute the previous query from the query stacks.
|
||||
WHENCE determines where the query is taken from and is a symbol,
|
||||
either `future' or `past'."
|
||||
(let ((query (mu4e--search-pop-query whence))
|
||||
(where (if (eq whence 'future) 'past 'future)))
|
||||
(when query
|
||||
(mu4e--search-push-query mu4e--search-last-query where)
|
||||
(mu4e-search query nil nil t))))
|
||||
|
||||
(defun mu4e-search-next ()
|
||||
"Execute the next query from the query stack."
|
||||
(interactive)
|
||||
(mu4e--search-query-navigate 'future))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-query-next
|
||||
'mu4e-search-next "1.7.0")
|
||||
|
||||
(defun mu4e-search-prev ()
|
||||
"Execute the previous query from the query stacks."
|
||||
(interactive)
|
||||
(mu4e--search-query-navigate 'past))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-query-prev
|
||||
'mu4e-search-prev "1.7.0")
|
||||
|
||||
;; forget the past so we don't repeat it :/
|
||||
(defun mu4e-search-forget ()
|
||||
"Forget the search history."
|
||||
(interactive)
|
||||
(setq mu4e--search-query-past nil
|
||||
mu4e--search-query-future nil)
|
||||
(mu4e-message "Query history cleared"))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-headers-forget-queries
|
||||
'mu4e-search-forget "1.7.0")
|
||||
|
||||
(defun mu4e-last-query ()
|
||||
"Get the most recent query or nil if there is none."
|
||||
mu4e--search-last-query)
|
||||
|
||||
;;; Completion for queries
|
||||
|
||||
(defvar mu4e--search-hist nil "History list of searches.")
|
||||
(defvar mu4e-minibuffer-search-query-map
|
||||
(let ((map (copy-keymap minibuffer-local-map)))
|
||||
(define-key map (kbd "TAB") #'completion-at-point)
|
||||
map)
|
||||
"The keymap for reading a search query.")
|
||||
|
||||
(defun mu4e-search-read-query (prompt &optional initial-input)
|
||||
"Read a query with completion using PROMPT and INITIAL-INPUT."
|
||||
(minibuffer-with-setup-hook
|
||||
(lambda ()
|
||||
(setq-local completion-at-point-functions
|
||||
#'mu4e--search-query-competion-at-point)
|
||||
(use-local-map mu4e-minibuffer-search-query-map))
|
||||
(read-string prompt initial-input 'mu4e--search-hist)))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-read-query
|
||||
'mu4e-search-read-query "1.7.0")
|
||||
|
||||
(defconst mu4e--search-query-keywords
|
||||
'("and" "or" "not"
|
||||
"from:" "to:" "cc:" "bcc:" "contact:" "date:" "subject:" "body:"
|
||||
"list:" "maildir:" "flag:" "mime:" "file:" "prio:" "tag:" "msgid:"
|
||||
"size:" "embed:"))
|
||||
|
||||
(defun mu4e--search-query-competion-at-point ()
|
||||
"Provide completion when entering search expressions."
|
||||
(cond
|
||||
((not (looking-back "[:\"][^ \t]*" nil))
|
||||
(let ((bounds (bounds-of-thing-at-point 'word)))
|
||||
(list (or (car bounds) (point))
|
||||
(or (cdr bounds) (point))
|
||||
mu4e--search-query-keywords)))
|
||||
((looking-back "flag:\\(\\w*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
'("attach" "draft" "flagged" "list" "new" "passed" "replied"
|
||||
"seen" "trashed" "unread" "encrypted" "signed")))
|
||||
;; ((looking-back "maildir:\\([a-zA-Z0-9/.]*\\)" nil)
|
||||
;; (list (match-beginning 1)
|
||||
;; (match-end 1)
|
||||
;; (mu4e-get-maildirs)))
|
||||
((looking-back "prio:\\(\\w*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
(list "high" "normal" "low")))
|
||||
((looking-back "mime:\\([a-zA-Z0-9/-]*\\)" nil)
|
||||
(list (match-beginning 1)
|
||||
(match-end 1)
|
||||
(mailcap-mime-types)))))
|
||||
|
||||
(define-minor-mode mu4e-search-minor-mode
|
||||
"Mode for searching for messages."
|
||||
:global nil
|
||||
:init-value nil ;; disabled by default
|
||||
:group 'mu4e
|
||||
:lighter ""
|
||||
:keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map "s" 'mu4e-search)
|
||||
(define-key map "S" 'mu4e-search-edit)
|
||||
(define-key map "/" 'mu4e-search-narrow)
|
||||
;;(define-key map "j" 'mu4e~headers-jump-to-maildir)
|
||||
(define-key map (kbd "<M-left>") 'mu4e-search-prev)
|
||||
(define-key map (kbd "\\") 'mu4e-search-prev)
|
||||
(define-key map (kbd "<M-right>") 'mu4e-search-next)
|
||||
|
||||
(define-key map "b" 'mu4e-search-bookmark)
|
||||
(define-key map "B" 'mu4e-search-bookmark-edit)
|
||||
map))
|
||||
|
||||
(provide 'mu4e-search)
|
||||
;;; mu4e-search.el ends here
|
|
@ -1,6 +1,6 @@
|
|||
;;; mu4e-speedbar --- Speedbar support for mu4e -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2012-2021 Antono Vasiljev, Dirk-Jan C. Binnema
|
||||
;; Copyright (C) 2012-2020 Antono Vasiljev, Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Antono Vasiljev <self@antono.info>
|
||||
;; Version: 0.1
|
||||
|
@ -35,7 +35,7 @@
|
|||
(require 'mu4e-vars)
|
||||
(require 'mu4e-headers)
|
||||
(require 'mu4e-context)
|
||||
(require 'mu4e-bookmarks)
|
||||
(require 'mu4e-utils)
|
||||
|
||||
(defvar mu4e-main-speedbar-key-map nil
|
||||
"Keymap used when in mu4e display mode.")
|
||||
|
@ -91,7 +91,8 @@
|
|||
(defun mu4e~speedbar-maildir (&optional _text token _ident)
|
||||
"Jump to maildir TOKEN. TEXT and INDENT are not used."
|
||||
(dframe-with-attached-buffer
|
||||
(mu4e-search (concat "\"maildir:" token "\"") current-prefix-arg)))
|
||||
(mu4e-headers-search (concat "\"maildir:" token "\"")
|
||||
current-prefix-arg)))
|
||||
|
||||
(defun mu4e~speedbar-render-bookmark-list ()
|
||||
"Insert the list of bookmarks in the speedbar"
|
||||
|
@ -109,7 +110,7 @@
|
|||
(defun mu4e~speedbar-bookmark (&optional _text token _ident)
|
||||
"Run bookmarked query TOKEN. TEXT and INDENT are not used."
|
||||
(dframe-with-attached-buffer
|
||||
(mu4e-search token current-prefix-arg)))
|
||||
(mu4e-headers-search token current-prefix-arg)))
|
||||
|
||||
;;;###autoload
|
||||
(defun mu4e-speedbar-buttons (&optional _buffer)
|
||||
|
|
|
@ -1,320 +0,0 @@
|
|||
;;; mu4e-update.el -- part of mu4e, -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2020 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Updating the mu4e message: calling a mail retrieval program
|
||||
;; and re-running the index.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'mu4e-helpers)
|
||||
(require 'mu4e-server)
|
||||
|
||||
;;; Customization
|
||||
|
||||
(defcustom mu4e-get-mail-command "true"
|
||||
"Shell command to run to retrieve new mail.
|
||||
Common values are \"offlineimap\", \"fetchmail\" or \"mbsync\", but
|
||||
arbitrary shell-commands can be used.
|
||||
|
||||
When set to the literal string \"true\" (the default), the
|
||||
command simply finishes successfully (running the 'true' command)
|
||||
without retrieving any mail. This can be useful when mail is
|
||||
already retrieved in another way."
|
||||
:type 'string
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-index-update-error-warning t
|
||||
"Whether to display warnings during the retrieval process.
|
||||
This depends on the `mu4e-get-mail-command' exit code."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-update-error-continue t
|
||||
"Whether to continue with indexing after an error during retrieval."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-update-in-background t
|
||||
"Whether to retrieve mail in the background."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-cleanup t
|
||||
"Whether to run a cleanup phase after indexing.
|
||||
|
||||
That is, validate that each message in the message store has a
|
||||
corresponding message file in the filesystem.
|
||||
|
||||
Having this option as t ensures that no non-existing messages are
|
||||
shown but can slow with large message stores on slow file-systems."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-lazy-check nil
|
||||
"Whether to only use a 'lazy check' during reindexing.
|
||||
This influences how we decide whether a message
|
||||
needs (re)indexing or not.
|
||||
|
||||
When this is set to non-nil, mu only uses the directory
|
||||
timestamps to decide whether it needs to check the messages
|
||||
beneath it. This makes indexing much faster, but might miss some
|
||||
changes. For this, you might want to occasionally call
|
||||
`mu4e-update-index-nonlazy'."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-update-interval nil
|
||||
"Number of seconds between mail retrieval/indexing.
|
||||
If nil, don't update automatically. Note, changes in
|
||||
`mu4e-update-interval' only take effect after restarting mu4e."
|
||||
:type '(choice (const :tag "No automatic update" nil)
|
||||
(integer :tag "Seconds"))
|
||||
:group 'mu4e
|
||||
:safe 'integerp)
|
||||
|
||||
(defvar mu4e-update-pre-hook nil
|
||||
"Hook run just *before* the mail-retrieval / database updating process starts.
|
||||
You can use this hook for example to `mu4e-get-mail-command' with
|
||||
some specific setting.")
|
||||
|
||||
(defcustom mu4e-hide-index-messages nil
|
||||
"Whether to hide the \"Indexing...\" and contacts messages."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
|
||||
(defvar mu4e-index-updated-hook nil
|
||||
"Hook run when the indexing process had one or more updated messages.
|
||||
This can be used as a simple way to invoke some action when new
|
||||
messages appear, but note that an update in the index does not
|
||||
necessarily mean a new message.")
|
||||
|
||||
|
||||
(defvar mu4e-message-changed-hook nil
|
||||
"Hook run when there is a message changed in db.
|
||||
For new messages, it depends on `mu4e-index-updated-hook'. This
|
||||
can be used as a simple way to invoke some action when a message
|
||||
changed.")
|
||||
|
||||
(make-obsolete-variable 'mu4e-msg-changed-hook
|
||||
'mu4e-message-changed-hook "0.9.19")
|
||||
|
||||
|
||||
|
||||
;;; Internal variables
|
||||
(defvar mu4e--progress-reporter nil
|
||||
"Internal, the progress reporter object.")
|
||||
(defvar mu4e--update-timer nil
|
||||
"The mu4e update timer.")
|
||||
(defconst mu4e--update-name " *mu4e-update*"
|
||||
"Name of the process and buffer to update mail.")
|
||||
(defconst mu4e--update-buffer-height 8
|
||||
"Height of the mu4e message retrieval/update buffer.")
|
||||
(defvar mu4e--get-mail-ask-password "mu4e get-mail: Enter password: "
|
||||
"Query string for `mu4e-get-mail-command' password.")
|
||||
(defvar mu4e--get-mail-password-regexp "^Remote: Enter password: $"
|
||||
"Regexp for a `mu4e-get-mail-command' password query.")
|
||||
|
||||
|
||||
(defun mu4e--get-mail-process-filter (proc msg)
|
||||
"Filter the MSG output of the `mu4e-get-mail-command' PROC.
|
||||
|
||||
Currently the filter only checks if the command asks for a
|
||||
password by matching the output against
|
||||
`mu4e~get-mail-password-regexp'. The messages are inserted into
|
||||
the process buffer.
|
||||
|
||||
Also scrolls to the final line, and update the progress
|
||||
throbber."
|
||||
(when mu4e--progress-reporter
|
||||
(progress-reporter-update mu4e--progress-reporter))
|
||||
|
||||
(when (string-match mu4e--get-mail-password-regexp msg)
|
||||
(if (process-get proc 'x-interactive)
|
||||
(process-send-string proc
|
||||
(concat (read-passwd mu4e--get-mail-ask-password)
|
||||
"\n"))
|
||||
;; TODO kill process?
|
||||
(mu4e-error "Unrecognized password request")))
|
||||
(when (process-buffer proc)
|
||||
(let ((inhibit-read-only t)
|
||||
(procwin (get-buffer-window (process-buffer proc))))
|
||||
;; Insert at end of buffer. Leave point alone.
|
||||
(with-current-buffer (process-buffer proc)
|
||||
(goto-char (point-max))
|
||||
(if (string-match ".*\r\\(.*\\)" msg)
|
||||
(progn
|
||||
;; kill even with \r
|
||||
(end-of-line)
|
||||
(let ((end (point)))
|
||||
(beginning-of-line)
|
||||
(delete-region (point) end))
|
||||
(insert (match-string 1 msg)))
|
||||
(insert msg)))
|
||||
;; Auto-scroll unless user is interacting with the window.
|
||||
(when (and (window-live-p procwin)
|
||||
(not (eq (selected-window) procwin)))
|
||||
(with-selected-window procwin
|
||||
(goto-char (point-max)))))))
|
||||
|
||||
|
||||
(defun mu4e-index-message (frm &rest args)
|
||||
"Display FRM with ARGS like `mu4e-message' for index messages.
|
||||
However, if `mu4e-hide-index-messages' is non-nil, do not display anything."
|
||||
(unless mu4e-hide-index-messages
|
||||
(apply 'mu4e-message frm args)))
|
||||
|
||||
(defun mu4e-update-index ()
|
||||
"Update the mu4e index."
|
||||
(interactive)
|
||||
(mu4e--server-index mu4e-index-cleanup mu4e-index-lazy-check))
|
||||
|
||||
(defun mu4e-update-index-nonlazy ()
|
||||
"Update the mu4e index non-lazily.
|
||||
This is just a convenience wrapper for indexing the non-lazy way
|
||||
if you otherwise want to use `mu4e-index-lazy-check'."
|
||||
(interactive)
|
||||
(let ((mu4e-index-cleanup t) (mu4e-index-lazy-check nil))
|
||||
(mu4e-update-index)))
|
||||
|
||||
(defvar mu4e--update-buffer nil
|
||||
"The buffer of the update process when updating.")
|
||||
|
||||
(define-derived-mode mu4e--update-mail-mode
|
||||
special-mode "mu4e:update"
|
||||
"Major mode used for retrieving new e-mail messages in `mu4e'.")
|
||||
|
||||
(define-key mu4e--update-mail-mode-map (kbd "q") 'mu4e-kill-update-mail)
|
||||
|
||||
(defun mu4e--temp-window (buf height)
|
||||
"Create a temporary window with HEIGHT at the bottom BUF."
|
||||
(let ((win
|
||||
(split-window
|
||||
(frame-root-window)
|
||||
(- (window-height (frame-root-window)) height))))
|
||||
(set-window-buffer win buf)
|
||||
(set-window-dedicated-p win t)
|
||||
win))
|
||||
|
||||
(defun mu4e--update-sentinel-func (proc _msg)
|
||||
"Sentinel function for the update process PROC."
|
||||
(when mu4e--progress-reporter
|
||||
(progress-reporter-done mu4e--progress-reporter)
|
||||
(setq mu4e--progress-reporter nil))
|
||||
(unless mu4e-hide-index-messages
|
||||
(message nil))
|
||||
(if (or (not (eq (process-status proc) 'exit))
|
||||
(/= (process-exit-status proc) 0))
|
||||
(progn
|
||||
(when mu4e-index-update-error-warning
|
||||
(mu4e-message "Update process returned with non-zero exit code")
|
||||
(sit-for 5))
|
||||
(when mu4e-index-update-error-continue
|
||||
(mu4e-update-index)))
|
||||
(mu4e-update-index))
|
||||
(when (buffer-live-p mu4e--update-buffer)
|
||||
(unless (eq mu4e-split-view 'single-window)
|
||||
(mapc #'delete-window (get-buffer-window-list mu4e--update-buffer)))
|
||||
(kill-buffer mu4e--update-buffer)))
|
||||
|
||||
;; complicated function, as it:
|
||||
;; - needs to check for errors
|
||||
;; - (optionally) pop-up a window
|
||||
;; - (optionally) check password requests
|
||||
(defun mu4e--update-mail-and-index-real (run-in-background)
|
||||
"Get a new mail by running `mu4e-get-mail-command'.
|
||||
If
|
||||
RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
|
||||
run in the background; otherwise, pop up a window."
|
||||
(let* ((process-connection-type t)
|
||||
(proc (start-process-shell-command
|
||||
"mu4e-update" mu4e--update-name
|
||||
mu4e-get-mail-command))
|
||||
(buf (process-buffer proc))
|
||||
(win (or run-in-background
|
||||
(mu4e--temp-window buf mu4e--update-buffer-height))))
|
||||
(setq mu4e--update-buffer buf)
|
||||
(when (window-live-p win)
|
||||
(with-selected-window win
|
||||
;; ;;(switch-to-buffer buf)
|
||||
;; (set-window-dedicated-p win t)
|
||||
(erase-buffer)
|
||||
(insert "\n") ;; FIXME -- needed so output starts
|
||||
(mu4e--update-mail-mode)))
|
||||
(setq mu4e--progress-reporter
|
||||
(unless mu4e-hide-index-messages
|
||||
(make-progress-reporter
|
||||
(mu4e-format "Retrieving mail..."))))
|
||||
(set-process-sentinel proc 'mu4e--update-sentinel-func)
|
||||
;; if we're running in the foreground, handle password requests
|
||||
(unless run-in-background
|
||||
(process-put proc 'x-interactive (not run-in-background))
|
||||
(set-process-filter proc 'mu4e--get-mail-process-filter))))
|
||||
|
||||
(defun mu4e-update-mail-and-index (run-in-background)
|
||||
"Get a new mail by running `mu4e-get-mail-command'.
|
||||
If RUN-IN-BACKGROUND is non-nil (or called with prefix-argument),
|
||||
run in the background; otherwise, pop up a window."
|
||||
(interactive "P")
|
||||
(unless mu4e-get-mail-command
|
||||
(mu4e-error "`mu4e-get-mail-command' is not defined"))
|
||||
(if (and (buffer-live-p mu4e--update-buffer)
|
||||
(process-live-p (get-buffer-process mu4e--update-buffer)))
|
||||
(mu4e-message "Update process is already running")
|
||||
(progn
|
||||
(run-hooks 'mu4e-update-pre-hook)
|
||||
(mu4e--update-mail-and-index-real run-in-background))))
|
||||
|
||||
(defun mu4e-kill-update-mail ()
|
||||
"Stop the update process by killing it."
|
||||
(interactive)
|
||||
(let* ((proc (and (buffer-live-p mu4e--update-buffer)
|
||||
(get-buffer-process mu4e--update-buffer))))
|
||||
(when (process-live-p proc)
|
||||
(kill-process proc t))))
|
||||
|
||||
(define-obsolete-function-alias 'mu4e-interrupt-update-mail
|
||||
'mu4e-kill-update-mail "1.0-alpha0")
|
||||
|
||||
(define-minor-mode mu4e-update-minor-mode
|
||||
"Mode for triggering mu4e updates."
|
||||
:global nil
|
||||
:init-value nil ;; disabled by default
|
||||
:group 'mu4e
|
||||
:lighter ""
|
||||
:keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-S-u") #'mu4e-update-mail-and-index)
|
||||
;; for terminal users
|
||||
(define-key map (kbd "C-c C-u") #'mu4e-update-mail-and-index)
|
||||
map))
|
||||
|
||||
(provide 'mu4e-update)
|
||||
;;; mu4e-update.el ends here
|
File diff suppressed because it is too large
Load Diff
|
@ -26,13 +26,139 @@
|
|||
|
||||
(require 'mu4e-meta)
|
||||
(require 'message)
|
||||
(require 'mu4e-helpers)
|
||||
|
||||
;;; Configuration
|
||||
|
||||
(declare-function mu4e-error "mu4e-utils")
|
||||
|
||||
;;; Customization
|
||||
|
||||
(defgroup mu4e nil
|
||||
"Mu4e - an email-client for Emacs."
|
||||
"mu4e - mu for emacs"
|
||||
:group 'mail)
|
||||
|
||||
(defcustom mu4e-mu-home nil
|
||||
"Location of an alternate mu home dir. If not set, use the
|
||||
defaults, based on the XDG Base Directory Specification."
|
||||
:group 'mu4e
|
||||
:type '(choice (const :tag "Default location" nil)
|
||||
(directory :tag "Specify location"))
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-mu-binary (executable-find "mu")
|
||||
"Name of the mu-binary to use.
|
||||
If it cannot be found in your PATH, you can specify the full
|
||||
path."
|
||||
:type 'file
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-mu-debug nil
|
||||
"Whether to run the mu binary in debug-mode.
|
||||
Setting this to t increases the amount of information in the log."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(make-obsolete-variable 'mu4e-maildir
|
||||
"determined by server; see `mu4e-root-maildir'." "1.3.8")
|
||||
|
||||
(defcustom mu4e-org-support t
|
||||
"Support org-mode links."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defgroup mu4e-view nil
|
||||
"Settings for the message view."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-view-use-old nil
|
||||
"If non-nil, use the old viewer.
|
||||
Otherwise, use the new, Gnus-based viewer."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
(make-obsolete-variable 'mu4e-view-use-gnus 'mu4e-view-use-old "1.5.10")
|
||||
|
||||
(defcustom mu4e-speedbar-support nil
|
||||
"Support having a speedbar to navigate folders/bookmarks."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-get-mail-command "true"
|
||||
"Shell command to run to retrieve new mail.
|
||||
Common values are \"offlineimap\", \"fetchmail\" or \"mbsync\", but
|
||||
arbitrary shell-commands can be used.
|
||||
|
||||
When set to the literal string \"true\" (the default), the
|
||||
command simply finishes successfully (running the 'true' command)
|
||||
without retrieving any mail. This can be useful when mail is
|
||||
already retrieved in another way."
|
||||
:type 'string
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
(defcustom mu4e-index-update-error-warning t
|
||||
"Whether to display warnings during the retrieval process.
|
||||
This depends on the `mu4e-get-mail-command' exit code."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-update-error-continue t
|
||||
"Whether to continue with indexing after an error during retrieval."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-update-in-background t
|
||||
"Whether to retrieve mail in the background."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-cleanup t
|
||||
"Whether to run a cleanup phase after indexing.
|
||||
|
||||
That is, validate that each message in the message store has a
|
||||
corresponding message file in the filesystem.
|
||||
|
||||
Having this option as t ensures that no non-existing messages are
|
||||
shown but can slow with large message stores on slow file-systems."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-index-lazy-check nil
|
||||
"Whether to only use a 'lazy check' during reindexing.
|
||||
This influences how we decide whether a message
|
||||
needs (re)indexing or not.
|
||||
|
||||
When this is set to non-nil, mu only uses the directory
|
||||
timestamps to decide whether it needs to check the messages
|
||||
beneath it. This makes indexing much faster, but might miss some
|
||||
changes. For this, you might want to occasionally call
|
||||
`mu4e-update-index-nonlazy'."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-update-interval nil
|
||||
"Number of seconds between mail retrieval/indexing.
|
||||
If nil, don't update automatically. Note, changes in
|
||||
`mu4e-update-interval' only take effect after restarting mu4e."
|
||||
:type '(choice (const :tag "No automatic update" nil)
|
||||
(integer :tag "Seconds"))
|
||||
:group 'mu4e
|
||||
:safe 'integerp)
|
||||
|
||||
(defvar mu4e-update-pre-hook nil
|
||||
"Hook run just *before* the mail-retrieval / database updating process starts.
|
||||
You can use this hook for example to `mu4e-get-mail-command' with
|
||||
some specific setting.")
|
||||
|
||||
(defcustom mu4e-hide-index-messages nil
|
||||
"Whether to hide the \"Indexing...\" and contacts messages."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-headers-include-related t
|
||||
"With this option set to non-nil, not just return the matches for
|
||||
a searches, but also messages that are related (through their
|
||||
|
@ -49,13 +175,510 @@ and offlineimap."
|
|||
:type 'boolean
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(defcustom mu4e-change-filenames-when-moving nil
|
||||
"Change message file names when moving them.
|
||||
When moving messages to different folders, normally mu/mu4e keep
|
||||
the base filename the same (the flags-part of the filename may
|
||||
change still). With this option set to non-nil, mu4e instead
|
||||
changes the filename. This latter behavior works better with some
|
||||
IMAP-synchronization programs such as mbsync; the default works
|
||||
better with e.g. offlineimap."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-attachment-dir (expand-file-name "~/")
|
||||
"Default directory for attaching and saving attachments.
|
||||
|
||||
This can be either a string (a file system path), or a function
|
||||
that takes a filename and the mime-type as arguments, and returns
|
||||
the attachment dir. See Info node `(mu4e) Attachments' for
|
||||
details.
|
||||
|
||||
When this called for composing a message, both filename and
|
||||
mime-type are nill."
|
||||
:type 'directory
|
||||
:group 'mu4e
|
||||
:safe 'stringp)
|
||||
|
||||
;; don't use the older vars anymore
|
||||
(make-obsolete-variable 'mu4e-user-mail-address-regexp
|
||||
'mu4e-user-mail-address-list "0.9.9.x")
|
||||
(make-obsolete-variable 'mu4e-my-email-addresses
|
||||
'mu4e-user-mail-address-list "0.9.9.x")
|
||||
(make-obsolete-variable 'mu4e-user-mail-address-list
|
||||
"determined by server; see `mu4e-personal-addresses'." "1.3.8")
|
||||
|
||||
(defcustom mu4e-use-fancy-chars nil
|
||||
"When set, allow fancy (Unicode) characters for marks/threads.
|
||||
You can customize the exact fancy characters used with
|
||||
`mu4e-marks' and various `mu4e-headers-..-mark' and
|
||||
`mu4e-headers..-prefix' variables."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-date-format-long "%c"
|
||||
"Date format to use in the message view.
|
||||
Follows the format of `format-time-string'."
|
||||
:type 'string
|
||||
:group 'mu4e)
|
||||
|
||||
|
||||
(defcustom mu4e-modeline-max-width 42
|
||||
"Determines the maximum length of the modeline string.
|
||||
If the string exceeds this limit, it will be truncated to fit."
|
||||
:type 'integer
|
||||
:group 'mu4e)
|
||||
|
||||
(defvar mu4e-debug nil
|
||||
"When set to non-nil, log debug information to the *mu4e-log* buffer.")
|
||||
|
||||
;; for backward compatibility, when a bookmark was defined with defstruct.
|
||||
(cl-defun make-mu4e-bookmark (&key name query key)
|
||||
"Create a mu4e proplist with the following elements:
|
||||
- `name': the user-visible name of the bookmark
|
||||
- `key': a single key to search for this bookmark
|
||||
- `query': the query for this bookmark. Either a literal string or a function
|
||||
that evaluates to a string."
|
||||
`(:name ,name :query ,query :key ,key))
|
||||
(make-obsolete 'make-mu4e-bookmark "`unneeded; `mu4e-bookmarks'
|
||||
are plists" "1.3.7")
|
||||
|
||||
(defcustom mu4e-bookmarks
|
||||
'(( :name "Unread messages"
|
||||
:query "flag:unread AND NOT flag:trashed"
|
||||
:key ?u)
|
||||
( :name "Today's messages"
|
||||
:query "date:today..now"
|
||||
:key ?t)
|
||||
( :name "Last 7 days"
|
||||
:query "date:7d..now"
|
||||
:hide-unread t
|
||||
:key ?w)
|
||||
( :name "Messages with images"
|
||||
:query "mime:image/*"
|
||||
:key ?p))
|
||||
"List of pre-defined queries that are shown on the main screen.
|
||||
|
||||
Each of the list elements is a plist with at least:
|
||||
`:name' - the name of the query
|
||||
`:query' - the query expression or function
|
||||
`:key' - the shortcut key.
|
||||
|
||||
Note that the :query parameter can be a function/lambda.
|
||||
|
||||
Optionally, you can add the following:
|
||||
`:hide' - if t, the bookmark is hidden from the main-view and
|
||||
speedbar.
|
||||
`:hide-unread' - do not show the counts of unread/total number
|
||||
of matches for the query in the main-view. This can be useful
|
||||
if a bookmark uses a very slow query. :hide-unread
|
||||
is implied from :hide. Furthermore, it is implied if
|
||||
`:query' is a function.
|
||||
|
||||
Queries used to determine the unread/all counts do _not_ apply
|
||||
`mu4e-query-rewrite-function'; nor do they discard duplicate or
|
||||
unreadable messages (for efficiency). Thus, the numbers shown may
|
||||
differ from the number you get from a 'real' query."
|
||||
:type '(repeat (plist))
|
||||
:version "1.3.9"
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-query-rewrite-function 'identity
|
||||
"Function that takes a search expression string, and returns a
|
||||
possibly changed search expression string.
|
||||
|
||||
This function is applied on the search expression just before
|
||||
searching, and allows users to modify the query.
|
||||
|
||||
For instance, we could change and of workmail into
|
||||
\"maildir:/long-path-to-work-related-emails\", by setting the function
|
||||
|
||||
(setq mu4e-query-rewrite-function
|
||||
(lambda(expr)
|
||||
(replace-regexp-in-string \"workmail\"
|
||||
\"maildir:/long-path-to-work-related-emails\" expr)))
|
||||
|
||||
It is good to remember that the replacement does not understand
|
||||
anything about the query, it just does text replacement."
|
||||
:type 'function
|
||||
:group 'mu4e)
|
||||
|
||||
(defun mu4e-bookmarks ()
|
||||
"Get `mu4e-bookmarks' in the (new) format, converting from the
|
||||
old format if needed."
|
||||
(cl-map 'list
|
||||
(lambda (item)
|
||||
(if (and (listp item) (= (length item) 3))
|
||||
`(:name ,(nth 1 item)
|
||||
:query ,(nth 0 item)
|
||||
:key ,(nth 2 item))
|
||||
item))
|
||||
mu4e-bookmarks))
|
||||
|
||||
|
||||
(defcustom mu4e-split-view 'horizontal
|
||||
"How to show messages / headers.
|
||||
A symbol which is either:
|
||||
* `horizontal': split horizontally (headers on top)
|
||||
* `vertical': split vertically (headers on the left).
|
||||
* `single-window': view and headers in one window (mu4e will try not to
|
||||
touch your window layout), main view in minibuffer
|
||||
* anything else: don't split (show either headers or messages,
|
||||
not both)
|
||||
Also see `mu4e-headers-visible-lines'
|
||||
and `mu4e-headers-visible-columns'."
|
||||
:type '(choice (const :tag "Split horizontally" horizontal)
|
||||
(const :tag "Split vertically" vertical)
|
||||
(const :tag "Single window" single-window)
|
||||
(const :tag "Don't split" nil))
|
||||
:group 'mu4e-headers)
|
||||
|
||||
(defcustom mu4e-view-max-specpdl-size 4096
|
||||
"The value of `max-specpdl-size' for displaying messages with Gnus."
|
||||
:type 'integer
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-show-images nil
|
||||
"If non-nil, automatically display images in the view
|
||||
buffer. Applies only to the _old_ message view."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-auto-mark-as-read t
|
||||
"Automatically mark messages are 'read' when you read them.
|
||||
This is the default behavior, but can be turned off, for example
|
||||
when using a read-only file-system.
|
||||
|
||||
This can also be set to a function; if so, receives a message
|
||||
plist which should evaluate to nil if the message should *not* be
|
||||
marked as read-only, or non-nil otherwise."
|
||||
:type '(choice
|
||||
boolean
|
||||
function)
|
||||
:group 'mu4e-view)
|
||||
|
||||
|
||||
(defcustom mu4e-confirm-quit t
|
||||
"Whether to confirm to quit mu4e."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-cited-regexp
|
||||
"^\\(\\([[:alpha:]]+\\)\\|\\( *\\)\\)\\(\\(>+ ?\\)+\\)"
|
||||
"Regex that determines whether a line is a citation.
|
||||
This recognizes lines starting with numbers of '>'
|
||||
and spaces as well as citations of the type \"John> ... \"."
|
||||
:type 'string
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-completing-read-function 'ido-completing-read
|
||||
"Function to be used to receive user-input during completion.
|
||||
This is used to receive the name of the maildir to switch to via
|
||||
`mu4e~headers-jump-to-maildir'.
|
||||
|
||||
Suggested possible values are:
|
||||
* `completing-read': built-in completion method
|
||||
* `ido-completing-read': dynamic completion within the minibuffer."
|
||||
:type 'function
|
||||
:options '(completing-read ido-completing-read)
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-context-policy 'ask-if-none
|
||||
"The policy to determine the context when entering the mu4e main view.
|
||||
|
||||
If the value is `always-ask', ask the user unconditionally.
|
||||
|
||||
In all other cases, if any context matches (using its match
|
||||
function), this context is used. Otherwise, if none of the
|
||||
contexts match, we have the following choices:
|
||||
|
||||
- `pick-first': pick the first of the contexts available (ie. the default)
|
||||
- `ask': ask the user
|
||||
- `ask-if-none': ask if there is no context yet, otherwise leave it as it is
|
||||
- nil: return nil; leaves the current context as is.
|
||||
|
||||
Also see `mu4e-compose-context-policy'."
|
||||
:type '(choice
|
||||
(const :tag "Always ask what context to use, even if one matches"
|
||||
always-ask)
|
||||
(const :tag "Ask if none of the contexts match" ask)
|
||||
(const :tag "Ask when there's no context yet" ask-if-none)
|
||||
(const :tag "Pick the first context if none match" pick-first)
|
||||
(const :tag "Don't change the context when none match" nil))
|
||||
:group 'mu4e)
|
||||
|
||||
;;;; Crypto
|
||||
|
||||
(defgroup mu4e-crypto nil
|
||||
"Crypto-related settings."
|
||||
:group 'mu4e)
|
||||
|
||||
(make-obsolete-variable 'mu4e-auto-retrieve-keys "no longer used." "1.3.1")
|
||||
|
||||
(defcustom mu4e-decryption-policy t
|
||||
"Policy for dealing with encrypted parts.
|
||||
The setting is a symbol:
|
||||
* t: try to decrypt automatically
|
||||
* `ask': ask before decrypting anything
|
||||
* nil: don't try to decrypt anything.
|
||||
|
||||
Note that this is not used unless `mu4e-view-use-old' is enabled."
|
||||
:type '(choice (const :tag "Try to decrypt automatically" t)
|
||||
(const :tag "Ask before decrypting anything" ask)
|
||||
(const :tag "Don't try to decrypt anything" nil))
|
||||
:group 'mu4e-crypto)
|
||||
|
||||
;;;; Address completion
|
||||
;;
|
||||
;; We put these options here rather than in mu4e-compose, because
|
||||
;; mu4e-utils needs them.
|
||||
|
||||
(defgroup mu4e-compose nil
|
||||
"Message-composition related settings."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-compose-complete-addresses t
|
||||
"Whether to do auto-completion of e-mail addresses."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-complete-only-personal nil
|
||||
"Whether to consider only 'personal' e-mail addresses for completion.
|
||||
That is, addresses from messages where user was explicitly in one
|
||||
of the address fields (this excludes mailing list messages).
|
||||
These addresses are the ones specified with `mu init'."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-complete-only-after "2014-01-01"
|
||||
"Consider only contacts last seen after this date.
|
||||
|
||||
Date must be a string of the form YYY-MM-DD.
|
||||
|
||||
This is useful for limiting a potentially enormous set of
|
||||
contacts for auto-completion to just those that are present in
|
||||
the e-mail corpus in recent timses. Set to nil to not have any
|
||||
time-based restriction."
|
||||
:type 'string
|
||||
:group 'mu4e-compose)
|
||||
|
||||
;; names and mail-addresses can be mapped onto their canonical
|
||||
;; counterpart. use the customizeable function
|
||||
;; mu4e-canonical-contact-function to do that. below the identity
|
||||
;; function for mapping a contact onto the canonical one.
|
||||
(defun mu4e-contact-identity (contact)
|
||||
"Return the name and the mail-address of a CONTACT.
|
||||
It is used as the identity function for converting contacts to
|
||||
their canonical counterpart; useful as an example."
|
||||
(let ((name (plist-get contact :name))
|
||||
(mail (plist-get contact :mail)))
|
||||
(list :name name :mail mail)))
|
||||
|
||||
(make-obsolete-variable 'mu4e-contact-rewrite-function
|
||||
"mu4e-contact-process-function (see docstring)" "mu4e 1.3.2")
|
||||
(make-obsolete-variable 'mu4e-compose-complete-ignore-address-regexp
|
||||
"mu4e-contact-process-function (see docstring)" "mu4e 1.3.2")
|
||||
|
||||
(defcustom mu4e-contact-process-function
|
||||
(lambda(addr) ;; filter-out no-reply addresses
|
||||
(unless (string-match-p "no[t]?[-\\.]?repl\\(y\\|ies\\)" addr)
|
||||
addr))
|
||||
"Function for processing contact information for use in auto-completion.
|
||||
|
||||
The function receives the contact as a string, e.g
|
||||
\"Foo Bar <foo.bar@example.com>\"
|
||||
\"cuux@example.com\"
|
||||
|
||||
The function should return either:
|
||||
- nil: do not use this contact for completion
|
||||
- the (possibly rewritten) address, which must be
|
||||
an RFC-2822-compatible e-mail address."
|
||||
:type 'function
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-reply-ignore-address
|
||||
'("no-?reply")
|
||||
"Addresses to prune when doing wide replies.
|
||||
|
||||
This can be a regexp matching the address, a list of regexps or a
|
||||
predicate function. A value of nil keeps all the addresses."
|
||||
:type '(choice
|
||||
(const nil)
|
||||
function
|
||||
string
|
||||
(repeat string))
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-reply-recipients 'ask
|
||||
"Which recipients to use when replying to a message.
|
||||
May be 'ask, 'all, 'sender. Note that that only applies to
|
||||
non-mailing-list message; for those, mu4e always asks."
|
||||
:type '(choice ask
|
||||
all
|
||||
sender)
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-reply-to-address nil
|
||||
"The Reply-To address.
|
||||
Useful when this is not equal to the From: address."
|
||||
:type 'string
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defcustom mu4e-compose-forward-as-attachment nil
|
||||
"Whether to forward messages as attachments instead of inline."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
;; backward compatibility
|
||||
(make-obsolete-variable 'mu4e-reply-to-address
|
||||
'mu4e-compose-reply-to-address
|
||||
"v0.9.9")
|
||||
|
||||
(defcustom mu4e-compose-keep-self-cc nil
|
||||
"When non-nil. keep your e-mail address in Cc: when replying."
|
||||
:type 'boolean
|
||||
:group 'mu4e-compose)
|
||||
|
||||
(defvar mu4e-compose-parent-message nil
|
||||
"The parent message plist.
|
||||
This is the message being replied to, forwarded or edited; used
|
||||
in `mu4e-compose-pre-hook'. For new messages, it is nil.")
|
||||
|
||||
;;;; Calendar
|
||||
|
||||
(defgroup mu4e-icalendar nil
|
||||
"Icalendar related settings."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-icalendar-trash-after-reply nil
|
||||
"If non-nil, trash the icalendar invitation after replying."
|
||||
:type 'boolean
|
||||
:group 'mu4e-icalendar)
|
||||
|
||||
(defcustom mu4e-icalendar-diary-file nil
|
||||
"If non-nil, the file in which to add events upon reply."
|
||||
:type '(choice (const :tag "Do not insert a diary entry" nil)
|
||||
(string :tag "Insert a diary entry in this file"))
|
||||
:group 'mu4e-icalendar)
|
||||
|
||||
|
||||
;;;; Folders
|
||||
|
||||
(defgroup mu4e-folders nil
|
||||
"Special folders."
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-drafts-folder "/drafts"
|
||||
"Your folder for draft messages, relative to the root maildir.
|
||||
For instance, \"/drafts\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. Note, the message
|
||||
parameter refers to the original message being replied to / being
|
||||
forwarded / re-edited and is nil otherwise. `mu4e-drafts-folder'
|
||||
is only evaluated once."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-refile-folder "/archive"
|
||||
"Your folder for refiling messages, relative to the root maildir.
|
||||
For instance \"/Archive\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. Note that the
|
||||
message parameter refers to the message-at-point."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-sent-folder "/sent"
|
||||
"Your folder for sent messages, relative to the root maildir.
|
||||
For instance, \"/Sent Items\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. Note that the
|
||||
message parameter refers to the original message being replied to
|
||||
/ being forwarded / re-edited, and is nil otherwise."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-trash-folder "/trash"
|
||||
"Your folder for trashed messages, relative to the root maildir.
|
||||
For instance, \"/trash\". Instead of a string, may also be a
|
||||
function that takes a message (a msg plist, see
|
||||
`mu4e-message-field'), and returns a folder. When using
|
||||
`mu4e-trash-folder' in the headers view (when marking messages
|
||||
for trash). Note that the message parameter refers to the
|
||||
message-at-point. When using it when composing a message (see
|
||||
`mu4e-sent-messages-behavior'), this refers to the original
|
||||
message being replied to / being forwarded / re-edited, and is
|
||||
nil otherwise."
|
||||
:type '(choice
|
||||
(string :tag "Folder name")
|
||||
(function :tag "Function return folder name"))
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-maildir-shortcuts nil
|
||||
"A list of maildir shortcuts.
|
||||
This makes it possible to quickly go to a particular
|
||||
maildir (folder), or quickly moving messages to them (e.g., for
|
||||
archiving or refiling).
|
||||
|
||||
Each of the list elements is a plist with at least:
|
||||
`:maildir' - the maildir for the shortcut (e.g. \"/archive\")
|
||||
`:key' - the shortcut key.
|
||||
|
||||
Optionally, you can add the following:
|
||||
`:hide' - if t, the shortcut is hidden from the main-view and
|
||||
speedbar.
|
||||
`:hide-unread' - do not show the counts of unread/total number
|
||||
of matches for the maildir in the main-view, and is implied
|
||||
from `:hide'.
|
||||
|
||||
For backward compatibility, an older form is recognized as well:
|
||||
|
||||
(maildir . key), where MAILDIR is a maildir (such as
|
||||
\"/archive/\"), and key is a single character.
|
||||
|
||||
You can use these shortcuts in the headers and view buffers, for
|
||||
example with `mu4e-mark-for-move-quick' (or 'm', by default) or
|
||||
`mu4e-jump-to-maildir' (or 'j', by default), followed by the
|
||||
designated shortcut character for the maildir.
|
||||
|
||||
Unlike in search queries, folder names with spaces in them must
|
||||
NOT be quoted, since mu4e does this for you."
|
||||
:type '(repeat (cons (string :tag "Maildir") character))
|
||||
:version "1.3.9"
|
||||
:group 'mu4e-folders)
|
||||
|
||||
(defcustom mu4e-maildir-info-delimiter
|
||||
(if (member system-type '(ms-dos windows-nt cygwin))
|
||||
";" ":")
|
||||
"Separator character between message identifier and flags.
|
||||
It defaults to ':' on most platforms, except on Windows,
|
||||
where it is not allowed and we use ';' for compatibility
|
||||
with mbsync, offlineimap and other programs."
|
||||
:type 'string
|
||||
:group 'mu4e-folders)
|
||||
|
||||
|
||||
(defun mu4e-maildir-shortcuts ()
|
||||
"Get `mu4e-maildir-shortcuts' in the (new) format, converting
|
||||
from the old format if needed."
|
||||
(cl-map 'list
|
||||
(lambda (item) ;; convert from old format?
|
||||
(if (and (consp item) (not (consp (cdr item))))
|
||||
`(:maildir ,(car item) :key ,(cdr item))
|
||||
item))
|
||||
mu4e-maildir-shortcuts))
|
||||
|
||||
(defcustom mu4e-display-update-status-in-modeline nil
|
||||
"Non-nil value will display the update status in the modeline."
|
||||
:group 'mu4e
|
||||
:type 'boolean)
|
||||
|
||||
;;; Faces
|
||||
|
||||
(defgroup mu4e-faces nil
|
||||
|
@ -150,6 +773,11 @@ I.e. a message with the draft flag set."
|
|||
"Face for a header title in the headers view."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-context-face
|
||||
'((t :inherit mu4e-title-face :weight bold))
|
||||
"Face for displaying the context in the modeline."
|
||||
:group 'mu4e-faces)
|
||||
|
||||
(defface mu4e-modeline-face
|
||||
'((t :inherit font-lock-string-face :weight bold))
|
||||
"Face for the query in the mode-line."
|
||||
|
@ -420,8 +1048,146 @@ header-view, not including, for instance, the message body.")
|
|||
|
||||
;;;; Main
|
||||
|
||||
(defvar mu4e-main-buffer-name " *mu4e-main*"
|
||||
"Name of the mu4e main view buffer. The default name starts
|
||||
with SPC and therefore is not visible in buffer list.")
|
||||
|
||||
|
||||
;;;; Headers
|
||||
|
||||
(defconst mu4e~headers-buffer-name "*mu4e-headers*"
|
||||
"Name of the buffer for message headers.")
|
||||
|
||||
(defvar mu4e~headers-last-query nil
|
||||
"The present (most recent) query.")
|
||||
|
||||
;;;; View
|
||||
|
||||
(defconst mu4e~view-buffer-name "*mu4e-view*"
|
||||
"Name for the message view buffer.")
|
||||
|
||||
(defconst mu4e~view-embedded-buffer-name " *mu4e-embedded-view*"
|
||||
"Name for the embedded message view buffer.")
|
||||
|
||||
;;;; Other
|
||||
|
||||
(defvar mu4e~contacts-hash nil
|
||||
"Hash that maps contacts (ie. 'name <e-mail>') to an integer for sorting.
|
||||
We need to keep this information around to quickly re-sort
|
||||
subsets of the contacts in the completions function in
|
||||
mu4e-compose.")
|
||||
|
||||
(defvar mu4e~server-props nil
|
||||
"Information we receive from the mu4e server process \(in the 'pong-handler').")
|
||||
|
||||
(defun mu4e-root-maildir()
|
||||
"Get the root maildir."
|
||||
(let ((root-maildir (and mu4e~server-props
|
||||
(plist-get mu4e~server-props :root-maildir))))
|
||||
(unless root-maildir
|
||||
(mu4e-error "root maildir unknown; did you start mu4e?"))
|
||||
root-maildir))
|
||||
|
||||
(defun mu4e-database-path()
|
||||
"Get the mu4e database path"
|
||||
(let ((path (and mu4e~server-props
|
||||
(plist-get mu4e~server-props :database-path))))
|
||||
(unless path
|
||||
(mu4e-error "database-path unknown; did you start mu4e?"))
|
||||
path))
|
||||
|
||||
(defun mu4e-personal-addresses(&optional no-regexp)
|
||||
"Get the list user's personal addresses, as passed to `mu init --my-address=...'.
|
||||
The address are either plain e-mail address or /regular
|
||||
expressions/. When NO_REGEXP is non-nil, do not include regexp
|
||||
address patterns (if any)."
|
||||
(seq-remove
|
||||
(lambda(addr) (and no-regexp (string-match-p "^/.*/" addr)))
|
||||
(when mu4e~server-props (plist-get mu4e~server-props :personal-addresses))))
|
||||
|
||||
(defun mu4e-server-version()
|
||||
"Get the server version, which should match mu4e's."
|
||||
(let ((version (and mu4e~server-props (plist-get mu4e~server-props :version))))
|
||||
(unless version
|
||||
(mu4e-error "version unknown; did you start mu4e?"))
|
||||
version))
|
||||
|
||||
|
||||
;;; Handler functions
|
||||
;;
|
||||
;; The handler functions define what happens when we receive a certain
|
||||
;; message from the server. Here we register our handler functions;
|
||||
;; these connect server messages to functions to handle them.
|
||||
;;
|
||||
;; These bindings form mu4e's central nervous system so it's not
|
||||
;; really recommended to override them (they reference various
|
||||
;; internal bits, which could change).
|
||||
|
||||
(defun mu4e~default-handler (&rest args)
|
||||
"Dummy handler function with arbitrary ARGS."
|
||||
(error "Not handled: %S" args))
|
||||
|
||||
(defvar mu4e-error-func 'mu4e-error-handler
|
||||
"Function called for each error received.
|
||||
The function is passed an error plist as argument. See
|
||||
`mu4e~proc-filter' for the format.")
|
||||
|
||||
(defvar mu4e-update-func 'mu4e~headers-update-handler
|
||||
"Function called for each :update sexp returned.
|
||||
The function is passed a msg sexp as argument.
|
||||
See `mu4e~proc-filter' for the format.")
|
||||
|
||||
(defvar mu4e-remove-func 'mu4e~headers-remove-handler
|
||||
"Function called for each :remove sexp returned.
|
||||
This happens when some message has been deleted. The function is
|
||||
passed the docid of the removed message.")
|
||||
|
||||
(defvar mu4e-sent-func 'mu4e~default-handler
|
||||
"Function called for each :sent sexp received.
|
||||
This happens when some message has been sent. The function is
|
||||
passed the docid and the draft-path of the sent message.")
|
||||
|
||||
(defvar mu4e-view-func 'mu4e~headers-view-handler
|
||||
"Function called for each single-message sexp.
|
||||
The function is passed a message sexp as argument. See
|
||||
`mu4e~proc-filter' for the format.")
|
||||
|
||||
(defvar mu4e-header-func 'mu4e~headers-header-handler
|
||||
"Function called for each message-header received.
|
||||
The function is passed a msg plist as argument. See
|
||||
`mu4e~proc-filter' for the format.")
|
||||
|
||||
(defvar mu4e-found-func 'mu4e~headers-found-handler
|
||||
"Function called for when we received a :found sexp.
|
||||
This happens after the headers have been returned, to report on
|
||||
the number of matches. See `mu4e~proc-filter' for the format.")
|
||||
|
||||
(defvar mu4e-erase-func 'mu4e~headers-clear
|
||||
"Function called we receive an :erase sexp.
|
||||
This before new headers are displayed, to clear the current
|
||||
headers buffer. See `mu4e~proc-filter' for the format.")
|
||||
|
||||
(defvar mu4e-compose-func 'mu4e~compose-handler
|
||||
"Function called for each compose message received.
|
||||
I.e., the original message that is used as basis for composing a
|
||||
new message (i.e., either a reply or a forward); the function is
|
||||
passed msg and a symbol (either reply or forward). See
|
||||
`mu4e~proc-filter' for the format of <msg-plist>.")
|
||||
|
||||
(defvar mu4e-info-func 'mu4e-info-handler
|
||||
"Function called for each (:info type ....) sexp received.
|
||||
from the server process.")
|
||||
|
||||
(defvar mu4e-pong-func 'mu4e~default-handler
|
||||
"Function called for each (:pong type ....) sexp received.")
|
||||
|
||||
(defvar mu4e-contacts-func 'mu4e-contacts-func
|
||||
"A function called for each (:contacts (<list-of-contacts>)
|
||||
sexp received from the server process.")
|
||||
|
||||
(defvar mu4e-temp-func 'mu4e~view-temp-handler
|
||||
"A function called for each (:temp <file> <cookie>) sexp.")
|
||||
|
||||
;;; Internals
|
||||
|
||||
(defvar mu4e~headers-view-win nil
|
||||
|
|
|
@ -0,0 +1,642 @@
|
|||
;;; mu4e-view-common.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; In this file we define common utils for 'old' and 'gnus' view mode.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'mu4e-utils) ;; utility functions
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-headers)
|
||||
(require 'mu4e-mark)
|
||||
(require 'mu4e-proc)
|
||||
(require 'mu4e-compose)
|
||||
(require 'mu4e-actions)
|
||||
(require 'mu4e-message)
|
||||
|
||||
(require 'comint)
|
||||
(require 'browse-url)
|
||||
(require 'button)
|
||||
(require 'epa)
|
||||
(require 'epg)
|
||||
(require 'thingatpt)
|
||||
|
||||
;;; Options
|
||||
|
||||
(defcustom mu4e-view-scroll-to-next t
|
||||
"Move to the next message when calling
|
||||
`mu4e-view-scroll-up-or-next' (typically bound to SPC) when at
|
||||
the end of a message. Otherwise, don't move to the next message."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-fields
|
||||
'(:from :to :cc :subject :flags :date :maildir :mailing-list :tags
|
||||
:attachments :signature :decryption)
|
||||
"Header fields to display in the message view buffer.
|
||||
For the complete list of available headers, see
|
||||
`mu4e-header-info'.
|
||||
|
||||
Note, when using the gnus-based viewer you can only use this add
|
||||
fields that are otherwise not shows; you can further tweak the
|
||||
fields using e.g. `gnus-article-hide-boring-headers',
|
||||
`gnus-article-hide-headers' etc., see the gnus documentation for
|
||||
details."
|
||||
:type (list 'symbol)
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-actions
|
||||
'( ("capture message" . mu4e-action-capture-message)
|
||||
("view in browser" . mu4e-action-view-in-browser)
|
||||
("show this thread" . mu4e-action-show-thread))
|
||||
"List of actions to perform on messages in view mode.
|
||||
The actions are cons-cells of the form:
|
||||
(NAME . FUNC)
|
||||
where:
|
||||
* NAME is the name of the action (e.g. \"Count lines\")
|
||||
* FUNC is a function which receives a message plist as an argument.
|
||||
|
||||
The first letter of NAME is used as a shortcut character."
|
||||
:group 'mu4e-view
|
||||
:type '(alist :key-type string :value-type function))
|
||||
|
||||
|
||||
;;; Old options
|
||||
|
||||
;; These don't do anything useful when in "gnus" mode, except for avoid errors
|
||||
;; for people that have these in their config.
|
||||
|
||||
(defcustom mu4e-view-show-addresses nil
|
||||
"Whether to initially show full e-mail addresses for contacts.
|
||||
Otherwise, just show their names. Ignored when using the gnus-based view."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
(make-obsolete-variable 'mu4e-view-wrap-lines nil "0.9.9-dev7")
|
||||
(make-obsolete-variable 'mu4e-view-hide-cited nil "0.9.9-dev7")
|
||||
|
||||
(defcustom mu4e-view-date-format "%c"
|
||||
"Date format to use in the message view.
|
||||
In the format of `format-time-string'. Ignored when using the gnus-based view."
|
||||
:type 'string
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-image-max-width 800
|
||||
"The maximum width for images to display.
|
||||
This is only effective if you're using an Emacs with Imagemagick
|
||||
support, and `mu4e-view-show-images' is non-nil. Ignored when
|
||||
using the gnus-based view."
|
||||
:type 'integer
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-image-max-height 600
|
||||
"The maximum height for images to display.
|
||||
This is only effective if you're using an Emacs with Imagemagick
|
||||
support, and `mu4e-view-show-images' is non-nil. Ignored when
|
||||
using the gnus-based view."
|
||||
:type 'integer
|
||||
:group 'mu4e-view)
|
||||
|
||||
|
||||
(defcustom mu4e-save-multiple-attachments-without-asking nil
|
||||
"If non-nil, saving multiple attachments asks once for a
|
||||
directory and saves all attachments in the chosen directory.
|
||||
Ignored when using the gnus-based view."
|
||||
:type 'boolean
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defcustom mu4e-view-attachment-assoc nil
|
||||
"Alist of (EXTENSION . PROGRAM).
|
||||
Specify which PROGRAM to use to open attachment with EXTENSION.
|
||||
Args EXTENSION and PROGRAM should be specified as strings.
|
||||
Ignored when using the gnus-based view."
|
||||
:group 'mu4e-view
|
||||
:type '(alist :key-type string :value-type string))
|
||||
|
||||
(defcustom mu4e-view-attachment-actions
|
||||
'( ("ssave" . mu4e-view-save-attachment-single)
|
||||
("Ssave multi" . mu4e-view-save-attachment-multi)
|
||||
("wopen-with" . mu4e-view-open-attachment-with)
|
||||
("ein-emacs" . mu4e-view-open-attachment-emacs)
|
||||
("dimport-in-diary" . mu4e-view-import-attachment-diary)
|
||||
("kimport-public-key" . mu4e-view-import-public-key)
|
||||
("|pipe" . mu4e-view-pipe-attachment))
|
||||
"List of actions to perform on message attachments.
|
||||
The actions are cons-cells of the form:
|
||||
(NAME . FUNC)
|
||||
where:
|
||||
* NAME is the name of the action (e.g. \"Count lines\")
|
||||
* FUNC is a function which receives two arguments: the message
|
||||
plist and the attachment number.
|
||||
The first letter of NAME is used as a shortcut character.
|
||||
Ignored when using the gnus-based view."
|
||||
:group 'mu4e-view
|
||||
:type '(alist :key-type string :value-type function))
|
||||
|
||||
;;; Keymaps
|
||||
|
||||
(defvar mu4e-view-header-field-keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [mouse-1] 'mu4e~view-header-field-fold)
|
||||
(define-key map (kbd "TAB") 'mu4e~view-header-field-fold)
|
||||
map)
|
||||
"Keymap used for header fields. Ignored when using the
|
||||
gnus-based view.")
|
||||
|
||||
(defvar mu4e-view-contacts-header-keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [mouse-2] 'mu4e~view-compose-contact)
|
||||
(define-key map "C" 'mu4e~view-compose-contact)
|
||||
(define-key map "c" 'mu4e~view-copy-contact)
|
||||
map)
|
||||
"Keymap used for the contacts in the header fields.
|
||||
Ignored when using the gnus-based view.")
|
||||
|
||||
(defvar mu4e-view-attachments-header-keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [mouse-1] 'mu4e~view-open-attach-from-binding)
|
||||
(define-key map [?\M-\r] 'mu4e~view-open-attach-from-binding)
|
||||
(define-key map [mouse-2] 'mu4e~view-save-attach-from-binding)
|
||||
(define-key map (kbd "<S-return>") 'mu4e~view-save-attach-from-binding)
|
||||
map)
|
||||
"Keymap used in the \"Attachments\" header field. Ignored when
|
||||
using the gnus-based view.")
|
||||
|
||||
;; Helpers
|
||||
|
||||
(defun mu4e~view-quit-buffer ()
|
||||
"Quit the mu4e-view buffer.
|
||||
This is a rather complex function, to ensure we don't disturb
|
||||
other windows."
|
||||
(interactive)
|
||||
(if (eq mu4e-split-view 'single-window)
|
||||
(when (buffer-live-p (mu4e-get-view-buffer))
|
||||
(kill-buffer (mu4e-get-view-buffer)))
|
||||
(unless (eq major-mode 'mu4e-view-mode)
|
||||
(mu4e-error "Must be in mu4e-view-mode (%S)" major-mode))
|
||||
(let ((curbuf (current-buffer))
|
||||
(curwin (selected-window))
|
||||
(headers-win))
|
||||
(walk-windows
|
||||
(lambda (win)
|
||||
;; check whether the headers buffer window is visible
|
||||
(when (eq (mu4e-get-headers-buffer) (window-buffer win))
|
||||
(setq headers-win win))
|
||||
;; and kill any _other_ (non-selected) window that shows the current
|
||||
;; buffer
|
||||
(when
|
||||
(and
|
||||
(eq curbuf (window-buffer win)) ;; does win show curbuf?
|
||||
(not (eq curwin win)) ;; but it's not the curwin?
|
||||
(not (one-window-p))) ;; and not the last one on the frame?
|
||||
(delete-window win)))) ;; delete it!
|
||||
;; now, all *other* windows should be gone.
|
||||
;; if the headers view is also visible, kill ourselves + window; otherwise
|
||||
;; switch to the headers view
|
||||
(if (window-live-p headers-win)
|
||||
;; headers are visible
|
||||
(progn
|
||||
(kill-buffer-and-window) ;; kill the view win
|
||||
(setq mu4e~headers-view-win nil)
|
||||
(select-window headers-win)) ;; and switch to the headers win...
|
||||
;; headers are not visible...
|
||||
(progn
|
||||
(kill-buffer)
|
||||
(setq mu4e~headers-view-win nil)
|
||||
(when (buffer-live-p (mu4e-get-headers-buffer))
|
||||
(switch-to-buffer (mu4e-get-headers-buffer))))))))
|
||||
|
||||
|
||||
(defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*"
|
||||
"Name for the raw message view buffer.")
|
||||
|
||||
(defun mu4e-view-raw-message ()
|
||||
"Display the raw contents of message at point in a new buffer."
|
||||
(interactive)
|
||||
(let ((path (mu4e-message-field-at-point :path))
|
||||
(buf (get-buffer-create mu4e~view-raw-buffer-name)))
|
||||
(unless (and path (file-readable-p path))
|
||||
(mu4e-error "Not a readable file: %S" path))
|
||||
(with-current-buffer buf
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert-file-contents path)
|
||||
(view-mode)
|
||||
(goto-char (point-min))))
|
||||
(switch-to-buffer buf)))
|
||||
|
||||
(defun mu4e-view-pipe (cmd)
|
||||
"Pipe the message at point through shell command CMD.
|
||||
Then, display the results."
|
||||
(interactive "sShell command: ")
|
||||
(let ((path (mu4e-message-field (mu4e-message-at-point) :path)))
|
||||
(mu4e-process-file-through-pipe path cmd)))
|
||||
|
||||
|
||||
(defmacro mu4e~view-in-headers-context (&rest body)
|
||||
"Evaluate BODY in the context of the headers buffer connected to
|
||||
this view."
|
||||
`(progn
|
||||
(unless (buffer-live-p (mu4e-get-headers-buffer))
|
||||
(mu4e-error "no headers buffer connected"))
|
||||
(let* ((msg (mu4e-message-at-point))
|
||||
(docid (mu4e-message-field msg :docid)))
|
||||
(unless docid
|
||||
(mu4e-error "message without docid: action is not possible."))
|
||||
(with-current-buffer (mu4e-get-headers-buffer)
|
||||
(unless (eq mu4e-split-view 'single-window)
|
||||
(when (get-buffer-window)
|
||||
(select-window (get-buffer-window))))
|
||||
(if (mu4e~headers-goto-docid docid)
|
||||
,@body
|
||||
(mu4e-error "cannot find message in headers buffer."))))))
|
||||
|
||||
(defun mu4e-view-headers-next (&optional n)
|
||||
"Move point to the next message header in the headers buffer
|
||||
connected with this message view. If this succeeds, return the new
|
||||
docid. Otherwise, return nil. Optionally, takes an integer
|
||||
N (prefix argument), to the Nth next header."
|
||||
(interactive "P")
|
||||
(mu4e~view-in-headers-context
|
||||
(mu4e~headers-move (or n 1))))
|
||||
|
||||
(defun mu4e-view-headers-prev (&optional n)
|
||||
"Move point to the previous message header in the headers buffer
|
||||
connected with this message view. If this succeeds, return the new
|
||||
docid. Otherwise, return nil. Optionally, takes an integer
|
||||
N (prefix argument), to the Nth previous header."
|
||||
(interactive "P")
|
||||
(mu4e~view-in-headers-context
|
||||
(mu4e~headers-move (- (or n 1)))))
|
||||
|
||||
(defun mu4e~view-prev-or-next-unread (backwards)
|
||||
"Move point to the next or previous (when BACKWARDS is non-`nil')
|
||||
unread message header in the headers buffer connected with this
|
||||
message view. If this succeeds, return the new docid. Otherwise,
|
||||
return nil."
|
||||
(mu4e~view-in-headers-context
|
||||
(mu4e~headers-prev-or-next-unread backwards))
|
||||
(if (eq mu4e-split-view 'single-window)
|
||||
(when (eq (window-buffer) (mu4e-get-view-buffer))
|
||||
(with-current-buffer (mu4e-get-headers-buffer)
|
||||
(mu4e-headers-view-message)))
|
||||
(mu4e-select-other-view)
|
||||
(mu4e-headers-view-message)))
|
||||
|
||||
(defun mu4e-view-headers-prev-unread ()
|
||||
"Move point to the previous unread message header in the headers
|
||||
buffer connected with this message view. If this succeeds, return
|
||||
the new docid. Otherwise, return nil."
|
||||
(interactive)
|
||||
(mu4e~view-prev-or-next-unread t))
|
||||
|
||||
(defun mu4e-view-headers-next-unread ()
|
||||
"Move point to the next unread message header in the headers
|
||||
buffer connected with this message view. If this succeeds, return
|
||||
the new docid. Otherwise, return nil."
|
||||
(interactive)
|
||||
(mu4e~view-prev-or-next-unread nil))
|
||||
|
||||
|
||||
;;; Interactive functions
|
||||
(defun mu4e-view-action (&optional msg)
|
||||
"Ask user for some action to apply on MSG, then do it.
|
||||
If MSG is nil apply action to message returned
|
||||
bymessage-at-point. The actions are specified in
|
||||
`mu4e-view-actions'."
|
||||
(interactive)
|
||||
(let* ((msg (or msg (mu4e-message-at-point)))
|
||||
(actionfunc (mu4e-read-option "Action: " mu4e-view-actions)))
|
||||
(funcall actionfunc msg)))
|
||||
|
||||
(defun mu4e-view-mark-pattern ()
|
||||
"Ask user for a kind of mark (move, delete etc.), a field to
|
||||
match and a regular expression to match with. Then, mark all
|
||||
matching messages with that mark."
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context (mu4e-headers-mark-pattern)))
|
||||
|
||||
(defun mu4e-view-mark-thread (&optional markpair)
|
||||
"Ask user for a kind of mark (move, delete etc.), and apply it
|
||||
to all messages in the thread at point in the headers view. The
|
||||
optional MARKPAIR can also be used to provide the mark
|
||||
selection."
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context
|
||||
(if markpair (mu4e-headers-mark-thread nil markpair)
|
||||
(call-interactively 'mu4e-headers-mark-thread))))
|
||||
|
||||
(defun mu4e-view-mark-subthread (&optional markpair)
|
||||
"Ask user for a kind of mark (move, delete etc.), and apply it
|
||||
to all messages in the subthread at point in the headers view.
|
||||
The optional MARKPAIR can also be used to provide the mark
|
||||
selection."
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context
|
||||
(if markpair (mu4e-headers-mark-subthread markpair)
|
||||
(mu4e-headers-mark-subthread))))
|
||||
|
||||
(defun mu4e-view-search-narrow ()
|
||||
"Run `mu4e-headers-search-narrow' in the headers buffer."
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context
|
||||
(call-interactively 'mu4e-headers-search-narrow)))
|
||||
|
||||
(defun mu4e-view-search-edit ()
|
||||
"Run `mu4e-headers-search-edit' in the headers buffer."
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context (mu4e-headers-search-edit)))
|
||||
|
||||
(defun mu4e-mark-region-code ()
|
||||
"Highlight region marked with `message-mark-inserted-region'.
|
||||
Add this function to `mu4e-view-mode-hook' to enable this feature."
|
||||
(require 'message)
|
||||
(let (beg end ov-beg ov-end ov-inv)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward
|
||||
(concat "^" message-mark-insert-begin) nil t)
|
||||
(setq ov-beg (match-beginning 0)
|
||||
ov-end (match-end 0)
|
||||
ov-inv (make-overlay ov-beg ov-end)
|
||||
beg ov-end)
|
||||
(overlay-put ov-inv 'invisible t)
|
||||
(when (re-search-forward
|
||||
(concat "^" message-mark-insert-end) nil t)
|
||||
(setq ov-beg (match-beginning 0)
|
||||
ov-end (match-end 0)
|
||||
ov-inv (make-overlay ov-beg ov-end)
|
||||
end ov-beg)
|
||||
(overlay-put ov-inv 'invisible t))
|
||||
(when (and beg end)
|
||||
(let ((ov (make-overlay beg end)))
|
||||
(overlay-put ov 'face 'mu4e-region-code))
|
||||
(setq beg nil end nil))))))
|
||||
|
||||
;;; View Utilities
|
||||
|
||||
(defun mu4e-view-mark-custom ()
|
||||
"Run some custom mark function."
|
||||
(mu4e~view-in-headers-context
|
||||
(mu4e-headers-mark-custom)))
|
||||
|
||||
(defun mu4e~view-split-view-p ()
|
||||
"Return t if we're in split-view, nil otherwise."
|
||||
(member mu4e-split-view '(horizontal vertical)))
|
||||
|
||||
;;; Scroll commands
|
||||
|
||||
(defun mu4e-view-scroll-up-or-next ()
|
||||
"Scroll-up the current message.
|
||||
If `mu4e-view-scroll-to-next' is non-nil, and we can't scroll-up
|
||||
anymore, go the next message."
|
||||
(interactive)
|
||||
(condition-case nil
|
||||
(scroll-up)
|
||||
(error
|
||||
(when mu4e-view-scroll-to-next
|
||||
(mu4e-view-headers-next)))))
|
||||
|
||||
(defun mu4e-scroll-up ()
|
||||
"Scroll text of selected window up one line."
|
||||
(interactive)
|
||||
(scroll-up 1))
|
||||
|
||||
(defun mu4e-scroll-down ()
|
||||
"Scroll text of selected window down one line."
|
||||
(interactive)
|
||||
(scroll-down 1))
|
||||
|
||||
;;; Mark commands
|
||||
|
||||
(defun mu4e-view-unmark-all ()
|
||||
"If we're in split-view, unmark all messages.
|
||||
Otherwise, warn user that unmarking only works in the header
|
||||
list."
|
||||
(interactive)
|
||||
(if (mu4e~view-split-view-p)
|
||||
(mu4e~view-in-headers-context (mu4e-mark-unmark-all))
|
||||
(mu4e-message "Unmarking needs to be done in the header list view")))
|
||||
|
||||
(defun mu4e-view-unmark ()
|
||||
"If we're in split-view, unmark message at point.
|
||||
Otherwise, warn user that unmarking only works in the header
|
||||
list."
|
||||
(interactive)
|
||||
(if (mu4e~view-split-view-p)
|
||||
(mu4e-view-mark-for-unmark)
|
||||
(mu4e-message "Unmarking needs to be done in the header list view")))
|
||||
|
||||
(defmacro mu4e~view-defun-mark-for (mark)
|
||||
"Define a function mu4e-view-mark-for-MARK."
|
||||
(let ((funcname (intern (format "mu4e-view-mark-for-%s" mark)))
|
||||
(docstring (format "Mark the current message for %s." mark)))
|
||||
`(progn
|
||||
(defun ,funcname () ,docstring
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context
|
||||
(mu4e-headers-mark-and-next ',mark)))
|
||||
(put ',funcname 'definition-name ',mark))))
|
||||
|
||||
(mu4e~view-defun-mark-for move)
|
||||
(mu4e~view-defun-mark-for refile)
|
||||
(mu4e~view-defun-mark-for delete)
|
||||
(mu4e~view-defun-mark-for flag)
|
||||
(mu4e~view-defun-mark-for unflag)
|
||||
(mu4e~view-defun-mark-for unmark)
|
||||
(mu4e~view-defun-mark-for something)
|
||||
(mu4e~view-defun-mark-for read)
|
||||
(mu4e~view-defun-mark-for unread)
|
||||
(mu4e~view-defun-mark-for trash)
|
||||
(mu4e~view-defun-mark-for untrash)
|
||||
|
||||
(defun mu4e-view-marked-execute ()
|
||||
"Execute the marked actions."
|
||||
(interactive)
|
||||
(mu4e~view-in-headers-context
|
||||
(mu4e-mark-execute-all)))
|
||||
|
||||
|
||||
;;; URL handling
|
||||
|
||||
(defvar mu4e~view-link-map nil
|
||||
"A map of some number->url so we can jump to url by number.")
|
||||
(put 'mu4e~view-link-map 'permanent-local t)
|
||||
|
||||
(defvar mu4e-view-active-urls-keymap
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [down-mouse-1] 'mu4e~view-browse-url-from-binding)
|
||||
(define-key map [mouse-1] 'mu4e~view-browse-url-from-binding)
|
||||
(define-key map (kbd "M-<return>") 'mu4e~view-browse-url-from-binding)
|
||||
map)
|
||||
"Keymap used for the urls inside the body.")
|
||||
|
||||
(defvar mu4e~view-beginning-of-url-regexp
|
||||
"https?\\://\\|mailto:"
|
||||
"Regexp that matches the beginning of http:/https:/mailto:
|
||||
URLs; match-string 1 will contain the matched URL, if any.")
|
||||
|
||||
|
||||
(defun mu4e~view-browse-url-from-binding (&optional url)
|
||||
"View in browser the url at point, or click location.
|
||||
If the optional argument URL is provided, browse that instead.
|
||||
If the url is mailto link, start writing an email to that address."
|
||||
(interactive)
|
||||
(let* (( url (or url (mu4e~view-get-property-from-event 'mu4e-url))))
|
||||
(when url
|
||||
(if (string-match-p "^mailto:" url)
|
||||
(browse-url-mail url)
|
||||
(browse-url url)))))
|
||||
|
||||
|
||||
(defun mu4e~view-get-property-from-event (prop)
|
||||
"Get the property PROP at point, or the location of the mouse.
|
||||
The action is chosen based on the `last-command-event'.
|
||||
Meant to be evoked from interactive commands."
|
||||
(if (and (eventp last-command-event)
|
||||
(mouse-event-p last-command-event))
|
||||
(let ((posn (event-end last-command-event)))
|
||||
(when (numberp (posn-point posn))
|
||||
(get-text-property
|
||||
(posn-point posn)
|
||||
prop
|
||||
(window-buffer (posn-window posn)))))
|
||||
(get-text-property (point) prop)))
|
||||
|
||||
;; this is fairly simplistic...
|
||||
(defun mu4e~view-activate-urls ()
|
||||
"Turn things that look like URLs into clickable things.
|
||||
Also number them so they can be opened using `mu4e-view-go-to-url'."
|
||||
(let ((num 0))
|
||||
(save-excursion
|
||||
(setq mu4e~view-link-map ;; buffer local
|
||||
(make-hash-table :size 32 :weakness nil))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward mu4e~view-beginning-of-url-regexp nil t)
|
||||
(let ((bounds (thing-at-point-bounds-of-url-at-point)))
|
||||
(when bounds
|
||||
(let* ((url (thing-at-point-url-at-point))
|
||||
(ov (make-overlay (car bounds) (cdr bounds))))
|
||||
(puthash (cl-incf num) url mu4e~view-link-map)
|
||||
(add-text-properties
|
||||
(car bounds)
|
||||
(cdr bounds)
|
||||
`(face mu4e-link-face
|
||||
mouse-face highlight
|
||||
mu4e-url ,url
|
||||
keymap ,mu4e-view-active-urls-keymap
|
||||
help-echo
|
||||
"[mouse-1] or [M-RET] to open the link"))
|
||||
(overlay-put ov 'after-string
|
||||
(propertize (format "\u200B[%d]" num)
|
||||
'face 'mu4e-url-number-face)))))))))
|
||||
|
||||
|
||||
(defun mu4e~view-get-urls-num (prompt &optional multi)
|
||||
"Ask the user with PROMPT for an URL number for MSG, and ensure
|
||||
it is valid. The number is [1..n] for URLs \[0..(n-1)] in the
|
||||
message. If MULTI is nil, return the number for the URL;
|
||||
otherwise (MULTI is non-nil), accept ranges of URL numbers, as
|
||||
per `mu4e-split-ranges-to-numbers', and return the corresponding
|
||||
string."
|
||||
(let* ((count (hash-table-count mu4e~view-link-map)) (def))
|
||||
(when (zerop count) (mu4e-error "No links for this message"))
|
||||
(if (not multi)
|
||||
(if (= count 1)
|
||||
(read-number (mu4e-format "%s: " prompt) 1)
|
||||
(read-number (mu4e-format "%s (1-%d): " prompt count)))
|
||||
(progn
|
||||
(setq def (if (= count 1) "1" (format "1-%d" count)))
|
||||
(read-string (mu4e-format "%s (default %s): " prompt def)
|
||||
nil nil def)))))
|
||||
|
||||
(defun mu4e-view-go-to-url (&optional multi)
|
||||
"Offer to go to url(s). If MULTI (prefix-argument) is nil, go to
|
||||
a single one, otherwise, offer to go to a range of urls."
|
||||
(interactive "P")
|
||||
(mu4e~view-handle-urls "URL to visit"
|
||||
multi
|
||||
(lambda (url) (mu4e~view-browse-url-from-binding url))))
|
||||
|
||||
(defun mu4e-view-save-url (&optional multi)
|
||||
"Offer to save urls(s) to the kill-ring. If
|
||||
MULTI (prefix-argument) is nil, save a single one, otherwise, offer
|
||||
to save a range of URLs."
|
||||
(interactive "P")
|
||||
(mu4e~view-handle-urls "URL to save" multi
|
||||
(lambda (url)
|
||||
(kill-new url)
|
||||
(mu4e-message "Saved %s to the kill-ring" url))))
|
||||
|
||||
(defun mu4e-view-fetch-url (&optional multi)
|
||||
"Offer to fetch (download) urls(s). If MULTI (prefix-argument) is nil,
|
||||
download a single one, otherwise, offer to fetch a range of
|
||||
URLs. The urls are fetched to `mu4e-attachment-dir'."
|
||||
(interactive "P")
|
||||
(mu4e~view-handle-urls "URL to fetch" multi
|
||||
(lambda (url)
|
||||
(let ((target (concat (mu4e~get-attachment-dir url) "/"
|
||||
(file-name-nondirectory url))))
|
||||
(url-copy-file url target)
|
||||
(mu4e-message "Fetched %s -> %s" url target)))))
|
||||
|
||||
(defun mu4e~view-handle-urls (prompt multi urlfunc)
|
||||
"If MULTI is nil, apply URLFUNC to a single uri, otherwise, apply
|
||||
it to a range of uris. PROMPT is the query to present to the user."
|
||||
(if multi
|
||||
(mu4e~view-handle-multi-urls prompt urlfunc)
|
||||
(mu4e~view-handle-single-url prompt urlfunc)))
|
||||
|
||||
(defun mu4e~view-handle-single-url (prompt urlfunc &optional num)
|
||||
"Apply URLFUNC to url NUM in the current message, prompting the
|
||||
user with PROMPT."
|
||||
(let* ((num (or num (mu4e~view-get-urls-num prompt)))
|
||||
(url (gethash num mu4e~view-link-map)))
|
||||
(unless url (mu4e-warn "Invalid number for URL"))
|
||||
(funcall urlfunc url)))
|
||||
|
||||
(defun mu4e~view-handle-multi-urls (prompt urlfunc)
|
||||
"Apply URLFUNC to a a range of urls in the current message,
|
||||
prompting the user with PROMPT.
|
||||
|
||||
Default is to apply it to all URLs, [1..n], where n is the number
|
||||
of urls. You can type multiple values separated by space, e.g. 1
|
||||
3-6 8 will visit urls 1,3,4,5,6 and 8.
|
||||
|
||||
Furthermore, there is a shortcut \"a\" which means all urls, but as
|
||||
this is the default, you may not need it."
|
||||
(let* ((linkstr (mu4e~view-get-urls-num
|
||||
"URL number range (or 'a' for 'all')" t))
|
||||
(count (hash-table-count mu4e~view-link-map))
|
||||
(linknums (mu4e-split-ranges-to-numbers linkstr count)))
|
||||
(dolist (num linknums)
|
||||
(mu4e~view-handle-single-url prompt urlfunc num))))
|
||||
|
||||
(defun mu4e-view-for-each-uri (func)
|
||||
"Evaluate FUNC(uri) for each uri in the current message."
|
||||
(maphash (lambda (_num uri) (funcall func uri)) mu4e~view-link-map))
|
||||
|
||||
|
||||
(provide 'mu4e-view-common)
|
|
@ -0,0 +1,657 @@
|
|||
;;; mu4e-view-gnus.el -- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2021 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; mu4e 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.
|
||||
|
||||
;; mu4e 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 mu4e. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; In this file we define mu4e-view-mode (+ helper functions), which is used for
|
||||
;; viewing e-mail messages
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'mu4e-view-common)
|
||||
(require 'calendar)
|
||||
(require 'gnus-art)
|
||||
|
||||
;;; Variables
|
||||
|
||||
(defvar gnus-icalendar-additional-identities)
|
||||
(defvar helm-comp-read-use-marked)
|
||||
(defvar-local mu4e~view-rendering nil)
|
||||
|
||||
(make-obsolete-variable 'mu4e-view-blocked-images 'gnus-blocked-images
|
||||
"1.5.12")
|
||||
(make-obsolete-variable 'mu4e-view-inhibit-images 'gnus-inhibit-images
|
||||
"1.5.12")
|
||||
;;; Main
|
||||
|
||||
;; remember the mime-handles, so we can clean them up when
|
||||
;; we quit this buffer.
|
||||
(defvar-local mu4e~gnus-article-mime-handles nil)
|
||||
(put 'mu4e~gnus-article-mime-handles 'permanent-local t)
|
||||
|
||||
(defun mu4e~view-gnus (msg)
|
||||
"View MSG using Gnus' article mode."
|
||||
(when (bufferp gnus-article-buffer)
|
||||
(kill-buffer gnus-article-buffer))
|
||||
(with-current-buffer (get-buffer-create gnus-article-buffer)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert-file-contents-literally
|
||||
(mu4e-message-field msg :path) nil nil nil t)))
|
||||
(switch-to-buffer gnus-article-buffer)
|
||||
(setq mu4e~view-message msg)
|
||||
(mu4e~view-render-buffer msg))
|
||||
|
||||
(defun mu4e-view-message-text (msg)
|
||||
"Return the pristine MSG as a string."
|
||||
;; we need this for replying/forwarding, since the mu4e-compose
|
||||
;; wants it that way.
|
||||
(with-temp-buffer
|
||||
(insert-file-contents-literally
|
||||
(mu4e-message-field msg :path) nil nil nil t)
|
||||
(mu4e~view-render-buffer msg)
|
||||
(buffer-substring-no-properties (point-min) (point-max))))
|
||||
|
||||
(defun mu4e-action-view-in-browser (msg)
|
||||
"Show current MSG in browser if it includes an HTML-part.
|
||||
The variables `browse-url-browser-function',
|
||||
`browse-url-handlers', and `browse-url-default-handlers'
|
||||
determine which browser function to use."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents-literally
|
||||
(mu4e-message-field msg :path) nil nil nil t)
|
||||
(run-hooks 'gnus-article-decode-hook)
|
||||
(let ((header (cl-loop for field in '("from" "to" "cc" "date" "subject")
|
||||
when (message-fetch-field field)
|
||||
concat (format "%s: %s\n" (capitalize field) it)))
|
||||
(parts (mm-dissect-buffer t t)))
|
||||
;; If singlepart, enforce a list.
|
||||
(when (and (bufferp (car parts))
|
||||
(stringp (car (mm-handle-type parts))))
|
||||
(setq parts (list parts)))
|
||||
;; Process the list
|
||||
(unless (gnus-article-browse-html-parts parts header)
|
||||
(mu4e-warn "Message does not contain a \"text/html\" part"))
|
||||
(mm-destroy-parts parts))))
|
||||
|
||||
|
||||
(defun mu4e~view-render-buffer (msg)
|
||||
"Render current buffer with MSG using Gnus' article mode."
|
||||
(setq gnus-summary-buffer (get-buffer-create " *appease-gnus*"))
|
||||
(let* ((inhibit-read-only t)
|
||||
(max-specpdl-size mu4e-view-max-specpdl-size)
|
||||
(mm-decrypt-option 'known)
|
||||
(ct (mail-fetch-field "Content-Type"))
|
||||
(ct (and ct (mail-header-parse-content-type ct)))
|
||||
(charset (mail-content-type-get ct 'charset))
|
||||
(charset (and charset (intern charset)))
|
||||
(mu4e~view-rendering t); Needed if e.g. an ics file is buttonized
|
||||
(gnus-article-emulate-mime t)
|
||||
(gnus-unbuttonized-mime-types '(".*/.*"))
|
||||
(gnus-buttonized-mime-types
|
||||
(append (list "multipart/signed" "multipart/encrypted")
|
||||
gnus-buttonized-mime-types))
|
||||
(gnus-newsgroup-charset
|
||||
(if (and charset (coding-system-p charset)) charset
|
||||
(detect-coding-region (point-min) (point-max) t)))
|
||||
;; Possibly add headers (before "Attachments")
|
||||
(gnus-display-mime-function (mu4e~view-gnus-display-mime msg))
|
||||
(gnus-icalendar-additional-identities
|
||||
(mu4e-personal-addresses 'no-regexp)))
|
||||
(mm-enable-multibyte)
|
||||
(mu4e-view-mode)
|
||||
(run-hooks 'gnus-article-decode-hook)
|
||||
(gnus-article-prepare-display)
|
||||
(mu4e~view-activate-urls)
|
||||
(setq mu4e~gnus-article-mime-handles gnus-article-mime-handles
|
||||
gnus-article-decoded-p gnus-article-decode-hook)
|
||||
(set-buffer-modified-p nil)
|
||||
(add-hook 'kill-buffer-hook #'mu4e~view-kill-mime-handles)))
|
||||
|
||||
(defun mu4e~view-kill-mime-handles ()
|
||||
"Kill cached MIME-handles, if any."
|
||||
(when mu4e~gnus-article-mime-handles
|
||||
(mm-destroy-parts mu4e~gnus-article-mime-handles)
|
||||
(setq mu4e~gnus-article-mime-handles nil)))
|
||||
|
||||
(defun mu4e~view-gnus-display-mime (msg)
|
||||
"Like `gnus-display-mime' but include mu4e headers to MSG."
|
||||
(lambda (&optional ihandles)
|
||||
(gnus-display-mime ihandles)
|
||||
(unless ihandles
|
||||
(save-restriction
|
||||
(article-goto-body)
|
||||
(forward-line -1)
|
||||
(narrow-to-region (point) (point))
|
||||
(dolist (field mu4e-view-fields)
|
||||
(let ((fieldval (mu4e-message-field msg field)))
|
||||
(cl-case field
|
||||
((:path :maildir :user-agent :mailing-list :message-id)
|
||||
(mu4e~view-gnus-insert-header field fieldval))
|
||||
((:flags :tags)
|
||||
(let ((flags (mapconcat (lambda (flag)
|
||||
(if (symbolp flag)
|
||||
(symbol-name flag)
|
||||
flag)) fieldval ", ")))
|
||||
(mu4e~view-gnus-insert-header field flags)))
|
||||
(:size (mu4e~view-gnus-insert-header
|
||||
field (mu4e-display-size fieldval)))
|
||||
((:subject :to :from :cc :bcc :from-or-to :date :attachments
|
||||
:signature :decryption)) ; handled by Gnus
|
||||
(t
|
||||
(mu4e~view-gnus-insert-header-custom msg field)))))
|
||||
(let ((gnus-treatment-function-alist
|
||||
'((gnus-treat-highlight-headers
|
||||
gnus-article-highlight-headers))))
|
||||
(gnus-treat-article 'head))))))
|
||||
|
||||
(defun mu4e~view-gnus-insert-header (field val)
|
||||
"Insert a header FIELD with value VAL."
|
||||
(let* ((info (cdr (assoc field mu4e-header-info)))
|
||||
(key (plist-get info :name))
|
||||
(help (plist-get info :help)))
|
||||
(if (and val (> (length val) 0))
|
||||
(insert (propertize (concat key ":") 'help-echo help)
|
||||
" " val "\n"))))
|
||||
|
||||
(defun mu4e~view-gnus-insert-header-custom (msg field)
|
||||
"Insert MSG's custom FIELD."
|
||||
(let* ((info (cdr-safe (or (assoc field mu4e-header-info-custom)
|
||||
(mu4e-error "Custom field %S not found" field))))
|
||||
(key (plist-get info :name))
|
||||
(func (or (plist-get info :function)
|
||||
(mu4e-error "No :function defined for custom field %S %S"
|
||||
field info)))
|
||||
(val (funcall func msg))
|
||||
(help (plist-get info :help)))
|
||||
(when (and val (> (length val) 0))
|
||||
(insert (propertize (concat key ":") 'help-echo help) " " val "\n"))))
|
||||
|
||||
(define-advice gnus-icalendar-event-from-handle
|
||||
(:filter-args (handle-attendee) mu4e~view-fix-missing-charset)
|
||||
"Avoid error when displaying an ical attachment without a charset."
|
||||
(if (and (boundp 'mu4e~view-rendering) mu4e~view-rendering)
|
||||
(let* ((handle (car handle-attendee))
|
||||
(attendee (cadr handle-attendee))
|
||||
(buf (mm-handle-buffer handle))
|
||||
(ty (mm-handle-type handle))
|
||||
(rest (cddr handle)))
|
||||
;; Put the fallback at the end:
|
||||
(setq ty (append ty '((charset . "utf-8"))))
|
||||
(setq handle (cons buf (cons ty rest)))
|
||||
(list handle attendee))
|
||||
handle-attendee))
|
||||
|
||||
(defun mu4e~view-mode-p ()
|
||||
"Is the buffer in mu4e-view-mode or one of its descendants?"
|
||||
(or (eq major-mode 'mu4e-view-mode)
|
||||
(derived-mode-p '(mu4e-view-mode))))
|
||||
|
||||
(defun mu4e~view-nop (func &rest args)
|
||||
"Do not invoke FUNC with ARGS when in mu4e-view-mode.
|
||||
This is useful for advising some Gnus-functionality that does not work in mu4e."
|
||||
(unless (mu4e~view-mode-p)
|
||||
(apply func args)))
|
||||
|
||||
(defun mu4e~view-button-reply (func &rest args)
|
||||
"Advise FUNC with ARGS to make `gnus-button-reply' links work in mu4e."
|
||||
(if (mu4e~view-mode-p)
|
||||
(mu4e-compose-reply)
|
||||
(apply func args)))
|
||||
|
||||
(defun mu4e~view-msg-mail (func &rest args)
|
||||
"Advise FUNC with ARGS to make `gnus-msg-mail' links compose with mu4e."
|
||||
(if (mu4e~view-mode-p)
|
||||
(apply 'mu4e~compose-mail args)
|
||||
(apply func args)))
|
||||
|
||||
(defvar mu4e-view-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
|
||||
(define-key map (kbd "C-S-u") 'mu4e-update-mail-and-index)
|
||||
(define-key map (kbd "C-c C-u") 'mu4e-update-mail-and-index)
|
||||
|
||||
(define-key map "q" 'mu4e~view-quit-buffer)
|
||||
|
||||
;; note, 'z' is by-default bound to 'bury-buffer'
|
||||
;; but that's not very useful in this case
|
||||
(define-key map "z" 'ignore)
|
||||
|
||||
(define-key map "s" #'mu4e-headers-search)
|
||||
(define-key map "S" #'mu4e-view-search-edit)
|
||||
(define-key map "/" #'mu4e-view-search-narrow)
|
||||
|
||||
(define-key map (kbd "<M-left>") #'mu4e-headers-query-prev)
|
||||
(define-key map (kbd "<M-right>") #'mu4e-headers-query-next)
|
||||
|
||||
(define-key map "b" #'mu4e-headers-search-bookmark)
|
||||
(define-key map "B" #'mu4e-headers-search-bookmark-edit)
|
||||
|
||||
(define-key map "%" #'mu4e-view-mark-pattern)
|
||||
(define-key map "t" #'mu4e-view-mark-subthread)
|
||||
(define-key map "T" #'mu4e-view-mark-thread)
|
||||
(define-key map "j" 'mu4e~headers-jump-to-maildir)
|
||||
|
||||
(define-key map "g" #'mu4e-view-go-to-url)
|
||||
(define-key map "k" #'mu4e-view-save-url)
|
||||
(define-key map "f" #'mu4e-view-fetch-url)
|
||||
|
||||
(define-key map "F" #'mu4e-compose-forward)
|
||||
(define-key map "R" #'mu4e-compose-reply)
|
||||
(define-key map "C" #'mu4e-compose-new)
|
||||
(define-key map "E" #'mu4e-compose-edit)
|
||||
|
||||
(define-key map "." #'mu4e-view-raw-message)
|
||||
(define-key map "|" #'mu4e-view-pipe)
|
||||
(define-key map "a" #'mu4e-view-action)
|
||||
(define-key map "A" #'mu4e-view-mime-part-action)
|
||||
(define-key map "e" #'mu4e-view-save-attachments)
|
||||
|
||||
(define-key map ";" #'mu4e-context-switch)
|
||||
|
||||
;; toggle header settings
|
||||
(define-key map "O" #'mu4e-headers-change-sorting)
|
||||
(define-key map "P" #'mu4e-headers-toggle-threading)
|
||||
(define-key map "Q" #'mu4e-headers-toggle-full-search)
|
||||
(define-key map "W" #'mu4e-headers-toggle-include-related)
|
||||
|
||||
;; change the number of headers
|
||||
(define-key map (kbd "C-+") #'mu4e-headers-split-view-grow)
|
||||
(define-key map (kbd "C--") #'mu4e-headers-split-view-shrink)
|
||||
(define-key map (kbd "<C-kp-add>") #'mu4e-headers-split-view-grow)
|
||||
(define-key map (kbd "<C-kp-subtract>") #'mu4e-headers-split-view-shrink)
|
||||
|
||||
;; intra-message navigation
|
||||
(define-key map (kbd "S-SPC") #'scroll-down)
|
||||
(define-key map (kbd "SPC") #'mu4e-view-scroll-up-or-next)
|
||||
(define-key map (kbd "RET") #'mu4e-scroll-up)
|
||||
(define-key map (kbd "<backspace>") #'mu4e-scroll-down)
|
||||
|
||||
;; navigation between messages
|
||||
(define-key map "p" #'mu4e-view-headers-prev)
|
||||
(define-key map "n" #'mu4e-view-headers-next)
|
||||
;; the same
|
||||
(define-key map (kbd "<M-down>") #'mu4e-view-headers-next)
|
||||
(define-key map (kbd "<M-up>") #'mu4e-view-headers-prev)
|
||||
|
||||
(define-key map (kbd "[") #'mu4e-view-headers-prev-unread)
|
||||
(define-key map (kbd "]") #'mu4e-view-headers-next-unread)
|
||||
|
||||
;; switching from view <-> headers (when visible)
|
||||
(define-key map "y" #'mu4e-select-other-view)
|
||||
|
||||
;; marking/unmarking
|
||||
(define-key map "d" #'mu4e-view-mark-for-trash)
|
||||
(define-key map (kbd "<delete>") #'mu4e-view-mark-for-delete)
|
||||
(define-key map (kbd "<deletechar>") #'mu4e-view-mark-for-delete)
|
||||
(define-key map (kbd "D") #'mu4e-view-mark-for-delete)
|
||||
(define-key map (kbd "m") #'mu4e-view-mark-for-move)
|
||||
(define-key map (kbd "r") #'mu4e-view-mark-for-refile)
|
||||
|
||||
(define-key map (kbd "?") #'mu4e-view-mark-for-unread)
|
||||
(define-key map (kbd "!") #'mu4e-view-mark-for-read)
|
||||
|
||||
(define-key map (kbd "+") #'mu4e-view-mark-for-flag)
|
||||
(define-key map (kbd "-") #'mu4e-view-mark-for-unflag)
|
||||
(define-key map (kbd "=") #'mu4e-view-mark-for-untrash)
|
||||
(define-key map (kbd "&") #'mu4e-view-mark-custom)
|
||||
|
||||
(define-key map (kbd "*") #'mu4e-view-mark-for-something)
|
||||
(define-key map (kbd "<kp-multiply>") #'mu4e-view-mark-for-something)
|
||||
(define-key map (kbd "<insert>") #'mu4e-view-mark-for-something)
|
||||
(define-key map (kbd "<insertchar>") #'mu4e-view-mark-for-something)
|
||||
|
||||
(define-key map (kbd "#") #'mu4e-mark-resolve-deferred-marks)
|
||||
|
||||
;; misc
|
||||
(define-key map "M" #'mu4e-view-massage)
|
||||
|
||||
(define-key map "w" 'visual-line-mode)
|
||||
(define-key map "h" #'mu4e-view-toggle-html)
|
||||
(define-key map (kbd "M-q") 'article-fill-long-lines)
|
||||
|
||||
;; next 3 only warn user when attempt in the message view
|
||||
(define-key map "u" #'mu4e-view-unmark)
|
||||
(define-key map "U" #'mu4e-view-unmark-all)
|
||||
(define-key map "x" #'mu4e-view-marked-execute)
|
||||
|
||||
(define-key map "$" #'mu4e-show-log)
|
||||
(define-key map "H" #'mu4e-display-manual)
|
||||
|
||||
;; menu
|
||||
;;(define-key map [menu-bar] (make-sparse-keymap))
|
||||
(let ((menumap (make-sparse-keymap)))
|
||||
(define-key map [menu-bar headers] (cons "Mu4e" menumap))
|
||||
|
||||
(define-key menumap [quit-buffer]
|
||||
'("Quit view" . mu4e~view-quit-buffer))
|
||||
(define-key menumap [display-help] '("Help" . mu4e-display-manual))
|
||||
|
||||
(define-key menumap [sepa0] '("--"))
|
||||
(define-key menumap [wrap-lines]
|
||||
'("Toggle wrap lines" . visual-line-mode))
|
||||
(define-key menumap [raw-view]
|
||||
'("View raw message" . mu4e-view-raw-message))
|
||||
(define-key menumap [pipe]
|
||||
'("Pipe through shell" . mu4e-view-pipe))
|
||||
|
||||
(define-key menumap [sepa1] '("--"))
|
||||
(define-key menumap [mark-delete]
|
||||
'("Mark for deletion" . mu4e-view-mark-for-delete))
|
||||
(define-key menumap [mark-untrash]
|
||||
'("Mark for untrash" . mu4e-view-mark-for-untrash))
|
||||
(define-key menumap [mark-trash]
|
||||
'("Mark for trash" . mu4e-view-mark-for-trash))
|
||||
(define-key menumap [mark-move]
|
||||
'("Mark for move" . mu4e-view-mark-for-move))
|
||||
|
||||
(define-key menumap [sepa2] '("--"))
|
||||
(define-key menumap [resend] '("Resend" . mu4e-compose-resend))
|
||||
(define-key menumap [forward] '("Forward" . mu4e-compose-forward))
|
||||
(define-key menumap [reply] '("Reply" . mu4e-compose-reply))
|
||||
(define-key menumap [compose-new] '("Compose new" . mu4e-compose-new))
|
||||
(define-key menumap [sepa3] '("--"))
|
||||
|
||||
(define-key menumap [query-next]
|
||||
'("Next query" . mu4e-headers-query-next))
|
||||
(define-key menumap [query-prev]
|
||||
'("Previous query" . mu4e-headers-query-prev))
|
||||
(define-key menumap [narrow-search]
|
||||
'("Narrow search" . mu4e-headers-search-narrow))
|
||||
(define-key menumap [bookmark]
|
||||
'("Search bookmark" . mu4e-headers-search-bookmark))
|
||||
(define-key menumap [jump]
|
||||
'("Jump to maildir" . mu4e~headers-jump-to-maildir))
|
||||
(define-key menumap [search]
|
||||
'("Search" . mu4e-headers-search))
|
||||
|
||||
(define-key menumap [sepa4] '("--"))
|
||||
(define-key menumap [next] '("Next" . mu4e-view-headers-next))
|
||||
(define-key menumap [previous] '("Previous" . mu4e-view-headers-prev)))
|
||||
|
||||
(set-keymap-parent map special-mode-map)
|
||||
map)
|
||||
"Keymap for mu4e-view mode.")
|
||||
|
||||
(set-keymap-parent mu4e-view-mode-map button-buffer-map)
|
||||
(suppress-keymap mu4e-view-mode-map)
|
||||
|
||||
(defcustom mu4e-view-mode-hook nil
|
||||
"Hook run when entering Mu4e-View mode."
|
||||
:options '(turn-on-visual-line-mode)
|
||||
:type 'hook
|
||||
:group 'mu4e-view)
|
||||
|
||||
(defvar mu4e-view-mode-abbrev-table nil)
|
||||
|
||||
(defun mu4e~view-mode-body ()
|
||||
"Body of the mode-function."
|
||||
(use-local-map mu4e-view-mode-map)
|
||||
(mu4e-context-in-modeline)
|
||||
(setq buffer-undo-list t);; don't record undo info
|
||||
;; autopair mode gives error when pressing RET
|
||||
;; turn it off
|
||||
(when (boundp 'autopair-dont-activate)
|
||||
(setq autopair-dont-activate t)))
|
||||
|
||||
;; "Define the major-mode for the mu4e-view."
|
||||
(define-derived-mode mu4e-view-mode gnus-article-mode "mu4e:view"
|
||||
"Major mode for viewing an e-mail message in mu4e.
|
||||
Based on Gnus' article-mode."
|
||||
;; Restore C-h b default behavior
|
||||
(define-key mu4e-view-mode-map (kbd "C-h b") 'describe-bindings)
|
||||
;; ;; turn off gnus modeline changes and menu items
|
||||
(advice-add 'gnus-set-mode-line :around #'mu4e~view-nop)
|
||||
(advice-add 'gnus-button-reply :around #'mu4e~view-button-reply)
|
||||
(advice-add 'gnus-msg-mail :around #'mu4e~view-msg-mail)
|
||||
|
||||
;; advice gnus-block-private-groups to always return "."
|
||||
;; so that by default we block images.
|
||||
(advice-add 'gnus-block-private-groups :around
|
||||
(lambda(func &rest args)
|
||||
(if (mu4e~view-mode-p)
|
||||
"." (apply func args))))
|
||||
(mu4e~view-mode-body))
|
||||
|
||||
;;; Massaging the message view
|
||||
|
||||
(defcustom mu4e-view-massage-options
|
||||
'( ("ctoggle citations" . gnus-article-hide-citation)
|
||||
("htoggle headers" . gnus-article-hide-headers)
|
||||
("ytoggle crypto" . gnus-article-hide-pem))
|
||||
"Various options for 'massaging' the message view. See `(gnus)
|
||||
Article Treatment' for more options."
|
||||
:group 'mu4e-view
|
||||
:type '(alist :key-type string :value-type function))
|
||||
|
||||
(defun mu4e-view-massage()
|
||||
"Massage current message view as per `mu4e-view-massage-options'."
|
||||
(interactive)
|
||||
(funcall (mu4e-read-option "Massage: " mu4e-view-massage-options)))
|
||||
|
||||
;;; MIME-parts
|
||||
|
||||
(defun mu4e~view-gather-mime-parts ()
|
||||
"Gather all MIME parts as an alist.
|
||||
The alist uniquely maps the number to the gnus-part."
|
||||
(let ((parts '()))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(let ((part (get-text-property (point) 'gnus-data))
|
||||
(index (get-text-property (point) 'gnus-part)))
|
||||
(when (and part (numberp index) (not (assoc index parts))
|
||||
(push `(,index . ,part) parts)))
|
||||
(goto-char (or (next-single-property-change (point) 'gnus-part)
|
||||
(point-max))))))
|
||||
parts))
|
||||
|
||||
|
||||
(defun mu4e-view-save-attachments (&optional arg)
|
||||
"Save mime parts from current mu4e gnus view buffer.
|
||||
|
||||
When helm-mode is enabled provide completion on attachments and
|
||||
possibility to mark candidates to save, otherwise completion on
|
||||
attachments is done with `completing-read-multiple', in this case
|
||||
use \",\" to separate candidate, completion is provided after
|
||||
each \",\".
|
||||
|
||||
Note, currently this does not work well with file names
|
||||
containing commas."
|
||||
(interactive "P")
|
||||
(cl-assert (and (eq major-mode 'mu4e-view-mode)
|
||||
(derived-mode-p 'gnus-article-mode)))
|
||||
(let* ((parts (mu4e~view-gather-mime-parts))
|
||||
(handles '())
|
||||
(files '())
|
||||
(compfn (if (and (boundp 'helm-mode) helm-mode)
|
||||
#'completing-read
|
||||
;; Fallback to `completing-read-multiple' with poor
|
||||
;; completion
|
||||
#'completing-read-multiple))
|
||||
dir)
|
||||
(dolist (part parts)
|
||||
(let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr part))))))
|
||||
(when fname
|
||||
(push `(,fname . ,(cdr part)) handles)
|
||||
(push fname files))))
|
||||
(if files
|
||||
(progn
|
||||
(setq files (let ((helm-comp-read-use-marked t))
|
||||
(funcall compfn "Save part(s): " files))
|
||||
dir (if arg (read-directory-name "Save to directory: ") mu4e-attachment-dir))
|
||||
(cl-loop for (f . h) in handles
|
||||
when (member f files)
|
||||
do (mm-save-part-to-file
|
||||
h (let ((file (expand-file-name f dir)))
|
||||
(if (file-exists-p file)
|
||||
(let (newname (count 1))
|
||||
(while (and
|
||||
(setq newname
|
||||
(concat
|
||||
(file-name-sans-extension file)
|
||||
(format "(%s)" count)
|
||||
(file-name-extension file t)))
|
||||
(file-exists-p newname))
|
||||
(cl-incf count))
|
||||
newname)
|
||||
file)))))
|
||||
(mu4e-message "No attached files found"))))
|
||||
|
||||
|
||||
(defvar mu4e-view-mime-part-actions
|
||||
'(
|
||||
;;
|
||||
;; some basic ones
|
||||
;;
|
||||
|
||||
;; save MIME-part to a file
|
||||
(:name "save" :handler gnus-article-save-part :receives index)
|
||||
;; pipe MIME-part to some arbitrary shell command
|
||||
(:name "|pipe" :handler gnus-article-pipe-part :receives index)
|
||||
;; open with the default handler, if any
|
||||
(:name "open" :handler mu4e~view-open-file :receives temp)
|
||||
;; open with some custom file.
|
||||
(:name "wopen-with" :handler (lambda (file)(mu4e~view-open-file file t))
|
||||
:receives temp)
|
||||
|
||||
;;
|
||||
;; some more examples
|
||||
;;
|
||||
|
||||
;; import GPG key
|
||||
(:name "gpg" :handler epa-import-keys :receives temp)
|
||||
;; count the number of lines in a MIME-part
|
||||
(:name "line-count" :handler "wc -l" :receives pipe)
|
||||
;; open in this emacs instance; tries to use the attachment name,
|
||||
;; so emacs can use specific modes etc.
|
||||
(:name "emacs" :handler find-file :receives temp)
|
||||
;; open in this emacs instance, "raw"
|
||||
(:name "raw" :handler (lambda (str)
|
||||
(let ((tmpbuf (get-buffer-create " *mu4e-raw-mime*")))
|
||||
(with-current-buffer tmpbuf
|
||||
(insert str)
|
||||
(view-mode)
|
||||
(goto-char (point-min)))
|
||||
(switch-to-buffer tmpbuf))) :receives pipe))
|
||||
|
||||
"Specifies actions for MIME-parts.
|
||||
|
||||
Each of the actions is a plist with keys
|
||||
`(:name <name> ;; name of the action; shortcut is first letter of name
|
||||
|
||||
:handler ;; one of:
|
||||
;; - a function receiving the index/temp/pipe
|
||||
;; - a string, which is taken as a shell command
|
||||
|
||||
:receives ;; a symbol specifying what the handler receives
|
||||
;; - index: the index number of the mime part (default)
|
||||
;; - temp: the full path to the mime part in a
|
||||
;; temporary file, which is deleted immediately
|
||||
;; after invoking handler
|
||||
;; - pipe: the attachment is piped to some shell command
|
||||
;; or as a string parameter to a function
|
||||
).")
|
||||
|
||||
|
||||
(defun mu4e~view-mime-part-to-temp-file (handle)
|
||||
"Write MIME-part HANDLE to a temporary file and return the file name.
|
||||
The filename is deduced from the MIME-part's filename, or
|
||||
otherwise random; the result is placed in a temporary directory
|
||||
with a unique name. Returns the full path for the file created.
|
||||
The directory and file are self-destructed."
|
||||
(let* ((tmpdir (make-temp-file "mu4e-temp-" t))
|
||||
(fname (cdr-safe (assoc 'filename (assoc "attachment" (cdr handle)))))
|
||||
(fname (if fname
|
||||
(concat tmpdir "/" (replace-regexp-in-string "/" "-" fname))
|
||||
(let ((temporary-file-directory tmpdir))
|
||||
(make-temp-file "mimepart")))))
|
||||
(mm-save-part-to-file handle fname)
|
||||
(run-at-time "30 sec" nil (lambda () (ignore-errors (delete-directory tmpdir t))))
|
||||
fname))
|
||||
|
||||
|
||||
(defun mu4e~view-open-file (file &optional force-ask)
|
||||
"Open FILE with default handler, if any.
|
||||
Otherwise, or if FORCE-ASK is set, ask user for the program to
|
||||
open with."
|
||||
(let* ((opener
|
||||
(pcase system-type
|
||||
(`darwin "open")
|
||||
((or 'gnu 'gnu/linux 'gnu/kfreebsd) "xdg-open")))
|
||||
(prog (if (or force-ask (not opener))
|
||||
(read-shell-command "Open MIME-part with: ")
|
||||
opener)))
|
||||
(call-process prog nil 0 nil file)))
|
||||
|
||||
(defun mu4e-view-mime-part-action (&optional n)
|
||||
"Apply some action to MIME-part N in the current messsage.
|
||||
If N is not specified, ask for it. For instance, '3 A o' opens
|
||||
the third MIME-part."
|
||||
(interactive "NNumber of MIME-part: ")
|
||||
(let* ((parts (mu4e~view-gather-mime-parts))
|
||||
(options (mapcar (lambda (action) `(,(plist-get action :name) . ,action))
|
||||
mu4e-view-mime-part-actions))
|
||||
(handle (or (cdr-safe (cl-find-if (lambda (part) (eq (car part) n)) parts))
|
||||
(mu4e-error "MIME-part %s not found" n)))
|
||||
(action (or (and options (mu4e-read-option "Action on MIME-part: " options))
|
||||
(mu4e-error "No such action")))
|
||||
(handler (or (plist-get action :handler)
|
||||
(mu4e-error "No :handler item found for action %S" action)))
|
||||
(receives (or (plist-get action :receives)
|
||||
(mu4e-error "No :receives item found for action %S" action))))
|
||||
(save-excursion
|
||||
(cond
|
||||
((functionp handler)
|
||||
(cond
|
||||
((eq receives 'index) (funcall handler n))
|
||||
((eq receives 'pipe) (funcall handler (mm-with-unibyte-buffer
|
||||
(mm-insert-part handle)
|
||||
(buffer-string))))
|
||||
((eq receives 'temp)
|
||||
(funcall handler (mu4e~view-mime-part-to-temp-file handle)))
|
||||
(t (mu4e-error "Invalid :receive for %S" action))))
|
||||
((stringp handler)
|
||||
(cond
|
||||
((eq receives 'index) (shell-command (concat handler " " (shell-quote-argument n))))
|
||||
((eq receives 'pipe) (mm-pipe-part handle handler))
|
||||
((eq receives 'temp)
|
||||
(shell-command (shell-command (concat handler " "
|
||||
(shell-quote-argument
|
||||
(mu4e~view-mime-part-to-temp-file handle))))))
|
||||
(t (mu4e-error "Invalid action %S" action))))))))
|
||||
|
||||
(defun mu4e-view-toggle-html ()
|
||||
"Toggle html-display of the first html-part found."
|
||||
(interactive)
|
||||
;; This function assumes `gnus-article-mime-handle-alist' is sorted by
|
||||
;; pertinence, i.e. the first HTML part found in it is the most important one.
|
||||
(if-let ((html-part
|
||||
(seq-find (lambda (handle)
|
||||
(equal (mm-handle-media-type (cdr handle)) "text/html"))
|
||||
gnus-article-mime-handle-alist)))
|
||||
(gnus-article-inline-part (car html-part))
|
||||
(mu4e-warn "No html part in this message")))
|
||||
|
||||
|
||||
(provide 'mu4e-view-gnus)
|
||||
;;; mu4e-view-gnus.el ends here
|
File diff suppressed because it is too large
Load Diff
1249
mu4e/mu4e-view.el
1249
mu4e/mu4e-view.el
File diff suppressed because it is too large
Load Diff
203
mu4e/mu4e.el
203
mu4e/mu4e.el
|
@ -1,6 +1,6 @@
|
|||
;;; mu4e.el --- part of mu4e, the mu mail user agent -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2021 Dirk-Jan C. Binnema
|
||||
;; Copyright (C) 2011-2019 Dirk-Jan C. Binnema
|
||||
|
||||
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
|
@ -27,33 +27,13 @@
|
|||
;;; Code:
|
||||
|
||||
(require 'mu4e-vars)
|
||||
(require 'mu4e-helpers)
|
||||
(require 'mu4e-folders)
|
||||
(require 'mu4e-context)
|
||||
(require 'mu4e-contacts)
|
||||
(require 'mu4e-headers)
|
||||
(require 'mu4e-compose)
|
||||
(require 'mu4e-bookmarks)
|
||||
(require 'mu4e-update)
|
||||
(require 'mu4e-main)
|
||||
(require 'mu4e-server) ;; communication with backend
|
||||
|
||||
|
||||
|
||||
(defcustom mu4e-confirm-quit t
|
||||
"Whether to confirm to quit mu4e."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-org-support t
|
||||
"Support Org-mode links."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
|
||||
(defcustom mu4e-speedbar-support nil
|
||||
"Support having a speedbar to navigate folders/bookmarks."
|
||||
:type 'boolean
|
||||
:group 'mu4e)
|
||||
(require 'mu4e-headers) ;; headers view
|
||||
(require 'mu4e-view) ;; message view
|
||||
(require 'mu4e-main) ;; main screen
|
||||
(require 'mu4e-compose) ;; message composition / sending
|
||||
(require 'mu4e-proc) ;; communication with backend
|
||||
(require 'mu4e-utils) ;; utility functions
|
||||
(require 'mu4e-context) ;; support for contexts
|
||||
|
||||
(when mu4e-speedbar-support
|
||||
(require 'mu4e-speedbar)) ;; support for speedbar
|
||||
|
@ -68,177 +48,20 @@
|
|||
|
||||
;;;###autoload
|
||||
(defun mu4e (&optional background)
|
||||
"If mu4e is not running yet, start it.
|
||||
Then, show the main window, unless BACKGROUND (prefix-argument)
|
||||
is non-nil."
|
||||
"If mu4e is not running yet, start it. Then, show the main
|
||||
window, unless BACKGROUND (prefix-argument) is non-nil."
|
||||
(interactive "P")
|
||||
;; start mu4e, then show the main view
|
||||
(mu4e--init-handlers)
|
||||
(mu4e--start (unless background 'mu4e~main-view)))
|
||||
(mu4e~start (unless background 'mu4e~main-view)))
|
||||
|
||||
(defun mu4e-quit()
|
||||
"Quit the mu4e session."
|
||||
(interactive)
|
||||
(if mu4e-confirm-quit
|
||||
(when (y-or-n-p (mu4e-format "Are you sure you want to quit?"))
|
||||
(mu4e--stop))
|
||||
(mu4e--stop)))
|
||||
|
||||
;;; Internals
|
||||
(mu4e~stop))
|
||||
(mu4e~stop)))
|
||||
|
||||
(defun mu4e--check-requirements ()
|
||||
"Check for the settings required for running mu4e."
|
||||
(unless (>= emacs-major-version 25)
|
||||
(mu4e-error "Emacs >= 25.x is required for mu4e"))
|
||||
(when (mu4e-server-properties)
|
||||
(unless (string= (mu4e-server-version) mu4e-mu-version)
|
||||
(mu4e-error "The mu server has version %s, but we need %s"
|
||||
(mu4e-server-version) mu4e-mu-version)))
|
||||
(unless (and mu4e-mu-binary (file-executable-p mu4e-mu-binary))
|
||||
(mu4e-error "Please set `mu4e-mu-binary' to the full path to the mu
|
||||
binary"))
|
||||
(dolist (var '(mu4e-sent-folder mu4e-drafts-folder
|
||||
mu4e-trash-folder))
|
||||
(unless (and (boundp var) (symbol-value var))
|
||||
(mu4e-error "Please set %S" var))
|
||||
(unless (functionp (symbol-value var)) ;; functions are okay, too
|
||||
(let* ((dir (symbol-value var))
|
||||
(path (concat (mu4e-root-maildir) dir)))
|
||||
(unless (string= (substring dir 0 1) "/")
|
||||
(mu4e-error "%S must start with a '/'" dir))
|
||||
(unless (mu4e-create-maildir-maybe path)
|
||||
(mu4e-error "%s (%S) does not exist" path var))))))
|
||||
|
||||
;;; Starting / getting mail / updating the index
|
||||
|
||||
(defun mu4e--pong-handler (_data func)
|
||||
"Handle 'pong' responses from the mu server.
|
||||
Invoke FUNC if non-nil."
|
||||
(let ((doccount (plist-get (mu4e-server-properties) :doccount)))
|
||||
(mu4e--check-requirements)
|
||||
(when func (funcall func))
|
||||
(when (zerop doccount)
|
||||
(mu4e-message "Store is empty; (re)indexing. This may take a while.") ;
|
||||
(mu4e-update-index))
|
||||
(when (and mu4e-update-interval (null mu4e--update-timer))
|
||||
(setq mu4e--update-timer
|
||||
(run-at-time 0 mu4e-update-interval
|
||||
(lambda () (mu4e-update-mail-and-index
|
||||
mu4e-index-update-in-background)))))))
|
||||
|
||||
(defun mu4e--start (&optional func)
|
||||
"Start mu4e.
|
||||
If `mu4e-contexts' have been defined, but we don't have a context
|
||||
yet, switch to the matching one, or none matches, the first. If
|
||||
mu4e is already running, execute function FUNC (if non-nil).
|
||||
Otherwise, check various requireme`'nts, then start mu4e. When
|
||||
successful, call FUNC (if non-nil) afterwards."
|
||||
(unless (mu4e-context-current)
|
||||
(mu4e--context-autoswitch nil mu4e-context-policy))
|
||||
(setq mu4e-pong-func (lambda (info) (mu4e--pong-handler info func)))
|
||||
(mu4e--server-ping
|
||||
(mapcar ;; send it a list of queries we'd like to see read/unread info for
|
||||
(lambda (bm)
|
||||
(funcall (or mu4e-search-query-rewrite-function #'identity)
|
||||
(plist-get bm :query)))
|
||||
;; exclude bookmarks that are not strings, and with certain flags
|
||||
(seq-filter (lambda (bm)
|
||||
(and (stringp (plist-get bm :query))
|
||||
(not (or (plist-get bm :hide)
|
||||
(plist-get bm :hide-unread)))))
|
||||
(append (mu4e-bookmarks)
|
||||
(mu4e--maildirs-with-query)))))
|
||||
;; maybe request the list of contacts, automatically refreshed after
|
||||
;; reindexing
|
||||
(unless mu4e--contacts-hash (mu4e--request-contacts-maybe)))
|
||||
|
||||
(defun mu4e--stop ()
|
||||
"Stop mu4e."
|
||||
(when mu4e--update-timer
|
||||
(cancel-timer mu4e--update-timer)
|
||||
(setq mu4e--update-timer nil))
|
||||
(mu4e-clear-caches)
|
||||
(mu4e--server-kill)
|
||||
;; kill all mu4e buffers
|
||||
(mapc
|
||||
(lambda (buf)
|
||||
;; When using the Gnus-based viewer, the view buffer has the
|
||||
;; kill-buffer-hook function mu4e~view-kill-buffer-hook-fn which kills the
|
||||
;; mm-* buffers created by Gnus' article mode. Those have been returned by
|
||||
;; `buffer-list' but might already be deleted in case the view buffer has
|
||||
;; been killed first. So we need a `buffer-live-p' check here.
|
||||
(when (buffer-live-p buf)
|
||||
(with-current-buffer buf
|
||||
(when (member major-mode
|
||||
'(mu4e-headers-mode mu4e-view-mode mu4e-main-mode))
|
||||
(kill-buffer)))))
|
||||
(buffer-list)))
|
||||
|
||||
;;; Handlers
|
||||
(defun mu4e--error-handler (errcode errmsg)
|
||||
"Handler function for showing an error with ERRCODE and ERRMSG."
|
||||
;; don't use mu4e-error here; it's running in the process filter context
|
||||
(cl-case errcode
|
||||
(4 (mu4e-warn "No matches for this search query."))
|
||||
(110 (display-warning 'mu4e errmsg :error)) ;; schema version.
|
||||
(t (error "Error %d: %s" errcode errmsg))))
|
||||
|
||||
|
||||
(defun mu4e--info-handler (info)
|
||||
"Handler function for (:INFO ...) sexps received from server."
|
||||
(let* ((type (plist-get info :info))
|
||||
(processed (plist-get info :processed))
|
||||
(updated (plist-get info :updated))
|
||||
(cleaned-up (plist-get info :cleaned-up))
|
||||
(mainbuf (get-buffer mu4e-main-buffer-name)))
|
||||
(cond
|
||||
((eq type 'add) t) ;; do nothing
|
||||
((eq type 'index)
|
||||
(if (eq (plist-get info :status) 'running)
|
||||
(mu4e-index-message
|
||||
"Indexing... processed %d, updated %d" processed updated)
|
||||
(progn
|
||||
(mu4e-index-message
|
||||
"%s completed; processed %d, updated %d, cleaned-up %d"
|
||||
(if mu4e-index-lazy-check "Lazy indexing" "Indexing")
|
||||
processed updated cleaned-up)
|
||||
;; call the updated hook if anything changed.
|
||||
(unless (zerop (+ updated cleaned-up))
|
||||
(run-hooks 'mu4e-index-updated-hook))
|
||||
(unless (and (not (string= mu4e--contacts-tstamp "0"))
|
||||
(zerop (plist-get info :updated)))
|
||||
(mu4e--request-contacts-maybe))
|
||||
(when (and (buffer-live-p mainbuf) (get-buffer-window mainbuf))
|
||||
(save-window-excursion
|
||||
(select-window (get-buffer-window mainbuf))
|
||||
(mu4e~main-view 'refresh))))))
|
||||
((plist-get info :message)
|
||||
(mu4e-index-message "%s" (plist-get info :message))))))
|
||||
|
||||
(defun mu4e--init-handlers()
|
||||
"Initialize the server message handlers.
|
||||
Only set set them if they were nil before, so overriding has a
|
||||
chance."
|
||||
(mu4e-setq-if-nil mu4e-error-func #'mu4e--error-handler)
|
||||
(mu4e-setq-if-nil mu4e-update-func #'mu4e~headers-update-handler)
|
||||
(mu4e-setq-if-nil mu4e-remove-func #'mu4e~headers-remove-handler)
|
||||
(mu4e-setq-if-nil mu4e-view-func #'mu4e~headers-view-handler)
|
||||
(mu4e-setq-if-nil mu4e-header-func #'mu4e~headers-header-handler)
|
||||
(mu4e-setq-if-nil mu4e-found-func #'mu4e~headers-found-handler)
|
||||
(mu4e-setq-if-nil mu4e-erase-func #'mu4e~headers-clear)
|
||||
|
||||
(mu4e-setq-if-nil mu4e-sent-func #'mu4e--default-handler)
|
||||
(mu4e-setq-if-nil mu4e-compose-func #'mu4e~compose-handler)
|
||||
(mu4e-setq-if-nil mu4e-contacts-func #'mu4e--update-contacts)
|
||||
(mu4e-setq-if-nil mu4e-info-func #'mu4e--info-handler)
|
||||
(mu4e-setq-if-nil mu4e-pong-func #'mu4e--default-handler))
|
||||
|
||||
(defun mu4e-clear-caches ()
|
||||
"Clear any cached resources."
|
||||
(setq
|
||||
mu4e-maildir-list nil
|
||||
mu4e--contacts-hash nil
|
||||
mu4e--contacts-tstamp "0"))
|
||||
;;; _
|
||||
(provide 'mu4e)
|
||||
;;; mu4e.el ends here
|
||||
|
|
|
@ -266,20 +266,38 @@ details depend on your distribution. If you're using another
|
|||
distribution (or another OS), the below can at least be helpful in
|
||||
identifying the packages to install.
|
||||
|
||||
We provide some instructions for Debian, Ubuntu and Fedora; if those
|
||||
do not apply to you, you can follow either @ref{Building from a
|
||||
release tarball} or @ref{Building from git}.
|
||||
We provide some instructions for Debian, Ubuntu and Fedora; if those do not
|
||||
apply to you, you can follow either @ref{Building from a release tarball} or
|
||||
@ref{Building from git}.
|
||||
|
||||
@subsection Dependencies for Debian/Ubuntu
|
||||
|
||||
@example
|
||||
$ sudo apt-get install libgmime-3.0-dev libxapian-dev emacs
|
||||
$ sudo apt-get install libgmime-3.0-dev libxapian-dev
|
||||
|
||||
# get emacs 25 or higher if you don't have it yet
|
||||
$ sudo apt-get install emacs
|
||||
|
||||
# optional
|
||||
$ sudo apt-get install guile-2.2-dev html2text xdg-utils
|
||||
|
||||
# optional: only needed for msg2pdf and mug (toy gtk+ frontend)
|
||||
$ sudo apt-get install libwebkitgtk-3.0-dev
|
||||
@end example
|
||||
|
||||
@subsection Dependencies for Fedora
|
||||
|
||||
@example
|
||||
$ sudo yum install gmime30-devel xapian-core-devel emacs
|
||||
$ sudo yum install gmime30-devel xapian-core-devel
|
||||
|
||||
# get emacs 25 or higher if you don't have it yet
|
||||
$ sudo yum install emacs
|
||||
|
||||
# optional
|
||||
$ sudo yum install html2text xdg-utils guile22-devel
|
||||
|
||||
# optional: only needed for msg2pdf and mug (toy gtk+ frontend)
|
||||
$ sudo yum install webkitgtk3-devel
|
||||
@end example
|
||||
|
||||
@subsection Building on Msys2
|
||||
|
@ -330,18 +348,10 @@ Xapian, GMime and their dependencies must be installed.
|
|||
@subsection Building from git
|
||||
@anchor{Building from git}
|
||||
|
||||
By default, @t{mu} use the Meson@footnote{@url{https://mesonbuild.com/}} build-system.
|
||||
|
||||
@example
|
||||
$ git clone git://github.com/djcb/mu.git
|
||||
$ cd mu
|
||||
$ meson build && ninja -C build
|
||||
$ sudo ninja -C install
|
||||
@end example
|
||||
|
||||
For now, you can also use the (deprecated) @t{autotools} build setup,
|
||||
assuming you have autotools (@t{autoconf}, @t{automake}, @t{libtool},
|
||||
@t{texinfo}) installed:
|
||||
Alternatively, if you build from the git repository or use a tarball
|
||||
like the ones that @t{github} produces, the instructions are slightly
|
||||
different, and require you to have autotools (@t{autoconf},
|
||||
@t{automake}, @t{libtool}, @t{texinfo}) installed:
|
||||
|
||||
@example
|
||||
# get from git (alternatively, use a github tarball)
|
||||
|
@ -358,18 +368,29 @@ $ sudo make install
|
|||
After this, @t{mu} and @t{mu4e} should be installed @footnote{there's
|
||||
a hard dependency between versions of @t{mu4e} and @t{mu} --- you
|
||||
cannot combine different versions} on your system, and be available
|
||||
from the command line and in Emacs.
|
||||
from the command line and in Emacs.
|
||||
|
||||
You may need to restart Emacs, so it can find @t{mu4e} in its
|
||||
@code{load-path}. If, even after restarting, Emacs cannot find
|
||||
@t{mu4e}, you may need to add it to your @code{load-path} explicitly;
|
||||
check where @t{mu4e} is installed, and add something like the
|
||||
following to your configuration before trying again:
|
||||
@t{mu4e}, you may need to add it to your @code{load-path} explicitly; check
|
||||
where @t{mu4e} is installed, and add something like the following to your
|
||||
configuration before trying again:
|
||||
@lisp
|
||||
;; the exact path may differ --- check it
|
||||
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e")
|
||||
@end lisp
|
||||
|
||||
@subsection Building using the meson build system
|
||||
|
||||
As an (experimental) alternative to the @t{autotools}-build, it is possible to use
|
||||
the Meson@footnote{@url{https://mesonbuild.com/}} build-system instead.
|
||||
|
||||
@example
|
||||
$ git clone git://github.com/djcb/mu.git
|
||||
$ cd mu
|
||||
$ meson build && ninja -C build
|
||||
$ sudo ninja -C install
|
||||
@end example
|
||||
|
||||
@subsection mu4e and emacs customization
|
||||
|
||||
|
@ -1005,6 +1026,7 @@ E edit (only allowed for draft messages)
|
|||
|
||||
misc
|
||||
----
|
||||
; switch context
|
||||
a execute some custom action on a header
|
||||
| pipe message through shell command
|
||||
C-+,C-- increase / decrease the number of headers shown
|
||||
|
@ -1013,10 +1035,6 @@ C-S-u update mail & reindex
|
|||
q leave the headers buffer
|
||||
@end verbatim
|
||||
|
||||
Furthermore, a number of keybindings are available through minor modes:
|
||||
@itemize
|
||||
@item Context; see @pxref{Contexts}.
|
||||
@end itemize
|
||||
|
||||
@node HV Marking
|
||||
@section Marking
|
||||
|
@ -1303,6 +1321,7 @@ A execute some custom action on the message's MIME-parts
|
|||
|
||||
misc
|
||||
----
|
||||
; switch context
|
||||
. show the raw message view. 'q' takes you back.
|
||||
C-+,C-- increase / decrease the number of headers shown
|
||||
H get help
|
||||
|
@ -1310,11 +1329,6 @@ C-S-u update mail & reindex
|
|||
q leave the message view
|
||||
@end verbatim
|
||||
|
||||
Furthermore, a number of keybindings are available through minor modes:
|
||||
@itemize
|
||||
@item Context; see @pxref{Contexts}.
|
||||
@end itemize
|
||||
|
||||
For the marking commands, please refer to @ref{Marking messages}.
|
||||
|
||||
@node MSGV Rich-text and images
|
||||
|
@ -2287,7 +2301,7 @@ loading @t{mu4e}):
|
|||
;; must come before proc-move since retag runs
|
||||
;; 'sed' on the file
|
||||
(mu4e-action-retag-message msg "-\\Inbox")
|
||||
(mu4e--server-move docid nil "+S-u-N"))))
|
||||
(mu4e~proc-move docid nil "+S-u-N"))))
|
||||
@end lisp
|
||||
|
||||
Adding to @code{mu4e-marks} list allows to use the mark in bulk operations
|
||||
|
@ -5058,15 +5072,15 @@ to provide this information (this is implemented in
|
|||
@file{mu-cmd-server.c}).
|
||||
|
||||
We start this sequence when @t{mu4e} is invoked (when the program is
|
||||
started). It calls @t{mu4e-server-ping}, and registers a (lambda) function for
|
||||
@t{mu4e-server-pong-func}, to handle the response.
|
||||
started). It calls @t{mu4e-proc-ping}, and registers a (lambda) function for
|
||||
@t{mu4e-proc-pong-func}, to handle the response.
|
||||
|
||||
@verbatim
|
||||
-> (ping)
|
||||
<-<prefix>(:pong "mu" :props (:version "x.x.x" :doccount 78545))
|
||||
@end verbatim
|
||||
|
||||
When we receive such a @t{pong} (in @file{mu4e-server.el}), the lambda
|
||||
When we receive such a @t{pong} (in @file{mu4e-proc.el}), the lambda
|
||||
function we registered is called, and it compares the version we got
|
||||
from the @t{pong} with the version we expected, and raises an error if
|
||||
they differ.
|
||||
|
|
Loading…
Reference in New Issue