diff --git a/evil-collection-mu4e.el b/evil-collection-mu4e.el new file mode 100644 index 0000000..87ee390 --- /dev/null +++ b/evil-collection-mu4e.el @@ -0,0 +1,336 @@ +;;; evil-collection-mu4e.el --- Evil bindings for mu4e -*- lexical-binding: t -*- + +;; Copyright (C) 2015-2018 Joris Engbers +;; Copyright (C) 2018 Pierre Neidhardt + +;; Author: Joris Engbers +;; Maintainer: James Nguyen +;; Pierre Neidhardt +;; URL: https://github.com/emacs-evil/evil-collection +;; Version: 0.0.9 +;; Package-Requires: ((emacs "24.4") (evil "1.2.10")) +;; Keywords: evil, mu4e, tools + +;; This file 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, or (at your +;; option) any later version. +;; +;; This file 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. +;; +;; For a full copy of the GNU General Public License +;; see . + +;;; Commentary: +;; Evil keybindings for mu4e that make sense for Evil users. The following +;; keybindings are defined: +;; +;; General commands: +;; | Commmand | evil-mu4e | Alternative | +;; |--------------------------+-----------+-------------| +;; | Jump to maildir | J | | +;; | Update | u | | +;; | Compose message | cc | C | +;; | Kill update mail process | x | | + +;; Commands for header-mode and view-mode: +;; | Command | evil-mu4e | Alternative | +;; |---------------------------------+-----------+-------------| +;; | Next message | C-j | | +;; | Previous message | C-k | | +;; | Mark the current thread as read | T | | +;; | Compose message | cc | C | +;; | Compose edit** | ce | E | +;; | Compose forward** | cf | F | +;; | Compose reply | cr | R | +;; | Change sorting*** | o | O | +;; | Rerun search | gr | | +;; | Toggle include related | zr | | +;; | Toggle threading | zt | | +;; | Toggle hide cited | za | | +;; | Skip duplicates | zd | | +;; | Show log | gl | | +;; | Select other view | gv | | +;; | Save attachement(s) | p | P | +;; | Save url | yu | | +;; | Go to url | gx | | +;; | Fetch url | gX | | +;; +;; - * denotes only in header-mode +;; - ** denotes Alternative only in header-mode +;; - *** denotes Alternative only in view-mode +;; +;;; Code: + +(require 'evil) +(require 'mu4e) + +(declare-function evil-collection-define-key "evil-collection") +(defconst evil-collection-mu4e-maps '(mu4e-main-mode-map + mu4e-headers-mode-map + mu4e-view-mode-map + mu4e-compose-mode-map)) + +(defcustom evil-collection-mu4e-state 'normal + "State to use in mu4e buffers where keybindings are altered." + :group 'mu4e + :type 'symbol) + + +;;; By default all mu4e modes except for mu4e-compose-mode will start in +;;; evil-emacs-state. This section makes all modes start in `evil-collection-mu4e-state'. + +(defvar evil-collection-mu4e-emacs-to-evil-mu4e-state-modes + '(mu4e-main-mode + mu4e-headers-mode + mu4e-view-mode + mu4e-org-mode) + "Modes that should switch from Emacs state to `evil-collection-mu4e-state'.") + +(defun evil-collection-mu4e-set-state () + "Associate all relevant modes with the evil-collection-mu4e-state." + (dolist (mode evil-collection-mu4e-emacs-to-evil-mu4e-state-modes) + (evil-set-initial-state mode evil-collection-mu4e-state)) + (evil-set-initial-state 'mu4e-compose-mode 'insert)) + + + +;;; Define bindings + +;; TODO: Inhibit insert-state functions as per Evil Collection. +(defvar evil-mu4e-mode-map-bindings + `((,evil-collection-mu4e-state mu4e-main-mode-map "J" mu4e~headers-jump-to-maildir) + (,evil-collection-mu4e-state mu4e-main-mode-map "j" next-line) + (,evil-collection-mu4e-state mu4e-main-mode-map "k" previous-line) + (,evil-collection-mu4e-state mu4e-main-mode-map "u" mu4e-update-mail-and-index) + (,evil-collection-mu4e-state mu4e-main-mode-map "gr" revert-buffer) + (,evil-collection-mu4e-state mu4e-main-mode-map "b" mu4e-headers-search-bookmark) + (,evil-collection-mu4e-state mu4e-main-mode-map "B" mu4e-headers-search-bookmark-edit) + (,evil-collection-mu4e-state mu4e-main-mode-map "N" mu4e-news) + (,evil-collection-mu4e-state mu4e-main-mode-map ";" mu4e-context-switch) + (,evil-collection-mu4e-state mu4e-main-mode-map "H" mu4e-display-manual) + (,evil-collection-mu4e-state mu4e-main-mode-map "C" mu4e-compose-new) + (,evil-collection-mu4e-state mu4e-main-mode-map "cc" mu4e-compose-new) + (,evil-collection-mu4e-state mu4e-main-mode-map "x" mu4e-kill-update-mail) + (,evil-collection-mu4e-state mu4e-main-mode-map "A" mu4e-about) + (,evil-collection-mu4e-state mu4e-main-mode-map "f" smtpmail-send-queued-mail) + (,evil-collection-mu4e-state mu4e-main-mode-map "m" mu4e~main-toggle-mail-sending-mode) + (,evil-collection-mu4e-state mu4e-main-mode-map "s" mu4e-headers-search) + (,evil-collection-mu4e-state mu4e-main-mode-map "q" mu4e-quit) + + (,evil-collection-mu4e-state mu4e-headers-mode-map "q" mu4e~headers-quit-buffer) + (,evil-collection-mu4e-state mu4e-headers-mode-map "J" mu4e~headers-jump-to-maildir) + (,evil-collection-mu4e-state mu4e-headers-mode-map "C" mu4e-compose-new) + (,evil-collection-mu4e-state mu4e-headers-mode-map "E" mu4e-compose-edit) + (,evil-collection-mu4e-state mu4e-headers-mode-map "F" mu4e-compose-forward) + (,evil-collection-mu4e-state mu4e-headers-mode-map "R" mu4e-compose-reply) + (,evil-collection-mu4e-state mu4e-headers-mode-map "cc" mu4e-compose-new) + (,evil-collection-mu4e-state mu4e-headers-mode-map "ce" mu4e-compose-edit) + (,evil-collection-mu4e-state mu4e-headers-mode-map "cf" mu4e-compose-forward) + (,evil-collection-mu4e-state mu4e-headers-mode-map "cr" mu4e-compose-reply) + (,evil-collection-mu4e-state mu4e-headers-mode-map "o" mu4e-headers-change-sorting) + (,evil-collection-mu4e-state mu4e-headers-mode-map "j" mu4e-headers-next) + (,evil-collection-mu4e-state mu4e-headers-mode-map "k" mu4e-headers-prev) + (,evil-collection-mu4e-state mu4e-headers-mode-map "gr" mu4e-headers-rerun-search) + (,evil-collection-mu4e-state mu4e-headers-mode-map "b" mu4e-headers-search-bookmark) + (,evil-collection-mu4e-state mu4e-headers-mode-map "B" mu4e-headers-search-bookmark-edit) + (,evil-collection-mu4e-state mu4e-headers-mode-map ";" mu4e-context-switch) + (,evil-collection-mu4e-state mu4e-headers-mode-map ,(kbd "RET") mu4e-headers-view-message) + (,evil-collection-mu4e-state mu4e-headers-mode-map "/" mu4e-headers-search-narrow) + (,evil-collection-mu4e-state mu4e-headers-mode-map "s" mu4e-headers-search) + (,evil-collection-mu4e-state mu4e-headers-mode-map "S" mu4e-headers-search-edit) + (,evil-collection-mu4e-state mu4e-headers-mode-map "x" mu4e-mark-execute-all) + (,evil-collection-mu4e-state mu4e-headers-mode-map "a" mu4e-headers-action) + (,evil-collection-mu4e-state mu4e-headers-mode-map "*" mu4e-headers-mark-for-something) ; TODO: Don't override evil-seach-word-forward? + (,evil-collection-mu4e-state mu4e-headers-mode-map "&" mu4e-headers-mark-custom) + (,evil-collection-mu4e-state mu4e-headers-mode-map "A" mu4e-headers-mark-for-action) + (,evil-collection-mu4e-state mu4e-headers-mode-map "m" mu4e-headers-mark-for-move) + (,evil-collection-mu4e-state mu4e-headers-mode-map "r" mu4e-headers-mark-for-refile) + (,evil-collection-mu4e-state mu4e-headers-mode-map "D" mu4e-headers-mark-for-delete) + (,evil-collection-mu4e-state mu4e-headers-mode-map "d" mu4e-headers-mark-for-trash) + (,evil-collection-mu4e-state mu4e-headers-mode-map "=" mu4e-headers-mark-for-untrash) + (,evil-collection-mu4e-state mu4e-headers-mode-map "u" mu4e-headers-mark-for-unmark) + (,evil-collection-mu4e-state mu4e-headers-mode-map "U" mu4e-mark-unmark-all) + (,evil-collection-mu4e-state mu4e-headers-mode-map "?" mu4e-headers-mark-for-unread) + (,evil-collection-mu4e-state mu4e-headers-mode-map "!" mu4e-headers-mark-for-read) + (,evil-collection-mu4e-state mu4e-headers-mode-map "%" mu4e-headers-mark-pattern) + (,evil-collection-mu4e-state mu4e-headers-mode-map "+" mu4e-headers-mark-for-flag) + (,evil-collection-mu4e-state mu4e-headers-mode-map "-" mu4e-headers-mark-for-unflag) + (,evil-collection-mu4e-state mu4e-headers-mode-map "[" mu4e-headers-prev-unread) + (,evil-collection-mu4e-state mu4e-headers-mode-map "]" mu4e-headers-next-unread) + (,evil-collection-mu4e-state mu4e-headers-mode-map "gk" mu4e-headers-prev-unread) + (,evil-collection-mu4e-state mu4e-headers-mode-map "gj" mu4e-headers-next-unread) + (,evil-collection-mu4e-state mu4e-headers-mode-map "\C-j" mu4e-headers-next) + (,evil-collection-mu4e-state mu4e-headers-mode-map "\C-k" mu4e-headers-prev) + (,evil-collection-mu4e-state mu4e-headers-mode-map "zr" mu4e-headers-toggle-include-related) + (,evil-collection-mu4e-state mu4e-headers-mode-map "zt" mu4e-headers-toggle-threading) + (,evil-collection-mu4e-state mu4e-headers-mode-map "zd" mu4e-headers-toggle-skip-duplicates) + (,evil-collection-mu4e-state mu4e-headers-mode-map "gl" mu4e-show-log) + (,evil-collection-mu4e-state mu4e-headers-mode-map "gv" mu4e-select-other-view) + (,evil-collection-mu4e-state mu4e-headers-mode-map "T" (lambda () + (interactive) + (mu4e-headers-mark-thread nil '(read)))) + + ;; (,evil-collection-mu4e-state mu4e-compose-mode-map "gg" mu4e-compose-goto-top) ; TODO: Make this work. + + (,evil-collection-mu4e-state mu4e-view-mode-map ,(kbd "SPC") mu4e-view-scroll-up-or-next) + (,evil-collection-mu4e-state mu4e-view-mode-map ,(kbd "") shr-next-link) + (,evil-collection-mu4e-state mu4e-view-mode-map ,(kbd "") shr-previous-link) + (,evil-collection-mu4e-state mu4e-view-mode-map "q" mu4e~view-quit-buffer) + (,evil-collection-mu4e-state mu4e-view-mode-map "gx" mu4e-view-go-to-url) + (,evil-collection-mu4e-state mu4e-view-mode-map "gX" mu4e-view-fetch-url) + (,evil-collection-mu4e-state mu4e-view-mode-map "C" mu4e-compose-new) + (,evil-collection-mu4e-state mu4e-view-mode-map "H" mu4e-view-toggle-html) + ;; (,evil-collection-mu4e-state mu4e-view-mode-map "E" mu4e-compose-edit) + ;; (,evil-collection-mu4e-state mu4e-view-mode-map "F" mu4e-compose-forward) + (,evil-collection-mu4e-state mu4e-view-mode-map "R" mu4e-compose-reply) + (,evil-collection-mu4e-state mu4e-view-mode-map "cc" mu4e-compose-new) + (,evil-collection-mu4e-state mu4e-view-mode-map "ce" mu4e-compose-edit) + (,evil-collection-mu4e-state mu4e-view-mode-map "cf" mu4e-compose-forward) + (,evil-collection-mu4e-state mu4e-view-mode-map "cr" mu4e-compose-reply) + (,evil-collection-mu4e-state mu4e-view-mode-map "p" mu4e-view-save-attachment) + (,evil-collection-mu4e-state mu4e-view-mode-map "P" mu4e-view-save-attachment-multi) ; Since mu4e 1.0, -multi is same as normal. + (,evil-collection-mu4e-state mu4e-view-mode-map "O" mu4e-headers-change-sorting) + (,evil-collection-mu4e-state mu4e-view-mode-map "o" mu4e-view-open-attachment) + (,evil-collection-mu4e-state mu4e-view-mode-map "A" mu4e-view-attachment-action) + (,evil-collection-mu4e-state mu4e-view-mode-map "a" mu4e-view-action) + (,evil-collection-mu4e-state mu4e-view-mode-map "J" mu4e~headers-jump-to-maildir) + (,evil-collection-mu4e-state mu4e-view-mode-map "[" mu4e-view-headers-prev-unread) + (,evil-collection-mu4e-state mu4e-view-mode-map "]" mu4e-view-headers-next-unread) + (,evil-collection-mu4e-state mu4e-view-mode-map "gk" mu4e-view-headers-prev-unread) + (,evil-collection-mu4e-state mu4e-view-mode-map "gj" mu4e-view-headers-next-unread) + (,evil-collection-mu4e-state mu4e-view-mode-map "\C-j" mu4e-view-headers-next) + (,evil-collection-mu4e-state mu4e-view-mode-map "\C-k" mu4e-view-headers-prev) + (,evil-collection-mu4e-state mu4e-view-mode-map "x" mu4e-view-marked-execute) + (,evil-collection-mu4e-state mu4e-view-mode-map "&" mu4e-view-mark-custom) + (,evil-collection-mu4e-state mu4e-view-mode-map "*" mu4e-view-mark-for-something) ; TODO: Don't override "*". + (,evil-collection-mu4e-state mu4e-view-mode-map "m" mu4e-view-mark-for-move) + (,evil-collection-mu4e-state mu4e-view-mode-map "r" mu4e-view-mark-for-refile) + (,evil-collection-mu4e-state mu4e-view-mode-map "D" mu4e-view-mark-for-delete) + (,evil-collection-mu4e-state mu4e-view-mode-map "d" mu4e-view-mark-for-trash) + (,evil-collection-mu4e-state mu4e-view-mode-map "=" mu4e-view-mark-for-untrash) + (,evil-collection-mu4e-state mu4e-view-mode-map "u" mu4e-view-unmark) + (,evil-collection-mu4e-state mu4e-view-mode-map "U" mu4e-view-unmark-all) + (,evil-collection-mu4e-state mu4e-view-mode-map "?" mu4e-view-mark-for-unread) + (,evil-collection-mu4e-state mu4e-view-mode-map "!" mu4e-view-mark-for-read) + (,evil-collection-mu4e-state mu4e-view-mode-map "%" mu4e-view-mark-pattern) + (,evil-collection-mu4e-state mu4e-view-mode-map "+" mu4e-view-mark-for-flag) + (,evil-collection-mu4e-state mu4e-view-mode-map "-" mu4e-view-mark-for-unflag) + (,evil-collection-mu4e-state mu4e-view-mode-map "\C-u" evil-scroll-up) + (,evil-collection-mu4e-state mu4e-view-mode-map "zr" mu4e-headers-toggle-include-related) + (,evil-collection-mu4e-state mu4e-view-mode-map "zt" mu4e-headers-toggle-threading) + (,evil-collection-mu4e-state mu4e-view-mode-map "za" mu4e-view-toggle-hide-cited) + (,evil-collection-mu4e-state mu4e-view-mode-map "gl" mu4e-show-log) + (,evil-collection-mu4e-state mu4e-view-mode-map "s" mu4e-view-search-edit) + (,evil-collection-mu4e-state mu4e-view-mode-map "|" mu4e-view-pipe) + (,evil-collection-mu4e-state mu4e-view-mode-map "." mu4e-view-raw-message) + (,evil-collection-mu4e-state mu4e-view-mode-map ,(kbd "C--") mu4e-headers-split-view-shrink) + (,evil-collection-mu4e-state mu4e-view-mode-map ,(kbd "C-+") mu4e-headers-split-view-grow) + (,evil-collection-mu4e-state mu4e-view-mode-map "T" (lambda () + (interactive) + (mu4e-headers-mark-thread nil '(read))))) + ;; TODO: Add mu4e-headers-search-bookmark? + "All evil-mu4e bindings.") + +(defun evil-collection-mu4e-set-bindings () + "Set the bindings." + ;; WARNING: With lexical binding, lambdas from `mapc' and `dolist' become + ;; closures in which we must use `evil-define-key*' instead of + ;; `evil-define-key'. + (dolist (binding evil-mu4e-mode-map-bindings) + (evil-collection-define-key + (nth 0 binding) (nth 1 binding) (nth 2 binding) (nth 3 binding))) + (evil-define-key 'operator mu4e-view-mode-map + "u" '(menu-item + "" + nil + :filter (lambda (&optional _) + (when (memq evil-this-operator + '(evil-yank evil-cp-yank evil-sp-yank lispyville-yank)) + (setq evil-inhibit-operator t) + #'mu4e-view-save-url))))) + + +;;; Update mu4e-main-view +;;; To avoid confusion the main-view is updated to show the keys that are in use +;;; for evil-mu4e. + +(defvar evil-collection-mu4e-begin-region-basic "\n Basics" + "The place where to start overriding Basic section.") + +(defvar evil-collection-mu4e-end-region-basic "a new message\n" + "The place where to end overriding Basic section.") + +(defvar evil-collection-mu4e-new-region-basic + (concat (mu4e~main-action-str "\t* [J]ump to some maildir\n" 'mu4e-jump-to-maildir) + (mu4e~main-action-str "\t* enter a [s]earch query\n" 'mu4e-search) + (mu4e~main-action-str "\t* [C]ompose a new message\n" 'mu4e-compose-new)) + "Define the evil-mu4e Basic region.") + +(defvar evil-collection-mu4e-begin-region-misc "\n Misc" + "The place where to start overriding Misc section.") + +(defvar evil-collection-mu4e-end-region-misc "q]uit" + "The place where to end overriding Misc section.") + +(defvar evil-collection-mu4e-new-region-misc + (concat + (mu4e~main-action-str "\t* [;]Switch focus\n" 'mu4e-context-switch) + (mu4e~main-action-str "\t* [u]pdate email & database (Alternatively: gr)\n" + 'mu4e-update-mail-and-index) + + ;; show the queue functions if `smtpmail-queue-dir' is defined + (if (file-directory-p smtpmail-queue-dir) + (mu4e~main-view-queue) + "") + "\n" + + (mu4e~main-action-str "\t* [N]ews\n" 'mu4e-news) + (mu4e~main-action-str "\t* [A]bout mu4e\n" 'mu4e-about) + (mu4e~main-action-str "\t* [H]elp\n" 'mu4e-display-manual) + (mu4e~main-action-str "\t* [q]uit\n" 'mu4e-quit)) + "Define the evil-mu4e Misc region.") + +(defun evil-collection-mu4e-replace-region (new-region start end) + "Replace region between START and END with NEW-REGION. +START end END end are regular expressions." + ;; move to start of region + (goto-char (point-min)) + (re-search-forward start) + + ;; insert new headings + (insert "\n\n") + (insert new-region) + ;; Delete text until end of region. + (let ((start-point (point)) + (end-point (re-search-forward end))) + (delete-region start-point end-point))) + + +(defun evil-collection-mu4e-update-main-view () + "Update 'Basic' and 'Misc' regions to reflect the new +keybindings." + (evil-collection-mu4e-replace-region evil-collection-mu4e-new-region-basic + evil-collection-mu4e-begin-region-basic + evil-collection-mu4e-end-region-basic) + (evil-collection-mu4e-replace-region evil-collection-mu4e-new-region-misc + evil-collection-mu4e-begin-region-misc + evil-collection-mu4e-end-region-misc)) + + + +;;; Initialize evil-collection-mu4e + +(defun evil-collection-mu4e-setup () + "Initialize evil-mu4e if necessary. +If mu4e-main-mode is in evil-state-motion-modes, initialization +is already done earlier." + (evil-collection-mu4e-set-state) + (evil-collection-mu4e-set-bindings) + (add-hook 'mu4e-main-mode-hook 'evil-collection-mu4e-update-main-view)) + +(provide 'evil-collection-mu4e) +;;; evil-collection-mu4e.el ends here diff --git a/evil-collection.el b/evil-collection.el index 9332052..cf7b302 100644 --- a/evil-collection.el +++ b/evil-collection.el @@ -121,6 +121,7 @@ or evil-collection.") man magit ,@(when evil-collection-setup-minibuffer '(minibuffer)) + mu4e neotree notmuch nov