Inspired by Sacha Chua, I have moved my Emacs configuration into an organized and descriptive org-mode file. What you are reading now is, in fact, my Emacs configuration file.
Well, sort of.
How this works is based around a part of org-mode
called
org-babel
. org-babel
allows org-mode
to execute code that is
embedded into a .org file. If you look at the actual init.el file
that my Emacs loads, you’ll see that all it does is load the .org
file containing my configuration (the one you’re reading now) and
parse it through org-babel to execute only the blocks of elisp that
make up the actual configuration, while ignoring the extra
documentation and narrative, like this introduction section.
If you’re wondering about performance, org-babel doesn’t do this
parse every time I open Emacs. Instead, it sees that I’m trying to
load emacs-config.org
and checks for the existence of
emacs-config.el
. If it doesn’t find that file, or finds an out of
date version, only then does it parse the .org file to create a new
.el file. This means there’s a bit of a slow startup the first time
after the org-mode file is changes, but after that there’s no
noticeable change in performance (at least on my machine). I have
accounted for this one-off performance hit, however, by
automatically regenerating emacs-config.el
when I save
emacs-config.org
. You’ll see this later.
The only other source of major slowdown, usually only on the first
run, is the installation of packages. On a fresh install of Emacs
using this configuration, the first time Emacs is run org-babel
parses the .org file and also installs all the external packages
required by my config. This makes for a slow first-time startup, but
the benefit is that all of the configuration, installation, and
setup is done for me automatically. I don’t have to manually set
anything up, which, when setting up other editors, can often take
much longer than the first run of this config. Plus, it’s more work
to have to manually set things up, so I’ll take a one-time slowdown
in trade of automatically having my Emacs setup installed and ready
for me.
Anyway, what follows is my actual Emacs configuration, embedded into a descriptive narrative.
First, you need to get the config from GitHub. I recommend actually cloning instead of just downloading a zip file, because a cloned repo will be easier to update.
First, delete, move, or rename your existing Emacs configuration
(both the .emacs.d/
directory and your .emacs
init file). Next,
clone the repository into your home directory:
$ git clone git@github.com:echosa/emacs.d.git ~/.emacs.d
Simply run Emacs and wait a bit! All the necessary packages will
download and install automatically and everything will be
configured. This is because of the use-package
package’s :ensure
flag. That flag tells Emacs to download and install the package if
it is not already installed. Because of this, the first time you
start Emacs make take a few seconds as all the packages are
downloaded and installed. Subsequent starts will not take so long.
In order to make maintaining this config easier, I’ve made this
echosa-export-config
function which will export the proper
emacs-config.el
and README.org
files when emacs-config.org
is
updated and saved.
(defun echosa-export-config ()
(when (string= (buffer-name (current-buffer)) "emacs-config.org")
(let ((org-file "~/.emacs.d/emacs-config.org")
(elisp-file "~/.emacs.d/emacs-config.el")
(readme-file "~/.emacs.d/README.org"))
(org-babel-tangle-file org-file elisp-file "emacs-lisp")
(copy-file org-file readme-file t)
(message "Config export complete!"))))
(add-hook 'after-save-hook 'echosa-export-config)
External and third-party packages are great. They make adding new things to Emacs much nicer and less complicated.
We need to set up the package repositories for Emacs’ package manager.
(setq package-archives
'(("gnu" . "http://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")))
I’ve come to appreciate the use-package way of handling package
management. Everything is nice, clean, and compact. Best of all, all
setup for a package is self-contained within a use-package
declaration, so no need to hunt through your whole config to find
related parts (hooks in one place, key bindings in another, etc.)
Since I use use-package
to install all the other packages, this is
the only place where I pull in and use package.el. I only do that to
ensure that use-package
is installed and ready to go.
(require 'package)
(package-initialize)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile
(require 'use-package))
Here, I change some generic Emacs behavior. These are all things that aren’t tied to a specific mode or programming language. Most of these are self-explanatory. However, for more info, you can look them up with Emacs’ built-in help. That will do a better job of explaining that I can, plus there’s no need for me to reiterate it all here.
(temp-buffer-resize-mode 0)
(add-hook 'before-save-hook 'time-stamp)
(setq fill-column 80)
(setq scroll-conservatively 101)
(setq case-fold-search t)
(setq case-replace t)
(setq display-buffer-reuse-frames t)
(setq display-time-24hr-format nil)
(setq display-time-day-and-date t)
(setq large-file-warning-threshold nil)
(setq truncate-partial-width-windows nil)
Allow a
to be used in dired to reuse the buffer instead of creating new buffers for every
directory.
(put 'dired-find-alternate-file 'disabled nil)
Don’t load outdated complied files.
(setq load-prefer-newer t)
Make sure Emacs can find and run commands on the PATH.
(when (memq window-system '(mac ns x))
(setenv "PATH" (concat "/usr/local/bin:" (getenv "PATH")))
(setq exec-path (append '("/usr/local/bin") exec-path)))
Remember open files and buffers between sessions.
(desktop-save-mode 1)
I don’t like Emacs littering and leaving a bunch of temporary files all over the place, so here I tell it to keep all those files in one place.
(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/.tmp/" nil)))
(setq auto-save-list-file-prefix "~/.emacs.d/.tmp/.saves-")
(setq backup-directory-alist '(("" . "~/.emacs.d/.tmp")))
(when (fboundp 'toggle-scroll-bar) (toggle-scroll-bar nil))
(tool-bar-mode -1)
(menu-bar-mode -1)
It’s unnecessary, really.
(setq inhibit-startup-screen t)
(transient-mark-mode t)
(global-font-lock-mode t)
(column-number-mode t)
(show-paren-mode t)
(setq blink-cursor-mode t)
(setq indicate-empty-lines t)
(global-hl-line-mode 1)
I like line numbers. They help quite a bit with moving around.
(global-display-line-numbers-mode)
I don’t want to hear a blip every time I do something wrong, so I’m turning on the visible bell.
(setq visible-bell t)
If I have two buffers open with two files that have the same name, (e.g. two
different README files from two different projects), Emacs will, by default,
name the buffers README
and README<1>
. This is useless. Therefore, I turn on
uniquify and use it to name buffers with the same file name based on their
parent directories: README<projdir1>
and README<projectdir2>
.
(use-package uniquify
:defer t
:config
(setq uniquify-buffer-name-style 'post-forward-angle-brackets))
Here I configure Ido and Icomplete. Ido gives improved file finding
and buffer switching. Icomplete gives improved command execution
with M-x
.
(use-package icomplete
:config
(icomplete-mode))
(use-package ido
:config
(ido-mode 1)
(ido-everywhere 1)
(setq ido-enable-flex-matching t))
Update: At the moment, I have Evil disabled. I’m seeing how I get
by without it. I might learn that I no longer need or want
it. However, just in case, I leaving my config here, disabled
through use-package
. (Have I mentioned how awesome use-package
is?)
Call me heathen if you wish, but I prefer Vim navigation keys. Also,
I want Ido buffer switching and file finding when using Vim’s :b
and :e
.
(use-package evil
:disabled
:ensure t
:after (key-chord)
:config
(setq evil-default-cursor '(t))
(evil-mode 1)
(define-key evil-ex-map "b " 'ido-switch-buffer)
(define-key evil-ex-map "e " 'ido-find-file)
(key-chord-define evil-insert-state-map "jk" 'evil-normal-state)
(key-chord-define evil-motion-state-map "jk" 'evil-normal-state)
(key-chord-define evil-visual-state-map "jk" 'evil-normal-state)
(key-chord-define evil-emacs-state-map "jk" 'evil-normal-state))
Using key-chord-mode
, I have the vim equivalent of imap jk <Esc>
, which
allows me to use jk
instead of Esc
to get out of insert mode.
(use-package key-chord
:disabled
:ensure t
:config
(key-chord-mode 1))
To make things even easier, I set up a “leader key” of Space
, so that I can
type Space <letter>
to run a command. For instance, Space x
instead of
M-x
to execute commands.
(use-package evil-leader
:disabled
:ensure t
:after (evil)
:config
(evil-leader/set-leader "<SPC>")
(evil-leader/set-key "x" 'execute-extended-command)
(evil-leader/set-key ":" 'eval-expression)
(evil-leader/set-key "k" 'ido-kill-buffer)
(evil-leader/set-key "p" 'projectile-commander)
(evil-leader/set-key "d" 'dired)
(evil-leader/set-key "e" 'er/expand-region)
(evil-leader/set-key "m" 'mc/mark-more-like-this-extended)
(evil-leader/set-key "s" 'string-inflection-toggle)
(evil-leader/set-key "r" 'xref-find-definitions)
(evil-leader/set-key "?" 'xref-find-references)
(global-evil-leader-mode))
Let’s make sure we have “surround” support.
(use-package evil-surround
:disabled
:ensure t
:config
(global-evil-surround-mode 1))
Finally, there are some modes that I want to always be in Emacs mode instead of Evil.
Major modes:
(setq evil-emacs-state-modes
'(archive-mode bbdb-mode bookmark-bmenu-mode bookmark-edit-annotation-mode browse-kill-ring-mode bzr-annotate-mode calc-mode cfw:calendar-mode completion-list-mode Custom-mode debugger-mode delicious-search-mode desktop-menu-blist-mode desktop-menu-mode doc-view-mode dvc-bookmarks-mode dvc-diff-mode dvc-info-buffer-mode dvc-log-buffer-mode dvc-revlist-mode dvc-revlog-mode dvc-status-mode dvc-tips-mode ediff-mode ediff-meta-mode efs-mode Electric-buffer-menu-mode emms-browser-mode emms-mark-mode emms-metaplaylist-mode emms-playlist-mode etags-select-mode fj-mode gc-issues-mode gdb-breakpoints-mode gdb-disassembly-mode gdb-frames-mode gdb-locals-mode gdb-memory-mode gdb-registers-mode gdb-threads-mode gist-list-mode git-rebase-mode gnus-article-mode gnus-browse-mode gnus-group-mode gnus-server-mode gnus-summary-mode google-maps-static-mode ibuffer-mode jde-javadoc-checker-report-mode magit-popup-mode magit-popup-sequence-mode magit-commit-mode magit-revision-mode magit-diff-mode magit-key-mode magit-log-mode magit-mode magit-reflog-mode magit-show-branches-mode magit-branch-manager-mode magit-stash-mode magit-status-mode magit-wazzup-mode magit-refs-mode mh-folder-mode monky-mode mu4e-main-mode mu4e-headers-mode mu4e-view-mode notmuch-hello-mode notmuch-search-mode notmuch-show-mode occur-mode org-agenda-mode package-menu-mode proced-mode rcirc-mode rebase-mode recentf-dialog-mode reftex-select-bib-mode reftex-select-label-mode reftex-toc-mode sldb-mode slime-inspector-mode slime-thread-control-mode slime-xref-mode sr-buttons-mode sr-mode sr-tree-mode sr-virtual-mode tar-mode tetris-mode tla-annotate-mode tla-archive-list-mode tla-bconfig-mode tla-bookmarks-mode tla-branch-list-mode tla-browse-mode tla-category-list-mode tla-changelog-mode tla-follow-symlinks-mode tla-inventory-file-mode tla-inventory-mode tla-lint-mode tla-logs-mode tla-revision-list-mode tla-revlog-mode tla-tree-lint-mode tla-version-list-mode twittering-mode urlview-mode vc-annotate-mode vc-dir-mode vc-git-log-view-mode vc-svn-log-view-mode vm-mode vm-summary-mode w3m-mode wab-compilation-mode xgit-annotate-mode xgit-changelog-mode xgit-diff-mode xgit-revlog-mode xhg-annotate-mode xhg-log-mode xhg-mode xhg-mq-mode xhg-mq-sub-mode xhg-status-extra-mode cider-repl-mode emacsagist-mode elfeed-show-mode elfeed-search-mode notmuch-tree term-mode xref--xref-buffer-mode))
Winner-mode makes it really easy to handle window changes in
Emacs. C-c left-arrow
goes back to the previous window
configuration (undo), and C-c right-arrow
goes forward
(redo). This is especially helpful for when a popup window ruins
your layout. Simply C-c left-arrow
to get back to where you were.
(use-package winner
:defer 5
:config
(winner-mode 1))
Clipboard sharing. Copy in Emacs, paste in OS X, and vice versa.
(use-package pbcopy
:ensure t
:defer t
:config
(turn-on-pbcopy))
This little snippet adds eldoc support to the minibuffer. Requires Emacs 24.4 or later. Found on EndlessParenthesis.com.
(add-hook 'eval-expression-minibuffer-setup-hook #'eldoc-mode)
This package makes it easy to select regions based on various bounds: words, braces, etc.
(use-package expand-region
:ensure t
:bind (("C-=" . er/expand-region)))
Indent with 4 spaces, not a tab stop.
(setq-default c-basic-offset 4)
(setq-default tab-width 4)
(setq-default indent-tabs-mode nil)
Magit is awesome.
(use-package magit
:ensure t)
Show changes in the gutter/fringe.
(use-package git-gutter-fringe
:ensure t
:if window-system
:config
(global-git-gutter-mode))
(use-package git-gutter
:ensure t
:if (not window-system)
:config
(global-git-gutter-mode 1))
Projectile is, quite simply and objectively, the shit. There’s no other way to put it. I consider it pretty much necessary for working with full projects (as opposed to individual, unrelated files).
(use-package projectile
:ensure t
:defer 5
:init
(define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
:config
(projectile-global-mode))
Who doesn’t like a little auto-completion? I choose to use company
instead of auto-complete
(aka ac
). This decision is based on
lots of reading about both and comparing/trying out both.
(use-package company
:ensure t
:bind (("C-<tab>" . company-complete))
:config
(global-company-mode)
(setq company-dabbrev-downcase nil)
(setq company-dabbrev-ignore-case t))
The Silver Searcher (ag) is awesome. Using it, Emacs is even more awesome! Also, with this installed, Projectile can use it, as well. What a perfect match!
This requires that you have The Silver Searcher installed on your computer.
(use-package ag
:ensure t)
If you write any form of Lisp and don’t use paredit, change that. It does so much for you and helps out in so many ways. I highly recommend it, even though it is quite weird (and, honestly, sometimes frustrating) at first.
Emacs Rocks episode on paredit
(use-package paredit
:ensure t
:defer t
:hook ((emacs-lisp-mode clojure-mode) . paredit-mode))
Let’s start with adding basic PHP handling.
(use-package php-mode
:ensure t
:config
(add-hook 'php-mode-hook 'flymake-mode)
(add-hook 'php-mode-hook 'php-enable-symfony2-coding-style))
Next, let’s improve completion. This sets up ac-php to give better
PHP specific completions with company
.
(use-package company-php
:ensure t)
(use-package ac-php
:ensure t
:after (php-mode company-php)
:init
(bind-key "C-c ]" 'ac-php-find-symbol-at-point php-mode-map)
(bind-key "C-c [" 'ac-php-location-stack-back php-mode-map)
:config
(add-hook 'php-mode-hook
'(lambda ()
(require 'company-php)
(company-mode t)
(ac-php-core-eldoc-setup)
(make-local-variable 'company-backends)
(add-to-list 'company-backends 'company-ac-php-backend))))
Now, let’s set up php-cs-fixer so that it automatically fixes our PHP files on save.
Note that I have a config file for this set with M-x customize
,
not seen in this config.
(use-package php-cs-fixer
:ensure t
:config
(require 'cl)
(add-hook 'before-save-hook 'php-cs-fixer-before-save))
Of course, we want to be able to debug our PHP files. That’s where geben comes in.
Note that some geben config, like path mappings, I have done with
M-x customize
, so they do not appear in this file.
(use-package geben
:ensure t
:defer t)
Finally, let’s get a lot more detailed and IDE-like functionality with LSP in Emacs.
I currently have this disabled because it isn’t working properly.
(use-package lsp-mode
:disabled
:ensure t
:commands lsp
:init
(add-hook 'php-mode-hook #'lsp)
)
(use-package lsp-ui
:disabled
:ensure t
:commands lsp-ui-mode)
(use-package company-lsp
:disabled
:ensure t
:commands company-lsp)
The built-in JS support in Emacs is lacking.
(use-package js2-mode
:ensure t
:defer t
:mode "\\.js\\'")
(use-package json-mode
:ensure t
:defer t
:mode "\\.json\\'")
As far as I can tell, web-mode
is the best mode for dealing with
web files like HTML, Twig, etc.
(use-package web-mode
:ensure t
:mode (("\\.html\\'" . web-mode)
("\\.twig\\'" . web-mode)))
Syntax highlighting for YAML files is nice, too.
(use-package yaml-mode
:ensure t
:mode "\\.ya?ml\\'")
The ultimate experience for Clojure development: cider!
(use-package cider
:ensure t)
This customizes org-mode
a bit. For instance, I like my org files
to have auto-fill
turned on.
(defun my-org-mode-hook ()
(auto-fill-mode))
(add-hook 'org-mode-hook 'my-org-mode-hook)
(use-package encourage-mode
:ensure t
:config
(encourage-mode t))
This is a quite useful function that will change a frame with two horizontal windows into a frame with two vertical windows and vice versa.
;; http://www.emacswiki.org/emacs/ToggleWindowSplit
(defun toggle-window-split ()
(interactive)
(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))))))
(define-key ctl-x-4-map "t" 'toggle-window-split)
Any customization that is machine specific or does not belong in git
can go in custom.el
. This file is ignored from git and is where all
changes from M-x customize
are saved.
(setq custom-file "~/.emacs.d/custom.el")
(load custom-file 'noerror)
I set my theme through M-x customize
. That way, it doesn’t require
changes to this init file.