From 0920c1b361e50d99c943d30255fed95f7bc0da23 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Mon, 17 Oct 2022 23:28:38 -0500 Subject: meh --- early-init.el | 2 + init.el | 36 ++++- lisp/+emacs.el | 410 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lisp/acdw.el | 27 ---- lisp/yoke.el | 66 +++++++--- 5 files changed, 495 insertions(+), 46 deletions(-) create mode 100644 lisp/+emacs.el diff --git a/early-init.el b/early-init.el index 173625f..340cbf7 100644 --- a/early-init.el +++ b/early-init.el @@ -1,5 +1,7 @@ ;;; emacs early init -*- lexical-binding: t; -*- ;; by C. Duckworth +;; Bankruptcy: 9 + (provide 'early-init) ;;; Speed up init diff --git a/init.el b/init.el index d7a55d4..6889957 100644 --- a/init.el +++ b/init.el @@ -9,7 +9,8 @@ ;; - Be kind to yourself. ;; - Make good choices. -(progn +(yoke +emacs (locate-user-emacs-file "lisp/") + (require '+emacs) ;; Settings (setq truncate-string-ellipsis "…" ring-bell-function #'ignore @@ -19,9 +20,23 @@ "C-x C-k" #'kill-current-buffer "C-/" #'undo-only "C-?" #'undo-redo + "C-x C-c" #'+save-buffers-quit + "M-SPC" #'+cycle-spacing + "M-/" #'hippie-expand + "M-=" #'count-words + "C-x C-b" #'ibuffer +"C-x 4 n" #'clone-buffer + "S-" #'mouse-set-mark + "C-x 0" #'+delete-window-or-bury-buffer "M-j" nil "" nil) + ;; Hooks + (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) + (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) + (add-hook 'find-file-not-found-functions #'+auto-create-missing-dirs) ;; Advice + (add-function :after after-focus-change-function #'+save-some-buffers-debounce) + (advice-add 'keyboard-escape-quit :around #'keyboard-escape-quit-keep-window-open) (define-advice keyboard-escape-quit (:around (fn &rest r)) "Don't close quits on `keyboard-escape-quit'." (let ((buffer-quit-function #'ignore)) @@ -29,6 +44,13 @@ ;; Themes (load-theme 'modus-operandi)) +(yoke isearch nil + (define-keys (current-global-map) + "C-s" #'isearch-forward-regexp + "C-r" #'isearch-backward-regexp + "C-M-s" #'isearch-forward + "C-M-r" #'isearch-backward)) + (yoke auth-source nil (setq auth-sources `(default "secrets:passwords")) (setq-local-hook authinfo-mode-hook @@ -52,7 +74,6 @@ "C-c k" #'consult-kmacro ;; C-x bindings (ctl-x-map) "C-x M-:" #'consult-complex-command - "" #'consult-buffer "C-x b" #'consult-buffer "C-x 4 b" #'consult-buffer-other-window "C-x 5 b" #'consult-buffer-other-frame @@ -97,7 +118,7 @@ (define-key org-mode-map (kbd "M-g o") #'consult-org-heading))) (yoke orderless "https://github.com/oantolin/orderless" - (require 'orderless) +(require 'orderless) (setq completion-styles '(substring orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles basic partial-completion))) @@ -174,3 +195,12 @@ (yoke minions "https://github.com/tarsius/minions" (minions-mode)) + +(yoke magit "https://github.com/magit/magit" + :load (locate-user-emacs-file "yoke/magit/lisp") + :depends ((transient "https://github.com/magit/transient" + (locate-user-emacs-file "yoke/transient/lisp")) + (dash "https://github.com/magnars/dash.el") + (with-editor "https://github.com/magit/with-editor" + (locate-user-emacs-file "yoke/with-editor/lisp"))) + (autoload #'transient--with-suspended-override "transient")) diff --git a/lisp/+emacs.el b/lisp/+emacs.el new file mode 100644 index 0000000..6f37b83 --- /dev/null +++ b/lisp/+emacs.el @@ -0,0 +1,410 @@ +;;; +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 t + auto-save-default nil + auto-save-file-name-transforms `((".*" ,(.etc "auto-save/") ,(car (secure-hash-algorithms))) + (".*" ,(.etc "auto-save/") t)) + auto-save-interval 30 + auto-save-list-file-prefix (.etc "auto-save/.saves-" t) + auto-save-timeout 30 + auto-save-visited-interval 5 + 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 1048576 ; 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 + save-some-buffers-default-predicate #'+save-some-buffers-p + 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 'parenthesis + 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 + auto-save-visited-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 + +(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)))) + +(defvar +save-some-buffers-debounce-time nil + "Last time `+save-some-buffers-debounce' was run.") + +(defcustom +save-some-buffers-debounce-timeout 5 + "Number of seconds to wait before saving buffers again.") + +(defun +save-some-buffers-debounce (&rest _) + "Run `save-some-buffers', but only if it's been a while." + (unless (and +save-some-buffers-debounce-time + (< (- (time-convert nil 'integer) +save-some-buffers-debounce-time) + +save-some-buffers-debounce-timeout)) + (save-some-buffers t) + (setq +save-some-buffers-debounce-time (time-convert nil 'integer)))) + + +;;; 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) + + +;;; Extra functions + +(defun +save-some-buffers-p () + "Predicate for `save-some-buffers-default-predicate'. +It returns nil with remote files and those without attached files." + (and (buffer-file-name) + (not (file-remote-p (buffer-file-name))))) + +;; https://www.wwwtech.de/articles/2013/may/emacs:-jump-to-matching-paren-beginning-of-block +(defun +goto-matching-paren (&optional arg) + "Go to the matching paren, similar to vi's %." + (interactive "p") + (or arg (setq arg 1)) + (cond + ;; Check for "outside of bracket" positions + ((looking-at "[\[\(\{]") (forward-sexp arg)) + ((looking-back "[\]\)\}]" 1) (backward-sexp arg)) + ;; Otherwise, move from inside the bracket + ((looking-at "[\]\)\}]") (forward-char) (backward-sexp arg)) + ((looking-back "[\[\(\{]" 1) (backward-char) (forward-sexp arg)) + (t (up-list arg t t)))) + +(defun +delete-window-or-bury-buffer () + "Delete the current window, or bury the current buffer. +If the current window is the only window, bury the buffer." + (interactive) + (condition-case e + (delete-window) + (t (bury-buffer)))) + + +;;; 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 diff --git a/lisp/acdw.el b/lisp/acdw.el index 1c6f826..f972d08 100644 --- a/lisp/acdw.el +++ b/lisp/acdw.el @@ -28,33 +28,6 @@ the filesystem, unless INHIBIT-MKDIR is non-nil." (make-directory (file-name-directory file-name) :parents)) file-name)))) -;;; Convenience macros - -(defun eval-after-init (fn) - "Evaluate FN after inititation, or now if Emacs is initialized. -FN is called with no arguments." - (if after-init-time - (funcall fn) - (add-hook 'after-init-hook fn))) - -(defmacro eval-after (features &rest body) - "Evaluate BODY, but only after loading FEATURES. -FEATURES can be an atom or a list; as an atom it works like -`with-eval-after-load'. The special feature `init' will evaluate -BODY after Emacs is finished initializing." - (declare (indent 1) - (debug (form def-body))) - (if (eq features 'init) - `(eval-after-init (lambda () ,@body)) - (unless (listp features) - (setq features (list features))) - (if (null features) - (macroexp-progn body) - (let* ((this (car features)) - (rest (cdr features))) - `(with-eval-after-load ',this - (eval-after ,rest ,@body)))))) - ;;; Convenience functions (defun define-keys (maps &rest keydefs) diff --git a/lisp/yoke.el b/lisp/yoke.el index 2673e5e..4f40869 100644 --- a/lisp/yoke.el +++ b/lisp/yoke.el @@ -32,7 +32,7 @@ directory created." (message "Downloading %S... done" repo)) dir)) -(defun yoke-lasso (pkg repo) +(defun yoke-lasso (pkg repo &optional load-path) "Add PKG to `load-path' so it can be used. If PKG is not installed, install it from REPO. Packages will be installed to `yoke-dir'." @@ -40,7 +40,8 @@ installed to `yoke-dir'." (yoke-git repo dir) (cond ((file-exists-p dir) - (add-to-list 'load-path dir) + (when (or load-path dir) + (add-to-list 'load-path (expand-file-name (or load-path dir)))) ;; This bit is stolen from `straight'. (eval-and-compile (require 'autoload)) (let ((generated-autoload-file @@ -91,6 +92,31 @@ Similar-ish to `plist-get', but works on non-proper plists." (setq list (cdr list))) (reverse r))) +(defun eval-after-init (fn) + "Evaluate FN after inititation, or now if Emacs is initialized. +FN is called with no arguments." + (if after-init-time + (funcall fn) + (add-hook 'after-init-hook fn))) + +(defmacro eval-after (features &rest body) + "Evaluate BODY, but only after loading FEATURES. +FEATURES can be an atom or a list; as an atom it works like +`with-eval-after-load'. The special feature `init' will evaluate +BODY after Emacs is finished initializing." + (declare (indent 1) + (debug (form def-body))) + (if (eq features 'init) + `(eval-after-init (lambda () ,@body)) + (unless (listp features) + (setq features (list features))) + (if (null features) + (macroexp-progn body) + (let* ((this (car features)) + (rest (cdr features))) + `(with-eval-after-load ',this + (eval-after ,rest ,@body)))))) + (defun yoke-pkg-name (pkg) (intern (format "yoke:%s" pkg))) @@ -98,28 +124,36 @@ Similar-ish to `plist-get', but works on non-proper plists." &optional repo &body body &key - requires ; :requires ((PKG REPO)...) - dest ; :dest DESTINATION + after ; :after (FEATURE...) + depends ; :depends ((PKG REPO)...) + load ; :load DIRECTORY (when t whenp) ; :when PREDICATE (unless nil unlessp) ; :unless PREDICATE &allow-other-keys) "Yoke a PKG into your Emacs session." (declare (indent defun)) - (let ((name (yoke-pkg-name pkg))) + (let ((name (yoke-pkg-name pkg)) + (body (delete2 body + :depends :when :unless :after :load))) `(cl-block ,name (condition-case e (let ((*yoke-name* ',name) (*yoke-repo* ,repo) (*yoke-dest* ,(when repo `(yoke-repo-dir ',pkg ,repo)))) - ,@(list (cond - ((and whenp unlessp) - `(when (or (not ,when) ,unless) - (cl-return-from ,name nil))) - (whenp `(unless ,when (cl-return-from ,name nil))) - (unlessp `(when ,unless (cl-return-from ,name nil))))) - ,@(cl-loop for (pkg repo) in requires - collect `(or (yoke-lasso ',pkg ,repo) + ,@(cond + ((and whenp unlessp) + `((when (or (not ,when) ,unless) + (cl-return-from ,name nil)))) + (whenp `((unless ,when (cl-return-from ,name nil)))) + (unlessp `((when ,unless (cl-return-from ,name nil))))) + ,@(cl-loop for (pkg* repo* load-path*) in depends + collect `(or (yoke-lasso ',pkg* ,repo* ,load-path*) (cl-return-from ,name nil))) - ,@(when repo `((yoke-lasso ',pkg ,repo))) - ,@(delete2 body :requires :when :unless)) - (t (message "%s: %S" ',name e)))))) + ,@(cond + (repo `((yoke-lasso ',pkg ,repo ,load))) + (load `((add-to-list 'load-path ,load)))) + ,@(if after + `((eval-after ,after ,@body)) + body)) + (:success ',pkg) + (t (message "%s: %s" ',name e)))))) -- cgit 1.4.1-21-gabe81