#+TITLE: Emacs, emacs, emacs #+AUTHOR: Case Duckworth #+PROPERTY: header-args :tangle config.el :comments both :mkdirp yes #+EXPORT_FILE_NAME: README.md #+BANKRUPTCY_COUNT: 3 #+Time-stamp: <2020-12-08 23:51:18 acdw> * Pave the way ** Correct =exec-path= #+begin_src emacs-lisp (let ((win-downloads "c:/Users/aduckworth/Downloads")) (dolist (path `(;; Linux ,(expand-file-name "bin" user-emacs-directory) ,(expand-file-name "~/bin") ,(expand-file-name "~/.local/bin") ,(expand-file-name "~/Scripts") ;; Windows ,(expand-file-name "emacs/bin" win-downloads) ,(expand-file-name "PortableGit/bin" win-downloads) ,(expand-file-name "PortableGit/usr/bin" win-downloads))) (when (file-exists-p path) (add-to-list 'exec-path path)))) #+end_src ** Package management *** Straight.el #+begin_src emacs-lisp (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) #+end_src *** Use-package #+begin_src emacs-lisp (setq straight-use-package-by-default t) (straight-use-package 'use-package) #+end_src *** Extra use-package keywords **** :custom-update #+begin_src emacs-lisp (straight-use-package '(use-package-custom-update :host "github" :repo "a13/use-package-custom-update")) (require 'use-package-custom-update) #+end_src ** Customize variables *** Put customizations in a separate file #+begin_src emacs-lisp (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) #+end_src *** A macro for ease of customization #+begin_src emacs-lisp (defmacro cuss (var val &optional docstring) "Basically `:custom' from `use-package', broken out." `(funcall (or (get ',var 'custom-set) #'set-default) ',var ,val)) #+end_src ** Keep a tidy =~/.emacs= #+begin_src emacs-lisp (use-package no-littering :custom (backup-directory-alist `((".*" . ,(no-littering-expand-var-file-name "backup/")))) (auto-save-file-name-transforms `((".*" ,(no-littering-expand-var-file-name "autosaves/") t))) (save-place-file (no-littering-expand-var-file-name "places")) (undo-fu-session-directory (no-littering-expand-var-file-name "undos/")) (undohist-directory (no-littering-expand-var-file-name "undos/")) (elpher-certificate-directory (no-littering-expand-var-file-name "elpher-certificates/"))) (dolist (dir '("backup" "autosaves" "undos" "elpher-certificates")) (make-directory (no-littering-expand-var-file-name dir) t)) #+end_src * Look and Feel ** Simplify the UI *** Tool bars and menu bars #+begin_src emacs-lisp (cuss default-frame-alist '((tool-bar-lines . 0) (menu-bar-lines . 0))) (menu-bar-mode -1) (tool-bar-mode -1) #+end_src *** Scroll bars #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(vertical-scroll-bars . nil)) (scroll-bar-mode -1) (add-to-list 'default-frame-alist '(horizontal-scroll-bars . nil)) (horizontal-scroll-bar-mode -1) #+end_src *** Dialog boxen #+begin_src emacs-lisp (cuss use-dialog-box nil) #+end_src *** Shorten confirmations #+begin_src emacs-lisp (fset 'yes-or-no-p #'y-or-n-p) #+end_src *** Remove the bell #+begin_src emacs-lisp (cuss visible-bell (not (string= (system-name) "larry"))) #+end_src *** Tell Ediff to setup windows better #+begin_src emacs-lisp (declare-function ediff-setup-windows-plain "ediff-wind.el") (cuss ediff-window-setup-function #'ediff-setup-windows-plain) #+end_src ** Tweak the remaining UI *** Window dividers #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(right-divider-width . 2)) (add-to-list 'default-frame-alist '(bottom-divider-width . 2)) #+end_src *** Fringes #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(left-fringe-width . 2)) (add-to-list 'default-frame-alist '(right-fringe-width . 2)) #+end_src *** Minibuffer **** Setup the minibuffer frame #+begin_src emacs-lisp (cuss minibuffer-frame-alist '((width . 80) (height . 2) (vertical-scrollbars . nil))) (set-window-scroll-bars (minibuffer-window) nil nil) #+end_src **** Keep the cursor from going into the prompt #+begin_src emacs-lisp (cuss minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) #+end_src *** Tabs **** Show the tabs as current buffer, plus window count #+begin_src emacs-lisp (cuss tab-bar-tab-name-function #'tab-bar-tab-name-current-with-count) #+end_src **** Only show the tab bar when there's more than one tab #+begin_src emacs-lisp (cuss tab-bar-show 1) #+end_src *** Cursor #+begin_src emacs-lisp (cuss cursor-type 'bar) (cuss cursor-in-non-selected-windows 'hollow) #+end_src *** Buffer names #+begin_src emacs-lisp (require 'uniquify) (cuss uniquify-buffer-name-style 'forward) #+end_src *** Buffer boundaries #+begin_src emacs-lisp (cuss indicate-buffer-boundaries '((top . right) (bottom . right) (t . nil))) (cuss indicate-empty-lines t) #+end_src ** Startup #+begin_src emacs-lisp (cuss inhibit-startup-buffer-menu t) (cuss inhibit-start-screen t) (cuss initial-buffer-choice t) (cuss initial-scratch-message ";; Hi there!\n") #+end_src ** Theme #+begin_src emacs-lisp (use-package modus-themes :straight (modus-themes :host gitlab :repo "protesilaos/modus-themes" :branch "main") :custom (modus-themes-slanted-constructs t) (modus-themes-bold-constructs t) (modus-themes-fringes nil) (modus-themes-mode-line '3d) (modus-themes-syntax 'yellow-comments) (modus-themes-intense-hl-line nil) (modus-themes-paren-match 'intense-bold) (modus-themes-links nil) (modus-themes-no-mixed-fonts nil) (modus-themes-prompts nil) (modus-themes-completions nil) (modus-themes-diffs nil) (modus-themes-org-blocks 'grayscale) (modus-themes-headings '()) (modus-themes-variable-pitch-headings t) (modus-themes-scale-headings t) (modus-themes-scale-1 1.1) (modus-themes-scale-2 1.15) (modus-themes-scale-3 1.21) (modus-themes-scale-4 1.27) (modus-themes-scale-5 1.33) :custom-face (font-lock-comment-face ((t (:inherit (custom-comment italic variable-pitch))))) :init (load-theme 'modus-operandi t)) #+end_src *** Fonts **** Define fonts #+begin_src emacs-lisp (defun font-candidate (&rest fonts) (catch :font (dolist (font fonts) (if (find-font (font-spec :name font)) (throw :font font))))) (defun acdw/setup-fonts () "Setup fonts. This has to happen after the frame is setup for the first time, so it should be added to `window-setup-hook'. It removes itself from that hook." (interactive) (set-face-attribute 'default nil :font (font-candidate "Libertinus Mono-11" "Linux Libertine Mono O-11" "Go Mono-10" "Consolas-10")) (set-face-attribute 'fixed-pitch nil :font (font-candidate "Libertinus Mono-11" "Linux Libertine Mono O-11" "Go Mono-10" "Consolas-10")) (set-face-attribute 'variable-pitch nil :font (font-candidate "Libertinus Serif-13" "Linux Libertine O-12" "Georgia-11")) (remove-hook 'window-setup-hook #'acdw/setup-fonts)) (add-hook 'window-setup-hook #'acdw/setup-fonts) #+end_src **** Variable-pitch in text modes #+begin_src emacs-lisp (add-hook 'text-mode-hook #'variable-pitch-mode) #+end_src **** Line spacing #+begin_src emacs-lisp (cuss line-spacing 0.1) #+end_src **** Unicode fonts #+begin_src emacs-lisp (use-package unicode-fonts :config (unicode-fonts-setup)) #+end_src * Interactivity ** Selectrum #+begin_src emacs-lisp (use-package selectrum :config (selectrum-mode +1)) #+end_src ** Prescient #+begin_src emacs-lisp (use-package prescient :config (prescient-persist-mode +1)) (use-package selectrum-prescient :after (selectrum prescient) :config (selectrum-prescient-mode +1)) #+end_src ** Consult #+begin_src emacs-lisp (use-package consult :after (selectrum) :straight (consult :host github :repo "minad/consult") :bind (("C-x b" . consult-buffer) ("C-x 4 b" . consult-buffer-other-window) ("C-x 5 b" . consult-buffer-other-frame) ("M-g o" . consult-outline) ("M-g l" . consult-line) ("M-y" . consult-yank-pop) (" a" . consult-apropos)) :init (fset 'multi-occur #'consult-multi-occur)) #+end_src ** Marginalia #+begin_src emacs-lisp (use-package marginalia :straight (marginalia :host github :repo "minad/marginalia" :branch "main") :custom (marginalia-annotators '((command . marginalia-annotate-command-full) (customize-group . marginalia-annotate-customize-group) (variable . marginalia-annotate-variable) (face . marginalia-annotate-face) (symbol . marginalia-annotate-symbol) (variable . marginalia-annotate-variable) (package . marginalia-annotate-package))) :init (marginalia-mode +1)) #+end_src ** Ignore case #+begin_src emacs-lisp (cuss completion-ignore-case t) (cuss read-buffer-completion-ignore-case t) (cuss read-file-name-completion-ignore-case t) #+end_src ** Search #+begin_src emacs-lisp (use-package ctrlf :custom (ctrlf-show-match-count-at-eol nil) :bind ("C-s" . ctrlf-forward-regexp) ("C-r" . ctrlf-backward-regexp) ("C-M-s" . ctrlf-forward-literal) ("C-M-r" . ctrlf-backward-literal) :config (ctrlf-mode +1)) #+end_src * Persistence ** Save history #+begin_src emacs-lisp (require 'savehist) (cuss savehist-additional-variables '(kill-ring search-ring regexp-search-ring)) (cuss savehist-save-minibuffer-history t) (cuss history-length t) (cuss history-delete-duplicates t) (savehist-mode +1) #+end_src ** Save places in files #+begin_src emacs-lisp (require 'saveplace) (cuss save-place-forget-unreadable-files (not (eq system-type 'windows-nt))) (save-place-mode 1) #+end_src ** Recent files #+begin_src emacs-lisp (require 'recentf) (cuss recentf-max-menu-items 100) (cuss recentf-max-saved-items 100) (with-eval-after-load 'no-littering (add-to-list 'recentf-exclude no-littering-var-directory) (add-to-list 'recentf-exclude no-littering-etc-directory)) (recentf-mode 1) #+end_src *** Easily navigate recent files #+begin_src emacs-lisp (defun recentf-find-file () "Find a recent file using `completing-read'." (interactive) (let ((file (completing-read "Recent file: " recentf-list nil t))) (when file (find-file file)))) (global-set-key (kbd "C-x C-r") #'recentf-find-file) #+end_src ** Undo #+begin_src emacs-lisp (use-package undohist :config (undohist-initialize)) #+end_src * Editing ** Operate visually on lines #+begin_src emacs-lisp (global-visual-line-mode +1) #+end_src ** Require a final newline #+begin_src emacs-lisp (cuss require-final-newline t) #+end_src ** Killing & Yanking *** Replace selection when typing #+begin_src emacs-lisp (delete-selection-mode +1) #+end_src *** Save existing clipboard text into kill ring before replacing it #+begin_src emacs-lisp (cuss save-interprogram-paste-before-kill t) #+end_src ** So long mode #+begin_src emacs-lisp (when (fboundp 'global-so-long-mode) (global-so-long-mode)) #+end_src * Files ** Encoding *** UTF-8 #+begin_src emacs-lisp (set-language-environment 'utf-8) (set-terminal-coding-system 'utf-8) (cuss locale-coding-system 'utf-8) (set-default-coding-systems 'utf-8) (set-selection-coding-system 'utf-8) (prefer-coding-system 'utf-8) #+end_src *** Convert all files to UNIX-style line endings from [[https://www.emacswiki.org/emacs/EndOfLineTips][Emacs Wiki]]. #+begin_src emacs-lisp (defun ewiki/no-junk-please-were-unixish () "Convert line endings to UNIX, dammit." (let ((coding-str (symbol-name buffer-file-coding-system))) (when (string-match "-\\(?:dos\\|mac\\)$" coding-str) (set-buffer-file-coding-system 'unix)))) #+end_src I add it to the ~find-file-hook~ /and/ ~before-save-hook~ because I don't want to ever work with anything other than UNIX line endings ever again. I just don't care. Even Microsoft Notepad can handle UNIX line endings, so I don't want to hear it. #+begin_src emacs-lisp (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish) (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish) #+end_src ** Backups #+begin_src emacs-lisp (cuss backup-by-copying 1) (cuss delete-old-versions -1) (cuss version-control t) (cuss vc-make-backup-files t) #+end_src ** Auto-saves #+begin_src emacs-lisp (auto-save-visited-mode 1) #+end_src ** Revert files #+begin_src emacs-lisp (cuss auto-revert-verbose nil) (global-auto-revert-mode +1) #+end_src ** Add a timestamp to files #+begin_src emacs-lisp (add-hook 'before-save-hook #'time-stamp) #+end_src * Programming ** Which function are we in? #+begin_src emacs-lisp (which-function-mode +1) #+end_src * Writing ** Visual Fill Column #+begin_src emacs-lisp (use-package visual-fill-column :custom (split-window-preferred-function 'visual-fill-column-split-window-sensibly) (visual-fill-column-center-text t) (fill-column 80) :config (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust) :hook (text-mode . visual-fill-column-mode)) #+end_src ** Type nice-looking quote-type marks #+begin_src emacs-lisp (use-package typo :hook (text-mode . typo-mode)) #+end_src * Applications ** Magit #+begin_src emacs-lisp (use-package magit :bind ("C-x g" . magit-status)) #+end_src * Appendices ** Emacs' files *** init.el :PROPERTIES: :header-args: :tangle init.el :END: #+begin_src emacs-lisp :comments no ;; init.el -*- lexical-binding: t -*- #+end_src **** Load config from [[https://protesilaos.com/dotemacs/#h:584c3604-55a1-49d0-9c31-abe46cb1f028][Protesilaos Stavrou]]. #+begin_src emacs-lisp (let* ((conf (expand-file-name "config" user-emacs-directory)) (elc (concat conf ".elc")) (el (concat conf ".el")) (org (concat conf ".org"))) (cond ((file-exists-p elc) (load-file elc)) ((file-exists-p el) (load-file el)) (t (require 'org) (org-babel-load-file org)))) #+end_src *** early-init.el :PROPERTIES: :header-args: :tangle early-init.el :END: #+begin_src emacs-lisp :comments no ;; early-init.el -*- lexical-binding: t; no-byte-compile: t; -*- #+end_src #+begin_src emacs-lisp (setq load-prefer-newer t) (setq frame-inhibit-implied-resize t) #+end_src ** Ease tangling and loading of Emacs' init #+begin_src emacs-lisp (defun acdw/tangle-and-load-init () (interactive) "If the current buffer is `config.org', tangle it, then byte-compile." (let ((config (expand-file-name "config.org" user-emacs-directory))) (when (string= (buffer-file-name) config) (let ((prog-mode-hook nil)) (require 'org) (org-babel-tangle-file config) (org-md-export-to-markdown) (dolist (file `(,(expand-file-name "init.el" user-emacs-directory) ,(expand-file-name "config.el" user-emacs-directory))) (byte-compile-file file t)))))) (add-hook 'after-save-hook #'acdw/tangle-and-load-init) #+end_src ** License :PROPERTIES: :header-args: :tangle LICENSE :comments no :END: Copyright © 2020 Case Duckworth This work is free. You can redistribute it and/or modify it under the terms of the Do What the Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the =LICENSE= file, tangled from the following source block, for details. #+begin_src text DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. #+end_src *** Note on the license It's highly likely that the WTFPL is completely incompatible with the GPL, for what should be fairly obvious reasons. To that, I say: *SUE ME, RMS!*