dotfiles/emacs/.emacs.d/init.org

24 KiB

Tom-Emacs Interface

This is my personal Emacs configuration. The name was inspired by "Ghost in the Shell 2: Man-Machine Interface and Ryan Rix's "Complete Computing Environment".

To start off, first I need to enable lexical binding.

  ;; -*- lexical-binding: t; -*-

Package configuration

Require package.el since I immediately start using its variables and functions anyway, no need to delay loading.

  (require 'package)

Add the MELPA and org package archives because I like living on the bleeding edge. This should be done both at run-time and compile-time so I can install packages at compile time.

  (eval-and-compile
    (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
    (add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/")))

Initialize package.el so that packages can be loaded and used. This also needs to be done at both run-time and compile-time so packages can be installed at compile-time.

  (eval-and-compile (package-initialize))

Refresh the package contents so packages can be installed from all configured archives. Don't do this at run-time because it slows down the process too much.

  (eval-when-compile (package-refresh-contents))

This macro is inspired by use-package, but I want to maintain some control of the syntax I use to configure my settings.

  (defmacro ensure-library (library &rest args)
    "Make sure LIBRARY is installed.

  ARGS should be a plist which may contain one of the following options:

  - :package

    Specify which package should actually be installed to ensure
    the library named in LIBRARY exists.

  - :path

    Specify a path to add to the load path to be able to load this
    package."
    (declare (indent 1))
    (let ((library-symbol (cl-gensym))
          (package-symbol (cl-gensym))
          (path-symbol (cl-gensym))
          (package (or (plist-get args :package) library))
          (path (plist-get args :path)))
      `(progn
         (eval-and-compile
           (let ((,path-symbol ,path))
             (if ,path-symbol
                 (add-to-list 'load-path
                              (if (file-name-absolute-p ,path-symbol)
                                  ,path-symbol
                                (concat user-emacs-directory ,path-symbol))))))
         (eval-when-compile
           (let ((,library-symbol ',library)
                 (,package-symbol ',package))
             (unless (require ,library-symbol nil :noerror)
               (package-install ,package-symbol)
               (require ,library-symbol)))))))

Helper functions

I have noticed that I refer to the combination of user-emacs-directory and "data/" a lot, so I wrote this function to make referencing it cleaner. Also useful if I ever want to move my data directory.

  (defun oni:data-location (file-name)
    "Return the location of FILE-NAME within my data directory.
  This is currently the data directory under the
  `user-emacs-directory'."
    (concat user-emacs-directory "data/" file-name))

I also wrote a test for it.

  (with-eval-after-load 'ert
    (ert-deftest oni:data-location ()
      "Test that `oni:data-location' returns the correct locations."
      (should (string= "~/.emacs.d/data/backup-files/"
                       (oni:data-location "backup-files/")))
      (should (string= "~/.emacs.d/data/auto-save-files/"
                       (oni:data-location "auto-save-files/")))
      (should (string= "~/.emacs.d/data/auto-save-list/.saves-"
                       (oni:data-location "auto-save-list/.saves-")))))

Backups

I don't like having every directory filled with "filename~" files. So instead of saving backup files to the same directory, save them to a special one instead.

  (setq backup-directory-alist `((".*" . ,(oni:data-location "backup-files/"))))

Auto saves

I prefer to keep all autosave files in a single directory so they don't clog up my filesystem so much. Usually these files get deleted, but sometimes they don't, and I don't think they look pretty. Add it to the end of the list because the default value stores auto-saves for remote files in /tmp, which is fine by me.

  (add-to-list 'auto-save-file-name-transforms
               `(".*" ,(oni:data-location "auto-save-files/") t) :append)

Place the files which contain the auto save files in a similar directory.

  (setq auto-save-list-file-prefix (oni:data-location "auto-save-list/.saves-"))

Tabs

Generally I prefer using spaces over tabs. Especially for lisp-like languages.

  (setq-default indent-tabs-mode nil)

A tab-width of 8 is too wide for me, and 2 is too narrow. 4 is just right.

  (setq-default tab-width 4)

Font

Set the default font to a more pleasing one, in my opinion, with a better size as well.

  (add-to-list 'default-frame-alist '(font . "Fantasque Sans Mono-13"))

Menu bar

I don't use the menu bar, so it just takes up space.

  (menu-bar-mode -1)

Tool bar

I don't use the tool bar, so it just takes up space.

  (tool-bar-mode -1)

Scroll bar

I don't use the scroll bar to either navigate my buffers or see whereabouts I am, so they just take up space.

  (scroll-bar-mode -1)

Whitespace

I hate it when trailing whitespace is left around a file. I've been using this for years, and apart from having some trouble working with people who don't pay attention to it, it has worked flawlessly.

  (ensure-library destroy-trailing-whitespace
    :path "vendor-lisp/destroy-trailing-whitespace")
  (require 'destroy-trailing-whitespace)
  (global-destroy-trailing-whitespace-mode)

Having a final newline at the end of the file is always a good idea. Some programs just don't work without it and others produce some strange results. Github diffs are an example.

  (setq require-final-newline t)

Long lines

By default Emacs wraps long lines around to the next line when they reach the far end of the window. However I prefer to have them truncated instead.

  (setq-default truncate-lines t)

Theme

  (ensure-library yoshi-theme
    :path "vendor-lisp/yoshi-theme")
  (add-to-list 'custom-theme-load-path
               (concat user-emacs-directory "vendor-lisp/yoshi-theme"))
  (load-theme 'yoshi :no-confirm)

Diminish

I really don't need to see some of the minor modes.

  (ensure-library diminish)
  (require 'diminish)

Ivy

Ivy is a completing read implementation that offers choises vertically. I'm surprised how much I like it. I've tried Swiper before and I didn't like that so much.

  (ensure-library ivy)

Also install the flx package to allow ivy to use fuzzy matching.

  (ensure-library flx)

Since I immediately use and enable Ivy, there's no need to autoload it, so require it to keep the byte-compiler quiet.

  (require 'ivy)

Don't show that ivy is enabled in the mode-line. It's enabled globally and I'll notice it from other things anyway (like it showing up).

  (diminish 'ivy-mode)

Enable fuzzy matching in Ivy.

  (setq ivy-re-builders-alist '((t . ivy--regex-fuzzy))
        ivy-initial-inputs-alist nil)

Enable Ivy.

  (ivy-mode)

Counsel

Counsel is a group of functions that use Ivy to specialize on certain built-in commands, such as M-x.

  (ensure-library counsel)

Since I enable Counsel mode immediately, there's no point in leaving it to be autoloaded. Requiring it keeps the byte-compiler happy.

  (require 'counsel)

Don't show that counsel is enabled in the mode-line. It's enabled globally and I'll notice whenever I press M-x for example.

  (diminish 'counsel-mode)

Enable Counsel.

  (counsel-mode)

Bookmarks

Save bookmarks in my data directory so my user-emacs-directory is less cluttered.

  (eval-when-compile (require 'bookmark))
  (setq bookmark-default-file (oni:data-location "bookmarks"))

Personal info

Set some personal info for, for example, Gnus to use.

  (setq user-full-name "Tom Willemse"
        user-mail-address "tom@ryuslash.org")

Minor modes

Paredit

Paredit is an awesome minor-mode to have when you write in any lisp-like languages. It can feel rather strict and uncomfortable at first, but once you get the hang of using it, you won't want to live without it.

  (ensure-library paredit)

Electric indent mode

By default `electric-indent-mode' is enabled globally, but I prefer to enable it locally where I need it.

  (electric-indent-mode -1)

Since Emacs 24 `electric-indent-mode' switches the behavior of the C-j and RET keys. I prefer the original situation because my muscle-memory still remembers to use C-j for newline-and-indent behaviour.

  (defun oni:switch-newline-keys ()
    "Switch the C-j and RET keys in the local buffer."
    (if electric-indent-mode
        (progn
          (local-set-key (kbd "C-j") 'newline)
          (local-set-key (kbd "RET") 'electric-newline-and-maybe-indent))
      (local-unset-key (kbd "C-j"))
      (local-unset-key (kbd "RET"))))

  (add-hook 'electric-indent-local-mode-hook #'oni:switch-newline-keys)

Flycheck

Flycheck lets me see (compiler) errors, warnings and info messages while writing code.

  (ensure-library flycheck)

When developing packages with Cask, some special care needs to be taken to ensure the checkers work correctly.

  (ensure-library flycheck-cask)
  (add-hook 'flycheck-mode-hook 'flycheck-cask-setup)

I disable the pylint and pyflakes checkers because they don't seem to add much except noise when used together with flake8. Also pylint seems hell-bent on making Python written like a statically-typed langauge.

  (with-eval-after-load 'flycheck
    (mapc (lambda (c) (delq c flycheck-checkers))
          '(python-pylint python-pyflakes)))

Also show which columns messages appear in.

  (with-eval-after-load 'flycheck
    (setq flycheck-highlighting-mode 'columns))

Show the error message at point in a tooltip.

  (ensure-library flycheck-pos-tip)

  (with-eval-after-load 'flycheck
    (require 'flycheck-pos-tip)
    (flycheck-pos-tip-mode))

Auto revert mode

ARev isn't very descriptive, and fairly wide. Use a font-awesome icon instead.

  (diminish 'auto-revert-mode
            (propertize (concat "  " (char-to-string #xf021))
                        'face '(:family "Font Awesome" :height 0.75)))

Auto fill mode

"Fill" is fine as a mode-line lighter, but I prefer something shorter.

  (diminish 'auto-fill-function
            (propertize (concat "  " (char-to-string #xf149))
                        'face '(:family "Font Awesome" :height 0.75)))

Major modes

Emacs lisp mode

Enable paredit mode.

  (add-hook 'emacs-lisp-mode-hook 'paredit-mode)

Scheme mode

Enable paredit mode.

  (add-hook 'scheme-mode-hook 'paredit-mode)

Add scsh to the list of known interpreters for scheme mode. This way shell-scripts that don't have a file extension but specify scsh as the interpreter are opened in scheme mode.

  (add-to-list 'interpreter-mode-alist '("scsh" . scheme-mode))

Inferior Emacs lisp mode (ielm)

Enable paredit mode.

  (add-hook 'inferior-emacs-lisp-mode-hook 'paredit-mode)

Mbsync configuration mode

I wrote a simple major-mode for editing my .mbsyncrc file. I might release it as a package, but for now I keep it with the rest of my configuration.

  (ensure-library mbsync-conf-mode
    :path "vendor-lisp/mbsync-conf-mode")

Since it isn't installed by package.el, I need to specify the autoload myself.

  (autoload 'mbsync-conf-mode "mbsync-conf-mode"
    "Major mode for editing mbsync configuration files."
    :interactive)

I also need to add it to the auto-mode-alist so .mbsyncrc is opened with mbsync conf mode.

  (add-to-list 'auto-mode-alist '("\\.mbsyncrc\\'" . mbsync-conf-mode))

Msmtprc mode

I wrote a simple major-mode for editing my .msmtprc file. I might release it as a package, but for now I keep it with the rest of my configuration.

  (ensure-library msmtprc-mode
    :path "vendor-lisp/msmtprc-mode")

Since it isn't installed by package.el, I need to specify the autoload myself.

  (autoload 'msmtprc-mode "msmtprc-mode"
    "Major mode for editing msmtp configuration files."
    :interactive)

I also need to add it to the auto-mode-alist so .msmtprc is opened with msmtprc mode.

  (add-to-list 'auto-mode-alist '("\\.msmtprc\\'" . msmtprc-mode))

Git commit mode

Enable electric-quote-local-mode to easily type nice-looking quotes while writing commits.

  (add-hook 'git-commit-mode-hook 'electric-quote-local-mode)

Python mode

Enable electric pair mode.

  (add-hook 'python-mode-hook 'electric-pair-local-mode)

Enable syntax and style checking with flycheck.

  (add-hook 'python-mode-hook 'flycheck-mode)

Applications

Magit

Magit is a very nice interface to Git for Emacs. It allows you to do just about anything with Git without leaving the comfort of your Emacs session.

  (ensure-library magit)

Show refined diffs in magit. This makes it much easier to see what has changed on a line.

  (eval-when-compile (require 'magit))

  (with-eval-after-load 'magit
    (setq magit-diff-refine-hunk 'all))

Gnus

Gnus is one of the most extensible Email programs on the planet. And it's not even made for email but NNTP.

Store all Gnus-related data in my data directory.

  (eval-when-compile (require 'gnus))

  (with-eval-after-load 'gnus
    (setq gnus-directory (oni:data-location "News")
          gnus-article-save-directory gnus-directory
          gnus-cache-directory gnus-directory
          gnus-kill-files-directory gnus-directory))

Store all Mail source-related data in my data directory.

  (eval-when-compile (require 'mail-source))

  (with-eval-after-load 'mail-source
    (setq mail-source-directory (oni:data-location "Mail")))

Store all message-related data in the same place as the Mail source data.

  (eval-when-compile (require 'message))

  (with-eval-after-load 'message
    (setq message-directory (oni:data-location "Mail")))

Store all nnfolder-related data in the same place as the Mail source data.

  (eval-when-compile (require 'nnfolder))

  (with-eval-after-load 'nnfolder
    (setq nnfolder-directory (oni:data-location "Mail")))

Use msmtp to send mail.

  (eval-when-compile (require 'sendmail))

  (with-eval-after-load 'sendmail
    (setq send-mail-function 'sendmail-send-it)
    (setq sendmail-program "/usr/bin/msmtp"))

Tell Gnus I'm not a novice anymore. One of the features of Gnus I use a lot is deleting messages and as long as Gnus thinks I'm a novice it will ask me if I'm sure every single time.

  (setq gnus-novice-user nil)

Add a keybinding to the Gnus summary mode to easily delete messages.

  (defun oni:gnus-delete-forward ()
    "Delete the article under point and move to the next one."
    (interactive)
    (gnus-summary-delete-article)
    (gnus-summary-next-subject 1))

  (with-eval-after-load 'gnus
    (define-key gnus-summary-mode-map (kbd "M-d") #'oni:gnus-delete-forward))

ryuslash.org

Set my main email address as the primary select method for Gnus.

  (with-eval-after-load 'gnus
    (setq gnus-select-method
          '(nnmaildir "ryuslash" (directory "~/documents/mail/ryuslash/"))))

When sending mail from the ryuslash inbox, use the ryuslash msmtp account.

  (eval-when-compile (require 'gnus-msg))

  (with-eval-after-load 'gnus-msg
    (add-to-list 'gnus-posting-styles
                 '(".*"
                   (address "tom@ryuslash.org")
                   (eval (setq message-sendmail-extra-arguments
                               '("-a" "ryuslash"))))))

picturefix

Add my work email account as a secondary select method.

  (with-eval-after-load 'gnus
    (add-to-list 'gnus-secondary-select-methods
                 '(nnmaildir "picturefix"
                             (directory "~/documents/mail/picturefix/"))))

When sending mail from the picturefix account, use the picturefix msmtp account and set the proper name and email address.

  (with-eval-after-load 'gnus-msg
    (add-to-list 'gnus-posting-styles
                 '("picturefix:"
                   (name "Tom Willemsen")
                   (address "tom@picturefix.nl")
                   (eval (setq message-sendmail-extra-arguments
                               '("-a" "picturefix"))))))

Linewise user-interface

This is the library used by Circe and Slack to display messages.

  (eval-when-compile (require 'lui))

Put the time stamp in lui buffers in the right margin. This gives the text some extra room.

  (with-eval-after-load 'lui
    (setq lui-time-stamp-position 'right-margin))

Remove the "[]" from the time stamp, it's not really necessary.

  (with-eval-after-load 'lui
    (setq lui-time-stamp-format "%H:%M"))

Give the right margin just enough room to show the time-stamps, no more, no less.

  (defun oni:set-circe-margin-width ()
    (setq right-margin-width 5))

  (add-hook 'lui-mode-hook #'oni:set-circe-margin-width)

Fix the wrap prefix so that text at the prompt is aligned properly.

  (defun oni:set-lui-prompt-wrap-prefix ()
    (setq wrap-prefix "  "))

  (add-hook 'lui-mode-hook #'oni:set-lui-prompt-wrap-prefix)

Enable visual line mode in lui buffers so my text doesn't go off-screen.

  (add-hook 'lui-mode-hook 'visual-line-mode)

Circe

I switched to Circe from ERC because I couldn't make the customizations I wanted to, Circe seems much better at this.

  (ensure-library circe)

I prefer storing my passwords in a GPG encrypted authinfo file. Circe doesn't look at this out-of-the-box, but it's easy enough to get this. This function returns a function that will get a stored password for the given host.

  (defun oni:circe-get-password-for (host)
    (lambda (_)
      (let ((found (nth 0 (auth-source-search :max 1
                                              :host host
                                              :require '(:secret)))))
        (when found
          (let ((secret (plist-get found :secret)))
            (if (functionp secret)
                (funcall secret)
              secret))))))

I spend most of my time on IRC on Freenode.

  (eval-when-compile (require 'circe))

  (with-eval-after-load 'circe
    (add-to-list 'circe-network-options
                 `("Freenode"
                   :nick "ryuslash"
                   :channels ("#emacs"
                              "#mowedline"
                              "#ninthfloor"
                              "#dispass"
                              "#linuxvoice"
                              "#conkeror")
                   :nickserv-password
                   ,(oni:circe-get-password-for "irc.freenode.net"))))

Sometimes I watch some Twitch streams as well.

  (with-eval-after-load 'circe
    (add-to-list 'circe-network-options
                 `("Twitch"
                   :use-tls nil
                   :nick "ryuslash"
                   :host "irc.twitch.tv"
                   :pass ,(oni:circe-get-password-for "irc.twitch.tv")
                   :port 6667)))

Enable coloring of nicks.

  (with-eval-after-load 'circe
    (require 'circe-color-nicks)
    (enable-circe-color-nicks))

Align all nicks.

  (ensure-library circe-aligned-nicks
    :path "vendor-lisp/circe-aligned-nicks")

  (with-eval-after-load 'circe
    (require 'circe-aligned-nicks)
    (enable-circe-aligned-nicks))

Org

Tell org-mode to fontify code blocks in their specified languages.

  (eval-when-compile (require 'org))

  (with-eval-after-load 'org
    (setq org-src-fontify-natively t))

Custom

Put the customize settings in a different file so that Emacs doesn't have to modify this file whenever something changes through customize. I put this into my init file last so any settings made in there can overwrite the ones in the rest of the file, not that I usually like to do that.

  (setq custom-file (concat user-emacs-directory "custom.el"))
  (load custom-file)