Adolfo’s Emacs Lisp Initialization File

#+PROPERTY :tangle “~/.emacs.el”

Menu

This is my .emacs.org initialization file. There are many like it, but this one is mine. It is tangled in an Org mode file.

Introduction

I moved my Emacs initialization file to Org Mode some time ago and I am very fond of it:

  1. Documentation and code live together, which is super nice when there are some customizations which are tricky
  2. It is easy to exclude/include some section in the tangled file, in fact keeping code I no longer use, just in case.

For instance, when I want to exclude a section from the initialization file, I simply need to add a COMMENT keyword at the beginning of the entry.

A finer-grained solution controls different aspects of the exporting procedure. For instance, I can set the following properties in the enclosing heading:

* Exwm                                                             :noexport:
 :PROPERTIES:
 :visibility: folded
 :header-args: :tangle no
 :END:

(The :noexport: property is not strictly necessary.)

Matter of fact, if you look at the source code of this file, you will notice there are various sections which do not appear in the rendered HTML file.

While the advantages are clear, it took me some time to understand how the loading process would work. There are basically three ways:

  1. Loading this file from init.el.
  2. Manually tangling the code. This is what I do.
  3. Automatically tangling the code on buffer save.

In all cases, make sure the following properties are set for the file or no code will be exported:

#+PROPERTY: header-args :tangle yes
#+PROPERTY :tangle "~/.emacs.el"

Loading this file from init.el

Put the following code in file://~/.emacs.d/init.el:

