My emacs + evil mode (vim keys!) configuration, written using Org mode.
When emacs starts, it loads init.el
like normal. The code in init.el
loads this config.org
file, extracts all the emacs lisp source code blocks, and writes them to a file (config.el
). Emacs will then load config.el
and byte compile it for future runs. There’s also some fancy stuff happening to check last modified dates and load compiled files if no changes have happened in order to speed things up (thanks to Holger Schurig, whose public emacs config repo was the source of most of this tangling functionality!)
Since the config.el file is composed of source blocks extracted from this org file, it’s very easy to turn off bits of configuration. To exclude a block, either mark BEGIN-SRC lines with :tangle no
, or apply the custom org TODO state of “OFF” to the org outline that contains the source block.
An example of both:
Disable tangle:
* My Outline
# +BEGIN_SRC emacs-lisp :tangle no
;; code here
# +END_SRC
Turn off parent outline:
* OFF My Outline
# +BEGIN_SRC emacs-lisp
;; code here
# +END_SRC
Clone this repo into ~/.emacs.d
and launch emacs (ensure that no ~/.emacs
file exists to avoid conflicts).
git clone https://github.com/GeordieP/emacs-cfg.git $HOME/.emacs.d
This repo is a fair starting point for anyone looking to try emacs + evil mode, but there’s probably many things in here you don’t need. I recommend reading through (it’s really not that long!) and stripping stuff out.
(set 'debug-on-error t)
- Backup Files are created as a copy of the file just before Emacs writes new changes to it. They look like
filename.ext~
- Autosave Files are intermittently created by Emacs while you are working on a file in order to provide a restore point for files if a crash or otherwise descructive event occurs. They look like
#filename.ext#
- Lock Files are created when Emacs visits a file, in order to prevent other instances of Emacs from visiting the same file and creating conflicts in the file contents. They look like
.#filename.ext
(let* ((dir-file-backups (concat user-emacs-directory "file_backups/"))
(dir-file-autosaves (concat dir-file-backups)))
;; create file backup dirs if they dont exist
(unless (file-exists-p dir-file-backups) (make-directory dir-file-backups))
(unless (file-exists-p dir-file-autosaves) (make-directory dir-file-autosaves))
;; file backup configuration
(setq
backup-by-copying t
delete-old-versions t
kept-new-versions 2
kept-old-versions 1
version-control nil)
(setq
;; file to store all active autosaved file names
auto-save-list-file-name (concat dir-file-autosaves "auto-save-list")
;; directory to store autosaves: #filename.ext#
auto-save-file-name-transforms `((".*" ,(concat dir-file-autosaves "\\1") t))
;; directory for backup files: filename.ext~
backup-directory-alist `(("." . ,dir-file-backups))
;; disable lock files: .#filename.ext
create-lockfiles nil))
Configure custom file. This is where emacs will place all of its auto-generated config; anything that’s customized in the editor or otherwise needs to be remembered by Emacs between sessions.
Create file if it doesn’t already exist
(setq custom-file-path (concat user-emacs-directory "auto_custom.el"))
(unless (file-exists-p custom-file-path) (write-region "" nil custom-file-path))
Set own custom file path, then load it
(setq custom-file custom-file-path)
(load custom-file)
(setq load-prefer-newer t)
(setq-default require-final-newline t)
Switch to tabs mode
(defun gp-indent-use-tabs () (interactive)
"Use tabs for indentation"
(setq-local indent-tabs-mode t)
(setq-default indent-tabs-mode t))
Switch to spaces mode
(defun gp-indent-use-spaces () (interactive)
"Use spaces for indentation"
(setq-local indent-tabs-mode nil)
(setq-default indent-tabs-mode nil))
Set width of tabs, space indents, and evil mode shifts in the local buffer. Interactive command with a prompt.
(defun gp-set-indent-width (&optional width) (interactive)
"Set tab width and evil-shift-width"
(let ((width (or width (read-from-minibuffer "Indent Width: " nil nil 'read))))
(unless (eq (mod width 2) 0)
(error "Arg is not a multiple of 2, indent width not set."))
;; set tab stop width and width of evil shift commands
(setq-local tab-width width)
(setq-local evil-shift-width width)
;; generate a sequence of numbers from 'width' to 120, with each increasing by 'width'
(setq-local tab-stop-list (number-sequence width 120 width))))
(defun gp-set-font (name size &optional weight) (interactive)
(let ((nameAndSize (concat name "-" size))
;; use Regular as defualt weight
(weight (or weight "Regular")))
(set-face-attribute 'default nil :font nameAndSize :weight (intern weight))
(add-to-list 'default-frame-alist `(font . ,nameAndSize))))
Interactive function for setting frame title
(defun gp-set-frame-title (&optional title) (interactive)
(let ((title (or title (read-from-minibuffer "New Frame Title: "))))
(setq frame-title-format title)))
(defun gp-setup-machine-unrecognized ()
(defun gp-machine-set-font ()
(gp-set-font "Monospace" "10")))
(defun gp-setup-machine-desktop ()
(defun gp-machine-set-font ()
(gp-set-font "DejaVu Sans Mono" "11" "Medium")))
(defun gp-setup-machine-macbook ()
;; keep menu bar enabled on mac as it's not annoying
(menu-bar-mode -1)
;; fix colors in powerline separators
;; (macOS SRGB issue with certain versions of emacs)
;;
;; Two fixes here:
;; disable srgb color space (not ideal, has an effect on colors outside of powerline):
;; (setq ns-use-srgb-colorspace nil)
;; ;; OR ;; ;;
;; use built-in powerline patch (recommended):
;; https://github.com/milkypostman/powerline/issues/54#issuecomment-310867163
(defvar powerline-image-apple-rgb t)
;; set font
(defun gp-machine-set-font ()
(gp-set-font "Source Code Pro for Powerline" "16")))
(defun gp-setup-machine-toshiba ()
(defun gp-machine-set-font ()
;; (gp-set-font "Droid Sans Mono Dotted for Powerline" "11")))
(gp-set-font "Hack" "11")))
Figure out which machine we’re on and call the appropriate setup function.
If we don’t recognize the machine name, call unrecognized
to set up defaults for otherwise machine-dependant settings.
(defun gp-determine-machine ()
(cond
;; macbook pro
((string-equal (system-name) "Geordies-MacBook-Pro.local")
(gp-setup-machine-macbook))
;; work laptop
((string-equal (system-name) "gp-toshiba")
(gp-setup-machine-toshiba))
;; desktop pc
((string-equal (system-name) "gp-desktop")
(gp-setup-machine-desktop))
;; default case - unrecognized
(t (gp-setup-machine-unrecognized))))
Call the function right away to perform machine setup
(gp-determine-machine)
Turn off the native window toolbar, scrollbar, and menu bar
(tool-bar-mode -1)
(scroll-bar-mode -1)
(menu-bar-mode -1)
Enable line numbers, and add a bit of spacing around the number
(global-linum-mode)
(setq linum-format " %d ")
(global-hl-line-mode)
(setq-default line-spacing 0.15)
Set fringes to 1px. Use set-fringe-style
command to change it within a session.
(setq default-frame-alist
(nconc default-frame-alist '((left-fringe . 1) (right-fringe . 1))))
(blink-cursor-mode 0)
(setq inhibit-startup-message t)
(setq initial-scratch-message "")
Frame titles should show filename, even if only one frame exists
(setq frame-title-format "%b")
Set preferred font for current machine by calling function gp-machine-set-font
, which is a function defined based on which machine our Emacs instance is running on (see Machine specific configuration section)
(gp-machine-set-font)
Use spaces by default.
Call functions gp-indent-use-spaces
and gp-indent-use-tabs
to switch style for current session.
(setq-default indent-tabs-mode nil)
Tabs (and evil mode shifts) should be 4 spaces wide
(setq-default tab-width 4)
(setq-default evil-shift-width 4)
(setq-default js-indent-level 4)
Tabs and evil mode shifts set to 2 spaces wide in certain modes (See Mode Hooks
section)
Change dabbrev (used by evil-complete-previous (C-p binding in insert mode)) behavior to;
- Not be case sensitive when searching for matches (typing in all lowercase will register matches that contain an uppercase letter)
- Be case sensitive when applying the match (if typed text is all lowercase but matches text with uppercase, when applying the match, the uppercase characters will be used)
;; ignore case when looking for matches
(setq-default dabbrev-case-fold-search case-fold-search)
;; apply matched case when match is accepted
(setq-default dabbrev-case-replace nil)
Disable truncating lines and word wrap by default (Can be toggled using ,tw and ,tW)
(setq-default truncate-lines t)
(setq-default word-wrap t)
Auto-close braces, parens, quotes, etc
(electric-pair-mode)
Highlight matching scope delimiter to the one under the cursor
(show-paren-mode)
Stop dired from creating new dired buffers when entering a directory
(require 'dired)
(define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file)
(define-key dired-mode-map (kbd "^") (lambda () (interactive)
(find-alternate-file "..")))
(put 'dired-find-alternate-file 'disabled nil)
Set up org mode TODO states. OFF state is used to disable sections of this config file.
(setq org-todo-keywords
'((sequence "TODO(t)" "DOING(d!)" "DONE(x)" "|" "OFF(o)")))
When in an org file with source blocks, apply syntax highlighting to the blocks
(setq org-src-fontify-natively t
org-src-tab-acts-natively t
org-confirm-babel-evaluate nil
org-edit-src-content-indentation 0)
(setq mouse-wheel-scroll-amount '(3))
(setq mouse-wheel-progressive-speed nil)
(setq mouse-wheel-follow-mouse 't)
(setq scroll-step 1
scroll-conservatively 99)
Reproduce vim behavior. Only yank to emacs kill ring, unless +
register is selected (this functionality is implemented by Evil mode to work the same as vim: "+y
).
(setq x-select-enable-clipboard nil)
Load my own plugins from local ./gp/plugins
directory (must be in load path - should be done by init.el)
;; session manager
(require 'sesh)
Define package repositories, check our package list and install any that are missing.
(package-initialize)
;; package repos
(defconst gnu '("gnu" . "https://elpa.gnu.org/packages/"))
(defconst melpa '("melpa" . "https://melpa.org/packages/"))
(defconst melpa-stable '("melpa-stable" . "https://stable.melpa.org/packages/"))
;; add repos to archives list
(defvar package-archives nil)
(add-to-list 'package-archives melpa-stable t)
(add-to-list 'package-archives melpa t)
(add-to-list 'package-archives gnu t)
(unless (and (file-exists-p "~/.emacs.d/elpa/archives/gnu")
(file-exists-p "~/.emacs.d/elpa/archives/melpa")
(file-exists-p "~/.emacs.d/elpa/archives/melpa-stable"))
(package-refresh-contents))
;; evaluate the package list and install missing packages
(defun packages-install (&rest packages)
(mapc (lambda (package)
(let ((name (car package))
(repo (cdr package)))
(when (not (package-installed-p name))
(let ((package-archives (list repo)))
(package-initialize)
(package-install name)))))
packages)
(package-initialize)
(delete-other-windows))
(condition-case nil
(packages-install (cons 'use-package melpa))
(error (package-refresh-contents)
(packages-install (cons 'use-package melpa))))
General handles key bindings.
(use-package general
:ensure t
:config
;; KEY BINDS
;; different states get different general-define-key blocks
;; eg, we dont want the , leader key to be active in insert mode
;; =============
;; GENERAL KEYS - MISC
;; =============
(general-define-key
:states '(normal motion emacs insert)
"C-h" 'evil-window-left
"C-j" 'evil-window-down
"C-k" 'evil-window-up
"C-l" 'evil-window-right
"C-u" 'evil-scroll-up
"C-f" 'swiper
;; ctrl+shift+enter to insert line above
"C-S-<return>" '(lambda () (interactive)
(previous-line)
(end-of-line)
(newline-and-indent))
;; ctrl+return to insert line below, without adding break to current line
"C-<return>" '(lambda () (interactive)
(end-of-line)
(newline-and-indent)))
;; =============
;; GENERAL KEYS - MISC - NO INSERT MODE
;; =============
(general-define-key
:states '(normal motion emacs)
"C-p" 'counsel-projectile
;; confirm ivy minibuffer with currently typed value rather than suggestion
"C-M-j" 'ivy-immediate-done)
;; =============
;; GENERAL KEYS - VIM
;; =============
;; first unbind comma leader key - this fixes some issues in terminal emacs
(general-def :states '(normal motion emacs) "," nil)
(general-define-key
:states '(normal motion emacs)
:prefix ","
;; SHORTCUTS (misc keys, not inside a "menu")
"v" 'evil-window-vsplit
"c" 'kill-this-buffer
"q" 'next-buffer
"z" 'previous-buffer
"x" 'execute-extended-command
;; MENUS - <leader><menu key> enters a "menu"
;; b - BUFFERS
"bd" 'kill-buffer
"bb" 'switch-to-buffer
"bn" 'next-buffer
"bp" 'previous-buffer
"bl" 'list-buffers
;; s - SPLITS
"sv" 'evil-window-vsplit
"sh" 'evil-window-split
;; f - FILES
"ff" 'counsel-find-file
"fo" 'counsel-find-file
"fed" '(gp-session-load "config")
"fc" '(gp-session-load "config")
;; w - WINDOW
"wd" 'evil-window-delete
"wc" 'evil-window-delete
"wv" 'evil-window-vnew
"wh" 'evil-window-new
;; t - UI TOGGLES
"tn" 'global-linum-mode
"th" 'hl-line-mode
"tw" 'toggle-truncate-lines
"tW" 'toggle-word-wrap
"tm" 'hidden-mode-line-mode
"ts" 'whitespace-mode
"tis" 'gp-indent-use-spaces
"tit" 'gp-indent-use-tabs
;; e - EXECUTE
"et" 'gp-launch-terminal
"ec" 'execute-extended-command
"ee" 'eval-expression
;; s - SESSION
"ss" 'sesh-write-opened-files
"so" 'sesh-load-files
;; "sa" ;; TODO: toggle session auto-save
;; h - HELP
;; h d - HELP > DESCRIBE
"hdv" 'describe-variable
"hdf" 'describe-function
"hdk" 'describe-key
"hda" 'counsel-describe-face))
Set up mnemonics menu which appears after a short delay on pressing the configured evil leader key in an ivy minibuffer. Map descriptions to commands defined by General.
(use-package which-key
:ensure t
:defer t
:init
(which-key-mode)
;; BUFFERS
(which-key-add-key-based-replacements ",b" "Buffers...")
;; SPLITS
(which-key-add-key-based-replacements ",s" "Splits...")
;; FILES
(which-key-add-key-based-replacements ",f" "Files...")
(which-key-add-key-based-replacements ",fc" "Edit Emacs configuration files")
(which-key-add-key-based-replacements ",fed" "Edit Emacs configuration files")
;; WINDOW
(which-key-add-key-based-replacements ",w" "Window...")
;; TOGGLES
(which-key-add-key-based-replacements ",t" "UI/Visual Toggles...")
(which-key-add-key-based-replacements ",tn" "Line Numbers (Toggle)")
(which-key-add-key-based-replacements ",th" "Highlight Current Line (Toggle)")
(which-key-add-key-based-replacements ",tw" "Word Wrap (Toggle)")
;; EXECUTE
(which-key-add-key-based-replacements ",e" "Execute...")
(which-key-add-key-based-replacements ",et" "Terminal (zsh)")
(which-key-add-key-based-replacements ",ec" "Command")
(which-key-add-key-based-replacements ",ee" "Evaluate Expression")
;; HELP
(which-key-add-key-based-replacements ",h" "Help...")
(which-key-add-key-based-replacements ",hd" "Describe..."))
Core evil package
(use-package evil
:ensure t
:init (evil-mode 1)
:config
(define-key evil-normal-state-map "," nil)
(evil-ex-define-cmd "W" "w")
(evil-ex-define-cmd "Wq" "wq")
(evil-ex-define-cmd "WQ" "wq")
(evil-ex-define-cmd "E" "e"))
Evil-escape lets us define an alternate key combo to enter normal mode. I like kj
.
(use-package evil-escape
:ensure t
:defer
:init (evil-escape-mode)
:config (setq-default evil-escape-key-sequence "kj"))
Evil-commentary allows us to comment things out using the key binds from vim-commentary, like gcc
for a line, gc
for a region, etc
(use-package evil-commentary
:ensure t
:defer t
:init (evil-commentary-mode))
Evil bindings for org mode
(use-package evil-org
:ensure t
:after org
:config (use-package org-bullets :ensure t))
This package adds a lot to emacs boot time, so we leave it out (org mode OFF todo status) for now. Options for powerline-default-separator are: alternate, arrow, arrow-fade, bar, box, brace, butt, chamfer, contour, curve, rounded, roundstub, slant, wave, zigzag, nil. A preview of each can be seen at http://spacemacs.org/doc/DOCUMENTATION.html#mode-line
(use-package powerline
:ensure t
:init (setq powerline-default-separator 'slant))
(use-package airline-themes
:ensure t
:config
(powerline-default-theme)
(load-theme 'airline-wombat t)
(force-mode-line-update)
(redraw-display))
- ivy: Minibuffer completion framework
- flx: Used to tweak the ivy fuzzy finding behavior. More details can be found at https://oremacs.com/2016/01/06/ivy-flx/
- ivy-rich: A nicer looking
ivy-switch-buffer
display
(use-package ivy
:ensure t
:defer t
:init
(use-package flx :ensure t :defer t)
(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(setq enable-recursive-minibuffers t)
(setq ivy-re-builders-alist '((t . ivy--regex-fuzzy)))
(setq ivy-initial-inputs-alist nil)
(use-package ivy-rich
:ensure t
:defer t
:init
(ivy-set-display-transformer 'ivy-switch-buffer 'ivy-rich-switch-buffer-transformer)
(setq ivy-rich-path-style 'abbrev)
(setq ivy-virtual-abbreviate 'full
ivy-rich-switch-buffer-align-virtual-buffer t)))
Counsel provides some additional key bindings to common commands using completing-read-function
, such as find-file
(which becomes counsel-find-file
)
(use-package counsel
:ensure t
:defer t)
Better isearch
(use-package swiper
:ensure t
:defer t)
Projectile lets us jump between files inside a git repository dir (or a dir with a .projectile
file at its root).
Also install counsel-projectile for the additional features when using projectile.
(use-package projectile
:ensure t
:defer t
:init (use-package counsel-projectile :ensure t))
Company provides nice code completion features. Comes with support for a few languages, and more can be installed.
(use-package company
:ensure t
:defer t
:config (setq company-idle-delay 0.3))
Highlight descriptive comment words like TODO
, HACK
etc with a more noticable text face
(use-package hl-todo
:ensure t
:defer t
:init (global-hl-todo-mode))
Show lines depicting indentation level. Slows down rendering quite a bit, so set to OFF for now.
(use-package highlight-indent-guides
:ensure t
:defer t
:init (setq highlight-indent-guides-method 'character))
Give scope delimiters rainbow colors to more easily determine where we are inside a deeply nested scope. Only use this for elisp at the moment, so only enable it when we load an elisp file via the emacs-lisp-mode-hook
.
This package doesn’t get enabled until a lisp file is loaded. (See Mode Hooks
section)
(use-package rainbow-delimiters
:defer t
:ensure t
:config (custom-set-faces
'(rainbow-delimiters-depth-1-face ((t (:foreground "dark orange"))))
'(rainbow-delimiters-depth-2-face ((t (:foreground "deep pink"))))
'(rainbow-delimiters-depth-3-face ((t (:foreground "chartreuse"))))
'(rainbow-delimiters-depth-4-face ((t (:foreground "deep sky blue"))))
'(rainbow-delimiters-depth-5-face ((t (:foreground "yellow"))))
'(rainbow-delimiters-depth-6-face ((t (:foreground "orchid"))))
'(rainbow-delimiters-depth-7-face ((t (:foreground "spring green"))))
'(rainbow-delimiters-depth-8-face ((t (:foreground "sienna1"))))))
(use-package rust-mode
:ensure t
:defer t)
(use-package rjsx-mode
:ensure t
:defer t
:config (setq js2-strict-missing-semi-warning nil))
(use-package fish-mode
:ensure t
:defer t)
Theming tools
(use-package autothemer
:ensure t
:defer t)
Replace the background of color codes with the color they represent
(use-package rainbow-mode
:ensure t
:defer t
:config
(setq
rainbow-html-colors nil
rainbow-ansi-colors nil
rainbow-latex-colors nil
rainbow-r-colors nil
rainbow-x-colors nil))
Enable company mode
(add-hook 'after-init-hook 'global-company-mode)
When we enter org mode, also load evil-org and org-bullets mode, and set up evil-org keys
(add-hook 'org-mode-hook 'evil-org-mode)
(add-hook 'evil-org-mode-hook
(lambda ()
(evil-org-set-key-theme '(textobjects insert navigation shift todo))
(org-bullets-mode 1)))
When we load an elisp file, turn on rainbow delims and set indent & evil shift widths to 2 spaces to match elisp-mode indent widths
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(rainbow-delimiters-mode)
(setq-local tab-width 2)
(setq-local evil-shift-width 2)))
This mode line is pretty minimal. It’s formatted as follows:
[evil mode state] [buffer status (modified, read only, etc)] [line number] [file name]
An example of what it might look like:
◗ insert ** 136 config.org
(setq
evil-normal-state-tag " normal"
evil-insert-state-tag " insert"
evil-visual-state-tag " visual"
mode-line-position '((line-number-mode ("%l")))
evil-mode-line-format '(before . mode-line-front-space))
(setq-default mode-line-format '("%e"
"◗"
mode-line-front-space
evil-mode-line-tag
mode-line-modified " "
mode-line-position " "
mode-line-buffer-identification
mode-line-end-spaces))
;; remove borders, set height etc
(custom-set-faces '(mode-line ((t (:box nil :overline nil :underline nil :weight normal :height 100))))
'(mode-line-inactive ((t (:box nil :overline nil :underline nil :weight normal :height 100)))))
Change color of line in between split windows
(set-face-foreground 'vertical-border "#363636")
Only one of these should be enabled at a time; the rest should have the OFF todo status so tangle ignores them.
(load-theme 'nimbostratus t)
(load-theme 'brown t)
(load-theme 'elegance t)
(load-theme 'gmacs t)
(load-theme 'gmacs-blue t)
(load-theme 'nord t)