225 lines
7.9 KiB
EmacsLisp
225 lines
7.9 KiB
EmacsLisp
;;; persist.el --- Persist Variables between Emacs Sessions -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2019, 2024, 2025 Free Software Foundation, Inc.
|
|
|
|
;; Author: Phillip Lord <phillip.lord@russet.org.uk>
|
|
;; Maintainer: Joseph Turner <persist-el@breatheoutbreathe.in>
|
|
;; Package-Type: multi
|
|
;; Package-Requires: ((emacs "26.1") (compat "30.0.2.0"))
|
|
;; Version: 0.8
|
|
|
|
;; The contents of this file are subject to the GPL License, Version 3.0.
|
|
|
|
;; This file is not part of Emacs
|
|
|
|
;; This program is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; This package provides variables which persist across sessions.
|
|
|
|
;; The main entry point is `persist-defvar' which behaves like
|
|
;; `defvar' but which persists the variables between session. Variables
|
|
;; are automatically saved when Emacs exits.
|
|
|
|
;; Other useful functions are `persist-save' which saves the variable
|
|
;; immediately, `persist-load' which loads the saved value,
|
|
;; `persist-reset' which resets to the default value.
|
|
|
|
;; Values are stored in a directory in `user-emacs-directory', using
|
|
;; one file per value. This makes it easy to delete or remove unused
|
|
;; variables.
|
|
|
|
;;; Code:
|
|
|
|
(require 'compat)
|
|
|
|
(defvar persist--directory-location
|
|
(locate-user-emacs-file "persist")
|
|
"The location of persist directory.")
|
|
|
|
(defvar persist--symbols nil
|
|
"List of symbols to persist.")
|
|
|
|
(defvar persist-load-hook nil
|
|
"Special hook run on loading a variable.
|
|
|
|
Hook functions are called with two values: the symbol and the
|
|
value it will be set to. If any function returns nil, the
|
|
variable is not set to the value.")
|
|
|
|
(defun persist--file-location (symbol)
|
|
"Return the file name at which SYMBOL does or will persist."
|
|
(expand-file-name
|
|
(symbol-name symbol)
|
|
(or (get symbol 'persist-location)
|
|
persist--directory-location)))
|
|
|
|
(defun persist--defvar-1 (symbol location initvalue)
|
|
"Set symbol up for persistence."
|
|
(when location
|
|
(persist-location symbol location))
|
|
(persist-symbol symbol initvalue)
|
|
(persist-load symbol))
|
|
|
|
(defmacro persist-defvar (symbol initvalue docstring &optional location)
|
|
"Define SYMBOL as a persistent variable and return SYMBOL.
|
|
|
|
This form is nearly equivalent to `defvar', except that the
|
|
variable persists between Emacs sessions. When this form is
|
|
evaluated, the variable's default value is always set to
|
|
INITVALUE.
|
|
|
|
It does not support the optional parameters. Both INITVALUE and
|
|
DOCSTRING need to be given."
|
|
;; We cannot distinguish between calls with initvalue of nil and a
|
|
;; single parameter call. Unfortunately, these two calls have
|
|
;; different semantics -- the single arity shuts up the byte
|
|
;; compiler, but does not define the symbol. So, don't support a
|
|
;; single arity persist-defvar.
|
|
|
|
;; Don't support 2-arity calls either because we are lazy and
|
|
;; because if you want to persist it, you want to doc it.
|
|
(declare (debug (symbolp form stringp &optional form))
|
|
(doc-string 3)
|
|
(indent defun))
|
|
;; Define inside progn so the byte compiler sees defvar
|
|
`(progn
|
|
(defvar ,symbol ,initvalue ,docstring)
|
|
;; `defvar' must stay at top level within `progn'. Pass init
|
|
;; value to `persist--defvar-1' since the `defvar' form may not
|
|
;; set the symbol's value and we don't want to set the
|
|
;; persist-default property to the current value of the symbol.
|
|
;; See bug#75779 for details.
|
|
(persist--defvar-1 ',symbol ,location ,initvalue)
|
|
',symbol))
|
|
|
|
(defun persist-location (symbol directory)
|
|
"Set the directory for persisting the value of symbol.
|
|
|
|
This does not force the loading of value from this directory, so
|
|
to persist a variable, you will normally need to call
|
|
`persist-load' to load a previously saved location."
|
|
(put symbol 'persist-location (expand-file-name directory)))
|
|
|
|
(defun persist-symbol (symbol initvalue)
|
|
"Make SYMBOL a persistent variable.
|
|
|
|
INITVALUE is the value to which SYMBOL will be set if `persist-reset' is
|
|
called. INITVALUE is set for the session and will itself not persist
|
|
across sessions.
|
|
|
|
This does force the loading of value from this directory, so to
|
|
persist a variable, you will normally need to call `persist-load'
|
|
to load a previously saved location."
|
|
(add-to-list 'persist--symbols symbol)
|
|
(put symbol 'persist t)
|
|
(put symbol 'persist-default (persist-copy initvalue)))
|
|
|
|
(defun persist--persistant-p (symbol)
|
|
"Return non-nil if SYMBOL is a persistent variable."
|
|
(get symbol 'persist))
|
|
|
|
(defun persist-save (symbol)
|
|
"Save SYMBOL now.
|
|
|
|
Normally, it should not be necessary to call this explicitly, as
|
|
variables persist automatically when Emacs exits."
|
|
(unless (persist--persistant-p symbol)
|
|
(error (format
|
|
"Symbol %s is not persistent" symbol)))
|
|
(let ((symbol-file-loc (persist--file-location symbol)))
|
|
(if (persist-equal (symbol-value symbol)
|
|
(persist-default symbol))
|
|
(when (file-exists-p symbol-file-loc)
|
|
(delete-file symbol-file-loc))
|
|
(let ((dir-loc
|
|
(file-name-directory symbol-file-loc)))
|
|
(unless (file-exists-p dir-loc)
|
|
(mkdir dir-loc))
|
|
(with-temp-buffer
|
|
(let (print-level
|
|
print-length
|
|
print-quoted
|
|
(print-escape-control-characters t)
|
|
(print-escape-nonascii t)
|
|
(print-circle t))
|
|
(print (symbol-value symbol) (current-buffer)))
|
|
(write-region (point-min) (point-max)
|
|
symbol-file-loc
|
|
nil 'quiet))))))
|
|
|
|
(defun persist-default (symbol)
|
|
"Return the default value for SYMBOL."
|
|
(get symbol 'persist-default))
|
|
|
|
(defun persist-reset (symbol)
|
|
"Set the value of SYMBOL to a copy of the default."
|
|
(set symbol (persist-copy (persist-default symbol))))
|
|
|
|
(defun persist-load (symbol)
|
|
"Load the saved value of SYMBOL."
|
|
(when (file-exists-p (persist--file-location symbol))
|
|
(with-temp-buffer
|
|
(insert-file-contents (persist--file-location symbol))
|
|
(let ((val (read (current-buffer))))
|
|
(when (run-hook-with-args-until-failure 'persist-load-hook
|
|
symbol val)
|
|
(set symbol val))))))
|
|
|
|
(defun persist-unpersist (symbol)
|
|
"Stop the value in SYMBOL from persisting.
|
|
|
|
This does not remove any saved value of SYMBOL."
|
|
(put symbol 'persist nil)
|
|
(setq persist--symbols
|
|
(remove symbol persist--symbols)))
|
|
|
|
(defun persist--save-all ()
|
|
"Save all persistent symbols."
|
|
(mapc 'persist-save persist--symbols))
|
|
|
|
;; Save on kill-emacs-hook anyway
|
|
(add-hook 'kill-emacs-hook
|
|
'persist--save-all)
|
|
|
|
(defun persist-equal (a b)
|
|
"Return non-nil when the values of A and B are equal.
|
|
A and B are compared using `equal' unless they are both hash
|
|
tables. In that case, the following are compared:
|
|
|
|
- hash table count
|
|
- hash table predicate
|
|
- values, using `persist-equal'"
|
|
(if (and (hash-table-p a) (hash-table-p b))
|
|
(and (= (hash-table-count a) (hash-table-count b))
|
|
(eq (hash-table-test a) (hash-table-test b))
|
|
(catch 'done
|
|
(maphash
|
|
(lambda (key a-value)
|
|
(unless (persist-equal a-value (gethash key b (not a-value)))
|
|
(throw 'done nil)))
|
|
a)
|
|
t))
|
|
(equal a b)))
|
|
|
|
(defun persist-copy (obj)
|
|
"Return copy of OBJ."
|
|
(if (hash-table-p obj)
|
|
(copy-hash-table obj)
|
|
(compat-call copy-tree obj t)))
|
|
|
|
(provide 'persist)
|
|
;;; persist.el ends here
|