;;; emacs-config --- Summary ;; #+TITLE: Emacs Configuration for Alan Pearce ;; #+OPTIONS: ^:nil ;; #+PROPERTY: results silent ;; #+PROPERTY: eval no-export ;; #+PROPERTY: header-args :comments link ;;; Commentary: ;;; This is my Emacs configuration. ;;; Header: ;; This is a living document, detailing my Emacs configuration using org-mode ;;; Code: ;;;; Basics ;;;;; Startup ;; Open Emacs with just a plain window. No graphics or messages, please! (setq inhibit-startup-screen t) (setq gc-cons-threshold 100000000) (add-hook 'after-init-hook (lambda () (setq gc-cons-threshold 800000))) (remove-hook 'find-file-hooks #'vc-refresh-state) ;; Are we running on Windows via the WSL? (when (file-exists-p "/proc/sys/kernel/osrelease") (with-temp-buffer (insert-file-contents-literally "/proc/sys/kernel/osrelease") (decode-coding-region (point-min) (point-max) 'utf-8 t) (when (string-match "Microsoft$" (buffer-string)) (setq system-type 'gnu/linux/windows)))) ;;;;; Compatibility (if (version< emacs-version "25.0") (defmacro with-eval-after-load (file &rest body) `(eval-after-load ,file (lambda () ,@body)))) ;;;;; Scratch buffers ;; I usually use scratch buffers for any sort of text. If I need a ;; programming mode in one, then I’ll just call it manually. I also like ;; the buffer to be empty. (setq initial-scratch-message "" initial-major-mode 'text-mode) ;;;;; Personal Information (setq user-full-name "Alan Pearce") ;; #+end_src ;;;; Packaging ;;;;; Use-package (autoload 'package-installed-p "package.el") (eval-and-compile (require 'seq) (defun is-nix-emacs () (and invocation-directory (string-match "^/nix/store" invocation-directory) (not (null (seq-some (lambda (dir) (string-match "^/nix/store" dir)) load-path))))) (defvar nix-emacs (is-nix-emacs)) (setq tls-checktrust t gnutls-verify-error t package-menu-async t package-user-dir (concat "~/.emacs.d/packages/" emacs-version "/elpa") package-menu-hide-low-priority t package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa-stable" . "https://stable.melpa.org/packages/") ("melpa" . "https://melpa.org/packages/"))) (unless nix-emacs (setq package-pinned-packages '(("use-package" . melpa-stable) ("bind-key" . melpa-stable)) package-archive-priorities '(("melpa" . 10) ("gnu" . 10) ("melpa-stable" . 5) ("marmalade" . 0)))) (when (eq system-type 'darwin) (with-eval-after-load "gnutls" (add-to-list 'gnutls-trustfiles "/etc/ssl/cert.pem"))) (unless nix-emacs (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)))) (eval-when-compile (require 'use-package)) (unless (featurep 'use-package) (require 'bind-key) (use-package use-package :commands (use-package-autoload-keymap) :defer 5)) (setq use-package-always-ensure (not nix-emacs) use-package-always-demand (daemonp) package-enable-at-startup nil) ;;;;; Helpers ;;;;;; Hook Helpers ;; An improvement over add-hook with lamda functions that allows ;; modification and removal, without the boilerplate of an extra function ;; definition. (eval-and-compile (use-package hook-helpers)) ;;;; Customize ;; I don’t really like using customize for normal configuration. ;; Instead, I use it for things that get saved automatically. That’s why ;; I use a different file, which is ignored by the VCS. It also means ;; that it’s not important whether the file exists or not, which is why I ;; pass =:noerror= to =load= (setq custom-file "~/.emacs.d/custom.el") (load custom-file :noerror :nomessage) ;;;; Styles ;; I prefer an always-visible cursor. Feels less distracting. (when (fboundp #'blink-cursor-mode) (blink-cursor-mode -1)) ;; Disable all the bars, unless on OSX, in which case, keep the menu bar. (when (and menu-bar-mode (not (eq window-system 'ns))) (menu-bar-mode -1)) (with-eval-after-load 'scroll-bar (set-scroll-bar-mode nil)) (with-eval-after-load 'tooltip (tooltip-mode -1)) (with-eval-after-load 'tool-bar (tool-bar-mode -1)) (set-fringe-mode '(4 . 4)) ;; Ring the bell sometimes, but not so often (setq ring-bell-function (lambda () (unless (memq this-command '(isearch-abort abort-recursive-edit exit-minibuffer keyboard-quit undo-tree-undo)) (ding)))) ;;;;; Colours (load-theme 'spacemacs-light t) ;; Highlighting quasi-quoted expressions in lisps is quite useful, but I ;; don't need it all the time. I'll keep it around for a while so that I ;; can enable it if needed. (use-package highlight-stages) (global-hl-line-mode +1) (let ((line (face-attribute 'mode-line :underline))) (set-face-attribute 'mode-line nil :overline line) (set-face-attribute 'mode-line-inactive nil :overline line) (set-face-attribute 'mode-line-inactive nil :underline line) (set-face-attribute 'mode-line nil :box nil) (set-face-attribute 'mode-line-inactive nil :box nil) (set-face-attribute 'mode-line-inactive nil :background (face-attribute 'hl-line :background))) ;;;;; Fonts ;; When possible, set up fonts. I don’t have any settings here for X11, ;; because I manage those in my [[file:~/projects/dotfiles/tag-xresources/xresources/main][XResources file]]. (when (or (display-graphic-p) (daemonp)) (setq-default line-spacing 0.2) (defun use-variable-fonts () (interactive) (variable-pitch-mode) (setq cursor-type '(bar . 1))) (defun use-fixed-fonts () (interactive) (variable-pitch-mode -1) (setq cursor-type 'box)) (defun ap/set-fonts (mono-face mono-font-size variable-face variable-font-size antialias &optional new-line-spacing) (if (boundp 'ns-antialias-text) (setq ns-antialias-text antialias)) (if (boundp 'new-line-spacing) (setq line-spacing new-line-spacing)) (when mono-face (let ((default-font (font-spec :family mono-face :size mono-font-size))) (add-to-list 'default-frame-alist `(font . ,(format "%s %s" mono-face mono-font-size))) (set-face-font 'fixed-pitch default-font) (set-frame-font default-font t t))) (when variable-face (set-face-font 'variable-pitch (font-spec :name variable-face :size variable-font-size)))) (defun ap/set-fonts-according-to-system () (interactive) (cond ((eq window-system 'w32) (ap/set-fonts "Liberation Mono" 11 "Segoe UI" 11 t)) ((or (eq window-system 'mac) (eq window-system 'ns)) (let ((displays (string-to-number (shell-command-to-string "system_profiler SPDisplaysDataType | grep \"Online: Yes\" | wc -l")))) (ap/set-fonts "SF Mono" 12 "Helvetica Neue" 12 t 0.1))) ((and (eq window-system 'x) (eq system-type 'gnu/linux/windows)) (ap/set-fonts "Noto Mono" 12 "Sans" 12 nil))))) (add-hook 'first-frame-hook #'ap/set-fonts-according-to-system) ;; Reduce font decoration. I’m trying to see whether this helps me focus ;; on the right things. (setq font-lock-maximum-decoration '((dired-mode . 1) (t . 1))) ;; Make symbols prettier. Turns out, in many cases, this is already ;; configured, just not enabled. If using the mac-port version of Emacs, ;; it has it's own, more extensive version. (if (eq window-system 'mac) (if (fboundp 'mac-auto-operator-composition-mode) (mac-auto-operator-composition-mode +1)) (global-prettify-symbols-mode +1)) ;;;;; Page Breaks ;; By default, Emacs displays page breaks as ^L. Lines look much nicer. ;; On Windows, Emacs incorrectly detects that U+2500 (Box Drawings Light ;; Horizontal) can only be displayed with a different font, which is not ;; correct, at least for Liberation Mono. (use-package page-break-lines :defer 5 :config (progn (global-page-break-lines-mode) (unless (eq (char-displayable-p ?─) (char-displayable-p ?a)) (set-fontset-font "fontset-default" (cons page-break-lines-char page-break-lines-char) (face-attribute 'default :family))))) (require 'f) (setq frame-title-format (list "Emacs")) ;;;;; Chrome (setq-default cursor-in-non-selected-windows nil) (add-to-list 'default-frame-alist '(border-width . 0)) (add-to-list 'default-frame-alist '(internal-border-width . 0)) (when (eq system-type 'darwin) (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . light))) (when (or (eq window-system 'x) (eq window-system 'mac)) (setq window-divider-default-bottom-width 1 window-divider-default-right-width 1 window-divider-default-places t) (window-divider-mode +1)) (use-package minions :config (progn (setq minions-mode-line-lighter "#") (minions-mode +1))) (use-package moody :config (progn (setq x-underline-at-descent-line t) (moody-replace-mode-line-buffer-identification) (moody-replace-vc-mode))) ;;;; Environment Variables ;; MacOS doesn’t have a reasonable way to set environment variables and ;; read them automatically any more. So, let’s use the ;; [[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] package to set up ~exec-path~ and similar ;; variables from whatever my shell configuration is. ;; On Windows, I like to run Emacs from the system tray menu of VcXsrv. ;; It starts up without an environment in this case as well. (use-package exec-path-from-shell :if (or (eq system-type 'darwin) (eq system-type 'gnu/linux/windows) (and (eq system-type 'gnu/linux) (daemonp))) :config (progn (setq exec-path-from-shell-arguments '("-l")) (exec-path-from-shell-initialize))) (with-eval-after-load 'browse-url (when (executable-find "xdg-open") (setq browse-url-browser-function #'browse-url-xdg-open))) ;;;;; NixOS sandboxes ;; I'm currently exploring using nix to create sandboxes for ;; development. This package allows using tools from inside sandboxes, ;; and some convenience commands for building packages and launching shells. (use-package nix-sandbox :defines (flycheck-command-wrapper-function flycheck-executable-find) :config (progn (with-eval-after-load 'flycheck (setq flycheck-command-wrapper-function (lambda (cmd) (apply 'nix-shell-command (nix-current-sandbox) (list (mapconcat 'shell-quote-argument cmd " ")))) flycheck-executable-find (lambda (cmd) (nix-executable-find (nix-current-sandbox) cmd)))))) ;;;; Keybindings ;; I think =set-keyboard-coding-system= stops OS X from doing something ;; annoying to add accents. The modifier setup is to match my ;; re-arrangement of modifiers on OSX: Cmd on the outside, then ;; Option/alt, then Control. (when (eq system-type 'darwin) (set-keyboard-coding-system nil) (custom-set-variables '(mac-option-modifier 'meta) '(mac-right-option-modifier 'none) '(mac-control-modifier 'control) '(mac-right-control-modifier 'left) '(mac-command-modifier 'super) '(mac-right-command-modifier 'left) '(mac-function-modifier 'hyper)) (unbind-key "s-x")) (unbind-key "") (bind-key* "" #'compile) (bind-key* "" #'kmacro-start-macro-or-insert-counter) (bind-key* "" #'kmacro-end-or-call-macro) (bind-key* "" #'execute-extended-command) (unbind-key "C-x C-c") (bind-key* "C-c i" #'insert-char) (bind-key* "M-/" #'hippie-expand) (unbind-key "s-h") (unbind-key "s-n") (unbind-key "s-p") (unbind-key "s-w") (bind-key* "s-x" (define-prefix-command 'super-x-map)) (bind-key* "s-," #'switch-to-dotfiles) (bind-key* "C-x M-x" #'execute-extended-command) ;;;;; Crux ;; I can replace most of the simple helper/wrapper functions in my ;; configuration with crux.el (use-package crux :bind (("C-x 4 t" . crux-transpose-windows) ("C-c e" . crux-eval-and-replace) ("C-c D" . crux-delete-file-and-buffer) ("C-c R" . crux-rename-file-and-buffer))) ;;;; Projects (defun switch-to-dotfiles () "Switch to dotfiles project." (interactive) (projectile-switch-project-by-name (car (split-string (shell-command-to-string "ghq list --full-path dotfiles"))))) ;;;;; Grep (use-package grep :config (progn (dolist (v '("node_modules" "bower_components" ".sass_cache" ".cache" ".npm")) (add-to-list 'grep-find-ignored-directories v)) (dolist (v '("*.min.js" "*.bundle.js" "*.min.css" "*.lock" "package-lock.json" "*.log")) (add-to-list 'grep-find-ignored-files v)))) ;;;;; The Silver Searcher (use-package ag :defer 30 :config (progn (setq ag-project-root-function #'projectile-project-root) (add-to-list 'ag-arguments "--hidden"))) (use-package wgrep-ag :after ag) ;;;;; Ripgrep ;; Step over Silver Search, here comes a new challenger. (use-package ripgrep :if (executable-find "rg") :config (progn (setq ripgrep-arguments '("--hidden")))) (use-package projectile-ripgrep :after (ripgrep projectile) :if (executable-find "rg") :config (bind-key "s r" #'projectile-ripgrep projectile-command-map)) ;;;;; Projectile ;; Projectile is awesome for working in projects, especially VCS-backed ;; ones. (add-to-list 'byte-compile-not-obsolete-funcs 'projectile-global-mode) (use-package projectile :bind (("s-p" . projectile-switch-project) ("C-c C-f" . projectile-find-file) ("s-x s-f" . projectile-find-file) ("C-x g" . projectile-vc) ("C-M-g" . projectile-vc)) :demand t :config (progn (projectile-global-mode +1) (add-to-list 'projectile-globally-ignored-directories ".stversions") (add-to-list 'projectile-globally-ignored-directories "node_modules") (add-to-list 'projectile-globally-ignored-files "package-lock.json") (setq projectile-mode-line "P") (defun yarn-install (&optional arg) (interactive "P") (projectile-with-default-dir (projectile-project-root) (cond ((string-equal (projectile-project-type) "node-yarn") (cmd-to-echo "yarn" "install")) (t (cmd-to-echo "npm" "install"))))) (defalias 'npm-install #'yarn-install) (defun yarn-add-dev (package) (interactive "spackage: ") (projectile-with-default-dir (projectile-project-root) (cond ((string-equal (projectile-project-type) "node-yarn") (cmd-to-echo "yarn" (concat "add --dev " package))) (t (cmd-to-echo "npm" (concat "install --save-dev " package)))))) (defalias 'npm-save-dev #'yarn-add-dev) (defun yarn-add (package) (interactive "spackage: ") (projectile-with-default-dir (projectile-project-root) (cond ((string-equal (projectile-project-type) "node-yarn") (cmd-to-echo "yarn" (concat "add " package))) (t (cmd-to-echo "npm" (concat "install --save " package)))))) (defalias 'npm-save #'yarn-add) (defun yarn-remove (package) (interactive "spackage: ") (projectile-with-default-dir (projectile-project-root) (cond ((string-equal (projectile-project-type) "node-yarn") (cmd-to-echo "yarn" (concat "remove " package))) (t (cmd-to-echo "npm" (concat "remove " package)))))) (defalias 'npm-remove #'yarn-remove) (defun yarn-run (cmd) (interactive (list (projectile-completing-read "command: " (alist-get 'scripts (json-read-file (expand-file-name "package.json" (projectile-project-root))))))) (projectile-with-default-dir (projectile-project-root) (cond ((string-equal (projectile-project-type) "node-yarn") (cmd-to-echo "yarn" (concat "run " cmd))) (t (cmd-to-echo "npm" (concat "run " cmd)))))) (defalias 'npm-run #'yarn-run) (defun npx-run (cmd) (interactive "scommand: ") (projectile-with-default-dir (projectile-project-root) (cmd-to-echo "npx" cmd))) (defun npm-version (type) (interactive (list (projectile-completing-read "version: " '("major" "minor" "patch" "premajor" "preminor" "prepatch" "prerelease" "from-git")))) (projectile-with-default-dir (projectile-project-root) (message (shell-command-to-string (concat "npm" " version " type))))) (defun ap/open-project (&optional arg) (interactive "P") (let ((project-dir (projectile-completing-read "Open project: " (ghq--find-projects-full-path)))) (projectile-switch-project-by-name project-dir arg))) (defun git-bug (bug) (interactive "sbug: ") (projectile-with-default-dir (projectile-project-root) (call-process-shell-command (concat "git bug " bug)))) (defun git-feature (feature) (interactive "sfeature: ") (projectile-with-default-dir (projectile-project-root) (call-process-shell-command (concat "git feature " feature)))) (defun git-chore (chore) (interactive "schore: ") (projectile-with-default-dir (projectile-project-root) (call-process-shell-command (concat "git chore " chore)))) (setq projectile-switch-project-action #'projectile-commander projectile-completion-system 'ivy projectile-create-missing-test-files t) (defun ap/projectile-test-suffix (project-type) (cond ((member project-type '(node-yarn node-npm)) ".test") (t (projectile-test-suffix project-type)))) (setq projectile-test-suffix-function #'ap/projectile-test-suffix) (projectile-register-project-type 'node-yarn '("yarn.lock") :run "yarn start" :test "yarn test") (projectile-register-project-type 'node '("package.json") :run "npm start" :test "npm test"))) (use-package counsel-projectile :after (counsel projectile ivy-hydra) :config (progn (counsel-projectile-mode +1) (def-projectile-commander-method ?A "Find rg on project." (call-interactively #'counsel-projectile-rg)) (if (boundp 'counsel-projectile-command-map) (define-key counsel-projectile-command-map (kbd "s s") #'counsel-projectile-rg)))) ;;;;; vc ;; This is nice for some things that magit doesn’t do, and for those rare ;; occasions that I’m working with something other than git. (use-package vc :defer t :bind (("C-x v C" . vc-resolve-conflicts)) :config (progn (setq vc-follow-symlinks t) (setq vc-ignore-dir-regexp (format "\\(%s\\)\\|\\(%s\\)" vc-ignore-dir-regexp tramp-file-name-regexp)))) ;;;;; git-gutter-fringe ;; It’s nice to be able to see at a glance which lines of a file have ;; changed. This package colours the fringe. I have it set to the right ;; fringe so it doesn’t interfere with flycheck. (eval-when-compile (require 'fringe-helper)) (use-package git-gutter-fringe :defer 2 :config (progn (global-git-gutter-mode 1) ;; places the git gutter outside the margins. (setq-default fringes-outside-margins t) ;; thin fringe bitmaps (fringe-helper-define 'git-gutter-fr:added '(center repeated) ".XXX....") (fringe-helper-define 'git-gutter-fr:modified '(center repeated) ".XXX....") (fringe-helper-define 'git-gutter-fr:deleted '(center repeated) ".XXX....") (setq git-gutter-fr:side 'right-fringe))) ;;;;; magit ;; Magit is my favourite way to use git. I use selective staging all the ;; time. Make sure to set it up with a nice =completing-read-function= (use-package magit :defer 5 :commands (magit-status) :config (progn (setq magit-completing-read-function #'ivy-completing-read magit-popup-use-prefix-argument 'default magit-repository-directories (list (cons (file-name-as-directory (ghq--find-root)) 3)) magit-display-buffer-function #'magit-display-buffer-fullcolumn-most-v1 global-magit-file-mode t) (add-to-list 'magit-no-confirm 'safe-with-wip)) :init (add-hook 'magit-mode-hook #'magit-load-config-extensions)) ;;;;; git-messenger ;; Popup the last commit that changed the line at point. (use-package git-messenger :bind* (("C-x v p" . git-messenger:popup-message)) :config (progn (setq git-messenger:use-magit-popup t))) ;;;;; git-timemachine ;; This package allow me to go through a file’s history with just a few ;; keys. It makes it very easy to figure what what exactly was in a file ;; in the past. I often find it useful when I remember writing something ;; a particular way, but it changed later. (use-package git-timemachine :commands git-timemachine) ;;;;; ghq ;; [[https://github.com/motemen/ghq][=ghq=]] clones VCS-backed projects to a common directory. It should ;; seem familiar to anyone who's used =go get= before. [[https://github.com/rcoedo/emacs-ghq][=emacs-ghq=]] is a ;; simple wrapper for it. (use-package ghq :if (executable-find "ghq")) ;;;; Files ;;;;; Auto-saving ;; Auto-save everything to a temporary directory, instead of cluttering ;; the filesystem. I don’t want emacs-specific lockfiles, either. (setq auto-save-file-name-transforms `((".*" ,temporary-file-directory t)) create-lockfiles nil) ;;;;; Backups ;; I like to keep my backups out of regular folders. I tell emacs to use ;; a subfolder of its configuration directory for that. Also, use the ;; trash for deleting on OS X. (let ((backup-dir (expand-file-name "~/.emacs.d/backups/"))) (unless (file-directory-p backup-dir) (make-directory backup-dir)) (setq backup-directory-alist `((".*" . ,backup-dir)) backup-by-copying-when-linked t backup-by-copying-when-mismatch t)) (if (eq system-type 'darwin) (setq delete-by-moving-to-trash t) (if (and (executable-find "trash") (not (fboundp #'system-move-file-to-trash))) (defun system-move-file-to-trash (file) (call-process (executable-find "trash") nil 0 nil file)))) ;;;;; autorevert (use-package autorevert :init (progn (global-auto-revert-mode 1) (setq auto-revert-verbose t auto-revert-use-notify (not (eq system-type 'darwin))))) ;;;;; Encoding ;; UTF-8 is usually appropriate. Note that =prefer-coding-system= expects ;; only a coding system, not a coding system and line ending combination. (prefer-coding-system 'utf-8) (setq-default buffer-file-coding-system 'utf-8-auto-unix) ;;;;; Buffer-file management ;; Ask if I want to create a directory when it doesn’t exist. This is ;; especially nice when starting new projects. (defun my-create-non-existent-directory () "Offer to create non-existent directories of found-file." (let ((parent-directory (file-name-directory buffer-file-name))) (when (and (not (file-exists-p parent-directory)) (y-or-n-p (format "Directory `%s' does not exist! Create it? " parent-directory))) (make-directory parent-directory t)))) (add-to-list 'find-file-not-found-functions #'my-create-non-existent-directory) ;; I often want to rename or delete the file that I’m currently visiting ;; with a buffer. (defun kill-or-delete-this-buffer-dwim (&optional arg) "Kill current buffer. With prefix ARG, delete it." (interactive "P") (if (equal arg '(4)) (crux-delete-file-and-buffer) (if server-buffer-clients (server-edit) (let ((buf (buffer-name))) (when (equal buf "*HTTP Response*") (other-window 1)) (kill-buffer buf))))) ;;;;; Whitespace ;; Show bad whitespace, so that I can fix it. (defun show-trailing-whitespace-on () "Show trailing whitespace." (interactive) (setq-local show-trailing-whitespace t)) (defun show-trailing-whitespace-off () "Hide trailing whitespace." (interactive) (setq-local show-trailing-whitespace nil)) (add-hook 'prog-mode-hook #'show-trailing-whitespace-on) (add-hook 'text-mode-hook #'show-trailing-whitespace-on) ;;;;; shrink-whitespace ;; DWIM whitespace removal. So I don’t need =M-SPC=, =M-\= and =C-x o= ;; for similar things any more. (use-package shrink-whitespace :bind ("M-SPC" . shrink-whitespace)) ;;;;; Tramp ;; Tramp is awesome. It makes SSH feel Unix-y. The proxy setup is so ;; that I can sudo on remote machines (use-package tramp :defer 7 :defines tramp-ssh-controlmaster-options :config (progn (unless (and (getenv "SSH_AUTH_SOCK") (file-exists-p (getenv "SSH_AUTH_SOCK"))) (setenv "SSH_AUTH_SOCK" (format "/run/user/%s/gnupg/S.gpg-agent.ssh" (user-uid)))) (setq tramp-default-method "ssh" tramp-default-user-alist '(("\\`su\\(do\\)?\\'" nil "root")) tramp-backup-directory-alist backup-directory-alist tramp-completion-reread-directory-timeout 60 tramp-ssh-controlmaster-options nil backup-enable-predicate (lambda (name) (and (normal-backup-enable-predicate name) (not (let ((method (file-remote-p name 'method))) (when (stringp method) (member method '("su" "sudo"))))))) tramp-shell-prompt-pattern "\\(?:^\\| \\)[^]#$%>\n]*#?[]#$%>❯›] *\\(\\[\\??[0-9;]*[a-zA-Z] *\\)*") (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" (concat "/" tramp-default-method ":%h:"))) (add-to-list 'tramp-default-proxies-alist `(,(regexp-quote (system-name)) nil nil)) (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil)))) ;;;;; ediff ;; I like a horizonal diff setup, with everything in one frame. (use-package ediff :defer t :config (progn (setq ediff-split-window-function 'split-window-horizontally ediff-window-setup-function 'ediff-setup-windows-plain))) ;;;; Indentation ;; Ah, a complicated topic. One day we’ll all be using elastic ;; tabstops. I’ve recently switched to using two spaces, since elastic ;; tabstops is probably never going to happen. (setq-default tab-width 2 indent-tabs-mode nil) ;;;;; smart-tabs-mode ;; Not related to [[smart-tab][=smart-tab=]], this mode indents with tabs and aligns ;; with spaces. Perfect! (use-package smart-tabs-mode :defer 1 :config (progn (smart-tabs-insinuate 'c 'cperl 'python))) ;;;;; editorconfig (use-package editorconfig :init (progn (unless (executable-find "editorconfig") (warn "Missing `editorconfig' executable."))) :config (editorconfig-mode 1)) ;;;;; dtrt-indent-mode ;; Sometimes people use different indentation settings. [[https://github.com/jscheid/dtrt-indent][dtrt-indent]] ;; guesses the correct settings for me. (use-package dtrt-indent :config (progn (define-hook-helper prog-mode () (unless (and (boundp editorconfig-mode) editorconfig-mode) (dtrt-indent-adapt))) (defadvice dtrt-indent-try-set-offset (after toggle-smart-tabs activate) (smart-tabs-mode (or indent-tabs-mode -1))))) ;;;; Security ;;;;; password-store ;; This is a frontend to the GPG-powered =pass= program. (use-package password-store :defer 15 :config (progn (setq password-store-password-length 16))) ;;;; Buffers ;;;;; Ibuffer ;; Ibuffer is quite nice for listing all buffers. (use-package ibuffer :bind (("C-x C-b" . ibuffer)) :config (progn (setq ibuffer-saved-filter-groups (quote (("default" ("org" (mode . org-mode)) ("emacs" (mode . emacs-lisp-mode)) ("zsh" (filename . "/zsh")) ("server" (filename . "/su:root@server")))))) ;; Human-readable base-2 size column (define-ibuffer-column size-h (:name "Size" :inline t) (cond ((> (buffer-size) 1024) (format "%7.2fK" (/ (buffer-size) 1024.0))) ((> (buffer-size) 1048576) (format "%7.2fM" (/ (buffer-size) 1048576.0))) (t (format "%8d" (buffer-size))))) (setq ibuffer-formats '((mark modified read-only " " (name 18 18 :left :elide) " " (size-h 9 -1 :right) " " (mode 16 16 :left :elide) " " filename-and-process))))) ;;;;; Relative Buffer names (use-package relative-buffers :defer 15 :config (progn (global-relative-buffers-mode))) ;;;;; Narrowing ;; Enable it without prompting (put 'narrow-to-defun 'disabled nil) (put 'narrow-to-page 'disabled nil) (put 'narrow-to-region 'disabled nil) ;;;; Windows ;; Scrolling is tricky. I use this setup to help me keep track of the ;; point whilst I’m moving about. (setq scroll-conservatively 100 scroll-margin 1 scroll-preserve-screen-position t mouse-wheel-scroll-amount '(1 ((shift) . 1) ((control))) split-height-threshold 80 split-width-threshold 160 frame-resize-pixelwise nil) (if (boundp 'ns-pop-up-frames) (setq ns-pop-up-frames nil)) (when (version<= "26.0" emacs-version) (use-package display-line-numbers :config (progn (setq display-line-numbers-type 'visual) (global-display-line-numbers-mode +1)))) ;;;;; eyebrowse ;; Workspaces, a bit like dwm. On Windows and Linux (at least the WMs ;; I'm likely to use), super+{0-9} are taken from the OS, so use meta ;; instead. On macOS, super makes a lot of sense, as it's used by most ;; programs to switch between program windows or views. (use-package eyebrowse :after (evil) :config (progn (setq eyebrowse-new-workspace t) (when (eq system-type 'darwin) (bind-keys ("s-0" . eyebrowse-switch-to-window-config-0) ("s-1" . eyebrowse-switch-to-window-config-1) ("s-2" . eyebrowse-switch-to-window-config-2) ("s-3" . eyebrowse-switch-to-window-config-3) ("s-4" . eyebrowse-switch-to-window-config-4) ("s-5" . eyebrowse-switch-to-window-config-5) ("s-6" . eyebrowse-switch-to-window-config-6) ("s-7" . eyebrowse-switch-to-window-config-7) ("s-8" . eyebrowse-switch-to-window-config-8) ("s-9" . eyebrowse-switch-to-window-config-9))) (eyebrowse-mode +1) (with-eval-after-load "evil-vars" (bind-keys :map evil-window-map ("0" . eyebrowse-switch-to-window-config-0) ("1" . eyebrowse-switch-to-window-config-1) ("2" . eyebrowse-switch-to-window-config-2) ("3" . eyebrowse-switch-to-window-config-3) ("4" . eyebrowse-switch-to-window-config-4) ("5" . eyebrowse-switch-to-window-config-5) ("6" . eyebrowse-switch-to-window-config-6) ("7" . eyebrowse-switch-to-window-config-7) ("8" . eyebrowse-switch-to-window-config-8) ("9" . eyebrowse-switch-to-window-config-9))) (define-hook-helper evil-after-load () :name evil-eyebrowse-setup (eyebrowse-setup-evil-keys)))) ;;;; Sessions ;;;;; winner ;; Undo, for window-based commands. (use-package winner :config (setq winner-boring-buffers '("*Completions*" "*Help*" "*Apropos*" "*Buffer List*" "*info*" "*Compile-Log*")) :init (progn (winner-mode 1))) ;;;; Blogging ;; I have a [[https://alanpearce.uk][blog]] that I publish with hugo. (use-package easy-hugo :config (setq easy-hugo-basedir (car (split-string (shell-command-to-string "ghq list --full-path alanpearce.uk"))) easy-hugo-url "https://alanpearce.uk" easy-hugo-default-ext ".md")) ;;;; Completion ;; Make built-in completion a bit more intelligent, by adding substring ;; and initial-based completion and ignoring case. (setq completion-styles '(basic initials partial-completion substring) completion-ignore-case t tab-always-indent t) ;;;;; Company ;; The main choices for automatic completion in Emacs are company and ;; auto-complete-mode. I’ve not tried auto-complete-mode as company ;; seems to work perfectly well for me. (use-package company :bind* (("C-" . company-complete)) :bind (("TAB" . company-complete)) :init (progn (setq company-backends '(company-web-html company-tide company-tern company-nxml company-css company-eclim company-semantic company-elisp company-clang company-xcode company-cmake company-capf company-files (company-gtags company-etags company-keywords) company-oddmuse) company-frontends '(company-pseudo-tooltip-unless-just-one-frontend company-preview-frontend company-echo-metadata-frontend) company-idle-delay .3 company-begin-commands '(self-insert-command) company-auto-complete #'company-explicit-action-p company-auto-complete-chars '(?\ ?\( ?\) ?.) company-tooltip-align-annotations t company-selection-wrap-around t) (global-company-mode +1))) (use-package company-web :after company) ;;;;; EACL ;; Auto-complete lines by grepping the project. (use-package eacl :bind (("C-c " . eacl-complete-line) ("C-c C-;" . eacl-complete-statement) ("C-c C-\]" . eacl-complete-snippet) ("C-c C-/" . eacl-complete-tag))) ;;;; Dates & Times ;;;;; Calendar ;; Weeks start on Monday for me and I prefer ISO-style dates. (use-package calendar :defer 1 :config (progn (setq calendar-week-start-day 1) (calendar-set-date-style 'iso))) ;; Sometimes I want to insert a date or time into a buffer. (defun insert-date (prefix) "Insert the current date. With PREFIX, use British format. With two prefix arguments, write out the day and month name." (interactive "P") (let ((format (cond ((not prefix) "%Y-%m-%d") ((equal prefix '(4)) "%d/%m/%Y") ((equal prefix '(16)) "%A, %d %B %Y")))) (insert (format-time-string format)))) (defun insert-datetime (prefix) "Insert current date and time. With PREFIX, use ISO8601 format." (interactive "P") (let ((format (cond ((not prefix) "%Y-%m-%d %H:%M:%S") ((equal prefix '(4)) "%Y-%m-%dT%H:%M:%SZ")))) (insert (format-time-string format)))) (defun yesterday-time () "Provide the date/time 24 hours before the time now in the format of `current-time'." (timer-relative-time (current-time) -86400)) ;;;; Directories ;; Dired works quite nicely, but not always in the way I want. I don’t ;; like having so many prompts for recursive operations. Also, when I ;; have two dired windows open, assume that I’m going to be ;; copying/moving files between them. (use-package dired :defer 3 :ensure nil :config (progn (bind-key "" #'dired-find-file dired-mode-map) (bind-key "^" (lambda () (interactive) (find-alternate-file "..")) dired-mode-map) (setq dired-dwim-target t dired-recursive-copies 'top dired-recursive-deletes (if delete-by-moving-to-trash 'always 'top) dired-listing-switches "-alh") (when (and (eq system-type 'darwin) (executable-find "gls")) (setq insert-directory-program (executable-find "gls"))) (put 'dired-find-alternate-file 'disabled nil))) ;; Don’t show uninteresting files in dired listings. (defun turn-on-dired-omit-mode () "Enable dired-omit mode." (interactive) (dired-omit-mode 1)) (use-package dired-x :commands (dired-omit-mode dired-expunge) :ensure nil :config (progn (setq dired-omit-files "#\\|\\.$" dired-omit-verbose nil dired-find-subdir t dired-bind-jump nil)) :init (progn (add-hook 'dired-mode-hook #'turn-on-dired-omit-mode))) ;; Expand subfolders like a tree inside the parent (with-eval-after-load 'dired (use-package dired-subtree :functions (dired-subtree--get-ov dired-subtree-maybe-up) :init (progn (setq dired-subtree-use-backgrounds nil) (defun dired-subtree-maybe-up () "Jump up one subtree or directory" (interactive) (let ((ov (dired-subtree--get-ov))) (if ov (progn (goto-char (overlay-start ov)) (dired-previous-line 1)) (dired-up-directory)))) (bind-key "^" #'dired-subtree-maybe-up dired-mode-map) (bind-key "i" #'dired-subtree-toggle dired-mode-map)))) ;;;;; Disk usage ;; Combine dired and du (disk usage). (use-package dired-du :after dired :config (progn (setq dired-du-size-format t))) ;;;; Documentation ;;;;; ehelp ;; ehelp is a less well-known package that’s part of Emacs and slightly ;; improves the normal help commands, mostly by making quitting them easier. (use-package ehelp :bind-keymap ("C-h" . ehelp-map)) ;;;;; helpful (use-package helpful :after ehelp :bind (:map ehelp-map ("k" . helpful-key) ("v" . helpful-variable) ("f" . helpful-callable))) ;;;;; discover-my-major ;; A nicer way to browse keybindings for major modes. (use-package discover-my-major :bind (("" . discover-my-major) ("C-c C-m" . discover-my-major))) ;;;;; which-key ;; Popup keybindings following a prefix automatically. (use-package which-key :config (progn (which-key-mode 1) (which-key-setup-side-window-right-bottom))) ;;;;; eldoc ;; Documentation in the echo-area (where the minibuffer is displayed) is ;; rather useful. (use-package eldoc :commands (eldoc-mode global-eldoc-mode) :config (progn (setq eldoc-idle-delay 0.1) (global-eldoc-mode +1) (eldoc-add-command 'paredit-backward-delete 'paredit-close-round))) ;;;; Mail ;;;;; Basics (with-eval-after-load "mailcap" (when (eq system-type 'darwin) (mailcap-add-mailcap-entry "application" "pdf" '((viewer . "/usr/bin/qlmanage -p %s") (type . "application/pdf"))))) (with-eval-after-load "mm-decode" (add-to-list 'mm-discouraged-alternatives "text/html") (add-to-list 'mm-discouraged-alternatives "text/richtext")) (with-eval-after-load "mml-sec" (setq mml-secure-openpgp-encrypt-to-self t)) ;;;; Misc (defun ap/remove-extra-cr () "Remove extraneous CR codes from a file." (interactive) (save-excursion (goto-char (point-min)) (while (search-forward " " nil t) (replace-match "")))) ;;;;; Auxillary Configuration (defvar have-private-key (file-exists-p (expand-file-name "secring.gpg" "~/.gnupg/"))) (defvar gpg-agent-ssh-sock (or (getenv "GPG_AGENT_INFO") (concat "/run/user/" (number-to-string (user-uid)) "/gnupg/S.gpg-agent.ssh"))) (defun read-gpg-file (file) "Read (decrypt) given GPG file FILE." (let ((file-to-decrypt (expand-file-name file user-emacs-directory)) (ctx (epg-make-context epa-protocol))) (if (file-exists-p file-to-decrypt) (epg-decrypt-file ctx file-to-decrypt nil) (message "Decrypting %s...failed" file-to-decrypt) (error "File %s does not exist" file-to-decrypt)))) (defun load-gpg (file) "Load FILE if private key is available." (if have-private-key (load file) (message "WARNING: Couldn't load %s (No gpg key found)" file))) ; load this in a post-frame hook because gpg-agent asks for a password on first ; startup and caches it. Don't want emacs daemon to hang because of gpg-agent. (defun load-private-data () "Load encrypted config in file based upon hostname." (interactive) (if (not (file-exists-p (expand-file-name (concat (system-name) ".el.gpg") user-emacs-directory))) (message "No encrypted configuration matches system name `%s'" (system-name)) (if (not have-private-key) (message "ERROR: Private GPG key not found") (unless (or (getenv "GPG_AGENT_INFO") (getenv "SSH_AUTH_SOCK")) (start-process "gpg-agent" nil "gpg-agent" "--daemon") (setenv "SSH_AUTH_SOCK" gpg-agent-ssh-sock)) (setq password-cache-expiry nil) (unless (file-exists-p (concat pinentry--socket-dir "pinentry")) (pinentry-start) (add-hook 'kill-emacs-hook 'pinentry-stop)) (add-to-list 'load-suffixes ".el.gpg") (load-gpg (expand-file-name (system-name) user-emacs-directory))))) (defvar first-frame-hook nil "Hook for running code after first-frame is opened.") (defun first-frame-hook-handler (frame) "Hook run only after first frame FRAME is created." (remove-hook 'after-make-frame-functions #'first-frame-hook-handler) (run-at-time nil nil (lambda () (run-hooks 'first-frame-hook)))) (if (or (daemonp) (not (eq 1 (length (frame-list))))) (add-hook 'after-make-frame-functions #'first-frame-hook-handler) (run-at-time nil nil (lambda () (run-hooks 'first-frame-hook)))) (add-hook 'first-frame-hook #'load-private-data) ;;;; Minibuffer ;; Sometimes I want to use the minibuffer, but I’m already inside it. ;; Fortunately, this is possible. Of course, I need to know how many ;; minibuffers there are on the stack. (setq enable-recursive-minibuffers t) (minibuffer-depth-indicate-mode t) ;; This avoids some issue with the minibuffer and the point being behind ;; the prompt. I don’t remember what exactly. (setq minibuffer-prompt-properties '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)) ;; Occasionally, I exit emacs. I should probably reduce the frequency of this. (if (daemonp) (defalias 'exit-emacs #'delete-frame) (defalias 'exit-emacs #'save-buffers-kill-emacs)) ;;;;; swiper/ivy ;; Ivy is the new kid on the completion block. It seems to be a strong ;; replacement for helm so far. (use-package swiper :bind (("C-s" . swiper) ("C-r" . swiper) ("C-=" . swiper)) :demand t :config (progn (ivy-mode 1) (setq ivy-re-builders-alist '((t . ivy--regex-plus)) ivy-extra-directories '("./")) (ivy-set-actions 'ivy-switch-buffer '(("k" (lambda (x) (kill-buffer x) (ivy--reset-state ivy-last)) "kill"))) (add-to-list 'ivy-initial-inputs-alist '(counsel-M-x . "")))) (use-package ivy-hydra) ;;;;; counsel (use-package counsel :config (progn (bind-key "M-x" #'counsel-M-x) (bind-key "" #'counsel-M-x) (bind-key "" #'counsel-M-x) (bind-key "C-c M-x" #'execute-extended-command) (bind-key "C-x C-f" #'counsel-find-file) (bind-key "M-y" #'counsel-yank-pop) (bind-key "C-x i" #'counsel-imenu) (bind-key "M-y" #'ivy-next-line ivy-minibuffer-map) (setq counsel-ag-base-command "ag --nocolor --nogroup --hidden %s") (defadvice counsel-find-file (after find-file-sudo activate) "Find file as root if necessary." (when (and buffer-file-name (not (file-writable-p buffer-file-name))) (message "File not writable %s" buffer-file-name) (find-alternate-file (concat "/sudo::" buffer-file-name)))) (setq counsel-rg-base-command "rg -i --no-heading --line-number --hidden %s ."))) ;;;;; smex ;; Smex is my favourite way to use =M-x=. Counsel’s =counsel-M-x= ;; function uses it internally, so I’m keeping it around, even though I ;; don’t use it directly. (use-package smex :defines (smex-key-advice-ignore-menu-bar) :commands (smex smex-update smex-initialize) :config (progn (setq smex-key-advice-ignore-menu-bar t smex-auto-update nil) (defun smex-update-after-load (_unused) (if (boundp 'smex-cache) (smex-update))) (add-hook 'after-load-functions 'smex-update-after-load)) :init (progn (setq smex-history-length 100 smex-save-file (concat user-emacs-directory "smex-items")))) ;;;;; cmd-to-echo ;; I’ve been looking for some way to run programming projects (mostly ;; node.js) inside emacs. =cmd-to-echo= seems great for this, as new ;; output pops up in the echo area. (use-package cmd-to-echo :commands (cmd-to-echo) :config (setq cmd-to-echo-add-output-to-process-buffers t)) ;;;; Modes ;; Setup some modes for systemd files (add-to-list 'auto-mode-alist '("\\.service\\'" . conf-mode)) (add-to-list 'auto-mode-alist '("\\.target\\'" . conf-mode)) (add-to-list 'auto-mode-alist '("\\.socket\\'" . conf-mode)) ;; =direnv=’s files are basically shell scripts, it’s a nice way to ;; set environment variables for projects. (add-to-list 'auto-mode-alist '("\\.envrc\\'" . sh-mode)) ;; Some modes that I don’t really customise much, mostly for ;; configuration files. (use-package haskell-mode :mode (("\\.hs\\'" . haskell-mode))) (use-package dockerfile-mode :mode (("Dockerfile\\'" . dockerfile-mode))) (use-package docker-compose-mode :mode (("docker-compose.*.yml" . docker-compose-mode)) :config (progn (add-hook 'docker-compose-mode-hook #'company-mode-on))) (use-package nix-mode :mode (("\\.nix\\'" . nix-mode))) (define-derived-mode xmonad-mode haskell-mode "XM") (add-to-list 'auto-mode-alist '("xmobarrc\\'" . xmonad-mode)) (add-to-list 'auto-mode-alist '("xmonad.hs\\'" . xmonad-mode)) (use-package nginx-mode :defer t :mode (("/nginx/servers/" . nginx-mode) ("/nginx/.*\\.d/" . nginx-mode))) (use-package ruby-mode :mode (("\\.rb\\'" . ruby-mode) ("\\.cap\\'" . ruby-mode))) (use-package go-mode :mode (("\\.go\\'" . go-mode))) (use-package jinja2-mode :mode (("\\.j2\\'" . jinja2-mode) ("\\.jinja\\'" . jinja2-mode))) (use-package scss-mode :defer t :config (progn (setq scss-compile-at-save nil))) (use-package toml-mode :mode ("\\.toml\\'" . toml-mode)) (use-package yaml-mode :mode (("/group_vars/.*" . yaml-mode) ("/host_vars/.*" . yaml-mode))) (define-derived-mode ansible-mode yaml-mode "Ansible") (add-to-list 'auto-mode-alist '("\\(?:ansible.+\\|roles/.+/\\(?:tasks\\|handlers\\)\\)/.+\\.yml\\'" . ansible-mode)) (define-derived-mode saltstack-mode yaml-mode "Salt") (add-to-list 'auto-mode-alist '("\\.sls\\'" . saltstack-mode)) ;;;;; Beancount (let ((beancount-dir (car (split-string (shell-command-to-string "ghq list --full-path blais/beancount"))))) (when (and beancount-dir (file-directory-p beancount-dir)) (add-to-list 'load-path (expand-file-name "editors/emacs/" beancount-dir)) (use-package beancount :defines (beancount-use-ido beancount-mode-map beancount-mode-map-prefix) :commands beancount-mode :bind (:map beancount-mode-map ("C-c d" . insert-date)) :config (setq beancount-use-ido nil beancount-mode-map-prefix (kbd ", a"))))) ;;;;; Markdown (use-package markdown-mode :defer t) ;;;;; Org ;; Org is wünderbar. (use-package org :bind (("C-c C-a" . org-agenda-list) ("C-c a" . org-agenda) ("C-c l" . org-store-link)) :defer 8 :defines (org-table-duration-custom-format) :init (setq org-replace-disputed-keys t org-support-shift-select 'always org-ellipsis "…") :config (progn (setq org-directory "~/Documents/org" org-agenda-files `(,(concat org-directory "/agenda")) org-default-notes-file (concat org-directory "/notes") ;; ‘Remember’: new items at top org-reverse-note-order t org-modules '(org-protocol) ;; Add time done to ‘done’ tasks org-log-done 'time org-list-allow-alphabetical t org-adapt-indentation nil org-pretty-entities t org-table-duration-custom-format 'seconds org-src-fontify-natively nil org-blank-before-new-entry '((heading . t) (plain-list-item . auto)) org-fontify-done-headline t org-goto-interface 'outline-path-completion org-outline-path-complete-in-steps nil org-todo-keywords '((sequence "BACKLOG(b)" "TODO(t)" "WAIT(w@/!)" "STARTED(s!)" "|" "DONE(d!)") (sequence "|" "CANCELLED(c@)")) org-log-into-drawer "LOGBOOK") (set-register ?o `(file . ,(expand-file-name "organiser.org" org-directory))) (add-hook 'org-mode-hook #'turn-on-auto-fill) (org-load-modules-maybe t))) (use-package org-src :ensure nil :after org :config (progn (bind-key "C-x C-s" #'org-edit-src-exit org-src-mode-map))) ;;;;;;; org-babel ;; Org’s babel feature is really nice. I use it for this file, and I can ;; use it to communicate between programming languages. Sometime I hope ;; to have my =ledger= setup in an org file with some graph processing ;; with R or something. (use-package ob-core :defer t :ensure nil :config (progn (org-babel-do-load-languages 'org-babel-load-languages '((ledger . t) (shell . t))) (setq org-src-tab-acts-natively t org-edit-src-content-indentation 0 org-src-preserve-indentation t))) ;;;;;;; org-journal ;; I can use this to keep a journal. I should use it. (use-package org-journal :bind ("s-j" . org-journal-new-entry) :defer 20 :config (progn (setq org-journal-date-format "%A, %d %B %Y" org-journal-dir "~/Documents/journal/") (define-hook-helper org-journal-mode () (use-variable-fonts) (text-scale-adjust 4)) (defun org-journal-display-entry-yesterday () "Show org-journal entry for yesterday" (interactive) (org-journal-read-or-display-entry (yesterday-time))))) ;;;; Programming ;;;;; flycheck ;; On-the-fly error checking in programming modes? Yes please. (use-package flycheck :defer 5 :config (progn (global-flycheck-mode) (setq flycheck-check-syntax-automatically '(save mode-enabled)) (setq flycheck-indication-mode 'left-fringe) (with-eval-after-load 'git-gutter-fringe (fringe-helper-define 'flycheck-fringe-bitmap-double-arrow '(center repeated) "XXX.....")) (if (executable-find "eslint_d") (setq flycheck-javascript-eslint-executable "eslint_d")))) ;;;;;; flycheck-pos-tip ;; Show flycheck errors in a little popup, so I don't lose my place (use-package flycheck-pos-tip :after flycheck :config (progn (setq flycheck-display-errors-delay 0.5 flycheck-pos-tip-timeout 15) (flycheck-pos-tip-mode 1))) ;;;;; golang ;; Go has a few packages to inter-operate with other emacs packages. (use-package company-go :commands company-go :config (progn (setq company-go-show-annotation t)) :init (progn (define-hook-helper go-mode () (set (make-local-variable 'company-backends) '(company-go))))) (use-package go-eldoc :commands go-eldoc-setup :init (progn (add-hook 'go-mode-hook #'go-eldoc-setup))) (use-package go-projectile :defer t :config (progn (setq go-projectile-switch-gopath 'maybe))) ;;;;; ggtags ;; A nice completion backend for programming modes. (use-package ggtags :if (executable-find "gtags") :commands turn-on-ggtags-mode :functions (ggtags-navigation-mode-abort) :config (progn (bind-key "q" #'ggtags-navigation-mode-abort ggtags-navigation-mode-map)) :init (progn (defun turn-on-ggtags-mode () (interactive) (ggtags-mode 1)) (add-hook 'c-mode-common-hook #'turn-on-ggtags-mode))) ;;;;; dumb-jump ;; A "clever" way of implementing go-to-definition across languages: use ;; a project-wide text search and apply heuristics to the results to ;; guess a definition. (use-package dumb-jump :bind (("M-g o" . dumb-jump-go-other-window) ("M-g j" . dumb-jump-go) ("M-g x" . dumb-jump-go-prefer-external) ("M-g z" . dumb-jump-go-prefer-external-other-window)) :config (setq dumb-jump-selector 'ivy)) ;;;;; imenu-anywhere ;; This is like imenu, but shows functions (or similar top-level ;; entities) across buffers in the same project. Neat! (use-package imenu-anywhere :if (featurep 'helm-imenu) :bind ("C-x C-." . ivy-imenu-anywhere)) ;;;;; Lisps ;;;;;; All ;; Lisp modes don’t seem to have a common ancestor. So I made a custom ;; hook which I trigger in every lispy-mode. (defcustom lisp-mode-common-hook nil "Hook run when entering any Lisp mode." :type 'hook :group 'lisp) (create-hook-helper lisp-mode-setup () :hooks (emacs-lisp-mode-hook scheme-mode-hook lisp-mode-hook clojure-mode-hook) (run-hooks 'lisp-mode-common-hook)) ;;;;;;; Redshank ;; Lisp syntax allows for really easy refactoring. Redshank gives some ;; operations that aren’t part of paredit, like extracting variables into ;; let bindings. (use-package redshank :after (paredit) :config (progn (add-hook 'lisp-mode-common-hook #'turn-on-redshank-mode))) ;;;;;; Emacs Lisp ;; Go-to function for elisp. Except it works through the entire Emacs ecosystem. (use-package elisp-slime-nav :commands elisp-slime-nav-mode :init (progn (add-hook 'emacs-lisp-mode-hook #'elisp-slime-nav-mode))) ;; Interactive elisp (use-package ielm :defer t :ensure nil :config (progn (define-hook-helper ielm-mode () (run-hooks 'lisp-mode-common-hook)))) ;;;;;; Scheme & Lisp ;; I don’t work with these as often as I would like (define-hook-helper lisp-mode () (set (make-local-variable 'lisp-indent-function) #'common-lisp-indent-function)) ;;;;;;; geiser ;; A REPL thing for Scheme. Hopefully I’ll get to use it more in the ;; future. (use-package geiser :commands (geiser-mode geiser run-geiser run-racket)) ;;;;;;; slime ;; A REPL thing (and more) for Lisp. (use-package slime :commands (slime) :config (progn (let ((ql-slime-helper (expand-file-name "~/quicklisp/slime-helper.el"))) (if (file-exists-p ql-slime-helper) (load ql-slime-helper)) (slime-setup '(slime-fancy slime-asdf))) (setq common-lisp-hyperspec-root "file://opt/local/share/doc/lisp/HyperSpec-7-0/" inferior-lisp-program (or (executable-find "sbcl") (executable-find "ccl64"))))) ;;;;;; Clojure (use-package clojure-mode :defer t :init (progn (define-hook-helper cider-repl-mode () (highlight-changes-mode -1)))) (use-package clj-refactor :defer t :functions (clj-refactor-mode cljr-add-keybindings-with-prefix) :config (progn (cljr-add-keybindings-with-prefix "C-c C-m")) :init (progn (define-hook-helper clojure-mode () (clj-refactor-mode 1)))) ;;;;;;; cider ;; A REPL thing for Clojure (use-package cider :defer t :config (progn (setq nrepl-hide-special-buffers t) (unbind-key "C-c C-f" cider-mode-map))) ;;;;; Auto-compile ;; Auto-compile emacs lisp when saving, asynchronously. (use-package auto-async-byte-compile :config (add-hook 'emacs-lisp-mode-hook #'enable-auto-async-byte-compile-mode)) ;;;;; cc-mode ;; Although I don’t use C or C++, setting up the mode is helpful because ;; quite a few other modes are derived from it. (use-package cc-mode :defer 5 :config (progn (setq c-default-style '((java-mode . "java") (awk-mode . "awk") (other . "k&r")) c-basic-offset 4) (c-set-offset 'case-label '+))) ;;;;; quickrun ;; It’s nice to be able to quickly evaluate some code. Although I don’t ;; really seem to use it. (use-package quickrun :bind (("C-c C-e" . quickrun))) ;;;;; Web development ;;;;;; js2-mode ;; This mode is really great for editing Javascript. It turns code into ;; an AST internally, so it can work with it almost like a lisp. (use-package js2-mode :mode (("\\.js\\'" . js2-mode)) :interpreter ("node" . js2-mode) :functions (js2-next-error js2--struct-put) :config (progn (define-key js2-mode-map [menu-bar Javascript] nil) (add-hook 'js2-mode-hook #'js2-imenu-extras-mode) (defun ap/js2-prev-error () (interactive) (js2-next-error -1)) (bind-key "M-g M-n" #'js2-next-error js2-mode-map) (bind-key "M-g M-p" #'ap/js2-prev-error js2-mode-map) (advice-add #'js--multi-line-declaration-indentation :around (lambda (orig-fun &rest args) nil)) (setq js2-basic-offset 2 js-switch-indent-offset 2 js2-include-node-externs t js2-highlight-level 1 js2-strict-missing-semi-warning nil js2-strict-inconsistent-return-warning nil))) ;;;;;;; typescript-mode (use-package typescript-mode :config (progn (setq typescript-indent-level 2))) ;;;;;;; rjsx-mode ;; A set of advice for js2-jsx-mode to work better with React. (use-package rjsx-mode :after js2-mode :if (fboundp #'js2--struct-put) :mode (("\\.jsx\\'" . rjsx-mode))) ;;;;;;; mocha (use-package mocha :after js2-mode :config (progn (setq mocha-reporter "spec" mocha-options "--bail") (defun mocha-test-project () "Test the current project." (interactive) (mocha-run "'./{src,lib,test}/**/*.test.js'")))) ;;;;;;; js2-refactor ;; Thanks to the AST provided by js2-mode, refactoring is possible. This ;; library implements some refactorings. (use-package js2-refactor :after js2-mode :config (progn (bind-key "C-k" #'js2r-kill js2-mode-map) (add-hook 'js2-mode-hook #'js2-refactor-mode) (js2r-add-keybindings-with-prefix "C-c C-m"))) ;;;;;;; add-node-modules-path ;; Inside a javascript project, it's common to install tools locally to ;; the project. This will allows emacs to find their executables. (use-package add-node-modules-path :config (progn (create-hook-helper js2-mode () :hooks (js2-mode-hook typescript-mode-hook) :name node-modules-flycheck (add-node-modules-path) (when (executable-find "tslint") (setq-local flycheck-typescript-tslint-executable (executable-find "tslint"))) (when (executable-find "eslint") (setq-local flycheck-javascript-eslint-executable (executable-find "eslint"))) (when (executable-find "prettier-standard") (setq-local flycheck-javascript-standard-executable (executable-find "prettier-standard"))) (when (executable-find "standard") (setq-local flycheck-javascript-standard-executable (executable-find "standard")))))) ;;;;;;; Prettier ;; Reformat files on save (use-package prettier-js :config (progn (add-hook 'typescript-mode #'prettier-js-mode) (add-hook 'js2-mode #'prettier-js-mode))) ;;;;;;; Indium ;; Javascript with an inferior node.js process and a debugger? Awesome. ;; To debug with node, use version 6.9.1 or later of node and run it with ;; ~--inspect~ and, to break on the first line, ~--debug-brk~. ;; For Chrom*, it needs to be launched with ;; ~--remote-debugging-port=9222~ ;; Node will tell you to open an URL in Chrome: ;; ~chrome-devtools://inspector.html?...&ws=127.0.0.1:PORT/PATH~ ;; Instead, do this: ;; ~M-x indium-connect-to-nodejs RET 127.0.0.1 RET PORT RET~ (use-package indium :config (progn (add-hook 'js2-mode-hook #'indium-interaction-mode))) ;;;;;; tide ;; Let's write some typescript (use-package tide :after (typescript-mode company flycheck) :hook ((typescript-mode . tide-setup))) ;;;;;; tern ;; Tern understands javascript. It adds really clever documented ;; completions, besides other IDE-like things. (use-package tern :if (executable-find "tern") :defer 5 :config (progn (setq tern-command (list (executable-find "tern"))) (create-hook-helper tern-mode-on () :hooks (js2-mode-hook) (tern-mode +1)))) (with-eval-after-load 'tern (use-package company-tern)) ;;;;;; json-mode (use-package json-mode :mode (("\\.json\\'" . json-mode) ("\\.sailsrc\\'" . json-mode) ("composer\\.lock\\'" . json-mode) ("\\.tern-project\\'" . json-mode))) ;;;;;; restclient ;; Restclient is really nice. It’s like a scratchpad for HTTP api ;; calls. Feels a bit like using =org-babel=. I wonder if there’s an ;; integration between the two yet. (use-package restclient :mode ("\\.api\\'" . restclient-mode) :config (progn (defun imenu-restclient-sections () (setq imenu-prev-index-position-function nil) (add-to-list 'imenu-generic-expression '("Services" "^## ?\\(.+\\)$" 1) t) (add-to-list 'imenu-generic-expression '("Calls" "^# ?\\(.+\\)$" 1) t)) (add-hook 'restclient-mode-hook #'imenu-restclient-sections))) (use-package company-restclient :after (company restclient) :init (add-to-list 'company-backends #'company-restclient t)) ;;;;;; sgml-mode ;; This is for HTML, since old versions of HTML were derived from SGML. (use-package sgml-mode :defer t :config (setq sgml-basic-offset 2)) ;;;;;; emmet-mode ;; Emmet is really nice to write HTML quickly. Especially with ;; frameworks that require multiple nested elements to do anything useful. (use-package emmet-mode :commands (emmet-mode) :init (progn (setq emmet-indentation 2 emmet-self-closing-tag-style " /") (add-hook 'sgml-mode-hook #'emmet-mode) (add-hook 'web-mode-hook #'emmet-mode) (add-hook 'css-mode-hook #'emmet-mode))) ;;;;;; web-mode ;; This mode handles just about every templating language out ther, which ;; is really nice, because it handles the HTML part the same way in all ;; of them as well. (use-package web-mode :mode (("/views/.*\\.php\\'" . web-mode) ("\\.html\\'" . web-mode) ("/templates/.*\\.php\\'" . web-mode) ("\\.handlebars\\'" . web-mode) ("\\.ejs\\'" . web-mode) ("\\.njk\\'" . web-mode)) :config (progn (setq web-mode-code-indent-offset 2 web-mode-css-indent-offset 2 web-mode-markup-indent-offset 2 web-mode-style-padding 0 web-mode-script-padding 0 web-mode-comment-style 2 web-mode-enable-auto-pairing nil web-mode-enable-auto-quoting nil))) ;;;;; Live coding ;; Sometimes I might want to show off my emacs usage. (defun live-coding () "Configure display for live coding." (interactive) (ap/set-fonts "SF Mono" 18 nil nil t 0.1) (global-command-log-mode 1)) (defun live-coding-stop () "Revert live coding display configuration." (interactive) (ap/set-fonts-according-to-system) (global-command-log-mode -1)) ;;;;;; command-log-mode (use-package command-log-mode :defines (command-log-mode-key-binding-open-log) :config (progn (setq command-log-mode-key-binding-open-log nil command-log-mode-auto-show t command-log-mode-is-global t))) ;;;; Systems (use-package kubernetes :commands (kubernetes-overview)) (use-package k8s-mode :config (progn (setq k8s-site-docs-version "v1.11" k8s-search-documentation-browser-function #'browse-url-chrome))) (use-package kubernetes-evil :after (kubernetes evil)) ;;;; Spelling (use-package ispell :bind (("" . ispell-word)) :config (progn (cond ((executable-find "aspell") (setq ispell-program-name "aspell" ispell-dictionary "british" ispell-really-aspell t ispell-really-hunspell nil)) ((executable-find "hunspell") (setq ispell-program-name "hunspell" ispell-really-aspell nil ispell-really-hunspell t))))) (use-package flyspell :config (progn (defun flyspell-detect-ispell-args (&optional run-together) "If RUN-TOGETHER is true, spell check the CamelCase words. Please note RUN-TOGETHER will make aspell less capable. So it should only be used in prog-mode-hook." (let (args) (when ispell-program-name (cond ((string-match "aspell$" ispell-program-name) (setq args (list "--sug-mode=ultra")) (if run-together (setq args (append args '("--run-together" "--run-together-limit=16" "--run-together-min=2"))))) ((string-match "hunspell$" ispell-program-name) (setq args nil)))) args)) ;; `ispell-extra-args' is *always* used when start CLI aspell process (setq-default ispell-extra-args (flyspell-detect-ispell-args t)) ;; (setq ispell-cmd-args (flyspell-detect-ispell-args)) (defadvice ispell-word (around my-ispell-word activate) (let ((old-ispell-extra-args ispell-extra-args)) (ispell-kill-ispell t) ;; use emacs original arguments (setq ispell-extra-args (flyspell-detect-ispell-args)) ad-do-it ;; restore our own ispell arguments (setq ispell-extra-args old-ispell-extra-args) (ispell-kill-ispell t))) (defadvice flyspell-auto-correct-word (around my-flyspell-auto-correct-word activate) (let* ((old-ispell-extra-args ispell-extra-args)) (ispell-kill-ispell t) ;; use emacs original arguments (setq ispell-extra-args (flyspell-detect-ispell-args)) ad-do-it ;; restore our own ispell arguments (setq ispell-extra-args old-ispell-extra-args) (ispell-kill-ispell t))) (setq flyspell-issue-message-flag nil) (defun fly-text-mode-hook-setup () ;; Turn off RUN-TOGETHER option when spell check text-mode (setq-local ispell-extra-args (flyspell-detect-ispell-args))) (add-hook 'text-mode-hook 'fly-text-mode-hook-setup) (add-hook 'prog-mode-hook #'flyspell-prog-mode))) ;;;;; Dictionary ;; One thing I miss from macOS is the "look up" functionality to define ;; words by a certain gesture. =define-word= can provide something ;; similar, at least in Emacs. (use-package define-word :bind ("M-" . define-word-at-point)) ;;;;; Style checking ;; [[https://github.com/ValeLint/vale][Vale]] is a linter, but for prose. Neat idea! Salesman is a bad term. (use-package flycheck-vale :if (executable-find "vale") :config (progn (add-to-list 'flycheck-vale-modes 'org-mode) (add-to-list 'flycheck-vale-modes 'org-journal-mode) (flycheck-vale-setup))) ;;;; Scripting ;; Make a shell-script buffer executable after saving it, if it has a shebang. (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) (use-package sh-script :mode (("\\.zsh\\'" . shell-script-mode) ("zshenv\\'" . shell-script-mode) ("zshrc\\'" . shell-script-mode)) :config (setq sh-shell-file "/usr/bin/env zsh" sh-basic-offset 2)) (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on) ;;;;; eshell ;; I should try to get into the habit of using this more. It’s really ;; nice, when I remember to use it. (use-package eshell :bind ("C-c s" . eshell) :defer 10 :functions (eshell/pwd) :defines (eshell-prompt-function eshell-prompt-regexp) :config (progn (setq eshell-directory-name "~/.emacs.d/eshell" eshell-prompt-function (lambda () (concat (eshell/pwd) "\n$ ")) eshell-prompt-regexp (rx bol (char "$") blank)) (define-hook-helper eshell-load () (bind-key "C-c C-l" #'counsel-esh-history eshell-mode-map)))) (use-package em-smart :ensure nil :commands eshell-smart-initialize :config (progn (setq eshell-where-to-jump 'begin eshell-review-quick-commands nil eshell-smart-space-goes-to-end t))) (autoload #'eshell/cd "em-dirs") (defun eshell-goto-current-dir (&optional arg) "Open `default-directory' in eshell. Pass optional ARG to `eshell' (which see)." (interactive "P") (let ((dir default-directory)) (eshell arg) (eshell/cd dir))) (bind-key "C-c S" #'eshell-goto-current-dir) ;;;;;; Shells (use-package shell :defer t :ensure nil :config (define-key shell-mode-map (kbd "C-d") 'comint-delchar-or-eof-or-kill-buffer)) (use-package comint :defer t :ensure nil :config (bind-key "C-c C-l" #'counsel-shell-history comint-mode-map)) (defun comint-delchar-or-eof-or-kill-buffer (arg) "DWIM command for ^D to behave like in shells. Pass ARG to `comint-delchar-or-maybe-eof'." (interactive "p") (if (null (get-buffer-process (current-buffer))) (kill-buffer) (comint-delchar-or-maybe-eof arg))) ;;;; Text editing ;; Emacs has an editor within. (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil) (setq sentence-end-double-space t line-move-visual nil) (visual-line-mode +1) (global-subword-mode +1) ;;;;; align ;; =Align= is a useful command to line things up, once given some rules. ;; The most important one for me is JSON property alignment. (use-package align :defer 10 :ensure nil :config (progn (add-to-list 'align-rules-list '(colon-key-value (regexp . ":\\(\\s-*\\)") (modes . '(js2-mode)))))) ;;;;; Clipboard ;; I like to use the clipboard more than the primary selection in X11. (setq select-enable-clipboard t save-interprogram-paste-before-kill t) (if (functionp 'x-cut-buffer-or-selection-value) (setq interprogram-paste-function 'x-cut-buffer-or-selection-value)) (when (boundp 'x-select-request-type) (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))) ;;;;; avy ;; Avy is a really nice way to move around files, like ace-jump-mode, but ;; somehow I prefer it. (use-package avy :defer 5 :bind* (("M-g g" . avy-goto-line) ("M-g M-g" . avy-goto-line) ("M-r" . avy-goto-word-1) ("C-c SPC" . avy-goto-char-timer)) :config (progn (avy-setup-default) (setq avy-all-windows nil))) ;;;;;; ace-link ;; Visit any link. Despite the name, this works with avy. (use-package ace-link :after avy :config (progn (ace-link-setup-default))) ;;;;; goto-chg ;; This is like popping the mark, only it filters to only change areas ;; and doesn’t go back to the same place more than once. (use-package goto-chg) ;;;;; paredit ;; Balanced parentheses in lisps are nice, but all the refactoring and ;; movement commands are much more interesting. (use-package paredit :config (progn (add-hook 'lisp-mode-common-hook #'enable-paredit-mode) (put #'paredit-forward-delete 'delete-selection 'supersede) (put #'paredit-backward-delete 'delete-selection 'supersede) (add-hook 'eval-expression-minibuffer-setup-hook #'enable-paredit-mode))) ;;;;; undo-tree ;; Emacs’ default handling of undo is a bit confusing. Undo-tree makes ;; it much clearer. It’s especially helpful for protoyping and refactoring. (use-package undo-tree :config (progn (global-undo-tree-mode) ;; Keep region when undoing in region (defadvice undo-tree-undo (around keep-region activate) (if (use-region-p) (let ((m (set-marker (make-marker) (mark))) (p (set-marker (make-marker) (point)))) ad-do-it (goto-char p) (set-mark m) (set-marker p nil) (set-marker m nil)) ad-do-it)))) ;;;;; replace (with-eval-after-load "replace.el" (setq case-replace nil)) ;;;; Evil (setq evil-want-keybinding nil) (use-package evil-leader ;; must load before evil-mode :init (global-evil-leader-mode +1) :config (progn (evil-leader/set-leader ",") (evil-leader/set-key "h" help-map "w" evil-window-map "x" ctl-x-map "s" #'save-buffer "q" #'kill-or-delete-this-buffer-dwim "p" projectile-command-map "v" #'split-window-right "o" #'other-window "y" #'evil-avy-goto-line "bb" #'ivy-switch-buffer "bx" #'kill-this-buffer "br" #'revert-buffer "dd" #'dired "fs" #'save-buffer "ff" #'find-file "fw" #'write-file "fd" #'crux-delete-file-and-buffer "fr" #'crux-rename-file-and-buffer "gs" #'magit-status "gm" #'magit-dispatch-popup "gt" #'git-timemachine "bb" #'evil-switch-to-windows-last-buffer "bi" #'ibuffer "bz" #'bury-buffer ";" #'counsel-M-x))) (use-package evil :defines evil-window-map :config (progn (evil-mode +1) (setq-default evil-shift-width 2) (bind-keys :map evil-normal-state-map (";" . evil-ex)) (define-key evil-motion-state-map (kbd "C-;") #'evil-avy-goto-line) (define-key evil-normal-state-map [escape] #'keyboard-quit) (define-key evil-visual-state-map [escape] #'keyboard-quit) (define-key evil-window-map (kbd "q") #'kill-or-delete-this-buffer-dwim) (define-key minibuffer-local-map [escape] #'minibuffer-keyboard-quit) (define-key minibuffer-local-ns-map [escape] #'minibuffer-keyboard-quit) (define-key minibuffer-local-completion-map [escape] #'minibuffer-keyboard-quit) (define-key minibuffer-local-must-match-map [escape] #'minibuffer-keyboard-quit) (define-key minibuffer-local-isearch-map [escape] #'minibuffer-keyboard-quit) (evil-define-key 'normal emacs-lisp-mode-map (kbd "K") #'elisp-slime-nav-describe-elisp-thing-at-point))) (use-package evil-collection :if evil-mode :init (progn (setq evil-collection-company-use-tng nil)) :config (evil-collection-init)) (use-package evil-commentary :if evil-mode :config (evil-commentary-mode +1)) (use-package evil-magit :if evil-mode :defines (evil-magit-use-y-for-yank) :after magit :config (progn (setq evil-magit-use-y-for-yank nil))) (use-package evil-quickscope :if evil-mode :config (progn (global-evil-quickscope-mode +1))) (use-package evil-org :if evil-mode :config (progn (define-hook-helper org-mode () (evil-org-mode +1)) (define-hook-helper evil-org-mode () (when (fboundp 'evil-org-set-key-theme) (evil-org-set-key-theme))))) (use-package evil-org-agenda :if evil-mode :after evil-org :functions (evil-org-agenda-set-keys) :config (progn (require 'evil-org-agenda) (evil-org-agenda-set-keys))) (use-package evil-snipe :defines (evil-snipe-scope) :if evil-mode :config (progn (setq evil-snipe-scope 'visible) (evil-snipe-mode +1) (add-hook 'magit-mode-hook #'turn-off-evil-snipe-override-mode))) (use-package evil-surround :if evil-mode :config (progn (global-evil-surround-mode +1))) (use-package evil-space :if evil-mode :config (progn (evil-space-mode +1))) (use-package sentence-navigation :if (and evil-mode (boundp 'sentence-nav-evil-forward)) :config (progn (define-key evil-motion-state-map ")" #'sentence-nav-evil-forward) (define-key evil-motion-state-map "(" #'sentence-nav-evil-backward) (define-key evil-motion-state-map "g)" #'sentence-nav-evil-forward-end) (define-key evil-motion-state-map "g(" #'sentence-nav-evil-backward-end) (define-key evil-outer-text-objects-map "s" #'sentence-nav-evil-a-sentence) (define-key evil-inner-text-objects-map "s" #'sentence-nav-evil-inner-sentence))) ;;;; End ;; Start a server if possible. A daemon is already a server. (use-package server :defer 2 :if (not (daemonp)) :config (unless (server-running-p server-name) (server-start))) ;; # Local Variables: ;; # lentic-init: lentic-orgel-org-init ;; # flycheck-disabled-checkers: 'emacs-lisp-checkdoc ;; # End: