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:
Dirk-Jan C. Binnema 2021-08-30 10:05:56 +03:00
parent 42f2e73025
commit 6d555b3a6c
45 changed files with 5993 additions and 4570 deletions

View File

@ -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:

2
.dir-locals.el Normal file
View File

@ -0,0 +1,2 @@
((emacs-lisp-mode
(indent-tabs-mode . nil)))

View File

@ -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

View File

@ -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

35
.travis.yml Normal file
View File

@ -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

130
HACKING Normal file
View File

@ -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:

View File

@ -44,6 +44,7 @@ tags:
EXTRA_DIST= \
TODO \
HACKING \
README.org \
gtest.mk \
NEWS \

View File

@ -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

View File

@ -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

View File

@ -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 $@

View File

@ -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

View File

@ -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));
}

View File

@ -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 \

View File

@ -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])])

View File

@ -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',
]

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."

View 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)))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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

View File

@ -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"))))

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

1392
mu4e/mu4e-utils.el Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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

642
mu4e/mu4e-view-common.el Normal file
View File

@ -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)

657
mu4e/mu4e-view-gnus.el Normal file
View File

@ -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

1097
mu4e/mu4e-view-old.el Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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.