Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1683 lines (1386 sloc) 49.9 KB

Emacs Configuration

This file bootstraps a complete Emacs environment to my liking.
If you’re new to Emacs check out spacemacs: http://spacemacs.org.

If you need some help, send me some electronic mail.

(setq user-full-name "Chad Stovern"
      user-mail-address "hello@chadstovern.com")

Table of contents

Package management

I use use-package for installing and managing package loading. This section contains configuration to bootstrap use-package.

Track emacs load time

I like to keep track of how long it takes emacs to load, so this will establish a starting timestamp.

(defconst emacs-start-time (current-time))

Emacs garbage collection threshold settings

Let’s Speed up operations by giving emacs garbage collection a more modern threshold.

(setq gc-cons-threshold 20000000) ; ~20MB

Load package support

(require 'package)
(setq package-enable-at-startup nil)

Define package repositories

(setq package-archives
      '(("gnu"          . "https://elpa.gnu.org/packages/")
        ("melpa"        . "https://melpa.org/packages/")
        ("melpa-stable" . "https://stable.melpa.org/packages/")))

Pin packages

The lastest packages are nice, but sometimes you want to stick with stable releases. Package will always install the lastest packages available unless a package is pinned to a repository.

(setq package-pinned-packages
      '(
        ;; (cider            . "melpa-stable")
        ;; (clj-refactor     . "melpa-stable")
        ;; (flycheck-clojure . "melpa-stable")
        ;; (use-package      . "melpa-stable")
        ))

Initialize package

(package-initialize)

Bootstrap use-package

use-package is great for lazy-loading packages efficiently.

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

