Skip to content

Text filling and wrapping plugin for Apple Mail

License

Notifications You must be signed in to change notification settings

arachsys/mailwrap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MailWrap
========

MailWrap is a plugin for Apple Mail on macOS 10.12 to 12.x, making it
easier to compose well-formed and correctly wrapped plain text messages. It
introduces line wrapping and paragraph filling operations, and extends the
built-in indentation functions to work in plain text mode.

Mail was once quite a good 'net citizen. It could be configured to generate
plain text email, and would do so with the text neatly wrapped at 76
columns. In addition, it used the RFC2646 format=flowed extension to
indicate that these wrapped paragraphs could be reflowed. Thus mailing list
archives and traditional mail clients saw readable 80-column text without
noisy encoding, but more sophisticated readers could re-fill the paragraphs
to suit the display width.

Unfortunately, Mail is poorly maintained and has declined in quality over
recent years, with many bugs introduced and incompetent design choices made.
Current releases have ditched format=flowed for plain text parts, instead
opting to emit each paragraph as a single long line. If this is longer than
77 characters, the text is mangled with a quoted-printable transfer
encoding. The resulting email leaves a very visible mess in mailing list
archives on the web, as well as drawing understandable ire from recipients
with traditional unix mail clients.

Fortunately, even if Mac users find themselves drawn to Apple Mail for its
convenient reading interface and good platform integration, all is not lost.
If lines are no longer than 77 characters, Mail won't use quoted-printable
in your outgoing messages, resulting in perfectly acceptable plain text
email that won't embarrass you in public. Manual paragraph filling is
tedious and time-consuming, but the MailWrap plugin can help automate the
job.

An alternative, more automatic solution to the same problem is implemented
in the MailFlow plugin, which reimplements format=flowed in Mail and
eliminates quoted-printable plain text email under most circumstances. It is
available from

  https://github.com/arachsys/mailflow
  https://bitbucket.org/arachsys/mailflow

alongside this plugin. The two plugins can now happily coexist.


Compatibility
-------------

MailWrap is currently compatible with Apple Mail 10.0 to 15.x included in
macOS 10.12 (Sierra) to 12.x (Monterey).

It does not yet support Mail 16.x included in macOS 13.x (Ventura). This
makes major changes to the message editor, requiring non-trivial fixes to
the way MailWrap hooks into the composer. In particular, it is no longer
obvious how to access the message DOM tree, which MailWrap relies on to
correct attribution lines, detect plain text mode and extend indentation.

I do not use macOS apart from maintaining MailFlow and MailWrap, and only
have occasional access to machines running the most recent versions. As far
as I can tell, the class-dump utility which I relied on to develop MailWrap
is also broken by the latest OS release. I would welcome input from anyone
with better insight into the Mail.app changes, or just with more patience
to reverse-engineer its internals once again. Please do get in touch with
Chris Webb <[email protected]>.


Installation
------------

To install, clone the git repository or unpack the source tar.gz, change to
the source directory and run 'python install.py' or 'python3 install.py'.
Your terminal will need access to ~/Library/Mail/Bundles/ which you can
grant in the Privacy and Security tab of System Settings.

The installer and plugin work with both the system Python 2.7 and more
recent Python 3.x, but py2app and pyobjc are required. The installer will
prompt you to install these with pip/pip3 if they can't be found.

Plugin bundles contain a list of UUIDs identifying versions of Mail with
which they are compatible. The install.py script extracts the correct UUID
from the installed version of Mail, generates a MailWrap.bundle to match,
and installs it in ~/Library/Mail/Bundles/. You will need to quit and
relaunch Mail for the plugin to be registered.

On macOS 10.14 and later, the plugin must be explicitly enabled in Mail
Preferences or Settings. Choose 'Manage Plug-ins...' from the General tab,
tick MailWrap.mailbundle, then choose 'Apply and Restart Mail'.

On macOS 11.0 and later, the plugin also needs to be ad-hoc signed and
authorised before it will work. The installer will run

  codesign -f -s - ~/Library/Mail/Bundles/MailWrap.mailbundle
  spctl --add --label MailWrap ~/Library/Mail/Bundles/MailWrap.mailbundle
  spctl --enable --label MailWrap

for you, but spctl will require your password to allow the changes. Many
thanks to A. Wilcox (awilfox) for providing these signing instructions on
their Cat Fox Life blog.

If the sandbox doesn't allow Mail.app to read the directory where your
Python is installed, you may need to change 'semi_standalone' to False in
the setup options in install.py. This issue doesn't affect /usr/bin/python
or /usr/bin/python3, but has been reported with Homebrew Python running
from /opt/homebrew/bin/python3 and /opt/homebrew/Cellar/[email protected]/.

If you use the system Python 2.7 on macOS 11.0 or later, you will need to
set SYSTEM_VERSION_COMPAT=0 in the environment when running install.py:

  SYSTEM_VERSION_COMPAT=0 /usr/bin/python install.py

Without this, a horrible Apple hack will cause the installer to detect the
OS version incorrectly as 10.16. It aborts with a warning if that happens.

Sometimes when Mail is updated, its compatibility UUID changes. Mail will
then disable plugins, moving them from 'Bundles/' to 'Bundles (Disabled)/'.
The user is notified when this happens, and it is sufficient to simply run
the install.py script again. The old disabled bundle will be cleared away,
and a new one built and installed to match the new version of Mail.


