Skip to content

Latest commit

 

History

History
1698 lines (1464 loc) · 57.4 KB

init.org

File metadata and controls

1698 lines (1464 loc) · 57.4 KB

Package repositories

Get packages from ELPA, MELPA and org by default. This can be customized (e.g. to use a local checkout of spacemacs-elpa-mirror by customizing package-archives).

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

GPG is not necessarily installed on Windows.

(when (eq system-type 'nt)
  (setq package-check-signature nil))

From here-on out use-package loads packages. We almost always want to :ensure packages are present.

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

(require 'use-package)

(setq use-package-always-ensure t)

Encoding

UTF-8 everywhere, but don’t mess with line-endings.

(if (eq system-type 'windows-nt)
    (prefer-coding-system 'utf-8-dos)
  (prefer-coding-system 'utf-8-unix))

It’s also handy to programmatically convert files, mainly when Windows has UTF-16’d something.

(defun convert-file-encoding (file coding-system)
  "Convert FILE to CODING-SYSTEM without visiting it."
  (with-temp-buffer
    (insert-file-contents file)
    (let ((coding-system-for-write coding-system))
      (write-region nil nil file))))

Init file

It’s convenient to be able to get at this quickly.

(defun init-file ()
  "Return the path to my init file."
  (if (eq system-type 'windows-nt)
      "C:/Cygwin64/home/chris.bowdon/init.org"
    "~/init.org"))

(defun find-init-file ()
  (interactive)
  (find-file (init-file)))

(global-set-key (kbd "C-x .") 'find-init-file)

I prefer not to clutter .emacs with customizations, so create an init-custom.el.

(setq custom-file "~/init-custom.el")
(unless (file-exists-p custom-file)
  (with-temp-buffer
    (write-file custom-file)))
(load-file custom-file)

It’s also convenient to allow loading some custom scripts per host, e.g. for work or home-specific setup.

(defcustom init-file-extra-files nil
  "Extra files to load with init.org"
  :type '(repeat (file :must-match t)))

(defun init-file-load-extra-files ()
  "Loads each .el or .org in init-file-extra-files"
  (interactive)
  (dolist (extra-file init-file-extra-files)
    (message "Extra init file: %s" extra-file)
    (pcase (file-name-extension extra-file)
      ("el" (load-file extra-file))
      ("org" (org-babel-load-file extra-file))
      (_ (message "I have no idea what this is: %s" extra-file)))))

(init-file-load-extra-files)

Modules

I’ve hacked together a module system so that most of these sections are opt-in. At the moment there is a macro defmodule that defines this, but it would be nice if I can modify org-babel-tangle so that it turns each section into a module (and automatically turns the text into module documentation).

(defgroup init-modules nil
  "The init modules customization group.")

(defvar init-modules-defs-plist nil
  "The module definitions plist.")

(defun init-module-load (name)
  "Load the module NAME."
  (let ((exec-module (plist-get init-modules-defs-plist name)))
    (message "Init-module-load %s: %s" name exec-module)
    (when exec-module
      (message "Found def for %s" name)
      (funcall exec-module))))

(defun init-module-set (name value)
  "Enable/disable the module NAME by setting boolean VALUE."
  (set-default name value)
  (when value
    (message "%s set: evaluating" name)
    (init-module-load name)))

(defmacro defmodule (name doc &rest body)
  "Define an init module called NAME that executes BODY when enabled."
  (message "Defining module for %s" name)
  (let ((module-name (intern (format "init-module-%s" name))))
    ;; Declare var if not already
    (when (not (boundp module-name))
      (custom-declare-variable module-name nil
                               doc
                               :group 'init-modules
                               :set 'init-module-set
                               :tag (symbol-name name)
                               :type '(boolean)))
    `(progn
       ;; Stash the definition in the symbol's plist
       (setq init-modules-defs-plist
             (plist-put init-modules-defs-plist
                        (quote ,module-name)
                        (lambda ()
                          (progn
                            (message "Evaluating definition of %s" ,module-name)
                            ,@body))))
       ;; If set, evaluate it
       (when ,module-name
         (message "Evaluating %s on first def" (quote ,module-name))
         ,@body))))

Evil

Packages

Join the dark side.

(use-package evil
  :demand
  :config (progn
            (evil-mode 1)
            (define-key evil-normal-state-map (kbd ";") 'evil-ex)
            (define-key evil-visual-state-map (kbd ";") 'evil-ex)))

(use-package evil-leader
  :demand
  :config (progn
            (setq evil-leader/in-all-states t)
            (global-evil-leader-mode)))

(setq-default indent-tabs-mode nil)

AND BECOME A GOD!

(use-package god-mode :demand)
(use-package evil-god-state :demand)

;; Try to remove C-w
(global-unset-key (kbd "C-w"))
(define-key global-map (kbd "C-w") nil)

(define-key global-map (kbd "C-<escape>") 'evil-normal-state)
(define-key global-map (kbd "C-~") 'evil-normal-state)
(define-key global-map (kbd "M-<escape>") 'god-mode)
(define-key global-map (kbd "C-M-<escape>") 'god-local-mode)
(define-key evil-normal-state-map (kbd "SPC") 'evil-execute-in-god-state)
(define-key evil-visual-state-map (kbd "SPC") 'evil-execute-in-god-state)

This evil/god combination should be the most ergonomic possible without manually remapping most bindings.

I’ve bound C-<escape> and C-~ in particular because in term-mode it’s possible to get stuck in evil-god-state if you hit some combinations (I prefer C-<escape> but Windows intercepts this).

Basic god-mode usage:

  • abcC-a C-b C-c
  • ab cC-a C-b c
  • gfM-f
  • GfC-M-f
  • 12fM-12 C-f
  • gf..M-f M-f M-f
  • ucoC-u C-c C-o

It’s nice to have a guide…

(use-package which-key :demand)
(which-key-mode)
(which-key-enable-god-mode-support)

State modes

I prefer not to clobber the default bindings with evil bindings in some modes.

(add-to-list 'evil-emacs-state-modes 'dired-mode)
(add-to-list 'evil-emacs-state-modes 'proced-mode)
(add-to-list 'evil-emacs-state-modes 'eshell-mode)
(add-to-list 'evil-emacs-state-modes 'term-mode)
(add-to-list 'evil-emacs-state-modes 'diff-mode)
(add-to-list 'evil-emacs-state-modes 'vc-mode)

Keyboard mapping

Use tilde for leader key until I’m sure it’s no longer necessary.

(evil-leader/set-leader "`")
(evil-leader/set-key
  "/" 'comment-region
  "\\\\" 'uncomment-region
  "." 'find-init-file
  "W" 'toggle-truncate-lines
  "X" 'delete-trailing-whitespace)

Set CAPSLOCK as another ESC. In GNOME 3 and MacOS use the system settings GUI. In X, use the function below. In Windows… screw around with the registry?

(defun set-x-caps-escape ()
  "Set CAPSLOCK to be another ESC key in X."
  (interactive)
  (shell-command "setxkbmap -option caps:escape"))

For future reference, find rules/base.lst in the xkb directory to learn all the options. (See the man page.)

(global-unset-key (kbd "C-f"))
(global-unset-key (kbd "C-b"))

(define-key evil-normal-state-map (kbd "C-f") nil)
(define-key evil-visual-state-map (kbd "C-f") nil)
(define-key evil-normal-state-map (kbd "C-b") nil)
(define-key evil-visual-state-map (kbd "C-b") nil)

(define-key global-map (kbd "C-f C-s") 'save-buffer)
(define-key global-map (kbd "C-f C-f") 'ido-find-file)
(define-key global-map (kbd "C-f C-p") 'projectile-find-file)
(define-key global-map (kbd "C-b") 'switch-to-buffer)

(define-key evil-normal-state-map (kbd "C-k") 'kill-buffer)
(define-key evil-visual-state-map (kbd "C-k") 'kill-buffer)
(define-key evil-god-state-map (kbd "C-k") 'kill-buffer)

(define-key evil-normal-state-map (kbd "C-w") 'evil-window-map)
(define-key evil-visual-state-map (kbd "C-w") 'evil-window-map)
(define-key evil-god-state-map (kbd "C-w") 'evil-window-map)

(define-key global-map (kbd "C-S-X") 'delete-trailing-whitespace)
(define-key global-map (kbd "C-S-W") 'toggle-truncate-lines)

(define-key global-map (kbd "C-S-P") 'proced)

REPL shortcuts

(defvar repl-map (make-sparse-keymap))

(define-key repl-map (kbd "C-c") 'cider-jack-in)
(define-key repl-map (kbd "C-d") 'run-dig)
(define-key repl-map (kbd "C-e") 'eshell)
(define-key repl-map (kbd "C-s") 'shell)
(define-key repl-map (kbd "C-i") 'ielm)
(define-key repl-map (kbd "C-p") 'run-python)
(define-key repl-map (kbd "C-j") 'js-comint)
(define-key repl-map (kbd "C-q C-m") 'sql-mysql)
(define-key repl-map (kbd "C-q C-p") 'sql-postgres)
(define-key repl-map (kbd "C-q C-s") 'sql-sqlite)
(define-key repl-map (kbd "C-t") 'term)

(evil-leader/set-key
  "rd" 'run-dig ;; not exactly a REPL, but fits nonetheless
  "rf" 'run-fsharp
  "ri" 'ielm
  "rp" 'run-python
  "rj" 'js-comint
  "rr" 'run-ruby
  "R" 'repl-map)

(global-unset-key (kbd "C-r"))
(define-key global-map (kbd "C-r") nil)

(define-key global-map (kbd "C-r") repl-map)
(define-key evil-god-state-map (kbd "C-r") repl-map)

Mode shortcuts

Sometimes the right mode isn’t picked up by buffer name/shebang.

(evil-leader/set-key
  "md" 'markdown-mode
  "me" 'ensime ;; ensime is slow, don't like to autoload
  "mh" 'html-mode
  "mj" 'javascript-mode
  "mnc" 'column-number-mode
  "mnl" 'linum-mode
  "mo" 'org-mode
  "ms" 'shell-script-mode
  "mx" 'nxml-mode)

Completion

Company mode is my preferred auto-completion package because it seems to be best supported by the languages I use.

(use-package company
  :demand
  :config (progn
            (add-hook 'prog-mode-hook #'(lambda () (company-mode)))
            (setq company-show-numbers t)))

Trying out Ido for Emacs command completion. Good for buffers so far.

(ido-mode)

Programming

General

By default every text editor should display line and column number, and not wrap text.

(setq-default truncate-lines t)
(add-hook 'prog-mode-hook 'column-number-mode)

(defun enable-line-numbers ()
  (setq display-line-numbers t))

(if (< emacs-major-version 26)
    (add-hook 'prog-mode-hook 'linum-mode)
  (add-hook 'prog-mode-hook 'enable-line-numbers))

Electric indent interferes with lots of modes’ own indenting, so disable it.

(setq electric-indent-inhibit t)

popup-imenu is a nice document-overview tool.

(use-package popup-imenu)
(evil-leader/set-key "p" 'popup-imenu)

(define-key evil-normal-state-map (kbd "C-p") nil)
(define-key evil-visual-state-map (kbd "C-p") nil)
(define-key evil-god-state-map (kbd "C-p") nil)
(define-key evil-normal-state-map (kbd "C-p") 'popup-imenu)
(define-key evil-visual-state-map (kbd "C-p") 'popup-imenu)
(define-key evil-god-state-map (kbd "C-p") 'popup-imenu)

I’m slowly warming up to Projectile.

(use-package projectile
  :config (define-key global-map (kbd "C-x C-p") 'projectile-command-map))
(projectile-mode)

Emacs Lisp

Nobody likes dynamic binding by default.

(setq lexical-binding t)

Helper functions

It’s often handy to know if we’re using *nix.

(defun is-nix ()
  (or (equal system-type 'gnu)
      (equal system-type 'gnu/linux)
      (equal system-type 'gnu/kfreebsd)
      (equal system-type 'darwin)))

A handy timer macro.

(defmacro time-sexp (body)
  "Run the BODY s-expression(s) and print the time between start and finish."
  `(let ((t0 (float-time))
         (result (progn ,body))
         (t1 (float-time)))
     (with-current-buffer (pop-to-buffer "*time-sexp*" nil t)
       (goto-char (point-max))
       (insert
        (format "time-sexp: %s\n" (quote ,body))
        (format "--> %fs\n" (- t1 t0))))
     result))

Threading macros

Emacs 25 brought some mod cons to the language that are worth shimming in older Emacsen.

(defun emacs-version-less-than (major-number)
  (and
   (string-match "\\([0-9]+\\)\\.[0-9]+.*" emacs-version)
   (> major-number (string-to-number (match-string 1 emacs-version)))))

(when (emacs-version-less-than 25)

  (use-package seq )

  (defmacro internal--thread-argument (first? &rest forms)
    "Internal implementation for `thread-first' and `thread-last'.
When Argument FIRST? is non-nil argument is threaded first, else
last.  FORMS are the expressions to be threaded."
    (pcase forms
      (`(,x (,f . ,args) . ,rest)
       `(internal--thread-argument
         ,first? ,(if first? `(,f ,x ,@args) `(,f ,@args ,x)) ,@rest))
      (`(,x ,f . ,rest) `(internal--thread-argument ,first? (,f ,x) ,@rest))
      (_ (car forms))))

  (defmacro thread-first (&rest forms)
    "Thread FORMS elements as the first argument of their succesor.
Example:
    (thread-first
      5
      (+ 20)
      (/ 25)
      -
      (+ 40))
Is equivalent to:
    (+ (- (/ (+ 5 20) 25)) 40)
Note how the single `-' got converted into a list before
threading."
    (declare (indent 1)
             (debug (form &rest [&or symbolp (sexp &rest form)])))
    `(internal--thread-argument t ,@forms))

  (defmacro thread-last (&rest forms)
    "Thread FORMS elements as the last argument of their succesor.
Example:
    (thread-last
      5
      (+ 20)
      (/ 25)
      -
      (+ 40))
Is equivalent to:
    (+ 40 (- (/ 25 (+ 20 5))))
Note how the single `-' got converted into a list before
threading."
    (declare (indent 1) (debug thread-first))
    `(internal--thread-argument nil ,@forms)))

Comfort

Make ad-hoc lisping more comfortable.

(use-package paredit)
(use-package flycheck)

(add-hook 'emacs-lisp-mode-hook 'prettify-symbols-mode)
(add-hook 'emacs-lisp-mode-hook 'paredit-mode)
(add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
(add-hook 'emacs-lisp-mode-hook 'flycheck-mode)

(evil-leader/set-key "xE" 'eval-buffer)

;; Create some vimmish bindings for paredit functions
(evil-define-key 'normal 'evil-normal-state-map
  (kbd ",dd") 'paredit-kill
  (kbd ",dw") 'paredit-forward-kill-word
  (kbd ",dB") 'paredit-backward-kill-word
  (kbd ",l") 'paredit-forward
  (kbd ",h") 'paredit-backward
  (kbd ",k") 'paredit-backward-up
  (kbd ",j") 'paredit-forward-down
  (kbd ",J") 'paredit-wrap-round
  (kbd ",K") 'paredit-splice-sexp)

(defun change-brackets (type)
  "Change |(..) to |[..]. | is point position."
  (cond ((eq type 'square) (paredit-open-square 1))
        ((eq type 'curly) (paredit-open-curly 1))
        ((eq type 'angled) (paredit-open-angled 1))
        ((eq type 'round) (paredit-open-round 1))
        ((eq type 'bracket) (paredit-open-bracket 1))
        ((eq type 'parenthesis (paredit-open-parenthesis 1))))
  (right-char 1)
  (paredit-splice-sexp)
  (left-char 1))

(evil-define-key 'normal 'evil-normal-state-map
  (kbd ",[") #'(lambda () (interactive) (change-brackets 'square))
  (kbd ",(") #'(lambda () (interactive) (change-brackets 'round))
  (kbd ",{") #'(lambda () (interactive) (change-brackets 'curly)))

Package development

(defmodule package-development
  "Init module to install helpers for Elisp package development."
  (use-package flycheck-package)
  (eval-after-load 'flycheck '(flycheck-package-setup))
  (use-package package-lint))

REST

It’s nice to wrap the built-in url functions into a higher-level API.

(defvar rest--default-headers '("DNT" . "1")
  "The default headers include a DNT.")

(defun rest-- (method url body &rest headers)
  "Make an HTTP METHOD request to URL with BODY and optional HEADERS.
Shows the result in a new buffer."
  (let ((url-request-method (upcase method))
        (url-request-extra-headers headers)
        (url-request-data body))
    (message (format "%s %s" (upcase method) url))
    (url-retrieve url
                  (lambda (status)
                    (when status
                      (message (format "RESPONSE STATUS %s <-- %s" status url)))
                    (rename-buffer "*rest response*" t)
                    (switch-to-buffer (current-buffer))))))

(cl-defun rest (&key url (method "GET") (body nil) (headers rest--default-headers))
  "Make an HTTP METHOD request to URL with BODY and HEADERS.
Defaults to a GET request with no body and default headers (see `rest--default-headers').
Shows the result in a new buffer."
  (rest-- method url body headers))

(cl-defun rest-delete (url &key (headers rest--default-headers))
  "Make an HTTP DELETE request to URL with optional HEADERS. Shows the result in a new buffer."
  (rest-- "delete" url nil headers))

(cl-defun rest-get (url &key (headers rest--default-headers))
  "Make an HTTP GET request to URL with optional HEADERS. Shows the result in a new buffer."
  (rest-- "get" url nil headers))

(cl-defun rest-head (url &key (headers rest--default-headers))
  "Make an HTTP HEAD request to URL with optional HEADERS. Shows the result in a new buffer."
  (rest-- "head" url nil headers))

(cl-defun rest-options (url &key (headers rest--default-headers))
  "Make an HTTP OPTIONS request to URL with optional HEADERS. Shows the result in a new buffer."
  (rest-- "options" url nil headers))

(cl-defun rest-post (url &key (body nil) (headers rest--default-headers))
  "Make an HTTP POST request to URL with BODY and optional HEADERS. Shows the result in a new buffer."
  (rest-- "post" url body headers))

(cl-defun rest-put (url &key (body nil) (headers rest--default-headers))
  "Make an HTTP PUT request to URL with BODY and optional HEADERS. Shows the result in a new buffer."
  (rest-- "put" url body headers))

F#

Ensure there’s a symlink to the F# bin dir!

(defmodule fsharp
  "Init module for F# development. Installs fsharp-mode and sets up path to interpreter."
  (use-package fsharp-mode
    :mode "\\.fsx?\\'"
    :config
    (progn
      (when (equal system-type 'windows-nt)
        (let ((fsDir "C:\\Program Files (x86)\\Microsoft SDKs\\F#\\3.1\\Framework\\v4.0"))
          ;; Doesn't necessarily work - just set your path!
          (setenv "PATH"
                  (concat (getenv "PATH")
                          (format ";%s" fsDir)))
          (setq exec-path
                (append exec-path '(fsDir)))))
      (setq inferior-fsharp-program
            (cond ((equal system-type 'windows-nt) "Fsi.exe")
                  ((equal system-type 'cygwin) "/home/chris.bowdon/fs/Fsi.exe"))))))

Python

Using eglot for interacting with an LSP backend. The Python support requires python-language-server from PIP. For minimal stress, just install ‘[all]’.

(defmodule python3
  "Init module for Python 3 development."
  (use-package eglot)
  (use-package pyvenv)

  (defun venv ()
    (interactive)
    (pyvenv-activate "./venv")
    (message (format "Activated venv: python is now %s"
                     (shell-command-to-string "which python"))))

  (defvar poetry--venv-dir
    (if (eq system-type 'darwin)
                       "~/Library/Caches/pypoetry/virtualenvs/"
                      "~/.cache/pypoetry/virtualenvs/"))

  (defun poetry--list ()
    (thread-first (format "ls %s" poetry--venv-dir)
                  (shell-command-to-string)
                  (split-string "[\n\r]+" t)))

  (defun poetry-activate (project-name)
    (interactive (list (completing-read "Project: " (poetry--list))))
    (pyvenv-activate (string-join (list poetry--venv-dir project-name) "")))

;; Doesn't recognise micromamba
  ;; (use-package conda
  ;;   :config (progn
  ;;             (conda-env-initialize-interactive-shells)
  ;;             (conda-env-initialize-eshell)))

  (defvar conda--venv-dir "~/micromamba/envs/")

  (defun conda--list ()
    (thread-first (format "ls %s" conda--venv-dir)
                  (shell-command-to-string)
                  (split-string "[\n\r]+" t)))

  (defun conda-activate (project-name)
    (interactive (list (completing-read "Project: " (conda--list))))
    (pyvenv-activate (string-join (list conda--venv-dir project-name) "")))

  (add-hook 'python-mode-hook #'(lambda () (define-key python-mode-map (kbd "C-c C-v") 'venv))))

JavaScript

Though I remain suspicious of Facebook, React has brought a nice functional taste to JS. I’ve configured rjsx-mode for all JS and JSX files since in various contexts (e.g. React Native) JSX files are still given a .js extension. It’s built on js2-mode, which is very good for general JS programming anyway.

prettier (and any standardised formatting program, e.g. yapf for Python) should be mandatory. Not having to give a damn about formatting is a big weight off the mind.

I’m using eglot for completion/checking support, which requires installing the javascript-typescript-langserver, i.e. yarn global add javascript-typescript-langserver. Make sure ~/.yarn/bin is on your path.

(defmodule javascript
  "Init module for JavaScript (including React/JSX)."
  ;; (use-package prettier-js
  ;;   :config (progn
  ;;             (define-key js2-mode-map (kbd "C-c C-p") 'prettier-js)))
  ;;
  (use-package typescript-mode :pin melpa-stable :mode "\\.ts")
  (use-package js-comint
    :pin melpa-stable
    :config (progn
              (define-key js-mode-map (kbd "C-c C-c") 'js-comint-send-buffer)
              (define-key js-mode-map (kbd "C-c C-e") 'js-comint-send-last-sexp)
              (define-key js-mode-map (kbd "C-c C-s") 'js-comint-send-region)))
  (use-package rjsx-mode :mode "\\.[jt]sx?$")
  (add-hook 'js2-mode-hook 'prettier-js-mode))

Clojure

(defmodule clojure
  "Init module for Clojure. A work in progress."
  (use-package clojure-mode
    :pin melpa-stable
    :config (progn
              (add-hook 'clojure-mode-hook 'paredit-mode)
              (add-to-list 'auto-mode-alist '("\\.boot$" . clojure-mode))
              (add-to-list 'magic-mode-alist '(".* boot" . clojure-mode))))

  (use-package company
    :demand
    :config (progn
              (add-hook 'clojure-mode-hook #'company-mode)
              (setq-default company-show-numbers t)))

  (defun cider-repl-require-helpers ()
    "Insert require expressions into a Clojure REPL to save some typing."
    (interactive)
    (when (eq major-mode 'clojure-mode)
      (cider-switch-to-repl-buffer))
    (insert "(require '[clojure.repl :refer :all] '[clojure.pprint :refer :all])")
    (cider-repl-return))

  (use-package cider
    :pin melpa-stable
    :config (progn
              (setq cider-auto-jump-to-error nil)
              (dolist (m '(cider-stacktrace-mode
                           cider-test-report-mode
                           cider-classpath-mode
                           cider-inspector-mode
                           cider-browse-ns-mode
                           cider-browse-spec-mode
                           cider-browse-spec-example-mode
                           cider-browse-spec-view-mode))
                (add-to-list 'evil-emacs-state-modes m))
              ;; Removed for bad behaviour of locking REPL:
              ;; (add-hook 'cider-repl-mode-hook 'paredit-mode)
              (add-hook 'cider-repl-mode-hook 'company-mode)
              (dolist (m (list cider-mode-map cider-repl-mode-map))
                (define-key m (kbd "C-c C-n") 'cider-repl-set-ns)
                (define-key m (kbd "C-c C-i") 'cider-inspect)
                (define-key m (kbd "C-c C-f") 'cider-find-var)
                (define-key m (kbd "C-c C-w") 'cider-ns-refresh)))))

Clojure (inf-clojure)

CIDER is amazing, but inf-clojure is more lightweight and can use socket REPLs, which is very handy. This module doesn’t interfere with CIDER’s keybindings unless inf-clojure-enable is called.

The below setup includes autocompletion with compliment and namespace refreshing with tools.namespace.

(defmodule clojure-inf
  "Init module for Clojure based on inf-clojure. Even more a work in progress."

  (use-package clojure-mode
    :pin melpa-stable
    :interpreter "clojure"
    :config (progn
              (add-hook 'clojure-mode-hook 'paredit-mode)
              (add-to-list 'auto-mode-alist '("\\.boot$" . clojure-mode))
              (add-to-list 'magic-mode-alist '(".* boot" . clojure-mode))
              (define-key clojure-mode-map (kbd "C-c C-!") 'inf-clojure-socket-repl)
              (define-key clojure-mode-map (kbd "C-c C-@") 'inf-clojure-connect-socket-repl)
              (define-key clojure-mode-map (kbd "C-c C-#") 'inf-clojure-connect)))

  (use-package company
    :demand
    :config (progn
              (add-hook 'clojure-mode-hook #'company-mode)
              (setq-default company-show-numbers t)))

  (use-package inf-clojure
    :pin melpa-stable
    :config (progn
              ;; Compliment is an active and seemingly pretty good completions library
              (setq-default inf-clojure-completion-form "(compliment.core/completions \"%s\")")
              (add-hook 'inf-clojure-mode-hook #'eldoc-mode)
              ;; TODO completions tend to make the prompt show up all the time - missing input?
              (add-hook 'inf-clojure-mode-hook #'eldoc-mode)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-d") 'inf-clojure-show-var-documentation)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-e") 'inf-clojure-eval-last-sexp)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-w") 'inf-clojure-ns-refresh)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-k") 'inf-clojure-eval-buffer)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-?") 'inf-clojure-apropos)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-l") 'inf-clojure-clear-repl-buffer)
              (define-key inf-clojure-minor-mode-map (kbd "C-c C-n") 'inf-clojure-set-ns)))

  (defun inf-clojure-enable ()
    "Must be called before working with inf-clojure."
    (interactive)
    (add-hook 'clojure-mode-hook #'inf-clojure-minor-mode)
    (add-hook 'clojure-mode-hook #'company-mode))

  (defun inf-clojure-disable ()
    (interactive)
    (remove-hook 'clojure-mode-hook #'inf-clojure-minor-mode))

  (defun inf-clojure-socket-repl ()
    (interactive)
    (inf-clojure-enable)
    (let* ((deps "{:deps {compliment {:mvn/version \"RELEASE\"} cider/orchard {:mvn/version \"RELEASE\"}}}")
           (reqs "(require '[compliment.core] '[clojure.tools.namespace.repl] '[orchard.info])")
           (opts "-Dclojure.server.repl={:port,50505,:accept,clojure.core.server/repl}")
           (cmd (format "clojure -J'%s' -Sdeps '%s' --eval \"%s\" --repl" opts deps reqs))
           (dir default-directory)
           (buf (get-buffer-create "*Clojure socket REPL*")))
      (message "Starting REPL in %s:\n%s" dir cmd)
      (with-current-buffer buf
        (setq-local default-directory dir)
        (start-process-shell-command "Clojure socket REPL" buf cmd))))

  (defun inf-clojure-connect-socket-repl ()
    (interactive)
    (inf-clojure-connect "localhost" 50505))

  (defun inf-clojure-kill-process ()
    (interactive)
    (kill-process "Clojure socket REPL"))

  (defun inf-clojure-ns-refresh ()
    (interactive)
    (inf-clojure-eval-string "(clojure.tools.namespace.repl/refresh)"))

  (defun inf-clojure-find-var ()
    (interactive)
    (let* ((form (format "(orchard.info/info (ns-name *ns*) (quote %s))" (symbol-at-point)))
           (info (inf-clojure-eval-string form)))
      ;; parse the info to get file and line, column
      ;; find-file and cursor to line and column
      ))

  (use-package clj-refactor
    :config (add-hook 'clojure-mode-hook
                      (lambda ()
                        (clj-refactor-mode 1)
                        (cljr-add-keybindings-with-prefix "C-c C-o"))))

  (use-package align-cljlet
            ;; Stick in clojure-mode-map because it's handy even for CIDER
    :config (define-key clojure-mode-map (kbd "C-c C-a") 'align-cljlet))

  (use-package paredit
    :config (add-hook 'clojure-mode-hook 'paredit-mode))

  (use-package yasnippet
    :config (progn
              (yas-global-mode 1)
              (add-to-list 'yas-snippet-dirs "~/.emacs.d/snippets")
              (yas-load-directory "~/.emacs.d/snippets")))

  (use-package clojure-snippets))

Java

eglot is the best Java mode I’ve tried so far (beating meghanada, eclim, ensime and lsp-java). Make sure to download the Eclipse JDT language server and put it in ~/.emacs.d/eclipse.jdt.ls/server/.

maven-test-mode is a convenient way to run individual tests rather than passing detailed arguments to mvn.

(defmodule java
  "Init module for Java."
  (use-package maven-test-mode))

Scala

Like there’s any other choice but ENSIME?

(defmodule scala
  "Fucking Scala init module"
  (use-package ensime)
  (setq ensime-startup-notification nil
        ensime-startup-snapshot-notification nil))

Ansible

Ansible is my configuration management solution of choice. The Emacs support isn’t bad.

(defmodule ansible
  "Init module for Ansible."
  (use-package ansible)
  (use-package ansible-doc)
  (use-package company)
  (use-package company-ansible))

Terraform

(defmodule terraform
  "Init module for Terraform."
  (use-package terraform-mode
    :mode "\\.tf|.hcl\\'"))

Ruby

I ended up coding some Ruby for use with Chef and Capistrano.

(defmodule ruby
  "Init module for Ruby. Uses Robe."
  (use-package inf-ruby)

  (add-hook 'after-init-hook 'inf-ruby-switch-setup)

  (use-package robe)

  (eval-after-load 'company
    '(push 'company-robe company-backends))

  (evil-leader/set-key-for-mode 'ruby-mode
    "r" 'robe-start
    "j" 'robe-jump
    "c" 'company-robe))

Rust

Rust support is pretty basic at this stage. Autocompletion comes from a separate program, racer that reads the stdlib source code.

(defmodule rust
  "Init module for Rust. Uses racer for autocompletion."
  (use-package rust-mode
    :config (add-hook 'rust-mode-hook '(lambda ()
                                         (racer-activate)
                                         (racer-turn-on-eldoc)
                                         (add-hook 'flycheck-mode-hook #'flycheck-rust-setup))))
  (use-package company
    :config (setq company-idle-delay 0.2
                  company-minimum-prefix-length 1))

  (use-package company-racer
    :config (set (make-local-variable 'company-backends)
                 '(company-racer)))

  ;; racer reads the rust src code to suggest competions
  ;; git clone https://github.com/rust-lang/rust.git ~/.rust
  ;; git clone https://github.com/phildawes/racer.git ~/.racer
  ;; cd ~/.racer
  ;; cargo build --release
  (use-package racer


    :config (setq racer-cmd (expand-file-name "~/.racer/target/release/racer")
                  racer-rust-src-path (expand-file-name "~/.rust/src")))

  (use-package flycheck  )
  (use-package flycheck-rust  ))

PHP

(defmodule php
  "Init module for php. Uses web-mode for Cake templates."
  (use-package php-mode :mode "\\.php$")
  (use-package web-mode :mode "\\.ctp$"))

Docker

(defmodule docker
  "Init module for working with Docker and Compose."
  (use-package docker)
  (use-package docker-compose-mode)
  (use-package dockerfile-mode :mode "Dockerfile$"))

SQL

(defmodule sql
  "Init module for SQL. Sets evil leader shortcuts for interpreters."
  (evil-leader/set-key
    "s q p" 'sql-postgres
    "s q s" 'sql-sqlite))

Data languages

CSV

(defmodule csv
  "Init module to add CSV mode."
  (use-package csv-mode))

JSON

(defmodule json
  "Init module to support JSON syntax highlighting/navigation/formatting."
  (use-package json-mode :mode "\\.json")
  (use-package json-navigator)
  (use-package json-reformat)

  (evil-leader/set-key-for-mode 'json-mode
    "jnp" 'json-navigator-navigate-after-point
    "jnr" 'json-navigator-navigate-region
    "jr" 'json-reformat-region
    "jpr" 'json-pretty-print
    "jpb" 'json-pretty-print-buffer))

XML

(defmodule xml
  "Init module for XML. Adds helper functions and tag folding."
  (defun split-xml-lines ()
    (interactive)
    ;; TODO use looking-at etc. because replace-regexp is interactive
    (replace-regexp "> *<" ">\n<"))

  (require 'hideshow)
  (require 'sgml-mode)
  (require 'nxml-mode)

  (add-to-list 'hs-special-modes-alist
               '(nxml-mode
                 "<!--\\|<[^/>]*[^/]>"
                 "-->\\|</[^/>]*[^/]>"

                 "<!--"
                 sgml-skip-tag-forward
                 nil))

  (add-hook 'nxml-mode-hook 'hs-minor-mode)

  (evil-leader/set-key-for-mode 'nxml-mode
    "h" 'hs-toggle-hiding))

YAML

(defmodule yaml
  "Init module for YAML support."
  (use-package yaml-mode :mode "\\.ya?ml|\\.dvc$"))

GraphQL

(defmodule graphql
  "Init module to add GraphQL support."
  (use-package graphql-mode :mode "\\.sdl$|\\.graphql$"))

ElasticSearch

(defmodule elasticsearch
  "Init module adding ES client."
  (use-package es-mode :mode "\\.es$"))

SPARQL

(defmodule sparql
  "Init module adding SparQL support."
  (use-package sparql-mode :mode "\\.sparql$"))

Config languages

(defmodule configs
  "Init module for config languages (e.g. Apache, nginx configs)."
  (use-package apache-mode)
  (use-package nginx-mode)
  (use-package syslog-mode
    :load-path "~/.emacs.d/syslog-mode.el"
    :mode "\\.log$")

  (evil-leader/set-key
    "mca" 'apache-mode
    "mcs" 'syslog-mode
    "mcn" 'nginx-mode))

Text markup languages

The ubiquitous, but not actually-that-pleasant, Markdown. I’ve looked at a couple of real-time preview modes and they’re nice, but all have external dependencies requiring Go or Ruby, which I’m not interested in installing. (I run a lean system. 😉)

(defmodule markdown
  "Init module adding Markdown mode and keymaps."
  (use-package markdown-mode
    :mode "\\.md$"
    :config (progn
              (define-key markdown-mode-map (kbd "C-c C-TAB") 'markdown-table-align))))

Org

Performance

Line numbers are helpful in structured docs like org, but perform poorly (until the native line numbers in Emacs 26, anyway).

(add-hook 'org-mode-hook
          (lambda ()
            (let ((lines (count-lines (point-min) (point-max))))
              (when (< lines 500)
                (linum-mode)))))

Evilification

It’s nice to disable evil indentation, which doesn’t play nice. I set a few leader bindings but really it’s just easier to use God mode here.

(evil-leader/set-key-for-mode 'org-mode
  "oi" 'org-clock-in
  "oo" 'org-clock-out
  "or" 'org-clock-report
  "ou" 'org-update-all-dblocks
  "oa" 'org-agenda
  "ot" 'org-todo
  "oT" 'org-set-tags
  "oe" 'org-set-effort
  "ov" 'org-columns
  "oq" 'org-columns-quit
  "oc" 'org-edit-special)

(evil-leader/set-key-for-mode 'org-mode
  "TAB" 'org-cycle)

(add-hook 'org-mode-hook #'(lambda () (electric-indent-local-mode 0)))
(add-hook 'org-mode-hook #'(lambda () (setq evil-auto-indent nil)))

Task management

(setq org-agenda-files '("~/chris.org")
      org-enforce-todo-dependencies t
      org-hide-emphasis-markers t
      ;; Don't show days when summing times, just hours and minutes
      org-time-clocksum-format (list :hours "%d"
                                     :require-hours t
                                     :minutes ":%02d"
                                     :require-minutes t))

Babel

(setq org-confirm-babel-evaluate #'(lambda (lang body)
                                     (not (or (string= lang "emacs-lisp")
                                              (string= lang "dot")
                                              (string= lang "python")))))

(org-babel-do-load-languages 'org-babel-load-languages
                             '((emacs-lisp . t)
                               (dot . t)
                               (python . t)
                               (ditaa . t)))

Exporting

Because everyone else uses GitHub-flavored markdown…

(defmodule org-exports
  "Init module installing Org mode export formats."
  (use-package htmlize)
  (use-package ox-gfm))

Diagrams (graphviz)

I prefer graphviz to ditaa. The binding for (re)displaying inline images helps iterate quickly on diagrams.

(defmodule graphviz
  "Init module to add Graphviz support and configure usage in Org mode."
  (setq default-tab-width tab-width)
  (use-package graphviz-dot-mode)
  (evil-leader/set-key-for-mode 'org-mode
    "oI" 'org-display-inline-images))

The dot language user guide is here.

System

Daemons

Use daemons to manage system services and customize it for evil.

(defmodule daemons
  "Init module to install and configure my daemons package."
  (use-package daemons
    :config
    (progn
      (define-prefix-command 'daemons-prefix-map)
      (define-key daemons-prefix-map (kbd "d") 'daemons)
      (define-key daemons-prefix-map (kbd "RET") 'daemons-status)
      (define-key daemons-prefix-map (kbd "s") 'daemons-start)
      (define-key daemons-prefix-map (kbd "S") 'daemons-stop)
      (define-key daemons-prefix-map (kbd "r") 'daemons-reload)
      (define-key daemons-prefix-map (kbd "R") 'daemons-restart)
      (define-key global-map (kbd "C-x C-y") 'daemons-prefix-map)
      (add-to-list 'evil-emacs-state-modes 'daemons-mode)
      (add-to-list 'evil-emacs-state-modes 'daemons-output-mode)
      (evil-leader/set-key
        "d d" 'daemons
        "d RET" 'daemons-status
        "d s" 'daemons-start
        "d S" 'daemons-stop
        "d r" 'daemons-reload
        "d R" 'daemons-restart))))

Env vars

It’s handy to have a function that can parse a `.env` file. The EmacsWiki cookbook helped a lot writing this.

(defun parse-env (filepath)
  (interactive "fPath to env file: ")
  (with-temp-buffer
    (insert-file-contents filepath)
    (goto-char (point-min))

    (while (re-search-forward "\\(export \\)?\\([A-Za-z0-9_]+\\)=[\"']?\\([^\n'\"]*\\)[\"']?\n?"
                              nil  ;; unbounded, whole buffer
                              t) ;; don't emit an error
      (let ((var (match-string 2))
            (val (match-string 3)))
        (message (format "Setting env var %s=%s"
                         var
                         (replace-regexp-in-string "%" "%%" val)))
        (setenv var val)))))

Frame

(defun set-frame-transparency (fg-percent bg-percent)
  "Set the transparency of this frame to FG-PERCENT when focused and BG-PERCENT when not focused."
  (interactive "nforeground percent:\nnbackground percent:")
  (set-frame-parameter (selected-frame) 'alpha (list fg-percent bg-percent)))

(set-frame-transparency 100 80)

(evil-leader/set-key
  "F" 'toggle-frame-fullscreen)

(define-key global-map (kbd "C-S-F") 'toggle-frame-fullscreen)

(setq frame-title-format
      (format "Emacs %s (%s) - %s@%s"
              emacs-version
              (cond ((eq system-type 'cygwin) "Cygwin")
                    ((eq system-type 'windows-nt) "Windows")
                    (t "*nix"))
              (user-login-name)
              (system-name)))

Window management

(defmodule golden-ratio
  "Init module to install and configure golden ratio for window management."
  (use-package golden-ratio)
  (evil-leader/set-key
    "w" 'evil-window-map
    "wg" 'golden-ratio
    "wG" 'golden-ratio-mode))

(define-key evil-window-map (kbd "C-l") 'evil-window-right)
(define-key evil-window-map (kbd "C-h") 'evil-window-left)
(define-key evil-window-map (kbd "C-k") 'evil-window-up)
(define-key evil-window-map (kbd "C-j") 'evil-window-down)

Display and appearance

Performance

Attempt to improve long line performance.

(setq-default bidi-display-reordering nil)

Themes

(defmodule themes
  "Init module to install preferred themes."
  ;; strong color/dark themes
  (use-package doom-themes :defer t)
  (use-package django-theme :defer t)
  (use-package ubuntu-theme :defer t)
  (use-package monokai-theme :defer t)
  (use-package darkokai-theme :defer t)
  (use-package badger-theme :defer t)

  ;; gentler themes
  (use-package color-theme-sanityinc-tomorrow :defer t)
  (use-package color-theme-sanityinc-solarized :defer t)

  ;; moody themes
  (use-package gotham-theme :defer t))

Widgets

(tool-bar-mode -1)

(menu-bar-mode -1)
(unless (frame-parameter nil 'tty)
    (scroll-bar-mode -1))

(setq inhibit-splash-screen t
      ring-bell-function 'ignore)

Modeline

Joining the rest of the Emacs universe with the DOOM modeline. Gave up on Telephone-Line because it spat errors into *Messages* too frequently.

Remember to M-x all-the-icons-install-fonts.

(defmodule modeline
  "Init module to configure modeline."
  (use-package doom-modeline
    :ensure t
    :hook (after-init . doom-modeline-mode)))

Dashboard

Another good idea lovingly ripped off from Spacemacs.

(defmodule dashboard
  "Init module to install and configure dashboard."
  (use-package dashboard
    :demand
    :config
    (progn
      (dashboard-setup-startup-hook)
      (setq dashboard-banner-logo-title "[ E M A C S ]"
            dashboard-startup-banner "~/unikitty.png" ;; replace 'logo with AWESOME
            dashboard-set-footer t
            dashboard-set-file-icons t
            dashboard-items '((recents  . 12)
                              (bookmarks . 8))))))

Icons

For a bit of snazz, all-the-icons is nice. Run all-the-icons-install-fonts after getting the package.

(defmodule icons
  "Init module to install fancy icons. (Needs manual command - see init.org.)"
  (use-package all-the-icons)
  (use-package all-the-icons-dired
    :config (add-hook 'dired-mode-hook 'all-the-icons-dired-mode)))

Font

Emacs 28 seems to be disrespecting my choices made via the options UI, so doing this in elisp.

(let ((font-name "Source Code Pro 12"))
  (condition-case nil
            (set-frame-font font-name nil t)
          (error (warn "Missing font %s - using system default" font-name))))

Temporary files

Backups and lock files not required.

(setq make-backup-files nil
      create-lockfiles nil)

Shells

Being in the correct mode (line or char) is the cure for all evils in term-mode, but for some reason I couldn’t get a key binding to work on term-mode-hook so just shove it here.

(evil-leader/set-key
  "se" 'eshell
  "st" 'term
  "sc" 'shell-command
  "sr" 'shell-command-on-region
  "sl" 'term-line-mode
  "sc" 'term-char-mode)

Term

(defun term-with-name (name)
  (interactive "sName: ")
  (term "/bin/bash")
  (rename-buffer name))

(define-key global-map (kbd "C-S-R") 'rename-buffer)

Eshell

Fix an irritating warning about the pager program.

(setenv "PAGER" "/bin/cat") ;; fixes git terminal warning
(add-hook 'eshell-mode-hook #'(lambda () (setenv "PAGER" "/bin/cat")))

I often manually list after changing directory, so let’s automate it.

(setq eshell-list-files-after-cd t)
(setq eshell-ls-initial-args "-lh")

Calling vim is deep in my muscle memory, so alias it to find-file.

(defun eshell/vim (file)
  (find-file file))

(defun eshell/vimo (file)
  (find-file-other-window file))

(defun eshell/emacs (file)
  (find-file-other-window file))

Pop-up shell

Thank you howardism.org. I no longer use this, but keep it around for reference.

(defun eshell-here ()
  "Opens up a new shell in the directory associated with the
current buffer's file. The eshell is renamed to match that
directory to make multiple eshell windows easier."
  (interactive)
  (let* ((parent (if (buffer-file-name)
                     (file-name-directory (buffer-file-name))
                   default-directory))
         (height (/ (window-total-height) 3))
         (name   (car (last (split-string parent "/" t)))))
    (split-window-vertically (- height))
    (other-window 1)
    (eshell "new")
    (rename-buffer (concat "*eshell: " name "*"))

    (insert (concat "ls"))
    (eshell-send-input)))

(defun eshell/quit ()
  (insert "exit")
  (eshell-send-input)
  (delete-window))

(defun toggle-eshell ()
  (interactive)
  (if (string-match "^\\*eshell: " (buffer-name))
      (eshell/quit)
    (eshell-here)))

Pop-down shell

Trying out equake.

(defmodule equake
  "Init module to run the equal pop out shell."
  (use-package equake
    :ensure t
    :config  ; some examples of optional settings follow:
    (global-set-key (kbd "C-x C-c") 'equake-check-if-in-equake-frame-before-closing) ; prevent accidental frame-closure
    (global-set-key (kbd "C-`") 'equake-invoke)
    (setq equake-size-width 0.99) ; set width a bit less than full-screen (prevent 'overflow' on multi-monitor)
    ;; set distinct face for Equake: white foreground with dark blue background, and different font
    (set-face-attribute 'equake-buffer-face 'nil :inherit 'default :family "Iosevka Term Regular" :background "#000022" :foreground "white")))

Prompt

You can’t be a professional without a try-hard prompt.

XPM shapes

;; pinched from powerline.el
(defun curve-right-xpm (color1 color2)
  "Return an XPM right curve string representing."
  (create-image
   (format "/* XPM */
static char * curve_right[] = {
\"12 18 2 1\",
\". c %s\",
\"  c %s\",
\"           .\",
\"         ...\",
\"         ...\",
\"       .....\",
\"       .....\",
\"       .....\",
\"      ......\",
\"      ......\",
\"      ......\",
\"      ......\",
\"      ......\",
\"      ......\",
\"       .....\",
\"       .....\",
\"       .....\",
\"         ...\",
\"         ...\",
\"           .\"};"
           (if color2 color2 "None")
           (if color1 color1 "None"))
   'xpm t :ascent 'center))

(defun curve-left-xpm (color1 color2)
  "Return an XPM left curve string representing."
  (create-image
   (format "/* XPM */
static char * curve_left[] = {
\"12 18 2 1\",
\". c %s\",
\"  c %s\",
\".           \",
\"...         \",
\"...         \",
\".....       \",
\".....       \",
\".....       \",
\"......      \",
\"......      \",
\"......      \",
\"......      \",
\"......      \",
\"......      \",
\".....       \",
\".....       \",
\".....       \",
\"...         \",
\"...         \",
\".           \"};"
           (if color1 color1 "None")
           (if color2 color2 "None"))
   'xpm t :ascent 'center))

(defun arrow-left-xpm (color1 color2)
  "Return an XPM left arrow string representing."
  (create-image
   (format "/* XPM */
static char * curve_right[] = {
\"12 18 2 1\",
\". c %s\",
\"  c %s\",
\"         ...\",
\"        ....\",
\"       .....\",
\"      ......\",
\"     .......\",
\"    ........\",
\"   .........\",
\"  ..........\",
\" ...........\",
\" ...........\",
\"  ..........\",
\"   .........\",
\"    ........\",
\"     .......\",
\"      ......\",
\"       .....\",
\"        ....\",
\"         ...\"};"
           (if color2 color2 "None")
           (if color1 color1 "None"))
   'xpm t :ascent 'center))

(defun arrow-right-xpm (color1 color2)
  "Return an XPM right arrow string representing."
  (create-image
   (format "/* XPM */
static char * curve_left[] = {
\"12 18 2 1\",
\". c %s\",
\"  c %s\",
\"...         \",
\"....        \",
\".....       \",
\"......      \",
\".......     \",
\"........    \",
\".........   \",
\"..........  \",
\"........... \",
\"........... \",
\"..........  \",
\".........   \",
\"........    \",
\".......     \",
\"......      \",
\".....       \",
\"....        \",
\"...         \"};"
           (if color1 color1 "None")
           (if color2 color2 "None"))
   'xpm t :ascent 'center))

Prompt functions

(defvar eshell-prompt-suffix
  (if (or (eq system-type 'gnu/linux)
          (eq system-type 'darwin)) "λ " " ")
  "String at end of prompt.")

(unless (frame-parameter nil 'tty)

  (require 'color)

  ;; TODO fancy prompt in terminal mode also
  (defvar eshell-fg-color
    (let ((fg-color (frame-parameter nil 'foreground-color)))
      (if (equal "unspecified-fg" fg-color) "#ffffff" fg-color)))

  (defvar eshell-bg-color
    (let ((bg-color (frame-parameter nil 'background-color)))
      (if (equal "unspecified-bg" bg-color) "#ffffff" bg-color)))

  (defvar eshell-prompt-bg-color
    (color-complement-hex (if (equal "unspecified-fg" eshell-fg-color) "#ffffff" eshell-fg-color))
    "Color of the prompt block background.")

  (defvar eshell-prompt-fg-color
    (color-complement-hex (if (equal "unspecified-bg" eshell-bg-color) "#000000" eshell-bg-color))
    "Color of the prompt block foreground.")

  (defvar eshell-prompt--separators (make-hash-table))

  (defun eshell-prompt--lhs (fg-color bg-color)
    (let ((hash-key (concat "lhs_" fg-color "_" bg-color)))
      (or (gethash hash-key eshell-prompt--separators))
      (puthash hash-key (arrow-right-xpm bg-color fg-color) eshell-prompt--separators)))

  (defun eshell-prompt--rhs (fg-color bg-color)
    (let ((hash-key (concat "rhs_" fg-color "_" bg-color)))
      (or (gethash hash-key eshell-prompt--separators))
      (puthash hash-key (arrow-right-xpm fg-color bg-color) eshell-prompt--separators)))

  (defun eshell-blocky-prompt ()
    "Create a blocky eshell prompt."
    (let ((bg eshell-bg-color)
          (fg eshell-fg-color))
      (concat
       (propertize " " 'display (eshell-prompt--lhs eshell-prompt-bg-color bg))
       (propertize (eshell/pwd) 'face
                   (list :foreground eshell-prompt-fg-color
                         :background eshell-prompt-bg-color))
       (propertize " " 'display (eshell-prompt--rhs eshell-prompt-bg-color bg))
       eshell-prompt-suffix)))

  (defconst eshell-blocky-prompt-regexp
    (string-join (list "^[^#\n]* " eshell-prompt-suffix)))

  (setq eshell-prompt-function 'eshell-blocky-prompt
        eshell-prompt-regexp eshell-blocky-prompt-regexp))

Directories

These days Treemacs is the coolest directory browser.

;;(use-package treemacs
;;  :config (add-to-list 'evil-emacs-state-modes  'treemacs-mode))

;;(evil-leader/set-key "t" 'treemacs)

Customize dired a bit and give it a key.

(evil-leader/set-key "D" 'dired)
(setq dired-listing-switches "-lh --group-directories-first")

Magit

“Better at git than git” true, but apparently not better at compiling and running on multiple platforms…

(defmodule magit
  "Init module for Magit."
  (use-package magit
    :pin melpa-stable
    :config
    (progn
      (global-set-key (kbd "C-x g") 'magit-status)
      (add-to-list 'evil-emacs-state-modes 'magit-mode)
      (add-to-list 'evil-emacs-state-modes 'magit-blame-mode)
      (evil-leader/set-key "g" 'magit-status))))

OS-specific

GNU/Linux

Terminal colors

(when (eq system-type 'gnu/linux)
  ;; (use-package eterm-256color :config (add-hook 'term-mode-hook #'eterm-256color-mode))
  )

MacOS

Terminal colors

It turns out term and ansi-term on MacOS needs a bit of setup.

(defun fix-terminal-colors ()
  "Installs a copy of eterm-color terminfo."
  (interactive)
  (let ((path-to-emacs-app "/Applications/Emacs.app"))
    (shell-command
     (format "tic -o ~/.terminfo %s/Contents/Resources/etc/e/eterm-color.ti"
             path-to-emacs-app))))

Windows

Performance

Some tweaks are required on Windows.

(when (eq system-type 'windows-nt)
  (if (>= emacs-major-version 25)
      (remove-hook 'find-file-hooks 'vc-refresh-state)
    (remove-hook 'find-file-hooks 'vc-find-file-hook))

  (progn
    (setq gc-cons-threshold (* 511 1024 1024)
          gc-cons-percentage 0.5
          garbage-collection-messages t)
    (run-with-idle-timer 5 t #'garbage-collect)))

TRAMP

PuTTY (and so plink.exe) is basically your only choice on Windows.

(when (eq system-type 'windows-nt)
  (require 'tramp)
  (setq tramp-default-method "plink"))

Helpers

Proxy

Provide functions to set all the necessary proxy variables for $day_job, assuming that proxy-host and proxy-port are set in custom-file.

(defun proxy-make-url (host port &optional username password)
  (concat
   (when (or username password)
     (format "%s:%s@"
             (if (not username) "" username)
             (if (not password) "" password)))
   (format "%s:%s" host port)))

(defun proxy-set (http-proxy)
  "Set proxy variables that Emacs uses from the provided HTTP-PROXY string."
  (setenv "HTTP_PROXY" (format "https://%s" http-proxy))
  (setq url-proxy-services (list (cons "http" http-proxy)
                                 (cons "https" http-proxy))))

(defun proxy-set-with-user ()
  "Set proxy using current user login name and asking for password."
  (interactive)
  (proxy-set (proxy-make-url proxy-host
                             proxy-port
                             (user-login-name)
                             (read-passwd "Password: "))))

Pass

For working with pass.

(defmodule pass
  "Init module to install and configure pass support."
  (use-package password-store
    :config (evil-leader/set-key
              "Pc" 'password-store-copy
              "Pg" 'password-store-generate
              "Pr" 'password-store-remove
              "Pe" 'password-store-edit
              "PR" 'password-store-rename
              "Pi" 'password-store-insert
              "Pu" 'password-store-url)))