diff --git a/config.org b/config.org index cf33168..2a9c97b 100644 --- a/config.org +++ b/config.org @@ -1,2443 +1,525 @@ -#+TITLE: Emacs, emacs, emacs +#+TITLE: Emacs configuration, literate-style #+AUTHOR: Case Duckworth -#+PROPERTY: header-args :tangle config.el :tangle-mode (identity #o444) :comments both :mkdirp yes -#+STARTUP: overview -#+EXPORT_FILE_NAME: README.md -#+OPTIONS: toc:nil -#+BANKRUPTCY_COUNT: 3.2 -#+Time-stamp: <2021-01-18 23:33:24 acdw> +#+PROPERTY: header-args :tangle yes :tangle-mode (identity #o444) :comments both :mkdirp yes -* Basics +* Settings -** Disclaimer +Basic settings necessary for a decent editing experience in Emacs. +These should not require non-built-in packages. - #+begin_src emacs-lisp :comments no - ;; config.el -*- lexical-binding: t -*- - ;; This file is automatically tangled from config.org. - ;; Hand edits will be overwritten! - #+end_src +** Prelude + +*** Enable lexical binding + +#+begin_src emacs-lisp :comments no + ;; config.el -*- lexical-binding: t -*- +#+end_src + +*** Disclaimer + +#+NAME: disclaimer +#+begin_src emacs-lisp :comments no + ;; This file is automatically tangled from config.org. + ;; Hand edits will be overwritten! +#+end_src + +** Customization + +*** Emulate use-package's =:custom= + +#+begin_src emacs-lisp + (defmacro cuss (var val &optional _docstring) + "`use-package''s `:custom', without `use-package'." + (declare (doc-string 3) + (indent 2)) + `(funcall (or (get ',var 'custom-set) #'set-default) + ',var ,val)) +#+end_src + +*** Emulate use-package's =:custom-face= + +#+begin_src emacs-lisp + (defvar acdw--custom-faces () + "List of custom faces to run through `acdw/set-custom-faces'.") + + (defun acdw/set-custom-faces () + "Customize faces using `customize-set-faces'. + + I only want to run this once, per the documentation of `customize-set-faces'." + (when acdw--custom-faces + (let ((msg "Customizing faces")) + (message "%s..." msg) + (apply #'custom-set-faces acdw--custom-faces) + (message "%s...Done." msg) + (remove-function after-focuse-change-function #'acdw/set-custom-faces)))) + + (add-function :before after-focus-change-function #'acdw/set-custom-faces) + + (defmacro cussface (face spec &optional _docstring) + "Add the form (FACE SPEC) to `acdw--custom-faces', and add a + hook to run `acdw/set-custom-faces' after init." + (declare (doc-string 3) + (indent 2)) + `(add-to-list acdw--custom-faces '(,face ,spec))) +#+end_src + +*** Only do something when Emacs is unfocused + +Since Emacs is single-threaded, I only want to run really expensive +operations when I won't notice, say .. when I'm focused on another +window. + +#+begin_src emacs-lisp + (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 + +*** Throw customizations away + +I use Emacs's Customize interface, but really only to learn about what +options a package presents to /be/ customized. I don't want to use +the custom file for anything at all. + +#+begin_src emacs-lisp + (cuss custom-file null-device) +#+end_src ** About me - #+begin_src emacs-lisp - (setq user-full-name "Case Duckworth" - user-mail-address "acdw@acdw.net") - #+end_src +My name and email address. -** Correct =exec-path= - - Straight depends on Git, so I need to tell Emacs where different paths are. - - #+begin_src emacs-lisp - (let ((win-downloads "c:/Users/aduckworth/Downloads")) - (dolist (path (list - ;; Linux - (expand-file-name "bin" - user-emacs-directory) - (expand-file-name "~/bin") - (expand-file-name "~/.local/bin") - (expand-file-name "~/Scripts") - ;; Windows - (expand-file-name "emacs/bin" - win-downloads) - (expand-file-name "m/usr/bin" - win-downloads) - (expand-file-name "m/mingw64/bin" - win-downloads) - (expand-file-name "PortableGit/bin" - win-downloads) - (expand-file-name "PortableGit/usr/bin" - win-downloads))) - (when (file-exists-p path) - (add-to-list 'exec-path path)))) - #+end_src - -** Package management - -*** Straight.el - - Straight can't bootstrap itself on Windows, so I've wrapped the - bootstrap code from straight's repo in a function. - - #+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 - - Now, I'll /try/ running it regular-style, ignoring the errors. If it - doesn't work, I'll call git directly and clone the repo myself. - - #+begin_src emacs-lisp - (unless (ignore-errors (acdw/bootstrap-straight)) - (message "%s" "Straight.el didn't bootstrap correctly. Cloning directly...") - (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)) - (acdw/bootstrap-straight)) - #+end_src - -** Ease-of-configuring functions - -*** Emulate use-package’s =:custom= - - #+begin_src emacs-lisp - (defmacro cuss (var val &optional _docstring) - "Basically, `:custom' from `use-package', but without `use-package'." - (declare (doc-string 3) - (indent 2)) - `(funcall (or (get ',var 'custom-set) #'set-default) - ',var ,val)) - #+end_src - -*** Emulate use-package’s =:custom-face=, but better - - #+begin_src emacs-lisp - (defvar acdw--custom-faces () - "List of custom faces to run through acdw/set-custom-faces.") - - (defun acdw/set-custom-faces () - "Run `customize-set-faces' on `acdw--custom-faces'." - (message "%s" "Customizing faces...") - (apply #'custom-set-faces acdw--custom-faces)) - - (defun cussface (spec) - "Add SPEC to `acdw--custom-faces', and add a hook to run - `acdw/set-custom-faces' after init." - (add-to-list 'acdw--custom-faces spec) - (add-hook 'after-init-hook #'acdw/set-custom-faces)) - #+end_src - -*** Determine whether any Emacs frame is focused or not - - This comes in handy when I want to garbage collect, say, or save recent files. - - #+begin_src emacs-lisp - (defun acdw/when-unfocused (func &rest args) - "Run FUNC with ARGS only if all frames are out of focus." - (if (seq-every-p #'null (mapcar #'frame-focus-state (frame-list))) - (apply func args))) - #+end_src - -*** Determine where I am - - I use Emacs at home, with Linux, and at work, with Windows. - - #+begin_src emacs-lisp - (defmacro when-at (conditions &rest commands) - "Only do COMMANDS when CONDITIONS are met. - CONDITIONS are one of `:work', `:home', or a list beginning with - the above and other conditions to check." - (declare (indent 1)) - (let ((at-work (memq system-type '(ms-dos windows-nt))) - (at-home (memq system-type '(gnu gnu/linux gnu/kfreebsd)))) - (pcase conditions - (:work `(when ',at-work ,@commands)) - (:home `(when ',at-home ,@commands)) - (`(:work ,others) `(when (and ',at-work ,others) - ,@commands)) - (`(:home ,others) `(when (and ',at-home ,others) - ,@commands))))) - #+end_src - -** Clean =.emacs.d= - - #+begin_src emacs-lisp - (straight-use-package 'no-littering) - (require 'no-littering) - #+end_src - -*** Don’t clutter =init.el= with customizations - - #+begin_src emacs-lisp - (with-eval-after-load 'no-littering - (cuss custom-file (no-littering-expand-etc-file-name "custom.el"))) - #+end_src +#+begin_src emacs-lisp + (setq user-full-name "Case Duckworth" + user-mail-address "acdw@acdw.net") +#+end_src ** Look and feel *** Cursor - #+begin_src emacs-lisp - (cuss cursor-type 'bar - "Show a vertical bar for the cursor.") +#+begin_src emacs-lisp + ;; Show a vertical bar cursor + (cuss cursor-type 'bar) - (cuss cursor-in-non-selected-windows nil - "Don't show a cursor in non-selected windows.") + ;; Hide the cursor in other windows + (cuss cursor-in-non-selected-windows nil) - ;; Don't blink the cursor - (blink-cursor-mode -1) - #+end_src + ;; Don't blink the cursor + (blink-cursor-mode -1) +#+end_src -*** Tool Bars +*** Dialogs and alerts -**** Tool bars and menu bars +**** Don't use a dialog box - #+begin_src emacs-lisp - (menu-bar-mode -1) - (tool-bar-mode -1) - #+end_src - -**** Scroll bars - - #+begin_src emacs-lisp - (scroll-bar-mode -1) - (horizontal-scroll-bar-mode -1) - #+end_src - -*** Dialogs - - #+begin_src emacs-lisp - (cuss use-dialog-box nil - "Don't use dialog boxes to ask questions.") - #+end_src +#+begin_src emacs-lisp + (cuss use-dialog-box nil) +#+end_src **** Yes or no questions - #+begin_src emacs-lisp - (fset 'yes-or-no-p #'y-or-n-p) - #+end_src +#+begin_src emacs-lisp + (fset 'yes-or-no-p #'y-or-n-p) +#+end_src **** The Bell - from [[https://www.emacswiki.org/emacs/AlarmBell#h5o-3][EmacsWiki]]. +#+begin_src emacs-lisp + ;; Don't flash the whole screen on bell + (cuss visible-bell nil) - #+begin_src emacs-lisp - (setq visible-bell nil - ring-bell-function 'flash-mode-line) + ;; Instead, flash the mode line + (cuss ring-bell-function #'flash-mode-line) - (defun flash-mode-line () - (invert-face 'mode-line) - (run-with-timer 0.1 nil #'invert-face 'mode-line)) - #+end_src + (defun flash-mode-line () + (invert-face 'mode-line) + (run-with-timer 0.2 nil #'invert-face 'mode-line)) +#+end_src + +t*** Minibuffer + +**** Keep the cursor away from the minibuffer prompt + +#+begin_src emacs-lisp + (cuss minibuffer-prompt-properties + '(read-only t cursor-intangible t face minibuffer-prompt)) +#+end_src + +*** Tabs + +**** Tab names should be current buffer + a count of windows + +#+begin_src emacs-lisp + (cuss tab-bar-tab-name-function + #'tab-bar-tab-name-current-with-count) +#+end_src + +**** Only show the tab bar when there's more than one tab + +For some reason, this doesn't work with multiple frames. + +#+begin_src emacs-lisp + (cuss tab-bar-show 1) +#+end_src *** Frames +/Frames/ are Emacs's concepts that generally correspond to other +programs' /windows/ -- that is, they're the boxen on the screen that +contain the Emacs programmen. + +**** Initial frame setup +:PROPERTIES: +:header-args: :noweb-ref initial-frame-setup +:END: + +***** 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 + **** Frame titles - #+begin_src emacs-lisp - (cuss frame-title-format (concat invocation-name "@" system-name - ": %b %+%+ %f")) - #+end_src +Set the frame title to something more useful than the default: include +the current buffer and the current filename. + +#+begin_src emacs-lisp + (cuss frame-title-format + (concat invocation-name "@" (system-name) + ": %b %+%+ %f")) +#+end_src **** Fringes - #+begin_src emacs-lisp - (cuss indicate-empty-lines t - "Show an indicator on the left fringe of empty lines past the - end of the buffer.") - (cuss indicate-buffer-boundaries 'right - "Indicate the beginning and end of the buffer and whether it - scrolls off-window in the right fringe.") +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. - (cuss visual-line-fringe-indicators '(left-curly-arrow nil) - "Indicate continuing lines with a curly arrow in the left fringe.") +***** Indicate empty lines after the end of the buffer - ;; redefine the `left-curly-arrow' indicator to be less distracting. - (define-fringe-bitmap 'left-curly-arrow - [#b11000000 - #b01100000 - #b00110000 - #b00011000]) - #+end_src +#+begin_src emacs-lisp + (cuss indicate-empty-lines t) +#+end_src -**** Minibuffer +***** Indicate the boundaries of the buffer - #+begin_src emacs-lisp - (cuss minibuffer-prompt-properties - '(read-only t cursor-intangible t face minibuffer-prompt) - "Keep the cursor away from the minibuffer prompt.") - #+end_src +#+begin_src emacs-lisp + (cuss indicate-buffer-boundaries 'right) +#+end_src -**** Tabs +***** Indicate continuation lines, but only on the left fringe - #+begin_src emacs-lisp - (cuss tab-bar-tab-name-function - #'tab-bar-tab-name-current-with-count - "Show the tab name as the name of the current buffer, plus a - count of the windows in the tab.") +#+begin_src emacs-lisp + (cuss visual-line-fringe-indicators '(left-curly-arrow nil)) - (cuss tab-bar-show 1 - "Show the tab bar, when there's more than one tab.") - #+end_src + ;; And make the `left-curly-arrow' indicator less distracting. + + (define-fringe-bitmap 'left-curly-arrow + [#b11000000 + #b01100000 + #b00110000 + #b00011000]) +#+end_src *** Windows -**** Winner mode - - #+begin_src emacs-lisp - (when (fboundp 'winner-mode) - (winner-mode +1)) - #+end_src - -**** Switch windows or buffers if one window - - from [[https://www.reddit.com/r/emacs/comments/kz347f/what_parts_of_your_config_do_you_like_best/gjlnp2c/][u/astoff1]]. - - #+begin_src emacs-lisp - (defun acdw/other-window-or-buffer () - "Switch to other window, or previous buffer." - (interactive) - (if (eq (count-windows) 1) - (switch-to-buffer nil) - (other-window 1))) - - (global-set-key (kbd "M-o") #'acdw/other-window-or-buffer) - #+end_src - -**** Pop-up windows - - #+begin_src emacs-lisp - (straight-use-package 'popwin) - (popwin-mode +1) - #+end_src - *** Buffers -**** Uniquify buffers - - #+begin_src emacs-lisp - (require 'uniquify) - (cuss uniquify-buffer-name-style 'forward - "Uniquify buffers' names by going up the path trees until they - become unique.") - #+end_src - -**** Startup buffers - - #+begin_src emacs-lisp - (cuss inhibit-startup-screen t - "Don't show Emacs' startup buffer.") - - (cuss initial-buffer-choice t - "Start with *scratch*.") - - (cuss initial-scratch-message "" - "Empty *scratch* buffer.") - #+end_src - -**** COMMENT Focus and move buffers directionally - - Commented for now because I really need to figure out the keybindings I want to use for this. The real issue is the collisions between Org-mode, Windows, and Emacs’ normal bindings. - - #+begin_src emacs-lisp - ;; change focus - ;; for more on `ignore-error-wrapper', see - ;; https://www.emacswiki.org/emacs/WindMove#h5o-3 - (defun ignore-error-wrapper (fn) - "Funtion return new function that ignore errors. - The function wraps a function with `ignore-errors' macro." - (lexical-let ((fn fn)) - (lambda () - (interactive) - (ignore-errors - (funcall fn))))) - - (global-set-key [S-left] (ignore-error-wrapper 'windmove-left)) - (global-set-key [S-right] (ignore-error-wrapper 'windmove-right)) - (global-set-key [S-up] (ignore-error-wrapper 'windmove-up)) - (global-set-key [S-down] (ignore-error-wrapper 'windmove-down)) - - ;; shift buffers - (straight-use-package 'buffer-move) - - (global-set-key (kbd "") 'buf-move-up) - (global-set-key (kbd "") 'buf-move-down) - (global-set-key (kbd "") 'buf-move-left) - (global-set-key (kbd "") 'buf-move-right) - #+end_src - -**** Kill the current buffer - - #+begin_src emacs-lisp - (defun acdw/kill-a-buffer (&optional prefix) - "Kill a buffer based on the following rules: - - C-x k ⇒ Kill current buffer & window - C-u C-x k ⇒ Kill OTHER window and its buffer - C-u C-u C-x C-k ⇒ Kill all other buffers and windows - - Prompt only if there are unsaved changes." - (interactive "P") - (pcase (or (car prefix) 0) - ;; C-x k ⇒ Kill current buffer & window - (0 (kill-current-buffer) - (unless (one-window-p) (delete-window))) - ;; C-u C-x k ⇒ Kill OTHER window and its buffer - (4 (other-window 1) - (kill-current-buffer) - (unless (one-window-p) (delete-window))) - ;; C-u C-u C-x C-k ⇒ Kill all other buffers and windows - (16 (mapc 'kill-buffer (delq (current-buffer) (buffer-list))) - (delete-other-windows)))) - - (define-key ctl-x-map "k" #'acdw/kill-a-buffer) - #+end_src - -***** Remap =C-x M-k= to bring up the buffer-killing menu - - #+begin_src emacs-lisp - (define-key ctl-x-map (kbd "M-k") #'kill-buffer) - #+end_src - -**** Immortal =*scratch*= buffer - - #+begin_src emacs-lisp - (defun immortal-scratch () - (if (eq (current-buffer) (get-buffer "*scratch*")) - (progn (bury-buffer) - nil) - t)) - - (add-hook 'kill-buffer-query-functions 'immortal-scratch) - #+end_src - -*** Modeline - -**** Smart mode line - - #+begin_src emacs-lisp - (straight-use-package 'smart-mode-line) - - (cuss sml/no-confirm-load-theme t - "Pass the NO-CONFIRM flag to `load-theme'.") - - (sml/setup) - #+end_src - -**** Rich minority - - Since this /comes/ with smart mode line, I’m just going to use it, - instead of =diminish= or another package. I do have to write this - helper function, though, to add things to the whitelist. - - #+begin_src emacs-lisp - (defun rm/whitelist-add (regexp) - "Add a REGEXP to the whitelist for `rich-minority'." - (if (listp 'rm--whitelist-regexps) - (add-to-list 'rm--whitelist-regexps regexp) - (setq rm--whitelist-regexps `(,regexp))) - (setq rm-whitelist - (mapconcat 'identity rm--whitelist-regexps "\\|"))) - - (straight-use-package 'rich-minority) - - (rm/whitelist-add "^$") - #+end_src - -*** Theme - -**** Modus Themes - - #+begin_src emacs-lisp - (straight-use-package 'modus-themes) - - (cuss modus-themes-slanted-constructs t - "Use more slanted constructs.") - (cuss modus-themes-bold-constructs t - "Use more bold constructs.") - - (cuss modus-themes-region 'bg-only - "Only highlight the background of the selected region.") - - (cuss modus-themes-org-blocks 'grayscale - "Show org-blocks with a grayscale background.") - (cuss modus-themes-headings - '((1 . line) - (t . t)) - "Highlight top headings with `line' style, and others by default.") - - (cuss modus-themes-scale-headings t - "Scale headings by the ratios below.") - (cuss modus-themes-scale-1 1.1) - (cuss modus-themes-scale-2 1.15) - (cuss modus-themes-scale-3 1.21) - (cuss modus-themes-scale-4 1.27) - (cuss modus-themes-scale-5 1.33) - - (load-theme 'modus-operandi t) - #+end_src - -**** Change themes based on time of day - - #+begin_src emacs-lisp - (cuss calendar-latitude 30.4515) - (cuss calendar-longitude -91.1871) - - (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))) - - (acdw/run-with-sun #'modus-themes-load-operandi #'modus-themes-load-vivendi) - #+end_src - -*** Fonts - -**** Define fonts - - #+begin_src emacs-lisp - (defun set-face-from-alternatives (face frame &rest fontspecs) - "Set FACE on FRAME from first available spec from FONTSPECS. - FACE and FRAME work the same as with `set-face-attribute.'" - (catch :return - (dolist (spec fontspecs) - (when-let ((found (find-font (apply #'font-spec spec)))) - (set-face-attribute face frame :font found) - (throw :return found))))) - - (defun acdw/setup-fonts () - "Setup fonts. This has to happen after the frame is setup for - the first time, so it should be added to `window-setup-hook'. It - removes itself from that hook." - (interactive) - (when (display-graphic-p) - (dolist (face '(default fixed-pitch)) - ;; fixed-pitch /is/ the default - (set-face-from-alternatives face nil - '(:family "Input Mono" - :slant normal - :weight normal - :height 110) - '(:family "Go Mono" - :slant normal - :weight normal - :height 100) - '(:family "Consolas" - :slant normal - :weight normal - :height 100))) - ;; variable-pitch is different - (set-face-from-alternatives 'variable-pitch nil - '(:family "Input Sans" - :slant normal - :weight normal) - '(:family "Georgia" - :slant normal - :weight normal))) - - ;; remove myself from the hook - (remove-function after-focus-change-function #'acdw/setup-fonts)) - - (add-function :before after-focus-change-function #'acdw/setup-fonts) - #+end_src - -**** Custom faces - - #+begin_src emacs-lisp - (cussface '(font-lock-comment-face - ((t (:inherit (custom-comment italic variable-pitch)))))) - #+end_src - -**** Line spacing - - #+begin_src emacs-lisp - (cuss line-spacing 0.1 - "Add 10% extra space below each line.") - #+end_src - -**** Underlines - - #+begin_src emacs-lisp - (cuss x-underline-at-descent-line t - "Draw the underline at the same place as the descent line.") - #+end_src - -**** Unicode Fonts - - #+begin_src emacs-lisp - (straight-use-package 'unicode-fonts) - (require 'unicode-fonts) - (unicode-fonts-setup) - #+end_src - -** Interactivity - -*** Completing read - -**** Shadow file names in =completing-read=. - - #+begin_src emacs-lisp - (cuss file-name-shadow-properties '(invisible t)) - - (file-name-shadow-mode +1) - #+end_src - -**** Ignore case in =completing-read= - - #+begin_src emacs-lisp - (cuss completion-ignore-case t) - (cuss read-buffer-completion-ignore-case t) - (cuss read-file-name-completion-ignore-case t) - #+end_src - -**** Minibuffer recursivity - - #+begin_src emacs-lisp - (cuss enable-recursive-minibuffers t) - (minibuffer-depth-indicate-mode +1) - #+end_src - -**** Selectrum - - #+begin_src emacs-lisp - (straight-use-package 'selectrum) - (require 'selectrum) - (selectrum-mode +1) - #+end_src - -**** Prescient - - #+begin_src emacs-lisp - (straight-use-package 'prescient) - (require 'prescient) - (prescient-persist-mode +1) - - (straight-use-package 'selectrum-prescient) - (require 'selectrum-prescient) - (selectrum-prescient-mode +1) - #+end_src - -**** Consult - - #+begin_src emacs-lisp - (straight-use-package '(consult - :host github - :repo "minad/consult" - :files (:defaults "consult-pkg.el"))) - (require 'consult) - - (with-eval-after-load 'consult - (define-key ctl-x-map "b" #'consult-buffer) - (define-key ctl-x-map (kbd "C-r") #'consult-buffer) - (define-key ctl-x-map "4b" #'consult-buffer-other-window) - (define-key ctl-x-map "5b" #'consult-buffer-other-frame) - - (define-key goto-map "o" #'consult-outline) - (define-key goto-map "g" #'consult-line) - (define-key goto-map (kbd "M-g") #'consult-line) - (define-key goto-map "l" #'consult-line) - (define-key goto-map "m" #'consult-mark) - (define-key goto-map "i" #'consult-imenu) - (define-key goto-map "e" #'consult-error) - - (global-set-key (kbd "M-y") #'consult-yank-pop) - - (define-key help-map "a" #'consult-apropos) - - (fset 'multi-occur #'consult-multi-occur)) - #+end_src - -**** Marginalia - - #+begin_src emacs-lisp - (straight-use-package '(marginalia - :host github - :repo "minad/marginalia" - :branch "main")) - - (cuss marginalia-annotators - '(marginalia-annotators-heavy - marginalia-annotators-light)) - - (marginalia-mode +1) - #+end_src - -**** COMMENT Ido - - [[https://wandersoncferreira.github.io/blog/ido/][Let’s try this out]]. - - #+begin_src emacs-lisp - (defun ido-choose-from-recentf () - "Use ido to select recently visited files." - (interactive) - (find-file (ido-completing-read "Open file: " recentf-list nil t))) - - (defun bk/go-straight-home () - (interactive) - (cond - ((looking-back "~/") (insert "projects/")) - ((looking-back "/") (insert "~/")) - (:else (call-interactively 'self-insert-command)))) - - (defun ido-disable-line-truncation () - (set (make-local-variable 'truncate-lines) nil)) - - (defun ido-define-keys () - (define-key ido-completion-map (kbd "C-n") 'ido-next-match) - (define-key ido-completion-map (kbd "C-p") 'ido-prev-match)) - - (setq ido-enable-flex-matching t - ido-use-filename-at-point nil - ido-create-new-buffer 'always - confirm-nonexistent-file-or-buffer nil - completion-ignored-extensions (cons "*.aux" completion-ignored-extensions) - max-mini-window-height 0.5 - ido-enable-tramp-completion t - ido-auto-merge-work-directories-length -1 - ido-confirm-unique-completion t - ido-default-file-method 'selected-window - ido-case-fold t - ido-show-dot-for-dired t - ido-everywhere t - ido-ignore-buffers (list (rx (or (and bos " ") - (and bos - (or "*Completions*" - "*Compile-Log*" - "*Ido Completions*" - "*Shell Command Output*" - "*vc-diff*") - eos)))) - ido-decorations (quote ("\n-> " "" "\n " "\n ..." "[" "]" " - [No match]" " [Matched]" " [Not readable]" " [Too big]" " - [Confirm]"))) - - (with-eval-after-load 'ido - (define-key ido-common-completion-map (kbd "M-SPC") 'just-one-space) - (define-key ido-common-completion-map (kbd "SPC") 'self-insert-command) - (define-key ido-file-completion-map (kbd "~") 'bk/go-straight-home) - - (add-hook 'ido-setup-hook 'ido-define-keys) - - (add-hook 'ido-minibuffer-setup-hook 'ido-disable-line-truncation) - - (set-default 'imenu-auto-rescan t) - - (add-to-list 'ido-ignore-directories "target") - (add-to-list 'ido-ignore-directories "node_modules") - ) - - (defun setup-ido-mode () - (require 'ido) - (ido-mode +1) - (ido-everywhere +1)) - - (add-hook 'after-init-hook #'setup-ido-mode) - #+end_src - -*** Completion - - #+begin_src emacs-lisp - (global-set-key (kbd "M-/") #'hippie-expand) - #+end_src - -*** Garbage collection - - #+begin_src emacs-lisp - (straight-use-package 'gcmh) - (gcmh-mode +1) - - (defun dotfiles--gc-on-last-frame-out-of-focus () - "GC if all frames are inactive." - (if (seq-every-p #'null (mapcar #'frame-focus-state (frame-list))) - (garbage-collect))) - - (add-function :after after-focus-change-function - #'dotfiles--gc-on-last-frame-out-of-focus) - #+end_src - -** Keyboard - -*** =ESC= cancels all - - #+begin_src emacs-lisp - (global-set-key (kbd "") #'keyboard-escape-quit) - #+end_src - -*** Personal prefix key: =C-z= - - #+begin_src emacs-lisp - (defvar acdw/map - (let ((map (make-sparse-keymap)) - (c-z (global-key-binding "\C-z"))) - (global-unset-key "\C-z") - (define-key global-map "\C-z" map) - (define-key map "\C-z" c-z) - map)) - - (run-hooks 'acdw/map-defined-hook) - #+end_src - -*** Show keybindings - - #+begin_src emacs-lisp - (straight-use-package 'which-key) - (which-key-mode +1) - #+end_src - -** Mouse - -*** Preserve screen position when scrolling with the mouse wheel - - from [[https://www.reddit.com/r/emacs/comments/km9by4/weekly_tipstricketc_thread/ghg2c9d/][u/TheFrenchPoulp]]. - - #+begin_src emacs-lisp - (advice-add 'mwheel-scroll :around #'me/mwheel-scroll) - - (defun me/mwheel-scroll (original &rest arguments) - "Like `mwheel-scroll' but preserve screen position. - See `scroll-preserve-screen-position'." - (let ((scroll-preserve-screen-position :always)) - (apply original arguments))) - #+end_src - -*** Scroll much faster - - from [[https://github.com/mpereira/.emacs.d#make-cursor-movement-an-order-of-magnitude-faster][mpereira]], from somewhere else. - - #+begin_src emacs-lisp - (cuss auto-window-vscroll nil - "Don't auto-adjust `window-vscroll' to view long lines.") - - (cuss fast-but-imprecise-scrolling t - "Scroll fast, but possibly with inaccurate text rendering.") - - (cuss jit-lock-defer-time 0 - "Only defer font-locking when input is pending.") - #+end_src - -** Persistence - -*** Minibuffer history - - #+begin_src emacs-lisp - (require 'savehist) - - (cuss savehist-additional-variables - '(kill-ring - search-ring - regexp-search-ring) - "Other variables to save alongside the minibuffer history.") - - (cuss history-length t - "Don't truncate history.") - - (cuss history-delete-duplicates t - "Delete history duplicates.") - - (savehist-mode +1) - #+end_src - -*** File places - - #+begin_src emacs-lisp - (require 'saveplace) ; this isn't required, but ... I like having it here - - (cuss save-place-forget-unreadable-files t - "Don't check if files are readable or not.") - - (save-place-mode +1) - #+end_src - -*** Recent files - - #+begin_src emacs-lisp - (require 'recentf) - - (cuss recentf-max-menu-items 100 - "The maximum number of items in the recentf menu.") - (cuss recentf-max-saved-items nil - "Don't limit the number of recent files.") - - (with-eval-after-load 'no-littering - (add-to-list 'recentf-exclude no-littering-var-directory) - (add-to-list 'recentf-exclude no-littering-etc-directory)) - - (recentf-mode +1) - - ;; save recentf list when focusing away - (defun acdw/maybe-save-recentf () - "Save `recentf-file' when out of focus, but only if we haven't - in five minutes." - (defvar recentf-last-save (time-convert nil 'integer) - "How long it's been since we last saved the recentf list.") - - (when (> (time-convert (time-since recentf-last-save) 'integer) - (* 60 5)) - (setq recentf-last-save (time-convert nil 'integer)) - (acdw/when-unfocused #'recentf-save-list))) - - (add-function :after after-focus-change-function - #'acdw/maybe-save-recentf) - #+end_src - -** Undo - - #+begin_src emacs-lisp - (straight-use-package 'undo-fu) - (require 'undo-fu) - - (global-set-key (kbd "C-/") #'undo-fu-only-undo) - (global-set-key (kbd "C-?") #'undo-fu-only-redo) - - (straight-use-package 'undo-fu-session) - (require 'undo-fu-session) - - (cuss undo-fu-session-incompatible-files - '("/COMMIT_EDITMSG\\'" - "/git-rebase-todo\\'") - "A list of files that are incompatible with the concept of undo sessions.") - - (with-eval-after-load 'no-littering - (let ((dir (no-littering-expand-var-file-name "undos"))) - (make-directory dir 'parents) - (cuss undo-fu-session-directory dir))) - - (global-undo-fu-session-mode +1) - #+end_src - -** Files - -*** Encoding - -**** UTF-8 - - from [[https://www.masteringemacs.org/article/working-coding-systems-unicode-emacs][Mastering Emacs]]. - - #+begin_src emacs-lisp - (prefer-coding-system 'utf-8) - (set-default-coding-systems 'utf-8) - (set-terminal-coding-system 'utf-8) - (set-keyboard-coding-system 'utf-8) - ;; backwards compatibility: - ;; `default-buffer-file-coding-system' is deprecated in 23.2. - (setq default-buffer-file-coding-system 'utf-8) - - ;; Treat clipboard as UTF-8 string first; compound text next, etc. - (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) - #+end_src - -**** Convert all files to UNIX-style line endings - - from [[https://www.emacswiki.org/emacs/EndOfLineTips][Emacs Wiki]]. - - #+begin_src emacs-lisp - (defun ewiki/no-junk-please-were-unixish () - "Convert line endings to UNIX, dammit." - (let ((coding-str (symbol-name buffer-file-coding-system))) - (when (string-match "-\\(?:dos\\|mac\\)$" coding-str) - (set-buffer-file-coding-system 'unix)))) - #+end_src - - I add it to the ~find-file-hook~ /and/ ~before-save-hook~ because I - don't want to ever work with anything other than UNIX line endings - ever again. I just don't care. Even Microsoft Notepad can handle - UNIX line endings, so I don't want to hear it. - - #+begin_src emacs-lisp - (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish) - (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish) - #+end_src - -*** Backups - - #+begin_src emacs-lisp - (cuss backup-by-copying 1) - (cuss delete-old-versions -1) - (cuss version-control t) - (cuss vc-make-backup-files t) - - (with-eval-after-load 'no-littering - (let ((dir (no-littering-expand-var-file-name "backup"))) - (make-directory dir 'parents) - (cuss backup-directory-alist - `((".*" . ,dir))))) - #+end_src - -*** Auto-saves - - #+begin_src emacs-lisp - ;; turn off auto-save-mode until we can set up the right directory for them - (auto-save-mode -1) - - (with-eval-after-load 'no-littering - (let ((dir (no-littering-expand-var-file-name "autosaves"))) - (make-directory dir 'parents) - (cuss auto-save-file-name-transforms - `((".*" ,dir t)))) - - (auto-save-mode +1)) - #+end_src - -*** Super-save - - Because I like /overkill/, or at least … over-/saving/. - - #+begin_src emacs-lisp - (straight-use-package 'super-save) - - (cuss super-save-remote-files nil - "Don't super-save remote files.") - - (cuss super-save-exclude '(".gpg") - "Ignore these files when saving.") - - (super-save-mode +1) - #+end_src - -*** Auto-revert buffers to files on disk - - #+begin_src emacs-lisp - (global-auto-revert-mode +1) - #+end_src - -*** Add a timestamp to files - - #+begin_src emacs-lisp - (add-hook 'before-save-hook #'time-stamp) - #+end_src - -*** Require a final new line - - #+begin_src emacs-lisp - (cuss require-final-newline t) - #+end_src - -*** Edit files with =sudo= - - #+begin_src emacs-lisp - (straight-use-package 'sudo-edit) - - (with-eval-after-load 'sudo-edit - (define-key acdw/map (kbd "C-r") #'sudo-edit)) - #+end_src - -**** Don’t add =/sudo:= files to =recentf=, though - - I’ve pretty much cribbed this from [[https://github.com/ncaq/recentf-remove-sudo-tramp-prefix/][recentf-remove-sudo-tramp-prefix]] – it’s a small enough package that I can just include it completely here. - - #+begin_src emacs-lisp - ;; appease the compiler - (declare-function tramp-tramp-file-p "tramp") - (declare-function tramp-dissect-file-name "tramp") - (declare-function tramp-file-name-method "tramp") - (declare-function tramp-file-name-localname "tramp") - - (defun recentf-remove-sudo-tramp-prefix (path) - "Remove sudo from PATH." - (require 'tramp) - (if (tramp-tramp-file-p path) - (let ((tx (tramp-dissect-file-name path))) - (if (string-equal "sudo" (tramp-file-name-method tx)) - (tramp-file-name-localname tx) - path)) - path) - - (defun recentf-remove-sudo-tramp-prefix-from-recentf-list () - (require 'recentf) - (setq recentf-list - (mapcar #'recentf-remove-sudo-tramp-prefix recentf-list))) - - (advice-add 'recentf-cleanup - :before #'recentf-remove-sudo-tramp-prefix-from-recentf-list)) - #+end_src - -** Text editing - -*** Operate visually on lines - - #+begin_src emacs-lisp - (global-visual-line-mode +1) - #+end_src - -*** View long lines like filled lines in the beginning - - #+begin_src emacs-lisp - (straight-use-package 'adaptive-wrap) - - (when (fboundp 'adaptive-wrap-prefix-mode) - (defun acdw/activate-adaptive-wrap-prefix-mode () - "Toggle `visual-line-mode' and `adaptive-wrap-prefix-mode' simultaneously." - (adaptive-wrap-prefix-mode (if visual-line-mode - +1 - -1))) - (add-hook 'visual-line-mode-hook #'acdw/activate-adaptive-wrap-prefix-mode)) - #+end_src - -*** Stay snappy with long-lined files - - #+begin_src emacs-lisp - (when (fboundp 'global-so-long-mode) - (global-so-long-mode +1)) - #+end_src - -*** Killing & Yanking - -**** Replace selection when typing - - #+begin_src emacs-lisp - (delete-selection-mode +1) - #+end_src - -**** Work better with the system clipboard - - #+begin_src emacs-lisp - (cuss save-interprogram-paste-before-kill t - "Save existing clipboard text into the kill ring before - replacing it.") - - (cuss yank-pop-change-selection t - "Update the X selection when rotating the kill ring.") - - (cuss x-select-enable-clipboard t) - (cuss x-select-enable-primary t) - (cuss mouse-drag-copy-region t) - #+end_src - -**** Don’t append the same thing twice to the kill-ring - - #+begin_src emacs-lisp - (cuss kill-do-not-save-duplicates t) - #+end_src - -*** Searching & Replacing - -**** COMMENT Search with CtrlF - - For right now, I’m /just/ using Anzu – I don’t like parts of =isearch= but … CtrlF doesn’t match with that sweet replace flow. - - #+begin_src emacs-lisp - (straight-use-package 'ctrlf) - (ctrlf-mode +1) - #+end_src - -**** Replace with Anzu - - #+begin_src emacs-lisp - (straight-use-package 'anzu) - (require 'anzu) - - ;; show search count in the modeline - (global-anzu-mode +1) - - (cuss anzu-replace-to-string-separator " → " - "What to separate the search from the replacement.") - - (global-set-key [remap query-replace] #'anzu-query-replace) - (global-set-key [remap query-replace-regexp] #'anzu-query-replace-regexp) - - (define-key isearch-mode-map [remap isearch-query-replace] #'anzu-isearch-query-replace) - (define-key isearch-mode-map [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp) - - - #+end_src - -*** Overwrite mode - - #+begin_src emacs-lisp - (defun acdw/overwrite-mode-change-cursor () - (setq cursor-type (if overwrite-mode t 'bar))) - - (add-hook 'overwrite-mode-hook #'acdw/overwrite-mode-change-cursor) - - (rm/whitelist-add "Ovwrt") - #+end_src - -*** The Mark - - #+begin_src emacs-lisp - (cuss set-mark-repeat-command-pop t - "Repeat `set-mark-command' with a prefix argument, without - repeatedly entering the prefix argument.") - #+end_src - -*** Whitespace - - #+begin_src emacs-lisp - (cuss whitespace-style - '(empty ;; remove blank lines at the beginning and end of buffers - indentation ;; clean up indentation - space-before-tab ;; fix mixed spaces and tabs - space-after-tab)) - - (defun acdw/whitespace-cleanup-maybe () - "Only cleanup whitespace when out-of-focus." - (acdw/when-unfocused #'whitespace-cleanup)) - - (add-hook 'before-save-hook #'acdw/whitespace-cleanup-maybe) - #+end_src - -*** Expand region - - #+begin_src emacs-lisp - (straight-use-package 'expand-region) - - (global-set-key (kbd "C-=") #'er/expand-region) - #+end_src - -*** Move where I mean - - #+begin_src emacs-lisp - (straight-use-package 'mwim) - (require 'mwim) - - (cuss mwim-beginning-of-line-function 'beginning-of-visual-line) - (cuss mwim-end-of-line-function 'end-of-visual-line) - - (global-set-key (kbd "C-a") #'mwim-beginning) - (global-set-key (kbd "C-e") #'mwim-end) - #+end_src - -* Programming - -** Prettify symbols - - #+begin_src emacs-lisp - (cuss prettify-symbols-unprettify-at-point 'right-edge - "Unprettify a symbol when inside it or next to it.") - - (add-hook 'prog-mode-hook #'prettify-symbols-mode) - #+end_src - -** Parentheses - -*** Smart parentheses - - #+begin_src emacs-lisp - (straight-use-package 'smartparens) - (require 'smartparens-config) - - ;; replace show-paren - - (cuss sp-show-pair-delay 0 - "Don't delay before showing the pairs.") - (cuss sp-show-pair-from-inside t - "Highlight the enclosing pair when immediately inside.") - - (add-hook 'prog-mode-hook #'show-smartparens-mode +1) - - ;; enable strict smartparens in prog mode - (add-hook 'prog-mode-hook #'smartparens-strict-mode) - #+end_src - -** Indent aggressively - - #+begin_src emacs-lisp - (straight-use-package 'aggressive-indent) - - (global-aggressive-indent-mode +1) - #+end_src - -** Auto-fill comments only - -from [[https://www.emacswiki.org/emacs/AutoFillMode][EmacsWiki]]. - - #+begin_src emacs-lisp - (defun comment-auto-fill () - (setq-local comment-auto-fill-only-comments t) - (auto-fill-mode 1)) - - (add-hook 'prog-mode-hook #'comment-auto-fill) - #+end_src - -** Completion - - #+begin_src emacs-lisp - (straight-use-package 'company) - - (add-hook 'prog-mode-hook #'company-mode) - - (cuss company-idle-delay 0.1 - "Show company sooner.") - (cuss company-minimum-prefix-length 3 - "Don't try to complete short words.") - - (with-eval-after-load 'company - (define-key company-active-map (kbd "C-n") - (lambda () (interactive) (company-complete-common-or-cycle +1))) - (define-key company-active-map (kbd "C-p") - (lambda () (interactive) (company-complete-common-or-cycle -1)))) - #+end_src - -*** Give it a frame and better help - - #+begin_src emacs-lisp - (straight-use-package 'company-posframe) - - (with-eval-after-load 'company - (company-posframe-mode +1)) - #+end_src - -*** Prescient integration - - #+begin_src emacs-lisp - (straight-use-package 'company-prescient) - - (add-hook 'company-mode-hook #'company-prescient-mode) - #+end_src - -** Language-specific packages - -*** Executable scripts - - #+begin_src emacs-lisp - (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) - #+end_src - -*** Emacs lisp - - #+begin_src emacs-lisp - (cuss eval-expression-print-length nil - "Don't truncate printed expressions by length.") - (cuss eval-expression-print-level nil - "Don't truncate printed expressions by level.") - #+end_src - -**** Eros (Evaluation Result OverlayS) - - #+begin_src emacs-lisp - (straight-use-package 'eros) - - (cuss eros-eval-result-prefix ";; => " - "Prefix displayed before eros overlays.") - - (eros-mode +1) - #+end_src - -**** Indent Elisp like Common Lisp - - #+begin_src emacs-lisp - (require 'cl-lib) - (setq 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 - -*** Janet - - #+begin_src emacs-lisp - (straight-use-package 'janet-mode) - (require 'janet-mode) - - (straight-use-package '(inf-janet - :host github - :repo "velkyel/inf-janet")) - - (add-hook 'janet-mode-hook #'inf-janet-minor-mode) - #+end_src - -*** INI - - #+begin_src emacs-lisp - (straight-use-package 'ini-mode) - - (add-to-list 'auto-mode-alist - '("\\.ini\\'" . ini-mode)) - #+end_src - -*** PHP - - see also [[https://sasanidas.gitlab.io/f-site/php-development/][this post by Fermin M]], it looks really useful. - - #+begin_src emacs-lisp - (straight-use-package 'php-mode) - #+end_src - -* Writing - -** Visual fill column - -*** 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 - -*** Load the package - - #+begin_src emacs-lisp - (straight-use-package 'visual-fill-column) - - (cuss visual-fill-column-center-text nil - "Whether to center the text in the frame.") - - (cuss fill-column 84 - "Width of fill-column, and thus, visual-fill-column.") - - (advice-add 'text-scale-adjust - :after #'visual-fill-column-adjust) - - (global-visual-fill-column-mode +1) - #+end_src - -** Typographical niceties - -*** Variable pitch in text-modes - - #+begin_src emacs-lisp - (add-hook 'text-mode-hook #'variable-pitch-mode) - #+end_src - -*** Typo mode - - #+begin_src emacs-lisp - (straight-use-package 'typo) - - (add-hook 'text-mode-hook #'typo-mode) - - ;; Disable `typo-mode' when inside an Org source block - (with-eval-after-load 'typo - (add-to-list 'typo-disable-electricity-functions - #'org-in-src-block-p)) - #+end_src - -*** Show =^L= as a horizontal line - - #+begin_src emacs-lisp - (straight-use-package 'form-feed) - (global-form-feed-mode +1) - #+end_src - -** Word count - - #+begin_src emacs-lisp - (straight-use-package 'wc-mode) - - (add-hook 'text-mode-hook #'wc-mode) - - (rm/whitelist-add "WC") - #+end_src - -* Applications - -** Web browsing - - #+begin_src emacs-lisp - (cuss browse-url-browser-function 'browse-url-firefox) - (cuss browse-url-new-window-flag t - "Always open a new browser window.") - - ;;(cuss browse-url-generic-program "firefox") - (cuss browse-url-firefox-new-window-is-tab t - "Or a new tab, in Firefox.") - - ;; we need to add Firefox to `exec-path' on Windows - (when-at :work - (add-to-list 'exec-path "c:/Program Files/Mozilla Firefox")) - #+end_src - -** Dired - -*** Basic customization - - #+begin_src emacs-lisp - (defun acdw/setup-dired-mode () - (hl-line-mode) - (dired-hide-details-mode)) - - ;; highlight the current line in dired. - (add-hook 'dired-mode-hook #'acdw/setup-dired-mode) - - (cuss dired-recursive-copies 'always - "Always recursively copy.") - - (cuss dired-recursive-deletes 'always - "Always recursively delete.") - - (cuss delete-by-moving-to-trash t) - - (cuss dired-listing-switches "-alh" - "Show (A)lmost all items, - (l)isted out, with (h)uman-readable sizes.") - #+end_src - -*** Expand subtrees - - - #+begin_src emacs-lisp - (straight-use-package 'dired-subtree) - - (with-eval-after-load 'dired - (define-key dired-mode-map "i" #'dired-subtree-toggle)) - #+end_src - -*** Collapse singleton directories - - #+begin_src emacs-lisp - (straight-use-package 'dired-collapse) - - (add-hook 'dired-mode-hook #'dired-collapse-mode) - #+end_src - -*** Kill dired buffers - - from [[https://github.com/munen/emacs.d/][munen]]. - - #+begin_src emacs-lisp - (defun kill-dired-buffers () - "Kill all open dired buffers." - (interactive) - (mapc (lambda (buffer) - (when (eq 'dired-mode (buffer-local-value 'major-mode buffer)) - (kill-buffer buffer))) - (buffer-list))) - #+end_src - -** Org mode - - I’ve put org mode under Applications, as opposed to Writing, because it’s more generally-applicable than that. - -*** Basics - - #+begin_src emacs-lisp - (straight-use-package 'org) - - (with-eval-after-load 'org - (require 'org-tempo) - (require 'ox-md) - (define-key org-mode-map (kbd "M-n") #'outline-next-visible-heading) - (define-key org-mode-map (kbd "M-p") #'outline-previous-visible-heading)) - - (cuss org-hide-emphasis-markers t) - (cuss org-fontify-done-headline t) - (cuss org-fontify-whole-heading-line t) - (cuss org-fontify-quote-and-verse-blocks t) - (cuss org-pretty-entities t) - (cuss org-src-tab-acts-natively t) - (cuss org-src-fontify-natively t) - (cuss org-src-window-setup 'current-window) - (cuss org-confirm-babel-evaluate nil) - (cuss org-directory "~/Org") - (cuss org-ellipsis "…") - (cuss org-catch-invisible-edits 'show) - (cuss org-special-ctrl-a/e t) - (cuss org-special-ctrl-k t) - - (cuss org-export-headline-levels 8 - "Maximum level of headlines to export /as/ a headline.") - #+end_src - -**** TODO configure =mwim= to work with Org mode - -**** Tags - - #+begin_src emacs-lisp - (cuss org-tags-column 0 - "Show tags directly after the headline. - This is the best-looking option with variable-pitch fonts.") - - (cussface - '(org-tag - ((t - (:height 0.8 :weight normal :slant italic :foreground "grey40" :inherit - (variable-pitch)))))) - #+end_src - -***** Align all tags in the buffer on changes - - from [[https://github.com/mpereira/.emacs.d#align-all-tags-in-the-buffer-on-tag-changes][mpereira]]. - - #+begin_src emacs-lisp - (defun acdw/org-align-all-tags () - "Align all org tags in the buffer." - (interactive) - (when (eq major-mode 'org-mode) - (org-align-tags t))) - - (add-hook 'org-after-tags-change-hook #'acdw/org-align-all-tags) - #+end_src - -**** Source blocks - - #+begin_src emacs-lisp - (with-eval-after-load 'org-faces - (set-face-attribute 'org-block-begin-line nil - :height 0.85)) - #+end_src - -**** Prettify - - #+begin_src emacs-lisp - (defun acdw/org-mode-prettify () - "Prettify `org-mode'." - (dolist (cell '(("[ ]" . ?☐) ("[X]" . ?☑) ("[-]" . ?◐) - ("#+BEGIN_SRC" . ?✎) ("#+begin_src" . ?✎) - ("#+END_SRC" . ?■) ("#+end_src" . ?■))) - (add-to-list 'prettify-symbols-alist cell :append)) - (prettify-symbols-mode +1)) - - (add-hook 'org-mode-hook #'acdw/org-mode-prettify) - #+end_src - -*** General - -**** [[https://github.com/alphapapa/unpackaged.el#org-return-dwim][Org Return: DWIM]] - - #+begin_src emacs-lisp - (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)))) - - ;;;###autoload - (defun unpackaged/org-return-dwim (&optional default) - "A helpful replacement for `org-return'. With prefix, call `org-return'. - - On headings, move point to position after entry content. In - lists, insert a new item or end the list, with checkbox if - appropriate. In tables, insert a new row or end the table." - ;; Inspired by John Kitchin: http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ - (interactive "P") - (if default - (org-return) - (cond - ;; Act depending on context around point. - - ;; NOTE: I prefer RET to not follow links, but by uncommenting this block, links will be - ;; followed. - - ;; ((eq 'link (car (org-element-context))) - ;; ;; Link: Open it. - ;; (org-open-at-point-global)) - - ((org-at-heading-p) - ;; Heading: Move to position after entry content. - ;; NOTE: This is probably the most interesting feature of this function. - (let ((heading-start (org-entry-beginning-position))) - (goto-char (org-entry-end-position)) - (cond ((and (org-at-heading-p) - (= heading-start (org-entry-beginning-position))) - ;; Entry ends on its heading; add newline after - (end-of-line) - (insert "\n\n")) - (t - ;; Entry ends after its heading; back up - (forward-line -1) - (end-of-line) - (when (org-at-heading-p) - ;; At the same heading - (forward-line) - (insert "\n") - (forward-line -1)) - ;; FIXME: looking-back is supposed to be called with more arguments. - (while (not (looking-back (rx (repeat 3 (seq (optional blank) "\n"))) nil)) - (insert "\n")) - (forward-line -1))))) - - ((org-at-item-checkbox-p) - ;; Checkbox: Insert new item with checkbox. - (org-insert-todo-heading nil)) - - ((org-in-item-p) - ;; Plain list. Yes, this gets a little complicated... - (let ((context (org-element-context))) - (if (or (eq 'plain-list (car context)) ; First item in list - (and (eq 'item (car context)) - (not (eq (org-element-property :contents-begin context) - (org-element-property :contents-end context)))) - (unpackaged/org-element-descendant-of 'item context)) ; Element in list item, e.g. a link - ;; Non-empty item: Add new item. - (org-insert-item) - ;; Empty item: Close the list. - ;; TODO: Do this with org functions rather than operating on the text. Can't seem to find the right function. - (delete-region (line-beginning-position) (line-end-position)) - (insert "\n")))) - - ((when (fboundp 'org-inlinetask-in-task-p) - (org-inlinetask-in-task-p)) - ;; Inline task: Don't insert a new heading. - (org-return)) - - ((org-at-table-p) - (cond ((save-excursion - (beginning-of-line) - ;; See `org-table-next-field'. - (cl-loop with end = (line-end-position) - for cell = (org-element-table-cell-parser) - always (equal (org-element-property :contents-begin cell) - (org-element-property :contents-end cell)) - while (re-search-forward "|" end t))) - ;; Empty row: end the table. - (delete-region (line-beginning-position) (line-end-position)) - (org-return)) - (t - ;; Non-empty row: call `org-return'. - (org-return)))) - (t - ;; All other cases: call `org-return'. - (org-return))))) - - (with-eval-after-load 'org - (define-key org-mode-map (kbd "RET") #'unpackaged/org-return-dwim)) - #+end_src - -**** Insert blank lines around headers - - from [[https://github.com/alphapapa/unpackaged.el#ensure-blank-lines-between-headings-and-before-contents][unpackaged.el]]. - - #+begin_src emacs-lisp - ;;;###autoload - (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 - -***** Add a before-save-hook - - #+begin_src emacs-lisp - (defun cribbed/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 #'cribbed/org-mode-fix-blank-lines) - #+end_src - -*** Org Templates (=org-tempo=) - - #+begin_src emacs-lisp - (with-eval-after-load 'org - (add-to-list 'org-structure-template-alist - '("el" . "src emacs-lisp"))) - #+end_src - -*** Org Agenda - - #+begin_src emacs-lisp - (cuss org-agenda-files - (let ((list)) - (dolist (file '(;; add more files to this list - "home.org" - "work.org") - list) - (push (expand-file-name file org-directory) list)))) - - (define-key acdw/map (kbd "C-a") #'org-agenda) - - (cuss org-agenda-span 5 - "Show today + N days.") - - (cuss org-todo-keywords - '((sequence "RECUR(r)" "TODO(t)" "|" "DONE(d)") - (sequence "APPT(a)") - (sequence "|" "CANCELLED(c)"))) - - (cuss org-agenda-skip-scheduled-if-done t) - (cuss org-agenda-skip-deadline-if-done t) - (cuss org-deadline-warning-days 0 - "Don't warn of an impending deadline.") - - (cuss org-log-into-drawer "LOGBOOK" - "Log state changes into the LOGBOOK drawer, instead of after - the headline.") - (cuss org-log-done t - "Save CLOSED timestamp when done.") - #+end_src - -*** Capture - -**** Templates - - #+begin_src emacs-lisp - (cuss org-default-notes-file (expand-file-name "inbox.org" - org-directory) - "Put unfiled notes in ~/Org/inbox.org.") - - - (cuss org-capture-templates - '(("w" "Work Todo") ;;; Work stuff - ("ws" "Small Business" entry - (file+headline "work.org" "Small Business") - "* TODO %? - :PROPERTIES: - :Via: - :Note: - :END: - :LOGBOOK: - - State \"TODO\" from \"\" %U - :END:" :empty-lines 1) - ("wc" "Career Center" entry - (file+headline "work.org" "Career Center") - "* TODO %? - :PROPERTIES: - :Via: - :Note: - :END: - :LOGBOOK: - - State \"TODO\" from \"\" %U - :END:" :empty-lines 1) - ("wg" "General" entry - (file+headline "work.org" "General") - "* TODO %? - :PROPERTIES: - :Via: - :Note: - :END: - :LOGBOOK: - - State \"TODO\" from \"\" %U - :END:" :empty-lines 1) - - ;;; Regular To-Dos - ("t" "Todo") - ("tt" "Todo" entry - (file+headline "home.org" "TODOs") - "* TODO %? - :PROPERTIES: - :Via: - :Note: - :END: - :LOGBOOK: - - State \"TODO\" from \"\" %U - :END:" :empty-lines 1) - - ("td" "Todo with deadline" entry - (file+headline "home.org" "TODOs") - "* TODO %? - DEADLINE: %^t - :PROPERTIES: - :Via: - :Note: - :END: - :LOGBOOK: - - State \"TODO\" from \"\" %U - :END:" :empty-lines 1) - - - ("g" "Gift idea" entry - (file+headline "home.org" "Gift ideas") - "* %^{Who?} - ,* %^{What?}" :empty-lines 1) - - ("d" "Diary entry" entry - (file+olp+datetree "diary.org") - "* %? - Entered on %U - - " :empty-lines 1))) - #+end_src - -**** Keybindings - - #+begin_src emacs-lisp - (require 'org-capture) - - (with-eval-after-load 'org-capture - (define-key acdw/map (kbd "C-c") #'org-capture)) - #+end_src - -*** Include Org links in source code - - #+begin_src emacs-lisp - (straight-use-package '(org-link-minor-mode - :host github - :repo "seanohalpin/org-link-minor-mode")) - - ;; enable in elisp buffers - (add-hook 'emacs-lisp-mode-hook #'org-link-minor-mode) - #+end_src - -** Git - - #+begin_src emacs-lisp - (straight-use-package 'magit) - - (define-key acdw/map "g" #'magit-status) - #+end_src - -*** Git file modes - - #+begin_src emacs-lisp - (dolist (feat '(gitattributes-mode - gitconfig-mode - gitignore-mode)) - (straight-use-package feat) - (require feat)) - #+end_src - -** Beancount mode - - #+begin_src emacs-lisp - (straight-use-package '(beancount-mode - :host github - :repo "beancount/beancount-mode")) - (require 'beancount) - - (add-to-list 'auto-mode-alist '("\\.beancount\\'" . beancount-mode)) - - (defun acdw/disable-aggressive-indent () - "Turn `aggressive-indent-mode' off for a buffer." - (aggressive-indent-mode -1)) - - (add-hook 'beancount-mode-hook #'outline-minor-mode) - (add-hook 'beancount-mode-hook #'acdw/disable-aggressive-indent) - - (define-key beancount-mode-map (kbd "M-n") #'outline-next-visible-heading) - (define-key beancount-mode-map (kbd "M-p") #'outline-previous-visible-heading) - #+end_src - -*** Company integration with company-ledger - - #+begin_src emacs-lisp - (straight-use-package 'company-ledger) - - (with-eval-after-load 'company - (add-to-list 'company-backends 'company-ledger)) - #+end_src - -** PDF Tools - - I’m only enabling this at home for now, since it requires building stuff. - - #+begin_src emacs-lisp - (defun acdw/disable-visual-fill-column-mode () - "Disable `visual-fill-column-mode'." - (visual-fill-column-mode -1)) - - (eval-when-compile - (when-at :home - (straight-use-package 'pdf-tools) - (pdf-loader-install) - - (add-hook 'pdf-view-mode-hook #'acdw/disable-visual-fill-column-mode))) - #+end_src - -** E-book tools - - #+begin_src emacs-lisp - (straight-use-package 'nov) - - (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) - - (cuss nov-text-width t - "Disable text filling -- `visual-fill-column-mode' takes care - of that.") - - (defun acdw/setup-nov-mode () - (visual-fill-column-mode +1) - (setq visual-fill-column-center-text t) - (text-scale-increase +1)) - - (add-hook 'nov-mode-hook #'acdw/setup-nov-mode) - #+end_src - -** Email - - #+begin_src emacs-lisp - (when-at (:home (executable-find "mu")) - (add-to-list 'load-path - "/usr/share/emacs/site-lisp/mu4e") - (require 'mu4e) - - (cuss mail-user-agent 'mu4e-user-agent) - - (cuss mu4e-headers-skip-duplicates t) - (cuss mu4e-view-show-images t) - (cuss mu4e-view-show-addresses t) - (cuss mu4e-compose-format-flowed t) - (cuss mu4e-change-filenames-when-moving t) - (cuss mu4e-attachments-dir "~/Downloads") - - (cuss mu4e-maildir "~/.mail/fastmail") - (cuss mu4e-refile-folder "/Archive") - (cuss mu4e-sent-folder "/Sent") - (cuss mu4e-drafts-folder "/Drafts") - (cuss mu4e-trash-folder "/Trash") - - (fset 'my-move-to-trash "mTrash") - (define-key mu4e-headers-mode-map (kbd "d") 'my-move-to-trash) - (define-key mu4e-view-mode-map (kbd "d") 'my-move-to-trash) - - (cuss message-send-mail-function 'smtpmail-send-it) - (cuss smtpmail-default-smtp-server "smtp.fastmail.com") - (cuss smtpmail-smtp-server "smtp.fastmail.com") - (cuss smtpmail-stream-type 'ssl) - (cuss smtpmail-smtp-service 465) - (cuss smtpmail-local-domain "acdw.net") - (cuss mu4e-compose-signature - "Best,\nCase\n") - - (cuss mu4e-completing-read-function 'completing-read) - (cuss message-kill-buffer-on-exit t) - (cuss mu4e-confirm-quit nil) - - (cuss mu4e-bookmarks - '((:name "Unread" - :query - "flag:unread AND NOT flag:trashed AND NOT maildir:/Spam" - :key ?u) - (:name "Today" - :query - "date:today..now and not flag:trashed and not maildir:/Spam" - :key ?t) - (:name "This week" - :query - "date:7d..now and not maildir:/Spam and not flag:trashed" - :hide-unread t - :key ?w))) - - (cuss mu4e-headers-fields - '((:human-date . 12) - (:flags . 6) - (:mailing-list . 10) - (:from-or-to . 22) - (:thread-subject))) - - (cuss mu4e-maildir-shortcuts - `(("/INBOX" . ?i) - (,mu4e-refile-folder . ?a) - (,mu4e-sent-folder . ?s) - (,mu4e-drafts-folder . ?d) - (,mu4e-trash-folder . ?t))) - - (cuss mu4e-get-mail-command (cond ((executable-find "mbsync") - "mbsync -a")) - "The command to update mail with.") - (cuss mu4e-update-interval 300 - "Update automatically every 5 minutes.") - - (defun acdw/setup-mu4e-headers-mode () - (visual-line-mode -1)) - (add-hook 'mu4e-headers-mode #'acdw/setup-mu4e-headers-mode) - - (defun acdw/setup-mu4e-view-mode () - (setq visual-fill-column-center-text t) - (visual-fill-column-mode +1)) - (add-hook 'mu4e-view-mode-hook #'acdw/setup-mu4e-view-mode) - (add-hook 'mu4e-compose-mode-hook #'acdw/setup-mu4e-view-mode) - - (declare-function mu4e "mu4e") - (mu4e +1)) - #+end_src - -*** Add a keybinding - - #+begin_src emacs-lisp - (defun acdw/mu4e-or-warn () - "If `mu4e' is around, run it, or tell the user it isn't." - (interactive) - (if (featurep 'mu4e) - (mu4e) - (warn "Mu4e isn't available :/."))) - - (define-key acdw/map "m" #'acdw/mu4e-or-warn) - #+end_src - -** Smolweb - -*** A common function to make a cohesive smolweb experience - - #+begin_src emacs-lisp - (defun acdw/setup-smolweb () - "Configure emacs to view the smolweb." - (setq visual-fill-column-center-text t) - (visual-fill-column-mode +1) - (visual-line-mode +1) - (variable-pitch-mode -1) - (text-scale-increase +1)) - #+end_src - -*** Elpher - - #+begin_src emacs-lisp - (straight-use-package '(elpher - :repo "git://thelambdalab.xyz/elpher.git")) - - (with-eval-after-load 'no-littering - (cuss elpher-certificate-directory - (no-littering-expand-var-file-name "elpher-certificates/"))) - - (cuss elpher-ipv4-always t) - - (cussface '(elpher-gemini-heading1 - ((t (:inherit (modus-theme-heading-1 variable-pitch)))))) - (cussface '(elpher-gemini-heading2 - ((t (:inherit (modus-theme-heading-2 variable-pitch)))))) - (cussface '(elpher-gemini-heading3 - ((t (:inherit (modus-theme-heading-3 variable-pitch)))))) - - (defun elpher:eww-browse-url (original url &optional new-window) - "Handle gemini/gopher links with eww." - (cond ((string-match-p "\\`\\(gemini\\|gopher\\)://" url) - (require 'elpher) - (elpher-go url)) - (t (funcall original url new-window)))) - (advice-add 'eww-browse-url :around 'elpher:eww-browse-url) - - (with-eval-after-load 'elpher - (define-key elpher-mode-map "n" #'elpher-next-link) - (define-key elpher-mode-map "p" #'elpher-prev-link) - (define-key elpher-mode-map "o" #'elpher-follow-current-link) - (define-key elpher-mode-map "G" #'elpher-go-current)) - - (add-hook 'elpher-mode-hook #'acdw/setup-smolweb) - - (autoload 'elpher-bookmarks "elpher") - (define-key acdw/map "e" #'elpher-bookmarks) - #+end_src - -*** Gemini-mode - - #+begin_src emacs-lisp - (straight-use-package '(gemini-mode - :repo "https://git.carcosa.net/jmcbray/gemini.el.git")) - - (add-to-list 'auto-mode-alist - '("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode)) - - (cussface '(gemini-heading-face-1 - ((t (:inherit (elpher-gemini-heading1)))))) - (cussface '(gemini-heading-face2 - ((t (:inherit (elpher-gemini-heading2)))))) - (cussface '(gemini-heading-face3 - ((t (:inherit (elpher-gemini-heading3)))))) - - (add-hook 'gemini-mode-hook #'acdw/setup-smolweb) - #+end_src - -*** Gemini-write - - #+begin_src emacs-lisp - (straight-use-package '(gemini-write - :repo "https://tildegit.org/acdw/gemini-write.git")) - (require 'gemini-write) - #+end_src - -** RSS - -*** elfeed - - #+begin_src emacs-lisp - (straight-use-package 'elfeed) - (require 'elfeed) - (define-key acdw/map "w" 'elfeed) - - (cuss elfeed-use-curl (executable-find "curl")) - (cuss elfeed-curl-extra-arguments '("--insecure") - "Extra arguments for curl.") - (elfeed-set-timeout (* 60 3)) - - (defun acdw/setup-elfeed-show () - (setq visual-fill-column-center-text t) - (visual-fill-column-mode +1)) - - (add-hook 'elfeed-show-mode-hook #'acdw/setup-elfeed-show) - #+end_src - -*** elfeed-protocol - - #+begin_src emacs-lisp - (straight-use-package 'elfeed-protocol) - (require 'elfeed-protocol) - - (cuss elfeed-protocol-ttrss-maxsize 200) - - (cuss elfeed-feeds (list - (list "ttrss+https://acdw@rss.tildeverse.org" - :use-authinfo t))) - - ;; Uncomment this line if elfeed is giving problems. - ;; (setq elfeed-log-level 'debug) - - (elfeed-protocol-enable) - - (defun acdw/update-elfeed-protocol-feeds () - "Wrap various (non-working) protocol updaters. - I could probably do this in a much more ... better way." - (interactive) - (elfeed-protocol-ttrss-reinit "https://rss.tildeverse.org")) - - (with-eval-after-load 'elfeed - (define-key elfeed-search-mode-map "G" #'acdw/update-elfeed-protocol-feeds)) - #+end_src - -* System integration +* System-specific + +I use both Linux (at home) and Windows (at work). To make Emacs +easier to use in both systems, I've included various system-specific +settings and written some ancillary scripts. + +** Determine where I am + +#+begin_src emacs-lisp + (defmacro when-at (conditions &rest commands) + "Run COMMANDS when at a specific place. + + CONDITIONS are one of `:work', `:home', or a list beginning with + those and other conditions to check. COMMANDS are only run if + all CONDITIONS are met." + (declare (indent 1)) + (let ((at-work (memq system-type '(ms-dos windows-nt))) + (at-home (memq system-type '(gnu gnu/linux gnu/kfreebsd)))) + (pcase conditions + (:work `(when ',at-work ,@commands)) + (:home `(when ',at-home ,@commands)) + (`(:work ,others) `(when (and ',at-work ,others) + ,@commands)) + (`(:home ,others) `(when (and ',at-home ,others) + ,@commands))))) +#+end_src ** Linux -*** Exec path from shell +*** Settings - #+begin_src emacs-lisp - (eval-when-compile - (when-at :home - (straight-use-package 'exec-path-from-shell) - (defvar acdw/exec-path-from-shell-initialized nil - "Stores whether we've initialized or not.") - (unless acdw/exec-path-from-shell-initialized - (exec-path-from-shell-initialize) - (setq acdw/exec-path-from-shell-initialized (current-time))))) - #+end_src +*** Scripts -* Areas for further research +**** em +:PROPERTIES: +:header-args: :tangle-mode (identity #o755) :tangle bin/em +:END: -- [[https://github.com/larstvei/dot-emacs#key-bindings][Use a minor-mode for keybindings]] +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" + if ! emacsclient -nc "$@"; then + emacs --daemon + emacsclient -nc "$@" + fi +#+end_src + +**** emacsclient.desktop +:PROPERTIES: +:header-args: :tangle bin/emacsclient.desktop +: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 + [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 + +I use Windows at work, where I /also/ don't have Admin rights. So I +kind of fly-by-night there. Much of the ideas and scripts in this +section come from [[https://github.com/termitereform/JunkPile/blob/master/emacs-on-windows.md][termitereform]] on Github. + +*** Settings + +#+NAME: w32-settings +#+begin_src emacs-lisp + (when (memq system-type '(windows-nt ms-dos cygwin)) + (setq w32-allow-system-shell t ; enable cmd.exe as shell + )) +#+end_src + +*** Scripts +:PROPERTIES: +:header-args: :noweb tangle +:END: + +**** Common variables + +#+NAME: w32-bat-common +#+begin_src bat +set HOME=%~dp0..\..\ +set EMACS=%~dp0..\..\..\bin\runemacs.exe +#+end_src + +**** Emacs Daemon + +Either run this once at startup, or put a shortcut of it in the +Startup folder: +=%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup=. + +#+begin_src bat :tangle "bin/Emacs Daemon.cmd" +<> +"%EMACS%" --daemon +#+end_src + +**** Emacs Client + +This will try to connect to the daemon above. If that fails, it'll +run =runemacs.exe=. + +*This is the main shortcut for running Emacs.* + +#+begin_src bat :tangle bin/Emacs.cmd +<> +set EMACSC=%~dp0..\..\..\bin\emacsclientw.exe +"%EMACSC%" -n -c -a "%EMACS%" %* +#+end_src + +**** Emacs Safe Start + +This runs Emacs with the factory settings. + +#+begin_src bat :tangle "bin/Emacs Safe Start.cmd" +<> +"%EMACS%" -Q %* +#+end_src + +**** Emacs Debug + +This runs Emacs with the =--debug-init= option enabled. + +#+begin_src bat :tangle "bin/Emacs Debug.cmd" +<> +"%EMACS%" --debug-init %* +#+end_src * Appendices - :PROPERTIES: - :header-args: :tangle LICENSE :comments no - :END: - -** Emacs' files +** Emacs's files *** init.el +:PROPERTIES: +:header-args: :tangle init.el :comments both +:END: - #+begin_src emacs-lisp :tangle init.el - ;; init.el -*- lexical-binding: t -*- +The classic Emacs initiation file. - (setq load-prefer-newer t) +**** Use lexical binding when evaluating =init.el= - (let* (;; Speed up init - (gc-cons-threshold most-positive-fixnum) - (file-name-handler-alist nil) - ;; Config file names - (conf (expand-file-name "config" - user-emacs-directory)) - (conf-el (concat conf ".el")) - (conf-org (concat conf ".org"))) - (unless (and (file-newer-than-file-p conf-el conf-org) - (load conf 'no-error)) - ;; A plain require here just loads the older `org' - ;; in Emacs' install dir. We need to add the newer - ;; one to the `load-path', hopefully that's all. - (add-to-list 'load-path (expand-file-name "straight/build/org" - user-emacs-directory)) - (require 'org) - (org-babel-load-file conf-org))) - #+end_src +#+begin_src emacs-lisp :comments no :noweb tangle + ;; init.el -*- lexical-binding: t -*- + <> +#+end_src + +**** Prefer newer files to older files + +#+begin_src emacs-lisp + (setq 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. + +#+begin_src emacs-lisp + (let* (;; Speed up init + (gc-cons-threshold most-positive-fixnum) + (file-name-handler-alist nil) + ;; Config file names + (conf (expand-file-name "config" + user-emacs-directory)) + (conf-el (concat conf ".el")) + (conf-org (concat conf ".org"))) + (unless (and (file-newer-than-file-p conf-el conf-org) + (load conf 'no-error)) + ;; A plain require here just loads the older `org' + ;; in Emacs' install dir. We need to add the newer + ;; one to the `load-path', hopefully that's all. + (add-to-list 'load-path (expand-file-name "straight/build/org" + user-emacs-directory)) + (require 'org) + (org-babel-load-file conf-org))) +#+end_src *** early-init.el +:PROPERTIES: +:header-args: :tangle early-init.el :comments both +:END: - #+begin_src emacs-lisp :tangle early-init.el - ;; early-init.el -*- no-byte-compile: t; -*- +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. - ;; I use `straight.el' instead of `package.el'. - (setq package-enable-at-startup nil) +**** Don't byte-compile this file - ;; Don't resize the frame when loading fonts - (setq frame-inhibit-implied-resize t) - ;; Resize frame by pixels - (setq frame-resize-pixelwise t) - #+end_src +#+begin_src emacs-lisp :comments no :noweb tangle + ;; early-init.el -*- no-byte-compile: t; -*- + <> +#+end_src -** Ease tangling and loading of Emacs' init +**** Disable loading of =package.el= - #+begin_src emacs-lisp :tangle config.el - (defun refresh-emacs (&optional disable-load) - "Tangle `config.org', then byte-compile the resulting files. - Then, load the byte-compilations unless passed with a prefix argument." - (interactive "P") - (let ((config (expand-file-name "config.org" user-emacs-directory))) - (save-mark-and-excursion - (with-current-buffer (find-file-noselect config) - (let ((prog-mode-hook nil)) - ;; generate the readme - (when (file-newer-than-file-p config (expand-file-name - "README.md" - user-emacs-directory)) - (message "%s" "Exporting README.md...") - (require 'ox-md) - (with-demoted-errors "Problem exporting README.md: %S" - (org-md-export-to-markdown))) - ;; tangle config.org - (when (file-newer-than-file-p config (expand-file-name - "config.el" - user-emacs-directory)) - (message "%s" "Tangling config.org...") - (add-to-list 'load-path (expand-file-name "straight/build/org/" - user-emacs-directory)) - (require 'org) - (let ((inits (org-babel-tangle))) - ;; byte-compile resulting files - (message "%s" "Byte-compiling...") - (dolist (f inits) - (when (string-match "\\.el\\'" f) - (byte-compile-file f (not disable-load))))))))))) - #+end_src +I use =straight.el= instead. -** Ancillary scripts +#+begin_src emacs-lisp + (setq package-enable-at-startup nil) +#+end_src -*** emacsdc - :PROPERTIES: - :header-args: :mkdirp yes :tangle-mode (identity #o755) - :END: +**** Don't resize the frame when loading fonts - Here's a wrapper script that'll start =emacs --daemon= if there isn't - one, and then launch =emacsclient= with the arguments. I'd recommend - installing with either ~ln -s bin/emacsdc $HOME/.local/bin/~, or - adding =$HOME/.local/bin= to your =$PATH=. - - #+begin_src sh :tangle bin/emacsdc :shebang "#!/bin/sh" - if ! emacsclient -nc "$@" 2>/dev/null; then - emacs --daemon - emacsclient -nc "$@" - fi - #+end_src +#+begin_src emacs-lisp + (setq frame-inhibit-implied-resize t) +#+end_src -*** Emacs.cmd - :PROPERTIES: - :header-args: :mkdirp yes - :END: +**** Resize frame by pixels - Here's a wrapper script that'll run Emacs on Windows, with a - custom =$HOME=. I have mine setup like this: Emacs is downloaded - from [[https://mirrors.tripadvisor.com/gnu/emacs/windows/emacs-27/emacs-27.1-x86_64.zip][the GNU mirror]] and unzipped to =~/Downloads/emacs/=. For - some reason, Emacs by default sets =$HOME= to =%APPDATA%=, which - doesn’t make a lot of sense to me. I change it to - =%USERPROFILE%\Downloads\home=, and then run Emacs with the - supplied arguments. +#+begin_src emacs-lisp + (setq frame-resize-pixelwise t) +#+end_src - As far as creating a shortcut to the Desktop, you’ll have to do - that yourself – /apparently/ Windows doesn’t have a built-in - shortcut-creating software >:(. +**** Shoe-horned from elsewhere in =config.org= - #+begin_src bat :tangle bin/Emacs.cmd - REM Set variables - set HOME=%USERPROFILE%\Downloads\home - set EMACS=%USERPROFILE%\Downloads\emacs\bin\runemacs.exe +A fundamental tension of literal programming is logical versus +programmatic ordering. I understand that's a problem it's meant to +solve but hey, maybe I'm not quite there yet. I feel that having this +weird shoe-horning of other bits of my config here, in a backwater +heading in an appendix, isn't quite the future I wanted. But it's +what I have for now. - REM Change the directory - chdir %HOME% - - REM Run "Quick Mode" - REM "%EMACS%" -Q %* - - REM Regular - "%EMACS%" %* - #+end_src - -*** emacsclient.desktop - - from [[https://www.taingram.org/blog/emacs-client.html][taingram]]. - - #+begin_src conf-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 +#+begin_src emacs-lisp :noweb tangle + <> +#+end_src ** License - :PROPERTIES: - :header-args: :tangle LICENSE :comments no - :END: +:PROPERTIES: +:header-args: :tangle LICENSE :comments no +:END: - Copyright © 2020 Case Duckworth +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. +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 +#+begin_src text + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 + Version 2, December 2004 - Copyright (C) 2004 Sam Hocevar + 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. + 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 + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 0. You just DO WHAT THE FUCK YOU WANT TO. - #+end_src + 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: +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!* - -** TODO Local variables - - One day, I’m going to add a Local Variables block to the end of this file, which will add an =after-save-hook= to auto-tangle the file after saving. But first I need to research how best to do that asynchronously. So. +*SUE ME, RMS!* diff --git a/early-init.el b/early-init.el index fc19618..b74f5e4 100644 --- a/early-init.el +++ b/early-init.el @@ -1,9 +1,53 @@ ;; early-init.el -*- no-byte-compile: t; -*- +;; This file is automatically tangled from config.org. +;; Hand edits will be overwritten! -;; I use `straight.el' instead of `package.el'. +;; Disable loading of =package.el= + +;; I use =straight.el= instead. + + +;; [[file:~/.config/emacs/config.org::*Disable loading of =package.el=][Disable loading of =package.el=:1]] (setq package-enable-at-startup nil) +;; Disable loading of =package.el=:1 ends here ;; Don't resize the frame when loading fonts + + +;; [[file:~/.config/emacs/config.org::*Don't resize the frame when loading fonts][Don't resize the frame when loading fonts:1]] (setq frame-inhibit-implied-resize t) +;; Don't resize the frame when loading fonts:1 ends here + ;; Resize frame by pixels + + +;; [[file:~/.config/emacs/config.org::*Resize frame by pixels][Resize frame by pixels:1]] (setq frame-resize-pixelwise t) +;; Resize frame by pixels:1 ends here + +;; Shoe-horned from elsewhere in =config.org= + +;; A fundamental tension of literal programming is logical versus +;; programmatic ordering. I understand that's a problem it's meant to +;; solve but hey, maybe I'm not quite there yet. I feel that having this +;; weird shoe-horning of other bits of my config here, in a backwater +;; heading in an appendix, isn't quite the future I wanted. But it's +;; what I have for now. + + +;; [[file:~/.config/emacs/config.org::*Shoe-horned from elsewhere in =config.org=][Shoe-horned from elsewhere in =config.org=:1]] +(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)) + +(scroll-bar-mode -1) +(horizontal-scroll-bar-mode -1) +;; Shoe-horned from elsewhere in =config.org=:1 ends here diff --git a/init.el b/init.el index e8b999d..60a63c2 100644 --- a/init.el +++ b/init.el @@ -1,21 +1,37 @@ ;; init.el -*- lexical-binding: t -*- +;; This file is automatically tangled from config.org. +;; Hand edits will be overwritten! +;; Prefer newer files to older files + + +;; [[file:~/.config/emacs/config.org::*Prefer newer files to older files][Prefer newer files to older files:1]] (setq load-prefer-newer t) +;; Prefer newer files to older files:1 ends here +;; 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. + + +;; [[file:~/.config/emacs/config.org::*Load the config][Load the config:1]] (let* (;; Speed up init (gc-cons-threshold most-positive-fixnum) (file-name-handler-alist nil) ;; Config file names (conf (expand-file-name "config" - user-emacs-directory)) + user-emacs-directory)) (conf-el (concat conf ".el")) (conf-org (concat conf ".org"))) (unless (and (file-newer-than-file-p conf-el conf-org) - (load conf 'no-error)) + (load conf 'no-error)) ;; A plain require here just loads the older `org' ;; in Emacs' install dir. We need to add the newer ;; one to the `load-path', hopefully that's all. (add-to-list 'load-path (expand-file-name "straight/build/org" - user-emacs-directory)) + user-emacs-directory)) (require 'org) (org-babel-load-file conf-org))) +;; Load the config:1 ends here