(eval-when-compile
  (require 'use-package))

(setq use-package-always-ensure t)

(use-package diminish) ; no longer included with use-package by default

;;; example use-package usage
;; (use-package foo
;;   :ensure t
;;   :defer t
;;   :if (display-graphic-p)
;;   :commands (foo-all foo-super)
;;   :mode "\\.mode\\'"
;;   :diminish "f"
;;   :init
;;   (setq foo-variable t)
;;   (evil-leader/set-key "fs" #'foo-super)
;;   :config
;;   (foo-mode 1))

Show package load time

Here’s where we report how long it took to load all installed packages to the Messages buffer.

;;; show package load time
(let ((elapsed (float-time (time-subtract (current-time)
                                          emacs-start-time))))
  (message "Loaded packages in %.3fs" elapsed))

Package specific configuration

This section documents all loaded packages and their configuration.

emacs enhancements

(use-package restart-emacs
  :defer t)

evil-mode settings

Emacs is great and has many amazing features, modal editing is not one of them. I like to edit text at the speed of thought, so evil-mode gives me a complete Vim implementation within Emacs.

(use-package evil-leader
  :init
  (setq evil-leader/in-all-states 1)
  :config
  ;; load evil-leader first so it's initialized for evil-mode
  (global-evil-leader-mode)
  (evil-leader/set-leader ","))

(use-package evil
  :config
  (evil-mode 1)
  ;; set initial evil state for particular modes
  (cl-loop for (mode . state) in '((cider-test-report-mode . emacs)
                                   (deft-mode              . emacs)
                                   (dired-mode             . normal)
                                   (magit-mode             . normal)
                                   (magit-status-mode      . emacs)
                                   (magit-diff-mode        . normal)
                                   (magit-log-mode         . normal)
                                   (magit-process-mode     . normal)
                                   (magit-popup-mode       . emacs)
                                   ;; this allows vi-mode in shells
                                   (term-mode              . emacs))
           do (evil-set-initial-state mode state)))

(use-package evil-matchit
  :config
  (global-evil-matchit-mode 1))

(use-package evil-nerd-commenter
  :defer t
  :config
  (evilnc-default-hotkeys))

(use-package evil-surround
  :config
  (global-evil-surround-mode 1)
  ;; use non-spaced pairs when surrounding with an opening brace
  (evil-add-to-alist
   'evil-surround-pairs-alist
   ?\( '("(" . ")")
   ?\[ '("[" . "]")
   ?\{ '("{" . "}")
   ?\) '("( " . " )")
   ?\] '("[ " . " ]")
   ?\} '("{ " . " }")))

(use-package evil-visualstar
  :config
  (global-evil-visualstar-mode))

(use-package evil-search-highlight-persist
  :config
  (global-evil-search-highlight-persist t))

themes

(use-package solarized-theme
  :if (display-graphic-p)
  :config
  (load-theme 'solarized-light t)
  (load-theme 'solarized-dark t))

(use-package ample-theme
  :if (not (display-graphic-p))
  :config
  (load-theme 'ample t))

cycle themes

(use-package cycle-themes
  :if (display-graphic-p)
  :init
  (setq cycle-themes-theme-list
        '(solarized-light
          solarized-dark))
  :config
  (evil-leader/set-key "ct" #'cycle-themes))

terminal settings

Explain.

(use-package multi-term
  :defer t
  :init
  (setq multi-term-dedicated-window-height 30
        multi-term-program "/usr/local/bin/zsh")
  (add-hook 'term-mode-hook
            (lambda ()
              (setq term-buffer-maximum-size 10000)
              (setq yas-dont-activate t)
              (setq-local scroll-margin 0)
              (setq-local scroll-conservatively 0)
              (setq-local scroll-step 1)
              (setq-local evil-emacs-state-cursor 'bar)
              (setq-local global-hl-line-mode nil))))

window management

Explain.

;; prevent reloading persp-mode when reloading emacs config
(use-package perspective
  :if (not (bound-and-true-p persp-mode))
  :config
  (persp-mode))

(use-package buffer-move
  :defer t)

(use-package zoom-window
  :defer t
  :init
  (setq zoom-window-mode-line-color nil))

navigation

Explain.

(use-package ivy
  :diminish ivy-mode
  :init
  (setq ivy-use-virtual-buffers t
        ivy-height 15
        ivy-count-format "(%d/%d) "
        ivy-re-builders-alist '((t . ivy--regex-ignore-order)))
  :config
  (ivy-mode 1))

(use-package counsel
  :defer t)

(use-package counsel-projectile
  :defer t)

(use-package smex
  :defer t)

(use-package neotree
  :defer t
  :init
  (setq neo-smart-open t
        neo-autorefresh t
        neo-force-change-root t))

project management

Explain.

;;; project management
(use-package projectile
  :defer t
  :diminish projectile-mode
  :init
  (setq projectile-require-project-root nil)
  :config
  (setq projectile-globally-ignored-directories
        (cl-union projectile-globally-ignored-directories
                  '(".git"
                    ".cljs_rhino_repl"
                    ".meghanada"
                    ".svn"
                    "out"
                    "node_modules"
                    "repl"
                    "resources/public/js/compiled"
                    "target"
                    "venv")))
  (setq projectile-globally-ignored-files
        (cl-union projectile-globally-ignored-files
                  '(".DS_Store"
                    ".lein-repl-history"
                    "*.gz"
                    "*.pyc"
                    "*.png"
                    "*.jpg"
                    "*.jar"
                    "*.retry"
                    "*.svg"
                    "*.tar.gz"
                    "*.tgz"
                    "*.zip")))
  (setq projectile-globally-unignored-files
        (cl-union projectile-globally-unignored-files
                  '("profiles.clj")))
  (setq projectile-mode-line '(:eval (format " [%s] " (projectile-project-name))))
  (projectile-mode))

documentation

(use-package deft
  :commands (deft)
  :init
  ;;; keybinds pre load
  (evil-leader/set-key
    "nv" (lambda () (interactive) ; (nv)alt
           ;; ensure we can filter by typing every time we launch deft
           (deft)
           (evil-emacs-state))
    "nf" #'deft-find-file) ; (n)valt (f)ind file
  :config
  ;;; keybinds on load
  (evil-leader/set-key-for-mode 'deft-mode
    "nd" #'deft-delete-file     ; (n)valt (d)elete file
    "nr" #'deft-rename-file     ; (n)valt (r)ename file
    "nn" #'deft-new-file-named) ; (n)valt (n)ew file
  (defvar --user-home-dir (getenv "HOME"))
  (defvar --user-notes-dir (concat --user-home-dir "/Dropbox/notes"))
  (setq deft-directory --user-notes-dir
        deft-extensions '("txt" "md" "org")
        deft-default-extension "org"
        deft-use-filename-as-title t
        deft-use-filter-string-for-filename t))

version control

magit so awesome.

(use-package magit
  :defer t
  :init
  ;; ? will pop up the built-in hotkeys from status mode
  (evil-leader/set-key
    "gg"  #'magit-dispatch-popup
    "gst" #'magit-status
    "gd"  #'magit-diff-working-tree
    "gco" #'magit-checkout
    "gcm" #'magit-checkout
    "gcb" #'magit-branch-and-checkout
    "gl"  #'magit-pull-from-upstream
    "gaa" #'magit-stage-modified
    "grh" #'magit-reset-head
    "gca" #'magit-commit
    "gpu" #'magit-push-current-to-upstream
    "gpp" #'magit-push-current-to-pushremote
    "gt"  #'magit-tag
    "gpt" #'magit-push-tags)
  (add-hook 'magit-status-mode-hook (lambda () (setq truncate-lines nil)))
  ;; specific within magit-mode
  (evil-leader/set-key-for-mode 'text-mode
    "cc" 'with-editor-finish
    "cC" 'with-editor-cancel)
  :config
  (setq truncate-lines nil) ; wrap lines, don't truncate.
  ;; let's improve evil-mode compatability
  (define-key magit-status-mode-map (kbd "k") #'previous-line)
  (define-key magit-status-mode-map (kbd "K") 'magit-discard)
  (define-key magit-status-mode-map (kbd "j") #'next-line))

diff-hl pretty cool.

(use-package diff-hl
  :defer t
  :init
  (add-hook 'after-init-hook 'global-diff-hl-mode)
  (add-hook 'dired-mode-hook 'diff-hl-dired-mode)
  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
  :config
  (diff-hl-flydiff-mode t)
  (unless (display-graphic-p)
    (diff-hl-margin-mode t)))

code auto-completion settings

For code completeion I’ve moved from auto-complete to company-mode since it is under active development and has great support in many modes.

I am giving up doc popups in some modes by making this move, but am admitting that more often than not I’m not using auto-complete to read docs, and instead will ensure I have a universal keybind that calls a mode’s doc lookup.

(use-package company
  :diminish ""
  :config
  (global-company-mode)
  (company-tng-configure-default))

syntax checking

Explain.

(use-package flycheck
  :defer t
  :diminish flycheck-mode
  :init
  (add-hook 'after-init-hook #'global-flycheck-mode)
  :config
  ;; disable documentation related emacs lisp checker
  (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc clojure-cider-typed))
  (setq flycheck-mode-line-prefix ""))

paredit

Explain.

barf == push out of current sexp
slurp == pull into current sexp \ use Y not yy for yanking a line maintaining balanced parens \ use y% for yanking a s-expression

(use-package paredit
  :defer t
  :diminish ""
  :init
  (add-hook 'prog-mode-hook 'enable-paredit-mode)
  (add-hook 'org-mode-hook 'enable-paredit-mode)
  (add-hook 'yaml-mode-hook (lambda ()
                              (enable-paredit-mode)
                              (electric-pair-mode)))
  (evil-leader/set-key
    "W"   #'paredit-wrap-sexp
    "w("  #'paredit-wrap-sexp
    "w["  #'paredit-wrap-square
    "w{"  #'paredit-wrap-curly
    "w<"  #'paredit-wrap-angled
    "w\"" #'paredit-meta-doublequote
    ">>"  #'paredit-forward-barf-sexp
    "><"  #'paredit-forward-slurp-sexp
    "<<"  #'paredit-backward-barf-sexp
    "<>"  #'paredit-backward-slurp-sexp
    "D"   #'paredit-splice-sexp         ; del surrounding ()[]{}
    "rs"  #'raise-sexp                  ; (r)aise (s)exp
    "ss"  #'paredit-split-sexp          ; (s)plit (s)exp
    "js"  #'paredit-join-sexps          ; (j)oin (s)exps
    "xs"  #'kill-sexp                   ; (x)delete (s)exp
    "xS"  #'backward-kill-sexp          ; (x)delete (S)exp backward
    "pt"  #'evil-cleverparens-mode)     ; clever(p)arens (t)oggle
  :config
  ;; prevent paredit from adding a space before opening paren in certain modes
  (defun cs-mode-space-delimiter-p (endp delimiter)
    "Don't insert a space before delimiters in certain modes"
    (or
     (bound-and-true-p js2-mode)
     (bound-and-true-p js-mode)
     (bound-and-true-p javascript-mode)))
  (add-to-list 'paredit-space-for-delimiter-predicates #'cs-mode-space-delimiter-p))

(use-package evil-cleverparens
  :defer t
  :diminish ""
  :init
  ;; enabled in the following modes
  (add-hook 'clojure-mode-hook 'evil-cleverparens-mode)
  (add-hook 'emacs-lisp-mode-hook 'evil-cleverparens-mode)
  (add-hook 'lisp-mode-hook 'evil-cleverparens-mode)
  (add-hook 'lisp-interaction-mode-hook 'evil-cleverparens-mode)
  (add-hook 'org-mode-hook 'evil-cleverparens-mode)
  (add-hook 'web-mode-hook 'evil-cleverparens-mode)
  (add-hook 'yaml-mode-hook 'evil-cleverparens-mode)
  ;; disabled in the following modes
  (add-hook 'js2-mode-hook (lambda () (evil-cleverparens-mode -1)))
  ;;; keybinds pre load
  (evil-leader/set-key "pt" #'evil-cleverparens-mode) ; clever(p)arens (t)oggle
  :config
  ;; prevent evil-cleverparens from setting x and X to delete and splice,
  ;; preventing it from "breaking" paredit's default strict behavior.
  (evil-define-key 'normal evil-cleverparens-mode-map
    (kbd "x") #'paredit-forward-delete
    (kbd "X") #'paredit-backward-delete))

aggressive indentation

(use-package aggressive-indent
  :diminish ""
  :config
  (global-aggressive-indent-mode 1)
  (setq aggressive-indent-excluded-modes
        (cl-union aggressive-indent-excluded-modes
                  '(html-mode
                    terraform-mode))))

rainbow delimiters

Explain.

(use-package rainbow-delimiters
  :defer t
  :init
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
  (add-hook 'yaml-mode-hook #'rainbow-delimiters-mode))

column width enforcement

Explain.

(use-package column-enforce-mode
  :hook (clojure-mode
         js2-mode
         shell-script-mode
         json-mode)
  :diminish column-enforce-mode
  :init
  (setq column-enforce-column 100
        column-enforce-comments nil))

show end of buffer in editing modes (easily see empty lines)

(use-package vi-tilde-fringe
  :defer t
  :diminish vi-tilde-fringe-mode
  :init
  (add-hook 'prog-mode-hook #'vi-tilde-fringe-mode)
  (add-hook 'conf-space-mode-hook #'vi-tilde-fringe-mode)
  (add-hook 'markdown-mode-hook #'vi-tilde-fringe-mode)
  (add-hook 'org-mode-hook #'vi-tilde-fringe-mode)
  (add-hook 'yaml-mode-hook #'vi-tilde-fringe-mode))

emoji / unicode support 😎👍🏼🚀

Explain.

(use-package emojify
  :defer t
  :init
  (add-hook 'after-init-hook #'global-emojify-mode)
  :config
  (setq emojify-inhibit-major-modes
        (cl-union emojify-inhibit-major-modes
                  '(cider-mode
                    cider-repl-mode
                    cider-test-report-mode
                    shell-script-mode
                    sql-mode
                    term-mode
                    web-mode
                    yaml-mode))
        emojify-prog-contexts "comments"))

keybind discovery

Explain.

(use-package which-key
  :diminish which-key-mode
  :config
  (which-key-mode))

jump to text

Explain.

(use-package avy
  :defer t
  :init
  ;;; keybinds pre load
  (evil-leader/set-key
    "jl" #'avy-goto-line
    "jw" #'avy-goto-word-1
    "jc" #'avy-goto-char))

editorconfig: indentation and whitespace settings

Explain.

(use-package editorconfig
  :diminish ""
  :init
  (setq auto-mode-alist
        (cl-union auto-mode-alist
                  '(("\\.editorconfig\\'" . editorconfig-conf-mode)
                    ("\\editorconfig\\'"  . editorconfig-conf-mode))))
  :config
  (editorconfig-mode 1))

documentation search

(use-package dash-at-point
  :defer t)

code snippets

(use-package yasnippet
  :commands (yas-minor-mode yas-minor-mode-on)
  :init
  (add-hook 'prog-mode-hook #'yas-minor-mode)
  :config
  (yas-reload-all))

(use-package yasnippet-snippets
  :defer t)

clojure support

Explain.

(use-package clojure-mode
  :defer t
  :init
  (add-hook 'clojure-mode-hook (lambda ()
                                 (clj-refactor-mode 1)
                                 (yas-minor-mode)
                                 (add-to-list 'write-file-functions 'delete-trailing-whitespace)))
  :config
  ;;; keybinds on load
  (evil-leader/set-key-for-mode 'clojure-mode
    "ri"  #'cider-jack-in                       ; (r)epl (i)nitialize
    "rr"  #'cider-restart                       ; (r)epl (r)estart
    "rq"  #'cider-quit                          ; (r)epl (q)uit
    "rc"  #'cider-connect                       ; (r)epl (c)onnect
    "eb"  #'cider-eval-buffer                   ; (e)val (b)uffer
    "ef"  #'cider-eval-defun-at-point           ; (e)val de(f)un
    "es"  #'cider-eval-last-sexp                ; (e)val (s)-expression
    "rtn" #'cider-test-run-ns-tests             ; (r)un (t)ests (n)amespace
    "rtp" #'cider-test-run-project-tests        ; (r)un (t)ests (p)roject
    "rtl" #'cider-test-run-loaded-tests         ; (r)un (t)ests (l)oaded namespaces
    "rtf" #'cider-test-rerun-failed-tests       ; (r)erun (t)ests (f)ailed tests
    "rta" #'cider-auto-test-mode                ; (r)un (t)ests (a)utomatically
    "rb"  #'cider-switch-to-repl-buffer         ; (r)epl (b)uffer
    "ff"  #'cider-format-defun                  ; (f)ormat (f)orm
    "fr"  #'cider-format-region                 ; (f)ormat (r)egion
    "fb"  #'cider-format-buffer                 ; (f)ormat (b)uffer
    "ds"  #'cider-doc                           ; (d)oc (s)earch
    ;; add keybindings here to replace cljr-helm (,rf)
    )
  (evil-leader/set-key-for-mode 'clojurescript-mode
    "ri"  #'cider-jack-in-clojurescript         ; (r)epl (i)nitialize
    "rr"  #'cider-restart                       ; (r)epl (r)estart
    "rq"  #'cider-quit                          ; (r)epl (q)uit
    "rc"  #'cider-connect-clojurescript         ; (r)epl (c)onnect
    "eb"  #'cider-eval-buffer                   ; (e)val (b)uffer
    "ef"  #'cider-eval-defun-at-point           ; (e)val de(f)un
    "es"  #'cider-eval-last-sexp                ; (e)val (s)-expression
    "rtn" #'cider-test-run-ns-tests             ; (r)un (t)ests (n)amespace
    "rtp" #'cider-test-run-project-tests        ; (r)un (t)ests (p)roject
    "rtl" #'cider-test-run-loaded-tests         ; (r)un (t)ests (l)oaded namespaces
    "rtf" #'cider-test-rerun-failed-tests       ; (r)erun (t)ests (f)ailed tests
    "rta" #'cider-auto-test-mode                ; (r)un (t)ests (a)utomatically
    "rb"  #'cider-switch-to-repl-buffer         ; (r)epl (b)uffer
    "ff"  #'cider-format-defun                  ; (f)ormat (f)orm
    "fr"  #'cider-format-region                 ; (f)ormat (r)egion
    "fb"  #'cider-format-buffer                 ; (f)ormat (b)uffer
    "ds"  #'cider-doc                           ; (d)oc (s)earch
    ))
(use-package clojure-mode-extra-font-locking
  :defer t)
(use-package cider
  :defer t
  :init
  (setq cider-repl-pop-to-buffer-on-connect nil ; don't show repl buffer on launch
        cider-repl-display-in-current-window t  ; open repl buffer in current window
        cider-show-error-buffer nil             ; don't show error buffer automatically
        cider-auto-select-error-buffer nil      ; don't switch to error buffer on error
        cider-font-lock-dynamically t           ; font-lock as much as possible
        cider-save-file-on-load t               ; save file on prompt when evaling
        cider-repl-use-clojure-font-lock t      ; nicer repl output
        cider-repl-history-file (concat user-emacs-directory "cider-history")
        cider-repl-wrap-history t
        cider-repl-history-size 3000
        nrepl-hide-special-buffers t)
  (add-hook 'cider-mode-hook (lambda ()
                               (eldoc-mode)))
  (add-hook 'cider-repl-mode-hook (lambda ()
                                    (paredit-mode)))
  ;;cljs
  (setq cider-cljs-lein-repl
        "(do (require 'figwheel-sidecar.repl-api)
             (figwheel-sidecar.repl-api/start-figwheel!)
             (figwheel-sidecar.repl-api/cljs-repl))")
  :config
  (setq cider-mode-line '(:eval (format " [%s]" (cider--modeline-info))))
  (eval-after-load 'flycheck '(flycheck-clojure-setup))
  ;;; keybinds on load
  (evil-leader/set-key-for-mode 'cider-repl-mode
    "rr"  #'cider-restart                       ; (r)epl (r)estart
    "rq"  #'cider-quit                          ; (r)epl (q)uit
    "rl"  #'cider-switch-to-last-clojure-buffer ; (r)epl (l)ast buffer
    "rn"  #'cider-repl-set-ns                   ; (r)epl set (n)amespace
    "rp"  #'cider-repl-toggle-pretty-printing   ; (r)epl (p)retty print
    "rh"  #'cider-repl-history                  ; (r)epl (h)istory
    "cr" #'cider-repl-clear-buffer              ; (c)lear (r)epl
    )
  (bind-key "S-<return>" #'cider-repl-newline-and-indent cider-repl-mode-map)
  (define-key cider-test-report-mode-map (kbd "k") #'previous-line)
  (define-key cider-test-report-mode-map (kbd "j") #'next-line))
(use-package clj-refactor
  :defer t
  :diminish "")
(use-package flycheck-clojure
  :defer t)

web templates

Explain.

(use-package web-mode
  :mode ("\\.html\\'"
         "\\.html\\.erb\\'"
         "\\.php\\'"
         "\\.jinja\\'"
         "\\.j2\\'")
  :init
  ;; fix paren matching web-mode conflict for jinja-like templates
  (add-hook
   'web-mode-hook
   (lambda ()
     (setq-local electric-pair-inhibit-predicate
                 (lambda (c)
                   (if (char-equal c ?{) t (electric-pair-default-inhibit c))))))
  :config
  (setq web-mode-code-indent-offset 2
        web-mode-css-indent-offset 2
        web-mode-markup-indent-offset 2)
  (evil-leader/set-key-for-mode 'web-mode
    "fh" #'web-beautify-html))

stylesheets

(use-package css-mode
  :ensure nil
  :mode "\\.css\\'"
  :config
  (setq css-indent-offset 2)
  (electric-pair-mode 1))

(use-package scss-mode
  :ensure nil
  :mode ("\\.scss\\'"
         "\\.sass\\'")
  :config
  (setq css-indent-offset 2)
  (electric-pair-mode 1))

(use-package rainbow-mode
  :defer t
  :diminish rainbow-mode
  :init
  (add-hook 'css-mode-hook 'rainbow-mode)
  (add-hook 'scss-mode-hook 'rainbow-mode))

yaml support

Explain.

(use-package yaml-mode
  :mode "\\.yml\\'"
  :config
  (add-to-list 'write-file-functions 'delete-trailing-whitespace))

shell script support

shell-script-mode is a built-in mode, but i’m using the use-package stanza for consistency.

(use-package shell-script-mode
  :ensure nil
  :defer t
  :mode "\\.sh\\'"
  :init
  (setq sh-basic-offset 2
        sh-indentation  2)
  (setq auto-mode-alist
        (cl-union auto-mode-alist
                  '(("\\bash_profile\\'"  . shell-script-mode)
                    ("\\.bash_profile\\'" . shell-script-mode)
                    ("\\bashrc\\'"        . shell-script-mode)
                    ("\\.bashrc\\'"       . shell-script-mode)
                    ("\\inputrc\\'"       . shell-script-mode)
                    ("\\.inputrc\\'"      . shell-script-mode)
                    ("\\profile\\'"       . shell-script-mode)
                    ("\\.profile\\'"      . shell-script-mode)
                    ("\\sh_aliases\\'"    . shell-script-mode)
                    ("\\.sh_aliases\\'"   . shell-script-mode)
                    ("\\zprofile\\'"      . shell-script-mode)
                    ("\\.zprofile\\'"     . shell-script-mode)
                    ("\\zshrc\\'"         . shell-script-mode)
                    ("\\.zshrc\\'"        . shell-script-mode))))
  (electric-pair-mode 1))

ruby support

Explain.

(use-package inf-ruby
  :defer t
  :init
  (add-hook 'ruby-mode-hook 'inf-ruby-minor-mode))
(use-package robe
  :defer t
  :init
  (add-hook 'ruby-mode-hook 'robe-mode)
  :config
  (push 'company-robe company-backends))

python support

Explain.

(use-package elpy
  :defer t
  :init
  (add-hook 'python-mode-hook 'elpy-enable))

javascript support

;; a better javascript mode
(use-package js2-mode
  :mode "\\.js\\'"
  :config
  (setq js2-mode-show-parse-errors nil
        js2-mode-show-strict-warnings nil
        js2-basic-offset 2
        js-indent-level 2)
  (electric-pair-mode 1)
  ;;; keybinds on load
  (evil-leader/set-key-for-mode 'js2-mode
    "ri"  #'indium-connect               ; (r)epl (i)nitialize
    "rb"  #'indium-switch-to-repl-buffer ; (r)epl (b)uffer
    "eb"  #'indium-eval-buffer           ; (e)val (b)uffer
    "ef"  #'indium-eval-defun            ; (e)val de(f)un
    "es"  #'indium-eval-last-node        ; (e)val (s)-expression
    "ds"  #'tern-get-docs                ; (d)oc (s)search
    ))

;; javascript completion
(use-package tern
  :defer t
  :init
  (add-hook 'js2-mode-hook (lambda () (tern-mode t)))
  :config
  (setq tern-command (append tern-command '("--no-port-file"))))

(use-package company-tern
  :hook tern-mode
  :config
  (add-to-list 'company-backends 'company-tern))

;; javascript eval and repl
(use-package indium
  :defer t
  :init
  (add-hook 'js2-mode-hook (lambda ()
                             (require 'indium)
                             (indium-interaction-mode)))
  :config
  ;;; keybinds on load
  (bind-key "S-<return>" #'newline indium-repl-mode-map)
  (evil-leader/set-key-for-mode 'indium-repl-mode
    "cr"  #'indium-repl-clear-output    ; (c)lear (r)epl
    ))

java support

(use-package meghanada
  :defer t
  :init
  (add-hook 'java-mode-hook
            (lambda ()
              (meghanada-mode t)
              (setq c-basic-offset 2)
              (add-hook 'before-save-hook 'meghanada-code-beautify-before-save))))

other syntaxes

(use-package dockerfile-mode
  :mode "Dockerfile\\'")
(use-package lua-mode
  :defer t)
(use-package markdown-mode
  :mode ("\\.md\\'"
         "\\.txt\\'"
         "\\.taskpaper\\'")
  :config
  ;;; keybinds on load
  (evil-leader/set-key-for-mode 'markdown-mode
    "Mb" 'markdown-insert-bold
    "Me" 'markdown-insert-italic
    "Ms" 'markdown-insert-strike-through
    "Ml" 'markdown-insert-link
    "Mu" 'markdown-insert-uri
    "Mi" 'markdown-insert-image
    "Mh" 'markdown-insert-hr
    "Mf" 'markdown-insert-footnote
    "Mp" 'cs-marked-preview-file))
(use-package typo
  :defer t
  :diminish typo-mode
  :init
  (add-hook 'markdown-mode-hook 'typo-mode))
(use-package json-mode
  :defer t
  :config
  (setq js-indent-level 2))
(use-package salt-mode
  :defer t
  :diminish mmm-mode)
(use-package terraform-mode
  :defer t)
(use-package web-beautify
  :defer t)
(use-package atomic-chrome
  :defer t
  :init
  (evil-leader/set-key
    "as"  #'atomic-chrome-start-server ; (a)tomic (s)tart
    "aq"  #'atomic-chrome-stop-server) ; (a)tomic (q)uit
  :config
  (setq atomic-chrome-buffer-open-style 'full
        atomic-chrome-default-major-mode 'markdown-mode
        atomic-chrome-url-major-mode-alist '(("atlassian\\.net" . web-mode))))

rest client

(use-package restclient
  :defer t
  :config
  ;;; keybinds on load
  (evil-leader/set-key-for-mode 'restclient-mode
    ;; (e)val (f)unction - aka rest call
    "ef" #'restclient-http-send-current))

emacs-lisp

(use-package emacs-lisp
  :ensure nil
  :defer t
  :init
  ;;; keybinds pre load
  (evil-leader/set-key-for-mode 'emacs-lisp-mode
    "ri" 'ielm)
  (evil-leader/set-key-for-mode 'lisp-interaction-mode
    "ri" 'ielm))

org-mode

Explain.

(use-package org-mode
  :ensure nil
  :defer t
  :init
  (setq org-insert-mode-line-in-empty-file t) ; for .txt file compatability
  (setq org-ellipsis "")
  (setq org-startup-truncated nil) ; wrap lines, don't truncate.
  (setq org-src-fontify-natively t)
  (setq org-src-tab-acts-natively t)
  (setq org-src-window-setup 'current-window)
  ;;; exporting
  (setq org-export-with-smart-quotes t)
  (setq org-html-postamble nil)
  (add-hook 'org-mode-hook
            (lambda ()
              (require 'ox-md)
              (require 'ox-beamer)))

  ;;; gtd settings
  ;; (setq org-todo-keywords
  ;;       '((sequence "TODO" "IN-PROGRESS" "WAITING" "|" "DONE" "CANCELLED")))
  ;; (setq org-agenda-files '("~/Dropbox/org/"))
  ;; (setq org-agenda-text-search-extra-files '(agenda-archives))
  ;; (setq org-blank-before-new-entry (quote ((heading) (plain-list-item))))
  ;; (setq org-enforce-todo-dependencies t)
  ;; (setq org-log-done (quote time))
  ;; (setq org-log-redeadline (quote time))
  ;; (setq org-log-reschedule (quote time))

  ;;; keybinds pre load
  (evil-leader/set-key-for-mode 'org-mode
    "es" 'org-edit-special
    "ri" 'ielm)
  (evil-leader/set-key-for-mode 'emacs-lisp-mode
    "cc" 'org-edit-src-exit
    "cC" 'org-edit-src-abort))

(use-package org-bullets
  :defer t
  :init
  (add-hook 'org-mode-hook
            (lambda ()
              (org-bullets-mode t))))

spell checking

(setq ispell-program-name "aspell")

User functions

This section documents any custom functions and their purpose.

command aliases

Explain: yes and no prompts

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

evil escape

Explain: Make escape act like C-g in evil-mode

(defun cs-minibuffer-keyboard-quit ()
  "Abort recursive edit.
In Delete Selection mode, if the mark is active, just deactivate it;
then it takes a second \\[keyboard-quit] to abort the minibuffer."
  (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)))

electric return

Explain: Electric return functionality

(defvar cs-electrify-return-match
  "[\]}\)]"
  "If this regexp matches the text after the cursor, do an \"electric\" return.")

(defun cs-electrify-return-if-match (arg)
  "When text after cursor and ARG match, open and indent an empty line.
Do this between the cursor and the text.  Then move the cursor to the new line."
  (interactive "P")
  (let ((case-fold-search nil))
    (if (looking-at cs-electrify-return-match)
        (save-excursion (newline-and-indent)))
    (newline arg)
    (indent-according-to-mode)))

open dired at current location

(defun cs-open-dired-at-current-dir ()
  (interactive)
  (dired (file-name-directory (buffer-file-name (current-buffer)))))

preview file with marked

(defun cs-marked-preview-file ()
  "use Marked 2 to preview the current file"
  (interactive)
  (shell-command
   (format "open -a 'Marked 2.app' %s"
           (shell-quote-argument (buffer-file-name)))))

General configuration

This section is where all general emacs configuration lives.

path fix for macOS gui mode

(when (memq window-system '(mac ns))
  (setenv "PATH" (shell-command-to-string "source ~/.profile && printf $PATH"))
  (setq exec-path (cl-union (split-string (shell-command-to-string "source ~/.profile && printf $PATH") ":") exec-path)))

macOS keybinding fix

For iTerm: Go to Preferences > Profiles > (your profile) > Keys > Left option key acts as: > choose +Esc

startup behavior

(setq inhibit-startup-message t)

don’t save customizations to init file

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

set default starting directory (avoid launching projectile at HOME or src root)

(defvar --user-home-dir (getenv "HOME"))
(defvar --user-src-dir (concat --user-home-dir "/src"))
(defvar --user-scratch-dir (concat --user-src-dir "/scratch"))
(unless (file-exists-p --user-scratch-dir)
  (make-directory --user-scratch-dir t))
(when (or (string= default-directory "~/")
          (string= default-directory --user-home-dir)
          (string= default-directory --user-src-dir))
  (setq default-directory --user-scratch-dir))

default to utf8

(prefer-coding-system 'utf-8)

pretty symbols

(global-prettify-symbols-mode)

always end with a newline

(setq require-final-newline t)

word wrapping

(setq-default word-wrap t)
(visual-line-mode 1)

move through camelCaseWords

(global-subword-mode 1)

highlight matching parens

(setq show-paren-style 'parenthesis
      show-paren-delay 0)
(show-paren-mode 1)

font settings

(set-face-attribute 'default nil :family "Menlo" :height 140 :weight 'normal)

turn off menu-bar, tool-bar, and scroll-bar

(menu-bar-mode -1)
(when (display-graphic-p)
  (tool-bar-mode -1)
  (scroll-bar-mode -1))

hi-light current line

(global-hl-line-mode)

smoother scrolling

(setq scroll-margin 8
      scroll-conservatively 100
      scroll-step 1)

slower smoother trackpad scrolling

(setq mouse-wheel-scroll-amount '(1 ((shift) . 1) ((control) . nil)))
(setq mouse-wheel-progressive-speed nil)

fix ls warning when dired launches on macOS

(when (eq system-type 'darwin)
  (require 'ls-lisp)
  (setq ls-lisp-use-insert-directory-program nil))

initial widow size and position (`left . -1` is to get close to right align)

(setq initial-frame-alist '((top . 0) (left . -1) (width . 120) (height . 80)))

prevent verticle split automatically on larger displays

(setq split-height-threshold 160)

tab settings

(setq indent-tabs-mode nil)

show trailing whitespace in buffers

(add-hook 'prog-mode-hook (lambda () (setq show-trailing-whitespace t)))
(add-hook 'yaml-mode-hook (lambda () (setq show-trailing-whitespace t)))
(add-hook 'org-mode-hook (lambda () (setq show-trailing-whitespace t)))
(add-hook 'markdown-mode-hook (lambda () (setq show-trailing-whitespace nil)))

remember cursor position in buffers

(if (version< emacs-version "25.1")
    (lambda ()
      (require 'saveplace)
      (setq-default save-place t))
  (save-place-mode 1))

store auto-save and backup files in ~/.emacs.d/backups/

(defvar --backup-dir (concat user-emacs-directory "backups"))
(unless (file-exists-p --backup-dir)
  (make-directory --backup-dir t))
(setq backup-directory-alist `((".*" . ,--backup-dir)))
(setq auto-save-file-name-transforms `((".*" ,--backup-dir t)))
(setq backup-by-copying t
      delete-old-versions t
      kept-new-versions 6
      kept-old-versions 2
      version-control t
      auto-save-default t)

version control

(setq vc-follow-symlinks t)

declutter the modeline

For built in packages, installed packages use the :diminish keyword via use-package.

(diminish 'auto-revert-mode "")
(diminish 'subword-mode)
(diminish 'undo-tree-mode)

custom mode-line configuration

Packages like spaceline are great, but can add a lot of overhead, and also limit you. I’ve set up my own custom modeline that provides a format that looks like this:

N [*]filename [project] ᚠbranch (modes) Err U: line:col 29% [main]

(setq x-underline-at-descent-line t) ; better modeline underline alignment
(setq-default
 mode-line-format
 (list
  '(:eval
    (propertize
     evil-mode-line-tag
     ;; let's give our evil/vim state a nice visual cue by adding some color
     'face (cond
            ((string= evil-mode-line-tag " <E> ") '(:background "#6c71c4" :foreground "#eee8d5"))
            ((string= evil-mode-line-tag " <N> ") '(:background "#859900" :foreground "#eee8d5"))
            ((string= evil-mode-line-tag " <I> ") '(:background "#268bd2" :foreground "#eee8d5"))
            ((string= evil-mode-line-tag " <V> ") '(:background "#cb4b16" :foreground "#eee8d5"))
            ((string= evil-mode-line-tag " <R> ") '(:background "#dc322f" :foreground "#eee8d5"))
            ;; ((string= evil-mode-line-tag " <O> ") '(:background "#d33682" :foreground "#eee8d5"))
            )))
  "[%*]" mode-line-buffer-identification
  '(projectile-mode-line projectile-mode-line) " "
  '(vc-mode (:eval (concat "" (substring vc-mode 5 nil)))) " "
  mode-line-modes
  '(flycheck-mode-line flycheck-mode-line) " "
  "%Z "
  "%l:%c "
  "%p "
  '(:eval (persp-mode-line))))

open urls in default browser

(when (display-graphic-p)
  (setq browse-url-browser-function 'browse-url-default-macosx-browser))

General global key bindings

This section contains general global emacs key bindings. Mode specific key bindings (global and mode local) are within each use-package stanza.

emacs settings

;;; (e)dit (e)macs user init file
(defvar --emacs-config (concat user-emacs-directory "emacs-config.org"))
(evil-leader/set-key "ee" (lambda () (interactive) (find-file --emacs-config)))

;;; (s)ource (e)macs user init file
(evil-leader/set-key "se" (lambda () (interactive) (load-file user-init-file)))

;;; (r)estart (e)macs
(evil-leader/set-key "re" #'restart-emacs)

package management

;;; package management
(evil-leader/set-key
  "Pl" #'package-list-packages             ; (P)ackage (l)ist
  "Pu" #'package-list-packages             ; (P)ackage (u)pgrade
  "Pi" #'package-install                   ; (P)ackage (i)nstall
  "PI" #'package-install-selected-packages ; (P)ackage (I)nstall full list
  "Pd" #'package-delete                    ; (P)ackage (d)elete
  "Pa" #'package-autoremove)               ; (P)ackage (a)utoremove

evil-mode

;;; evil emacs conflicts
(define-key evil-normal-state-map (kbd "C-u") #'evil-scroll-up)
(define-key evil-visual-state-map (kbd "C-u") #'evil-scroll-up)

;;; enter evil-emacs-state for interacting with certain buffers
(evil-leader/set-key "em" #'evil-emacs-state)

;;; evil vim inconsistencies
(define-key evil-visual-state-map (kbd "x") #'evil-delete)

;;; evil escape (use escape for C-g in evil-mode)
(define-key evil-normal-state-map           [escape] #'keyboard-quit)
(define-key evil-visual-state-map           [escape] #'keyboard-quit)
(define-key minibuffer-local-map            [escape] #'cs-minibuffer-keyboard-quit)
(define-key minibuffer-local-ns-map         [escape] #'cs-minibuffer-keyboard-quit)
(define-key minibuffer-local-completion-map [escape] #'cs-minibuffer-keyboard-quit)
(define-key minibuffer-local-must-match-map [escape] #'cs-minibuffer-keyboard-quit)
(define-key minibuffer-local-isearch-map    [escape] #'cs-minibuffer-keyboard-quit)
(define-key ivy-minibuffer-map              [escape] #'cs-minibuffer-keyboard-quit)
(global-set-key                             [escape] #'evil-exit-emacs-state)

;;; evil line movement tweaks
(define-key evil-motion-state-map "j" #'evil-next-visual-line)
(define-key evil-motion-state-map "k" #'evil-previous-visual-line)
(define-key evil-visual-state-map "j" #'evil-next-visual-line)
(define-key evil-visual-state-map "k" #'evil-previous-visual-line)

window control

;;; cycle themes
;;(evil-leader/set-key "ct" #'cycle-themes)

;;; full screen toggle
(global-set-key (kbd "s-<return>") #'toggle-frame-fullscreen) ; s = super (⌘ on mac)

;;; hide others with macOS default keyboard shortcut of `⌥⌘H`
(global-set-key (kbd "M-s-˙") #'ns-do-hide-others)
;; the `˙` in the above keybind is due to opt h producing that char

;;; window splitting
(global-set-key (kbd "C--")  #'evil-window-split)
(global-set-key (kbd "C-\\") #'evil-window-vsplit)
(global-set-key (kbd "C-=")  #'balance-windows)

;;; resize windows
(global-set-key (kbd "s-<right>") #'evil-window-increase-width)
(global-set-key (kbd "s-<left>")  #'evil-window-decrease-width)
(global-set-key (kbd "s-<up>")    #'evil-window-increase-height)
(global-set-key (kbd "s-<down>")  #'evil-window-decrease-height)

;;; move to next / prev window
;; force override bindings from all modes
(bind-keys*
 ("C-k" . evil-window-up)
 ("C-j" . evil-window-down)
 ("C-h" . evil-window-left)
 ("C-l" . evil-window-right))

;;; move/swap buffers between windows
(global-set-key (kbd "C-S-K") #'buf-move-up)
(global-set-key (kbd "C-S-J") #'buf-move-down)
(global-set-key (kbd "C-S-H") #'buf-move-left)
(global-set-key (kbd "C-S-L") #'buf-move-right)

;;; window controls
;;; press `C-w` to see built-in evil-mode window controls
(evil-leader/set-key
  "wc" #'evil-window-delete    ; (w)indow (c)lose
  "wm" #'delete-other-windows) ; (w)indow (m)ain
(define-key evil-motion-state-map (kbd "C-z") #'zoom-window-zoom)

;;; clear / recenter screen
(evil-leader/set-key
  "cs" #'recenter-top-bottom) ; (c)lear (s)creen

;;; text scale
(global-set-key (kbd "s-+") #'text-scale-increase)
(global-set-key (kbd "s--") #'text-scale-decrease)
(global-set-key (kbd "s-=") #'text-scale-adjust)

project navigation

;;; bookmarks
(evil-leader/set-key
  "ml" #'bookmark-jump
  "mj" #'bookmark-jump
  "ms" #'bookmark-set
  "md" #'bookmark-delete)

;;; set a nicer M-x
(global-set-key (kbd "M-x") #'counsel-M-x)

;;; allow for jk menu nav
(define-key ivy-minibuffer-map (kbd "s-j") #'ivy-next-line)
(define-key ivy-minibuffer-map (kbd "s-k") #'ivy-previous-line)

;;; projects / files / buffers
(evil-leader/set-key
  "Ff" #'find-file                     ; (F)ind (f)ile
  "t"  #'counsel-projectile-find-file  ; emulate command-(t)
  "b"  #'ivy-switch-buffer             ; switch to (b)uffer
  "kb" #'kill-buffer                   ; (k)ill (b)uffer
  "gf" #'counsel-projectile-ag)        ; (g)rep in (f)iles

;;; neotree
(evil-leader/set-key "nt" #'neotree-toggle)
(evil-define-key 'normal neotree-mode-map (kbd "TAB") 'neotree-enter)
(evil-define-key 'normal neotree-mode-map (kbd "SPC") 'neotree-enter)
(evil-define-key 'normal neotree-mode-map (kbd "q") 'neotree-hide)
(evil-define-key 'normal neotree-mode-map (kbd "RET") 'neotree-enter)

;;; workspaces
(evil-leader/set-key
  "ps" 'persp-switch
  "pk" 'persp-remove-buffer
  "pc" 'persp-kill
  "pr" 'persp-rename
  "pa" 'persp-add-buffer
  "pA" 'persp-set-buffer
  "pi" 'persp-import
  "pn" 'persp-next
  "pp" 'persp-prev)

;;; dired navigation
;; g to update dired buffer info
;; s to toggle between sort by name and by date/time
;; + create dir
;; for creating, deleting, renaming, just toggle shell visor, then update dired
(evil-leader/set-key "Fd" #'cs-open-dired-at-current-dir) ; (F)ind via (d)ired

terminal

;;; toggle/open shell
(evil-leader/set-key
  "sv" (lambda () (interactive)         ; toggle (s)hell (v)isor
         (multi-term-dedicated-toggle)
         (multi-term-dedicated-select))
  "sn" (lambda () (interactive)         ; toggle (s)hell (n)ew
         ;; update buffer name setting dynamically for each perspective
         (setq-default multi-term-buffer-name (concat "term-" (persp-name (persp-curr))))
         (multi-term)))

;;; multi term keybind setup - full vi-mode in zsh within emacs
;; don't leave emacs mode when pressing esc, pass through for vim compatability
(evil-define-key 'emacs  term-raw-map [escape]           #'term-send-esc)
;; super-esc toggle emacs and evil modes
(evil-define-key 'emacs  term-raw-map (kbd "s-<escape>") #'evil-exit-emacs-state)
(evil-define-key 'normal term-raw-map (kbd "s-<escape>") #'evil-emacs-state)
;; never use evil insert mode in term-mode, prefer our shell's vi-mode
(evil-define-key 'normal term-raw-map "i"                #'evil-emacs-state)
;; trample "C-c" emacs bind so it behaves like a normal shell interrupt
(evil-define-key 'normal term-raw-map (kbd "C-c")        #'term-send-raw)
(evil-define-key 'emacs  term-raw-map (kbd "C-c")        #'term-send-raw)
;; fix pasting into terminal without needing line-mode
(evil-define-key 'emacs  term-raw-map (kbd "s-v")        #'term-paste)
;; vi-mode and vim compatability
(evil-define-key 'emacs  term-raw-map (kbd "C-v")        #'term-send-raw)
(evil-define-key 'emacs  term-raw-map (kbd "C-r")        #'term-send-raw)

electric return

be explicit about where to enable “electric return”, as some modes have their own.

(dolist (hook
         '(cider-mode-hook
           clojure-mode-hook
           emacs-lisp-mode-hook
           lisp-interaction-mode-hook
           lisp-mode-hook
           org-mode-hook
           python-mode-hook
           ruby-mode-hook
           yaml-mode-hook))
  (add-hook hook
            (lambda ()
              (local-set-key (kbd "RET") #'cs-electrify-return-if-match))))

remove search highlight

(evil-leader/set-key "/" #'evil-search-highlight-persist-remove-all)

delete trailing whitespace

(evil-leader/set-key "dw" #'delete-trailing-whitespace)

toggle truncate-lines

(evil-leader/set-key "lt" #'toggle-truncate-lines) ; (l)ine truncate (t)oggle

commenting

(evil-leader/set-key
  "cl" #'evilnc-comment-or-uncomment-lines
  "cp" #'evilnc-comment-or-uncomment-paragraphs)

yank / kill history

(evil-leader/set-key "kr" #'counsel-yank-pop)

doc search

(evil-leader/set-key
  "dd" #'dash-at-point      ; (d)ash (d)oc
  "dv" #'describe-variable) ; (d)escribe (v)ariable

line number toggle

(evil-leader/set-key "nn" #'linum-mode)

column enforcement toggle

(evil-leader/set-key "ce" #'column-enforce-mode)

flycheck

(evil-leader/set-key
  "fcb" 'flycheck-buffer         ; (f)ly(c)heck (b)uffer
  "fcn" 'flycheck-next-error     ; (f)ly(c)heck (n)ext
  "fcp" 'flycheck-previous-error ; (f)ly(c)heck (p)revious
  "fcl" 'flycheck-list-errors)   ; (f)ly(c)heck (l)ist

report emacs total load time

(let ((elapsed (float-time (time-subtract (current-time)
                                          emacs-start-time))))
  (message "Loaded emacs in %.3fs" elapsed))

suppress flycheck warnings in emacs config

;; Local Variables:
;; byte-compile-warnings: (not free-vars)
;; End: