#+Title: Emacs Customizations #+Author: Ricardo Wurmus #+PROPERTY: noweb tangle #+PROPERTY: mkdirp t #+OPTIONS: tasks:nil toc:1 * Introduction My Emacs configuration is a mess. As I’m writing this my Emacs configuration stretches across multiple files, each containing various snippets of code that seemed like a good idea to group. Unfortunately, there are a some things that don’t have a “natural” home. Enabling the same minor mode in various major modes is one of these cases—do I duplicate the hook and place it in a file for each major mode? Or do I write a new file for the minor mode in which I add it to the major modes at once? With multiple files I spend too much time trying to find the best place for any bit of configuration I add. This slows me down and sometimes I just append to the main =init.el=, so I often feel that my configuration is in need of reorganisation. But configuring Emacs should be fun! I don’t want it to create an uncomfortable clean-up task as a side-effect. This is why I’m now trying to use a literate approach with =org-mode=. My Emacs configuration should be prose first and code second. In my experience, finding the right spot in prose for a new paragraph requires a lot less effort as the text itself acts as a connection between unrelated bits of code. * How to use this? The sole purpose of the =~/.emacs.d/init.el= file is now to load up and interpret this =org-mode= file. (We don’t execute this code here.) #+BEGIN_SRC elisp :tangle no (require 'org) ;; Load the actual configuration file (org-babel-load-file (expand-file-name (concat user-emacs-directory "init.org"))) #+END_SRC Alternatively, we can take all code blocks in this file and assemble an =init.el= from it. This is what the =org-babel-tangle= procedure does for us. I actually prefer doing it this way, because startup times are faster this way as org-mode doesn’t have to be loaded first. We can even byte-compile the init file. * Initialise packages :PROPERTIES: :noweb-ref: packages :END: Emacs is an operating system and I use it as such (see [[http://elephly.net/posts/2016-02-14-ilovefs-emacs.html][this blog post]]). I rely on quite a few extensions that have been made available on various ELPA repositories. Recently, I have moved to installing and managing Emacs packages like any other software package on my system with the functional package manager [[https://gnu.org/s/guix][GNU Guix]]. I find this more reliable, although at first it is slightly less convenient as I can no longer just use =package.el= but first need to package the Elisp code for Guix. To install all packages via GNU Guix I can either use a manifest file or use the following invocation: #+BEGIN_SRC sh :noweb-ref nil guix package -i \ emacs \ emacs-auctex \ emacs-better-defaults \ emacs-clojure-mode \ emacs-company \ emacs-debbugs \ emacs-emms \ emacs-expand-region \ emacs-fill-column-indicator \ emacs-god-mode \ haskell-mode \ emacs-ido-ubiquitous \ emacs-js2-mode \ emacs-lispy \ emacs-markdown-mode \ emacs-mmm-mode \ emacs-multiple-cursors \ emacs-org-bullets \ emacs-page-break-lines \ emacs-paren-face \ emacs-pdf-tools \ emacs-perspective \ emacs-projectile \ emacs-rich-minority \ emacs-shell-switcher \ emacs-skewer-mode \ emacs-smart-mode-line \ emacs-smex \ emacs-tagedit \ emacs-typo \ emacs-undo-tree \ emacs-web-mode \ emacs-wget \ emacs-zenburn-theme \ emacs-znc \ paredit \ geiser \ magit \ sicp #+END_SRC There are some packages I use that are not yet packaged for Guix. Here’s a list of them: #+BEGIN_SRC elisp (defvar my/packages '(centered-cursor-mode guide-key ido-vertical-mode scss-mode)) #+END_SRC I want these packages to be installed automatically. First I need to define in what repositories Emacs should look for the packages. I’m using packages from both the “melpa” and “marmalade” repositories. #+BEGIN_SRC elisp (require 'package) (add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/")) (add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/")) (package-initialize) #+END_SRC If this is a fresh Emacs installation melpa needs to be initialised first: #+BEGIN_SRC elisp (unless (file-exists-p "~/.emacs.d/elpa/archives/melpa") (package-refresh-contents)) #+END_SRC Now we are ready to install packages if they aren’t yet installed. #+BEGIN_SRC elisp (defun packages-install (packages) (mapcar (lambda (package) (when (not (package-installed-p package)) (package-install package))) packages) (delete-other-windows)) (defun init--install-packages () (packages-install my/packages)) #+END_SRC Install packages as soon as this configuration is evaluated. If there’s an error (e.g. because a package by this name cannot be found) ask Emacs to refresh the list of packages and retry. If there’s an error again we just ignore it. It could be that it’s because there’s no Internet connection. #+BEGIN_SRC elisp (condition-case e (init--install-packages) (error (ignore-errors (package-refresh-contents) (init--install-packages)))) #+END_SRC * Default fonts :PROPERTIES: :noweb-ref: default-fonts :END: I like pretty faces. For coding I like to use the DejaVu Sans Mono font. In =org-mode= and in =eww= I like to use a font with variable pitch instead of the default mono-spaced font. I find Linux Biolinum pretty, especially when it’s rendered large. #+BEGIN_SRC elisp (set-default-font "DejaVu Sans Mono") (set-face-attribute 'variable-pitch nil :height 1.3 :family "Linux Biolinum") #+END_SRC * TODO Org-mode This is my org mode configuration. Document it. #+BEGIN_SRC elisp :noweb-ref org-mode-part1 (setq org-ellipsis "⤵") (setq org-src-fontify-natively t) (global-set-key (kbd "C-c o l") 'org-store-link) (global-set-key (kbd "C-c o a") 'org-agenda) ;; TODO: make these available in org-mode only (global-set-key (kbd "C-c o s") 'org-schedule) (global-set-key (kbd "C-c o c") 'org-capture) (setq org-log-done t) (setq org-return-follows-link t) (setq org-directory "~/Documents/org") (setq org-agenda-files (mapcar (lambda (x) (concat org-directory x)) (list "/master.org" "/work.org" "/study.org" "/home.org"))) (setq org-default-notes-file (concat org-directory "/notes.org")) (setq org-agenda-custom-commands '(("w" todo "WAITING" nil) ("n" todo "NEXT" nil) ("d" "Agenda + Next Actions" ((agenda) (todo "NEXT"))))) (defun my/modify-org-done-face () (setq org-fontify-done-headline t) (set-face-attribute 'org-done nil :strike-through t) (set-face-attribute 'org-headline-done nil :strike-through t :foreground "light gray")) (require 'org-bullets) (add-hook 'org-add-hook 'my/modify-org-done-face) (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1) ;; render with variable pitch font (with a few exceptions) (variable-pitch-mode 1) (set-face-attribute 'org-document-title nil :height 1.6 :foreground "black") (set-face-attribute 'org-level-1 nil :height 1.3 :foreground "black") (set-face-attribute 'org-block-background nil :background "#efe9d6") (dolist (face '(org-meta-line org-document-info-keyword org-special-keyword)) (set-face-attribute face nil :foreground "#93a1a1" :weight 'normal)) (dolist (face '(org-block-begin-line org-block-end-line org-table org-meta-line org-document-info-keyword org-special-keyword org-verbatim org-todo org-block-background)) (set-face-attribute face nil :inherit 'fixed-pitch) ;; TODO: this is ugly. When scaling up the variable-pitch face the ;; fixed-pitch face will become even larger. (set-face-attribute face nil :height .8)))) (defun gtd () (interactive) (find-file (concat org-directory "/master.org"))) #+END_SRC The following snippet is an attempt to prettify the somewhat ugly headers of source code blocks in =org-mode=. The snippet was taken from [[https://pank.eu/blog/pretty-babel-src-blocks.html][the blog of Rasmus Pank]] and slightly modified to suit my needs. #+BEGIN_SRC elisp :noweb-ref org-mode-pretty (defvar-local rasmus/org-at-src-begin -1 "Variable that holds whether last position was a ") (defvar rasmus/ob-header-symbol ?☰ "Symbol used for babel headers") (defun rasmus/org-prettify-src--update () (let ((case-fold-search t) (re "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*") found) (save-excursion (goto-char (point-min)) (while (re-search-forward re nil t) (goto-char (match-end 0)) (let ((args (org-trim (buffer-substring-no-properties (point) (line-end-position))))) (when (org-string-nw-p args) (let ((new-cell (cons args rasmus/ob-header-symbol))) (cl-pushnew new-cell prettify-symbols-alist :test #'equal) (cl-pushnew new-cell found :test #'equal))))) (setq prettify-symbols-alist (cl-set-difference prettify-symbols-alist (cl-set-difference (cl-remove-if-not (lambda (elm) (eq (cdr elm) rasmus/ob-header-symbol)) prettify-symbols-alist) found :test #'equal))) ;; Clean up old font-lock-keywords. (font-lock-remove-keywords nil prettify-symbols--keywords) (setq prettify-symbols--keywords (prettify-symbols--make-keywords)) (font-lock-add-keywords nil prettify-symbols--keywords) (while (re-search-forward re nil t) (font-lock-flush (line-beginning-position) (line-end-position)))))) (defun rasmus/org-prettify-src () "Hide src options via `prettify-symbols-mode'. `prettify-symbols-mode' is used because it has uncollpasing. It's may not be efficient." (let* ((case-fold-search t) (at-src-block (save-excursion (beginning-of-line) (looking-at "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*")))) ;; Test if we moved out of a block. (when (or (and rasmus/org-at-src-begin (not at-src-block)) ;; File was just opened. (eq rasmus/org-at-src-begin -1)) (rasmus/org-prettify-src--update)) ;; Remove composition if at line; doesn't work properly. ;; (when at-src-block ;; (with-silent-modifications ;; (remove-text-properties (match-end 0) ;; (1+ (line-end-position)) ;; '(composition)))) (setq rasmus/org-at-src-begin at-src-block))) (defun rasmus/org-prettify-symbols () (mapc (apply-partially 'add-to-list 'prettify-symbols-alist) (cl-reduce 'append (mapcar (lambda (x) (list x (cons (upcase (car x)) (cdr x)))) `(("#+begin_src" . ?✎) ;; ➤ 🖝 ➟ ➤ ✎ ("#+end_src" . ?□) ;; ⏹ ("#+header:" . ,rasmus/ob-header-symbol) ("#+begin_quote" . ?») ("#+end_quote" . ?«))))) (turn-on-prettify-symbols-mode) (add-hook 'post-command-hook 'rasmus/org-prettify-src t t)) (add-hook 'org-mode-hook #'rasmus/org-prettify-symbols) #+END_SRC All of this should be loaded lazily. #+BEGIN_SRC elisp :noweb-ref org-mode (provide 'my/init-org) (with-eval-after-load "org" <> <> ) (require 'org) #+END_SRC * TODO Initial stuff :PROPERTIES: :noweb-ref: initial :END: This is supposed to happen at the very beginning, even before loading packages. I still need to arrange these things nicely. #+BEGIN_SRC elisp ;; No splash screen please ... (setq inhibit-startup-message t) ;; FIXME: This is needed to be able to use IBus Pinyin with Emacs (setenv "LC_CTYPE" "zh_CN.utf8") ;; display tool tips in echo area only (tooltip-mode -1) (setq frame-resize-pixelwise t) (defalias 'yes-or-no-p 'y-or-n-p) ;; disable mouse scrolling (mouse-wheel-mode -1) (add-to-list 'load-path (concat user-emacs-directory "lisp")) #+END_SRC * TODO More stuff :PROPERTIES: :noweb-ref: initial-after-packages :END: This is even more stuff to be done after initialising packages. I still need to process all of this and clean it up. #+BEGIN_SRC elisp ;; better defaults, includes hiding the GUI (require 'better-defaults) (require 'paren-face) (global-paren-face-mode 1) (require 'paren) (setq show-paren-delay 0) (show-paren-mode 1) (require 'projectile) (projectile-global-mode) (require 'guide-key) (setq guide-key/guide-key-sequence '("C-x r" "C-x 4" "C-c p")) (guide-key-mode) ;; Keep emacs Custom-settings in separate file (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file) (setq frame-background-mode 'light) (load-theme 'solarized-light t) (add-hook 'haskell-mode-hook (lambda () (turn-on-haskell-indentation) (turn-on-haskell-doc))) #+END_SRC * Putting it all together Having defined named code blocks in the sections above we can finally put them all together to build the init file #+BEGIN_SRC elisp :noweb yes :tangle "~/.emacs.d/init.el" <> <> <> <> <> (load "~/.emacs.d/old-init.el") #+END_SRC # Local Variables: # org-edit-src-content-indentation: 0 # eval: (add-hook 'org-babel-post-tangle-hook (lambda nil (byte-compile-file "~/.emacs.d/init.el"))) # End: