;;; 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-mail-address "alan@alanpearce.eu" user-full-name "Alan Pearce") ;; #+end_src ;;;; Packaging ;;;;; Use-package (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) ("diminish" . 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 'diminish) (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 ;; Monotropic-theme. I don't need colours. (use-package monotropic-theme :if (or window-system (daemonp)) :config (progn (load-theme 'monotropic t) (custom-theme-set-faces 'user '(git-gutter-fr:added ((t (:foreground "#b8b8b8")))) '(git-gutter-fr:modified ((t (:foreground "#b8b8b8")))) '(git-gutter-fr:deleted ((t (:foreground "#b8b8b8")))) '(mode-line ((t (:height 1.0))))))) ;; 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 :diminish highlight-stages-mode) (global-hl-line-mode +1) ;;;;; 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")))) (if (eq displays 1) (ap/set-fonts "Fira Code" 14 "Lucida Grande" 14 t nil) (ap/set-fonts "Monoid" 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 :diminish page-break-lines-mode :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))))) ;;;;; Modeline (column-number-mode -1) (line-number-mode -1) (size-indication-mode t) (require 'f) (setq frame-title-format (list "Emacs" '(buffer-file-name " — %f") '(dired-directory (" — " dired-directory)))) ;;;;; Spaceline (use-package spaceline-config :after (eyebrowse projectile flycheck) :config (progn (setq powerline-default-separator 'bar powerline-height (* 2 (ceiling (* (default-font-height) (+ 1 (* 2 line-spacing))) 2)) spaceline-responsive nil) (define-hook-helper after-setting-font () :name resize-powerline (setq powerline-height (* 2 (ceiling (* (default-font-height) (+ 1 (* 2 line-spacing))) 2))) (setq-default mode-line-format (spaceline-spacemacs-theme))) (setq-default mode-line-format (spaceline-spacemacs-theme)))) ;;;;; 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 (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)) ;;;;; Highlight Changes ;; Highlight what just changed when I undo, yank, and so on. (use-package volatile-highlights :diminish volatile-highlights-mode :config (progn (volatile-highlights-mode t))) ;;;;; Renaming major modes ;; Diminishing major modes does not happen in the same manner as minor ;; modes. (unless (version<= emacs-version "24.4") (use-package cyphejor :defer 2 :config (progn (setq cyphejor-rules `(("emacs" "ε") ("diff" "Δ") ("js2" "js") ("magit-status" ,(char-to-string (seq-find #'char-displayable-p '(11942 5848 177)))) ("inferior" "i" :prefix) ("interaction" "i" :prefix) ("interactive" "i" :prefix) ("menu" "▤" :postfix) ("ledger" "Ledger") ("mode" "") ("shell" "sh" :postfix))) (cyphejor-mode 1)))) ;;;; 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))) ;;;;; 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 (command) (if (nix-current-sandbox) (apply 'nix-shell-command (nix-current-sandbox) command) command)) flycheck-executable-find (lambda (cmd) (if (nix-current-sandbox) (nix-executable-find (nix-current-sandbox) cmd) (executable-find 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-z") (bind-key* "C-" #'other-window) (bind-key* "C-x C-r" #'revert-buffer) (bind-key* "C-x C-j" #'delete-indentation) (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-k" #'kill-or-delete-this-buffer-dwim) (bind-key "C-M-a" #'backward-paragraph text-mode-map) (bind-key "C-M-e" #'forward-paragraph text-mode-map) (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) (set-register ?z `(file . ,(expand-file-name ".config/zsh/zshrc" "~"))) ;;;;; Crux ;; I can replace most of the simple helper/wrapper functions in my ;; configuration with crux.el (use-package crux :bind (("M-o" . crux-smart-open-line-above) ("C-o" . crux-smart-open-line) ("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)) :init (progn (defalias 'delete-current-buffer-file #'crux-delete-file-and-buffer) (defalias 'rename-current-buffer-file #'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 (setq ag-project-root-function #'projectile-project-root)) (use-package wgrep-ag :after ag) ;;;;; Ripgrep ;; Step over Silver Search, here comes a new challenger. (use-package ripgrep :if (executable-find "rg")) (use-package projectile-ripgrep :after (ripgrep projectile) :if (executable-find "rg") :bind (:map projectile-command-map ("s r" . projectile-ripgrep))) ;;;;; 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 :diminish projectile-mode :config (progn (projectile-global-mode +1) (add-to-list 'projectile-globally-ignored-directories ".stversions") (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)))) (projectile-switch-project-by-name (expand-file-name project-dir (ghq--find-root)) 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 open-pull-request () (interactive) (async-shell-command "hub pr")) (defalias 'open-pr #'open-pull-request) (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") "yarn start" "yarn test") (projectile-register-project-type 'node '("package.json") "npm start" "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)) (setq counsel-projectile-rg-initial-input '(projectile-symbol-or-selection-at-point)) (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 :diminish git-gutter-mode :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 'bottom ".......X" "......XX" ".....XXX" "....XXXX") (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-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 :diminish auto-revert-mode :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)) (delete-current-buffer-file) (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 :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) (electric-indent-mode +1) ;;;;; 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) (define-hook-helper php-mode () (smart-tabs-mode indent-tabs-mode)))) ;;;;; editorconfig (use-package editorconfig :diminish editorconfig-mode :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 after-change-major-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) ;;;;; ace-window ;; I don’t often have many windows open at once, but when I do, ;; =ace-window= is really nice to jump around them in the same way that ;; =ace-jump= or =avy= work. (use-package ace-window :bind (("s-s" . ace-window)) :config (progn (setq aw-dispatch-always t aw-dispatch-alist '((?k aw-delete-window " Ace - Delete Window") (?K aw-delete-window) (?m aw-swap-window " Ace - Swap Window") (?f aw-flip-window) (?v aw-split-window-vert " Ace - Split Vert Window") (?b aw-split-window-horz " Ace - Split Horz Window") (?m delete-other-windows " Ace - Maximize Window") (?l delete-other-windows) (?, winner-undo) (?. winner-redo)) aw-keys '(?a ?r ?s ?t ?n ?e ?i ?o)))) ;;;; 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)) ;;;;; 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 :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))) (bind-keys* ("M-0" . eyebrowse-switch-to-window-config-0) ("M-1" . eyebrowse-switch-to-window-config-1) ("M-2" . eyebrowse-switch-to-window-config-2) ("M-3" . eyebrowse-switch-to-window-config-3) ("M-4" . eyebrowse-switch-to-window-config-4) ("M-5" . eyebrowse-switch-to-window-config-5) ("M-6" . eyebrowse-switch-to-window-config-6) ("M-7" . eyebrowse-switch-to-window-config-7) ("M-8" . eyebrowse-switch-to-window-config-8) ("M-9" . eyebrowse-switch-to-window-config-9)) (eyebrowse-mode +1))) ;;;; Sessions ;;;;; Desktop ;; Save my Emacs session and restore it on startup. (use-package desktop :ensure nil :config (progn (setq desktop-dirname (expand-file-name "desktop/" user-emacs-directory) desktop-base-file-name "emacs.desktop" desktop-base-lock-name "lock" desktop-path (list desktop-dirname) desktop-save 'if-exists desktop-files-not-to-save "^$" desktop-load-locked-desktop nil) (unless (file-directory-p desktop-dirname) (make-directory desktop-dirname)) (desktop-save-mode 1))) ;;;;; 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))) ;;;;; windmove ;; Directional window movement (use-package windmove :bind* (("S-" . windmove-left) ("S-" . windmove-right) ("S-" . windmove-up) ("S-" . windmove-down))) ;;;; 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 'complete) ;;;;; 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 :commands (company-mode) :diminish " C" :bind* (("C-" . company-complete)) :bind (("TAB" . company-indent-or-complete-common)) :init (progn (add-hook 'prog-mode-hook #'company-mode-on) (setq company-backends '(company-bbdb company-web-html 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))) (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))) ;;;;; Dired-narrow ;; One can already use dired with wildcards to browse a filtered ;; directory listing, but it opens a new buffer. Dired-narrow is a ;; slightly nicer interface: with a currently-open dired buffer, use =/= ;; to start filtering, =RET= to complete the filter and =g= to refresh ;; the buffer, removing the filter. (with-eval-after-load 'dired (use-package dired-narrow :bind (:map dired-mode-map ("/" . dired-narrow)))) ;;;; Documentation ;;;;; helpful (use-package helpful :after ehelp :bind (:map ehelp-map ("k" . helpful-key) ("v" . helpful-variable) ("f" . helpful-callable))) ;;;;; 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)) ;;;;; discover-my-major ;; A nicer way to browse keybindings for major modes. (use-package discover-my-major :bind ("" . discover-my-major)) ;;;;; which-key ;; Popup keybindings following a prefix automatically. (use-package which-key :diminish which-key-mode :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) :diminish eldoc-mode :config (progn (setq eldoc-idle-delay 0.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)) ;;;;; mu (when (executable-find "mu") (add-to-list 'load-path (expand-file-name "../share/emacs/site-lisp/mu4e" (f-dirname (file-truename (executable-find "mu"))))) (use-package mu4e :defines (mu4e-use-fancy-chars mu4e-attachment-dir mu4e-view-show-images mu4e-update-interval mu4e-maildir-shortcuts mu4e-headers-date-format mu4e-maildir mu4e-mu-home mu4e-sent-folder mu4e-drafts-folder mu4e-refile-folder mu4e-trash-folder mu4e-user-mail-address-list mu4e-contexts) :init (setq mail-user-agent 'mu4e-user-agent) :config (progn (setq mu4e-use-fancy-chars nil mu4e-attachment-dir "~/Downloads/" mu4e-view-show-images t mu4e-hide-index-messages t mu4e-completing-read-function #'ivy-completing-read mu4e-headers-date-format "%Y-%m-%d %H:%M" mu4e-update-interval 900 mu4e-index-cleanup nil mu4e-index-lazy-check t mu4e-maildir (expand-file-name "~/mail") mu4e-change-filenames-when-moving t message-kill-buffer-on-exit t mu4e-compose-format-flowed t message-send-mail-function #'message-send-mail-with-sendmail sendmail-program "msmtp" mu4e-get-mail-command "mbsync -a" mu4e-contexts (list (make-mu4e-context :name "Personal" :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "alan@alanpearce\..*"))) :vars `((user-mail-address . "alan@alanpearce.eu") (mu4e-sent-messages-behavior . sent) (mu4e-sent-folder . "/personal/alanpearce/Sent") (mu4e-drafts-folder . "/personal/alanpearce/Drafts") (mu4e-refile-folder . "/personal/alanpearce/Archive") (mu4e-trash-folder . "/personal/alanpearce/Trash") (mu4e-maildir-shortcuts . (("/personal/alanpearce/INBOX" . ?i) ("/personal/alanpearce/Archive" . ?a) ("/personal/alanpearce/Sent" . ?s) ("/personal/alanpearce/Spam" . ?p) ("/personal/alanpearce/Orders" . ?r))) )) (make-mu4e-context :name "Riseup" :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "alanpearce@riseup.net"))) :vars `((user-mail-address . "alanpearce@riseup.net") (mu4e-sent-messages-behavior . sent) (mu4e-sent-folder . "/personal/riseup/Sent") (mu4e-drafts-folder . "/personal/riseup/Drafts") (mu4e-refile-folder . "/personal/riseup/Archive") (mu4e-trash-folder . "/personal/riseup/Trash") (mu4e-maildir-shortcuts . (("/personal/riseup/INBOX" . ?i) ("/personal/riseup/Archive" . ?a) ("/personal/riseup/Sent" . ?s) ("/personal/riseup/Spam" . ?p))))) (make-mu4e-context :name "Satoshipay" :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "alan@satoshipay.io"))) :vars `((user-mail-address . "alan@satoshipay.io") (mu4e-sent-messages-behavior . sent) (mu4e-drafts-folder . "/satoshipay/[Gmail]/.Drafts") (mu4e-sent-folder . "/satoshipay/[Gmail]/.Sent Mail") (mu4e-refile-folder . "/satoshipay/[Gmail]/.All Mail") (mu4e-trash-folder . "/satoshipay/[Gmail]/.Bin") (mu4e-maildir-shortcuts . (("/satoshipay/INBOX" . ?i) ("/satoshipay/[Gmail]/.All Mail" . ?a) ("/satoshipay/[Gmail]/.Sent Mail" . ?s) ("/satoshipay/[Gmail]/.Spam" . ?p)))))) mu4e-user-mail-address-list (append (delq nil (mapcar (lambda (context) (when (mu4e-context-vars context) (cdr (assq 'user-mail-address (mu4e-context-vars context))))) mu4e-contexts)) '("alan@alanpearce.uk" "alan@alanpearce.co.uk"))) (define-hook-helper mu4e-view-mode () ;; try to emulate some of the eww key-bindings (local-set-key (kbd "") 'shr-next-link) (local-set-key (kbd "") 'shr-previous-link))))) ;;;; Misc (defvar *init-file* (file-truename user-init-file) "Where the Emacs init file really is, passing through symlinks.") (set-register ?e `(file . ,*init-file*)) (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 "")))) (use-package rect :ensure nil :defines (killed-rectangle) :init (defun copy-rectangle (start end) "Copy the region-rectangle." (interactive "r") (setq killed-rectangle (extract-rectangle start end)))) (defun shell-execute (to-current-buffer) "Execute shell command, replacing % with the current buffer file name. With prefix TO-CURRENT-BUFFER, insert command output into buffer." (interactive "P") (let ((file-buffer (if (buffer-file-name) (file-name-nondirectory (buffer-file-name)) "")) (command (read-shell-command "Shell command: " nil nil nil))) (shell-command (replace-regexp-in-string "%" file-buffer command) to-current-buffer))) (defun process-exit-code (program &rest args) "Run PROGRAM with ARGS and return the exit code." (apply 'call-process program nil nil nil args)) (defun narrow-to-region-indirect (start end) "Restrict editing in this buffer to the current region, indirectly." (interactive "r") (deactivate-mark) (let ((buf (clone-indirect-buffer nil nil))) (with-current-buffer buf (narrow-to-region start end)) (switch-to-buffer buf))) (bind-key* "M-!" #'shell-execute) (bind-key* "C-x r M-w" #'copy-rectangle) ;;;;; Auxillary Configuration (require 'pinentry) (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 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)) :diminish ivy-mode :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) (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)) :config (progn (define-hook-helper nix-mode () (when (fboundp 'smartparens-mode) (smartparens-strict-mode -1) (smartparens-mode -1)) (electric-pair-mode +1)))) (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 lua-mode :defer t) (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)) ;;;;; ledger ;; I use [[http://ledger-cli.org/][=ledger=]] to manage my finances. It has an Emacs mode, which ;; works really nicely. (use-package ledger-mode :mode ("\\.ledger\\'" . ledger-mode) :functions ledger-report :defines (ledger-use-iso-dates ledger-post-use-completion-engine) :init (progn (defun open-budget () (interactive) (projectile-switch-project-by-name "~/Sync/Default") (find-file (expand-file-name "ledger/my.ledger" (projectile-project-root))) (ledger-report "Budget (Cumulative)" nil))) :config (progn (setq ledger-use-iso-dates t ledger-post-use-completion-engine :ido ledger-reconcile-default-commodity "€" ledger-clear-whole-transactions t ledger-narrow-on-reconcile t ledger-reports `(("Monthly Expenses" "ledger -f %(ledger-file) reg -M \\^Flex --real -X EUR -l \"payee != 'Opening Balances'\"") ("Average Monthly Expenses (Past 12 Months)" ,(concat "ledger -f %(ledger-file) -b " (format-time-string "%Y-%m" (time-add (current-time) (days-to-time -365))) " --monthly --average balance ^Flex")) ("Expenses:This Month" "ledger -f %(ledger-file) bal \\^Flex -p \"this month\"") ("On-budget Balances" "ledger -f %(ledger-file) bal --current -R :Budget: Assets:Receivable Liabilities:Personal") ("All Account Balances" "ledger -f %(ledger-file) bal --current -R \\^Assets \\^Liabilities") ("Budget Values (Current Month)" "ledger -f %(ledger-file) bal -p \"this month\" --limit \"payee=~/budget/\" \\^Funds") ("Budget (Cumulative)" "ledger -f %(ledger-file) bal -E \\^Funds \\^Assets:Budget$") ("Budget Allocation" "ledger -f %(ledger-file) bal -p \"this month\" --limit \"payee=~/budget/\" \\^Funds --format \"\\ %-17((depth_spacer)+(partial_account))\\ %10(percent(market(display_total), market(parent.total)))\\ %16(market(display_total))\n%/\"") ("bal" "ledger -f %(ledger-file) bal") ("reg" "ledger -f %(ledger-file) reg") ("equity" "ledger -f %(ledger-file) equity") ("payee" "ledger -f %(ledger-file) reg @%(payee)") ("account" "ledger -f %(ledger-file) reg %(account)"))))) ;;;;; 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) :commands beancount-mode :bind (:map beancount-mode-map ("C-c d" . insert-date)) :config (setq beancount-use-ido nil)))) ;;;;; Markdown (use-package markdown-mode :defer t :config (progn (add-hook 'markdown-mode-hook #'turn-on-auto-fill))) ;;;;; Outshine ;; Org-ified source code. I think this might work better than ;; lentic-mode, whilst also being more general. (defvar outline-minor-mode-prefix "\M-#") (use-package outshine :bind (:map outline-minor-mode-map ("" . outline-cycle)) :config (progn (add-hook 'outline-minor-mode-hook #'outshine-hook-function) (add-hook 'emacs-lisp-mode-hook #'outline-minor-mode) (add-hook 'ledger-mode-hook #'outline-minor-mode) (add-hook 'sh-mode-hook #'outline-minor-mode))) (use-package navi-mode) ;;;;; 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 "~/Sync/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) (sh . 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 "~/Sync/Default/Documents/journal") (define-hook-helper org-journal-mode () (use-variable-fonts) (text-scale-adjust 4) (if (fboundp 'smartparens-strict-mode) (smartparens-strict-mode -1)) (if (fboundp 'show-smartparens-mode) (show-smartparens-mode -1))) (defun org-journal-display-entry-yesterday () "Show org-journal entry for yesterday" (interactive) (org-journal-read-or-display-entry (yesterday-time))))) ;;;; Programming (define-hook-helper prog-mode () :name long-lines (setq-local truncate-lines t)) ;;;;; flycheck ;; On-the-fly error checking in programming modes? Yes please. (use-package flycheck :diminish " ✓" :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-mode 1))) ;;;;;; flycheck-flow (use-package flycheck-flow :after js2-mode :if (executable-find "flow") :config (progn (flycheck-add-next-checker 'javascript-eslint 'javascript-flow))) ;;;;;; prog-fill (use-package prog-fill :bind (:map prog-mode-map ("M-q" . prog-fill)) :config (progn (setq prog-fill-floating-close-paren-p nil prog-fill-floating-open-paren-p nil))) ;;;;; 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 :bind ("C-x C-." . ivy-imenu-anywhere)) ;;;;; Language Server Protocol ;; Enable smart language features by communicating with an outside application. Neat. (use-package lsp-mode) (use-package lsp-imenu :config (progn (add-hook 'lsp-after-open-hook #'lsp-enable-imenu))) (use-package company-lsp :after (company lsp-mode) :config (progn (add-to-list 'company-backends #'company-lsp) (setq company-lsp-async nil))) ;;;;; 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 :diminish redshank-mode :after (paredit) :config (progn (add-hook 'lisp-mode-common-hook #'turn-on-redshank-mode))) ;;;;;; Emacs Lisp ;; Customise the modeline-display of =emacs-lisp-mode=. Then make sure ;; it runs the common lisp hooks. (add-hook 'emacs-lisp-mode-hook #'eldoc-mode) ;; Go-to function for elisp. Except it works through the entire Emacs ecosystem. (use-package elisp-slime-nav :commands elisp-slime-nav-mode :diminish 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) (add-hook 'cider-mode-hook #'eldoc-mode))) ;;;;; Auto-compile ;; Auto-compile emacs lisp when saving. (use-package auto-compile :defer t :init (add-hook 'emacs-lisp-mode-hook #'auto-compile-on-save-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))) ;;;;; Scala ;; Let’s try using Scala. (use-package scala-mode) ;; And add ensime, an IDE-style environment. (use-package ensime) ;;;;; 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))) ;;;;;;; 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 :diminish js2-refactor-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 "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")))))) ;;;;;;; Flow (use-package flow-minor-mode :after js2-mode :config (progn (add-hook 'js2-mode-hook #'flow-minor-enable-automatically))) ;;;;;;; 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 :diminish (indium-interaction-mode . "In") :config (progn (add-hook 'js2-mode-hook #'indium-interaction-mode))) ;;;;;; lsp-javascript (use-package lsp-javascript-typescript :if (executable-find "javascript-typescript-langserver") :config (progn (add-hook 'js2-mode-hook #'lsp-javascript-typescript-enable) (add-hook 'typescript-mode-hook #'lsp-javascript-typescript-enable))) ;;;;;; tern ;; Tern understands javascript. It adds really clever documented ;; completions, besides other IDE-like things. (use-package tern :diminish tern-mode :if (executable-find "tern") :defer 5 :config (progn (setq tern-command (list (executable-find "tern"))) (create-hook-helper tern-mode-on () :hooks (web-mode-hook 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) :diminish (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) (sp-local-pair '(web-mode) "<%" "%>"))) ;;;;; 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))) ;;;; 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 :diminish " ﹏" :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-indentation 2 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) :config (progn (setq eshell-directory-name "~/.emacs.d/eshell" eshell-prompt-function (lambda () (concat (eshell/pwd) "\n$ "))) (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 :init (progn (add-hook 'eshell-load-hook #'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) (setq-default truncate-lines nil) ;;;;; 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))) ;;;;; Selection ;; I’m quite used to deleting text by selecting it and typing. Emacs has ;; a mode for that. (use-package delsel :config (delete-selection-mode t)) ;; Sub-word movement is really nice for camel- and Pascal-case (use-package subword :diminish subword-mode :init (global-subword-mode t)) ;; I find that =zap-up-to-char= normally makes more sense to me than ;; =zap-to-char=. (use-package misc :ensure nil :bind (("M-z" . zap-up-to-char) ("M-Z" . zap-to-char))) ;; Expanding the region by semantic units was something I quite liked ;; from Sublime Text. As always, there’s a mode for that. (use-package expand-region :bind ("C-M-SPC" . er/expand-region) :config (setq expand-region-fast-keys-enabled nil)) ;;;;; 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) ("C-|" . 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 :bind ("C-c C-SPC" . goto-last-change)) ;;;;; beginend ;; In special buffers, I would rather have =M->= and =M-<= goto the ;; logical beginning/end rather than the physical ones. (use-package beginend :config (progn (beginend-setup-all) (seq-do (lambda (modepair) (diminish (cdr modepair))) beginend-modes))) ;;;;; fontawesome ;; Sometimes I might want to add a font-awesome icon to some text. ;; This package gives me two interfaces to find the icons (use-package fontawesome) ;;;;; multiple-cursors ;; I mentioned before that I’d used Sublime Text before. Multiple ;; cursors was one of my favourite features, so I was really happy when I ;; saw that multiple-cursors was released for Emacs. (use-package multiple-cursors :defer 1 :bind* (("C-." . mc/mark-next-like-this) ("C-," . mc/mark-previous-like-this) ("M-" . mc/mark-all-like-this-dwim) ("C-" . mc/mark-more-like-this-extended) ("C-S-L" . mc/edit-lines))) ;;;;; paredit ;; Balanced parentheses in lisps are nice, but all the refactoring and ;; movement commands are much more interesting. (use-package paredit :diminish "()" :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))) ;;;;; smartparens ;; I like to use smartparens where paredit isn’t already useful. Somehow ;; I didn’t find smartparens’ implementation of paredit style to be as ;; nice as the real version (eval-when-compile (require 'smartparens nil :noerror)) (use-package smartparens-config :ensure smartparens :config (progn (sp-use-smartparens-bindings) (setq sp-highlight-pair-overlay nil) (fset 'wrap-with-paren "\C-](") ;; `sp-select-next-thing-exchange' (bind-key "C-(" #'wrap-with-paren smartparens-mode-map) (bind-key "C-)" #'sp-forward-slurp-sexp smartparens-mode-map) (bind-key "M-" #'backward-kill-word smartparens-mode-map) (bind-key "M-?" #'sp-convolute-sexp smartparens-mode-map) (bind-key "C-M-t" #'sp-transpose-sexp smartparens-mode-map) (bind-key "M-R" #'sp-raise-sexp smartparens-mode-map) (bind-key "M-S" #'sp-splice-sexp smartparens-mode-map) (bind-key "C-M-s" #'sp-split-sexp smartparens-mode-map) (bind-key "M-J" #'sp-join-sexp smartparens-mode-map) (bind-key "M-" #'sp-splice-sexp-killing-backward smartparens-mode-map) (bind-key "M-" #'sp-splice-sexp-killing-forward smartparens-mode-map) (bind-key "C-M-S-k" #'sp-kill-hybrid-sexp smartparens-mode-map) (bind-key "C-S-" #'sp-slurp-hybrid-sexp smartparens-mode-map) (show-smartparens-global-mode t) (smartparens-global-strict-mode t) (define-hook-helper lisp-mode-common () (smartparens-strict-mode -1) (smartparens-mode -1)))) ;;;;; move-text ;; Transposing lines, made easier. (use-package move-text :config (move-text-default-bindings)) ;;;;; 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))) :diminish undo-tree-mode) ;;;;; replace (with-eval-after-load "replace.el" (setq case-replace nil)) ;;;;; visual-regexp ;; I don’t always remember exactly how Emacs’ regular expressions work, ;; so this package is pretty useful because it highlights everything in ;; the buffer for me. (use-package visual-regexp :bind (("C-c r" . vr/replace) ("C-c q" . vr/query-replace) ("C-c m" . vr/mc-mark))) ;;;; 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 ;; # End: