;;; persist-state.el --- Persist Emacs state regularly -*- lexical-binding: t; -*- ;; Copyright (C) 2023 Bram Schoenmakers ;; Author: Bram Schoenmakers ;; Maintainer: Bram Schoenmakers ;; Created: 05 May 2023 ;; Package-Version: 0.1 ;; Keywords: convenience ;; URL: https://codeberg.org/bram85/emacs-persist-state.git ;; This file is not part of GNU Emacs. ;; MIT License ;; Copyright (c) 2023 Bram Schoenmakers ;; Permission is hereby granted, free of charge, to any person obtaining a copy ;; of this software and associated documentation files (the "Software"), to deal ;; in the Software without restriction, including without limitation the rights ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ;; copies of the Software, and to permit persons to whom the Software is ;; furnished to do so, subject to the following conditions: ;; The above copyright notice and this permission notice shall be included in all ;; copies or substantial portions of the Software. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ;; SOFTWARE. ;;; Commentary: ;; See README.org. ;;; Code: (defgroup persist-state nil "Persist the Emacs state at regular intervals." :group 'convenience) (defcustom persist-state-saving-functions nil "A list of functions that should be executed as part of saving state." :type '(repeat function) :group 'persist-state) (defvar persist-state--save-state-timer nil "This variable holds the timer object to trigger regular saves.") (defcustom persist-state-save-interval (* 15 60) "Interval (in seconds) to persist state." :type '(integer) :group 'persist-state) (defcustom persist-state-wait-idle 5 "When it's time to save, wait for this amount of idle time (in seconds)." :type '(integer) :group 'persist-state) (defvar persist-state-supported-packages-alist `((bookmark . (:function (lambda () (when bookmark-save-flag (bookmark-save))))) (desktop . (:function (lambda () (when desktop-save-mode (desktop-save (car desktop-path) nil t))))) (em-hist . (:function eshell-save-some-history :label "Eshell history")) (prescient . (:function (lambda () (when prescient-persist-mode (prescient--save))) :label "Prescient.el" :url "https://github.com/radian-software/prescient.el")) (recentf . (:function recentf-save-list)) (savehist . (:function (lambda () (when savehist-mode (savehist-autosave)))))) "A list of packages supported by persist-state. Each package is a cons cell with the package name and a plist with: - :function (mandatory): function to call to save state; - :label (optional): a readable package name (for the README); - :url (optional): URL to the package.") (defun persist-state--regularly-run-on-idle (interval idle-seconds f &rest args) "Run function F with ARGS every INTERVAL seconds, plus IDLE-SECONDS." (run-with-timer interval interval (lambda () (run-with-idle-timer idle-seconds nil (lambda () (apply f args)))))) (defun persist-state--save-state () "Save state but only when the user was active recently." (when (< (float-time (current-idle-time)) persist-state-save-interval) (mapc #'funcall persist-state-saving-functions))) (defun persist-state--enable-packages () "Enables all supported packages. If a package has no arguments (no `:args' attribute, the function is added as-is, otherwise it's wrapped in a lambda performing an `apply' call.)" (mapc (lambda (package) (with-eval-after-load (car package) (let ((attrs (cdr package))) (add-to-list 'persist-state-saving-functions (plist-get attrs :function))))) persist-state-supported-packages-alist)) (defun persist-state--enable () "Start saving the Emacs state at the configured interval." (interactive) (persist-state--enable-packages) ;; only start the timer once (when (null (timerp persist-state--save-state-timer)) (setq persist-state--save-state-timer (persist-state--regularly-run-on-idle persist-state-save-interval persist-state-wait-idle #'persist-state--save-state)))) (defun persist-state--disable () "Stop saving the Emacs state." (interactive) (when (timerp persist-state--save-state-timer) (cancel-timer persist-state--save-state-timer))) ;;;###autoload (define-minor-mode persist-state-mode "Create minor mode to enable/disable saving state." :global t :lighter " Persist" (cond (persist-state-mode (persist-state--enable)) (t (persist-state--disable)))) (provide 'persist-state) ;;; persist-state.el ends here