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 approach can be used to control 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.)

While the advantages of using literate programming for my init file, it took me some time to understand how the loading process would work.

There are basically three ways of making this file into a proper Emacs init file:

  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:

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)

Exwm

Exwm is Emacs as a window manager. See EXWM ArchWiki page.

This variable controls whether we load exwm, which, in turns, tries to switch Window Manager.

(setq exwm-switch nil)
(setq mouse-autoselect-window t
      focus-follows-mouse t)

(if exwm-switch
  (progn
    (require 'exwm)
    (require 'exwm-config)
    (exwm-config-default)
    ;; override some defaults of exwm
    (menu-bar-mode) 
    (ido-mode 0)))

(setq exwm-input-global-keys
      `(([?\s-r] . exwm-reset)
        ([?\s-w] . exwm-workspace-switch)
        ,@(mapcar (lambda (i)
                    `(,(kbd (format "s-%d" i)) .
                      (lambda ()
                        (interactive)
                        (exwm-workspace-switch-create ,i))))
                  (number-sequence 0 9))))

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 Desktop 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 mean even when I type \C-x\C-d.

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

Default printer

(setq printer-name "epson_home")

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 color themes are fully reversible, something custom themes does not do guarantee.

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

Switch between two themes manually

Adapted from the Emacs Wiki page about Color Themes, switch between two themes, e.g., one dark, one light.

