#+TITLE: Emacs configuration, literate-style #+AUTHOR: Case Duckworth * About me #+begin_src emacs-lisp :noweb-ref settings (setq user-full-name "Case Duckworth" user-mail-address "acdw@acdw.net") #+end_src ** Where I am #+begin_src emacs-lisp :noweb-ref settings (setq calendar-location-name "Baton Rouge, LA" calendar-latitude 30.4 calendar-longitude -91.1) #+end_src * Look and feel ** Frames *** Initial frame setup :PROPERTIES: :header-args: :noweb-ref early-init-frame :END: I tangle this section to =early-init.el=, since that's evaluated before GUI set-up. Which, in turn, means Emacs will skip the "flash of unstyled content" thing. **** Tool bar #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(tool-bar-lines . 0)) (tool-bar-mode -1) #+end_src **** Menu bar #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(menu-bar-lines . 0)) (menu-bar-mode -1) #+end_src **** Scroll bars #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(vertical-scroll-bars . nil) '(horizontal-scroll-bars . nil)) (scroll-bar-mode -1) (horizontal-scroll-bar-mode -1) #+end_src **** Resizing I don't want the frame to resize when I change fonts and stuff, and I want it to resize by pixels -- we /are/ using a GUI, after all. #+begin_src emacs-lisp (setq-default frame-inhibit-implied-resize t frame-resize-pixelwise t) #+end_src *** Frame titles #+begin_src emacs-lisp :noweb-ref settings (setq-default frame-title-format (concat invocation-name "@" (system-name) ": %b %+%+ %f")) #+end_src *** Fringes :PROPERTIES: :header-args: :noweb-ref settings :END: I have grown to love Emacs's little fringes on the side of the windows. In fact, I love them so much that I really went overboard and have made a custom fringe bitmap. **** Indicate empty lines after the end of the buffer #+begin_src emacs-lisp (setq-default indicate-empty-lines t) #+end_src **** Indicate the boundaries of the buffer #+begin_src emacs-lisp (setq-default indicate-buffer-boundaries 'right) #+end_src **** Indicate continuation lines, but only on the left fringe #+begin_src emacs-lisp (setq-default visual-line-fringe-indicators '(left-curly-arrow nil)) #+end_src **** Customize fringe bitmaps ***** Curly arrows (continuation lines) #+begin_src emacs-lisp (define-fringe-bitmap 'left-curly-arrow [#b11000000 #b01100000 #b00110000 #b00011000]) (define-fringe-bitmap 'right-curly-arrow [#b00011000 #b00110000 #b01100000 #b11000000]) #+end_src ***** Arrows (truncation lines) #+begin_src emacs-lisp (define-fringe-bitmap 'left-arrow [#b00000000 #b01010100 #b01010100 #b00000000]) (define-fringe-bitmap 'right-arrow [#b00000000 #b00101010 #b00101010 #b00000000]) #+end_src ** Windows *** Switch to other window or buffer I grabbed this from [[https://www.reddit.com/r/emacs/comments/kz347f/what_parts_of_your_config_do_you_like_best/gjlnp2c/][u/astoff1]]: it extends the =other-window= command to switch to the other buffer if there's only one window in the frame. #+begin_src emacs-lisp :noweb-ref functions (defun other-window-or-buffer () "Switch to other window, or the previous buffer." (interactive) (if (eq (count-windows) 1) (switch-to-buffer nil) (other-window 1))) #+end_src And I'll bind it to =M-o=, since that's easier to reach than =C-x o=. #+begin_src emacs-lisp :noweb-ref bindings (define-key global-map (kbd "M-o") #'other-window-or-buffer) #+end_src ** Buffers *** Uniquify buffers The default way Emacs makes buffer names unique is really ugly and, dare I say it, stupid. Instead, I want them to be uniquified by their filesystem paths. #+begin_src emacs-lisp :noweb-ref requires (require 'uniquify) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default uniquify-buffer-name-style 'forward) #+end_src *** Startup buffers When Emacs starts up, I want a blank slate: the *scratch* buffer. I also want it to show a cute little message to myself. #+begin_src emacs-lisp :noweb-ref settings (setq-default inhibit-startup-screen t ; Don't show that splash screen thing. initial-buffer-choice t ; Start on *scratch* initial-scratch-message (concat ";; Howdy, " (nth 0 (split-string user-full-name)) "!\n" ";; Welcome to Emacs." "\n\n")) #+end_src *** Immortal =*scratch*= buffer I don't want to accidentally kill the *scratch* buffer. So, I add a function to the =kill-buffer-query-functions= hook that will return =nil= if the buffer is *scratch*. #+begin_src emacs-lisp :noweb-ref functions (defun immortal-scratch () (if (not (eq (current-buffer) (get-buffer "*scratch*"))) t (bury-buffer) nil)) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'kill-buffer-query-functions #'immortal-scratch) #+end_src *** Kill buffers better #+begin_src emacs-lisp :noweb-ref functions (defun kill-a-buffer (&optional prefix) "Kill a buffer and its window, prompting only on unsaved changes. `kill-a-buffer' uses the PREFIX argument to determine which buffer(s) to kill: 0 => Kill current buffer & window 4 (C-u) => Kill OTHER buffer & window 16 (C-u C-u) => Kill ALL OTHER buffers & windows" (interactive "P") (pcase (or (car prefix) 0) (0 (kill-current-buffer) (unless (one-window-p) (delete-window))) (4 (other-window 1) (kill-current-buffer) (unless (one-window-p) (delete-window))) (16 (mapc #'kill-buffer (delq (current-buffer) (buffer-list))) (delete-other-windows)))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key ctl-x-map "k" #'kill-a-buffer) #+end_src ** Cursor *** Cursor shape I like a vertical bar, but only in the selected window. #+begin_src emacs-lisp :noweb-ref settings (setq-default cursor-type 'bar cursor-in-non-selected-windows nil) #+end_src *** Don't blink the cursor #+begin_src emacs-lisp :noweb-ref modes (blink-cursor-mode -1) #+end_src ** Tabs *** Tab names #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-bar-tab-name-function #'tab-bar-tab-name-current-with-count) #+end_src *** When to show the tab bar Only when there's more than one tab. #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-bar-show 1) #+end_src ** Fonts I have different fonts installed on Linux and on Windows. #+begin_src emacs-lisp :noweb-ref linux-specific (set-face-attribute 'default nil :family "Iosevka Acdw" :height 105) (set-face-attribute 'fixed-pitch nil :family "Iosevka Acdw" :height 105) #+end_src #+begin_src emacs-lisp :noweb-ref windows-specific (set-face-attribute 'default nil :family "Consolas" :height 110) (set-face-attribute 'fixed-pitch nil :family "Consolas" :height 110) #+end_src *** Underlines I like the /fancy/ underlines in newer browsers that skip all the descenders. Emacs doesn't /quite/ have that, but it can put the underline below all the text. #+begin_src emacs-lisp :noweb-ref settings (setq-default x-underline-at-descent-line t) #+end_src ** Theming *** Modus themes :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'modus-themes) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default modus-themes-slanted-constructs t modus-themes-bold-constructs t modus-themes-region 'bg-only modus-themes-org-blocks 'grayscale modus-themes-headings '((1 . line) (t . t)) modus-themes-scale-headings nil) #+end_src *** Change themes based on time of day #+begin_src emacs-lisp :noweb-ref functions (defun acdw/run-with-sun (sunrise-command sunset-command) "Run commands at sunrise and sunset." (let* ((times-regex (rx (* nonl) (: (any ?s ?S) "unrise") " " (group (repeat 1 2 digit) ":" (repeat 1 2 digit) (: (any ?a ?A ?p ?P) (any ?m ?M))) (* nonl) (: (any ?s ?S) "unset") " " (group (repeat 1 2 digit) ":" (repeat 1 2 digit) (: (any ?a ?A ?p ?P) (any ?m ?M))) (* nonl))) (ss (sunrise-sunset)) (_m (string-match times-regex ss)) (sunrise-time (match-string 1 ss)) (sunset-time (match-string 2 ss))) (run-at-time sunrise-time (* 60 60 24) sunrise-command) (run-at-time sunset-time (* 60 60 24) sunset-command))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (acdw/run-with-sun #'modus-themes-load-operandi #'modus-themes-load-vivendi) #+end_src *** Mode line **** Simple modeline :package: After trying =doom-mode-line= and =smart-mode-line=, I think I've finally landed on a good one: =simple-modeline=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'simple-modeline) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default simple-modeline-segments '((simple-modeline-segment-modified simple-modeline-segment-buffer-name simple-modeline-segment-position) (simple-modeline-segment-minor-modes simple-modeline-segment-input-method simple-modeline-segment-vc simple-modeline-segment-misc-info simple-modeline-segment-process simple-modeline-segment-major-mode))) #+end_src #+begin_src emacs-lisp :noweb-ref modes (simple-modeline-mode +1) #+end_src **** Blackout some modes :package: Like =diminish= or =delight=, =blackout= allows me to remove some minor-modes from the modeline. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(blackout :host github :repo "raxod502/blackout")) #+end_src * Interactivity ** Dialogs and alerts *** Don't use a dialog box Ask in the modeline instead. #+begin_src emacs-lisp :noweb-ref settings (setq-default use-dialog-box nil) #+end_src *** Yes or no questions I just want to type =y= or =n=, okay? #+begin_src emacs-lisp :noweb-ref functions (fset 'yes-or-no-p #'y-or-n-p) #+end_src *** The Bell The only system I /sort of/ like the bell on is my Thinkpad, which does a little on-board speaker beep. Until I can figure out how to let it do its thing, though, I'll just change the bell on all my systems. #+begin_src emacs-lisp :noweb-ref settings (setq-default visible-bell nil ring-bell-function #'flash-mode-line) #+end_src **** Flash the mode-line #+begin_src emacs-lisp :noweb-ref functions (defun flash-mode-line () (invert-face 'mode-line) (run-with-timer 0.2 nil #'invert-face 'mode-line)) #+end_src ** Minibuffer *** Keep the cursor away from the minibuffer prompt #+begin_src emacs-lisp :noweb-ref settings (setq-default minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) #+end_src *** Enable a recursive minibuffer #+begin_src emacs-lisp :noweb-ref (setq-default enable-recursive-minibuffers t) #+end_src *** Show the recursivity of the minibuffer in the mode-line #+begin_src emacs-lisp :noweb-ref modes (minibuffer-depth-indicate-mode +1) #+end_src ** Completing-read *** Shadow file names When typing =~= or =/= in the file-selection dialog, Emacs "pretends" that you've typed them at the beginning of the line. By default, however, it only /fades out/ the previous contents of the line. I want to /hide/ those contents. #+begin_src emacs-lisp :noweb-ref settings (setq-default file-name-shadow-properties '(invisible t)) #+end_src #+begin_src emacs-lisp :noweb-ref modes (file-name-shadow-mode +1) #+end_src *** Ignore case #+begin_src emacs-lisp :noweb-ref (setq-default completion-ignore-case t read-buffer-completion-ignore-case t read-file-name-completion-ignore-case t) #+end_src * Persistence ** Minibuffer history The =savehist= package saves minibuffer history between sessions, as well as the option for some other variables. Since storage is cheap, I keep all of it. #+begin_src emacs-lisp :noweb-ref requires (require 'savehist) #+end_src #+begin_src emacs-lisp :noweb-ref modes (setq-default savehist-additional-variables '(kill-ring search-ring regexp-search-ring) history-length t ; Don't truncate history-delete-duplicates t) #+end_src #+begin_src emacs-lisp :noweb-ref modes (savehist-mode +1) #+end_src ** File places The =saveplace= package saves where I've been in my visited files. #+begin_src emacs-lisp :noweb-ref requires (require 'saveplace) #+end_src Since storage is cheap, but I'm impatient -- especially on Windows -- I'm not going to check whether the files =save-place= saves the places of are readable or not. #+begin_src emacs-lisp :noweb-ref settings (setq-default save-place-forget-unreadable-files (when-at :home)) #+end_src #+begin_src emacs-lisp :noweb-ref modes (save-place-mode +1) #+end_src ** Recent files I also like to keep track of recently-opened files. =recentf= helps with that. #+begin_src emacs-lisp :noweb-ref requires (require 'recentf) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default recentf-max-menu-items 100 recentf-max-saved-items nil) #+end_src #+begin_src emacs-lisp :noweb-ref modes (recentf-mode +1) #+end_src I also want to ignore the =no-littering-var-directory= and =no-littering-etc-directory=, since those aren't useful. #+begin_src emacs-lisp :noweb-ref no-littering (add-to-list 'recentf-exclude no-littering-var-directory) (add-to-list 'recentf-exclude no-littering-etc-directory) #+end_src *** Save the recentf list periodically #+begin_src emacs-lisp :noweb-ref functions (defun maybe-save-recentf () "Save `recentf-file' every five minutes, but only when out of focus." (defvar recentf--last-save (time-convert nil 'integer) "When we last saved the `recentf-save-list'.") (when (> (time-convert (time-since recentf--last-save) 'integer) (* 60 5)) (setq-default recentf--last-save (time-convert nil 'integer)) (when-unfocused #'recentf-save-list))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-function :after after-focus-change-function #'maybe-save-recentf) #+end_src * Responsiveness Emacs has a slew of well-documented problems with snappiness. Luckily, there are a number of solutions. ** Only do things when unfocused Sometimes, we can fake responsiveness by only performing commands when the user is looking at something else. #+begin_src emacs-lisp :noweb-ref functions (defun when-unfocused (func &rest args) "Run FUNC, with ARGS, iff all frames are out of focus." (when (seq-every-p #'null (mapcar #'frame-focus-state (frame-list))) (apply func args))) #+end_src ** Garbage collection *** Garbage Collection Magic Hack :package: Look, I'm not going to look too deeply into this. It's /magic/ afer all. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'gcmh) #+end_src #+begin_src emacs-lisp :noweb-ref modes (gcmh-mode +1) (blackout 'gcmh-mode) #+end_src *** Garbage Collect when out of focus #+begin_src emacs-lisp :noweb-ref hooks (defun hook--gc-when-unfocused () (when-unfocused #'garbage-collect)) (add-function :after after-focus-change-function #'hook--gc-when-unfocused) #+end_src * Files ** Encoding *** UTF-8 It's 2020. Let's encode files like it is. #+begin_src emacs-lisp :noweb-ref settings (prefer-coding-system 'utf-8) (set-default-coding-systems 'utf-8) (set-terminal-coding-system 'utf-8) (set-keyboard-coding-system 'utf-8) (setq-default buffer-file-coding-system 'utf-8 x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) #+end_src *** UNIX-style line endings This function is from the [[https://www.emacswiki.org/emacs/EndOfLineTips][Emacs Wiki]]. #+begin_src emacs-lisp :noweb-ref functions (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 both =file-find-hook= /and/ =before-save-hook= because I'm /that/ over it. 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 :noweb-ref hooks (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish) (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish) #+end_src ** Keep =~/.emacs.d= clean :package: #+begin_src emacs-lisp :noweb-ref packages :noweb yes (straight-use-package 'no-littering) (require 'no-littering) (with-eval-after-load 'no-littering <> ) ; end of no-littering #+end_src ** Backups #+begin_src emacs-lisp :noweb-ref settings (setq-default backup-by-copying t ;; Don't delete old versions delete-old-versions -1 ;; Make numeric backups unconditionally version-control t ;; Also backup files covered by version control vc-make-backup-files t) #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (let ((dir (no-littering-expand-var-file-name "backup"))) (make-directory dir :parents) (setq-default backup-directory-alist `((".*" . ,dir)))) #+end_src ** Autosaves :package: I don't use the =auto-save= system, preferring instead to use Bozhidar Batsov's [[https://github.com/bbatsov/super-save][super-save]] package. #+begin_src emacs-lisp :noweb-ref settings (setq-default auto-save-default nil) (setq-default super-save-remote-files nil super-save-exclude '(".gpg") super-save-auto-save-when-idle t) #+end_src #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'super-save) #+end_src #+begin_src emacs-lisp :noweb-ref modes (super-save-mode +1) (blackout 'super-save-mode) #+end_src ** Auto-revert files I like to keep the buffers Emacs has in-memory in sync with the actual contents of the files the represent on-disk. Thus, we have =auto-revert-mode=. #+begin_src emacs-lisp :noweb-ref settings (setq-default auto-revert-verbose nil) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-auto-revert-mode +1) #+end_src * Editing ** Lines *** Auto-fill vs. Visual-line I've mostly been using visual-line mode, and it's been pretty good. There are some times, however, when lines are just ... really long, and they wrap weird or whatever. Not to mention, in Org mode, =visual-line-mode= screws up the bindings for line movement. So here's what I'm going to do. 1. Enable =visual-line-mode= with =text-mode=, but /not/ with =org-mode=. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--visual-line-mode () (unless (eq major-mode 'org-mode) (visual-line-mode +1))) (add-hook 'text-mode-hook #'hook--visual-line-mode) #+end_src 2. Enable =auto-fill-mode= with =org-mode=. #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'org-mode-hook #'auto-fill-mode) #+end_src 3. /Just/ in case ... let's "fix" =visual-line-mode= if we're in =org-mode=. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--visual-line-fix-org-keys () (when (derived-mode-p 'org-mode) (local-set-key (kbd "C-a") #'org-beginning-of-line) (local-set-key (kbd "C-e") #'org-end-of-line) (local-set-key (kbd "C-k") #'org-kill-line))) (add-hook 'visual-line-mode-hook #'hook--visual-line-fix-org-keys) #+end_src I think that'll work -- I only care about line aesthetics with text. Programming modes should be /allowed/ to have long lines, regardless of how /terrible/ it is to have them. *** Stay snappy with long-lined files #+begin_src emacs-lisp :noweb-ref modes (when (fboundp 'global-so-long-mode) (global-so-long-mode +1)) #+end_src ** Whitespace *** Whitespace style The =whitespace-style= defines what kinds of whitespace to clean up on =whitespace-cleanup=, as well as what to highlight (if that option is enabled). #+begin_src emacs-lisp :noweb-ref settings (setq-default whitespace-style '(empty ; remove blank lines at buffer edges indentation ; clean up indentation ;; fix mixed spaces and tabs space-before-tab space-after-tab)) #+end_src *** Clean up whitespace on save #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'before-save-hook #'whitespace-cleanup) #+end_src *** Don't use TABs I was team TAB for a while, but I find them easier to avoid in Emacs. It manages my whitespace for me, anyway. #+begin_src emacs-lisp :noweb-ref settings (setq-default indent-tabs-mode nil) #+end_src ** Killing & Yanking *** Replace the selection when typing #+begin_src emacs-lisp :noweb-ref modes (delete-selection-mode +1) #+end_src *** Work better with the system clipboard #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; Save existing clipboard text to the kill ring before replacing it. save-interprogram-paste-before-kill t ;; Update the X selection when rotating the kill ring. yank-pop-change-selection t ;; Enable clipboards x-select-enable-clipboard t x-select-enable-primary t ;; Copy a region when it's selected with the mouse mouse-drag-copy-region t) #+end_src *** Don't append the same thing twice to the kill ring #+begin_src emacs-lisp :noweb-ref settings (setq-default kill-do-not-save-duplicates t) #+end_src ** Overwrite mode *** Change the cursor #+begin_src emacs-lisp :noweb-ref hooks (defun hook--overwrite-mode-change-cursor () (setq cursor-type (if overwrite-mode t 'bar))) (add-hook 'overwrite-mode-hook #'hook--overwrite-mode-change-cursor) #+end_src ** The Mark *** Repeat popping the mark without repeating the prefix argument #+begin_src emacs-lisp :noweb-ref settings (setq-default set-mark-repeat-command-pop t) #+end_src ** Undo :package: *** Undo Fu #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'undo-fu) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key global-map (kbd "C-/") #'undo-fu-only-undo) (define-key global-map (kbd "C-?") #'undo-fu-only-redo) #+end_src *** Undo Fu session I'm not putting this in [[*Persistence]] because it'd be confusing away from =undo-fu=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'undo-fu-session) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'")) #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (let ((dir (no-littering-expand-var-file-name "undos"))) (make-directory dir :parents) (setq-default undo-fu-session-directory dir)) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-undo-fu-session-mode +1) #+end_src * Writing ** Word count :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'wc-mode) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'text-mode-hook #'wc-mode) #+end_src * Programming ** Comments *** Auto fill comments in programming modes Okay, so I lied in the [[*Auto-fill vs. Visual-line][Auto-fill vs. Visual-line]] section. I /do/ want to auto-fill in programming modes, but /only/ the comments. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--comment-auto-fill () (setq-local comment-auto-fill-only-comments t) (auto-fill-mode +1)) (add-hook 'prog-mode-hook #'hook--comment-auto-fill) #+end_src ** Parentheses *** Show parentheses #+begin_src emacs-lisp :noweb-ref modes (show-paren-mode +1) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default show-paren-delay 0 ;; Show the matching paren if visible, else the whole expression show-paren-style 'mixed) #+end_src ** Executable scripts This poorly-named function will make a file executable if it looks like a script (looking at the function definition, it looks like it checks for a shebang). #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) #+end_src ** Language-specific *** Emacs Lisp **** Don't limit the length of evaluated expressions #+begin_src emacs-lisp :noweb-ref settings (setq-default eval-expression-print-length nil eval-expression-print-level nil) #+end_src **** Indent Elisp like Common Lisp #+begin_src emacs-lisp :noweb-ref requires (require 'cl-lib) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default lisp-indent-function #'common-lisp-indent-function) #+end_src * Applications Emacs is well-known for its ability to subsume one's entire computing life. There are a few /killer apps/ that make Emacs really shine. Here, I configure them and a few others. My rubric for what makes a package an application, versus just a package, is mostly based on the way I feel about it. Don't expect to agree with all of my choices. ** Web browsing *** Browse-url I like using Firefox. #+begin_src emacs-lisp :noweb-ref settings (setq-default browse-url-browser-function 'browse-url-firefox browse-url-new-window-flag t browse-url-firefox-new-window-is-tab t) #+end_src At work, I need to tell Emacs where Firefox is. #+begin_src emacs-lisp :noweb-ref windows-specific (add-to-list 'exec-path "C:/Program Files/Mozilla Firefox") #+end_src ** Dired #+begin_src emacs-lisp :noweb-ref hooks (defun hook--dired-mode () (hl-line-mode +1) (dired-hide-details-mode +1)) (add-hook 'dired-mode-hook #'hook--dired-mode) #+end_src A note on =dired-listing-switches=: when I'm able to figure out how to move up a directory with a keybinding, I'll change =-a= to =-A=. #+begin_src emacs-lisp :noweb-ref settings (setq-default dired-recursive-copies 'always dired-recursive-deletes 'always delete-by-moving-to-trash t dired-listing-switches "-alh") #+end_src *** Expand subtrees :package: Instead of opening each folder in its own buffer, =dired-subtree= enables me to open them in the same buffer, fancily indented. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'dired-subtree) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'dired (define-key dired-mode-map "i" #'dired-subtree-toggle)) #+end_src *** Collapse singleton directories :package: If a directory only has one item in it, =dired-collapse= shows what that one item is. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'dired-collapse) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'dired-mode-hook #'dired-collapse-mode) #+end_src * Org mode :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'org) (with-eval-after-load 'org (require 'org-tempo) (require 'ox-md)) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; Where to look for Org files org-directory "~/org" ; this is the default ;; Fontify stuff org-hide-emphasis-markers t org-fontify-whole-heading-line t org-fontify-done-headline t org-fontify-quote-and-verse-blocks t org-src-fontify-natively t org-ellipsis "…" org-pretty-entities t ;; Source blocks org-src-tab-acts-natively t org-src-window-setup 'split-window-below ; could change this based on geom org-confirm-babel-evaluate nil ;; Behavior org-adapt-indentation nil ; don't indent things org-catch-invisible-edits 'smart ; let's try this org-special-ctrl-a/e t org-special-ctrl-k t ;; Exporting org-export-headline-levels 8) #+end_src ** Org templates #+begin_src emacs-lisp :noweb-ref settings (with-eval-after-load 'org-tempo (dolist (cell '(("el" . "src emacs-lisp") ("cr" . "src emacs-lisp :noweb-ref requires") ("cf" . "src emacs-lisp :noweb-ref functions") ("cs" . "src emacs-lisp :noweb-ref settings") ("cm" . "src emacs-lisp :noweb-ref modes") ("cl" . "src emacs-lisp :noweb-ref linux-specific") ("cw" . "src emacs-lisp :noweb-ref windows-specific") ("cp" . "src emacs-lisp :noweb-ref packages") ("ch" . "src emacs-lisp :noweb-ref hooks") ("cb" . "src emacs-lisp :noweb-ref bindings") ("cnl" . "src emacs-lisp :noweb-ref no-littering"))) (add-to-list 'org-structure-template-alist cell))) #+end_src ** Org Return: DWIM :unpackaged: #+begin_src emacs-lisp :noweb-ref functions (defun unpackaged/org-element-descendant-of (type element) "Return non-nil if ELEMENT is a descendant of TYPE. TYPE should be an element type, like `item' or `paragraph'. ELEMENT should be a list like that returned by `org-element-context'." ;; MAYBE: Use `org-element-lineage'. (when-let* ((parent (org-element-property :parent element))) (or (eq type (car parent)) (unpackaged/org-element-descendant-of type parent)))) (defun unpackaged/org-return-dwim (&optional default) "A helpful replacement for `org-return'. With prefix, call `org-return'. On headings, move point to position after entry content. In lists, insert a new item or end the list, with checkbox if appropriate. In tables, insert a new row or end the table." ;; Inspired by John Kitchin: http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ (interactive "P") (if default (org-return) (cond ;; Act depending on context around point. ;; NOTE: I prefer RET to not follow links, but by uncommenting this block, links will be ;; followed. ;; ((eq 'link (car (org-element-context))) ;; ;; Link: Open it. ;; (org-open-at-point-global)) ((org-at-heading-p) ;; Heading: Move to position after entry content. ;; NOTE: This is probably the most interesting feature of this function. (let ((heading-start (org-entry-beginning-position))) (goto-char (org-entry-end-position)) (cond ((and (org-at-heading-p) (= heading-start (org-entry-beginning-position))) ;; Entry ends on its heading; add newline after (end-of-line) (insert "\n\n")) (t ;; Entry ends after its heading; back up (forward-line -1) (end-of-line) (when (org-at-heading-p) ;; At the same heading (forward-line) (insert "\n") (forward-line -1)) ;; FIXME: looking-back is supposed to be called with more arguments. (while (not (looking-back (rx (repeat 3 (seq (optional blank) "\n"))) nil)) (insert "\n")) (forward-line -1))))) ((org-at-item-checkbox-p) ;; Checkbox: Insert new item with checkbox. (org-insert-todo-heading nil)) ((org-in-item-p) ;; Plain list. Yes, this gets a little complicated... (let ((context (org-element-context))) (if (or (eq 'plain-list (car context)) ; First item in list (and (eq 'item (car context)) (not (eq (org-element-property :contents-begin context) (org-element-property :contents-end context)))) (unpackaged/org-element-descendant-of 'item context)) ; Element in list item, e.g. a link ;; Non-empty item: Add new item. (org-insert-item) ;; Empty item: Close the list. ;; TODO: Do this with org functions rather than operating on the text. Can't seem to find the right function. (delete-region (line-beginning-position) (line-end-position)) (insert "\n")))) ((when (fboundp 'org-inlinetask-in-task-p) (org-inlinetask-in-task-p)) ;; Inline task: Don't insert a new heading. (org-return)) ((org-at-table-p) (cond ((save-excursion (beginning-of-line) ;; See `org-table-next-field'. (cl-loop with end = (line-end-position) for cell = (org-element-table-cell-parser) always (equal (org-element-property :contents-begin cell) (org-element-property :contents-end cell)) while (re-search-forward "|" end t))) ;; Empty row: end the table. (delete-region (line-beginning-position) (line-end-position)) (org-return)) (t ;; Non-empty row: call `org-return'. (org-return)))) (t ;; All other cases: call `org-return'. (org-return))))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'org (define-key org-mode-map (kbd "RET") #'unpackaged/org-return-dwim)) #+end_src * Package management :package: :PROPERTIES: :header-args: :noweb-ref early-init-package :END: Emacs is the /extensible/ editor, and that means I want to use third-party packages. Of course, first I have to /manage/ those packages. I use the excellent =straight.el=. ** Update the PATH PATH handling on Emacs is a little complicated. There's the regular environment variable =$PATH=, which we all know and love, and then Emacs has its own special =exec-path= on /top/ of that. From my research, it looks like Emacs uses =exec-path= for itself, and =$PATH= for any shells or other processes it spawns. They don't /have/ to be the same, but luckily for us, Emacs sets =exec-path= from =$PATH= on initialization, so when I add stuff to =exec-path= to, say, run git, I can just change =$PATH= right back to the expanded =exec-path= without any data loss. Here's what all that looks like. #+begin_src emacs-lisp (let ((win-app-dir "~/Applications")) (dolist (path (list ;; Windows (expand-file-name "Git/bin" win-app-dir) (expand-file-name "Git/usr/bin" win-app-dir) (expand-file-name "Git/mingw64/bin" win-app-dir) ;; Linux (expand-file-name "bin" user-emacs-directory) (expand-file-name "~/bin") (expand-file-name "~/.local/bin") (expand-file-name "~/Scripts") )) (when (file-exists-p path) (add-to-list 'exec-path path :append)))) ;; Set $PATH (setenv "PATH" (mapconcat #'identity exec-path path-separator)) #+end_src *** References - [[https://emacs.stackexchange.com/questions/550/exec-path-and-path][exec-path and $PATH (StackExchange)]] - [[https://utoi.tistory.com/entry/Difference-Between-Emacss-%E2%80%9Cgetenv-PATH%E2%80%9D-and-%E2%80%9Cexec-path%E2%80%9D][Difference between Emacs's "(getenv PATH)" and "exec-path" (U&I)]] - [[https://emacs.stackexchange.com/questions/27326/gui-emacs-sets-the-exec-path-only-from-windows-environment-variable-but-not-from][GUI Emacs sets the exec-path only from Windows environment variable but not from .emacs file (StackExchange)]] ** Disable =package.el= #+begin_src emacs-lisp (setq package-enable-at-startup nil) #+end_src ** Bootstrap The following is straight (heh) from the straight repo, wrapped in a function so I can call it in another wrapper. #+begin_src emacs-lisp (defun acdw/bootstrap-straight () "Bootstrap straight.el." (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 (concat "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 To actually bootstrap straight, I'll first try running the above directly. If it errors (it tends to on Windows), I'll directly clone the repo using git, /then/ run the bootstrap code. #+begin_src emacs-lisp (when (executable-find "git") (unless (ignore-errors (acdw/bootstrap-straight)) (let ((msg "Straight.el didn't bootstrap correctly. Cloning directly")) (message "%s..." msg) (call-process "git" nil (get-buffer-create "*bootstrap-straight-messages*") nil "clone" "https://github.com/raxod502/straight.el" (expand-file-name "straight/repos/straight.el" user-emacs-directory)) (message "%s...Done." msg) (acdw/bootstrap-straight)))) #+end_src * System-specific I use both Linux (at home) and Windows (at work). To make Emacs easier to use in both systems, I've included various system-specific settings and written some ancillary scripts. ** Determine where I am :PROPERTIES: :header-args: :noweb-ref when-at :END: This macro needs to go into =init.el=, /before/ loading =config.el= -- because I've used the =when-at= form in the =:tangle= directive for the scripts in this section. #+begin_src emacs-lisp (defmacro when-at (conditions &rest commands) "Run COMMANDS, or let the user know, when at a specific place. CONDITIONS are one of `:work', `:home', or a list beginning with those and other conditions to check. COMMANDS are only run if all CONDITIONS are met. If COMMANDS is empty or nil, simply return the result of CONDITIONS." (declare (indent 1)) (let ((at-work '(memq system-type '(ms-dos windows-nt))) (at-home '(memq system-type '(gnu gnu/linux gnu/kfreebsd)))) (pcase conditions (:work (if commands `(when ,at-work ,@commands) at-work)) (:home (if commands `(when ,at-home ,@commands) at-home)) ((guard (eq (car conditions) :work)) (if commands `(when (and ,at-work ,@(cdr conditions)) ,@commands) `(and ,at-work ,@(cdr conditions)))) ((guard (eq (car conditions) :home)) (if commands `(when (and ,at-home ,@(cdr conditions)) ,@commands) `(and ,at-work ,@(cdr conditions))))))) #+end_src ** Linux (home) :PROPERTIES: :header-args: :noweb-ref linux-specific :END: *** Settings *** Scripts **** em :PROPERTIES: :header-args: :tangle-mode (identity #o755) :mkdirp yes :END: Here's a wrapper script that'll start =emacs --daemon= if there isn't one, and then launch =emacsclient= with the arguments. Install it to your =$PATH= somewhere. #+begin_src sh :shebang "#!/bin/sh" :tangle (if (eq system-type 'gnu/linux) "~/bin/em" "") if ! emacsclient -nc "$@"; then emacs --daemon emacsclient -nc "$@" fi #+end_src **** emacsclient.desktop :PROPERTIES: :header-args: :mkdirp yes :END: I haven't really tested this yet, but it should allow me to open other files and things in Emacs. From [[https://www.taingram.org/blog/emacs-client.html][taingram]]. #+begin_src conf-desktop :tangle (if (eq system-type 'gnu/linux) "~/.local/share/applications/emacsclient.desktop" "") [Desktop Entry] Name=Emacs Client GenericName=Text Editor Comment=Edit text MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; Exec=emacsclient -c %f Icon=emacs Type=Application Terminal=false Categories=Utility;TextEditor; #+end_src ** Windows (work) :PROPERTIES: :header-args: :noweb-ref windows-specific :END: I use Windows at work, where I /also/ don't have Admin rights. So I kind of fly-by-night there. Much of the ideas and scripts in this section come from [[https://github.com/termitereform/JunkPile/blob/master/emacs-on-windows.md][termitereform]] on Github. *** Settings #+begin_src emacs-lisp (setq-default w32-allow-system-shell t) ; enable cmd.exe as shell #+end_src *** Scripts :PROPERTIES: :header-args: :noweb yes :mkdirp yes :END: **** Common variables #+begin_src bat :noweb-ref w32-bat-common set HOME=%~dp0..\.. set EMACS=%HOME%\Applications\Emacs\bin\runemacs.exe chdir %HOME% #+end_src **** Emacs Daemon Either run this once at startup, or put a shortcut of it in the Startup folder: =%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup=. #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Daemon.cmd" "") <> %EMACS% --daemon #+end_src **** Emacs Client This will try to connect to the daemon above. If that fails, it'll run =runemacs.exe=. *This is the main shortcut for running Emacs.* #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs.cmd" "") <> set EMACSC=%HOME%\Applications\Emacs\bin\emacsclientw.exe "%EMACSC%" -n -c -a "%EMACS%" %* #+end_src **** Emacs Safe Start This runs Emacs with the factory settings. #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Safe Start.cmd" "") <> "%EMACS%" -Q %* #+end_src **** Emacs Debug This runs Emacs with the =--debug-init= option enabled. #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Debug.cmd" "") <> "%EMACS%" --debug-init %* #+end_src * Appendices ** =config.el= :PROPERTIES: :header-args: :tangle config.el :noweb yes :END: While =config.el= is written above, I use Noweb references to tangle them all together in the following block, which enables me to organize my config here /logically/, while keeping the generated file organized /programmatically/. *** Enable lexical binding #+begin_src emacs-lisp ;; config.el -*- lexical-binding: t -*- #+end_src *** Disclaimer :PROPERTIES: :header-args: :noweb-ref disclaimer :END: #+begin_src emacs-lisp ;; This file is automatically tangled from config.org. ;; Hand edits will be overwritten! #+end_src *** The rest #+begin_src emacs-lisp ;;; REQUIRES <> ;;; PACKAGES ;; straight.el depends on git, which /should be/ find-able by the PATH ;; manipulation in early-init.el. Just in case, though, we'll check ;; that we can find git. (when (executable-find "git") <> ) ;;; FUNCTIONS <> ;;; SETTINGS <> ;;; SYSTEM-DEPENDENT SETTINGS (when-at :home <> ) ; end when-at :home (when-at :work <> ) ; end when-at :work ;;; MODES <> ;;; HOOKS <> ;;; BINDINGS <> #+end_src ** Emacs's files *** init.el :PROPERTIES: :header-args: :tangle init.el :noweb yes :END: The classic Emacs initiation file. **** Use lexical binding when evaluating =init.el= #+begin_src emacs-lisp ;; init.el -*- lexical-binding: t -*- <> #+end_src **** Prefer newer files to older files #+begin_src emacs-lisp (setq-default load-prefer-newer t) #+end_src **** =when-at= See [[*Determine where I am][the definition above]] for rationale as to why this is here. #+begin_src emacs-lisp <> #+end_src **** Load the config I keep most of my config in =config.el=, which is tangled directly from this file. This init just loads that file, either from lisp ~or directly from Org if it's newer~. I found out that =org-babel-load-file= /caches/ its runs, and checks for me whether the .org or .el file is newer. /Plus/ it can byte-compile!! #+begin_src emacs-lisp (let* (;; Speed up init (gc-cons-threshold most-positive-fixnum) (file-name-handler-alist nil) ;; Config file names (config (expand-file-name "config" user-emacs-directory)) (config.el (concat config ".el")) (config.org (concat config ".org")) (straight-org-dir (expand-file-name "straight/build/org" user-emacs-directory))) (unless (load config 'no-error)) ;; A plain require here just loads the older `org' ;; in Emacs' install dir. We need to add the newer ;; one to the `load-path', hopefully that's all. (when (file-exists-p straight-org-dir) (add-to-list 'load-path straight-org-dir)) ;; Load config.org (require 'org) (org-babel-load-file config.org :compile)) #+end_src *** early-init.el :PROPERTIES: :header-args: :tangle early-init.el :noweb yes :END: Beginning with 27.1, Emacs also loads an =early-init.el= file, before the package manager or the UI code. The Info says we should put as little as possible in this file, so I only have what I need. #+begin_src emacs-lisp ;; early-init.el -*- no-byte-compile: t; -*- <> ;; BOOTSTRAP PACKAGE MANAGEMENT <> ;; SETUP FRAME <> #+end_src ** License :PROPERTIES: :header-args: :tangle LICENSE :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!*