My Emacs configuration directory
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.gitignore
README.org
config.org
init.el

README.org

Fausto’s Emacs config

This is my Emacs configuration file. It’s built using org-mode. GitHub can render org files and I’ve included a symlink README.org -> config.org so GitHub renders my config directly on the repository page.

This is a work-in-progress and I’m trying to adopt Emacs after a relatively long time using Neovim. Both are great projects and I’m having a difficult time abandoning either for the other, so you should check them out. I made a blog post about switching from Neovim to Emacs. Maybe you’ll find it interesting, as it narrates the creation of this file.

Personal information

(setq user-full-name "Fausto Núñez Alberro")
(setq user-mail-address "fausto.nunez@mailbox.org")

Package management

(load "package")
(add-to-list 'package-archives
             '("marmalade" . "http://marmalade-repo.org/packages/"))
(add-to-list 'package-archives
             '("melpa" . "http://melpa.milkbox.net/packages/") t)
(add-to-list 'package-archives
             '("org" . "http://orgmode.org/elpa/") t)
(package-initialize)

Server and client setup

EDITOR env variable and git config

With this EDITOR env variable, Emacs can lazy start the server and connect to it if it exists. My git configuration uses the global $EDITOR too.

export EDITOR='emacsclient -nw -c -a ""'

Important note about emacsclient not loading eyecandy

At the end of the config file we include some hooks to reload eyecandy that gets reset when connecting a client to the server.

Set up use-package

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(require 'use-package)
(setq use-package-always-ensure t)

Theme

(setq my-theme 'nord)
(use-package nord-theme
  :ensure t)
(load-theme my-theme t)

Powerline

(use-package powerline
  :ensure t
  :config (powerline-center-evil-theme))

Sane defaults

Interface

Turn off UI elements

(menu-bar-mode -1)
(scroll-bar-mode 0)
(tool-bar-mode 0)

Say y or n instead of yes or no

(defalias 'yes-or-no-p 'y-or-n-p)

Line numbers

(global-linum-mode 1)
(setq linum-format " %4d ")

hlinum-mode highlights the current line for linum

(use-package hlinum
  :ensure t)
(set-face-foreground 'linum-highlight-face "white")
(set-face-background 'linum-highlight-face nil)
(hlinum-activate)

Show line and column in the mode-line

(line-number-mode 1)
(column-number-mode 1)

Reduce startup screen noise

(setq inhibit-startup-message t)
(setq initial-scratch-message nil)

Disable “Text is read-only” warning

I find it annoying because it makes it hard to find the cursor after it appears. Solution found on this StackOverflow question.

(defun my-command-error-function (data context caller)
  "Ignore the buffer-read-only signal; pass the rest to the default handler."
  (when (not (eq (car data) 'text-read-only))
    (command-error-default-function data context caller)))

(setq command-error-function #'my-command-error-function)

Enable usage of xclip

(use-package xclip
  :config (xclip-mode 1))

Initialization

Emacs system customizations go on a separate file

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

Store all backup and autosave files in the tmp dir

(defconst emacs-tmp-dir (expand-file-name (format "emacs%d" (user-uid)) temporary-file-directory))
(setq backup-directory-alist
    `((".*" . ,emacs-tmp-dir)))
(setq auto-save-file-name-transforms
    `((".*" ,emacs-tmp-dir t)))
(setq auto-save-list-file-prefix
    emacs-tmp-dir)

Do not create lockfiles - I’m the only user

I’m not sure about the rationale behind this setting, but the auto-generated files are an annoyance, so they walk the plank.

(setq create-lockfiles nil)

Editing

Unset keybindings

Sorry Richard

(defun add-kbd (key) (kbd key))
(defvar keybindings-to-unset '("M-k" "M-j"))
(dolist (key (mapcar 'add-kbd keybindings-to-unset))
  (global-unset-key key))

Enable auto pairs

(electric-pair-mode 1)

Enable visual-line-mode for word wrap

(global-visual-line-mode t)

Standard indentation & no tabs

(setq standard-indent 2)
(setq-default indent-tabs-mode nil)

Drag stuff up and down

(use-package drag-stuff
  :ensure t)
(drag-stuff-global-mode 1)
(global-set-key (kbd "M-k") 'drag-stuff-up)
(global-set-key (kbd "M-j") 'drag-stuff-down)

Highlight matching parens with zero delay

(setq show-paren-delay 0)
(show-paren-mode 1)

Evil mode

Reset some defaults

Restore default tab functionality in org-mode

(setq evil-want-C-i-jump nil)

Restore default C-u functionality with Evil

(setq evil-want-C-u-scroll t)

Initialize Evil mode and friends

(use-package evil
:ensure t
:init
(setq evil-vsplit-window-right t)
:config
(evil-mode 1)

Leader

(use-package evil-leader
:ensure t
:config
(global-evil-leader-mode))

Surround mode

(use-package evil-surround
:ensure t
:config
(global-evil-surround-mode))

Org

(use-package evil-org
:ensure t
:after org
:config
(add-hook 'org-mode-hook 'evil-org-mode)
(add-hook 'evil-org-mode-hook
(lambda () (evil-org-set-key-theme))))

Indent textobject

(use-package evil-indent-textobject
:ensure t)
(use-package evil-commentary
:ensure t
:config
(evil-commentary-mode)))

Cursor changer

(use-package evil-terminal-cursor-changer
:ensure t
:init
(setq evil-motion-state-cursor 'box)  ;
(setq evil-visual-state-cursor 'box)  ;
(setq evil-normal-state-cursor 'box)  ;
(setq evil-insert-state-cursor 'bar)  ;
(setq evil-emacs-state-cursor  'hbar) ; _
:config
(evil-terminal-cursor-changer-activate))

Make escape quit most things

In Delete Selection mode, if the mark is active, just deactivate it then it takes a second `keyboard-quit` to abort the minibuffer.

(defun minibuffer-keyboard-quit ()
(interactive)
(if (and delete-selection-mode transient-mark-mode mark-active)
    (setq deactivate-mark  t)
(when (get-buffer "*Completions*") (delete-windows-on "*Completions*"))
(abort-recursive-edit)))

(define-key evil-visual-state-map [escape] 'keyboard-quit)
(define-key minibuffer-local-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-ns-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-completion-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-must-match-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-isearch-map [escape] 'minibuffer-keyboard-quit)

Use vim-navigator Emacs port for tmux panes

(use-package navigate
:ensure t)

This package enables seamless C-[hjkl] movement through tmux panes and Emacs windows. The following commands are required to be present in your tmux config:

bind -n C-h run "(tmux display-message -p '#{pane_current_command}' | grep -iqE '(^|\/)n?vim(diff)?$|emacs.*$' && tmux send-keys C-h) || tmux select-pane -L"
bind -n C-j run "(tmux display-message -p '#{pane_current_command}' | grep -iqE '(^|\/)n?vim(diff)?$|emacs.*$' && tmux send-keys C-j) || tmux select-pane -D"
bind -n C-k run "(tmux display-message -p '#{pane_current_command}' | grep -iqE '(^|\/)n?vim(diff)?$|emacs.*$' && tmux send-keys C-k) || tmux select-pane -U"
bind -n C-l run "(tmux display-message -p '#{pane_current_command}' | grep -iqE '(^|\/)n?vim(diff)?$|emacs.*$' && tmux send-keys C-l) || tmux select-pane -R"

Navigate visual lines with j and k

(define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line)
(define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line)

Swap 0 and ^, i.e. make 0 move the cursor back to the first non-whitespace character

(define-key evil-motion-state-map (kbd "0") 'evil-first-non-blank)
(define-key evil-motion-state-map (kbd "^") 'evil-beginning-of-line)

Evil Leader keybindings

(evil-leader/set-leader "<SPC>")
(evil-leader/set-key
  "f" 'helm-projectile-find-file
  "F" 'helm-projectile-ag
  "q" 'evil-quit
  "w" 'save-buffer
  "t" 'neotree-toggle
  "e" 'emojify-insert-emoji
  "g" 'magit)

Evil Leader org keybindings

(evil-leader/set-key-for-mode 'org-mode
  "A" 'org-archive-subtree
  "a" 'org-agenda
  "c" 'org-capture
  "d" 'org-deadline
  "l" 'evil-org-open-links
  "s" 'org-schedule
  "t" 'org-todo)

Org-mode

(setq org-startup-indented t
      org-ellipsis ""
      org-hide-leading-stars t
      org-src-fontify-natively t
      org-src-tab-acts-natively t
      org-pretty-entities t
      org-hide-emphasis-markers t
      org-agenda-block-separator ""
      org-fontify-whole-heading-line t
      org-fontify-done-headline t
      org-fontify-quote-and-verse-blocks t)

Explicitly use org to get the latest version

(use-package org
:ensure org-plus-contrib)

Pretty bullets

(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))

Make agenda show a full-sized screen always

Usually when I use the agenda I concentrate only on what’s inside and I don’t need to cross-reference with other windows.

(setq org-agenda-window-setup 'only-window)

Set the face for ellipses

Setting the foreground color to nil causes the ellipsis to take the color of its heading.

(custom-set-faces
 '(org-ellipsis ((t (:foreground nil)))))

GTD

For starters, I’ll be using a setup similar to the one specified in this post by Nicolas Petton.

Directories for GTD-related stuff

(setq gtd-base-path (expand-file-name "~/Projects/"))
(defun gtd-path (sub-path) (concat gtd-base-path sub-path))

(defvar inbox (gtd-path "inbox.org"))
(defvar projects (gtd-path "projects.org"))
(defvar someday (gtd-path "someday.org"))
(defvar tickler (gtd-path "tickler.org"))

Specify agenda-relevant files

(setq org-agenda-files (list inbox projects tickler))

Set targets for refiling

(setq org-refile-targets `((,projects :maxlevel . 3)
                           (,someday :level . 1)
                           (,tickler :maxlevel . 2)))

Org-capture templates

Enter insert mode when capturing

(add-hook 'org-capture-mode-hook 'evil-insert-state)

Define and register capture templates

(defvar inbox-capture-template "* %i%?")
(defvar todo-capture-template "* TODO %i%?")
(defvar tickler-capture-template "* %i%?")

(setq org-capture-templates `(("i" "Inbox" entry (file+headline inbox "Inbox") ,inbox-capture-template)
                              ("t" "Inbox [TODO]" entry (file+headline inbox "Inbox") ,todo-capture-template)
                              ("T" "Tickler" entry (file+headline tickler "Tickler") ,tickler-capture-template)))

Keywords for TODOs

Documentation about tracking state changes in TODOs can be found here.

(setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w!)" "|" "DONE(d!)" "CANCELLED(c!)")))

Log changes into the LOGBOOK and not as text inside the headling

(setq org-log-into-drawer 'LOGBOOK)

Color TODO keywords

Documentation on available colors can be found here.

(setq org-todo-keyword-faces
 '(("WAITING" . "grey20") ("CANCELED" . "darkred") ("NEXT" . "orange")))

Define tags, a.k.a. contexts

(setq org-tag-alist
  '(("work" . ?w)
    ("home" . ?h)
    ("computer" . ?c)
    ("phone" . ?p)
    ("brain" . ?b)
    ("out" . ?o)))

Helm & Projectile

(use-package helm
  :ensure t
  :config (helm-mode t))
(use-package projectile
  :ensure projectile
  :config
  (setq projectile-indexing-method 'git))
(use-package helm-projectile
  :ensure t)
(use-package helm-ag
  :ensure t)

Neotree

(use-package neotree :ensure t)

If you use evil-mode, by default some of evil key bindings conflict with neotree-mode keys.

(evil-define-key 'normal neotree-mode-map (kbd "TAB") 'neotree-enter)
(evil-define-key 'normal neotree-mode-map (kbd "SPC") 'neotree-quick-look)
(evil-define-key 'normal neotree-mode-map (kbd "q") 'neotree-hide)
(evil-define-key 'normal neotree-mode-map (kbd "RET") 'neotree-enter)

Auto-complete

(use-package company
  :ensure t
  :config
  (global-company-mode t)
  (setq company-global-modes '(not org-mode)))
(define-key company-mode-map (kbd "TAB") 'company-complete)

Emojify

(use-package emojify
  :ensure t
  :init
    (add-hook 'after-init-hook #'global-emojify-mode)
    (setq emojify-display-style 'unicode))

Rainbow delimiters

(use-package rainbow-delimiters
  :init
    (add-hook 'web-mode-hook #'rainbow-delimiters-mode)
    (add-hook 'rust-mode-hook #'rainbow-delimiters-mode))

Magit

(use-package magit
:ensure t
:config (setq magit-diff-refine-hunk 'all))

Evil-magit

(use-package evil-magit :ensure t)

GitHub pull requests

(use-package magit-gh-pulls
  :ensure t
  :config (add-hook 'magit-mode-hook 'turn-on-magit-gh-pulls))

Git gutters

(use-package diff-hl
  :ensure t
  :init
    (setq diff-hl-side 'right))
(global-diff-hl-mode 1)
(diff-hl-margin-mode 1)
(diff-hl-flydiff-mode 1)

Language-specific

Web languages

Web-mode

Initialize web-mode and recognize extensions. Also consider the possibility of JSX files with a .js extension istead of .jsx.

(use-package web-mode
  :ensure t
  :init
    (setq web-mode-content-types-alist '(("jsx" . "\\.tsx\\'")))
    (setq web-mode-content-types-alist '(("jsx" . "\\.js\\'")))
  :config
    (add-to-list 'auto-mode-alist '("\\.erb?\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.js[x]?\\'" . web-mode))
    (add-to-list 'auto-mode-alist '("\\.ts[x]?\\'" . web-mode)))

Add node_modules path

(use-package add-node-modules-path
  :ensure t)

Run prettier on save if web-mode

(eval-after-load 'web-mode
    '(progn
       (add-hook 'web-mode-hook #'add-node-modules-path)
       (add-hook 'web-mode-hook #'prettier-js-mode)))

Yaml-mode

(use-package yaml-mode :ensure t)

Haml-mode

(use-package haml-mode :ensure t)

SCSS-mode

(use-package scss-mode
  :mode (("\.scss\'" . scss-mode)))

TypeScript

(use-package tide
  :ensure t)
(defun setup-tide-mode ()
  (interactive)
  (tide-setup)
  (flycheck-mode +1)
  (setq flycheck-check-syntax-automatically '(save mode-enabled))
  (eldoc-mode +1)
  (tide-hl-identifier-mode +1)
  ;; company is an optional dependency. You have to
  ;; install it separately via package-install
  ;; `M-x package-install [ret] company`
  (company-mode +1))

;; aligns annotation to the right hand side
(setq company-tooltip-align-annotations t)

(setq tide-tsserver-executable "node_modules/.bin/tsserver")

(add-hook 'web-mode-hook #'setup-tide-mode)

GraphQL

(use-package graphql-mode
  :ensure t)

Rust

(use-package rust-mode
  :ensure t)

Markdown

(use-package markdown-mode
  :ensure t
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "multimarkdown"))

TeX

Highlight .tex.tera files as TeX (pape-rs)

(add-to-list 'auto-mode-alist '("\\.tex.tera\\'" . latex-mode))

Ruby

Disable adding magit comments in ruby-mode.

(setq ruby-insert-encoding-magic-comment nil)

Editorconfig

(use-package editorconfig
  :ensure t
  :config
  (editorconfig-mode 1))

Flycheck

Flycheck is used for on-the-fly linting. We set the indication mode to nil, because otherwise it conflicts with the line numbers. This disables the indicators in the fringe, but still shows the marked errors in the buffer. We set a zero delay to show the error message on the status bar below, and set a 0.2 second delay to avoid machine-gunning eslint.

(use-package flycheck
  :ensure t
  :init
(setq flycheck-indication-mode nil)
(setq flycheck-display-errors-delay nil)
(setq flycheck-idle-change-delay 2)
(global-flycheck-mode))

Add eslint to the available checkers.

(flycheck-add-mode 'javascript-eslint 'web-mode)

Make sure eslint does not try to --print-config after each buffer opens. Here’s a related Flycheck issue.

(with-eval-after-load 'flycheck
  (advice-add 'flycheck-eslint-config-exists-p :override (lambda() t)))

Make sure the eslint instance is the one local to the project

(defun my/use-eslint-from-node-modules ()
  (let* ((root (locate-dominating-file
                (or (buffer-file-name) default-directory)
                "node_modules"))
         (eslint (and root
                      (expand-file-name "node_modules/eslint/bin/eslint.js"
                                        root))))
    (when (and eslint (file-executable-p eslint))
      (setq-local flycheck-javascript-eslint-executable eslint))))

(add-hook 'flycheck-mode-hook #'my/use-eslint-from-node-modules)

Use <leader>[jk] to navigate to the next and previous error

(evil-leader/set-key
  "j" 'flycheck-next-error
  "k" 'flycheck-previous-error)

Emacsclient rice reloading

Make a list of things we want to reevaluate when connecting to the server

(defun reevaluate-eyecandy ()
    (load-theme my-theme t))

Reload the theme and eyecandy settings when a new frame opens if running a server

(if (daemonp)
    (add-hook 'after-make-frame-functions
        (lambda (frame)
            (select-frame frame)
            (reevaluate-eyecandy))))