(require 'org)
(org-babel-load-file
  (expand-file-name "~/.emacs.org"))

The org-babel-load-file function tangles the file and loads it.

Tangling is performed only if the .org mode file is newer than the existing tangled file. This has a catch: in my setup, the .org mode lives in a Git controlled directory and the file is symbolically linked from home. However when dates are checked, the symbolic link (which never changes) is always older than the tangled file and the wrong version is loaded.

Manually tangling this file

This is similar to the solution above, but the source code extraction is invoked from Emacs with M-x org-babel-tangle when you want.

Automatically tangling this file

Code is tangled every time this buffer is saved.

Add org-babel-tangle to after-save-hook, e.g., by appending the following lines at the end of this file:


* Local Variables

# Local Variables:
# org-confirm-babel-evaluate: nil
# eval: (add-hook 'after-save-hook (lambda () (org-babel-tangle)) nil t)
# End:

Improvements

TODO org-contacts/org-vcard

I want to manage my contacts with org-mode and sync them with a vcard server

TODO SMIME messages take ages to decrypt

TODO Write a function to switch between gnus and internal mu4e message viewer

TODO Check mu4e is finally the default mail composer

DONE I am faster than emacs at resizing first frame, but I want the other frames to be sized

TODO Sidebar

TODO Extract blog posts

TODO BCC (rather than CC) myself in mu4e

Packages

Even though Melpa is a large repository, some packages can be found only in the GNU repository. So I use both.

Emacs 27 made the call to (package-initialize) useless, since it is called in early init. However, if you want to invoke Emacs in batch mode—for instance to generate Org Mode Agenda views—we need to be able to load .emacs.el and this, in turn, requires all the package machinery up and running. Hence we also initialize package here.

(require 'package)
(setq package-check-signature nil)

(setq repositories
      '(
        ("gnu" . "https://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ;; DEPRECATED: ("org" . "http://orgmode.org/elpa/")
      ))
(setq package-archives repositories)

(package-initialize)

Load and Exec Path

This is where the system and I keep Emacs lisp files.

(setq extra-load-paths
      `("/usr/share/emacs/site-lisp"
        "/usr/share/emacs/site-lisp/mu4e/"
        ,(expand-file-name "~/elisp")
        ,(expand-file-name "~/Sources/elisp")
        ))
(setq load-path (append load-path extra-load-paths))

Customize

Some variables are simpler to manage with customize (e.g. fonts, sidebars).

All customizations must be stored in a special file or they will be lost every time we tangle .emacs.el.

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

If we load the files set with customize (code below not commented), we better do it early, so that what we write in this files has precedence over what is written in customize.

(load custom-file t)

Emacs Server

(This section ended up becoming a note on my website: Launch Emacs with Systemd).

Emacs can be started and run as a server using systemctl (see Emacs Server and Managing Emacs Server as Systemd Service) to reduce startup time and help enforcing the execution of a single instance of the editor, so that, for instance, the same file does not happen to be edited in two different Emacs instances at the same time.

To setup Emacs as a systemd service:

cp /usr/share/emacs/27.2/etc/emacs.service ~/.config/systemd/user
systemctl --user enable emacs

Note. If you run into issues with the authentication agent you can try uncommenting the line:

Environment=SSH_AUTH_SOCK=%t/keyring/ssh=

Alternatively, a server on a X11 instance can be obtained using an emacs-lisp function:

(require 'server)
(unless (server-running-p)
  (server-start))

Even when the server is started, you might end up launching other instances of Emacs, rather than reusing the existing instance. In such cases, it might be useful to add a desktop entry for the Emacs client. This can be done in Linux by adding a file to ~/.local/share/applications (see the section Deskttop Client of the Emacs Wiki):

cat ~/.local/share/applications/emacs.desktop
[Desktop Entry]
Name=EmacsClient
GenericName=Text Editor
Comment=Edit text
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
Exec=emacsclient -c -a "emacs" %F
Icon=emacs
Type=Application
Terminal=false
Categories=Development;TextEditor;
StartupWMClass=Emacs
Keywords=Text;Editor;

If the file is named emacs.desktop it will shadow the system entry. You can use another name for the file, if you prefer to keep an entry also for Emacs

Finally, I also define a few aliases in my .bashrc to prefer emacsclient over emacs:

export EDITOR="emacsclient -t --alternate-editor /usr/bin/emacs" 
export VISUAL="emacsclient -c --alternate-editor /usr/bin/emacs"

export TEXEDIT="emacsclient -t +%d %s -a /usr/bin/emacs"

# Override Emacs... although not always a good idea
alias emacs="emacsclient -c -a /usr/bin/emacs"

alias emacs_gui="emacsclient -c -a /usr/bin/emacs"
alias emacs_cli="emacsclient -t -a /usr/bin/emacs"

Major Mode according to extension

Somehow, these defaults are not automatically set.

(add-to-list 'auto-mode-alist '("\\.\\(yml\\|yaml\\)$" . yaml-mode))
(add-to-list 'auto-mode-alist '("\\.\\(rb\\|prawn\\)$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.journal" . ledger-mode))
(add-to-list 'auto-mode-alist '("\\.plant" . plantuml-mode))

Visual Layout

No startup screen:

(setq inhibit-startup-screen t)

No sidebar:

(set-scroll-bar-mode nil)

No toolbar:

(tool-bar-mode 0)

Editing Defaults

Typing text won’t delete the text of the active region. This is different from modern defaults, but it protects from accidental text deletion, which, I am sad to report, sometimes happens to me :-)

(cua-mode -1)
(setq cua-delete-selection nil)
(setq delete-active-region nil)
(delete-selection-mode -1)

Indent with spaces:

(setq-default indent-tabs-mode nil)
(setq tab-width 2)

Two spaces for indenting in web mode:

(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)

Delete by moving to the trash:

(setq delete-by-moving-to-trash t)

List directory briefly… Blah! find-file is usually what I meant even when I type \C-x\C-d.

(global-set-key "\C-x\C-d" 'find-file)

Tell Emacs I don’t need help

Emacs decided some commands are dangerous and warns you the first time you use them. However, I know what I am doing and for some commands I don’t need to be warned (there is always undo coming to the rescue).

(put 'downcase-region 'disabled nil)
(put 'erase-buffer 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'upcase-region 'disabled nil)

Colors

Custom Themes

Custom themes are enabled by default in Emacs, so we are left with the task of selecting and installing the themes we like:

  1. Preview themes here: https://emacsthemes.com/
  2. Install them from Melpa.

An alternative solution is color themes, whose settings are fully reversible.

Color Themes

Color theme is an alternative to custom themes. The advantage of color theme is that they are fully reversible, something theme does not do.

More documentation here: https://github.com/emacs-jp/replace-colorthemes.

(require 'color-theme-modern nil t)
(require 'color-theme-sanityinc-tomorrow nil t)
(require 'color-theme-sanityinc-solarized nil t)
(require 'tangotango-theme nil t)

Color themes have to be explicitly loaded. The list of available themes is here: https://github.com/emacs-jp/replace-colorthemes/blob/master/screenshots.md

The all-themes variables holds all themes, but too much choice is … just too much. I therefore enable a subset

(setq default-themes
      '(aalto-dark aalto-light aliceblue andreas arjen beige-diff beige-eshell bharadwaj-slate bharadwaj
                   billw black-on-gray blippblopp blue-erc blue-eshell blue-gnus blue-mood blue-sea
                   calm-forest charcoal-black clarity classic cobalt comidia dark-blue dark-blue2 dark-erc
                   dark-font-lock dark-gnus dark-green dark-info dark-laptop deep-blue desert digital-ofs1
                   emacs-21 emacs-nw euphoria feng-shui fischmeister gnome gnome2 goldenrod gray1 gray30
                   greiner gtk-ide high-contrast hober infodoc jb-simple jedit-grey jonadabian-slate
                   jonadabian jsc-dark jsc-light jsc-light2 katester kingsajz late-night lawrence ld-dark
                   lethe marine marquardt matrix midnight mistyday montz oswald parus pierson pok-wob pok-wog
                   ramangalahy raspopovic renegade resolve retro-green retro-orange robin-hood rotor ryerson
                   salmon-diff salmon-font-lock scintilla shaman simple-1 sitaramv-nt sitaramv-solaris snow
                   snowish standard-ediff standard subtle-blue subtle-hacker taming-mr-arneson taylor
                   tty-dark vim-colors whateveryouwant wheat word-perfect xemacs xp julie subdued railscast
                   ))

(setq default-light-nice
      '(andreas blue-gnus high-contrast gtk-ide emacs-nw emacs-21))

(setq default-dark-nice
      '(cobalt desert hober lethe renegade railscast))

(setq extras
      '(tangotango))

(setq sanityinc-tomorrow-themes
      '(sanityinc-tomorrow-day
        sanityinc-tomorrow-night
        sanityinc-tomorrow-blue
        sanityinc-tomorrow-bright
        sanityinc-tomorrow-eighties))

(setq sanityinc-solarized-themes
      '(sanityinc-solarized-light
        sanityinc-solarized-dark))

(setq enabled-themes (append sanityinc-tomorrow-themes
                             sanityinc-solarized-themes
                             extras
                             default-light-nice
                             default-dark-nice))

(mapcar (lambda (x) (load-theme x t t)) enabled-themes)

We then provide some functions to simplify previewing themes:

(defun color-theme-preview-page ()
  "Go to the GitHub repository and preview themes"
  (interactive)
  (eww "https://github.com/emacs-jp/replace-colorthemes/blob/master/screenshots.md"))

(defun color-theme-switch ()
  "Disable all enabled themes and enable a new one"
  (interactive)
  (let ( (theme (completing-read "Enable theme: " enabled-themes)) )
    (progn
      (dolist (th custom-enabled-themes) (disable-theme th))
      (enable-theme (intern theme)))))

;; (defun color-theme-preview ()
;;   "Open the color-theme-switcher page and select a theme"
;;   (interactive)
;;   (let ( (buffer (find-file (expand-file-name "~/Sources/elisp/color-theme-switcher.org"))) )
;;     (progn
;;       (set-buffer buffer)
;;       (read-only-mode))))

Change theme by the time of day

Change theme according time of day (https://github.com/hadronzoo/theme-changer)

(setq calendar-location-name "Rome, IT") 
(setq calendar-latitude 46.04)
(setq calendar-longitude 11.07)
(setq theme-changer-mode "color-theme")

(require 'theme-changer nil t)
(if (featurep 'theme-changer)
    (change-theme 'andreas 'railscast))

Customization of visual frame

Split Horizontally

(setq split-width-threshold nil)
(setq split-height-threshold 40)

Customize Mode Line

(display-time-mode)

Working with many buffers and different tasks

As you start using Emacs for various different tasks, the number of buffers and windows layout you use increases. For instance, you might be working on a Rails project open, while reading email with MU4E and publishing a website written in Jekyll.

In my workflow there are basically three things I want to control:

  • Quickly finding a buffer among many, such as, for instance, the email I was composing before I had to switch and check the Org Mode agenda
  • Controlling the layout of a frame, such as, for instance splitting the frame in two vertical windows when I am editing Ruby code.

This is the modes I am currently using:

  • Switching buffers: Ibuffer Mode, which organizes buffers in groups and has functions to filter and operate on buffers. It is built into Emacs.
  • Controlling frame layout: There are various way to programmatically define how to split a frame into windows. For instance, you can decide that shells are always shown on the right window of the current frame. Notice that this works better with larger monitors or with additional modes which dynamically increase window size when needed. See Buffer Positions in Windows for more details.

In the past I also used:

  • Perspective for Emacs The Perspective package provides multiple named workspaces (or “perspectives”) in Emacs, similar to multiple desktops in window managers like Awesome and XMonad, and Spaces on the Mac. different layouts and filtering the buffer visibile in each perspective. It integrates with projectile. The homepage has also a list of packages for managing window configurations.

Perspective is an excellent solution, but it requires a bit more active management (e.g. creating perspectives, moving buffers to the right perspective, if open by mistake in the ’wrong’ perspective) than the solution I am currently trying out.

There are some nice alternatives, which, however, I did not manage to get accustomed to:

  • Persp Mode is base on perspectives, but perspectives are shared among frames + ability to save/restore its state from/to a file.
  • Eyebrowse (M-x eyebrowse-mode) defines workspaces which can be switched to with C-c C-w N, where N is a number.
  • Winner mode allows to “undo” and “redo” changes to window configurations.
  • Tab Bar Mode shows tabs and can organize buffers by groups

Ibuffer

Ibuffer is built into Emacs but has to be explicitly bound to C-xC-b:

(global-set-key (kbd "C-x C-b") 'ibuffer)

From the Emacs wiki: Ibuffer has an excellent implementation of Gnus-style grouping. Try this:

(setq ibuffer-saved-filter-groups
      (quote (("default"
               ("dired" (mode . dired-mode))
               ("perl" (mode . cperl-mode))
               ("erc" (mode . erc-mode))
               ("planner" (or
                           (name . "^\\*Calendar\\*$")
                           (name . "^diary$")
                           (mode . muse-mode)))
               ("emacs" (or
                         (name . "^\\*scratch\\*$")
                         (name . "^\\*Messages\\*$")))
               ("svg" (name . "\\.svg")) ; group by file extension
               ("gnus" (or
                        (mode . message-mode)
                        (mode . bbdb-mode)
                        (mode . mail-mode)
                        (mode . gnus-group-mode)
                        (mode . gnus-summary-mode)
                        (mode . gnus-article-mode)
                        (name . "^\\.bbdb$")
                        (name . "^\\.newsrc-dribble")))))))

These are some extensions to improve Ibuffer.

Icons:

(require 'all-the-icons-ibuffer nil t)
(all-the-icons-ibuffer-mode 1)

Sidebar for ibuffer:

(require 'ibuffer-sidebar nil t)

Group buffers by various strategies (version control or project) (These seem to be kind of redundant with existing features, however):

(require 'ibuffer-vc nil t)

Buffer Positions in Windows

There are different ways of controlling the way in which Emacs splits a frame into windows.

Some packages simplify management by allowing to store window configurations. The user chooses the best configuration, which can be saved and restored using key combinations.

  • Registers are probably one of the oldest mechanism Emacs provides to save and restore window configurations. The main functions are:

    • C-x r w REGISTER, bound to (window-configuration-to register REGISTER) to store the current configuration
    • C-x r j REGISTER, bound to (jump-to-register REGISTER) to restore a window configuration

    Registers names are single characters (either letters or numbers) and, consequently, there is no possibility of assigning a memorable names. Registers, however, can be used to store different kind of information. See the documentation in the Emacs Manual online or, alternatively, by browsing your local Info file.

  • M-x winner-mode allows to switch configurations back and forth with C-c <left> and C-c <right>. It can be useful if you mess up your default configuration by mistake, but it does not allow you to directly jump to a specific configuration.

Another possibility is programmatically defining where the windows should appear. The elisp manual has a section on side windows, and example code which ensures special buffers (Dired, Buffer List, shell) are always shown in the same window/position (Frame Layouts with Side Windows).

This is the code, customized according to my tastes:

(setq additional-params
  '(window-parameters . (
    ;; (no-other-window . t)            ; the window is not accessible with C-x o
    ;; (no-delete-other-windows . t)    ; the window is not interested by C-x 1
    )))

(setq fit-window-to-buffer-horizontally t)
(setq window-resize-pixelwise t)

(setq my-display-buffer-alist
`(("\\*Buffer List\\*" display-buffer-in-side-window
   (side . bottom) (slot . 0)
   (window-height . fit-window-to-buffer)
   (preserve-size . (nil . nil))
   ,additional-params)
  ("\\*Tags List\\*" display-buffer-in-side-window
   (side . right) (slot . 0) 
   (window-width . fit-window-to-buffer)
   (preserve-size . (nil . nil)) 
   ,additional-params)
  ("\\*\\(?:[Hh]elp\\|grep\\|info\\)\\*" display-buffer-in-side-window
   (side . right) (slot . 0) 
   (window-width . fit-window-to-buffer)
   (preserve-size . (nil . nil)) 
   ,additional-params)
  ("\\*Org Agenda\\*" display-buffer-in-side-window
   (side . right) (slot . 0) 
   (window-width . fit-window-to-buffer)
   (preserve-size . (nil . nil))
   ,additional-params)
  ("\\*\\(?:Completions\\)\\*"
   display-buffer-in-side-window
   (side . bottom) (slot . -1)
   (preserve-size . (nil . nil))
   ,additional-params)
  ("\\(\\*magit:\\*\\|VC dir\\)" display-buffer-in-side-window
   (side . right) (slot . 0) 
   (window-width . fit-window-to-buffer)
   (preserve-size . (nil . nil))
   ,additional-params)
  ("\\*\\(?:shell\\|rails\\|compilation\\)\\*" display-buffer-in-side-window
   (side . right) (slot . 0)
   (preserve-size . (nil . nil))
   ,additional-params)
   ))

;; we use two functions to set and reset display-buffer-alist
(defun avm/set-fixed-window-positions ()
  "Set where each window appears in the frame, according to my-display-buffer-alist"
  (interactive)
  (setq display-buffer-alist my-display-buffer-alist))

(defun avm/reset-fixed-window-position ()
  "Reset where each window appears in the frame"
  (interactive)
  (setq display-buffer-alist nil))

Here you can choose whether to leave things as they are or set the var and bring order to the way in which windows are shown:

;; (set-fixed-window-positions)

You might also want to have a look at the documentation of the following functions: display-buffer, fit-window-to-buffer, which control the appearance and behavior of windows.

Perspective Mode

Commands are prefixed by C-x x

(require 'perspective nil t)
(if (featurep 'perspective)
    (progn
      ;; (global-set-key "\C-x\C-b" 'persp-list-buffers)
      (persp-mode)))

Remembering files last used

Bookmarks

Bookmarks is what I would like to use, but I seldom do.

  • C-x r m adds a bookmark
  • C-x r l lists all bookmarks

If you wonder about the r in the shortcut, it comes from that fact that the bookmarks package extends Emacs registers.

Other possibilities include:

  • Using an Org Mode File
  • Using Dashboard
  • Using Recent Files

Spelling

(require 'ispell nil t)

(setq ispell-program-name "hunspell") ; Use hunspell to correct mistakes

(setenv "DICTIONARY" "en_US")
(setq ispell-dictionary   "english")  ; Default dictionary to use

;; skipped regions (for org-mode)
(add-to-list 'ispell-skip-region-alist '(":\\(PROPERTIES\\|LOGBOOK\\|OPTIONS\\):" . ":END:"))
(add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_SRC" . "#\\+END_SRC"))
(add-to-list 'ispell-skip-region-alist '("^#\\+\\(TITLE\\|AUTHOR\\|DATE\\|OPTIONS\\|KEYWORDS\\|STARTUP\\|PRIORITIES\\):"))
(add-to-list 'ispell-skip-region-alist '("^#\\+\\(LATEX\\|BEAMER\\)_\\(COMPILER\\|HEADER\\|CLASS\\):.*"))
(add-to-list 'ispell-skip-region-alist '("^#\\+LATEX:.*"))
(add-to-list 'ispell-skip-region-alist '("^#\\+TBLFM:.*"))

(defun set-italian ()
  (interactive)
  (ispell-change-dictionary "italiano")
  (set-input-method "italian-postfix")
  (flyspell-buffer)
  (flyspell-mode 1))

(defun set-english ()
  (interactive)
  (ispell-change-dictionary "english")
  (flyspell-buffer)
  (flyspell-mode 1))

Automatically start the spell checker on certain buffers.

(dolist (hook '(text-mode-hook mu4e-compose-mode-hook))
(add-hook hook (lambda () (flyspell-mode 1))))

(dolist (hook '(ruby-mode-hook))
(add-hook hook (lambda () (flyspell-prog-mode))))

Snippets

This is really super-useful!

(require 'yasnippet nil t)
(if (featurep 'yasnippet) (yas-global-mode 1))

Shell Integration

These are no longer needed, but difficult to remember, so I keep them here:

;; match my prompt (and the default ones)... so that tramps work
;; (setq shell-prompt-pattern "^\\([a-zA-Z/-@ ]+[#$%>] *\\)")

;; forget escape chars for colored output
;; (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)

Open the shell in the current window (rather than creating a new frame or a different window)

(setq same-window-regexps '(".*shell.*"))

Shell has impacting performance issues as the buffer grows larger.

The following truncates the output of the shell and keeps the buffer reasonably small. Don’t forget to use C-x M-o to clean the buffer.

(setq comint-buffer-maximum-size 30000)
(add-hook 'comint-output-filter-functions
          'comint-truncate-buffer)

The real issue, however, is related to the lines’ length, rather than the number of lines. Thus, we also set bidi-inhibit-bpa and bidi-paragraph-direction, as suggested here: Prevent Long Lines Making Emacs Slow (and which, indeed, improves performances):

(setq bidi-inhibit-bpa t)
(setq-default bidi-paragraph-direction 'left-to-right)

ANSI term uses C-x as Escape character, which is simpler to remember than the default escape char of M-x term. So we shadow the M-x term command with ansi-term:

(defun term (program &optional new-buffer-name) 
  (interactive "sProgram: ")
  (ansi-term program new-buffer-name))

Dired

Writable Dired Buffer

Dired buffers can be made writable, greatly simplifying operations on files, such as renaming, moving, and changing permissions.

See: Rename File and Buffer in Emacs and Wdired, which explain how the mode works and where you learn that C-x C-q enters the mode.

All The Icons

Icons make a huge difference.

External packages are needed, but the package provide its own command:

M-x all-the-icons-install-fonts
(require 'all-the-icons nil t)
(require 'all-the-icons-dired nil t)

(if (featurep 'all-the-icons-dired)
    (add-hook 'dired-mode-hook 'all-the-icons-dired-mode))

Ruby and Ruby on Rails

Chruby mitigates risks related to incompatible gems, but Emacs needs to know about it:

Chruby

(require 'chruby nil t)
(if (featurep 'chruby) (chruby "ruby-2.6.6"))

Yet another RI browser:

(require 'yari nil t)

Inferior Ruby Shell

Run an inferior Ruby shell with M-x inf-ruby and M-x inf-ruby-console-auto:

(require 'inf-ruby nil t)

(autoload 'inf-ruby-minor-mode "inf-ruby" "Run an inferior Ruby process" t)
(add-hook 'ruby-mode-hook 'inf-ruby-minor-mode)
(defalias 'inferior-ruby-shell 'inf-ruby)

(if (featurep 'inf-ruby) 
    (progn 
      (define-key ruby-mode-map (kbd "C-c C-l") 'ruby-send-line)
      (define-key ruby-mode-map (kbd "C-c C-r") 'ruby-send-region)
      (define-key ruby-mode-map (kbd "C-c C-b") 'ruby-send-buffer)))

HTML and Web mode

(require 'web-mode nil t)
(if (featurep 'web-mode)
  (progn
    (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))

    ;; Using web-mode for editing plain HTML files can be done this way
    (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
    ))

LaTeX Mode

(setq font-latex-fontify-sectioning 'color)

;; should be replaced by a snippet
;; (defun latex-insert-figure (filename label caption)
;;   "Insert a LaTeX figure template at point"
;;   (interactive "FFile: \nsLabel: \nsCaption: ")
;;   (let ( (relative-filename (file-relative-name filename)) )
;;     (insert "\\begin{figure}\n"
;;             "\\begin{center}\n"
;;             "  \\includegraphics[width=0.8\\textwidth]{" relative-filename "}\n"
;;             "  \\caption{\\label{" label "} " caption "}\n"
;;             "\\end{center}\n"
;;             "\\end{figure}\n")))

Ledger Mode

Quoting https://hledger.org/editors.html:

https://github.com/ledger/ledger-mode (manual), for Emacs, is the most used and maintained helper mode for h/ledger files. Use M-x customize to browse ledger-mode’s settings and change them as seems wise.

(require 'ledger-mode nil t)

Recommended settings

(setq ledger-binary-path "/usr/bin/hledger")
(setq ledger-mode-should-check-version nil)

(defvar ledger-report-reg
  (list "reg" (concat ledger-binary-path " -f %(ledger-file) reg")))

(defvar ledger-report-payee
  (list "payee" (concat ledger-binary-path " -f %(ledger-file) reg @%(payee)")))

(defvar ledger-report-account
  (list "account" (concat ledger-binary-path " -f %(ledger-file) reg %(account)")))

(setq ledger-reports
      (list 'ledger-report-balance
            'ledger-report-reg
            'ledger-report-payee
            'ledger-report-account))

Org Mode

Global Defaults

Keybindings:

(define-key global-map "\C-ca" 'org-agenda)
(define-key global-map "\C-cc" 'org-capture)
(define-key global-map "\C-cl" 'org-store-link)
(define-key global-map "\C-cm" 'org-mu4e-store-and-capture)

(global-unset-key (kbd "M-;"))   ; org-comment-dwim 
(global-unset-key (kbd "C-c ;")) ; org-comment-dwim 

Some defaults for standardizing the structure of Org Mode files:

(setq outline-blank-line t)               ; preserve the blank lines in outlines
(setq org-archive-location "::* Archive") ; Archive in the same file, to simplify computation of clock tables
(setq org-log-into-drawer "LOGBOOK")      ; Log in LOGBOOK

Clocking and drawers:

(setq org-log-done nil) ;; 'time
(setq org-duration-format 'h:mm)
(setq org-clock-rounding-minutes 5)
(setq org-clock-into-drawer t)
(setq org-columns-default-format "%40ITEM(Task) %10TODO %17SCHEDULED(Scheduled) %18DEADLINE(Deadline) %10EFFORT(Effort){:} %CLOCKSUM")
;; require a note when clocking out
;; (setq org-log-note-clock-out t)

Agenda and Todos

The agenda files used to be those in my main Org directory:

(setq org-directory (expand-file-name "~/Nextcloud/OrgMode"))
(setq org-agenda-files `(,org-directory))
;; (setq org-agenda-files (expand-file-name "~/.agenda-files.txt")) ; alternative, if files are sparse

Use an Org Mode file for diary entries. This also interacts with M-x calendar and binds i d to add entries in the specified file

(setq org-agenda-diary-file (expand-file-name "~/Nextcloud/OrgMode/work-cal.org"))
;; (setq org-agenda-diary-file 'diary-file)

Getting data in the agenda

The simplest way is using M-x org-capture, for which a template has been defined, but also c and i in the calendar view.

By default agenda entries are entered in a tree view (which I dont’ quite like); the following two variables change the defaults, by inserting in a flat file and by extracting time information from the entry:

(setq org-agenda-insert-diary-strategy 'top-level)
(setq org-agenda-insert-diary-extract-time t)

Structure Templates

Enable block expansion with < key:

(require 'org-tempo nil t)

Workflow and capture templates

(setq org-todo-keywords
      '(
        (type "IDEA(i!)" "TODO(t!)" "DOING(!)" "WAITING(w!)"
              "|"
              "DUPLICATE(r)" "DONE(d)" "DROPPED(f)" "REJECTED(e@)" "INVOICED")
        ;;         (type "NEW(n)" "FIRST_CONTACT(f)" "ACTIVE(a)"
        ;;               "|"
        ;;               "SUCCESS" "FAILURE")
        ;;         (type "FEATURE(!)" "REFACTORING(!)" "BUG(b!)"
        ;;               "|"
        ;;               "FEEDBACK(f@)" "CLOSED(c!)" "REJECTED(e@)")
        ;;         (type "TENTATIVE"
        ;;               "|"
        ;;               "MEETING(m)" "CALL(c)" "ERRAND(e)" "TRAVEL(t)")
        ))

(setq ledger-capture-file (expand-file-name "~/Nextcloud/Balance/2018-2021/ledger-2021.journal"))
(setq org-capture-templates
      `(
        ("a" "Work Agenda Entry" entry (file+headline ,(concat org-directory "/work-cal.org") "Agenda")
         "** %?%^G\n %^{LOCATION}p %^{ATTENDEES}p\n   <%(org-read-date t)>")
        ("f" "Family Agenda Entry" entry (file+headline ,(concat org-directory "/family-cal.org") "Agenda")
         "** %?%^G\n %^{LOCATION}p %^{ATTENDEES}p\n   <%(org-read-date t)>")
        ("p" "Personal Agenda Entry" entry (file+headline ,(concat org-directory "/personal-cal.org") "Agenda")
         "** %?%^G\n %^{LOCATION}p %^{ATTENDEES}p\n   <%(org-read-date t)>")
        ("t" "Work Todo" entry (file+headline ,(concat org-directory "/work.org") "Inbox")
         "* TODO %?\n  %i\n  %a")
        ("F" "Family Todo" entry (file+headline ,(concat org-directory "/family.org") "Inbox")
         "* TODO %?\n  %i\n  %a")
        ("P" "Personal Todo" entry (file+headline ,(concat org-directory "/personal.org") "Inbox")
         "* TODO %?\n  %i\n  %a")
        ("W" "Waiting Todo" entry (file+headline ,(concat org-directory "/work-cal.org") "Inbox")
         "* WAITING %?\n  %i\n  %a")

        ;; this is taken from: https://karl-voit.at/2014/08/10/bookmarks-with-orgmode/
        ;; ("b" "Bookmark" entry (file+headline "~/share/all/org-mode/notes.org" "Bookmarks")
        ;;  "* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n" :empty-lines 1)

        ;; ("g" "Groceries" entry (file+headline ,(concat org-directory "groceries.org") "Groceries")
        ;;  "** %?\n")

        ("l" "Ledger Entry" plain
         (file+regexp ,ledger-capture-file "^;;; ADD HERE")
         "\n\n%(org-read-date) %^{Payee}\n    %^{Account}         %^{Amount}\n    %^{Account|cash|amex|bacash|qicash}\n\n"
         :prepend t :empty-lines 1)

        ))

Displaying the agenda

Unclutter the agenda view, while keeping important information.

(setq org-agenda-include-deadlines t)
;; (setq org-deadline-warning-days 5)

(setq org-agenda-skip-deadline-prewarning-if-schedule t)
(setq org-agenda-skip-scheduled-if-deadline-is-shown t)
(setq org-agenda-include-inactive-timestamps nil)

(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-skip-deadline-if-done t)
(setq org-agenda-skip-timestamp-if-done t)

Simplify how deadlines and scheduled timestamps are shown

(setq org-agenda-scheduled-leaders '("s.today:" "s.%dx"))
(setq org-agenda-deadline-leaders '("d.today" "d.%dd:" "d.%da"))

The settings for time grid for agenda display. This is a list of four items. The first item is again a list. It contains symbols specifying conditions when the grid should be displayed:

  • daily if the agenda shows a single day
  • weekly if the agenda shows an entire week
  • today show grid on current date, independent of daily/weekly display
  • require-timed show grid only if at least one item has a time specification
  • remove-match skip grid times already present in an entry

The second item is a list of integers, indicating the times that should have a grid line.

The third item is a string which will be placed right after the times that have a grid line.

The fourth item is a string placed after the grid times. This will align with agenda items

;; (setq org-agenda-time-grid
;;       '((today daily weekly require-timed)
;;         (800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000)
;;         "......" "----------------"))

Easter

From Worg’s FAQ (https://orgmode.org/worg/org-faq.html):

(defun da-easter (year)
  "Calculate the date for Easter Sunday in YEAR. Returns the date in the
Gregorian calendar, ie (MM DD YY) format."
  (let* ((century (1+ (/ year 100)))
         (shifted-epact (% (+ 14 (* 11 (% year 19))
                              (- (/ (* 3 century) 4))
                              (/ (+ 5 (* 8 century)) 25)
                              (* 30 century))
                           30))
         (adjusted-epact (if (or (= shifted-epact 0)
                                 (and (= shifted-epact 1)
                                      (< 10 (% year 19))))
                             (1+ shifted-epact)
                           shifted-epact))
         (paschal-moon (- (calendar-absolute-from-gregorian
                           (list 4 19 year))
                          adjusted-epact)))
    (calendar-dayname-on-or-before 0 (+ paschal-moon 7))))


(defun da-easter-gregorian (year)
  (calendar-gregorian-from-absolute (da-easter year)))

(defun calendar-days-from-easter ()
  "When used in a diary sexp, this function will calculate how many days
are between the current date (DATE) and Easter Sunday."
  (- (calendar-absolute-from-gregorian date)
     (da-easter (calendar-extract-year date))))

Exporting the Agenda

With package and org-agenda-custom-commands with filenames in place, we can invoke:

/usr/bin/emacs --batch -l ~/.emacs.el --eval "(org-batch-store-agenda-views)" --kill 

to generate the agenda views.

Notice that:

  1. loading .emacs.el is necessary to get the correct value of org-agenda-custom-commands (even though we could set the variable in the script)
  2. loading .emacs.el requires the following two lines to be in place or the command will fail; more in details: customize.el won’t find some packages. This is most likely an issue with my .emacs.el file, but it is just simpler to go with the simple solution.
(require 'package)
(package-initialize)
(setq org-agenda-custom-commands
      '(

        ("d" "Deadlines" agenda "display deadlines and exclude scheduled"
         ((org-agenda-span 'month)
          (org-agenda-time-grid nil)
          (org-agenda-show-all-dates nil)
          (org-agenda-entry-types '(:deadline)) ;; this entry excludes :scheduled
          (org-deadline-warning-days 0) )
         )

        ("y" "Kanban (todo by state)"
         ((todo "IDEA")
          (todo "TODO")
          (todo "DOING")
          (todo "DONE"))
         nil
         )

        ("x"
         ((agenda))
         nil
         ("~/Nextcloud/public_html/agenda.html"))

        ("w"
         ((todo))
         nil
         ("~/Nextcloud/public_html/todos.html"))

        ("z"
         ((agenda) (todo))
         nil
         ("~/Nextcloud/public_html/all.html"
          "~/Nextcloud/public_html/ics/all.ics"))

        ))

Timesheet, Export Clocking entries to CSV

This is here, because the default setting for the package causes an error.

(require 'org-clock-csv nil t)

(setq org-clock-csv-header "Title,Task,Parents,Category,Owner,Start,End,Minutes,Year,Month,Day,Effort,Tags,File")
(setq org-clock-csv-row-fmt
      '(lambda (plist)
         (let ( (start (plist-get plist ':start))
                (end (plist-get plist ':end)) )
           (let ( (start-year (substring start 0 4))
                  (start-month (substring start 5 7))
                  (start-day (substring start 8 10))
                  (start-time (substring start 11))

                  (end-year (substring end 0 4))
                  (end-month (substring end 5 7))
                  (end-day (substring end 8 10))
                  (end-time (substring end 11)) 

                  (start-parsed (date-to-time start))
                  (end-parsed (date-to-time end)) )
             (mapconcat #'identity
                        (list (plist-get plist :title)
                              (org-clock-csv--escape (plist-get plist ':task))
                              (org-clock-csv--escape (s-join org-clock-csv-headline-separator (plist-get plist ':parents)))
                              (org-clock-csv--escape (plist-get plist ':category))
                              (org-clock-csv--escape (org-clock-csv--read-property plist "OWNER"))
                              start
                              end
                              (format "%d" (/ (time-subtract end-parsed start-parsed) 60))
                              start-year
                              start-month
                              start-day
                              (plist-get plist ':effort)
                              (plist-get plist ':tags)
                              (org-clock-csv--read-property plist "FILE"))
                        ",")))))

Literate Programming

Source Code which I use

These are the code blocks I want to be able to evaluate

(require 'ob-shell nil t)

(org-babel-do-load-languages
 'org-babel-load-languages
 '((ruby . t)
   (calc . t)
   (emacs-lisp . t)
   (gnuplot . t)
   (plantuml . t)
   (shell . t)
   (ledger . t)
   (R . t)
   (dot . t)
   (ditaa . t)
   (latex . t)
   ))

Diagrams

Where is Plant UML?

(setq org-plantuml-jar-path "/usr/share/java/plantuml/plantuml.jar") ; alternate location: "/opt/plantuml/plantuml.jar"
(setq plantuml-jar-path org-plantuml-jar-path)

Where is Ditaa?

; (setq org-babel-ditaa-java-cmd "/usr/bin/ditaa")
(setq org-ditaa-jar-path "/usr/share/java/ditaa/ditaa-0.11.jar")

Don’t bother asking permission before executing code

By default, do not prompt for confirmation for safe languages (See Org Mode code evaluation and security issue for more details):

(defun my-org-confirm-babel-evaluate (lang body)
  (not (or (string= lang "plantuml")
           (string= lang "ledger")
           (string= lang "dot")
           (string= lang "ditaa"))))

(setq org-confirm-babel-evaluate 'my-org-confirm-babel-evaluate)

Display images inline after code execution

(add-hook 'org-babel-after-execute-hook 'org-display-inline-images)
(add-hook 'org-mode-hook 'org-display-inline-images)

Notice that these variables can be safely set in a buffer-local manner. So you might want to add the following code at the end of files which contains safe code and for which you do not want to be prompted before execution.

Exporting and Publishing

Export engines

Load extra export engines

(mapcar
  '(lambda (x) (require x nil t))
  '(ox-reveal ox-slimhtml ox-beamer))

Defaults for Exporting

(setq org-export-with-smart-quotes t)
(setq org-export-with-sub-superscripts t)

TODO Link You Tube Videos

This has to be updated to match the new Org Mode API

Many, many thanks to Artur Malabarba and his post Embedding Youtube videos with org-mode links!

(defvar yt-iframe-format
  ;; You may want to change your width and height.
  (concat "<iframe width=\"440\""
          " height=\"335\""
          " src=\"https://www.youtube.com/embed/%s\""
          " frameborder=\"0\""
          " allowfullscreen>%s</iframe>"))

(org-add-link-type
 "yt"
 (lambda (handle)
   (browse-url
    (concat "https://www.youtube.com/embed/"
            handle)))
 (lambda (path desc backend)
   (cl-case backend
     (html (format yt-iframe-format path (or desc "")))
     (latex (format "href{%s}{%s}" path (or desc "video"))))))

Org Mode Support Functions for Publishing Projects

This code loads the code necessary to build the websites I manage with Org Mode.

There are two components:

  1. A avm-org-publish-support.org file extends Org Mode publishing functions with preview and deploy
  2. A website-dependent project specification tells Org Mode how to build the website. That of my homepage can be found here: project-specification.html

The Org Mode project specification of my home page lives in my homepage. Load it, only if it exists and when the load-home-website function is invoked. This ensures my Emacs initialization file is not compromised by the project specification of my homepage being moved around or being buggy:

(defun load-website-functions ()
  "Load website support functions for preview, deploy and metadata."
  (interactive)
  (progn
    (if (not (featurep 'website-management))
        (org-babel-load-file (expand-file-name "~/Sites/home/project-specification/website-management.org")))
    (if (not (featurep 'website-metadata))
        (org-babel-load-file (expand-file-name "~/Sites/home/project-specification/website-metadata.org")))))

The specification of my homepage lives in my homepage, and it is in an Org Mode file.

(defun load-home-website ()
  "Load my homepage website building specification, so that I can build my website."
  (interactive)
  (let ( (project-location (expand-file-name "~/Sites/home/project-specification/project-specification.org")) )
    (if (file-exists-p project-location)
        (progn
          (load-website-functions)
          (org-babel-load-file project-location))
      (message "Warning! project specification (%s) not found (check init.el)" project-location))))

The specification of the cl-2020 website lives in another directory, but it is specified in the same way as my home page, that is, in an Org Mode file.

(defun load-cl-website ()
  "Load my homepage website building specification, so that I can build my website."
  (interactive)
  (let ( (project-location (expand-file-name "~/Sites/cl-2020/project-specification/project-specification.org")) )
    (if (file-exists-p project-location)
        (progn
          (load-website-functions)
          (org-babel-load-file project-location))
      (message "Warning! project specification (%s) not found (check init.el)" project-location))))

Export to Re-Reveal

(require 'org-re-reveal nil t)

Export to slim HTML

There is nothing to customize, here.

The documentation explains how to integrate this function when exporting a project: https://github.com/balddotcat/ox-slimhtml

Export to ical

(require 'ox-icalendar nil t)

(if (featurep 'ox-icalendar)
    (progn
      (setq org-icalendar-alarm-time 20)  ; minutes before event for alarms
      (setq org-icalendar-include-todo t)
      ))

Calfw

CalFW displays calendars in a grid. Very flexible, it supports many formats, among which Org Mode agenda files.

(require 'calfw nil t)
(require 'calfw-org nil t)
(require 'calfw-cal nil t)
(require 'calfw-ical nil t)

(setq cfw:display-calendar-holidays t)
(setq cfw:org-overwrite-default-keybinding t)


(defun my-open-calendar ()
  (interactive)
  (cfw:open-calendar-buffer
   :contents-sources
   (list
    (cfw:org-create-source "Orange")  ; orgmode source
    ;; (cfw:cal-create-source "Orange") ; diary source
    ;;(cfw:ical-create-source "Moon" "~/moon.ics" "Gray")  ; ICS source1
    ;;(cfw:ical-create-source "gcal" "https://..../basic.ics" "IndianRed") ; google calendar ICS
   ))) 

Sending mail

There are various ways and methods for sending mail in Emacs, among which mail mode and message mode. The former uses send-mail-function to specify how to send mail, while the latter uses message-send-mail-function.

Telling Emacs to use Message Mode is as simple as:

(setq mail-user-agent 'message-user-agent)

The following functions choose different methods to send email:

    (defun avm/mail-send-via-shairtech ()
      "Configure SMTP mail to use ShairTech account to send email"
      (interactive)
      (setq message-send-mail-function 'smtpmail-send-it
            send-mail-function 'smtpmail-send-it)
      (setq user-full-name "Adolfo Villafiorita"
            user-mail-address "adolfo@shair.tech"
            mail-from-address "Adolfo Villafiorita <adolfo@shair.tech>"
            smtpmail-smtp-server "smtps.aruba.it"
            smtpmail-smtp-service 465
            smtpmail-stream-type 'ssl
            smtpmail-smtp-user "adolfo@shair.tech"))

    (defun avm/mail-send-via-ict4g ()
      "Configure SMTP mail to use ICT4G account to send email"
      (interactive)
      (setq message-send-mail-function 'smtpmail-send-it
            send-mail-function 'smtpmail-send-it)
      (setq user-full-name "Adolfo Villafiorita"
            user-mail-address "adolfo@ict4g.net"
            mail-from-address "Adolfo Villafiorita <adolfo@ict4g.net>"
            smtpmail-smtp-server "smtp.zoho.eu"
            smtpmail-smtp-service 587
            smtpmail-stream-type 'starttls))

    (defun avm/mail-send-via-msmtp ()
      "Configure SMTP mail to send via Msmtp"
      (interactive)
      (setq user-full-name "Adolfo Villafiorita"
            user-mail-address "adolfo@shair.tech"
            mail-from-address "Adolfo Villafiorita <adolfo@shair.tech>")
      ;; use msmtp
      (setq message-send-mail-function 'message-send-mail-with-sendmail
            send-mail-function 'message-send-mail-with-sendmail)
      ;; msmtp has two ways of sending email, directly, using msmtp or
      ;; using a queue, which needs to be flushed
      ;; (setq sendmail-program "/usr/share/doc/msmtp/msmtpqueue/msmtp-enqueue.sh")
      (setq sendmail-program "/usr/bin/msmtp")
      ;; tell msmtp to choose the SMTP server according to the from field in the outgoing email
      (setq message-sendmail-extra-arguments '("--read-envelope-from"))
      (setq message-sendmail-f-is-evil 't))

    (avm/mail-send-via-msmtp)

(eval-after-load "simple"
  '(defun compose-email ()
      "Override compose-email using MU4E compose mail."
      (interactive)
      (mu4e-compose-new)))

Managing mail with MU4E

General Configuration

(require 'mu4e nil t)
(require 'mu4e-conversation nil t)

(require 'mu4e-maildirs-extension nil t)
;; (if (featurep 'mu4e-maildirs-extension)
;;     (mu4e-maildirs-extension))

(require 'mu4e-marker-icons nil t)
(if (featurep 'mu4e-marker-icons)
    (mu4e-marker-icons-mode 1))

Where do we find executables to support mu4e?

(setq mu4e-mu-binary "/usr/bin/mu")

Retrieval and indexing

From the official documentation, to speed up indexing (Speeding upindexing):

;; speed up retrieval
(setq
    mu4e-index-cleanup t      ;; whether to do a full cleanup after indexing
    mu4e-index-lazy-check nil)  ;; use only the timestamp to check a message

;; avoid duplicates
(setq mu4e-change-filenames-when-moving t)

Retrieval and indexing can be performed inside or outside Emacs.

Common ways of doing it are:

  1. With a cron job:
0-59/5 *       *       *       *       [ $(nmcli networking connectivity) = 'full' ] && mbsync -a && mu index
  1. Using a systemctl unit: see https://wiki.archlinux.org/index.php/Isync
cat ~/.config/systemd/user/mbsync.service

[Unit]
Description=Mailbox synchronization service

[Service]
Type=oneshot
ExecStart=/usr/bin/mbsync -Va

cat ~/.config/systemd/user/mbsync.timer

[Unit]
Description=Mailbox synchronization timer

[Timer]
OnBootSec=2m
OnUnitActiveSec=5m
Unit=mbsync.service

[Install]
WantedBy=timers.target
(defun avm/mu4e-retrieve-from-emacs ()
  (interactive)
  (setq 
    mu4e-update-interval nil
    mu4e-get-mail-command "mbsync -a"))

(defun avm/mu4e-retrieve-outside-emacs ()
  (interactive)
  (setq 
    mu4e-update-interval nil
    mu4e-get-mail-command "true"))

(defun avm/mbsync-retrieve-now ()
  (interactive)
  (let ( (buffer (get-buffer-create "*manual mbsync*")) )
    (progn
      (display-buffer buffer)
      (start-process "mbsync"
                     buffer
                     "/usr/bin/mbsync"
                     "-a")
      (start-process "mu"
                     buffer
                     "/usr/bin/mu"
                     "--nocolor"
                     "index"))))

(defalias 'mbsync-retrieve-now 'avm/mbsync-retrieve-now)

; (avm/mu4e-retrieve-from-emacs)
(avm/mu4e-retrieve-outside-emacs)

Headers view

Reading emails.

Decide how the window is split:

(setq mu4e-split-view 'vertical)
(setq mu4e-use-fancy-chars nil)

(defun avm/mu4e-alternate-view ()
  "Use MU4E in a single window setting"
  (interactive)
  (setq mu4e-split-view nil))

(defun avm/mu4e-single-window-view ()
  "Use MU4E in a single window setting"
  (interactive)
  (setq mu4e-split-view 'single-window))

(defun avm/mu4e-horizontal-split ()
  "Use mu4e with a horizontal setting (headers on top)"
  (interactive)
  (setq mu4e-split-view 'horizontal))

(defun avm/mu4e-vertical-split ()
  "Use mu4e with a vertical setting (headers on the left)"
  (interactive)
  (setq mu4e-split-view 'vertical))

Customize the header view:

;; show 78 columns of the headers view
(setq mu4e-headers-visible-columns 78)
(setq mu4e-headers-fields
      '((:human-date . 12)
        (:flags . 6)
        (:tags . 10)
        ;; (:mailing-list . 10)
        (:maildir . 22)
        (:from-or-to . 22)
        (:subject)))
(setq mu4e-date-format-long "%x") ;; "%a %d/%m @ %h:%m"

(setq mu4e-headers-leave-behavior 'apply)

Bookmarks

(mapcar 
 (lambda (x) (add-to-list 'mu4e-bookmarks x))
 '( ( :name  "pec-shair.tech Inbox"
             :query "maildir:\"/pec-shairtech\""
             :key   ?S)
    ( :name  "adolfo-shair.tech Inbox"
             :query "maildir:\"/pec-adolfo\""
             :key   ?A)
    ;; ( :name  "unibz Inbox"
    ;;   :query "maildir:/unibz/"
    ;;   :key   ?U)
    ( :name  "ict4g Inbox"
      :query "maildir:\"/ict4g\""
      :key   ?i)
    ( :name  "shair.tech Inbox"
             :query "maildir:\"/shairtech\""
             :key   ?s)
    ))

Maildir Extensions

;;(require 'mu4e-maildirs-extension nil t)
;;(mu4e-maildirs-extension)

Viewing Messages

mu4e offers different solutions for viewing HTML messages, on top of defining thresholds which allow to control when the text version is preferred over the HTML version.

The first big distinction if whether mu4e uses its own engine or Gnus engine. This is controlled by mu4e-view-use-gnus. Notice that Gnus can generate invitations and Org Agenda events out of invitations. the advantage of parsing calendar invitations.

If mu4e engine is used, then there are three options for rendering HTML:

  • Use the built-in renderer shr
  • Use an external application, such as, for instance html2text
  • Use a custom function

This is controlled by mu4e-html2text-command.

We start by defining a couple of options with external applications. [this is taken from the documentation] An example of such a program is the program that is actually called html2text. After installation, you can set it up with something like the following:

(setq html2text-command "html2text -b 78")

Another possibility is w3m:

(setq w3m-command "/usr/bin/w3m -dump -T text/html -cols 78 -o display_link_number=true")

If mu4e-html2text-command refers to an elisp function, the function is expected to take a message plist as its input, and returns the transformed data.

You can easily create your own function, for instance:

(defun my-mu4e-html2text (msg)
  "My html2text function; shows short message inline, show
long messages in some external browser (see `browse-url-generic-program')."
  (let ( (plain (or (mu4e-message-field msg :body-txt) ""))
         (html (or (mu4e-message-field msg :body-html) "")) )
    (if (> (length html) 60000)
        (progn
          (mu4e-action-view-in-browser msg)
          "[Viewing message in external browser]")
      (mu4e-shr2text msg))))

Here choose and allow to change our mind:

(defun avm/mu4e-view-with-html2text ()
  (interactive)
  (setq mu4e-view-use-gnus nil
        mu4e-html2text-command html2text-command))

(defun avm/mu4e-view-with-w3m ()
  (interactive)
  (setq mu4e-view-use-gnus nil
        mu4e-html2text-command w3m-command))

(defun avm/mu4e-view-with-shr ()
  (interactive)
  (setq mu4e-view-use-gnus nil
        mu4e-html2text-command 'mu4e-shr2text))

(defun avm/mu4e-view-with-gnus ()
  (interactive)
  (setq mu4e-view-use-gnus t))

By default we use Gnus, which renders significantly better than the other solutions:

(avm/mu4e-view-with-gnus)

Images:

;; enable inline images
(setq mu4e-view-show-images t)
;; use imagemagick, if available
(when (fboundp 'imagemagick-register-types)
  (imagemagick-register-types))

Accepting iCalendar Invitations

https://www.djcbsoftware.nl/code/mu/mu4e/iCalendar.html

(add-hook ’dired-mode-hook ’turn-on-gnus-dired-mode)

(require 'mu4e-icalendar)
(mu4e-icalendar-setup)
(setq mu4e-icalendar-trash-after-reply t)
(setq mu4e-icalendar-diary-file "~/Nextcloud/work-cal.org")
(setq gnus-icalendar-org-capture-file "~/Nextcloud/OrgMode/work-cal.org")
(setq gnus-icalendar-org-capture-headline '("Agenda"))
(gnus-icalendar-org-setup)

Dynamic Folders and Contexts

Question: Where do we find emails? Answer: with dynamic folders.

Contexts allow to be smart about which account to use:

(setq mu4e-contexts
      `( ,(make-mu4e-context
           :name "shair.tech"
           :enter-func (lambda () (mu4e-message "Switch to the Shair.Tech context"))
           ;; no leave-func
           ;; we match based on the maildir of the message
           ;; this matches maildir /Arkham and its sub-directories
           :match-func (lambda (msg)
                         (when msg
                           (or (mu4e-message-contact-field-matches msg :to "adolfo@shair.tech"))))
           :vars '( ( user-mail-address      . "adolfo@shair.tech" )
                    ( user-full-name         . "Adolfo Villafiorita" )
                    ( mu4e-refile-folder   . "/shairtech/.Archives")
                    ( mu4e-sent-folder     . "/shairtech/.Sent")
                    ( mu4e-drafts-folder   . "/shairtech/.Drafts")
                    ( mu4e-trash-folder    . "/shairtech/.Trash")
                    ( mu4e-compose-signature  .
                                              (concat
                                               "Adolfo Villafiorita\n"
                                               "Shair.Tech\n"
                                               "https://shair.tech\n"
                                               "adolfo@shair.tech - +39 349 6204514\n"))) )

         ,(make-mu4e-context
           :name "ict4g"
           :enter-func (lambda () (mu4e-message "Entering ict4g context"))
           :leave-func (lambda () (mu4e-message "Leaving ict4g context"))
           ;; we match based on the contact-fields of the message
           :match-func (lambda (msg)
                         (when msg
                           (or (mu4e-message-contact-field-matches msg :to "adolfo@ict4g.net")
                               (mu4e-message-contact-field-matches msg :to "adolfo.villafiorita@ict4g.net"))
                           ))
           :vars '( ( user-mail-address    . "adolfo@ict4g.net"  )
                    ( user-full-name         . "Adolfo Villafiorita" )
                    ( mu4e-refile-folder   . "/ict4g/.Archives")
                    ( mu4e-sent-folder     . "/ict4g/.Sent")
                    ( mu4e-drafts-folder   . "/ict4g/.Drafts")
                    ( mu4e-trash-folder    . "/ict4g/.Trash")
                    ( mu4e-compose-signature .
                                             (concat
                                              "Adolfo Villafiorita\n"
                                              "http://ict4g.net/adolfo\n"
                                              "adolfo@ict4g.net - +39 349 6204514\n"))))

         ;; ,(make-mu4e-context
         ;;   :name "unibz"
         ;;   :enter-func (lambda () (mu4e-message "Switch to the UNIBZ context"))
         ;;   :match-func (lambda (msg)
         ;;                 (when msg
         ;;                   (or (mu4e-message-contact-field-matches msg :to "AVillafiorita@unibz.it")
         ;;                       (mu4e-message-contact-field-matches msg :to "Adolfo.VillafioritaMonteleone@unibz.it"))))
         ;;   :vars '( ( user-mail-address           . "Adolfo.VillafioritaMonteleone@unibz.it" )
         ;;            ( user-full-name      . "Adolfo Villafiorita" )
         ;;            ( mu4e-refile-folder   . "/unibz/Archive")
         ;;            ( mu4e-sent-folder     . "/unibz/Sent Items")
         ;;            ( mu4e-drafts-folder   . "/shairtech/Drafts")
         ;;            ( mu4e-trash-folder    . "/shairtech/Deleted Items")
         ;;            ( mu4e-compose-signature  .
         ;;                                      (concat
         ;;                                       "Adolfo Villafiorita\n"
         ;;                                       "University of Bolzano\n"
         ;;                                       "https://ict4g.net/adolfo\n"
         ;;                                       "avillafiorita@unibz.it - +39 349 6204514\n"))) )

         ))

;; default is to ask-if-none (ask when there's no context yet, and none match)
(setq mu4e-context-policy 'pick-first)

;; compose with the current context is no context matches;
;; default is to ask
(setq mu4e-compose-context-policy nil)

Composing e-mail

Taken from: mu4e#Writing Messages.

;; kill, do not bury buffer
(setq message-kill-buffer-on-exit t)

;; instruct mailer it can reflow our emails
;; (so that text looks fine with other mailers)
(setq mu4e-compose-format-flowed t)

org-mime allows to send messages in HTML with org-mime-htmlize-buffer.

After org-mime-htmlize, you can always run org-mime-revert-to-plain-text-mail to restore the original plain text mail.

(require 'org-mime nil t)

(setq org-mime-export-options '(:section-numbers nil
                                :with-author nil
                                :with-toc nil))

Password Management

I used to keep my passwords with KeePassXC, but there was too much attrition for me: I never managed to have authentication agent work (to reduce the number of times my password was required) and similarly for the Firefox plugin.

I ended up migrating my password file to Org Mode and mostly replicating what Org Password Manager does.

(require 'org-pass nil t)

Utilities and Custom Functions

(defun toggle-window-split (prefix)
  "Toggle from vertical to horizontal split.

With prefix argument toggle also the way in which mu4e splits the window.

(adapted from the emacs wiki)"
  (interactive "P")
  (if prefix
      (setq mu4e-split-view
            (if (eq mu4e-split-view 'horizontal) 'vertical 'horizontal)))
  (if (= (count-windows) 2)
      (let* ((this-win-buffer (window-buffer))
            (next-win-buffer (window-buffer (next-window)))
            (this-win-edges (window-edges (selected-window)))
            (next-win-edges (window-edges (next-window)))
            (this-win-2nd (not (and (<= (car this-win-edges)
                                        (car next-win-edges))
                                    (<= (cadr this-win-edges)
                                        (cadr next-win-edges)))))
            (splitter
             (if (= (car this-win-edges)
                    (car (window-edges (next-window))))
                 'split-window-horizontally
               'split-window-vertically)))
       (delete-other-windows)
       (let ((first-win (selected-window)))
         (funcall splitter)
         (if this-win-2nd (other-window 1))
         (set-window-buffer (selected-window) this-win-buffer)
         (set-window-buffer (next-window) next-win-buffer)
         (select-window first-win)
         (if this-win-2nd (other-window 1))))))

(defun transpose-windows ()
  "Transpose two windows.  If more or less than two windows are visible, error."
  (interactive)
  (unless (= 2 (count-windows))
    (error "There are not 2 windows."))
  (let* ((windows (window-list))
         (w1 (car windows))
         (w2 (nth 1 windows))
         (w1b (window-buffer w1))
         (w2b (window-buffer w2)))
    (set-window-buffer w1 w2b)
    (set-window-buffer w2 w1b)))

This is super-useful and gotten from Stack Overflow:

(defalias 'rename-current-buffer-and-file 'rename-file-and-buffer)
(defun rename-file-and-buffer ()
  "Renames current buffer and file it is visiting
(from Stack Overflow)"
  (interactive)
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (error "Buffer '%s' is not visiting a file!" name)
      (let ((new-name (read-file-name "New name: " filename)))
        (if (get-buffer new-name)
            (error "A buffer named '%s' already exists!" new-name)
          (rename-file filename new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil)
          (message "File '%s' successfully renamed to '%s'"
                   name (file-name-nondirectory new-name)))))))

Somehow I never got used to the standard commands Emacs provides for searching with grep:

(defun mygrep ()
  "Run grep recursively from the directory of the current buffer or the default directory"
  (interactive)
  (let ( (dir (file-name-directory (or load-file-name buffer-file-name default-directory))) )
    (let ( (command (read-from-minibuffer "Run grep (like this): "
                                          (cons (concat "grep -nH -ir  " dir) 13))) )
      (grep command))))

Browsing the Web

EWW

(setq
 browse-url-browser-function 'browse-url-default-browser ; Use eww as the default browser with eww-browse-url
 shr-use-fonts  nil                          ; No special fonts
 shr-use-colors nil                          ; No colours
 shr-indentation 2                           ; Left-side margin
 shr-width 78                                ; Fold text to 70 columns
 eww-search-prefix "https://duckduckgo.com/?q=")    ; Use another engine for searching