Features
--------

Two new items are appended to the Edit menu, 'Fill Text' and 'Wrap Text',
with key-bindings Command-\ and Option-Command-\.

Fill Text reformats the current paragraph block to the configured width,
treating a block of lines separated by spaces or a change in quote level as
a single paragraph. If a block of text is selected, all paragraphs which
overlap the selection are filled.

This operation preserves quote level, allowing for the quote characters in
the fill width, and if the first line is indented by one or more spaces,
subsequent lines will be indented with spaces to the same column. Exactly
one blank line is left between the formatted paragraph and the next.

Similarly, Wrap Text wraps the current line to the configured width,
retaining any leading indent and the quote level on any continuation lines.
If a block of text is selected, all lines which overlap the selection are
wrapped.

MailWrap also fixes the built-in Increase/Decrease Indentation operations to
work on plain text messages. These will insert or remove a configurable
number of spaces at the start of the current line or all lines overlapping
the current selection.

Finally, by default MailWrap will trim the excessively verbose attribution
line Mail inserts when composing a reply, i.e.

  On 8 Apr 2014, at 10:08:34, Chris Webb <[email protected]> wrote:

 will become just

  Chris Webb <[email protected]> wrote:

Since version 8.0, Mail has a bug which causes the attribution line to be
quoted as if it were part of the original message. MailFlow will also fix
this whilst trimming the attribution line.

This behaviour can be disabled in the preferences.


Configuration
-------------

MailWrap reads a handful of preferences from the com.apple.mail domain.
These can be set at the command line with the macOS defaults command:

  defaults write com.apple.mail MailWrap -dict-add BulletLists -bool false
  defaults write com.apple.mail MailWrap -dict-add BulletLists -bool true
    - configure MailWrap to interpret '- ', '+ ' or '* ' at the start of a
      paragraph as introducing a bullet item and so indent subsequent lines
      appropriately. The default is on.

  defaults write com.apple.mail MailWrap -dict-add FixAttribution -bool false
  defaults write com.apple.mail MailWrap -dict-add FixAttribution -bool true
    - configure MailWrap to strip the verbose date and time information
      from the attribution line when composing a reply. The default is on.

  defaults write com.apple.mail MailWrap -dict-add IndentWidth -int NN
    - configure MailWrap to increase/decrease plain text indentation by NN
      spaces when Command-] and Command-[ are used. The default is 2.

  defaults write com.apple.mail MailWrap -dict-add WrapWidth -int NN
    - wrap lines and fill paragraphs at a width of NN characters. The
      default is 76, which is short enough that Mail won't inflict a
      quoted-printable transfer encoding on the message text.


pbmbox
------

For command-line users, the MailWrap distribution also includes a small
utility, pbmbox. When messages are selected and copied in Mail, they are
added to the clipboard as RFC822MessageDatasPboardType objects. pbmbox
decodes these objects and emits the messages in unix mbox format on stdout.

To install, copy it to a directory in your PATH and make it executable:

  sudo install -m 0755 pbmbox.py /usr/local/bin/pbmbox

A typical use is importing a patch series from email into a git repository.
Select the messages in Mail, copy them with Command-C, and then run

  pbmbox | git am

within the repository.

By default, the mboxrd format is used: lines beginning with />*From / are
quoted with one additional leading '>'. This encoding avoids corruption of
messages and is always reversible. If the -n or --no-quote-from option is
given, pbmbox will not attempt to quote 'From  ' lines. This is sometimes
useful for simple command-line handling of a single message, where no
ambiguity can result from an unquoted 'From '.


Implementation notes
--------------------

Apple haven't done everything wrong in Mail. Writing this plugin would be
near-impossible had their mail client not been furnished with a plugin
interface, albeit an undocumented one. The reading interface is nice, with
good platform integration, fast full-text search and cross-mailbox
threading.

Curious readers of the code may be surprised by the long-winded approach
taken in manipulating the message text. The editor is a WebKit editable
view, for both rich text (HTML) and plain text messages, and it is certainly
possible (and more efficient) to directly manipulate and modify the
underlying DOM tree. However, MailWrap deliberately takes another approach,
sticking to the simple NSResponder interface which offers simple operations
such as moveToBeginningOfParagraph:, moveToEndOfParagraphAndModifySelection:
and insertText:.

This is not due to a perverse desire to use the most awkward interface or
write the least efficient wrapping code possible! When manipulating the
message in simple ways that would not possible through the UI, it turns out
to be very easy to get Mail into a confused state, where for instance the
message looks fine in the editor but has unintended blank lines when
rendered to plain text upon sending. The BLOCKQUOTE elements used to
implement quoting are particularly fussy in this regard.

Even using the NSResponder interfaces, it is necessary to insert a temporary
padding space at the beginning of a paragraph before selecting and replacing
the rest of the text to avoid problems with lost quote level on the second
and subsequent lines of the reformatted paragraph. (Of course, this means
that this is a bug that can be triggered directly from normal user input at
the keyboard; I have reproduced and reported it to Apple in this form.)

An additional benefit of sticking to basic interfaces is that they are less
likely to change, disappear or develop significant bugs in future updates of
Mail.


Copying
-------

This software was written by Chris Webb <[email protected]> and is
distributed as Free Software under the terms of the MIT license in COPYING.

About

Text filling and wrapping plugin for Apple Mail

Topics

Resources

License

Stars

Watchers

Forks

Languages