From 3b6e46ef00d0195ec6f6bbb9f257fc1460d2c414 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Thu, 28 Nov 2024 16:08:30 -0500 Subject: [PATCH] feat(lib): move backports to doom-compat.el Moves backports to new doom-compat library. Also rolls out defbackport! calls, because it's unnecessary boilerplate. --- lisp/doom-compat.el | 113 ++++++++++++++++++++++++++++++++++++++++++++ lisp/doom-lib.el | 105 ++-------------------------------------- 2 files changed, 116 insertions(+), 102 deletions(-) create mode 100644 lisp/doom-compat.el diff --git a/lisp/doom-compat.el b/lisp/doom-compat.el new file mode 100644 index 000000000..fe9e5edb7 --- /dev/null +++ b/lisp/doom-compat.el @@ -0,0 +1,113 @@ +;;; lisp/doom-compat.el -*- lexical-binding: t; -*- +;;; Commentary: +;; +;; This file backports functions and variables from future versions of Emacs (so +;; Emacs 27.x users can enjoy them). Its goal is to support between Emacs 27.x +;; and 30.x. +;; +;;; Code: + +;;; From Emacs 28+ +;; `format-spec' wasn't autoloaded until 28.1 +(unless (fboundp 'format-spec) + (autoload 'format-spec "format-spec")) + +;; Introduced in Emacs 28.1 +(unless (fboundp 'ensure-list) + (defun ensure-list (object) + "Return OBJECT as a list. +If OBJECT is already a list, return OBJECT itself. If it's not a list, return a +one-element list containing OBJECT." + (declare (pure t) (side-effect-free t)) + (if (listp object) object (list object)))) + +;; Introduced in Emacs 28.1 +(unless (fboundp 'always) + (defun always (&rest _args) + "Do nothing and return t. +This function accepts any number of ARGUMENTS, but ignores them. Also see +`ignore'." + t)) + +;; Introduced in Emacs 28.1 +(unless (fboundp 'file-name-concat) + (defun file-name-concat (directory &rest components) + "Append COMPONENTS to DIRECTORY and return the resulting string. + +Elements in COMPONENTS must be a string or nil. +DIRECTORY or the non-final elements in COMPONENTS may or may not end +with a slash -- if they don't end with a slash, a slash will be +inserted before contatenating." + (mapconcat + #'identity + (cl-loop for str in (cons directory components) + if (and str (/= 0 (length str)) + (if (string-suffix-p "/" str) + (substring str 0 -1) + str)) + collect it) + "/"))) + +;; Introduced in Emacs 28.1 +(unless (fboundp 'with-environment-variables) + (defmacro with-environment-variables (variables &rest body) + "Set VARIABLES in the environment and execute BODY. +VARIABLES is a list of variable settings of the form (VAR VALUE), +where VAR is the name of the variable (a string) and VALUE +is its value (also a string). + +The previous values will be restored upon exit." + (declare (indent 1) (debug (sexp body))) + (unless (consp variables) + (error "Invalid VARIABLES: %s" variables)) + `(let ((process-environment (copy-sequence process-environment))) + ,@(cl-loop for var in variables + collect `(setenv ,(car var) ,(cadr var))) + ,@body))) + +;; Introduced in Emacs 28.1 +(unless (fboundp 'file-name-with-extension) + (defun file-name-with-extension (filename extension) + "Return FILENAME modified to have the specified EXTENSION. +The extension (in a file name) is the part that begins with the last \".\". +This function removes any existing extension from FILENAME, and then +appends EXTENSION to it. + +EXTENSION may include the leading dot; if it doesn't, this function +will provide it. + +It is an error if FILENAME or EXTENSION is empty, or if FILENAME +is in the form of a directory name according to `directory-name-p'. + +See also `file-name-sans-extension'." + (let ((extn (string-trim-left extension "[.]"))) + (cond ((string-empty-p filename) + (error "Empty filename")) + ((string-empty-p extn) + (error "Malformed extension: %s" extension)) + ((directory-name-p filename) + (error "Filename is a directory: %s" filename)) + ((concat (file-name-sans-extension filename) "." extn)))))) + + +;;; From Emacs 29+ +;; Introduced in Emacs 29+ +(unless (fboundp 'with-memoization) + (defmacro with-memoization (place &rest code) + "Return the value of CODE and stash it in PLACE. +If PLACE's value is non-nil, then don't bother evaluating CODE +and return the value found in PLACE instead." + (declare (indent 1) (debug (gv-place body))) + (gv-letplace (getter setter) place + `(or ,getter + ,(macroexp-let2 nil val (macroexp-progn code) + `(progn + ,(funcall setter val) + ,val)))))) + +;; Introduced in emacs-mirror/emacs@f117b5df4dc6 +(unless (fboundp 'bol) (defalias 'bol #'line-beginning-position)) +(unless (fboundp 'eol) (defalias 'eol #'line-end-position)) + +(provide 'doom-compat) +;;; doom-compat.el ends here diff --git a/lisp/doom-lib.el b/lisp/doom-lib.el index 0fb4dda00..474b64333 100644 --- a/lisp/doom-lib.el +++ b/lisp/doom-lib.el @@ -2,6 +2,9 @@ ;;; Commentary: ;;; Code: +(when (< emacs-major-version 30) + (require 'doom-compat)) + ;;; Custom error types (define-error 'doom-error "An unexpected Doom error") (dolist (type '((doom-font-error "Could not find a font on your system" doom-error) @@ -1014,108 +1017,6 @@ testing advice (when combined with `rotate-text'). (advice-remove target #',symbol))))) -;; -;;; Backports - -(defmacro defbackport! (type symbol &rest body) - "Backport a function/macro/alias from later versions of Emacs." - (declare (indent defun) (doc-string 4)) - (unless (fboundp (doom-unquote symbol)) - `(,type ,symbol ,@body))) - -;; `format-spec' wasn't autoloaded until 28.1 -(defbackport! autoload 'format-spec "format-spec") - -;; Introduced in Emacs 28.1 -(defbackport! defun ensure-list (object) - "Return OBJECT as a list. -If OBJECT is already a list, return OBJECT itself. If it's -not a list, return a one-element list containing OBJECT." - (declare (pure t) (side-effect-free t)) - (if (listp object) object (list object))) - -;; Introduced in Emacs 28.1 -(defbackport! defun always (&rest _args) - "Do nothing and return t. -This function accepts any number of ARGUMENTS, but ignores them. -Also see `ignore'." - t) - -;; Introduced in Emacs 28.1 -(defbackport! defun file-name-concat (directory &rest components) - "Append COMPONENTS to DIRECTORY and return the resulting string. - -Elements in COMPONENTS must be a string or nil. -DIRECTORY or the non-final elements in COMPONENTS may or may not end -with a slash -- if they don't end with a slash, a slash will be -inserted before contatenating." - (mapconcat - #'identity - (cl-loop for str in (cons directory components) - if (and str (/= 0 (length str)) - (if (string-suffix-p "/" str) - (substring str 0 -1) - str)) - collect it) - "/")) - -;; Introduced in Emacs 28.1 -(defbackport! defmacro with-environment-variables (variables &rest body) - "Set VARIABLES in the environment and execute BODY. -VARIABLES is a list of variable settings of the form (VAR VALUE), -where VAR is the name of the variable (a string) and VALUE -is its value (also a string). - -The previous values will be restored upon exit." - (declare (indent 1) (debug (sexp body))) - (unless (consp variables) - (error "Invalid VARIABLES: %s" variables)) - `(let ((process-environment (copy-sequence process-environment))) - ,@(cl-loop for var in variables - collect `(setenv ,(car var) ,(cadr var))) - ,@body)) - -;; Introduced in Emacs 28.1 -(defbackport! defun file-name-with-extension (filename extension) - "Return FILENAME modified to have the specified EXTENSION. -The extension (in a file name) is the part that begins with the last \".\". -This function removes any existing extension from FILENAME, and then -appends EXTENSION to it. - -EXTENSION may include the leading dot; if it doesn't, this function -will provide it. - -It is an error if FILENAME or EXTENSION is empty, or if FILENAME -is in the form of a directory name according to `directory-name-p'. - -See also `file-name-sans-extension'." - (let ((extn (string-trim-left extension "[.]"))) - (cond ((string-empty-p filename) - (error "Empty filename")) - ((string-empty-p extn) - (error "Malformed extension: %s" extension)) - ((directory-name-p filename) - (error "Filename is a directory: %s" filename)) - ((concat (file-name-sans-extension filename) "." extn))))) - -;; Introduced in Emacs 29+ -(defbackport! defmacro with-memoization (place &rest code) - "Return the value of CODE and stash it in PLACE. -If PLACE's value is non-nil, then don't bother evaluating CODE -and return the value found in PLACE instead." - (declare (indent 1) (debug (gv-place body))) - (gv-letplace (getter setter) place - `(or ,getter - ,(macroexp-let2 nil val (macroexp-progn code) - `(progn - ,(funcall setter val) - ,val))))) - -;; Introduced in Emacs 29+ (emacs-mirror/emacs@f117b5df4dc6) -(defbackport! defalias 'bol #'line-beginning-position) -(defbackport! defalias 'eol #'line-end-position) - - ;;; Types (cl-defstruct doom-module