Adolfo’s Emacs Lisp Initialization File
#+PROPERTY :tangle “~/.emacs.el”
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:
- Documentation and code live together, which is super nice when there are some customizations which are tricky
- 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:
- Loading this file from
init.el
. - Manually tangling the code. This is what I do.
- 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:
- Preview themes here: https://emacsthemes.com/
- 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 withC-c C-w N
, whereN
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 configurationC-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 withC-c <left>
andC-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 bookmarkC-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))
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 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))
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))
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))
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")) (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. 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 'top-level) (setq org-agenda-insert-diary-strategy 'date-tree) (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 "~/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) ))
Org Protocol
Nice instructions found here: https://github.com/alphapapa/org-protocol-capture-html#org-protocol-instructions
Require org-protocol
and add a template specifying where links are stored:
(require 'org-protocol nil t) (setq org-protocol-templates `( ("P" "Protocol" entry (file+headline ,(concat org-directory "bookmarks.org") "Inbox") "* %^{Title}\nSource: %u, %c\n #+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n\n%?") ("L" "Protocol Link" entry (file+headline ,(concat org-directory "bookmarks.org") "Inbox") "* %? [[%:link][%:description]] \nCaptured On: %U") )) (setq org-capture-templates (append org-capture-templates org-protocol-templates))
Add the following desktop entry to ~/.local/share/applications/
(this works on Gnome/Linux systems):
[Desktop Entry] Name=org-protocol Exec=emacsclient -n %u Type=Application Terminal=false Categories=System; MimeType=x-scheme-handler/org-protocol;
Tell the system how to handle the org-protocol
schema:
update-desktop-database ~/.local/share/applications/
Add the following bookmark to your browser:
javascript:location.href='org-protocol://store-link?'+new URLSearchParams({url:location.href, title:document.title});
Now when visiting a page, clicking on the bookmark executes the
corresponding Javascript, which invokes org-protocol
. The desktop
entry specifies that org-protocol
links have to be handled by
emacsclient
, which executes
Workflows
(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)") ))
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:
- loading
.emacs.el
is necessary to get the correct value oforg-agenda-custom-commands
(even though we could set the variable in the script) - 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
(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:
- A avm-org-publish-support.org file extends Org Mode publishing functions
with
preview
anddeploy
- 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 "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-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:
- With a cron job:
0-59/5 * * * * [ $(nmcli networking connectivity) = 'full' ] && mbsync -a && mu index
- 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 nil) (setq mu4e-icalendar-diary-file "~/Nextcloud/diary") (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) ;; 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 runorg-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) 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