#+TITLE: Emacs configuration, literate-style #+AUTHOR: Case Duckworth #+STARTUP: overview #+PROPERTY: header-args :results output silent * 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 ** Auth-sources Here feels like as good a place as any to setup =auth-sources=. Yes, I /need/ to use GPG. I'll get around to it. Until then, /please/ don't break into my workplace and steal my secrets. #+begin_src emacs-lisp :noweb-ref settings (setq-default auth-sources '("~/.authinfo")) #+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 **** Don't show default modeline Wait until my *fancy* modeline is loaded -- from [[https://github.com/KaratasFurkan/.emacs.d#remove-redundant-ui][Furkan Karataş]]. #+begin_src emacs-lisp :noweb-ref settings (setq-default mode-line-format nil) #+end_src **** Window Dividers #+begin_src emacs-lisp :noweb-ref settings (setq-default window-divider-default-places t ; bottom and right window-divider-default-bottom-width 2 window-divider-default-right-width 2) #+end_src #+begin_src emacs-lisp :noweb-ref modes (window-divider-mode +1) #+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 *** COMMENT Nyan mode +Fuck it, let's have some fun.+ COMMENTED out because it messes up my modeline :( This obviously needs a lot more research. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'nyan-mode) #+end_src I add it to the modeline [[*Simple modeline][below]]. #+begin_src emacs-lisp :noweb-ref settings (setq-default nyan-bar-length 20) #+end_src But I still have to enable the mode ?! #+begin_src emacs-lisp :noweb-ref settings (nyan-mode +1) #+end_src ** Windows *** Splitting windows sensibly This is extremely fiddly and I'd love another option. - [[https://www.emacswiki.org/emacs/ToggleWindowSplit][ToggleWindowSplit, EmacsWiki]] #+begin_src emacs-lisp :noweb-ref settings (setq-default split-width-threshold 100 split-height-threshold 50) #+end_src *** Switch to other window or buffer :crux: #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "M-o") #'crux-other-window-or-switch-buffer) #+end_src *** The *Help* window I want to select the *Help* window by default, so I can easily quit it. #+begin_src emacs-lisp :noweb-ref settings (setq-default help-window-select t) #+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 uniquify-separator "/" uniquify-after-kill-buffer-p t uniquify-ignore-buffers-re "^\\*") #+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)) "!" " 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 *** An /even better/ scratch buffer :package: The aptly-named =scratch= pops open a new scratch buffer /with the same mode as the file you're currently editing/. I'm pretty chuffed about it. I found it from [[https://old.reddit.com/r/emacs/comments/l4v1ux/one_of_the_most_useful_small_lisp_functions_in_my/][this discussion]], which might also come in handy someday. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'scratch) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "C-x") #'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) => Run `kill-buffer' without a prefix arg." (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 (let ((current-prefix-arg nil)) (kill-buffer))))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "C-x k") #'kill-a-buffer) #+end_src *** Kill old buffers after a while Adapted from =midnight-mode=, using suggestions from [[https://old.reddit.com/r/emacs/comments/l6jpxf/how_do_emacs_users_usually_have_multiple_files/gl2249u/][u/ndamee]]. #+begin_src emacs-lisp :noweb-ref packages (require 'midnight) #+end_src What time I run the clean up is a little tricky for me, since I use Emacs at work /and/ at home, and all at different times. However, I realized that since I close out of Emacs at work pretty much every day, I don't need to worry about too many buffers there -- so I just have =clean-buffer-list= run at 8:00 PM. #+begin_src emacs-lisp :noweb-ref settings (setq-default acdw/clean-buffer-list-timer (run-at-time "20:00" 86400 #'clean-buffer-list) clean-buffer-list-delay-general 5 clean-buffer-list-delay-special (* 7 24 60 60)) (add-to-list 'clean-buffer-list-kill-buffer-names "*Completions*") (add-to-list 'clean-buffer-list-kill-buffer-names "*Calendar*") #+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 bar mode settings #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-bar-show 1 ; show the tab bar when more than 1 tab tab-bar-new-tab-choice "*scratch*" tab-bar-tab-name-function #'tab-bar-tab-name-current-with-count) #+end_src *** Tab bar history #+begin_src emacs-lisp :noweb-ref modes (tab-bar-history-mode +1) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-bar-history-limit 25) #+end_src ** Fonts On Linux, I have a custom build of Iosevka that I like. #+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 But on Windows, I use Consolas. #+begin_src emacs-lisp :noweb-ref windows-specific (set-face-attribute 'default nil :family "Consolas" :height 100) (set-face-attribute 'fixed-pitch nil :family "Consolas" :height 100) #+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 *** Unicode fonts :package: =unicode-fonts= pulls in some other packages that still require the deprecated =cl= library. So, I've forked those libraries to require =cl-lib= instead. **** First: un-fuck =font-utils= and =list-utils= ... and =persistent-soft= ***** List-utils Since =font-utils= depends on =list-utils=, if I load the former first, it pulls in the unpatched latter. /So/ I need to do =list-utils= first. (=*straight-process*= is your friend, y'all!) Since =list-utils= requires =cl= in line 259 (see [[https://github.com/rolandwalker/list-utils/issues/6][this issue]], apparently just changing it breaks many tests, but I'll run with it until Emacs complains), I need to fork and change that to a =cl-lib=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(list-utils :host github :repo "rolandwalker/list-utils" :fork (:repo "duckwork/list-utils"))) #+end_src ***** Persistent-soft #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(persistent-soft :host github :repo "rolandwalker/persistent-soft" :fork (:repo "duckwork/persistent-soft"))) #+end_src ***** Font-utils I was able to actually create a [[https://github.com/rolandwalker/font-utils/pull/2][PR]] for this one, so fingers crossed. Since the last update on =font-utils= was in 2015, I'm not super hopeful that my fix will get merged upstream, but I'm using a =:fork= argument to stay hopeful. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(font-utils :host github :repo "rolandwalker/font-utils" :fork (:repo "duckwork/font-utils"))) #+end_src ***** A function in case it comes up again I keep googling [[https://github.com/hlissner/doom-emacs/issues/3372][this Doom Emacs issue]], because I keep forgetting what I need to do to see where =Package cl is deprecated= is coming from. So... function! #+begin_src emacs-lisp :noweb-ref functions ;; Make the compiler happy (autoload 'file-dependents "loadhist") (autoload 'feature-file "loadhist") (defun acdw/fucking-cl () "Find out where the fuck `cl' is being required from." (interactive) (require 'loadhist) (message "%S" (file-dependents (feature-file 'cl)))) #+end_src **** Unicode-fonts /Okay/ ... pull requests in, time to load =unicode-fonts=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(unicode-fonts :host github :repo "rolandwalker/unicode-fonts")) (require 'unicode-fonts) #+end_src According to [[https://github.com/rolandwalker/unicode-fonts/issues/3][Issue #3]], there can be problems with =unicode-fonts-setup= when using a daemon. Instead of forking this repo and merging [[https://github.com/rolandwalker/unicode-fonts/pull/4][PR #4]] into my personal fork, I'll use the workaround described in the issue. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--unicode-fonts-setup (frame) "Run `unicode-fonts-setup', then remove the hook." (select-frame frame) (unicode-fonts-setup) (remove-hook 'after-make-frame-functions #'hook--unicode-fonts-setup)) (add-hook 'after-make-frame-functions #'hook--unicode-fonts-setup) #+end_src *** Draw form-feeds (=^L=) properly :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'form-feed) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-form-feed-mode +1) (blackout 'form-feed-mode) #+end_src ** Theming *** Modus themes :package: I want the git version. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(modus-themes :host gitlab :repo "protesilaos/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 modus-themes-mode-line 'borderless-3d) #+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 '(;; left side (simple-modeline-segment-modified simple-modeline-segment-buffer-name simple-modeline-segment-position) ;; right side (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 ***** COMMENT Functions to figure out what window is focused How is this not built into Emacs? Oh well, I have [[https://github.com/jamesnvc/dotfiles/blob/master/emacs.d/modules/cogent-modeline.el][Cogent]] to thank. #+begin_src emacs-lisp :noweb-ref variables (defvar cogent-line-selected-window (frame-selected-window)) #+end_src #+begin_src emacs-lisp :noweb-ref functions (defun cogent-line-set-selected-window (&rest _args) (when (not (minibuffer-window-active-p (frame-selected-window))) (setq cogent-line-selected-window (frame-selected-window)) (force-mode-line-update))) (defun cogent-line-unset-selected-window () (setq cogent-line-selected-window nil) (force-mode-line-update)) (defun cogent-line-selected-window-active-p () (eq cogent-line-selected-window (selected-window))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'window-configuration-change-hook #'cogent-line-set-selected-window) (add-hook 'focus-in-hook #'cogent-line-set-selected-window) (add-hook 'focus-out-hook #'cogent-line-unset-selected-window) (advice-add 'handle-switch-frame :after #'cogent-line-set-selected-window) (advice-add 'select-window :after #'cogent-line-set-selected-window) #+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 **** Which-function mode Shows where we are in the modeline. #+begin_src emacs-lisp :noweb-ref modes (which-function-mode +1) #+end_src *** Setting faces It took me a while to find a function that'll let me customize faces /without/ using *customize*. Thanks to [[https://www.emacswiki.org/emacs/CustomizingFaces#toc5][Drew Adams]], I've got it! #+begin_src emacs-lisp :noweb-ref functions (defun doremi-face-set (face spec) "Tell Customize that FACE has been set to value SPEC. SPEC is as for `defface'." (put face 'customized-face spec) (face-spec-set face spec) (message "Customized face %s." (symbol-name face))) #+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 *** Selectrum :package: *COMMENT 2021-02-01*: For some reason, selectrum will randomly do a thing where it, like, flashes up and down in the minibuffer? I don't know how to explain it, it's super weird. I guess I should file a bug report ... but until then, I'll just use =icomplete-vertical=. My minibuffer completion system uses =selectrum=, =prescient=, =company=, and =marginalia=. At some point, I'd like to take a deep dive in =embark= (possibly switching out =selectrum=), =ido=, =orderless=, or others, for now .... I just want to see my completions. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'selectrum) #+end_src #+begin_src emacs-lisp :noweb-ref modes (selectrum-mode +1) #+end_src *** COMMENT Icomplete #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'icomplete-vertical) #+end_src #+begin_src emacs-lisp :noweb-ref modes (icomplete-mode +1) (icomplete-vertical-mode +1) #+end_src *** Prescient :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'prescient) (require 'prescient) #+end_src Prescient can persist itself too. #+begin_src emacs-lisp :noweb-ref modes (prescient-persist-mode +1) #+end_src **** Selectrum integration Let's have =prescient= and =selectrum= work together. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'selectrum-prescient) #+end_src #+begin_src emacs-lisp :noweb-ref modes (with-eval-after-load 'selectrum (selectrum-prescient-mode +1)) #+end_src *** Consult :package: #+begin_src emacs-lisp :noweb-ref modes (straight-use-package 'consult) (require 'consult) #+end_src Consult has a lot of great bindings that work well with Emacs's default completion system. These all come from the [[https://github.com/minad/consult#configuration][example configuration]]. #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'consult ;; C-c bindings (`mode-specific-map') (define-key acdw/map (kbd "C-c h") #'consult-history) (define-key acdw/map (kbd "C-c m") #'consult-mode-command) ;; C-x bindings (`ctl-x-map') (define-key acdw/map (kbd "C-x M-:") #'consult-complex-command) (define-key acdw/map (kbd "C-x b") #'consult-buffer) (define-key acdw/map (kbd "C-x 4 b") #'consult-buffer-other-window) (define-key acdw/map (kbd "C-x 5 b") #'consult-buffer-other-frame) (define-key acdw/map (kbd "C-x r x") #'consult-register) (define-key acdw/map (kbd "C-x r b") #'consult-bookmark) ;; M-g bindings (`goto-map') (define-key acdw/map (kbd "M-g g") #'consult-line) (define-key acdw/map (kbd "M-g M-g") #'consult-line) (define-key acdw/map (kbd "M-g o") #'consult-outline) (define-key acdw/map (kbd "M-g m") #'consult-mark) (define-key acdw/map (kbd "M-g k") #'consult-global-mark) (define-key acdw/map (kbd "M-g i") #'consult-imenu) (define-key acdw/map (kbd "M-g e") #'consult-error) ;; M-s bindings (`search-map') (define-key acdw/map (kbd "M-s g") #'consult-grep) ; alts: ; consult-git-grep, ; consult-ripgrep (define-key acdw/map (kbd "M-s f") #'consult-find) ; alts: ; consult-locate (define-key acdw/map (kbd "M-s l") #'consult-line) (define-key acdw/map (kbd "M-s m") #'consult-multi-occur) (define-key acdw/map (kbd "M-s k") #'consult-keep-lines) (define-key acdw/map (kbd "M-s u") #'consult-focus-lines) ;; Other bindings (define-key acdw/map (kbd "M-y") #'consult-yank-pop) (define-key acdw/map (kbd " a") #'consult-apropos) (define-key acdw/map (kbd "C-h a") #'consult-apropos)) #+end_src #+begin_src emacs-lisp :noweb-ref settings (autoload 'consult-register-preview "consult") ; make the compiler happy (setq-default register-preview-delay 0 register-preview-function #'consult-register-preview) #+end_src *** Marginalia :package: Finally, =marginalia= provides extra information about completion candidates. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'marginalia) (require 'marginalia) #+end_src #+begin_src emacs-lisp :noweb-ref modes (marginalia-mode +1) #+end_src I like the rich annotations provided by =marginalia=. #+begin_src emacs-lisp :noweb-ref settings (setq-default marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) #+end_src **** Integration with Selectrum #+begin_src emacs-lisp :noweb-ref functions (advice-add #'marginalia-cycle :after (lambda () (when (bound-and-true-p selectrum-mode) (selectrum-exhibit)))) #+end_src ** Imenu #+begin_src emacs-lisp :noweb-ref settings (setq-default imenu-auto-rescan t) #+end_src ** Completion *** Hippie Expand Before I install any completion framework, I want a good default for completing. =hippie-expand= fills that niche. #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "M-/") #'hippie-expand) #+end_src ** Bindings *** Acdw Mode I've decided to set up a custom minor mode for my keybindings, as suggested in [[https://github.com/larstvei/dot-emacs#key-bindings][Lars Tvei]]'s config, so that I can override all other modes with my own keybindings. Plus I can easily turn it off and back on as I please. #+begin_src emacs-lisp :noweb-ref acdw-mode (defvar acdw/map (make-sparse-keymap) "A keymap for my custom bindings.") (define-minor-mode acdw/mode "A mode for `acdw/map'." :init-value t :lighter " ⱷ" :keymap acdw/map) (define-globalized-minor-mode acdw/global-mode acdw/mode acdw/mode) #+end_src **** Turn off acdw/mode in the minibuffer #+begin_src emacs-lisp :noweb-ref acdw-mode (defun acdw/mode--disable () "Turn off acdw/mode." (acdw/mode -1)) (add-hook 'minibuffer-setup-hook #'acdw/mode--disable) #+end_src **** Custom leader Since =C-z= is generally pretty useless in Emacs (minimize the window? really?), I rebind it to be a sort of personal leader key. I generally use it as a leader for entering applications. #+begin_src emacs-lisp :noweb-ref acdw-mode (defvar acdw/leader (let ((map (make-sparse-keymap)) (c-z (global-key-binding "\C-z"))) ;(global-unset-key "\C-z") (define-key acdw/map "\C-z" map) (define-key map "\C-z" c-z) map)) ;; Just in case I want to run hooks after defining the leader map (run-hooks 'acdw/leader-defined-hook) #+end_src *** Show keybindings with =which-key= :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'which-key) #+end_src #+begin_src emacs-lisp :noweb-ref modes (which-key-mode +1) (blackout 'which-key-mode) #+end_src ** Scrolling According to [[https://github.com/mpereira/.emacs.d#make-cursor-movement-an-order-of-magnitude-faster][Murilo Pereira]], these settings will make Emacs scrolling "an order of magnitude faster." #+begin_src emacs-lisp :noweb-ref settings (setq-default auto-window-vscroll nil fast-but-imprecise-scrolling t) #+end_src ** Enable commands I think the /disabled command/ feature of Emacs is stupid, especially for a program that values freedom so much. #+begin_src emacs-lisp :noweb-ref settings (setq-default disabled-command-function nil) #+end_src ** CRUX :package:crux: A collection of generally-useful functions that I don't want to bother including here myself. This is kind of an experiment, to be honest. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(crux :host github :repo "bbatsov/crux")) (require 'crux) #+end_src A note: I /don't/ do the same with [[https://github.com/alphapapa/unpackaged.el][unpackaged]] (see below, specifically the *Org* sections) because it pulls in =hydra= and =use-package=, et al. * 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 savehist-autosave-interval 60) #+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 when I'm not at home. #+begin_src emacs-lisp :noweb-ref settings (setq-default save-place-forget-unreadable-files (memq system-type '(gnu gnu/linux gnu/kfreebsd))) #+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 recentf-auto-cleanup 'never) #+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) (set-language-environment "UTF-8") (setq-default locale-coding-system 'utf-8 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 ** Lockfiles I don't think these are really necessary as of now. #+begin_src emacs-lisp :noweb-ref settings (setq-default create-lockfiles nil) #+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 *** Fill-column #+begin_src emacs-lisp :noweb-ref settings (setq-default fill-column 80) #+end_src By default, Emacs uses =C-x f= to set the =fill-column=. I think it's pretty dumb that such an easy-to-reach binding (for Emacs) is set to a function that I /literally/ never use. So I'm going to bind it to =find-file= ... since that's the only time I accidentally call it, anyway. #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "C-x f") #'find-file) #+end_src *** Auto-fill vs. Visual-line 1. Enable =auto-fill-mode= with text modes. #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'text-mode-hook #'auto-fill-mode) #+end_src 2. /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. *** Visual fill column mode In reading-intensive views, this mode keeps the text from getting too wide. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'visual-fill-column) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default visual-fill-column-center-text t) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) (with-eval-after-load 'visual-fill-column (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) #+end_src *** 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 *** Kill the line if there is no region :crux: #+begin_src emacs-lisp :noweb-ref hooks (crux-with-region-or-line kill-ring-save) (crux-with-region-or-line kill-region) #+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 see also - [[https://spwhitton.name/blog/entry/transient-mark-mode/][Gnu Emacs' Transient Mark mode]], Sean Whitton *** 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 ** The Region *** Expand region :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'expand-region) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "C-=") #'er/expand-region) #+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 acdw/map (kbd "C-/") #'undo-fu-only-undo) (define-key acdw/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 ** Search/Replace :package: The /biggest/ thing I miss about my Neovim days is its ease of search/replace. It didn't matter where the point was in the buffer; it could wrap around. It had a little highlight to show you all the matching strings, /and/ it could show you what the replacement would look like. =anzu= does /most/ of this, except the wrapping around part -- =ctrlf= does the wrapping around okay, but I haven't really tried to get the two packages to play nice together. Until then, I'll just use =anzu= and =isearch=, which is honestly a pretty good search package. *** Anzu setup #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'anzu) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default anzu-mode-lighter "" ; hide anzu-mode in the modeline anzu-replace-to-string-separator " → ") ;; Set up anzu in the modeline (setq-default anzu-cons-mode-line-p nil) (setcar (cdr (assq 'isearch-mode minor-mode-alist)) '(:eval (concat " " (anzu--update-mode-line)))) #+end_src *** Regex I search with regex by default. #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; Search Regex by default search-default-mode t) #+end_src I've switched =query-replace= and =query-replace-regexp= with their anzu versions, because of the regex thing. #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map [remap query-replace] #'anzu-query-replace-regexp) (define-key acdw/map [remap query-replace-regexp] #'anzu-query-replace) (define-key isearch-mode-map [remap isearch-query-replace] #'anzu-isearch-query-replace) (define-key isearch-mode-map [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp) #+end_src ** Commenting :crux: I don't think the default =M-;= (=M-x comment-dwim=) binding makes sense. I want it to comment out the region or line, or uncomment it if it's already commented. That's it. #+begin_src emacs-lisp :noweb-ref hooks (crux-with-region-or-line comment-or-uncomment-region) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "M-;") #'comment-or-uncomment-region) #+end_src * Writing ** COMMENT Word count :package: I'm just going to comment this out for right now because I honestly don't know if it's that useful. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'wc-mode) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (defun hook--wc-mode-no-keybinds () (wc-mode +1) (define-key wc-mode-map (kbd "C-c C-w w") nil) (define-key wc-mode-map (kbd "C-c C-w l") nil) (define-key wc-mode-map (kbd "C-c C-w a") nil) (define-key wc-mode-map (kbd "C-c C-w c") nil)) (add-hook 'text-mode-hook #'hook--wc-mode-no-keybinds) #+end_src ** Spell checking Let's use =hunspell=. #+begin_src emacs-lisp :noweb-ref packages (with-eval-after-load "ispell" (setenv "LANG" "en_US") (setq-default ispell-program-name "hunspell" ispell-dictionary "en_US") (ispell-set-spellchecker-params)) (setq ispell-personal-dictionary "~/.hunspell_personal") (unless (file-exists-p ispell-personal-dictionary) (write-region "" nil ispell-personal-dictionary nil 0)) #+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 *** Smart parens :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'smartparens) (require 'smartparens-config) #+end_src **** Show parens #+begin_src emacs-lisp :noweb-ref settings (setq-default sp-show-pair-delay 0 sp-show-pair-from-inside t) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'prog-mode-hook #'show-smartparens-mode) #+end_src **** Hide the =smartparens= lighter #+begin_src emacs-lisp :noweb-ref modes (blackout 'smartparens-mode) #+end_src **** Be strict in =prog-mode= #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'prog-mode-hook #'smartparens-strict-mode) #+end_src **** Use =paredit= bindings #+begin_src emacs-lisp :noweb-ref settings (setq-default sp-base-keybindings 'paredit) #+end_src #+begin_src emacs-lisp :noweb-ref modes (with-eval-after-load 'smart-parens (sp-use-paredit-bindings)) #+end_src ** Formatting *** Aggressive indent :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'aggressive-indent) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-aggressive-indent-mode +1) (blackout 'aggressive-indent-mode) #+end_src Since aggressive indenting takes care of tabs, I can use == to complete things! #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-always-indent 'complete) #+end_src ** Typesetting *** Prettify-mode I like my pretty =lambda='s -- and maybe one day, I'll add more symbols, but only in prog-mode. I want to see what I'm actually typing in text. #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'prog-mode-hook #'prettify-symbols-mode) #+end_src Of course, I want to be able to /see/ the actual text in the buffer if I'm /in/ the symbols. #+begin_src emacs-lisp :noweb-ref settings (setq-default prettify-symbols-unprettify-at-point 'right-edge) #+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 ** Compilation #+begin_src emacs-lisp :noweb-ref settings (setq-default compilation-ask-about-save nil ; just save compilation-always-kill t ; kill the old processes compilation-scroll-output 'first-error) #+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) (put 'cl-flet 'common-lisp-indent-function (get 'flet 'common-lisp-indent-function)) (put 'cl-labels 'common-lisp-indent-function (get 'labels 'common-lisp-indent-function)) (put 'if 'common-lisp-indent-function 2) (put 'dotimes-protect 'common-lisp-indent-function (get 'when 'common-lisp-indent-function)) #+end_src *** Web #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'web-mode) (require 'web-mode) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default web-mode-enable-current-element-highlight t) #+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 "-AFgho --group-directories-first" dired-dwim-target t) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "C-x C-j") #'dired-jump) #+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 ** Git :package: *** Magit #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'magit) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader "g" #'magit-status) #+end_src **** Use =~/.authinfo= for passwords The =auth-info= line should look like this: #+begin_example machine git.example.com user acdw password hahayeahrightyamoroniwouldn'tgiveyouthat #+end_example #+begin_src emacs-lisp :noweb-ref hooks (autoload 'magit-process-password-auth-source "magit") (add-hook 'magit-process-find-password-functions #'magit-process-password-auth-source) #+end_src **** Windows setup Following the [[https://github.com/magit/magit/wiki/Pushing-with-Magit-from-Windows][wiki page located here]]. Also make sure to run the following in =cmd.exe= to set =$HOME= correctly: #+begin_src cmd setx HOME C:\Users\aduckworth\Downloads\acdw #+end_src /and/ run /this/ command to setup a git credential helper: #+begin_src sh git config --global credential.helper store #+end_src Okay, okay, using the =store= credential.helper is /super/ insecure. But here's the thing -- the Gits I Git at work (a) aren't my /real/ git, and (b) they're just tokens! So any time I think somebody got access, I just revoke the tokens and bingo bongo, good to go. If that's not true, please feel free to hack this repo and change this paragraph. #+begin_src emacs-lisp :noweb-ref windows-specific (setenv "GIT_ASKPASS" "git-gui--askpass") #+end_src **** Forge :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'forge) (with-eval-after-load 'magit (require 'forge)) #+end_src *** Git file modes :package: #+begin_src emacs-lisp :noweb-ref packages (dolist (feat '(gitattributes-mode gitconfig-mode gitignore-mode)) (straight-use-package feat) (require feat)) #+end_src ** Crosswords! :package: I love crosswords. I love Emacs. There ya go. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(crossword :host github :repo "Boruch-Baum/emacs-crossword")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default crossword-empty-position-char "#") #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (setq-default crossword-save-path (no-littering-expand-var-file-name "crosswords/")) (unless (file-exists-p crossword-save-path) (make-directory crossword-save-path :parents)) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (defun hook--setup-crossword () (setq cursor-type 'hbar)) (add-hook 'crossword-mode-hook #'hook--setup-crossword) #+end_src The problem with this package is that the default faces are pretty bad, to be honest. Let's change em. #+begin_src emacs-lisp :noweb-ref settings (doremi-face-set 'crossword-current-face '((((class color) (background light)) (:inherit 'normal :foreground "black" :background "lightgreen")) (((class color) (background dark)) (:inherit 'normal :foreground "white" :background "darkgreen")) (t (:inherit 'normal :foreground "black" :background "darkgreen")))) (doremi-face-set 'crossword-other-dir-face '((((class color) (background light)) (:inherit 'normal :foreground "black" :background "darkgrey")) (((class color) (background dark)) (:inherit 'normal :foreground "black" :background "darkgrey")) (t (:inherit 'normal :foreground "black" :background "darkgrey")))) #+end_src ** TODO Gnus See [[https://github.com/redguardtoo/mastering-emacs-in-one-year-guide/blob/master/gnus-guide-en.org][this guide]] and try it out. ** RSS Feeds :package: *** Elfeed and goodies #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'elfeed) (straight-use-package 'elfeed-goodies) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default elfeed-db-directory (expand-file-name "elfeed/db" (or (getenv "XDG_CACHE_HOME") "~/.cache"))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (with-eval-after-load 'elfeed (require 'elfeed-goodies) (elfeed-goodies/setup)) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default elfeed-goodies/entry-pane-position 'top elfeed-goodies/powerline-default-separator nil elfeed-goodies/tag-column-width 16) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (defun hook--elfeed-show () (text-scale-adjust +1) (visual-fill-column-mode +1)) (add-hook 'elfeed-show-mode-hook #'hook--elfeed-show) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader "f" #'elfeed) #+end_src *** Elfeed feeds #+begin_src emacs-lisp :noweb-ref settings (setq elfeed-feeds `( ("https://wflewis.com/feed.xml" blags fwends) ("HTTPS://atthis.link/rss.xml" blags tech) ("https://rachelbythebay.com/w/atom.xml" blags tech) ("https://notes.neeasade.net/rss_full.xml" blags tech) ("https://www.uninformativ.de/blog/feeds/en.atom" blags tech) ("http://blog.z3bra.org/rss/feed.xml" blags tech) ("https://blog.sanctum.geek.nz/feed/" blags tech) ("https://drewdevault.com/blog/index.xml" blags tech) ("https://usesthis.com/feed.atom" tech) ("https://occasionallycogent.com/feed.xml" blags) ("https://www.murilopereira.com/index.xml" blags tech) ("https://botanistinthekitchen.blog/feed/" blags food) ("https://www.careercenterbr.com/feed/" work) ("https://blog.ebrpl.com/feed/" work) (,(concat ; long-ass url "https://lemmy.ml/feeds/front/" "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." "eyJpZCI6MTY4MjQsImlzcyI6ImxlbW15Lm1sIn0" ".yE2zUGjvlEuTZZi3TiF9HR7L7ITM9f_" "fQnquyYLgdJ4.xml?sort=Active") news) ("https://lobste.rs/rss" news tech) ("https://feeds.npr.org/1001/rss.xml" news) (,(concat ; long-ass url "https://tilde.news/rss?token=" "FvdFj8rQkhrBy9j1yON1t6RYKDdcuG1MoUlyvRICmbgDGCf2JTWAEObDhdgt") news tildes tech) ("https://www.acdw.net/atom.xml" fwends) ("https://envs.net/~lucidiot/rsrsss/feed.xml" fwends) ("https://planet.emacslife.com/atom.xml" emacs) ("http://tilde.town/~lucidiot/fridaypostcard.xml" tildes art) ("https://quasivoid.net/feed.atom" tildes) ("https://benjaminwil.info/feed.xml" tildes fwends) ("https://benjaminwil.info/antisocial/feed.xml" tildes) ("https://blog.ryliejamesthomas.net/feed/" tildes) ("https://p1k3.com/feed" tildes) ("https://cosmic.voyage/rss.xml" tildes fiction sci-fi) ("https://jackreid.xyz/index.xml" tildes) ("http://lambdacreate.com/static/feed.rss" tildes fwends) ("https://gaffen.co.uk/feed.xml" tildes) ("https://gmb.21x2.net/rss.xml" tildes) ("https://www.insom.me.uk/feed.xml" tildes) ("https://invisibleup.com/atom.xml" tildes) ("https://m455.casa/feed.rss" tildes fwends) ("https://petras.space/index.xml" tildes) ("https://www.benovermyer.com/post/index.xml" tildes) ("https://tilde.town/~trm/blog/atom.xml" tildes) ("https://tilde.team/feed.rss" tildes) ("http://ajroach42.com/feed.xml" tildes) ("http://tilde.town/~mroik/blog/rss.xml" tildes) ("https://hamster.dance/blog/rss/" tildes) ("https://m455.neocities.org/feed.rss" tildes fwends) ("https://eli.li/feed.rss" tildes fwends) ("https://aiweirdness.com/rss" tech) ("http://tilde.town/~m455/javapool.rss" tilde) ("https://spwhitton.name/blog/index.rss" blags) ("https://www.theadvocate.com/search/?f=rss&t=article&l=50" news) ("https://esoteric.codes/rss" tech) ("https://wphicks.github.io/feed.xml" blags) )) #+end_src *** COMMENT Elfeed-protocol I'm not /the most/ happy with this -- it seems like a lot for not that much -- but until I figure out a proper syncing solution, it'll do. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'elfeed-protocol) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default elfeed-protocol-ttrss-maxsize 200 elfeed-feeds '(("ttrss+https://acdw@rss.tildeverse.org" :use-authinfo t))) #+end_src #+begin_src emacs-lisp :noweb-ref modes (with-eval-after-load 'elfeed (elfeed-protocol-enable)) #+end_src For whatever reason, =G= (=elfeed-search-fetch=) doesn't fetch anything new from TTRSS. So I rebind it to ... =elfeed-protocol-ttrss-reinit= (=elfeed-protocol-ttrss-update= doesn't pull everything for some reason ... this kind of thing is why I'm looking at other elfeed-sync options). #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'elfeed-protocol (defun acdw-elfeed-protocol-ttrss-update-then-reinit () "Update, then reinit, the first entry in `elfeed-feeds'." (interactive) (let ((url (caar elfeed-feeds)) (msg-update "Elfeed Protocol: Updating...") (msg-reinit "Elfeed Protocol: Re-initializing...")) (message "%s" msg-update) (elfeed-protocol-ttrss-update url) (message "%s Done." msg-update) (message "%s" msg-reinit) (elfeed-protocol-ttrss-reinit url) (message "%s Done." msg-reinit))) (define-key elfeed-search-mode-map "G" #'acdw-elfeed-protocol-ttrss-update-then-reinit)) #+end_src ** 0x0 (null pointer) :package: An ease-of-life package that lets me upload my shitty code to share it with others. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(0x0 :repo "https://git.sr.ht/~zge/nullpointer-emacs")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default 0x0-default-service 'ttm) #+end_src ** Gemini/gopher *** Elpher :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(elpher :repo "git://thelambdalab.xyz/elpher.git")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default elpher-ipv4-always t) (doremi-face-set 'elpher-gemini-heading1 '((t (:inherit (modus-theme-heading-1))))) (doremi-face-set 'elpher-gemini-heading2 '((t (:inherit (modus-theme-heading-2))))) (doremi-face-set 'elpher-gemini-heading3 '((t (:inherit (modus-theme-heading-3))))) #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (setq-default elpher-certificate-directory (no-littering-expand-var-file-name "elpher-certificates/")) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'elpher (define-key elpher-mode-map "n" #'elpher-next-link) (define-key elpher-mode-map "p" #'elpher-prev-link) (define-key elpher-mode-map "o" #'elpher-follow-current-link) (define-key elpher-mode-map "G" #'elpher-go-current)) #+end_src *** Gemini-mode :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(gemini-mode :repo "https://git.carcosa.net/jmcbray/gemini.el.git")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (add-to-list 'auto-mode-alist '("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode)) (doremi-face-set 'gemini-heading-face-1 '((t (:inherit (elpher-gemini-heading1))))) (doremi-face-set 'gemini-heading-face2 '((t (:inherit (elpher-gemini-heading2))))) (doremi-face-set 'gemini-heading-face3 '((t (:inherit (elpher-gemini-heading3))))) #+end_src *** Gemini-write :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(gemini-write :repo "https://alexschroeder.ch/cgit/gemini-write")) (with-eval-after-load 'elpher (require 'gemini-write)) #+end_src * Org mode :package: ** Install it with =straight.el= I want to use the newest version of Org that I can. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'org) (with-eval-after-load 'org (require 'org-tempo) (require 'ox-md)) #+end_src ** Basic settings #+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 org-tags-column (- 0 fill-column -1) ;; Source blocks org-src-tab-acts-natively t org-src-window-setup 'current-window ; the least stupid option 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 org-imenu-depth 8 ;; Exporting org-export-headline-levels 8 org-export-with-smart-quotes t org-export-with-sub-superscripts t) #+end_src ** Aesthetics *** Prettify some other symbols #+begin_src emacs-lisp :noweb-ref functions (defun acdw/org-mode-prettify () "Prettify `org-mode'." (dolist (cell '(("[ ]" . ?☐) ("[X]" . ?☑) ("[-]" . ?◐) ("#+BEGIN_SRC" . ?✎) ("#+begin_src" . ?✎) ("#+BEGIN_QUOTE" . ?❝) ("#+begin_quote" . ?❝) ("#+END_QUOTE" . ?❞) ("#+end_quote" . ?❞) ("#+END_SRC" . ?■) ("#+end_src" . ?■))) (add-to-list 'prettify-symbols-alist cell :append)) (prettify-symbols-mode +1)) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'org-mode-hook #'acdw/org-mode-prettify) #+end_src *** Prettify lists and checkboxes using font-lock from [[https://github.com/KaratasFurkan/.emacs.d#org-1][Furkan Karataş]]. #+begin_src emacs-lisp :noweb-ref modes (with-eval-after-load 'org (font-lock-add-keywords 'org-mode '(("^ *\\([-]\\) " (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))) (font-lock-add-keywords 'org-mode '(("^ *\\([+]\\) " (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "◦")))))) (defface org-checkbox-done-text '((t (:inherit 'font-lock-comment-face :slant normal))) "Face for the text part of a checked org-mode checkbox." :group 'org) (font-lock-add-keywords 'org-mode `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)" 1 'org-checkbox-done-text prepend)) 'append)) #+end_src *** Org-appear :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(org-appear :host github :repo "awth13/org-appear")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default org-appear-autoemphasis t org-appear-autolinks nil org-appear-autosubmarkers t) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'org-mode-hook #'org-appear-mode) #+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 #+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. ;; FURTHER NOTE: Ideally, I would follow links unless point ;; /appeared/ to be at the end of the line (even if it's still ;; inside the link) -- when it would do `org-return'. That ;; would take some /doing/, however. ;; ((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)))) ;; Element in list item, e.g. a link (unpackaged/org-element-descendant-of 'item context)) ;; 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 ** Insert blank lines around headers #+begin_src emacs-lisp :noweb-ref functions (defun unpackaged/org-fix-blank-lines (&optional prefix) "Ensure that blank lines exist between headings and between headings and their contents. With prefix, operate on whole buffer. Ensures that blank lines exist after each headings's drawers." (interactive "P") (org-map-entries (lambda () (org-with-wide-buffer ;; `org-map-entries' narrows the buffer, which prevents us ;; from seeing newlines before the current heading, so we ;; do this part widened. (while (not (looking-back "\n\n" nil)) ;; Insert blank lines before heading. (insert "\n"))) (let ((end (org-entry-end-position))) ;; Insert blank lines before entry content (forward-line) (while (and (org-at-planning-p) (< (point) (point-max))) ;; Skip planning lines (forward-line)) (while (re-search-forward org-drawer-regexp end t) ;; Skip drawers. You might think that `org-at-drawer-p' ;; would suffice, but for some reason it doesn't work ;; correctly when operating on hidden text. This ;; works, taken from `org-agenda-get-some-entry-text'. (re-search-forward "^[ \t]*:END:.*\n?" end t) (goto-char (match-end 0))) (unless (or (= (point) (point-max)) (org-at-heading-p) (looking-at-p "\n")) (insert "\n")))) t (if prefix nil 'tree))) #+end_src I fix the headline spacing every time I save. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--org-mode-fix-blank-lines () (when (eq major-mode 'org-mode) (let ((current-prefix-arg 4)) ; Emulate C-u (call-interactively 'unpackaged/org-fix-blank-lines)))) (add-hook 'before-save-hook #'hook--org-mode-fix-blank-lines) #+end_src ** Org Agenda I'm trying to organize my life. Inspo: - [[https://github.com/cadadr/configuration/blob/3e11ef25344188cc55b16f314c3c5358ace8a266/emacs.d/init.el#L4625][Göktuğ Kayaalp]] - [[https://pages.sachachua.com/.emacs.d/Sacha.html#org1db6fe9][Sacha Chua]] *** Basic Agenda Settings #+begin_src emacs-lisp :noweb-ref settings (setq-default org-agenda-files ; look for files in ~/org (list org-directory) ;; agenda org-agenda-span 5 org-agenda-skip-scheduled-if-done t org-agenda-skip-deadline-if-done t org-deadline-warning-days 2 ;; logging org-log-into-drawer "LOGBOOK" org-log-done t ;; archive org-archive-location (concat (expand-file-name ".archive.org" org-directory) "::")) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "C-a") #'org-agenda) #+end_src *** Agenda hooks #+begin_src emacs-lisp :noweb-ref hooks (defun hook--org-agenda-mode () (hl-line-mode +1)) (add-hook 'org-agenda-mode-hook #'hook--org-agenda-mode) #+end_src *** Refile #+begin_src emacs-lisp :noweb-ref settings (setq org-refile-targets '((org-agenda-files . (:maxlevel . 3)))) #+end_src *** Calendar settings I'm not sure where else to put these, to be honest. #+begin_src emacs-lisp :noweb-ref settings (setq-default calendar-date-style 'iso) ; YYYY-mm-dd #+end_src *** Habits Org can track habits! Great stuff. I need to add it to =org-modules=, though. #+begin_src emacs-lisp :noweb-ref settings (add-to-list 'org-modules 'org-habit) #+end_src Now I just add a =habit= property to a subtree, and BAM! *** Org Todo Keywords These need some more thinking -- e.g., the MEETING sequence should maybe be a type, or maybe I need to include those in something else altogether. Hm. #+begin_src emacs-lisp :noweb-ref settings (setq-default org-todo-keywords '((sequence "TODO(t)" "STARTED(s)" "WAITING(w@/!)" "SOMEDAY(.)" "|" "DONE(x!)" "CANCELLED(c@/!)") (sequence "RECUR(r)" "|" "DONE(x!)") (sequence "MEETING(m)"))) #+end_src ** Org Capture #+begin_src emacs-lisp :noweb-ref packages (require 'org-capture) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'org-capture (define-key acdw/leader (kbd "C-c") #'org-capture)) #+end_src I've still got a lot of thinking to do about what kinds of things I want to capture, but I guess for now I can start with the basics: TODO, and Diary. #+begin_src emacs-lisp :noweb-ref settings (defvar acdw/org-inbox-file (expand-file-name "inbox.org" org-directory)) (defvar acdw/org-diary-file (expand-file-name "diary.org" org-directory)) (setq-default org-capture-templates `(;; Todo -- these go to the Inbox for further processing ("t" "Quick Task" entry (file ,acdw/org-inbox-file) "* TODO %^{Task}\n" :immediate-finish t) ("T" "Task" entry (file ,acdw/org-inbox-file) "* TODO %^{Task}\n") ("." "Today" entry (file ,acdw/org-inbox-file) "* TODO %^{Task}\nSCHEDULED: %t\n" :immediate-finish t) ;; Meetings ("m" "Meeting" entry (file ,acdw/org-inbox-file) "* MEETING %^{Meeting}\n%^T\n\n%?") ;; Diary -- for the special Diary file ("j" "Diary entry" entry (file+olp+datetree ,acdw/org-diary-file) "* %U\n\n%?" :empty-lines 1) ;; Books to read ("b" "Book to read" entry (file "books.org") "* TOREAD %^{Title} :PROPERTIES: :Author: %^{Author} :END:\n" :immediate-finish t))) #+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 "exe" win-app-dir) (expand-file-name "exe/bin" win-app-dir) (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) (expand-file-name "Everything" win-app-dir) (expand-file-name "Win-builds/bin" win-app-dir) (expand-file-name "Z/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)]] - GUI Emacs sets the exec-path only from Windows environment variable but not from .emacs file [[https://emacs.stackexchange.com/questions/27326/gui-emacs-sets-the-exec-path-only-from-windows-environment-variable-but-not-from][(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 integration 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. ** All systems I'll put generic system-integrating code here. *** Edit with Emacs :package: Install the [[https://addons.mozilla.org/en-US/firefox/addon/edit-with-emacs1/][Firefox Addon]] alongside this package to edit web forms in Emacs (or the [[https://chrome.google.com/webstore/detail/edit-with-emacs/ljobjlafonikaiipfkggjbhkghgicgoh][Chrome one]] if you... /hate/ freedom :P). #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'edit-server) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'after-init-hook #'edit-server-start) #+end_src *** =git-sync= ~/org I don't know where else to put this, but it's just a little command to run =git-sync= in =org-directory=. #+begin_src emacs-lisp :noweb-ref functions (defun acdw/org-sync () "Run git-sync in `org-directory'. Requires git-sync." (interactive) (async-shell-command (format "git -C %s sync" (expand-file-name org-directory)))) (define-key acdw/leader (kbd "C-M-o") #'acdw/org-sync) #+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. Many 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 See also [[https://www.gnu.org/software/emacs/manual/html_mono/efaq-w32.html][the GNU FAQ for Windows]]. At some point I should really dig into the multiple settings available for w32 systems. See also [[https://github.com/bbatsov/prelude/blob/master/core/prelude-windows.el][the Prelude Windows configuration]] (modifier keys). #+begin_src emacs-lisp (setq-default w32-allow-system-shell t ; enable cmd.exe as shell ;; modifier keys w32-pass-lwindow-to-system nil w32-lwindow-modifier 'super w32-pass-rwindow-to-system nil w32-rwindow-modifier 'super w32-pass-apps-to-system nil w32-apps-modifier 'hyper) #+end_src *** Scripts :PROPERTIES: :header-args: :noweb yes :mkdirp yes :END: **** Common variables #+begin_src bat :noweb-ref w32-bat-common if not exist %HOME% (set HOME=%~dp0..\..) set EMACS=%HOME%\Applications\Emacs\bin\runemacs.exe set EMACSC=%HOME%\Applications\Emacs\bin\emacsclientw.exe set GIT=%HOME%\Applications\Git\bin\git.exe #+end_src **** Emacs Autostart 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 Autostart.cmd" "") <> REM git pull the config start "git" /wait %GIT% -C %HOME%\.emacs.d pull REM start emacs chdir %HOME% start "emacs-daemon" /wait %EMACS% --daemon start "emacs-client" %EMACSC% -n -c -a "" %* #+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" "") <> start "emacs" "%EMACSC%" -n -c -a "%EMACS%" %* #+end_src **** Emacs Daemon #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Daemon.cmd" "") <> start "emacs-daemon" %EMACS% --daemon #+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" "") <> start "emacs-safe" "%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" "") <> start "emacs-debug" "%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 --- personal configuration -*- lexical-binding: t -*- #+end_src *** Header & disclaimer :PROPERTIES: :header-args: :noweb-ref disclaimer :END: #+begin_src emacs-lisp ;; Copyright (C) 2020 Case Duckworth ;; Author: Case Duckworth ;; Created: Sometime during the Covid-19 lockdown, 2019 ;; Keywords: configuration ;; URL: https://tildegit.org/acdw/emacs ;; This file is not part of GNU Emacs. ;;; Commentary: ;; This file is automatically tangled from config.org. ;; Hand edits will be overwritten! #+end_src *** The rest #+begin_src emacs-lisp <> ;;; Code: ;;; REQUIRES <> ;;; VARIABLES <> ;;; ACDW MODE <> ;;; PACKAGES <> ;;; FUNCTIONS <> ;;; SETTINGS <> ;;; SYSTEM-DEPENDENT SETTINGS ;; at home (eval-and-compile (when (memq system-type '(gnu gnu/linux gnu/kfreebsd)) <> )) ;; at work (eval-and-compile (when (memq system-type '(ms-dos windows-nt)) <> )) ;;; MODES <> ;;; HOOKS <> ;;; BINDINGS <> ;;; config.el ends here #+end_src *** Ease of editing :PROPERTIES: :header-args: :tangle no :END: #+begin_src emacs-lisp :noweb-ref functions (defun acdw/find-config () "Find `config.org'." (interactive) (find-file (locate-user-emacs-file "config.org"))) #+end_src Bind it to =C-z i= because =C-z C-c= is taken for capture. #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "i") #'acdw/find-config) #+end_src *** Ease of reloading :PROPERTIES: :header-args: :tangle no :END: #+begin_src emacs-lisp :noweb-ref functions (defun acdw/reload () "Tangle and reload Emacs configuration." (interactive) (let ((config (locate-user-emacs-file "config.org"))) ;; tangle (with-current-buffer (find-file-noselect config) (message "Tangling config.org...") (let ((prog-mode-hook nil) (inhibit-redisplay t) (inhibit-message t)) (add-to-list 'load-path (locate-user-emacs-file "straight/build/org/")) (require 'org) (org-babel-tangle))) (message "Tangling config.org... Done.") ;; load init files (load (locate-user-emacs-file "early-init.el")) (load (locate-user-emacs-file "init.el")) (load (locate-user-emacs-file "config.el")))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "C-M-r") #'acdw/reload) #+end_src ** init.el :PROPERTIES: :header-args: :tangle init.el :noweb yes :END: The classic Emacs initiation file. *** Header #+begin_src emacs-lisp ;;; init.el -*- lexical-binding: t -*- <> ;;; Code: #+end_src *** Prefer newer files to older files #+begin_src emacs-lisp (setq-default load-prefer-newer t) #+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. /Note/ the longish comment before the =unless= form -- it was pretty tough for me to wrap my head around the needed boolean expression to tangle config.org. Booleans, yall! #+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 (locate-user-emacs-file "straight/build/org"))) ;; Okay, let's figure this out. ;; `and' evaluates each form, and returns nil on the first that ;; returns nil. `unless' only executes its body if the test ;; returns nil. So. ;; 1. Test if config.org is newer than config.el. If it is (t), we ;; *want* to evaluate the body, so we need to negate that test. ;; 2. Try to load the config. If it errors (nil), it'll bubble that ;; to the `and' and the body will be evaluated. (unless (and (not (file-newer-than-file-p config.org config.el)) (load config :noerror)) ;; 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))) ;;; init.el ends here #+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; -*- <> ;;; Code: ;; BOOTSTRAP PACKAGE MANAGEMENT <> ;; SETUP FRAME <> ;;; early-init.el ends here #+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!* ** Inspiration and further reading - [[https://github.com/bbatsov/prelude][Emacs Prelude]] - [[https://github.com/alphapapa/unpackaged.el][Unpackaged]]