diff --git a/.gitignore b/.gitignore index 7014eed..6836320 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,6 @@ -# ignore everything -* - -# except ... -!config.org -!init.el -!early-init.el -!.gitignore -!.gitattributes -!README.* - -# and ... -!var/ -var/* -!var/elpher-bookmarks.el -!var/elfeed/ -var/elfeed/* -!var/elfeed/db - -!etc/ -etc/* -!etc/eshell -!etc/eshell/* - -!elfeed.org +.org-id-locations +eln-cache/ +var/ +etc/ +straight/ +transient/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index de537e6..0000000 --- a/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -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. diff --git a/README.org b/README.org deleted file mode 120000 index f13833e..0000000 --- a/README.org +++ /dev/null @@ -1 +0,0 @@ -config.org \ No newline at end of file diff --git a/config.org b/config.org deleted file mode 100644 index c4af8b0..0000000 --- a/config.org +++ /dev/null @@ -1,3638 +0,0 @@ -#+TITLE: Emacs configuration, literate-style -#+AUTHOR: Case Duckworth -#+STARTUP: overview -#+PROPERTY: header-args :results output silent -#+AUTO_TANGLE: yes - -* 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 - -*** Frame titles - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default frame-title-format - '("Emacs " - mode-line-client - mode-line-modified - " " - (:eval (if (buffer-file-name) - (abbreviate-file-name (buffer-file-name)) - "%b")) - )) -#+end_src - -*** Fringes -:PROPERTIES: -:header-args: :noweb-ref early-init-frame -: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 - (defun hook--setup-fringes-curly-arrows () - "Set up curly-arrow fringes." - (define-fringe-bitmap 'left-curly-arrow - [#b11000000 - #b01100000 - #b00110000 - #b00011000]) - - (define-fringe-bitmap 'right-curly-arrow - [#b00011000 - #b00110000 - #b01100000 - #b11000000])) - - (add-hook 'after-init-hook #'hook--setup-fringes-curly-arrows) -#+end_src - -***** Arrows (truncation lines) - -#+begin_src emacs-lisp - (defun hook--setup-fringes-arrows () - "Setup arrow fringe bitmaps." - (define-fringe-bitmap 'left-arrow - [#b00000000 - #b01010100 - #b01010100 - #b00000000]) - - (define-fringe-bitmap 'right-arrow - [#b00000000 - #b00101010 - #b00101010 - #b00000000])) - - (add-hook 'after-init-hook #'hook--setup-fringes-arrows) -#+end_src - -** Windows - -*** Window Dividers - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default window-divider-default-places 'right-only ; only 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 - -*** 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 - -*** Window layouts - -Let's try settings from [[https://github.com/nex3/perspective-el#some-musings-on-emacs-window-layouts][nex3]] on Github. - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default - ;; `display-buffer-alist' is the big alist of things - display-buffer-alist - '((".*" (display-buffer-reuse-window display-buffer-same-window))) - ;; reuse windows in other frames - display-buffer-reuse-frames t - ;; avoid resizing - even-window-sizes nil) -#+end_src - -*** Switch to other window or buffer :crux: - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "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 - -*** Split and switch - -from [[https://github.com/danielmai/.emacs.d/blob/master/config.org#window][Daniel Mai]], though I've seen it before. - -#+begin_src emacs-lisp :noweb-ref functions - (defun vsplit-other-window () - "Split the window vertically and switch to the new window." - (interactive) - (split-window-vertically) - (other-window 1 nil)) - - (defun hsplit-other-window () - "Split the window horizontally and switch to the new window." - (interactive) - (split-window-horizontally) - (other-window 1 nil)) -#+end_src - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "C-x 2" #'vsplit-other-window) - (acdw/bind "C-x 3" #'hsplit-other-window) -#+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 - (acdw/bind "C-x" #'scratch :map acdw/leader) -#+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 - (acdw/bind "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 - (unless (fboundp 'clean-buffer-list) - (autoload #'clean-buffer-list "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)) - - (with-eval-after-load 'midnight - (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) - - (set-face-attribute 'variable-pitch nil - :family "DejaVu Serif" - :height 110) -#+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) - - (set-face-attribute 'variable-pitch nil - :family "Cambria" - :height 105) -#+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")) - (unless (fboundp 'unicode-fonts-setup) - (autoload #'unicode-fonts-setup "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 () - "Run `unicode-fonts-setup', then remove the hook." - (when (window-system) - (unicode-fonts-setup)) - (remove-hook 'after-init-hook #'hook--unicode-fonts-setup)) - - (add-hook 'after-init-hook #'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 - -*** =variable-pitch= fonts in =text-mode= - -#+begin_src emacs-lisp :noweb-ref modes - (add-hook 'text-mode-hook #'variable-pitch-mode) -#+end_src - -** Theme - -*** 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 . section) - (t . no-color)) - modus-themes-scale-headings nil - modus-themes-mode-line nil) -#+end_src - -**** Force headings to be =fixed-pitch= - -To enable the proper alignment of Org tags, I want headings to inherit from the -=fixed-pitch= family as well as themselves. This paves the way to enable -=variable-pitch-mode= in Emacs. - -#+begin_src emacs-lisp :noweb-ref settings - (dolist (face '(modus-theme-heading-1 - modus-theme-heading-2 - modus-theme-heading-3 - modus-theme-heading-4 - modus-theme-heading-5 - modus-theme-heading-6 - modus-theme-heading-7 - modus-theme-heading-8)) - (doremi-face-set face - '((t (:inherit (face fixed-pitch bold)))))) -#+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 - -**** Minions mode :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'minions) - (require 'minions) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (minions-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 - -**** Which-function mode - -Shows where we are in the modeline. - -#+begin_src emacs-lisp :noweb-ref modes - (which-function-mode +1) -#+end_src - -**** Mode-line faces - -#+begin_src emacs-lisp :noweb-ref linux-specific - (doremi-face-set 'mode-line - '((t (:family "Terminus" - :height 1.0)))) - (doremi-face-set 'mode-line-inactive - '((t (:family "Terminus" - :height 1.0)))) -#+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 - -*** Icomplete - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'icomplete-vertical) -#+end_src - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default - completion-styles '(partial-completion substring flex) - completion-category-overrides '((file - (styles basic substring flex)))) - (setq-default - icomplete-delay-completions-threshold 0 - icomplete-max-delay-chars 0 - icomplete-compute-delay 0 - icomplete-show-matches-on-no-input t - icomplete-hide-common-prefix nil - icomplete-with-completion-tables t - icomplete-in-buffer t) -#+end_src - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind-after-map icomplete icomplete-minibuffer-map - (("" #'icomplete-forward-completions) - ("C-n" #'icomplete-forward-completions) - ("" #'icomplete-backward-completions) - ("C-p" #'icomplete-backward-completions))) - - (acdw/bind "C-v" #'icomplete-vertical-toggle - :after 'icomplete-vertical - :map icomplete-minibuffer-map) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (fido-mode -1) ; I take care of this myself - (icomplete-mode +1) - (icomplete-vertical-mode +1) -#+end_src - -*** Orderless :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'orderless) - (unless (fboundp 'orderless) - (autoload #'orderless "orderless")) -#+end_src - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default completion-styles '(orderless)) -#+end_src - -*** Consult :package: - -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 - (acdw/bind-after-map consult nil - ;; C-c bindings (`mode-specific-map') - (("C-c h" #'consult-history) - ("C-c m" #'consult-mode-command) - ;; C-x bindings (`ctl-x-map') - ("C-x M-:" #'consult-complex-command) - ("C-x b" #'consult-buffer) - ("C-x 4 b" #'consult-buffer-other-window) - ("C-x 5 b" #'consult-buffer-other-frame) - ("C-x r x" #'consult-register) - ("C-x r b" #'consult-bookmark) - ;; M-g bindings (`goto-map') - ("M-g g" #'consult-line) - ("M-g M-g" #'consult-line) - ("M-g o" #'consult-outline) - ("M-g m" #'consult-mark) - ("M-g k" #'consult-global-mark) - ("M-g i" #'consult-imenu) - ("M-g e" #'consult-error) - ;; M-s bindings (`search-map') - ("M-s g" #'consult-grep) ; alts: - ; consult-git-grep, - ; consult-ripgrep - ("M-s f" #'consult-find) ; alts: - ; consult-locate - ("M-s l" #'consult-line) - ("M-s m" #'consult-multi-occur) - ("M-s k" #'consult-keep-lines) - ("M-s u" #'consult-focus-lines) - ;; Other bindings - ("M-y" #'consult-yank-pop) - (" a" #'consult-apropos) - ("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 - -** 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 - (acdw/bind "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 " acdw" - :keymap acdw/map) - - (define-globalized-minor-mode acdw/global-mode acdw/mode acdw/mode) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (blackout 'acdw/mode) -#+end_src - -*** =acdw/bind= macro - -Since defining keys can be a chore, I've written this macro to make it just a -/little/ bit easier. It's /not/ as comprehensive as =bind-key=, but it's just a -little sugar on top of =define-key= et al. - -#+begin_src emacs-lisp :noweb-ref functions - (defmacro acdw/bind (key def &rest args) - "A simple key-binding macro that takes care of the annoying stuff. - - If KEY is a vector, it's passed directly to `define-key', - otherwise it wraps it in `kbd'. It does NOT quote any - definitions, because I like to keep those explicit in the - definitions. - - The following keywords are recognized: - - :after PACKAGE-OR-FEATURE .. wrap key definition in `with-eval-after-load' - :map KEYMAP .. define key in KEYMAP instead of `acdw/map'" - (let* ((after (plist-get args :after)) - (map (or (plist-get args :map) 'acdw/map)) - (key (if (vectorp key) key `(kbd ,key))) - (def-key `(define-key ,map ,key ,def))) - (if after - `(with-eval-after-load ,after - ,def-key) - def-key))) - - (defmacro acdw/bind-after-map (feature keymap bindings) - "Wrap multiple calls to `acdw/bind' in a `with-eval-after-load' form. - - FEATURE is the argument to `with-eval-after-load'. KEYMAP is - passed to the `:map' argument of `acdw/bind', if it's non-nil." - (declare (indent 2)) - (let ((bind-list) - (name-string (if (stringp feature) feature - (symbol-name feature)))) - (dolist (bind bindings bind-list) - (push `(progn (unless (fboundp ,(cadr bind)) - (autoload ,(cadr bind) - ,name-string nil t)) - (acdw/bind ,@bind - :map - ,(if keymap keymap - 'acdw/map))) - bind-list)) - `(with-eval-after-load ',feature - ,@bind-list))) -#+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 - -*** Fast 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 - -*** Scroll margins - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default scroll-margin 0 - scroll-conservatively 101 ; if greater than 100 ... - scroll-preserve-screen-position 1) -#+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 - (run-with-idle-timer 3 nil #'require 'savehist nil t) -#+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 - (run-with-idle-timer 5 nil #'require 'recentf nil t) -#+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 - (with-eval-after-load 'recentf - (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 - (with-eval-after-load 'recentf - (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 - (with-eval-after-load 'recentf - (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 - -*** Simple GC munging about - -From [[https://bling.github.io/blog/2016/01/18/why-are-you-changing-gc-cons-threshold/][bling]], from ... 2016? Maybe this isn't great, but it's one less package so -I'm going to try it for now. - -#+begin_src emacs-lisp :noweb-ref hooks - (defconst gc-cons-basis (* 800 1000) - "The basis value to which to return after a max jump. - - 800,000 (800 KB) is Emacs' default.") - - (defun hook--gc-cons-maximize () - "Set `gc-cons-threshold' to the highest possible. - For memory-intensive features." - (setq gc-cons-threshold most-positive-fixnum)) - - (defun hook--gc-cons-baseline () - "Return `gc-cons-threshold' to `gc-cons-basis'. - For after memory intensive operations." - (setq gc-cons-threshold gc-cons-basis)) - - (add-hook 'minibuffer-setup-hook #'hook--gc-cons-maximize) - (add-hook 'minibuffer-exit-hook #'hook--gc-cons-baseline) -#+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 - -** Startup time - -Just for me to know, and in case I ever want to make it snappier. This function -is from [[https://blog.d46.us/advanced-emacs-startup/][Joe Schafer]]. - -As a benchmark, on Windows, =emacs -Q= starts up in *0.188585* seconds, and -=emacs -q= starts in *0.373297*. - -#+begin_src emacs-lisp :noweb-ref hooks - (defun hook--message-startup-time () - "Message Emacs' startup time." - (message "Emacs ready in %s with %d garbage collections." - (format "%.2f seconds" - (float-time (time-subtract after-init-time - before-init-time))) - gcs-done)) - - (add-hook 'emacs-startup-hook #'hook--message-startup-time) -#+end_src - -* Files - -** Encoding - -*** UTF-8 - -It's 2020. Let's encode files like it is. - -#+begin_src emacs-lisp :noweb-ref settings - (set-charset-priority 'unicode) - (set-language-environment "UTF-8") - - (prefer-coding-system 'utf-8-unix) - (set-default-coding-systems 'utf-8-unix) - (set-terminal-coding-system 'utf-8-unix) - (set-keyboard-coding-system 'utf-8-unix) - (set-selection-coding-system 'utf-8-unix) - - (setq-default locale-coding-system 'utf-8-unix - coding-system-for-read 'utf-8-unix - coding-system-for-write 'utf-8-unix - buffer-file-coding-system 'utf-8-unix - - org-export-coding-system 'utf-8-unix - org-html-coding-system 'utf-8-unix ; doesn't take from above - - default-process-coding-system '(utf-8-unix . utf-8-unix) - 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 - -I also want to display the fill-column: - -#+begin_src emacs-lisp :noweb-ref modes - (global-display-fill-column-indicator-mode +1) -#+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 - (acdw/bind "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 - -2a. Enable =visual-line-mode= everywhere. I do this because when I'm typing a -long line, I don't want it to accidentally push my viewport out to the right. I -want the text to invisibly wrap, and /then/ wrap "in the real world," as it were. - -#+begin_src emacs-lisp :noweb-ref modes - (global-visual-line-mode +1) -#+end_src - -2b. 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. - -#+begin_src emacs-lisp :noweb-ref modes - (blackout 'auto-fill-mode) -#+end_src - -*** Visual fill column mode - -**** First: fix scrolling in margins - -This has to be done /before/ loading the package. It's included in -=visual-fill-column=, too, but for some reason isn't loaded there. - -#+BEGIN_SRC emacs-lisp - (dolist (margin '(right-margin left-margin)) - (dolist (button '(mouse-1 mouse-2 mouse-3)) - (global-set-key (vector margin button) - (global-key-binding (vector button))))) - - (mouse-wheel-mode +1) - - (when (bound-and-true-p mouse-wheel-mode) - (dolist (margin '(right-margin left-margin)) - (dolist (event '(mouse-wheel-down-event - mouse-wheel-up-event - wheel-down - wheel-up - mouse-4 - mouse-5)) - (global-set-key (vector margin event) #'mwheel-scroll)))) -#+END_SRC - -**** Then: =visual-fill-column= :package: - -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 - -*** USE TABs - -I love TABs. They're great. I want to use them, dag nab it. Emacs sometimes -makes that harder than it should be, but let's start at the basics. - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default indent-tabs-mode t - tab-width 8 - ;; smie is a common indentation thing for a lot of other modes. - smie-indent-basic 8) -#+end_src - -**** Smart tabs :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'smart-tabs-mode) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (smart-tabs-insinuate 'c 'c++ 'java 'javascript 'cperl 'python 'ruby 'nxml) -#+end_src - -**** TODO smart-tabs-mode - -[[https://github.com/natecox/dotfiles/blob/master/emacs/emacs.d/nathancox.org#indentation][see here]], and [[https://github.com/jcsalomon/smarttabs][here]]. - -** 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 - (acdw/bind "C-=" #'er/expand-region) -#+end_src - -*** Pulse the modified region with goggles - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'goggles) -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (defun fix--goggles-mode () - "Fix goggles-mode to blackout the lighter." - (goggles-mode) - (blackout 'goggles-mode)) - - (add-hook 'text-mode-hook #'fix--goggles-mode) - (add-hook 'prog-mode-hook #'fix--goggles-mode) -#+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 - (acdw/bind "C-/" #'undo-fu-only-undo) - (acdw/bind "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. - -*** Isearch - -I want to search by regexp by default. - -#+begin_src emacs-lisp :noweb-ref bindings - (define-key acdw/map (kbd "C-s") #'isearch-forward-regexp) - (define-key acdw/map (kbd "C-r") #'isearch-backward-regexp) - (define-key acdw/map (kbd "C-M-s") #'isearch-forward) - (define-key acdw/map (kbd "C-M-r") #'isearch-backward) -#+end_src - -*** Anzu setup :package: - -#+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 - (acdw/bind-after-map anzu nil - (([remap query-replace] #'anzu-query-replace-regexp) - ([remap query-replace-regexp] #'anzu-query-replace) - ([remap isearch-query-replace] #'anzu-isearch-query-replace - :map isearch-mode-map) - ([remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp - :map isearch-mode-map))) -#+end_src - -** Smart scan :package: - -Like Vim's =*= / =#=. - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'smartscan) - (autoload 'smartscan-mode "smartscan") -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'prog-mode-hook #'smartscan-mode) -#+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 - (acdw/bind "M-;" #'comment-or-uncomment-region) -#+end_src - -** Goto address mode - -"Buttonize URLs and Email addresses." - -#+begin_src emacs-lisp :noweb-ref modes - (when (fboundp 'global-goto-address-mode) - (global-goto-address-mode +1)) -#+end_src - -* Writing - -** Word count - -*** Key binding - -I just found out that =M-== counts the words in a region. That's great, but I -often want to count the words in the whole buffer. - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "M-=" #'count-words) -#+end_src - -** Spell checking - -*** Settings - -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 - -*** Flyspell - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'text-mode-hook #'flyspell-mode) - (add-hook 'prog-mode-hook #'flyspell-prog-mode) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (blackout 'flyspell-mode) -#+end_src - -*** Flyspell-correct :package: - -Display corrections with =completing-read=. - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'flyspell-correct) -#+end_src - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "C-;" #'flyspell-correct-wrapper - :after 'flyspell - :map flyspell-mode-map) -#+end_src - -* Reading - -** Smooth-scrolling of images :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'iscroll) -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'text-mode-hook #'iscroll-mode) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (add-hook 'iscroll-mode-hook - #'(lambda () (blackout 'iscroll-mode))) -#+end_src - -** Reading mode - -A custom mode to make reading comfy - -#+begin_src emacs-lisp :noweb-ref modes - (define-minor-mode acdw/reading-mode - "Make reading comfier." - :lighter " Read" ; later: book emoji - (if acdw/reading-mode - (progn ;; turn on - (text-scale-increase +1) - (visual-fill-column-mode +1) - (iscroll-mode +1) - (display-fill-column-indicator-mode -1)) - (progn ;; turn off - (text-scale-increase 0) - (visual-fill-column-mode -1) - (iscroll-mode -1) - (display-fill-column-indicator-mode +1)))) -#+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 - show-paren-when-point-inside-paren t - show-paren-when-point-in-periphery t) -#+end_src - -*** COMMENT Smart parentheses :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package '(smartparens - :host github - :repo "Fuco1/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 - -**** Enable in programming modes - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'prog-mode-hook #'smartparens-mode) - - (dolist (hook '(lisp-mode-hook - emacs-lisp-mode-hook)) - (add-hook 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 'smartparens - (sp-use-paredit-bindings)) -#+end_src - -*** Electric pairs - -=smart-parens= is acting up on me, and Emacs has an included mode to do pretty -much everything I do with =smart-parens= -- =electric-pair-mode=. Let's try it. - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'prog-mode-hook #'electric-pair-local-mode) -#+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 nil) -#+end_src - -*** Reformatter :package: - -Steve Purcell's automatic reformatting tool. - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'reformatter) - (require 'reformatter) -#+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 - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "" #'recompile) -#+end_src - -** Language-specific - -*** Generic-x - -from [[https://www.reddit.com/r/emacs/comments/lfww57/weekly_tipstricketc_thread/gmtk79e/][u/Bodertz]], apparently =generic-x= just ... has syntax highlighting for a ton -of (I suppose) generic files. - -#+begin_src emacs-lisp :noweb-ref packages - (require 'generic-x) -#+end_src - -*** 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 - -**** Macrostep :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'macrostep) -#+end_src - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "`" #'macrostep-expand - :map acdw/leader) -#+end_src - -*** Web - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'web-mode) - (unless (fboundp 'web-mode) - (autoload #'web-mode "web-mode" nil t)) -#+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 - -*** i3 config - -I use i3 ... for now. But I only want to load the relevant mode /if/ I have i3 -installed. - -#+begin_src emacs-lisp :noweb-ref packages - (when (executable-find "i3") - (straight-use-package 'i3wm-config-mode)) -#+end_src - -*** Shell scripts - -**** Shellcheck :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'flymake-shellcheck) - (autoload 'flymake-shellcheck-load "flymake-shellcheck") -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'sh-mode-hook #'flymake-shellcheck-load) -#+end_src - -**** Formatting - -***** =shfmt= :package: - -#+begin_src emacs-lisp :noweb-ref modes - (when (executable-find "shfmt") - (reformatter-define sh-format - :program "shfmt" - - :lighter "Shfmt") - - (add-hook 'sh-mode-hook #'sh-format-on-save-mode)) -#+end_src - -***** =sh-mode= indenting - -I'm trying to make this match what =shfmt= does as much as I can, to avoid the -reformatting when saving. - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default sh-basic-offset 8 - sh-indent-after-case 0) -#+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 - -*** SHR - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default shr-max-width fill-column - shr-width fill-column) -#+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 - (acdw/bind "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 - (acdw/bind "i" #'dired-subtree-toggle :after 'dired :map dired-mode-map) -#+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 - (acdw/bind "g" #'magit-status :map acdw/leader) -#+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 - -#+begin_src emacs-lisp :noweb-ref packages - ;; first, "fix" org-version - (with-eval-after-load 'org - (defun org-version () - "Fix of `org-version' for `elfeed'. - - I don't know what the actual version is, but 9.5 should have us - covered. It's somewhere past 9." - "9.5") - (straight-use-package 'elfeed)) -#+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 - (add-hook 'elfeed-show-mode-hook #'acdw/reading-mode) -#+end_src - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "f" #'elfeed :map acdw/leader) -#+end_src - -**** Elfeed-protocol - -I have Miniflux set up on my server, so I can use its Fever integration. - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'elfeed-protocol) -#+end_src - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default elfeed-use-curl t - elfeed-curl-extra-arguments '("--insecure") - elfeed-protocol-fever-maxsize 1000) -#+end_src - -#+begin_src emacs-lisp :noweb-ref settings - (setq elfeed-feeds - '(("fever+https://acdw@mf.acdw.net" - :api-url "https://mf.acdw.net/fever/" - :use-authinfo))) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (elfeed-protocol-enable) -#+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) - :height 1.0)))) - (doremi-face-set 'elpher-gemini-heading2 - '((t (:inherit (modus-theme-heading-2) - :height 1.0)))) - (doremi-face-set 'elpher-gemini-heading3 - '((t (:inherit (modus-theme-heading-3) - :height 1.0)))) -#+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 - (acdw/bind-after-map elpher elpher-mode-map - (("n" #'elpher-next-link) - ("p" #'elpher-prev-link) - ("o" #'elpher-follow-current-link) - ("G" #'elpher-go-current))) -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'elpher-mode-hook #'acdw/reading-mode) -#+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" - :fork (:repo "https://tildegit.org/acdw/gemini-write" - :branch "main"))) - - (with-eval-after-load 'elpher - (require 'gemini-write)) -#+end_src - -** Eshell - -I use =eshell= with Emacs, because it works both on Windows and Linux. - -*** Open an eshell or bury its buffer - -adapted from [[https://git.sr.ht/~zge/setup][zge's setup]], which might also be an interesting macro to look into -one day. - -#+begin_src emacs-lisp :noweb-ref functions - (defun acdw/eshell-or-bury () - "Start an `eshell', or bury its buffer if focused." - (interactive) - (if (string= (buffer-name) "*eshell*") ;; XXX: brittle - (bury-buffer) - (eshell))) -#+end_src - -#+begin_src emacs-lisp :noweb-ref bindings - (define-key acdw/leader "s" #'acdw/eshell-or-bury) -#+end_src - -** E-books with nov.el :package: - -I /love/ the name of this package. - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'nov) -#+end_src - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default nov-text-width t) -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) - (add-hook 'nov-mode-hook #'acdw/reading-mode) -#+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 -3) - ;; 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 - -*** COMMENT 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 - -*** Visible-mode - -I'm going to try =visible-mode= instead of =org-appear= since it's built-in. - -#+begin_src emacs-lisp :noweb-ref bindings - (acdw/bind "v" #'visible-mode - :map acdw/leader) -#+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+headline "books.org" "Later") - "* TOREAD %^{Title} - :PROPERTIES: - :Author: %^{Author} - :END:\n" :immediate-finish t))) - -#+end_src - -** Org auto tangle :package: - -#+begin_src emacs-lisp :noweb-ref packages - (straight-use-package 'org-auto-tangle) - (with-eval-after-load 'org - (require 'org-auto-tangle)) -#+end_src - -#+begin_src emacs-lisp :noweb-ref hooks - (add-hook 'org-mode-hook #'org-auto-tangle-mode) -#+end_src - -#+begin_src emacs-lisp :noweb-ref modes - (blackout 'org-auto-tangle-mode) -#+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 =straight.el= - -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 - -** Ignore =straight/build/= - -#+begin_src emacs-lisp :noweb-ref settings - (with-eval-after-load 'recentf - (add-to-list 'recentf-exclude - (expand-file-name "straight/build/" - user-emacs-directory))) -#+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= stuff - -This function require [[https://github.com/simonthum/git-sync][git-sync]]. - -#+begin_src emacs-lisp :noweb-ref functions - (defun acdw/git-sync (directory) - "Run git-sync in DIRECTORY." - (interactive) - (message "Git-Syncing %s..." directory) - (let ((proc (start-process "git-sync" - (get-buffer-create (format "*git-sync:%s*" directory)) - "git" "-C" (expand-file-name directory) "sync"))) - (add-function :after (process-sentinel proc) - (lambda (proc ev) - (cond - ((string-match "finished\n\\'" ev) - (message "Git-Syncing %s...Done." directory))))))) -#+end_src - -**** ~/org - -#+begin_src emacs-lisp :noweb-ref bindings - (defun acdw/git-sync-org () - "Run `acdw/git-sync' on `org-directory'." - (interactive) - (acdw/git-sync org-directory)) - - (define-key acdw/leader (kbd "C-M-o") #'acdw/git-sync-org) -#+end_src - -**** ~/.cache/elfeed/db - -#+begin_src emacs-lisp :noweb-ref bindings - (defun acdw/git-sync-elfeed-db () - "Run `acdw/git-sync' on `elfeed-db-directory'." - (interactive) - (save-some-buffers :no-query nil) - (acdw/git-sync elfeed-db-directory)) - - (define-key acdw/leader (kbd "C-M-f") #'acdw/git-sync-elfeed-db) -#+end_src - -*** Passwords - -**** Password cache - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default password-cache-expiry nil) -#+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 - -*** TRAMP - -It stands for ... something kind of stupid, I don't remember. I'm pulling this -from [[https://github.com/grandfoobah/spartan-emacs/blob/master/spartan-layers/spartan-settings.el][Spartan Emacs]]. It recommends the following in =~/.ssh/config=: - -#+begin_example - Host * - ForwardAgent yes - AddKeysToAgent yes - ControlMaster auto - ControlPath ~/.ssh/master-%r@%h:%p - ControlPersist yes - ServerAliveInterval 10 - ServerAliveCountMax 10 -#+end_example - -#+begin_src emacs-lisp :noweb-ref settings - (setq-default tramp-default-method "ssh" - tramp-copy-size-limit nil - tramp-use-ssh-controlmaster-options nil - tramp-default-remote-shell "/bin/bash") -#+end_src - -** Linux (home) -:PROPERTIES: -:header-args: :noweb-ref linux-specific -:END: - -*** 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. - -*** Environment variables - -**** DICPATH, for Hunspell - -#+begin_src emacs-lisp :noweb-ref windows-specific - (setenv "DICPATH" (expand-file-name "exe/share/hunspell" - "~/Applications/")) -#+end_src - -*** 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 - -*** Other configuration files - -Since I kind of have this weird .. portable setup on Windows, I'm going to start -putting other config files in here that I need. It's a real pain in the ass to -have them all out of sync. - -**** ~/.gitconfig -:PROPERTIES: -:header-args: :noweb no :mkdirp yes -:END: - -#+begin_src gitconfig :tangle (if (eq system-type 'windows-nt) "~/.gitconfig" "") - # tangled from ~/.emacs.d/config.org - [github] - user = duckwork - [credential] - helper = store - [user] - name = Case Duckworth - email = acdw@acdw.net - [pull] - rebase = false - [core] - excludesfile = ~/.gitignore - attributesfile = ~/.gitattributes - autocrlf = false - eol = lf - [diff "lisp"] - # basic - #xfuncname = "^(\\(.*)$" - # advanced - xfuncname = "^(((;;;+ )|\\(|([ \t]+\\(((cl-|el-patch-)?def(un|var|macro|method|custom)|gb/))).*)$" - [diff "org"] - xfuncname = "^(\\*+ +.*)$" - - [url "https://github.com/"] - insteadOf = gh: - pushInsteadOf = gh: - - [url "https://gitlab.com/"] - insteadOf = gl: - pushInsteadOf = gl: - - [url "https://git.sr.ht/"] - insteadOf = sr: - pushInsteadOf = sr: -#+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; coding: utf-8 -*- - <> - ;;; 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 - (message "%s..." "Loading init.el") - (let* (;; Speed up init - (gc-cons-threshold most-positive-fixnum) - ;; (gc-cons-percentage 0.6) - (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 - (message "%s..." "Loading config.org") - (require 'org) - (org-babel-load-file config.org) - (message "%s... Done" "Loading config.org"))) - (message "%s... Done." "Loading init.el") - ;;; 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; coding: utf-8 -*- - <> - ;;; Code: - - (message "%s..." "Loading early-init.el") - ;; BOOTSTRAP PACKAGE MANAGEMENT - <> - ;; SETUP FRAME - <> - (message "%s... Done." "Loading early-init.el") - ;;; 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]] diff --git a/early-init.el b/early-init.el index ef72beb..36bacbe 100644 --- a/early-init.el +++ b/early-init.el @@ -1,21 +1,180 @@ -;;; early-init.el -*- no-byte-compile: t; coding: utf-8 -*- -;; Copyright (C) 2020 Case Duckworth - +;;; early-init.el -*- lexical-binding: t; coding: utf-8 -*- +;; Copyright (C) 2020-2021 Case Duckworth +;; ;; Author: Case Duckworth -;; Created: Sometime during the Covid-19 lockdown, 2019 +;; Created: Sometime during Covid-19, 2020 ;; 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! - +;; URL https://tildegit.org/acdw/emacs +;; +;; This file is NOT part of GNU Emacs. +;; +;;; License: +;; +;; Everyone is permitted to do whatever with this software, without +;; limitation. This software comes without any warranty whatsoever, +;; but with two pieces of advice: +;; - Don't hurt yourself. +;; - Make good choices. +;; +;;; Comentary: +;; +;; Starting with Emacs 27.1, `early-init' is sourced before `package' +;; or any frames. So those are the settings I run in this file. +;; ;;; Code: -(message "%s..." "Loading early-init.el") -;; BOOTSTRAP PACKAGE MANAGEMENT +;; Speed up init +(setq gc-cons-threshold most-positive-fixnum + gc-cons-percentage 0.6 + comp-deferred-compilation nil) + +(defconst gc-cons-basis (* 800 1024) + "The basis value to which to return after a max jump. +800,000 (800 KB) is Emacs' default.") + +(add-hook 'after-init-hook #'(lambda () + (setq gc-cons-threshold gc-cons-basis + gc-cons-percentage 0.1))) + +(defun hook--gc-cons-maximize () + "Set `gc-cons-threshold' to the highest possible. + For memory-intensive features." + (setq gc-cons-threshold most-positive-fixnum)) + +(defun hook--gc-cons-baseline () + "Return `gc-cons-threshold' to `gc-cons-basis'. + For after memory intensive operations." + (setq gc-cons-threshold gc-cons-basis)) + +(add-hook 'minibuffer-setup-hook #'hook--gc-cons-maximize) +(add-hook 'minibuffer-exit-hook #'hook--gc-cons-baseline) + +;; From doom-emacs +(unless (daemonp) + (defvar doom--initial-file-name-handler-alist file-name-handler-alist) + (setq file-name-handler-alist nil) + (defun doom-reset-file-handler-alist-h () + (dolist (handler file-name-handler-alist) + (add-to-list 'doom--initial-file-name-handler-alist handler)) + (setq file-name-handler-alist doom--initial-file-name-handler-alist)) + (add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h)) + +;; Where are we? +(defconst acdw/system (pcase system-type + ('gnu/linux :home) + ((or 'msdos 'windows-nt) :work) + (_ :other))) + +;; Frame initiation + +;; Initialize frames with as little UI as possible. +(setq-default + default-frame-alist ; The default look of frames + `((tool-bar-lines . 0) ; Remove tool bar + (menu-bar-lines . 0) ; Remove menu bar + (vertical-scroll-bars) ; Remove vertical scroll bars + (horizontal-scroll-bars) ; Remove horizontal scroll bars + (width . 84) ; A /little/ wider than `fill-column' + (height . 30) ; Text characters + (left-fringe . 8) ; Width of fringes + (right-fringe . 8) ; (8 is the default) + (font . ,(pcase acdw/system ; Default font + (:home "Terminus 12") + (:work "Consolas 11"))) + ) + + x-underline-at-descent-line t ; underline at the descent line + + scroll-margin 0 ; how many lines to show at window edge + scroll-conservatively 101 ; just enough to bring text into view + scroll-preserve-screen-position 1 ; always keep screen position + + frame-title-format ; Titles for frames + '((:eval (if (buffer-file-name) ; (cf. `mode-line-format') + (abbreviate-file-name (buffer-file-name)) + "%b")) + " " + mode-line-client + mode-line-modified + " - GNU Emacs") + ) + +;; Set the rest of the fonts after initiation +(defun hook--setup-fonts () + (pcase acdw/system + (:home (set-face-attribute 'default nil + :family "Terminus" + :height 120) + (set-face-attribute 'fixed-pitch nil + :family "Terminus" + :height 1.0) + (set-face-attribute 'variable-pitch nil + :family "DejaVu Sans" + :height 1.0)) + (:work (set-face-attribute 'default nil + :familiy "Consolas" + :height 110) + (set-face-attribute 'fixed-pitch nil + :family "Consolas" + :height 1.0) + (set-face-attribute 'variable-pitch nil + :family "Cambria" + :height 1.0)))) + +(add-hook 'after-init-hook #'hook--setup-fonts) + +;; In case I do want the UI elements later, I also disable the modes +;; -- otherwise I'd have to run the mode twice to actually show the +;; thing. + +(defun hook--disable-ui-modes () + (dolist (mode '(tool-bar-mode + menu-bar-mode + scroll-bar-mode + horizontal-scroll-bar-mode)) + (funcall mode -1))) + +;; I run it on the `after-init-hook' so it doesn't slow down init so much. +(add-hook 'after-init-hook #'hook--disable-ui-modes) + +;; Customize the fringe +(setq-default + indicate-empty-lines t ; show an indicator at the end of the buffer + indicate-buffer-boundaries 'right ; show buffer boundaries on the right + visual-line-fringe-indicators ; show continuation indicators on the left + '(left-curly-arrow nil)) + +(defun hook--setup-fringe-bitmaps () + (define-fringe-bitmap 'left-curly-arrow + [#b11000000 + #b01100000 + #b00110000 + #b00011000]) + (define-fringe-bitmap 'right-curly-arrow + [#b00011000 + #b00110000 + #b01100000 + #b11000000]) + (define-fringe-bitmap 'left-arrow + [#b00000000 + #b01010100 + #b01010100 + #b00000000]) + (define-fringe-bitmap 'right-arrow + [#b00000000 + #b00101010 + #b00101010 + #b00000000])) +(add-hook 'after-init-hook #'hook--setup-fringe-bitmaps) + +;; Resize like it's 2021 +(setq-default frame-inhibit-implied-resize t + frame-resize-pixelwise t) + +;; Bootstrap package manager (`straight') + +;; First, I need to make sure it's in the `exec-path'. + (let ((win-app-dir "~/Applications")) (dolist (path (list ;; Windows @@ -31,21 +190,44 @@ (expand-file-name "bin" user-emacs-directory) (expand-file-name "~/bin") (expand-file-name "~/.local/bin") - (expand-file-name "~/Scripts") + (expand-file-name "~/usr/bin") )) (when (file-exists-p path) (add-to-list 'exec-path path :append)))) -;; Set $PATH +;; Set $PATH back to `exec-path', for symmetry's sake. (setenv "PATH" (mapconcat #'identity exec-path path-separator)) -(setq package-enable-at-startup nil) -(defun acdw/bootstrap-straight () - "Bootstrap straight.el." + +;; Set some variables +(defvar acdw/etc-dir + (expand-file-name "etc/" user-emacs-directory) + "Where to put other configurations.") +(defvar acdw/var-dir + (expand-file-name "var/" user-emacs-directory) + "Where to put variable stuff.") + +(setq-default package-enable-at-startup nil + package-quickstart nil + straight-use-package-by-default t + straight-host-usernames '((github . "duckwork") + (gitlab . "acdw")) + straight-base-dir acdw/var-dir) + +;; Run `straight''s bootstrap code, after gitting the code directly. +(if (not (executable-find "git")) + (error "No 'git' in $PATH!") + (call-process "git" nil + (get-buffer-create "*bootstrap-straight-messages*") + nil + "clone" + "https://github.com/raxod502/straight.el" + (expand-file-name "repos/straight.el" + straight-base-dir)) (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name - "straight/repos/straight.el/bootstrap.el" - user-emacs-directory)) + "repos/straight.el/bootstrap.el" + straight-base-dir)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer @@ -57,67 +239,20 @@ (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage))) -(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)))) -;; SETUP FRAME -(add-to-list 'default-frame-alist - '(tool-bar-lines . 0)) -(tool-bar-mode -1) -(add-to-list 'default-frame-alist - '(menu-bar-lines . 0)) -(menu-bar-mode -1) -(add-to-list 'default-frame-alist - '(vertical-scroll-bars . nil) - '(horizontal-scroll-bars . nil)) +;; `use-package' -(scroll-bar-mode -1) -(horizontal-scroll-bar-mode -1) -(setq-default frame-inhibit-implied-resize t - frame-resize-pixelwise t) -(setq-default indicate-empty-lines t) -(setq-default indicate-buffer-boundaries 'right) -(setq-default visual-line-fringe-indicators '(left-curly-arrow nil)) -(defun hook--setup-fringes-curly-arrows () - "Set up curly-arrow fringes." - (define-fringe-bitmap 'left-curly-arrow - [#b11000000 - #b01100000 - #b00110000 - #b00011000]) +(straight-use-package 'use-package) +(require 'use-package) - (define-fringe-bitmap 'right-curly-arrow - [#b00011000 - #b00110000 - #b01100000 - #b11000000])) +;; Message startup time +(defun hook--message-startup-time () + "Message Emacs' startup time." + (message "Emacs ready in %s with %d garbage collections." + (format "%.2f seconds" + (float-time (time-subtract after-init-time + before-init-time))) + gcs-done)) -(add-hook 'after-init-hook #'hook--setup-fringes-curly-arrows) -(defun hook--setup-fringes-arrows () - "Setup arrow fringe bitmaps." - (define-fringe-bitmap 'left-arrow - [#b00000000 - #b01010100 - #b01010100 - #b00000000]) - - (define-fringe-bitmap 'right-arrow - [#b00000000 - #b00101010 - #b00101010 - #b00000000])) - -(add-hook 'after-init-hook #'hook--setup-fringes-arrows) -(message "%s... Done." "Loading early-init.el") -;;; early-init.el ends here +(add-hook 'emacs-startup-hook #'hook--message-startup-time) diff --git a/etc/eshell/aliases b/etc/eshell/aliases deleted file mode 100644 index 24a7efc..0000000 --- a/etc/eshell/aliases +++ /dev/null @@ -1 +0,0 @@ -alias e find-file $1 diff --git a/init.el b/init.el index 7cf9f7a..9808ae2 100644 --- a/init.el +++ b/init.el @@ -1,51 +1,573 @@ ;;; init.el -*- lexical-binding: t; coding: utf-8 -*- -;; Copyright (C) 2020 Case Duckworth - +;; Copyright (C) 2020-2021 Case Duckworth +;; ;; Author: Case Duckworth -;; Created: Sometime during the Covid-19 lockdown, 2019 +;; Created: Sometime during Covid-19, 2020 ;; 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! - +;; URL https://tildegit.org/acdw/emacs +;; +;; This file is NOT part of GNU Emacs. +;; +;;; License: +;; +;; Everyone is permitted to do whatever with this software, without +;; limitation. This software comes without any warranty whatsoever, +;; but with two pieces of advice: +;; - Don't hurt yourself. +;; - Make good choices. +;; +;;; Comentary: +;; ;;; Code: +;; User information +(setq user-full-name "Case Duckworth" + user-mail-address "acdw@acdw.net" + calendar-location-name "Baton Rouge, LA" + calendar-latitude 30.4 + calendar-longitude -91.1 + calendar-date-style 'iso) + +;; Load newer files first (setq-default load-prefer-newer t) -(message "%s..." "Loading init.el") -(let* (;; Speed up init - (gc-cons-threshold most-positive-fixnum) - ;; (gc-cons-percentage 0.6) - (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 - (message "%s..." "Loading config.org") - (require 'org) - (org-babel-load-file config.org) - (message "%s... Done" "Loading config.org"))) -(message "%s... Done." "Loading init.el") -;;; init.el ends here +;; Make C-z more useful +(defvar acdw/leader + (let ((map (make-sparse-keymap)) + (c-z (global-key-binding "\C-z"))) + (global-unset-key "\C-z") + (global-set-key "\C-z" map) + (define-key map "\C-z" c-z) + map) + "A leader key for apps and stuff.") + +(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))) + +;; Dialogs & alerts +(setq-default use-dialog-box nil) ; Don't use a dialog box +(fset 'yes-or-no-p #'y-or-n-p) + +(defun flash-mode-line () + (ding) + (invert-face 'mode-line) + (run-with-timer 0.2 nil #'invert-face 'mode-line)) + +(setq-default visible-bell nil ; Don't use a visible bell + ring-bell-function #'flash-mode-line) + +(defun hook--gc-when-unfocused () + (when-unfocused #'garbage-collect)) + +(add-function :after after-focus-change-function + #'hook--gc-when-unfocused) + +;; Minibuffer +(setq-default + minibuffer-prompt-properties '(read-only t + cursor-intangible t + face minibuffer-prompt) + enable-recursive-minibuffers t + file-name-shadow-properties '(invisible t)) +(file-name-shadow-mode +1) +(minibuffer-depth-indicate-mode +1) + +(use-package savehist + :straight nil + :init + (setq-default + savehist-file (expand-file-name "history" acdw/var-dir) + savehist-additional-variables '(kill-ring search-ring regexp-search-ring) + history-length t + history-delete-duplicates t + savehist-autosave-interval 60) + :config (savehist-mode +1)) + +;; Backups +(setq-default backup-by-copying t + delete-old-versions -1 ; Don't delete old versions + version-control t ; Make numeric backups + vc-make-backup-files t ; Backup version-controlled files + ) + +(let ((dir (expand-file-name "backup" acdw/var-dir))) + (make-directory dir 'parents) + (setq-default backup-directory-alist + `((".*" . ,dir)))) + +;; Lockfiles +(setq-default create-lockfiles nil) ; Are these necessary? + +;; Autosaves +(use-package super-save + :defer 5 ; This package can wait + :init + (setq-default + auto-save-default nil ; Don't use `auto-save' system + super-save-remote-files nil ; Don't save remote files + super-save-exclude '(".gpg") ; Wouldn't work anyway + super-save-auto-save-when-idle t) + :config + (super-save-mode +1)) + +;; Auto-revert +(global-auto-revert-mode +1) ; Automatically revert a file + ; to its on-disk contents + +(use-package saveplace + :straight nil + :init + (setq-default + save-place-file (expand-file-name "places" acdw/var-dir) + save-place-forget-unreadable-files (eq acdw/system :home)) + :config (save-place-mode +1)) + +(use-package recentf + :straight nil + :init + (setq recentf-save-file (expand-file-name "recentf" acdw/var-dir) + recentf-max-menu-items 100 + recentf-max-saved-items nil + recentf-auto-cleanup 'never) + (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))) + :config + (recentf-mode +1) + (add-to-list 'recentf-exclude acdw/var-dir) + (add-to-list 'recentf-exclude acdw/etc-dir) + (add-function :after after-focus-change-function + #'maybe-save-recentf)) + + +;; Uniquify +(use-package uniquify + :straight nil + :init + (setq-default + uniquify-buffer-name-style 'forward ; bubble 'up' the directory tree + uniquify-separator "/" ; separate path elements + uniquify-after-kill-buffer-p t ; hook into buffer kills + uniquify-ignore-buffers-re "^\\*" ; don't worry about special buffers + )) + +;; Scratch +(setq-default + inhibit-startup-screen t ; Don't show the splash screen + initial-buffer-choice t ; Start on *scratch* + initial-scratch-message + (concat ";; Howdy, " + (nth 0 (split-string user-full-name)) "!" + " Welcome to GNU Emacs.\n\n")) + +(defun immortal-scratch () + "Don't kill *scratch* when asked to by `kill-buffer'." + (if (not (eq (current-buffer) (get-buffer "*scratch*"))) + t + (bury-buffer) + nil)) +(add-hook 'kill-buffer-query-functions #'immortal-scratch) + +;; Easier buffer-killing +(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 THIS buffer & window +4 (C-u) => Kill OTHER buffer & window +16 (C-u C-u) => Run the default `kill-buffer'." + (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))))) + +(bind-key "C-x k" #'kill-a-buffer) + +;; UTF-8 with LF line endings +(set-charset-priority 'unicode) +(set-language-environment "UTF-8") + +(prefer-coding-system 'utf-8-unix) +(set-default-coding-systems 'utf-8-unix) +(set-terminal-coding-system 'utf-8-unix) +(set-keyboard-coding-system 'utf-8-unix) +(set-selection-coding-system 'utf-8-unix) + +(setq-default + locale-coding-system 'utf-8-unix + coding-system-for-read 'utf-8-unix + coding-system-for-write 'utf-8-unix + buffer-file-coding-system 'utf-8-unix + + org-export-coding-system 'utf-8-unix + org-html-coding-system 'utf-8-unix ; doesn't take from above + + default-process-coding-system '(utf-8-unix . utf-8-unix) + x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) + +(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)))) + +(add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish) +(add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish) + +;; Cursor +(setq-default cursor-type 'bar + cursor-in-non-selected-windows nil) +(blink-cursor-mode 0) + +;; Filling text +(setq-default fill-column 80) +(global-display-fill-column-indicator-mode +1) + +(bind-key "C-x f" #'find-file) ; I don't set `fill-column', ever + +(setq-default comment-auto-fill-only-comments t) +;; Enable `auto-fill-mode' everywhere +(add-hook 'text-mode-hook #'auto-fill-mode) +(add-hook 'prog-mode-hook #'auto-fill-mode) +;; Also enable `visual-line-mode' everywhere +(global-visual-line-mode +1) +;; "Fix" `visual-line-mode' in `org-mode' +(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) + +(use-package visual-fill-column + :init (setq-default visual-fill-column-center-text t) + :hook visual-fill-column-mode + :config + (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) + +(when (fboundp 'global-so-long-mode) + (global-so-long-mode +1)) + +;; Whitespace +(setq-default whitespace-style '(empty ; remove blank lines at buffer edges + indentation ; clean up indentation + ;; mixed tabs & spaces + space-before-tab + space-after-tab)) +(add-hook 'before-save-hook #'whitespace-cleanup) + +(setq-default indent-tabs-mode t + tab-width 8) + +(use-package smart-tabs-mode + :config + (smart-tabs-insinuate 'c 'c++ 'java 'javascript 'cperl 'python 'ruby 'nxml)) + +;; Window layouts +(setq-default + split-width-threshold 100 ; minimum width for window splits + split-height-threshold 50 ; minimum height for window splits + display-buffer-alist ; how to display buffers + '((".*" . (display-buffer-reuse-window display-buffer-same-window))) + display-buffer-reuse-frames t ; allow reuse of frames + even-window-sizes nil ; avoid resizing windows to even them + help-window-select t ; select *Help* window when opened + ) + +(defun vsplit-other-window () + "Split the window vertically and switch to the new window." + (interactive) + (split-window-vertically) + (other-window 1 nil)) + +(defun hsplit-other-window () + "Split the window horizontally and switch to the new window." + (interactive) + (split-window-horizontally) + (other-window 1 nil)) + +(bind-key "C-x 2" #'vsplit-other-window) +(bind-key "C-x 3" #'hsplit-other-window) + +;; Theming + +(use-package form-feed + :config (global-form-feed-mode +1)) + +(use-package modus-themes + :straight (:host gitlab :repo "protesilaos/modus-themes") + :demand + :init + (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 . section) + (t . no-color)) + modus-themes-scale-headings nil + modus-themes-mode-line nil) + :custom-face + (modus-theme-heading-1 + ((t (:inherit (modus-theme-heading-1 fixed-pitch bold))))) + (modus-theme-heading-2 + ((t (:inherit (modus-theme-heading-2 fixed-pitch bold))))) + (modus-theme-heading-3 + ((t (:inherit (modus-theme-heading-3 fixed-pitch bold))))) + (modus-theme-heading-4 + ((t (:inherit (modus-theme-heading-4 fixed-pitch bold))))) + (modus-theme-heading-5 + ((t (:inherit (modus-theme-heading-5 fixed-pitch bold))))) + (modus-theme-heading-6 + ((t (:inherit (modus-theme-heading-6 fixed-pitch bold))))) + (modus-theme-heading-7 + ((t (:inherit (modus-theme-heading-7 fixed-pitch bold))))) + (modus-theme-heading-8 + ((t (:inherit (modus-theme-heading-8 fixed-pitch bold)))))) + +;; Change themes based on time of day + +(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) + (run-at-time "0:00" (* 60 60 24) sunset-command))) + +(acdw/run-with-sun #'modus-themes-load-operandi + #'modus-themes-load-vivendi) + +(use-package minions + :config (minions-mode +1)) + +(which-function-mode +1) + +(use-package which-key + :config (which-key-mode +1)) + +(delete-selection-mode +1) + +(setq-default + save-interprogram-paste-before-kill t ; save existing text before replacing + yank-pop-change-selection t ; update X selection when rotating ring + x-select-enable-clipboard t ; Enable X clipboards + x-select-enable-primary t + mouse-drag-copy-region t ; Copy a region when mouse-selected + kill-do-not-save-duplicates t ; Don't append the same thing twice + ) + +(use-package smartscan + :config + (global-smartscan-mode +1)) + +(when (fboundp 'global-goto-address-mode) + (global-goto-address-mode +1)) + +(use-package flyspell + :init + (setenv "LANG" "en_US") + (setq-default ispell-program-name "hunspell" + ispell-dictionary "en_US" + ispell-personal-dictionary "~/.hunspell_personal") + :hook + (text-mode . flyspell-mode) + (prog-mode . flyspell-prog-mode) + :config + (ispell-set-spellchecker-params) + (unless (file-exists-p ispell-personal-dictionary) + (write-region "" nil ispell-personal-dictionary nil 0))) + +(use-package flyspell-correct + :bind ("C-;" . flyspell-correct-wrapper)) + +(setq-default show-paren-delay 0 + show-paren-style 'mixed + show-paren-when-point-inside-paren t + show-paren-when-point-in-periphery t) +(show-paren-mode +1) + +(add-hook 'prog-mode-hook #'electric-pair-local-mode) + +(setq-default prettify-symbols-unprettify-at-point 'right-edge) +(add-hook 'prog-mode-hook #'prettify-symbols-mode) + +(add-hook 'after-save-hook + #'executable-make-buffer-file-executable-if-script-p) + +(setq-default compilation-ask-about-save nil ; just save the buffer + compilation-always-kill t ; kill the processes without asking + compilation-scroll-output 'first-error) + +(use-package reformatter + :demand) + +;; Shell scripts +(setq-default sh-basic-offset 8 + smie-indent-basic 8) + +(use-package flymake-shellcheck + :when (executable-find "shellcheck") + :hook sh-mode) + +(when (executable-find "shfmt") + (reformatter-define sh-format + :program "shfmt" + :lighter "Shfmt") + (add-hook 'sh-mode-hook #'sh-format-on-save-mode)) + +(bind-key "M-/" #'hippie-expand) + +;; Tabs +(setq-default + tab-bar-show 1 ; show the tab bar when more than one + tab-bar-new-tab-choice "*scratch*" ; what to show on a new tab + tab-bar-tab-name-function ; how to name a new tab + #'tab-bar-tab-name-current-with-count + tab-bar-history-limit 25 ; how many tabs to save in history + ) + +(tab-bar-history-mode +1) + +;; Smart hungry delete +(use-package smart-hungry-delete + :defer nil + :bind (("" . smart-hungry-delete-backward-char) + ("C-d" . smart-hungry-delete-forward-char)) + :config (smart-hungry-delete-add-default-hooks)) + +;; Enable all commands +(setq-default disabled-command-function nil) + +;; Magit +(use-package magit + :bind ("C-z g" . magit-status)) + +;; crux +(use-package crux + :straight (:host github :repo "bbatsov/crux") + :bind + ("M-o" . crux-other-window-or-switch-buffer) + :config + (crux-with-region-or-line kill-ring-save) + (crux-with-region-or-line kill-region) + (crux-with-region-or-line comment-or-uncomment-region)) + +;; Completion and... stuff +(setq-default + completion-ignore-case t + read-buffer-completion-ignore-case t + read-file-name-completion-ignore-case t) + +(use-package icomplete-vertical + :demand + :init + (setq-default + icomplete-delay-completions-threshold 0 + icomplete-max-delay-chars 0 + icomplete-compute-delay 0 + icomplete-show-matches-on-no-input t + icomplete-hide-common-prefix nil + icomplete-with-completion-tables t + icomplete-in-buffer t) + :bind (:map icomplete-minibuffer-map + ("" . icomplete-forward-completions) + ("C-n" . icomplete-forward-completions) + ("" . icomplete-backward-completions) + ("C-p" . icomplete-backward-completions) + ("C-v" . icomplete-vertical-toggle)) + :config + (fido-mode -1) + (icomplete-mode +1) + (icomplete-vertical-mode +1)) + +(use-package orderless + :after icomplete + :init (setq-default completion-styles '(orderless))) + +(use-package marginalia + :after icomplete + :init (setq-default marginalia-annotators + '(marginalia-annotators-heavy + marginalia-annotators-light)) + :config (marginalia-mode +1)) + +(use-package consult + :after icomplete + :bind (;; C-c bindings (mode-specific-map) + ("C-c h" . consult-history) + ("C-c m" . consult-mode-command) + ("C-c b" . consult-bookmark) + ("C-c k" . consult-kmacro) + ;; C-x bindings (ctl-x-map) + ("C-x M-:" . consult-complex-command) ; orig. repeat-complet-command + ("C-x b" . consult-buffer) ; orig. switch-to-buffer + ("C-x 4 b" . consult-buffer-other-window) ; orig. switch-to-buffer-other-window + ("C-x 5 b" . consult-buffer-other-frame) ; orig. switch-to-buffer-other-frame + ;; Custom M-# bindings for fast register access + ("M-#" . consult-register-load) + ("M-'" . consult-register-store) ; orig. abbrev-prefix-mark (unrelated) + ("C-M-#" . consult-register) + ;; Other custom bindings + ("M-y" . consult-yank-pop) ; orig. yank-pop + (" a" . consult-apropos) ; orig. apropos-command + ;; M-g bindings (goto-map) + ("M-g e" . consult-compile-error) + ("M-g g" . consult-goto-line) ; orig. goto-line + ("M-g M-g" . consult-goto-line) ; orig. goto-line + ("M-g o" . consult-outline) + ("M-g m" . consult-mark) + ("M-g k" . consult-global-mark) + ("M-g i" . consult-imenu) + ("M-g I" . consult-project-imenu) + ;; M-s bindings (search-map) + ("M-s f" . consult-find) + ("M-s L" . consult-locate) + ("M-s g" . consult-grep) + ("M-s G" . consult-git-grep) + ("M-s r" . consult-ripgrep) + ("M-s l" . consult-line) + ("M-s m" . consult-multi-occur) + ("M-s k" . consult-keep-lines) + ("M-s u" . consult-focus-lines) + ;; Isearch integration + ("M-s e" . consult-isearch) + :map isearch-mode-map + ("M-e" . consult-isearch) ; orig. isearch-edit-string + ("M-s e" . consult-isearch) ; orig. isearch-edit-string + ("M-s l" . consult-line)) ; required by consult-line to detect isearch + :init + (setq register-preview-delay 0 + register-preview-function #'consult-register-format) + (advice-add #'register-preview :override #'consult-register-window) + (setq xref-show-xrefs-function #'consult-xref + xref-show-definitions-function #'consult-xref) + :config + ;; (setq consult-preview-key 'any) + ;; (setq consult-preview-key (kbd "M-p")) + (setq consult-narrow-key "<")) diff --git a/var/elpher-bookmarks.el b/var/elpher-bookmarks.el deleted file mode 100644 index 311d980..0000000 --- a/var/elpher-bookmarks.el +++ /dev/null @@ -1,18 +0,0 @@ -; Elpher bookmarks file - -; Bookmarks are stored as a list of (label URL) items. -; Feel free to edit by hand, but take care to ensure -; the list structure remains intact. - -(("MEDUSAE directory" "gemini://medusae.space/") - ("Will Lewis" "gemini://wflewis.com/") - ("Cornbread Recipe" "gemini://perplexing.space/2021/cornbread-recipe.gmi") - ("SMOG" "gemini://gemini.trans-neptunian.space/~smog/") - ("Caolan - Vermont Sourdough" "gemini://caolan.uk/baking/2020-11-26_vermont_sourdough.gmi") - ("BREADPUNK!" "gemini://breadpunk.club/") - ("ACDW" "gemini://gem.acdw.net/") - ("Spacewalk" "gemini://rawtext.club/~sloum/spacewalk.gmi") - ("CAPCOM" "gemini://gemini.circumlunar.space/capcom/") - ("cosmic voyage" "gemini://cosmic.voyage/") - ("kayw" "gemini://salejandro.me/") - ("low-key: weechat relay" "gemini://low-key.me/guides/weechat_irc_relay.gmi"))