;;; +emacs.el --- measured defaults for Emacs -*- lexical-binding: t -*- ;;; Commentary: ;; I find myself copy-pasting a lot of "boilerplate" type code when ;; bankrupting my Emacs config and starting afresh. Instead of doing ;; that, I'm putting it here, where it'll be easier to include in my ;; config. ;; Of course, some might say I could just ... stop bankrupting my ;; Emacs. But like, why would I want to? ;; Other notable packages include ;; - https://git.sr.ht/~technomancy/better-defaults/ ;; - https://github.com/susam/emfy ;;; Code: (require 'early-init (locate-user-emacs-file "early-init.el")) (defun +set-major-mode-from-buffer-name (&optional buf) "Set the major mode for BUF from the buffer's name. Do this only if the buffer is not visiting a file." (unless buffer-file-name (let ((buffer-file-name (buffer-name buf))) (set-auto-mode)))) ;;; General settings (setq-default apropos-do-all t async-shell-command-buffer 'new-buffer async-shell-command-display-buffer nil auto-hscroll-mode 'current-line auto-revert-verbose nil auto-save-file-name-transforms `((".*" ,(.etc "auto-save/" t) t)) auto-save-interval 60 auto-save-list-file-prefix (.etc "auto-save/.saves-" t) auto-save-timeout 60 auto-save-visited-interval 60 auto-window-vscroll nil backup-by-copying t backup-directory-alist `((".*" . ,(.etc "backup/" t))) blink-cursor-blinks 1 comp-deferred-compilation nil completion-category-defaults nil completion-category-overrides '((file (styles . (partial-completion)))) completion-ignore-case t completion-styles '(substring partial-completion) create-lockfiles nil cursor-in-non-selected-windows 'hollow cursor-type 'bar custom-file (.etc "custom.el") delete-old-versions t echo-keystrokes 0.1 ediff-window-setup-function 'ediff-setup-windows-plain eldoc-echo-area-use-multiline-p nil eldoc-idle-delay 0.1 enable-recursive-minibuffers t executable-prefix-env t fast-but-imprecise-scrolling t file-name-shadow-properties '(invisible t intangible t) fill-column 80 find-file-visit-truename t frame-resize-pixelwise t global-auto-revert-non-file-buffers t global-mark-ring-max 100 hscroll-margin 1 hscroll-step 1 imenu-auto-rescan t image-use-external-converter (or (executable-find "convert") (executable-find "gm") (executable-find "ffmpeg")) indent-tabs-mode nil inhibit-startup-screen t initial-buffer-choice t kept-new-versions 6 kept-old-versions 2 kill-do-not-save-duplicates t kill-read-only-ok t kill-ring-max 500 kmacro-ring-max 20 load-prefer-newer noninteractive major-mode '+set-major-mode-from-buffer-name mark-ring-max 50 minibuffer-eldef-shorten-default t minibuffer-prompt-properties (list 'read-only t 'cursor-intangible t 'face 'minibuffer-prompt) mode-require-final-newline 'visit-save mouse-drag-copy-region t mouse-wheel-progressive-speed nil mouse-yank-at-point t native-comp-async-report-warnings-errors 'silent native-comp-deferred-compilation nil read-answer-short t read-buffer-completion-ignore-case t ;; read-extended-command-predicate ;; (when (fboundp ;; 'command-completion-default-include-p) ;; 'command-completion-default-include-p) read-process-output-max (+bytes 1 :mib) ; We’re in the future man. Set that to at least a megabyte recenter-positions '(top middle bottom) regexp-search-ring-max 100 regexp-search-ring-max 200 save-interprogram-paste-before-kill t scroll-conservatively 101 scroll-down-aggressively 0.01 scroll-margin 2 scroll-preserve-screen-position 1 scroll-step 1 scroll-up-aggressively 0.01 search-ring-max 200 search-ring-max 200 sentence-end-double-space t set-mark-command-repeat-pop t show-paren-delay 0 show-paren-style 'mixed show-paren-when-point-in-periphery t show-paren-when-point-inside-paren t ;;show-trailing-whitespace t tab-bar-show 1 tab-width 8 ; so alignment expecting the default looks right tramp-backup-directory-alist backup-directory-alist undo-limit 100000000 ; 10 MB use-dialog-box nil use-file-dialog nil use-short-answers t vc-follow-symlinks t vc-make-backup-files t version-control t view-read-only t visible-bell nil window-resize-pixelwise t x-select-enable-clipboard t x-select-enable-primary t yank-pop-change-selection t ) ;; Programming language offsets. ;; Set these after the initial block so I can use `tab-width' (setq-default c-basic-offset tab-width) ;; Emacs 28 ships with an option, `use-short-answers', that makes this form ;; obsolete, but I still use 27 at work. (when (version< emacs-version "28") (fset 'yes-or-no-p 'y-or-n-p)) ;;; Encodings ;; Allegedly, this is the only one you need... (set-language-environment "UTF-8") ;; But I still set all of these, for fun. (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 default-process-coding-system '(utf-8-unix . utf-8-unix) x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) (set-charset-priority 'unicode) (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) (pcase system-type ((or 'ms-dos 'windows-nt) (set-clipboard-coding-system 'utf-16-le) (set-selection-coding-system 'utf-16-le)) (_ (set-selection-coding-system 'utf-8) (set-clipboard-coding-system 'utf-8))) ;;; Modes (dolist (enable-mode '(global-auto-revert-mode blink-cursor-mode electric-pair-mode show-paren-mode global-so-long-mode minibuffer-depth-indicate-mode file-name-shadow-mode minibuffer-electric-default-mode delete-selection-mode ;; column-number-mode )) (when (fboundp enable-mode) (funcall enable-mode +1))) (dolist (disable-mode '(tooltip-mode tool-bar-mode menu-bar-mode scroll-bar-mode horizontal-scroll-bar-mode)) (when (fboundp disable-mode) (funcall disable-mode -1))) ;;; Hooks (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (defun +auto-create-missing-dirs () "Automatically create missing directories when finding a file." ;; https://emacsredux.com/blog/2022/06/12/auto-create-missing-directories/ (let ((target-dir (file-name-directory buffer-file-name))) (unless (file-exists-p target-dir) (make-directory target-dir t)))) (add-hook 'find-file-not-found-functions #'+auto-create-missing-dirs) ;;; Better-default functions ... (defun +cycle-spacing (&optional n preserve-nl-back mode) "Negate N argument on `cycle-spacing'. That is, with a positive N, deletes newlines as well, leaving -N spaces. If N is negative, it will not delete newlines and leave N spaces. See docstring of `cycle-spacing' for the meaning of PRESERVE-NL-BACK and MODE." (interactive "*p") (cycle-spacing (- n) preserve-nl-back mode)) (defun +save-buffers-quit (&optional arg) "Silently save each buffer, then kill the current connection. If the current frame has no client, kill Emacs itself using `save-buffers-kill-emacs' after confirming with the user. With prefix ARG, silently save all file-visiting buffers, then kill without asking." (interactive "P") (save-some-buffers t) (if (and (not (frame-parameter nil 'client)) (and (not arg))) (when (yes-or-no-p "Sure you want to quit? ") (save-buffers-kill-emacs)) (delete-frame nil :force))) (defun +kill-word-backward-or-region (&optional arg backward-kill-word-fn) "Kill active region or ARG words backward. BACKWARD-KILL-WORD-FN is the function to call to kill a word backward. It defaults to `backward-kill-word'." (interactive "P") (call-interactively (if (region-active-p) #'kill-region (or backward-kill-word-fn #'backward-kill-word)))) (defun +backward-kill-word-wrapper (fn &optional arg) "Kill backward using FN until the beginning of a word, smartly. If point is on at the beginning of a line, kill the previous new line. If the only thing before point on the current line is whitespace, kill that whitespace. With argument ARG: if ARG is a number, just call FN ARG times. Otherwise, just call FN." ;; I want this to be a wrapper so that I can call other word-killing functions ;; with it. It's *NOT* advice because those functions probably use ;; `backward-kill-word' under the hood (looking at you, paredit), so advice ;; will make things weird. (if (null arg) (cond ((looking-back "^" 1) (let ((delete-active-region nil)) (delete-backward-char 1))) ((looking-back "^[ ]*") (delete-horizontal-space :backward-only)) (t (call-interactively fn))) (funcall fn (if (listp arg) 1 arg)))) (defun +backward-kill-word (&optional arg) "Kill word backward using `backward-kill-word'. ARG is passed to `backward-kill-word'." (interactive "P") (+backward-kill-word-wrapper #'backward-kill-word arg)) ;; ... and advice ;; Indent the region after a yank. (defun +yank@indent (&rest _) "Indent the current region." (indent-region (min (point) (mark)) (max (point) (mark)))) (advice-add #'yank :after #'+yank@indent) (advice-add #'yank-pop :after #'+yank@indent) ;;; Bindings ;; I need to place these bindings under `+key-mode-map' so that they aren't ;; shadowed by other maps. There might be a better way to do this. (require '+key) (dolist (binding '(("C-x C-c" . +save-buffers-quit) ("M-SPC" . +cycle-spacing) ("M-/" . hippie-expand) ("M-=" . count-words) ("C-x C-b" . ibuffer) ("C-s" . isearch-forward-regexp) ("C-r" . isearch-backward-regexp) ("C-M-s" . isearch-forward) ("C-M-r" . isearch-backward))) (define-key (current-global-map) (kbd (car binding)) (cdr binding))) ;;; Required libraries (when (require 'uniquify nil :noerror) (setq-default uniquify-buffer-name-style 'forward uniquify-separator path-separator uniquify-after-kill-buffer-p t uniquify-ignore-buffers-re "^\\*")) (when (require 'goto-addr) (if (fboundp 'global-goto-address-mode) (global-goto-address-mode +1) (add-hook 'after-change-major-mode-hook 'goto-address-mode))) (when (require 'recentf nil :noerror) (setq-default recentf-save-file (.etc "recentf.el") recentf-max-menu-items 100 recentf-max-saved-items nil recentf-auto-cleanup 'mode) (add-to-list 'recentf-exclude .etc) (recentf-mode +1)) (when (require 'savehist nil :noerror) (setq-default history-length t history-delete-duplicates t history-autosave-interval 60 savehist-file (.etc "savehist.el") ;; Other variables --- don't truncate any of these. ;; `add-to-history' uses the values of these variables unless ;; they're nil, in which case it falls back to `history-length'. kill-ring-max 100 mark-ring-max 100 global-mark-ring-max 100 regexp-search-ring-max 100 search-ring-max 100 kmacro-ring-max 100 eww-history-limit 100) (dolist (var '(extended-command-history global-mark-ring mark-ring kill-ring kmacro-ring regexp-search-ring search-ring)) (add-to-list 'savehist-additional-variables var)) (savehist-mode +1)) (when (require 'saveplace nil :noerror) (setq-default save-place-file (.etc "places.el") save-place-forget-unreadable-files (eq system-type 'gnu/linux)) (save-place-mode +1)) ;; (when (require 'tramp) ;; ;; thanks Irreal! https://irreal.org/blog/?p=895 ;; (add-to-list 'tramp-default-proxies-alist ;; '(nil "\\`root\\'" "/ssh:%h:")) ;; (add-to-list 'tramp-default-proxies-alist ;; '((regexp-quote (system-name)) nil nil))) ;;; Newer features ;; These aren't in older version of Emacs, but they're so nice. (when (fboundp 'repeat-mode) (setq-default repeat-exit-key "g" repeat-exit-timeout 5) (repeat-mode +1)) (when (fboundp 'pixel-scroll-precision-mode) (pixel-scroll-precision-mode +1)) (provide '+emacs) ;;; +emacs.el ends here