(setq default-light-color-theme 'andreas)
(setq default-dark-color-theme 'railscast)

;; theme alternation is based on the current theme
;; ... and while I find a solution to get the current theme, I store it
;; in a global variable.
;; This means you might be required to run the function twice to get the desired
;; result.
(setq color-theme-toggle-current-theme default-light-color-theme)

(defun color-theme-toggle ()
  "Switch between two themes, 'default-light-color-theme and 'default-dark-color-theme."
  (interactive)
  (if (eq color-theme-toggle-current-theme default-light-dark-theme)
      (progn
        (enable-theme default-light-color-theme)
        (setq color-theme-toggle-current-theme default-light-color-theme))
    (progn
      (enable-theme default-dark-color-theme)
      (setq color-theme-toggle-current-theme default-light-color-theme))))

Customization of visual frame

Split Horizontally

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

Customize Mode Line

(display-time-mode)

Flat modeline:

(set-face-attribute 'mode-line nil :box nil)
(set-face-attribute 'mode-line-inactive nil :box nil)

Sidebars

There are many same-frame sidebars available, such as dired-sidebar, dirtree, neotree, treemacs, ztree (see Sidebar Directory Trees in Emacs for my assessment).

Of these, dired-sidebar and sr-speedbar are the ones I use the most often; sr-speedbar also integrates with org-mode.

(require 'sr-speedbar nil t)

(setq sr-speedbar-right-side nil)
(setq speedbar-show-unknown-files t)
(setq speedbar-update-flag nil)
(setq sr-speedbar-refresh-turn-off nil)
(setq speedbar-use-images nil)

;; show all files
(setq speedbar-directory-unshown-regexp "^$")

Other good choices include ibuffer-sidebar-toggle-sidebar and org-sidebar-tree.

Start with an open sidebar, that is, if the code below is not commented:

;;  (if (and (featurep 'sr-speedbar) (display-graphic-p)) (sr-speedbar-open))

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

(defun avm/ibuffer-activate ()
  (interactive)
  (global-set-key (kbd "C-x C-b") 'ibuffer))

(defun avm/ibuffer-deactivate ()
  (interactive)
  (global-set-key (kbd "C-x C-b") 'list-buffers))

(avm/ibuffer-deactivate)

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)
(if (featurep 'all-the-icons-ibuffer)
    (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)
(require 'ibuffer-project nil t)
(add-hook 'ibuffer-hook
  (lambda ()
    (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))))

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.

Tab Bar Mode

Code taken from https://www.emacswiki.org/emacs/TabBarMode groups buffers in tabs according to different strategies.

Group according to Git repository:

(defun find-git-dir (dir)
  "Search up the directory tree looking for a .git folder."
  (cond
   ((eq major-mode 'dired-mode) "Dired")
   ((not dir) "process")
   ((string= dir "/") "no-git")
   ((file-exists-p (concat dir "/.git")) dir)
   (t (find-git-dir (directory-file-name (file-name-directory dir))))))

(defun git-tabbar-buffer-groups ()
  "Groups tabs in tabbar-mode by the git repository they are in."
  (list (find-git-dir (buffer-file-name (current-buffer)))))

Decide how to group:

(defun custom-tabbar-buffer-groups ()
  "Return the list of group names the current buffer belongs to.
   Return a list of one element based on major mode."
  (list
   (cond
    ((or (get-buffer-process (current-buffer))
         ;; Check if the major mode derives from `comint-mode' or
         ;; `compilation-mode'.
         (tabbar-buffer-mode-derived-p
          major-mode '(comint-mode compilation-mode)))
     "Process"
     )
    ;; ((member (buffer-name)
    ;;          '("*scratch*" "*Messages*" "*Help*"))
    ;;  "Common"
    ;;  )
    ((string-equal "*" (substring (buffer-name) 0 1))
     "Common"
     )
    ((member (buffer-name)
             '("xyz" "day" "m3" "abi" "for" "nws" "eng" "f_g" "tim" "tmp"))
     "Main"
     )
    ((eq major-mode 'dired-mode)
     "Dired"
     )
    ((memq major-mode
           '(help-mode apropos-mode Info-mode Man-mode))
     "Common"
     )
    ((memq major-mode
           '(rmail-mode
             rmail-edit-mode vm-summary-mode vm-mode mail-mode
             mh-letter-mode mh-show-mode mh-folder-mode
             gnus-summary-mode message-mode gnus-group-mode
             gnus-article-mode score-mode gnus-browse-killed-mode))
     "Mail"
     )
    (t
     ;; Return `mode-name' if not blank, `major-mode' otherwise.
     (if (and (stringp mode-name)
              ;; Take care of preserving the match-data because this
              ;; function is called when updating the header line.
              (save-match-data (string-match "[^ ]" mode-name)))
         mode-name
       (symbol-name major-mode))
     ))))

Set a strategy:

(setq tabbar-buffer-groups-function 'git-tabbar-buffer-groups)

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

Multiple Cursors

Multiple cursors, documentation available here.

Event though the package documentation states that the package does not work well when you invoke its commands with M-x, some commands can be used and these are the ones I use, since I keep forgetting which function is bound to each different key.

Here they are:

  • mc/edit-lines: Adds one cursor to each line in the current region.
  • mc/edit-beginnings-of-lines: Adds a cursor at the start of each line in the current region.
  • mc/edit-ends-of-lines: Adds a cursor at the end of each line in the current region.
  • mc/mark-all-like-this: Marks all parts of the buffer that matches the current region.
  • mc/mark-all-in-region: Prompts for a string to match in the region, adding cursors to all of them.

Workflows:

  1. Select a region
  2. Invoke one of the commands above
  3. Move the cursors where you need them and make the changes
  4. When you are done, press the Enter key
(require 'multiple-cursors nil t)

;; (global-set-key (kbd "C->") 'mc/mark-next-like-this-word)
;; (global-set-key (kbd "C-<") 'mc/mark-previous-like-this-word)
;; (global-set-key (kbd "C-c C-<") 'mc/mark-all-like-this)
;; (global-set-key (kbd "C-?") 'mc/edit-lines)

Snippets

This is really super-useful!

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

Integration with the Environment

Shell

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 60000)
(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))

Launch with native apps

From the documentation: With a buffer open to a file, launch it with C-c ! !. Or, to open the file manager for the current directory, use C-c ! d. Try it with ido-everywhere turned on.

Inside special buffers like dired or vc-dir, launch marked files using C-c ! !. To just launch the default directory itself, use C-c ! d.

(require 'launch nil t)
(if (featurep 'launch)
    (global-launch-mode +1))

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

Folding

Origami

Require and use origami (advantage over yafolding: a global mode):

(require 'origami nil t)
(if (featurep 'origami)
  (progn
    (global-origami-mode +1)
    (global-set-key (kbd "C-<return>")  'origami-recursively-toggle-node)))

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.9"))

Yet another RI browser:

(require 'yari nil t)

Inferior Superior 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)))

Dash, Dasht and Friends

Set the directory of dash so that is is compatible with dasht

(setq dash-docs-docsets-path (expand-file-name "~/.local/share/dasht/docsets/"))

(defun dash-search ()
  (interactive)
  (counsel-dash))

Hippie or Dabbrev … this is the question

; (global-set-key (kbd "s-/") 'hippie-expand)
(global-set-key (kbd "s-/") 'dabbrev-expand)

Company Mode

Completion Anywhere!

(require 'company nil t)

(if (featurep 'company)
  (progn
    (add-hook 'after-init-hook 'global-company-mode)))

Flycheck and Rubocop

Flycheck is a modern on-the-fly syntax checking extension for GNU Emacs, intended as replacement for the older Flymake extension which is part of GNU Emacs. For a detailed comparison to Flymake see Flycheck versus Flymake.

(require 'flycheck nil t)
(if (featurep 'flycheck)
    (progn
      (global-flycheck-mode)))

Rubocop checks projects:

; (require 'rubocop)

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

Diary

Italian Holidays

(setq holiday-general-holidays
      '((holiday-fixed 1 1 "Capodanno")
        (holiday-fixed 5 1 "1 Maggio")
        (holiday-fixed 4 25 "Liberazione")
        (holiday-fixed 6 2 "Festa Repubblica")
))

(setq holiday-christian-holidays
     '((holiday-fixed 12 8 "Immacolata Concezione")
       (holiday-fixed 12 25 "Natale")
       (holiday-fixed 12 26 "Santo Stefano")
       (holiday-fixed 1 6 "Epifania")
       (holiday-easter-etc -52 "Giovedì grasso")
       (holiday-easter-etc -47 "Martedì grasso")
       (holiday-easter-etc  -2 "Venerdì Santo")
       (holiday-easter-etc   0 "Pasqua")
       (holiday-easter-etc  +1 "Lunedì Pasqua")
       (holiday-fixed 8 15 "Assunzione di Maria")
       (holiday-fixed 11 1 "Ognissanti")
))

Customization

(setq diary-file (expand-file-name "~adolfo/Nextcloud/diary"))
(setq diary-mail-addr "adolfo@shair.tech")

(setq calendar-mark-diary-entries t)
(setq calendar-date-style 'european) ;; 'european) ;; iso ;; european

(setq diary-display-function 'diary-fancy-display)
(setq diary-list-include-blanks t)

(add-hook 'diary-list-entries-hook 'diary-include-other-diary-files)
(add-hook 'diary-list-entries-hook 'diary-sort-entries)

(setq cal-html-directory (expand-file-name "~adolfo/Nextcloud/public_html"))
(setq cal-html-print-day-number-flag t)

Include the diary in the org-mode agenda?

(setq org-agenda-include-diary t)

Sunrise and Sunset

(setq calendar-latitude 46.06434)
(setq calendar-longitude 11.23758)
(setq calendar-location-name "Pergine Valsugana")

(setq calendar-latitude 44.34935)
(setq calendar-longitude 9.15487)
(setq calendar-location-name "Camogli")

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

(defun avm/agenda-only-calendar-files ()
  (interactive)
  (setq org-agenda-file-regexp ".*-\\(cal\\|ics\\)\\.org$"))

(defun avm/agenda-all-org-files ()
  (interactive)
  (setq org-agenda-file-regexp ".*\\.org$"))

;; (avm/agenda-only-calendar-files)
(avm/agenda-all-org-files)
(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 adding entries in the specified file

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

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. The following two variables can be used to change the defaults, for instance by inserting in a flat file and by deciding whether time information has to stay in the header or embedded in the timestamp:

;; (setq org-agenda-insert-diary-strategy 'date-tree)
(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)

Capture templates

(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 ,(concat org-directory "/bookmarks.org") "Inbox")

         "** [[%x][%?]]\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)

        ))

Workflows

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

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 '("  " "%dx"))
;;(setq org-agenda-deadline-leaders '("D!!" "D-%2d:" "D+%2d"))

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

Declutter the view:

;; (setq org-agenda-time-grid
;;       '((today daily weekly require-timed)
;;         ()
;;         "" ""))

Decide how to show items:

;; (setq org-agenda-prefix-format
;;       '((agenda . "  %i %-12 c%?-12t % s")
;;         (todo .   "  %i %-12 c")
;;         (tags .   "  %i %-12 c")
;;         (search . "  %i %-12 c")))

;; (setq org-agenda-prefix-format
;;       '((agenda . "  %?-12t %i % s")
;;         (todo .   "   %i ")
;;         (tags .   "   %i ")
;;         (search . "   %i ")))

Add a line between days:

;; (setq org-agenda-format-date (lambda (date) (concat "\n"
;;                                                     (make-string (window-width) 9472)
;;                                                     "\n"
;;                                                     (org-agenda-format-date-aligned date))))

View categories as icons (it requires all-the-icons), list of icons available here: https://material.io/resources/icons/

;; (setq org-agenda-category-icon-alist
;;       `( ("work"     ,(list (all-the-icons-material "work" :height 1.0)) nil nil :ascent center)
;;          ("personal" ,(list (all-the-icons-material "people" :height 1.0)) nil nil :ascent center)
;;          ("family"   ,(list (all-the-icons-material "home" :height 1.0)) nil nil :ascent center)
;;          ;; ("todo"     ,(list (all-the-icons-material "check_box" :height 1.0)) nil nil :ascent center)
;;          ("shopping" ,(list (all-the-icons-material "shopping_cart" :height 1.0)) nil nil :ascent center)
;;          ("groceries" ,(list (all-the-icons-material "shopping_cart" :height 1.0)) nil nil :ascent center)
;;          ("code"     ,(list (all-the-icons-material "code" :height 1.0)) nil nil :ascent center)
;;          ("sport"    ,(list (all-the-icons-material "rowing" :height 1.0)) nil nil :ascent center)
;;          ("travel"   ,(list (all-the-icons-material "airport_shuttle" :height 1.0)) nil nil :ascent center)
;;          ("phone"    ,(list (all-the-icons-material "call" :height 1.0)) nil nil :ascent center)
;;          ("conf_call" ,(list (all-the-icons-material "videocam" :height 1.0)) nil nil :ascent center)
;;          ("meeting"  ,(list (all-the-icons-material "people" :height 1.0)) nil nil :ascent center)
;;          ("conference" ,(list (all-the-icons-material "cast" :height 1.0)) nil nil :ascent center)
;;          ("eating_out" ,(list (all-the-icons-material "restaurant" :height 1.0)) nil nil :ascent center)
;;          ("money" ,(list (all-the-icons-material "credit_card" :height 1.0)) nil nil :ascent center)
;;          ("health" ,(list (all-the-icons-material "spa" :height 1.0)) nil nil :ascent center)
;;          ("business_growth" ,(list (all-the-icons-material "assessment" :height 1.0)) nil nil :ascent center)
;;          ("waiting" ,(list (all-the-icons-material "hourglass_empty" :height 1.0)) nil nil :ascent center)
;;          ("tentative" ,(list (all-the-icons-material "build" :height 1.0)) nil nil :ascent center)
;;          ))

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 in HTML format"
         ((agenda))
         nil
         ("~/Nextcloud/public_html/agenda.html"))

        ("w" "Todos in HTML format"
         ((todo))
         nil
         ("~/Nextcloud/public_html/todos.html"))

        ("z" "Agenda and Todos, HTML and ics"
         ((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, together with a code snippet to run hledger with Babel (https://gist.github.com/simonmichael/c0d756705a1015c7e8d5bbbaae435863).

(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)
   (R . t)
   (dot . t)
   (ditaa . t)
   (latex . t)
   (restclient . 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)
(setq org-html-doctype "html5")

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

Notifications

There are different approaches.

Appointments with Notifications

This is taken from: https://psachin.gitlab.io/emacs_diary_desktop_notification.html

(require 'notifications nil t)

(defcustom appt-notification-bus :session
  "D-Bus bus to use for notification."
  :version "27.2"
  :group 'appt-notification
  :type '(choice (const :tag "Session bus" :session) string))

(defun psachin/appt-display (min-to-app new-time msg)
  "Send notification."
  (notifications-notify :bus appt-notification-bus
                        :title (format "Appointment in %s minutes." min-to-app)
                        :body (format "%s" msg)
                        :replaces-id nil
                        :app-icon nil
                        :timeout 5000
                        :desktop-entry "emacs"))

(setq appt-disp-window-function (function psachin/appt-display))

(appt-activate +1)

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 "Blue") ; diary source
    ;;(cfw:ical-create-source "Family" "https://www.ict4g.net/nc/remote.php/dav/calendars/avm/personal-1/" "Green")
    ;;(cfw:ical-create-source "Moon" "~/moon.ics" "Gray")  ; ICS source1
    ;;(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-mail ()
      "Override compose-mail 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))

(require 'mu4e-speedbar) 

Where do we find executable to support mu4e?

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

Retrieval and indexing

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

;; 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 (shell (get-buffer-create "*manual mbsync*"))) )
    (progn
      (display-buffer buffer)
      (comint-send-string
       (get-buffer-process buffer)
       "mbsync -a ; mu index\n"))))
(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 . 8)
        (:flags . 6)
        ;; (:tags . 10)
        ;; (:mailing-list . 10)
        (:from-or-to . 20)
        (:subject . 60)
        (:maildir . 25)))
(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)

Accepting iCalendar Invitations

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

Accept invitations:

(require 'mu4e-icalendar)
(mu4e-icalendar-setup)
(setq mu4e-icalendar-trash-after-reply nil)

Allow to choose between diary and Org mode

(defun avm/invitation-to-diary ()
  (interactive)
  (setq mu4e-icalendar-diary-file diary-file))

(defun avm/invitation-to-org ()
  (interactive)
  (progn
    (setq gnus-icalendar-org-capture-file "~/Nextcloud/OrgMode/work-cal.org")
    (setq gnus-icalendar-org-capture-headline '("Agenda"))
    (gnus-icalendar-org-setup)))

(avm/invitation-to-org)

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)

;; do not reply to myself
(setq mu4e-compose-keep-self-cc nil)

;; bcc myself
;; (add-hook 'mu4e-compose-mode-hook
;; (defun my-add-bcc ()
;;   "Add a Bcc: header."
;;   (save-excursion (message-add-header "Bcc: adolfo@shair.tech\n"))))

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 'avm/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) 14))) )
      (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://www.duckduckgo.com/lite?q=")    ; Use another engine for searching