├── .github └── FUNDING.yml ├── .gitignore ├── Makefile ├── README.md └── psession.el /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: emacs-helm 3 | custom: https://www.patreon.com/user?u=86324343 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | psession-autoloads.el 2 | psession.elc 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # makefile for psession. 2 | 3 | # Author: Thierry Volpiatto. 4 | # Copyright (C) 2018, Thierry Volpiatto, all rights reserved. 5 | 6 | ## This file is NOT part of GNU Emacs 7 | ## 8 | ## License 9 | ## 10 | ## This program is free software; you can redistribute it and/or modify 11 | ## it under the terms of the GNU General Public License as published by 12 | ## the Free Software Foundation; either version 3, or (at your option) 13 | ## any later version. 14 | ## 15 | ## This program is distributed in the hope that it will be useful, 16 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ## GNU General Public License for more details. 19 | ## 20 | ## You should have received a copy of the GNU General Public License 21 | ## along with this program; see the file COPYING. If not, write to 22 | ## the Free Software Foundation, Inc., 51 Franklin Street, Fifth 23 | ## Floor, Boston, MA 02110-1301, USA. 24 | 25 | # Emacs invocation 26 | EMACS_COMMAND := emacs 27 | 28 | EMACS := $(EMACS_COMMAND) -q -batch 29 | 30 | EVAL := $(EMACS) --eval 31 | 32 | PKGDIR := . 33 | 34 | # Additional emacs loadpath 35 | LOADPATH := -L $(PKGDIR) 36 | ELPA_DIR = $(HOME)/.emacs.d/elpa 37 | ASYNC_ELPA_DIR = $(shell \ 38 | test -d $(ELPA_DIR) && \ 39 | find -L $(ELPA_DIR) -maxdepth 1 -regex '.*/async-[.0-9]*' 2> /dev/null | \ 40 | sort | tail -n 1) 41 | ifneq "$(ASYNC_ELPA_DIR)" "" 42 | LOADPATH += -L $(ASYNC_ELPA_DIR) 43 | endif 44 | 45 | # Files to compile 46 | EL := psession.el 47 | 48 | # Compiled files 49 | ELC := $(EL:.el=.elc) 50 | 51 | 52 | .PHONY: clean autoloads batch-compile install uninstall 53 | 54 | all: clean autoloads batch-compile 55 | 56 | $(ELC): %.elc: %.el 57 | $(EMACS) $(LOADPATH) -f batch-byte-compile $< 58 | 59 | # Compile needed files 60 | compile: $(ELC) 61 | 62 | # Compile all files at once 63 | batch-compile: 64 | $(EMACS) $(LOADPATH) -f batch-byte-compile $(EL) 65 | 66 | # Remove all generated files 67 | clean: 68 | rm -f $(ELC) 69 | 70 | # Make autoloads file 71 | autoloads: 72 | $(EVAL) "(progn (setq generated-autoload-file (expand-file-name \"psession-autoloads.el\" \"$(PKGDIR)\")) \ 73 | (setq backup-inhibited t) (update-directory-autoloads \"$(PKGDIR)\"))" 74 | 75 | PREFIX=/usr/local/share/ 76 | DESTDIR=${PREFIX}emacs/site-lisp/psession/ 77 | install: 78 | test -d ${DESTDIR} || mkdir ${DESTDIR} 79 | cp -vf *.el $(DESTDIR) 80 | cp -vf *.elc $(DESTDIR) 81 | cp -vf psession-autoloads.el $(DESTDIR) 82 | 83 | uninstall: 84 | rm -vf ${DESTDIR}*.elc 85 | rm -vf ${DESTDIR}*.el 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | psession 2 | ======== 3 | 4 | # Description 5 | Yet another package for emacs persistent sessions 6 | 7 | # Commentary: 8 | 9 | Less featured than other alternatives, but do it faster with a minimal configuration. 10 | All objects are saved individually in compiled files. 11 | 12 | # Features: 13 | 14 | - Save and restore all your buffers 15 | - Save and restore your last window configuration 16 | - Save and restore value of any vars 17 | - Save automatically history variables (replacement of savehist-mode) 18 | - Save and restore registers except windows register 19 | (you can save your windows configs though with M-x psession-save-winconf) 20 | - Autosave your session 21 | 22 | Saving is done when quitting emacs (run in kill-emacs-hook), however you can save at regular time 23 | with `psession-auto-save` set to a non nil value if you want to have your session saved 24 | with your recentest changes in case emacs crashes. 25 | 26 | # Install: 27 | 28 | Add "psession.el" to `load-path` (not needed when installed from Emacs packaging system) 29 | 30 | Customize at least `psession-object-to-save-alist`, don't add here minibuffer history variables, 31 | instead enable `psession-savehist-mode` which will add these variables automatically. 32 | 33 | Add to init file: 34 | 35 | If installed from git 36 | 37 | (require 'psession) 38 | (psession-mode 1) 39 | 40 | Otherwise (Melpa install) only `(psession-mode 1)` is needed. 41 | 42 | For saving minibuffer history, use `(psession-savehist-mode 1)` as a replacement of `savehist-mode`. 43 | 44 | If you want to save periodically (autosave) your emacs session, add `(psession-autosave-mode 1)` to your init file. 45 | -------------------------------------------------------------------------------- /psession.el: -------------------------------------------------------------------------------- 1 | ;;; psession.el --- Persistent save of elisp objects. -*- lexical-binding: t -*- 2 | 3 | ;; Author: Thierry Volpiatto 4 | ;; Copyright (C) 2010~2023 Thierry Volpiatto, all rights reserved. 5 | ;; X-URL: https://github.com/thierryvolpiatto/psession 6 | 7 | ;; Compatibility: GNU Emacs 24.1+ 8 | ;; Package-Requires: ((emacs "24") (cl-lib "0.5") (async "1.9.3")) 9 | ;; Keywords: psession, persistent, save, session 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | 25 | ;;; Code: 26 | 27 | (eval-when-compile (require 'cl-lib)) 28 | (require 'async) 29 | (require 'frameset) 30 | 31 | (defvar dired-buffers) 32 | (defvar helm-comp-read-use-marked) 33 | (defvar print-symbols-bare) 34 | (defvar helm-comp-read-use-marked) 35 | 36 | 37 | (defgroup psession nil 38 | "Persistent sessions." 39 | :group 'frames) 40 | 41 | (defcustom psession-elisp-objects-default-directory 42 | (locate-user-emacs-file "elisp-objects/") 43 | "The directory where lisp objects will be stored." 44 | :group 'psession 45 | :type 'string) 46 | 47 | (defcustom psession-object-to-save-alist 48 | '((extended-command-history . "extended-command-history.el") 49 | (helm-external-command-history . "helm-external-command-history.el") 50 | (helm-surfraw-engines-history . "helm-surfraw-engines-history.el") 51 | (psession--save-buffers-alist . "psession-save-buffers-alist.el") 52 | (helm-ff-history . "helm-ff-history.el") 53 | (helm-browse-project-history . "helm-browse-project-history.el") 54 | (regexp-search-ring . "regexp-search-ring.el") 55 | (search-ring . "search-ring.el") 56 | (file-name-history . "file-name-history.el") 57 | (kill-ring . "kill-ring.el") 58 | (kill-ring-yank-pointer . "kill-ring-yank-pointer.el") 59 | (register-alist . "register-alist.el") 60 | (psession--winconf-alist . "psession-winconf-alist.el") 61 | (psession--selected-frame-parameters . "psession-selected-frame-parameters.el")) 62 | "Alist of vars to save persistently. 63 | It is composed of (var_name . \"var_name.el\"). 64 | Where \"var_name.el\" is the file where to save value of var_name. 65 | 66 | These variables are saved when `psession-mode' is enabled, you don't 67 | have to add here the `minibuffer-history' variables, instead enable 68 | `psession-savehist-mode' as a replacement of `savehist-mode'." 69 | :group 'psession 70 | :type '(alist :key-type symbol :value-type string)) 71 | 72 | (defcustom psession-save-buffers-unwanted-buffers-regexp 73 | "\\(\\.org\\|diary\\|\\.jpg\\|\\.png\\|\\*image-native-display\\*\\)$" 74 | "Regexp matching buffers you don't want to save." 75 | :group 'psession 76 | :type 'string) 77 | 78 | (defcustom psession-auto-save-delay 300 79 | "Delay in seconds to auto-save emacs session." 80 | :group 'psession 81 | :type 'integer) 82 | 83 | (defcustom psession-savehist-ignored-variables nil 84 | "List of `minibuffer-history' variables to not save." 85 | :group 'psession 86 | :type '(repeat symbol)) 87 | 88 | 89 | ;;;###autoload 90 | (defun psession-make-persistent-variable (var &optional save) 91 | "Make symbol variable VAR persistent with psession. 92 | 93 | Do not make `minibuffer-history' variables persistent from here, 94 | enable instead `psession-savehist-mode'. 95 | 96 | Variable VAR is added to `psession-object-to-save-alist'. 97 | 98 | When used interactively or SAVE is non nil, save VAR in 99 | `psession-object-to-save-alist' with customize. 100 | 101 | This function is meant to be used interactively, but 102 | if called from elisp in e.g. -your init file- you don't need to specify 103 | SAVE arg." 104 | (interactive (list (intern 105 | (completing-read "Make persistent variable: " 106 | obarray 107 | #'boundp 108 | t nil nil (thing-at-point 'symbol))) 109 | "\np")) 110 | (cl-assert (and var (boundp var))) 111 | (cl-pushnew (cons var (format "%s.el" var)) 112 | psession-object-to-save-alist 113 | :test 'equal) 114 | (when save ; Interactive. 115 | (customize-save-variable 'psession-object-to-save-alist 116 | psession-object-to-save-alist))) 117 | 118 | ;;;###autoload 119 | (defun psession-remove-persistent-variables (vars &optional save) 120 | "Make variables VARS no more persistent. 121 | 122 | When used interactively or SAVE is non nil, remove VAR from 123 | `psession-object-to-save-alist' persistently." 124 | (interactive (list (if (bound-and-true-p helm-mode) 125 | (let ((helm-comp-read-use-marked t)) 126 | (completing-read 127 | "Remove persistent variable(s): " 128 | (mapcar #'car psession-object-to-save-alist))) 129 | (completing-read-multiple 130 | "Remove persistent variable(s): " 131 | (mapcar #'car psession-object-to-save-alist))) 132 | "\np")) 133 | (cl-loop for v in vars 134 | for var = (if (stringp v) (intern v) v) 135 | do (psession-remove-persistent-variable var)) 136 | (when save 137 | (customize-save-variable 'psession-object-to-save-alist 138 | psession-object-to-save-alist))) 139 | 140 | (defun psession-remove-persistent-variable (var &optional save) 141 | "Make variable VAR no more persistent. 142 | 143 | When SAVE is no nil, remove VAR from `psession-object-to-save-alist' 144 | persistently." 145 | (let ((file (expand-file-name 146 | (format "%s.elc" var) 147 | psession-elisp-objects-default-directory))) 148 | (when (file-exists-p file) 149 | (delete-file file)) 150 | (when (assq var psession-object-to-save-alist) 151 | (setq psession-object-to-save-alist 152 | (delete (assq var psession-object-to-save-alist) 153 | psession-object-to-save-alist))) 154 | (when save 155 | (customize-save-variable 'psession-object-to-save-alist 156 | psession-object-to-save-alist)))) 157 | 158 | ;;; The main function to save objects to byte compiled file. 159 | ;; 160 | ;; Each object have its own compiled file. 161 | (defun psession--dump-object-to-file (obj file) 162 | "Save symbol object OBJ to the byte compiled version of FILE. 163 | OBJ can be any Lisp object, list, hash-table, etc... 164 | Window configurations and markers are not supported. 165 | FILE must be an elisp file with ext \"*.el\" (NOT \"*.elc\"). 166 | Loading the *.elc file will return the object. 167 | That may not work with Emacs versions <=23.1 for hash tables." 168 | (require 'cl-lib) ; Be sure we use the CL version of `eval-when-compile'. 169 | (cl-assert (not (file-exists-p file)) nil 170 | (format "dump-object-to-file: File `%s' already exists, please remove it." file)) 171 | (unwind-protect 172 | (let ((print-length nil) 173 | (print-level nil) 174 | (print-circle t) 175 | (print-escape-nonascii t) 176 | (print-escape-multibyte t) 177 | ;; No symbol's position. 178 | (print-symbols-bare t)) 179 | (with-temp-file file 180 | ;; Fix Emacs-30 lexbind warnings. 181 | (insert ";;; -*- lexical-binding: t -*-\n") 182 | (prin1 `(setq-default ,obj (eval-when-compile ,obj)) (current-buffer))) 183 | (byte-compile-file file) 184 | (message "`%s' dumped to %sc" obj file)) 185 | (delete-file file))) 186 | 187 | ;;; Objects (variables to save) 188 | ;; 189 | ;; 190 | (defun psession--dump-object-to-file-save-alist (&optional skip-props) 191 | (when psession-object-to-save-alist 192 | ;; Ensure `psession-elisp-objects-default-directory' is clean. 193 | (psession-cleanup-dir) 194 | (cl-loop for (o . f) in psession-object-to-save-alist 195 | for abs = (expand-file-name f psession-elisp-objects-default-directory) 196 | for compfile = (concat abs "c") 197 | ;; Registers and kill-ring are treated specially. 198 | do 199 | (cond ((and (eq o 'register-alist) 200 | (symbol-value o)) 201 | (psession--dump-object-save-register-alist f skip-props)) 202 | ;; Delete saved compiled file with 203 | ;; nil value to avoid restoring old non nil value later. 204 | ((and (boundp o) (null (symbol-value o)) 205 | (file-exists-p compfile)) 206 | (delete-file compfile)) 207 | ((and (boundp o) (symbol-value o)) 208 | (psession--dump-object-no-properties o abs skip-props)))))) 209 | 210 | (defun psession-cleanup-dir (&optional arg) 211 | "Delete \"*.el\" files in `psession-elisp-objects-default-directory'. 212 | When ARG is non nil (called interactively) ask before deleting. " 213 | (interactive "p") 214 | (let ((files (directory-files psession-elisp-objects-default-directory t "\\.el$"))) 215 | (when files 216 | ;; When not interactive, delete files inconditionally (bug#18). 217 | (if (or (null arg) 218 | (y-or-n-p (format "%s is not clean, cleanup ? " 219 | psession-elisp-objects-default-directory))) 220 | (cl-loop for f in files 221 | do (delete-file f)) 222 | (error "Psession aborted, *.el files found in '%s' please remove them" 223 | psession-elisp-objects-default-directory))))) 224 | 225 | (cl-defun psession--restore-objects-from-directory 226 | (&optional (dir psession-elisp-objects-default-directory)) 227 | (let ((file-list (directory-files dir t "\\.elc?")) 228 | (time (current-time))) 229 | ;; If system or Emacs crash we may still have some *.el files 230 | ;; around, if so delete them (rare but may happen). 231 | (cl-loop for file in file-list 232 | if (and file (string-match "\\.elc\\'" file)) 233 | do (load file) 234 | else do (delete-file file)) 235 | (message "Psession restored objects in %.2f seconds" 236 | (float-time (time-subtract (current-time) time))))) 237 | 238 | (defun psession--purecopy (object) 239 | (cond ((stringp object) 240 | (substring-no-properties object)) 241 | ((consp object) 242 | (cl-loop for elm in object 243 | ;; A string. 244 | if (stringp elm) 245 | collect (substring-no-properties elm) 246 | else 247 | ;; Proper lists. 248 | if (and (consp elm) (null (cdr (last elm)))) 249 | collect (psession--purecopy elm) 250 | else 251 | ;; Dotted lists. 252 | ;; We handle here only dotted list where car and cdr 253 | ;; are atoms i.e. (x . y) and not (x . (x . y)) or 254 | ;; (x . (x y)) which should fit most cases. 255 | if (and (consp elm) (cdr (last elm))) 256 | collect (let ((key (car elm)) 257 | (val (cdr elm))) 258 | (cons (if (stringp key) 259 | (substring-no-properties key) 260 | key) 261 | (if (stringp val) 262 | (substring-no-properties val) 263 | val))) 264 | else 265 | collect elm)) 266 | (t object))) 267 | 268 | (defun psession--dump-object-no-properties (object file &optional skip-props) 269 | ;; Force not checking properties with SKIP-PROPS. 270 | (let ((value (symbol-value object))) 271 | (unless skip-props 272 | (set object (psession--purecopy value))) 273 | (psession--dump-object-to-file object file))) 274 | 275 | (cl-defun psession--dump-object-save-register-alist (&optional (file "register-alist.el") skip-props) 276 | "Save `register-alist' but only supported objects." 277 | (let ((register-alist (cl-loop for (char . rval) in register-alist 278 | for e27 = (and (fboundp 'registerv-p) 279 | (registerv-p rval)) 280 | for val = (cond (e27 (registerv-data rval)) 281 | ((markerp rval) 282 | (list 'file-query 283 | (buffer-file-name (marker-buffer rval)) 284 | (marker-position rval))) 285 | (t rval)) 286 | unless (or (vectorp val) 287 | (and (consp val) (window-configuration-p (car val))) 288 | (frame-configuration-p val) 289 | (frameset-register-p val)) 290 | collect (cons char (cond ((stringp val) 291 | (substring-no-properties val)) 292 | (t val))))) 293 | (def-file (expand-file-name file psession-elisp-objects-default-directory))) 294 | (psession--dump-object-no-properties 'register-alist def-file skip-props))) 295 | 296 | ;;; Persistents window configs 297 | ;; 298 | ;; 299 | (defconst psession--last-winconf "last_session5247") 300 | (defvar psession--winconf-alist nil) 301 | (defun psession--window-name () 302 | (let (result) 303 | (walk-windows (lambda (w) (cl-pushnew (buffer-name (window-buffer w)) result))) 304 | (mapconcat 'identity result " | "))) 305 | 306 | ;;;###autoload 307 | (defun psession-save-winconf (place) 308 | "Save persistently current window config to PLACE. 309 | Arg PLACE is the key of an entry in `psession--winconf-alist'." 310 | (interactive (list (let ((name (psession--window-name))) 311 | (read-string (format "Place (%s) : " name) nil nil name)))) 312 | (let ((assoc (assoc place psession--winconf-alist)) 313 | (new-conf (list (cons place (window-state-get nil 'writable))))) 314 | (if assoc 315 | (setq psession--winconf-alist (append new-conf 316 | (delete assoc psession--winconf-alist))) 317 | (setq psession--winconf-alist (append new-conf psession--winconf-alist))))) 318 | 319 | (defun psession--restore-winconf-1 (conf &optional window ignore) 320 | (let ((winconf (assoc conf psession--winconf-alist))) 321 | (if winconf 322 | (with-selected-frame (last-nonminibuffer-frame) 323 | (delete-other-windows) 324 | (window-state-put (cdr (assoc conf psession--winconf-alist)) window ignore)) 325 | (user-error "Psession: Invalid window configuration `%s'" conf)))) 326 | 327 | ;;;###autoload 328 | (defun psession-restore-winconf (conf) 329 | "Restore window config CONF. 330 | Arg CONF is an entry in `psession--winconf-alist'." 331 | (interactive (list (completing-read 332 | "WinConfig: " 333 | (sort (mapcar 'car psession--winconf-alist) #'string-lessp)))) 334 | (psession--restore-winconf-1 conf)) 335 | 336 | ;;;###autoload 337 | (defun psession-delete-winconf (conf) 338 | "Delete window config CONF from `psession--winconf-alist'." 339 | (interactive (list (let ((helm-comp-read-use-marked t)) 340 | (completing-read 341 | "WinConfig: " 342 | (sort (mapcar 'car psession--winconf-alist) #'string-lessp))))) 343 | (when (stringp conf) 344 | (setq conf (list conf))) 345 | (cl-loop for cfg in conf 346 | for assoc = (assoc cfg psession--winconf-alist) 347 | do (setq psession--winconf-alist (delete assoc psession--winconf-alist)))) 348 | 349 | (defun psession-save-last-winconf () 350 | (unless (and (boundp 'helm-alive-p) helm-alive-p) 351 | (psession-save-winconf psession--last-winconf))) 352 | 353 | (defun psession-restore-last-winconf () 354 | (when (assoc-default psession--last-winconf psession--winconf-alist) 355 | (run-with-idle-timer 356 | 0.01 nil (lambda () 357 | (psession--restore-winconf-1 psession--last-winconf nil 'safe))))) 358 | 359 | ;;; Tabs and current frame settings 360 | ;; 361 | ;; 362 | (defvar psession--selected-frame-parameters nil) 363 | 364 | (defun psession-save-frame-tabs () 365 | (setq psession--selected-frame-parameters 366 | (and (boundp 'tab-bar-mode) 367 | tab-bar-mode 368 | (frameset-save (list (selected-frame)) 369 | :app 'psession)))) 370 | 371 | (defun psession-restore-frame-tabs () 372 | (when (frameset-valid-p psession--selected-frame-parameters) 373 | (frameset-restore psession--selected-frame-parameters 374 | :reuse-frames t :cleanup-frames t)) 375 | (when (frame-parameter (selected-frame) 'tabs) 376 | (tab-bar-mode 1))) 377 | 378 | 379 | ;;; Persistent-buffers 380 | ;; 381 | ;; 382 | (defun psession--save-some-buffers () 383 | (require 'dired) 384 | (cl-loop with dired-blist = (cl-loop for (_f . b) in dired-buffers 385 | when (buffer-name b) 386 | collect b) 387 | with blist = (append (buffer-list) dired-blist) 388 | for b in blist 389 | for buf-fname = (or (buffer-file-name b) (car (rassoc b dired-buffers))) 390 | for place = (with-current-buffer b (point)) 391 | when (and buf-fname 392 | (not (or (file-remote-p buf-fname) 393 | (and (fboundp 'tramp-archive-file-name-p) 394 | (tramp-archive-file-name-p buf-fname)))) 395 | (not (string-match psession-save-buffers-unwanted-buffers-regexp 396 | buf-fname)) 397 | (file-exists-p buf-fname)) 398 | collect (cons buf-fname place))) 399 | 400 | (defvar psession--save-buffers-alist nil) 401 | (defun psession--dump-some-buffers-to-list () 402 | (setq psession--save-buffers-alist (psession--save-some-buffers))) 403 | 404 | (defun psession--restore-some-buffers () 405 | (when psession--save-buffers-alist 406 | (let* ((max (length psession--save-buffers-alist)) 407 | (progress-reporter (make-progress-reporter "Restoring buffers..." 0 max)) 408 | (time (current-time))) 409 | (cl-loop for (f . p) in psession--save-buffers-alist 410 | for count from 0 411 | do 412 | (with-current-buffer (find-file-noselect f 'nowarn) 413 | (goto-char p) 414 | (push-mark p 'nomsg) 415 | (progress-reporter-update progress-reporter count))) 416 | (progress-reporter-done progress-reporter) 417 | (message "Buffers restored in %.2f seconds" 418 | (float-time (time-subtract (current-time) time)))))) 419 | 420 | 421 | ;;; Save history 422 | ;; 423 | (defvar psession--old-savehist-mode-state nil) 424 | (defun psession-savehist-hook () 425 | (unless (or (eq minibuffer-history-variable t) 426 | (memq minibuffer-history-variable psession-savehist-ignored-variables)) 427 | (cl-pushnew (cons minibuffer-history-variable 428 | (concat (symbol-name minibuffer-history-variable) ".el")) 429 | psession-object-to-save-alist 430 | :test 'equal))) 431 | 432 | ;;;###autoload 433 | (define-minor-mode psession-savehist-mode 434 | "Save minibuffer-history variables persistently." 435 | :global t 436 | (if psession-savehist-mode 437 | (progn 438 | (setq psession--old-savehist-mode-state (if savehist-mode 1 -1)) 439 | (and savehist-mode (savehist-mode -1)) 440 | (add-hook 'minibuffer-setup-hook 'psession-savehist-hook)) 441 | (remove-hook 'minibuffer-setup-hook 'psession-savehist-hook) 442 | (savehist-mode psession--old-savehist-mode-state))) 443 | 444 | 445 | ;;; Auto saving psession 446 | ;; 447 | (defun psession--get-variables-regexp () 448 | (regexp-opt (cl-loop for (k . _v) in 449 | (append psession-object-to-save-alist 450 | (mapcar 451 | 'list 452 | '(psession-elisp-objects-default-directory 453 | psession-savehist-ignored-variables 454 | psession-save-buffers-unwanted-buffers-regexp))) 455 | collect (symbol-name k)))) 456 | 457 | (defun psession-save-all-async () 458 | "Save current emacs session asynchronously." 459 | (message "Psession: auto saving session...") 460 | (psession--dump-some-buffers-to-list) 461 | (async-start 462 | `(lambda () 463 | (add-to-list 'load-path 464 | ,(file-name-directory (locate-library "psession"))) 465 | (require 'psession) 466 | ;; (setq create-lockfiles nil) 467 | ;; Inject variables without properties. 468 | ,(async-inject-variables (format "\\`%s" (psession--get-variables-regexp)) 469 | nil nil 'noprops) 470 | ;; No need to treat properties here it is already done. 471 | (psession--dump-object-to-file-save-alist 'skip-props)) 472 | (lambda (_result) 473 | (message "Psession: auto saving session done")))) 474 | 475 | (defvar psession--auto-save-timer nil) 476 | (defun psession-start-auto-save () 477 | "Start auto-saving emacs session in background." 478 | (setq psession--auto-save-timer 479 | (run-with-idle-timer 480 | psession-auto-save-delay t #'psession-save-all-async))) 481 | 482 | (defun psession-auto-save-cancel-timer () 483 | "Cancel psession auto-saving." 484 | (when psession--auto-save-timer 485 | (cancel-timer psession--auto-save-timer) 486 | (setq psession--auto-save-timer nil))) 487 | 488 | ;;;###autoload 489 | (define-minor-mode psession-autosave-mode 490 | "Auto save emacs session when enabled." 491 | :global t 492 | (if psession-autosave-mode 493 | (psession-start-auto-save) 494 | (psession-auto-save-cancel-timer))) 495 | 496 | ;;;###autoload 497 | (define-minor-mode psession-mode 498 | "Persistent emacs sessions." 499 | :global t 500 | (if psession-mode 501 | (progn 502 | (unless (file-directory-p psession-elisp-objects-default-directory) 503 | (make-directory psession-elisp-objects-default-directory t)) 504 | (add-hook 'kill-emacs-hook 'psession--dump-object-to-file-save-alist) 505 | (add-hook 'emacs-startup-hook 'psession--restore-objects-from-directory) 506 | (add-hook 'kill-emacs-hook 'psession--dump-some-buffers-to-list) 507 | (add-hook 'emacs-startup-hook 'psession--restore-some-buffers 'append) 508 | (add-hook 'kill-emacs-hook 'psession-save-last-winconf) 509 | (add-hook 'emacs-startup-hook 'psession-restore-last-winconf 'append) 510 | (when (fboundp 'tab-bar-mode) 511 | (add-hook 'kill-emacs-hook 'psession-save-frame-tabs) 512 | (add-hook 'emacs-startup-hook 'psession-restore-frame-tabs 'append)) 513 | (add-hook 'kill-emacs-hook 'psession-auto-save-cancel-timer)) 514 | (remove-hook 'kill-emacs-hook 'psession--dump-object-to-file-save-alist) 515 | (remove-hook 'emacs-startup-hook 'psession--restore-objects-from-directory) 516 | (remove-hook 'kill-emacs-hook 'psession--dump-some-buffers-to-list) 517 | (remove-hook 'emacs-startup-hook 'psession--restore-some-buffers) 518 | (remove-hook 'kill-emacs-hook 'psession-save-last-winconf) 519 | (remove-hook 'emacs-startup-hook 'psession-restore-last-winconf) 520 | (when (fboundp 'tab-bar-mode) 521 | (remove-hook 'kill-emacs-hook 'psession-save-frame-tabs) 522 | (remove-hook 'emacs-startup-hook 'psession-restore-frame-tabs)))) 523 | 524 | 525 | (provide 'psession) 526 | 527 | ;;; psession.el ends here 528 | --------------------------------------------------------------------------------