diff options
author | David Bremner <david@tethera.net> | 2018-07-25 14:48:19 +0800 |
---|---|---|
committer | David Bremner <david@tethera.net> | 2018-07-25 14:48:19 +0800 |
commit | 5c68baeef119390f876a394d65f4f9f0b4b98637 (patch) | |
tree | 547e11eafbc06fbcdf2a86a1d834db8c0953bb95 | |
parent | 7e3ebb2899896b81494b3927ff8af5d03391010b (diff) |
Imported session_2.4b.orig.tar.gz
-rwxr-xr-x | session.el | 1826 |
1 files changed, 1826 insertions, 0 deletions
diff --git a/session.el b/session.el new file mode 100755 index 0000000..0a87755 --- /dev/null +++ b/session.el @@ -0,0 +1,1826 @@ +;;; session.el --- use variables, registers and buffer places across sessions + +;; Copyright 1996, 1997, 1998, 1999, 2001, 2002, 2003, 2010, 2011, 2012, 2015 +;; Free Software Foundation, Inc. +;; +;; Author: Christoph Wedler <wedler@users.sourceforge.net> +;; Version: 2.4b (see also `session-version' below) +;; Keywords: session, session management, desktop, data, tools +;; X-URL: http://emacs-session.sourceforge.net/ + +;; 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 Licence, 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 <http://gnu.org/licences/>. + +;;; Commentary: + +;; When you start Emacs, package Session restores various variables (e.g., +;; input histories) from your last session. It also provides a menu +;; containing recently changed/visited files and restores the places (e.g., +;; point) of such a file when you revisit it. + +;; For details, check <http://emacs-session.sourceforge.net/> or, if you prefer +;; the manual style, the documentation of functions \\[session-save-session] +;; and `session-store-buffer-places'. + +;; Bug fixes, bug reports, improvements, and suggestions for the newest version +;; are strongly appreciated. + +;;; To-do: + +;; One could imagine a combination of desktop.el and session.el. IMHO it is +;; easier to include the remaining features of desktop.el (load some files at +;; startup) into session.el, but desktop.el is already part of Emacs... +;; Anyway, here are some ideas for the combined desktop/session: +;; +;; * Using contexts for buffer positions (idea from bookmark and vc). +;; * Define common code with bookmark to restore buffers from a +;; file-representation (for files, dired, info buffers). +;; * Saving window-configurations? + +;;; Installation, private: + +;; 1. Make sure to use Emacs-20.2, XEmacs-20.2 or higher. +;; 2. Put this file into your load-path, i.e. any directory mentioned in the +;; value of `load-path'. +;; 3. Byte-compile this file. +;; 4. Load this package by M-x load-library RET session RET +;; 5. Start customization with M-x customize-group RET session RET or the +;; menu [Options][Customize...]...[Data][Session]. +;; 6. Toggle the [Session Use Package] option to "in use". +;; 7. Save your customization via [Save for future sessions]. +;; 8. If you use both this package and desktop.el, customize the variable +;; `desktop-globals-to-save' to include only the symbol +;; `desktop-missing-file-warning'. + +;; Remark: adding some code to your ~/.emacs like in previous versions of +;; session.el still works. + +;;; Installation, system- or distribution-wide: + +;; The idea here should be to offer new defaults to your users (like using this +;; package), while allowing them to choose otherwise. This is probably best +;; done by defining a custom theme (you probably add other customizations to +;; the custom theme file as well, custom themes might only work with newer +;; Emacsen): + +;; 1. Like 1-3 in the private installation instruction. +;; 2. Create the autoloads and custom-loads for session.el and make sure that +;; they are loaded at Emacs startup. You can do it manually by adding the +;; code below to your site.start.el. +;; 3. Define a custom theme like `our-custom' by adding a file called +;; "our-custom-theme.el" with the code below to a directory in the +;; load-path. +;; 4. Enable your custom theme by adding the code below to your default.el. +;; 5. Tell your users that they can disable this package by customizing the +;; user option according to 5-7 in the private installation instruction or +;; by setting `inhibit-default-init' to t. + +;; ;; site-start.el, Emacs: +;; (autoload 'session-jump-to-last-change "session" nil t) +;; (autoload 'session-initialize "session" nil t) +;; (eval-after-load "cus-load" +;; '(progn (custom-add-load 'data 'session) +;; (custom-add-load 'session 'session))) + +;; ;; site-start.el, XEmacs: +;; (autoload 'session-jump-to-last-change "session" nil t) +;; (autoload 'session-initialize "session" nil t) +;; (custom-add-load 'data 'session) +;; (custom-add-load 'session 'session) + +;; ;; our-custom-theme.el, Emacs and XEmacs: +;; (deftheme our-custom "Created 2011-01-15.") +;; (custom-theme-set-variables +;; 'our-custom +;; '(session-use-package t nil (session))) +;; (provide-theme 'our-custom) + +;; ;; default.el, Emacs: +;; (enable-theme 'our-custom) + +;; ;; default.el, XEmacs: +;; (require-theme 'our-custom) + +;;; Installation, integration of other packages + +;;(defun my-org-reveal-session-jump () +;; (when (and (eq major-mode 'org-mode) +;; (outline-invisible-p)) +;; (org-reveal))) +;;(add-hook 'session-after-jump-to-last-change-hook 'my-org-reveal-session-jump) + +;;; Code: + +(require 'custom) + +;; General Emacs/XEmacs-compatibility compile-time macros +(eval-when-compile + (require 'cl) + (defmacro cond-emacs-xemacs (&rest args) + (cond-emacs-xemacs-macfn + args "`cond-emacs-xemacs' must return exactly one element")) + (defun cond-emacs-xemacs-macfn (args &optional msg) + (if (atom args) args + (and (eq (car args) :@) (null msg) ; (:@ ...spliced...) + (setq args (cdr args) + msg "(:@ ....) must return exactly one element")) + (let ((ignore (if (string-match "XEmacs" emacs-version) :EMACS :XEMACS)) + (mode :BOTH) code) + (while (consp args) + (if (memq (car args) '(:EMACS :XEMACS :BOTH)) (setq mode (pop args))) + (if (atom args) + (or args (error "Used selector %s without elements" mode)) + (or (eq ignore mode) + (push (cond-emacs-xemacs-macfn (car args)) code)) + (pop args))) + (cond (msg (if (or args (cdr code)) (error msg) (car code))) + ((or (null args) (eq ignore mode)) (nreverse code)) + (t (nconc (nreverse code) args)))))) + ;; Emacs/XEmacs-compatibility `defun': remove interactive "_" for Emacs, use + ;; existing functions when they are `fboundp', provide shortcuts if they are + ;; known to be defined in a specific Emacs branch (for short .elc) + (defmacro defunx (name arglist &rest definition) + (let ((xemacsp (string-match "XEmacs" emacs-version)) reuses first) + (while (memq (setq first (car definition)) + '(:try :emacs-and-try :xemacs-and-try + :emacs-only :xemacs-only)) + (if (memq first (if xemacsp + '(:xemacs-and-try :xemacs-only) + '(:emacs-and-try :emacs-only))) + (setq reuses (cadr definition) + definition nil) + (unless (memq first '(:emacs-only :xemacs-only)) + (push (cadr definition) reuses))) + (setq definition (cddr definition))) + (if (and reuses (symbolp reuses)) + `(defalias ',name ',reuses) + (let* ((docstring (if (stringp (car definition)) (pop definition))) + (spec (and (not xemacsp) + (eq (car-safe (car definition)) 'interactive) + (null (cddar definition)) + (cadar definition)))) + (if (and (stringp spec) + (not (string-equal spec "")) + (eq (aref spec 0) ?_)) + (setq definition + (cons (if (string-equal spec "_") + '(interactive) + `(interactive ,(substring spec 1))) + (cdr definition)))) + (if (null reuses) + `(defun ,name ,arglist ,docstring + ,@(cond-emacs-xemacs-macfn definition)) + ;; no dynamic docstring in this case + `(eval-and-compile ; no warnings in Emacs + (defalias ',name + (cond ,@(mapcar (lambda (func) `((fboundp ',func) ',func)) + (nreverse reuses)) + (t ,(if definition + `(lambda ,arglist ,docstring + ,@(cond-emacs-xemacs-macfn definition)) + 'ignore))))))))))) + +(eval-when-compile + (defvar put-buffer-names-in-file-menu) + (defvar menu-bar-files-menu) + (defvar yank-menu) + (defvar minibuffer-local-ns-map)) + + + +;;;;########################################################################## +;;;; User options, configuration variables +;;;;########################################################################## + + +(defconst session-version "2.4b" + "Current version of package session. +Check <http://emacs-session.sourceforge.net/> for the newest.") + + +;;;=========================================================================== +;;; Customization and initialization +;;;=========================================================================== + +(defgroup session nil + "Use variables, registers and buffer places across sessions." + :group 'data + :link '(emacs-commentary-link "session.el") + :link '(url-link "http://emacs-session.sourceforge.net/") + :prefix "session-") + +(defgroup session-globals nil + "Which variables and registers to save across sessions." + :group 'session + :prefix "session-") + +(defgroup session-places nil + "Which places are stored for which buffers." + :group 'session + :prefix "session-") + +(defgroup session-miscellaneous nil + "Miscellaneous configurations of package session." + :group 'session + :prefix "session-") + +(defcustom session-initialize t + "Whether/what to initialize with function `session-initialize'. +If t, do full initialization. Otherwise, the value should be a list +with element. To enable, include + + * `de-saveplace' to de-install package saveplace (is redundant), + * `session' to load and save the session file, + * `places' to store and use places for files/buffers, + * `keys' to setup the default key and mouse bindings, + * `menus' to setup the menus." + :group 'session-miscellaneous + :type '(choice (const :tag "All" t) + (set :value (de-saveplace session places keys menus) + (const :tag "De-install saveplace" de-saveplace) + (const :tag "Load/Save Session" session) + (const :tag "Store/Use Places" places) + (const :tag "Setup Key/Mouse Bindings" keys) + (const :tag "Setup Menus" menus)))) + + +;;;=========================================================================== +;;; User Options and Configuration: Menu +;;;=========================================================================== + +(defcustom session-menu-max-size 36 + "*Max number of entries which may appear in the session menus." + :group 'session-miscellaneous + :type 'integer) + +(defcustom session-yank-menu-binding + (and (string-match "XEmacs" emacs-version) [(control button3)]) + "*Mouse binding where `session-popup-yank-menu' is bound to." + :group 'session-miscellaneous + :type 'sexp) ; (choice (nil key-binding)) + +(defcustom session-file-menu-max-string + (if (if (boundp 'put-buffer-names-in-file-menu) + put-buffer-names-in-file-menu ; XEmacs + nil) ; Emacs: no buffer names in file menu + (cons 50 20) + 50) + "*Max length of strings in submenus of the File menu. +Value has the form MAX or (MAX . NAME-THRESHOLD). If the second form is +used and the length returned by `buffer-name' is longer than +NAME-THRESHOLD, the maximum length will be shortened accordingly. + +Deprecated: a negative number -MAX stands for (MAX . 0)." + :group 'session-miscellaneous + :type '(choice (cons (integer :tag "Max. length" 50) + (integer :tag "Name threshold" 20)) + (integer 50))) + +(defcustom session-edit-menu-max-string 50 + "*Max length of strings in submenus of the Edit menu. +See also `session-compact-yank-gap-regexp'. + +When running under Emacs, customize `yank-menu-length' instead." + :group 'session-miscellaneous + :type 'integer) + +(defcustom session-compact-yank-gap-regexp "\\(\n\\|[ \t][ \t][ \t]\\)[ \t\n]*" + "*Regexp used when trying to find a gap in a long compact string. +If non-nil, leading and trailing whitespaces are not shown, and we try +to find a gap consisting matched by this regexp if we have to split the +string according to `session-edit-menu-max-string'. + +This variable has no effect when running under Emacs." + :group 'session-miscellaneous + :type 'string) + +(defcustom session-menu-permanent-string " *" + "*Marker for permanent files in menu \"File >> Open...recently changed\". +A file can set as permanent with prefix argument 3 for a command in +`session-kill-buffer-commands'. It can be set as non-permanent with +prefix argument -1." + :group 'session-miscellaneous + :type 'string) + +;; TODO: not quite sure whehter this is needed anymore - and if whether +;; it is the best way to exclude certain files. Other options are: +;; based on directory, file name, mode, calling command +(defcustom session-set-file-name-exclude-regexp + "[/\\]\\.overview\\|[/\\]\\.session\\|News[/\\]" + "*Regexp matching file names not to be stored in `file-name-history'. +This is used by `session-set-file-name-history'. Value nil means, do +not exclude any file." + :group 'session-miscellaneous + :type '(choice (const nil) regexp)) + +(defvar session-menu-accelerator-support + (and (featurep 'menu-accelerator-support) + (fboundp 'submenu-generate-accelerator-spec) + 'submenu-generate-accelerator-spec) + "Function to generate menu accelerators, or nil if not supported.") + +;; calling `abbrev-file-name' on remote files opens the connection! + +(defvar session-abbrev-inhibit-function +;; this will be renamed with the next release (when minimum is +;; Emacs-22.1, jun 2007 and XEmacs 21.4.12, jan 2003) -> only there we have +;; `define-obsolete-variable-alias' + (cond ((fboundp 'file-remote-p) 'file-remote-p) + ;; `file-remote-p' doesn't exist in Emacs < 22.1 + ;; (cond-emacs-xemacs + ;; ;; as opposed to the docstring of `file-remote-p', + ;; ;; `tramp-handle-file-remote-p' does try to open a connection + ;; :EMACS (lambda (file) (find-file-name-handler file 'file-remote-p)) + ;; :XEMACS 'file-remote-p)) + ((fboundp 'efs-ftp-path) 'efs-ftp-path) + ((fboundp 'ange-ftp-ftp-name) 'ange-ftp-ftp-name) + ((fboundp 'ange-ftp-ftp-path) 'ange-ftp-ftp-path)) + "Function used to determine whether to abbreviate file name. +A file name is not abbreviated and no buffer information is shown in the +file menus if this function returns non-nil when called with the file name. +If the value is not a function, it directly determines whether to abbreviate +a file name.") + +(defvar session-directory-sep-char ; directory-sep-char is not set + (if (memq system-type '(ms-dos windows-nt)) ?\\ ?\/) + "Directory separator character for session menus.") + +(defvar session-save-file-coding-system + (cond-emacs-xemacs + :EMACS 'iso-latin-1-with-esc + ;; used `emacs-mule' but this fails with X-Symbol characters... + :XEMACS (and (featurep 'mule) 'escape-quoted)) + "Coding system to use when writing `session-save-file' if non-nil.") + + +;;;=========================================================================== +;;; User Options and Configuration: save global variables between sessions +;;;=========================================================================== + +(defcustom session-globals-max-size 50 + "*Maximal number of elements in the global variables. +Global variables are only saved if they are non-empty lists. This value +can be shadowed by some element in `session-globals-include'. If an +element appears more than once in the list, only the first appearance +will be stored." + :group 'session-globals + :type 'integer) + +(defcustom session-globals-max-string 1024 + "*Maximal length of string elements in global variables." + :group 'session-globals + :type 'integer) + +(defcustom session-registers-max-string 1024 + "*Maximal length of string elements in registers." + :group 'session-globals + :type 'integer) + +(defcustom session-save-file + (expand-file-name ".session" + (cond ((boundp 'user-emacs-directory) user-emacs-directory) + ((boundp 'user-init-directory) user-init-directory) + (t "~"))) + "File to save global variables and registers into. +It is saved with coding system `session-save-file-coding-system' at the +end of an Emacs session and loaded at the beginning. Used for variables +which are typically changed by editing operations, e.g., history and +ring variables. See \\[session-save-session] for details." + :group 'session-globals + :type 'file) + +(defvar session-old-save-file (expand-file-name ".session" "~") + "Value of `session-save-file' before Version 2.3 of this package.") + +(defvar session-save-print-spec '(t 2 1024) + ;; only for advanced users -> no custom + "*Print specification for the values of global variables to save. +The value looks like (CIRCLE LEVEL LENGTH) and is used to bind variables +`print-circle', `print-level' and `print-length' when evaluating +`session-save-session'.") + +(defcustom session-save-file-modes 384 + "Mode bits of session save file, as an integer, or nil. +After writing `session-save-file', set mode bits of that file to this +value if it is non-nil." + :group 'session-globals + :type '(choice (const :tag "Don't change" nil) integer)) + +(defvar session-before-save-hook nil + "Hook to be run before `session-save-file' is saved. +The functions are called after the global variables are written, +directly before the file is actually saved.") + +(defvar session-after-load-save-file-hook + (cond-emacs-xemacs + :EMACS (and (default-boundp 'yank-menu) + (fboundp 'menu-bar-update-yank-menu) + '(session-refresh-yank-menu))) + "Hook to be run after `session-save-file' has been loaded. +The functions are called when the file has been successfully loaded.") + +(defcustom session-globals-regexp "-\\(ring\\|history\\)\\'" + "Regexp matching global variables to be saved between sessions. +Variables in `session-globals-exclude' are not saved, but variables in +`session-globals-include' are always saved." + :group 'session-globals + :type 'regexp) + +(defcustom session-globals-exclude + '(load-history + register-alist flyspell-auto-correct-ring org-mark-ring + planner-browser-file-display-rule-ring + "\\`anything-c-source-") + "Global variables not to be saved between sessions. +An Element of this list is either a symbol or a regexp matching all +variable names not to be saved. +It affects `session-globals-regexp' and `session-globals-include'." + :group 'session-globals + :type '(repeat '(choice variable (string "Regexp")))) + +(defcustom session-globals-include '((kill-ring 10) + (session-file-alist 100 t) + (file-name-history 200) + search-ring regexp-search-ring) + "Global variables to be saved between sessions. +Each element has one of the following forms: + NAME, + (NAME MAX-SIZE), or + (NAME MAX-SIZE ASSOC-P). +where NAME is the symbol name of the variable, whose value must be a +non-empty list and string elements in this list must be smaller than +`session-globals-max-string'. MAX-SIZE (default is +`session-globals-max-size') is the maximal number of elements to be +saved for this symbol where only the first of equal elements are saved, +and ASSOC-P (default is nil) non-nil means that the variable is an alist +where the equality of elements is checked on the `car'. + +If MAX-SIZE or ASSOC-P is non-nil, it can be useful to include a +variable in this list even if it matches `session-globals-regexp'. +`session-globals-exclude' has no effect on these variables. + +Do not use this variable to customize your Emacs. Package custom is the +appropriate choice for this!" + :group 'session-globals + :type '(repeat (choice (variable :tag "List var with standard max size") + (list variable + (integer :tag "Max size") + (boolean :tag "Alist"))))) + + +;;;=========================================================================== +;;; Configuration: registers and local variables +;;;=========================================================================== + +(defcustom session-registers '((?0 . ?9) ?- ?= ?\\ ?` region (?a . ?z) (?A . ?Z)) + "*Registers to be saved in `session-save-file'. +Valid elements in this list are: + CHAR or (FROM . TO) or `file' or `region' or t. +CHAR is a register to save, (FROM . TO) represents a list of registers +from FROM to TO. `file' means, only save the following registers in +this list if they contain file or file-query references. `region' +means, only save registers if they contain a region which has less then +`session-registers-max-string' characters. t means, allow both content +types. Processing of this list starts with type `file'. + +Before saving the session files, markers in registers are turned into +file references, see variable `session-register-swap-out'." + :group 'session-globals + :type '(repeat (choice (const :tag "File registers:" file) + (const :tag "String registers:" region) + (const :tag "Any register type:" t) + (character :tag "Register") + (cons :tag "Registers" + (character :tag "From") + (character :tag "To"))))) + +(defcustom session-locals-include '(overwrite-mode) + "Local variables to be stored for specific buffers. +See also `session-locals-predicate'. + +Do not add variables to this list which are more appropriate for local +variables in files, i.e., variables which are related to the contents of +the file, e.g. `major-mode'!" + :group 'session-places + :type '(repeat variable)) + +(defcustom session-locals-predicate 'local-variable-p + "Function which must return non-nil for a local variable to be stored. +This function is called on all variables in `session-locals-include' +with the variable as the first and the current buffer as the second +argument. Good values are nil (do not store any variable), +`local-variable-p' for local variables, `local-variable-if-set-p' for +variables which become local when set, and t (store all variables in +`session-locals-include')." + :group 'session-places + :type '(choice (const :tag "none" nil) + (const :tag "All" t) + (function-item local-variable-p) + (function-item local-variable-if-set-p) + (function :tag "Other function"))) + +(defvar session-register-swap-out (if (fboundp 'register-swap-out) + 'register-swap-out + 'session-register-swap-out) + "Function processing markers in registers when a buffer is killed. +If non-nil, this function is added to `kill-buffer-hook'. Good values +are `register-swap-out' and the function `session-register-swap-out'.") + + +;;;=========================================================================== +;;; User Options and Configuration: buffer check--undo, mode+name +;;;=========================================================================== + +(defcustom session-jump-undo-threshold 240 + "*Number of character positions the undo position must be different. +Without prefix arg, `session-jump-to-last-change' jumps successively to +change positions which differ by at least `session-jump-undo-threshold' +characters compared to the current position and previously visited +change positions, see `session-jump-undo-remember'." + :group 'session-places + :type 'integer) + +(defcustom session-jump-undo-remember 2 + "*Number of previously visited change positions checked additionally. +See `session-jump-undo-threshold' and `session-jump-to-last-change'." + :group 'session-places + :type 'integer) + +(defvar session-after-jump-to-last-change-hook nil + "Hook to be after `session-jump-to-last-change' has been executed. +The functions are called if `point' has been moved.") + +;; Problem if homedir is a symlink (/bar/home -> /net/bar.home) & tmp-mounted +;; (file-truename "~/foo") => "/tmp_mnt/net/bar.home/foo" +;; (abbreviate-file-name "/tmp_mnt/net/bar.home/foo") => "/net/bar.home/foo" +;; I.e., there is a bug in `abbreviate-file-name' on both Emacs and XEmacs +;; (with 2nd arg t). Workaround: use the following in your ~/.emacs: + +;;(unless (string= (abbreviate-file-name (file-truename "~") t) "~") ; XEmacs +;; (setq abbreviated-home-dir +;; (let ((abbreviated-home-dir "$foo")) +;; (concat "\\`\\(?:" +;; (regexp-quote (abbreviate-file-name (expand-file-name "~"))) +;; "\\|" +;; (regexp-quote (abbreviate-file-name (file-truename "~"))) +;; "\\)\\(/\\|\\'\\)")))) + +(defconst session-use-truenames-default + (cond-emacs-xemacs + :EMACS (string= (abbreviate-file-name (file-truename "~")) "~") + :XEMACS (and (string= (abbreviate-file-name (file-truename "~") t) "~") + (if (eq system-type 'windows-nt) + 'session-xemacs-buffer-local-mswindows-file-p + t)))) + +(defcustom session-use-truenames session-use-truenames-default + "*Whether to use the canonical file names when saving/restoring places. +If a function, it is called with no argument and returns whether to use +the canonical names of files. If non-nil, store and check file names +returned by `file-truename'." + :group 'session-places + :type '(choice (const :tag "No" nil) + (const :tag "Yes" t) + (function-item :tag "If not starting with \\\\" + session-xemacs-buffer-local-mswindows-file-p) + (function :tag "Other function"))) + +(defcustom session-auto-store t + "*Determines whether a buffer to be killed passes the mode/name check. +This boolean is used by `session-default-buffer-check-p', see +`session-buffer-check-function'. + +A buffer passes the mode/name check, if it passes the mode check, see +below, and its file name is not matched by +`session-name-disable-regexp', or if fails the mode check and its file +name is matched by `session-name-enable-regexp'. + +A buffer passes the mode check, if this variable is non-nil and its +major mode is not a member of `session-mode-disable-list', or if this +variable is nil and its major mode is a member of +`session-mode-enable-list'." + :group 'session-places + :type 'boolean) + +(defcustom session-undo-check 1 + "*Determines how a buffer to be killed passes the undo check. +Its value is MIN or (MIN . LAST) where MIN is a number. Used by +`session-default-buffer-check-p', see `session-buffer-check-function'. + +To pass the undo check + * the length of `buffer-undo-list', assumed to be -1 if no undo + information is recorded, must be higher or equal to MIN, + * the first form is used or LAST is nil: no further requirement + * LAST is `and': additionally, `session-last-change' must be non-nil, + i.e., the buffer has been changed previously, + * LAST is `or': alternatively, `session-last-change' is non-nil." + :group 'session-places + :type '(choice (integer :tag "Min no of Changes") + (cons (integer :tag "Min no of Changes") + (choice :tag "Previous and New Changes" + (const :tag "Only consider New Changes" nil) + (const :tag "AND previously changed" and) + (const :tag "OR previously changed" or))))) + +(defcustom session-kill-buffer-commands '(kill-this-buffer) + "*Commands which kill a buffer. +If a prefix argument was provided to any of these commands, it will +influence the decision whether to store places for the buffer, see +`session-store-buffer-places'. Using commands which use the minibuffer +for input, is useless." + :group 'session-places + :type '(repeat (function :tag "Command"))) + +(defcustom session-buffer-check-function 'session-default-buffer-check-p + "Function which return non-nil if buffer places should be stored. +Used by `session-store-buffer-places'. This function is called with the +buffer to check as argument. You can also assume that the current +buffer is the buffer to check. + +The default value `session-default-buffer-check-p' returns non-nil, if +the buffer + * visits an existing readable file, + * passes the mode/name check, see `session-auto-store', and + * passes the undo check, see `session-undo-check', its default value 1 + means: the buffer must have been changed during the session." + :group 'session-globals + :type '(choice (function-item :tag "Default check" + session-default-buffer-check-p) + (function :tag "Other function"))) + +(defcustom session-mode-disable-list + '(vm-mode gnus-score-mode message-mode tar-mode) + "*Major modes of buffers for which no places are stored. +See `session-buffer-check-function'." + :group 'session-globals + :type '(repeat (function :tag "Major mode"))) + +(defcustom session-mode-enable-list nil + "*Major modes of buffers for which places are stored. +See `session-buffer-check-function'." + :group 'session-globals + :type '(repeat (function :tag "Major mode"))) + +(defcustom session-name-disable-regexp + (concat "\\`" (regexp-quote + (if (fboundp 'temp-directory) (temp-directory) "/tmp"))) + "*File names of buffers for which no places are stored. +See `session-buffer-check-function'." + :group 'session-places + :type '(choice (const nil) regexp)) + +(defcustom session-name-enable-regexp nil + "*File names of buffers for which places are stored. +See `session-buffer-check-function'." + :group 'session-places + :type '(choice (const nil) regexp)) + + + + +;;;;########################################################################## +;;;; Store buffer places and local variables, change register contents +;;;;########################################################################## + + +(defvar session-last-change nil + "Position of last change in current buffer. +This variable is set by `session-find-file-hook' if the buffer was +changed in a previous session. It can also be set by providing an +prefix argument to `session-jump-to-last-change'.") +(make-variable-buffer-local 'session-last-change) + +(defvar session-file-alist nil + "Alist for places and local variables for some files. +It has the form + (NAME POINT MARK POINT-MIN POINT-MAX PERMANENT LAST-CHANGE + (SYMBOL . VAR) ...) + +NAME is the file name, POINT is the point position, MARK is the mark +position, POINT-MIN and POINT-MAX determine the narrow part if non-nil, +PERMANENT is the permanent marker (see `session-buffer-check-function'), +LAST-CHANGE is the position of the last change in the previous session +or was explicitly set with prefix argument 0 for command +\\[session-jump-to-last-change]. Optional pairs (SYMBOL . VAR) are +local variables with their values.") + +(defvar session-jump-to-last-change-counter 0 + "Number of repeated invocations of `session-jump-to-last-change'.") + +(defvar session-jump-to-last-change-recent nil + "Current position and previously visited change positions.") + + +;;;=========================================================================== +;;; Position of last change +;;;=========================================================================== + +(defun session-undo-position (num pos1 pos2) + "Return a previous undo-position or set it. +If argument NUM is nil, set `session-last-change' to the recomputed +position given by argument POS1 and return POS1 normalized. + +Otherwise, return a previous undo-position or nil, if no such position +can be found. If `session-jump-to-last-change-counter' is nil, the +position found is the stored last-change position. + +If POS1 and POS2 are nil, NUM is the number of undo-boundaries to skip. +The position returned is the last change inside the corresponding undo +step. + +Otherwise, NUM is the number of undo entries to skip. The position +returned is the last change after these entries outside the range from +POS1 to POS2. Increment `session-jump-to-last-change-counter' by the +number of entries skipped additionally." + (let ((undo-list (and (consp buffer-undo-list) buffer-undo-list)) + elem ; element in undo-list, t = not of interest + back-list ; used position must be recomputed due to processed elems + len ; length of deletion/insertion + pos) ; interesting position in undo-list + (while (and undo-list (null (car undo-list))) + (pop undo-list)) ; ignore undo-boundaries at beg + (while undo-list + ;; inspect element in undo-list ---------------------------------------- + (setq elem (pop undo-list)) + (cond ((atom elem) ; marker position + (when (or elem pos1) ; undo-boundary is of interest if POS1=nil + (if (integerp elem) + (setq pos elem ; use point position in undo-list + back-list (cons nil back-list)) + (setq elem t)))) ; ignore uninteresting elem + ((stringp (car elem)) ; deletion: (TEXT . POSITION) + (setq pos (abs (cdr elem)) + len (length (car elem))) + (push (list* pos (+ pos len) (- len)) back-list) + (when pos1 ; adopt POS{1,2} if after deletion + (if (> pos1 pos) (incf pos1 len)) + (if (>= pos2 pos) (incf pos2 len)))) + ((integerp (car elem)) ; insertion: (START . END) + (setq pos (car elem) + len (- (cdr elem) pos)) + (push (list* pos pos len) back-list) + (when pos1 ; adopt POS{1,2} if after/in insertion + (if (> pos1 pos) + (setq pos1 (if (> pos1 (cdr elem)) (- pos1 len) pos))) + (if (> pos2 pos) + (setq pos2 (if (> pos2 (cdr elem)) (- pos2 len) pos)))) + (setq pos (cdr elem))) ; point more likely at end of insertion + (t + (setq elem t))) + ;; evaluation element inspection --------------------------------------- + (cond ((null num)) ; set POS1 as `session-last-change' + ((null pos1) ; looking for undo-boundaries + (when (if elem + (and (zerop num) pos) + (<= (decf num) 0)) + (setq undo-list nil))) + ((eq elem t) ; uninteresting element + (setq pos nil)) + ((> num 0) ; interesting, but not the NUM's one + (decf num) + (setq pos nil)) + ((and (<= pos1 pos) (<= pos pos2)) ; inside start region + (incf session-jump-to-last-change-counter) + (setq pos nil)) + (t + (setq undo-list nil)))) + ;; finalize: evaluate result and process back-list ----------------------- + (cond ((null num) ; set POS1 as `session-last-change' + (setq session-last-change pos1 + pos session-last-change)) + ((or (null pos) (> num 0)) ; no position found in undo-list + (setq session-jump-to-last-change-counter nil) + (setq pos session-last-change)) + (t ; pos in undo-list + (if session-jump-to-last-change-counter + (incf session-jump-to-last-change-counter)) + (setq back-list (cdr back-list)))) + (when pos + (while back-list + (setq elem (pop back-list)) + (cond ((null elem)) ; integer position in undo-list + ((> pos (cadr elem)) ; position after affected region + (incf pos (cddr elem))) ; increment/decrement position + ((> pos (car elem)) ; position in affected region + (setq pos (car elem))))) ; set position to region begin + pos))) + +;;;###autoload +(defun session-jump-to-last-change (&optional arg) + "Jump to the position of the last change. +Without prefix arg, jump successively to previous change positions which +differ by at least `session-jump-undo-threshold' characters by repeated +invocation of this command. With prefix argument 0, jump to end of last +change. With numeric prefix argument, jump to start of first change in +the ARG's undo block in the `buffer-undo-list'. + +With non-numeric prefix argument (\\[universal-argument] only), set +point as oldest change position. It might change slightly if you jump +to it due to intermediate insert/delete elements in the +`buffer-undo-list'." + ;; note: for compatibility reasons (pre v2.2), we use abs(ARG) + (interactive "P") + (if (consp arg) + (let ((pos (session-undo-position nil (point) (point))) + (undo-list (and (consp buffer-undo-list) buffer-undo-list))) + (setq arg 1) + (while (and undo-list (null (car undo-list))) (pop undo-list)) + (while undo-list (or (pop undo-list) (incf arg))) + (message "Store %d as special last-change position (%s %d %s)" + pos + (substitute-command-keys "\\[universal-argument]") + arg + (substitute-command-keys "\\[session-jump-to-last-change]"))) + ;; set and restrict previously visited undo positions -------------------- + (push (point) session-jump-to-last-change-recent) + (if (and (null arg) (eq last-command 'session-jump-to-last-change-seq)) + (let ((recent (nthcdr session-jump-undo-remember + session-jump-to-last-change-recent))) + (if recent (setcdr recent nil))) + (setcdr session-jump-to-last-change-recent nil) ; only point + (setq session-jump-to-last-change-counter 0)) + (let (pos) + (if arg + (setq pos (session-undo-position (abs (prefix-numeric-value arg)) + nil nil)) + ;; compute position, compare it with positions in + ;; `session-jump-to-last-change-recent' + (let ((recent session-jump-to-last-change-recent) old pos1 pos2) + (setq pos (point)) + (while recent ; at least point is there + (setq old (pop recent)) + (setq pos1 (- pos session-jump-undo-threshold) + pos2 (+ pos session-jump-undo-threshold)) + (when (and (<= pos1 old) (<= old pos2)) + (setq pos (session-undo-position + session-jump-to-last-change-counter pos1 pos2)) + (setq recent (and pos + session-jump-to-last-change-counter + session-jump-to-last-change-recent)))))) + (cond ((null pos) + (message (if (or arg (atom buffer-undo-list)) + "Do not know position of last change" + "Do not know position of last distant change"))) + ((< pos (point-min)) + (goto-char (point-min)) + (message "Change position outside visible region")) + ((> pos (point-max)) + (goto-char (point-max)) + (message "Change position outside visible region")) + (t + (goto-char pos) + (cond ((null session-jump-to-last-change-counter) + (message "Jumped to stored last-change position")) + ((null arg) + (setq this-command 'session-jump-to-last-change-seq))) + (run-hooks 'session-after-jump-to-last-change-hook)))))) + + +;;;=========================================================================== +;;; Yank menu (Emacs: refresh existing menu, XEmacs: do our own) +;;;=========================================================================== + +;; this function should be defined in menu-bar.el... +(defunx session-refresh-yank-menu () + :xemacs-only ignore + "Refresh `yank-menu' according to `kill-ring'." + (when (and (default-boundp 'yank-menu) + (fboundp 'menu-bar-update-yank-menu)) + (let ((killed (reverse (default-value 'kill-ring)))) + (while killed + (menu-bar-update-yank-menu (pop killed) nil))))) + +(defun session-yank (arg) + "Reinsert the last stretch of killed text, like \\[yank]. +Calls `yank' with argument ARG and with `interprogram-paste-function' +bound to nil." + (interactive "*p") + (let ((interprogram-paste-function nil)) ;#dynamic + (yank arg))) + +(defun session-popup-yank-menu (event) + ;; checkdoc-params: (event) + "Pop up a menu for inserting items in `kill-ring'." + (interactive "e") + (when kill-ring + (setq this-command last-command) + (popup-menu '("Select and Paste" + :filter session-yank-menu-filter)))) + +(defun session-yank-menu-filter (menu-items) + ;; checkdoc-params: (menu-items) + "Return a menu for inserting items in `kill-ring'." + (let ((menu nil) + (ring nil) + (max session-menu-max-size) + (len (length kill-ring)) + (half-str-len (/ (- session-edit-menu-max-string 4) 2)) + (i 0) + (active (not buffer-read-only)) + elem + (interprogram-paste-function nil)) ;#dynamic + ;; Traversing (append kill-ring-yank-pointer kill-ring) instead indexing + ;; (current-kill INDEX) would be probably more efficient, but would be a + ;; very low-level hack + (while (and (< i len) (> max 0)) + (setq elem (current-kill i t) + i (1+ i)) + (unless (or (assoc elem ring) (string-match "\\`[ \t\n]*\\'" elem)) + (push (cons elem i) ring) + (setq max (1- max)))) + (while ring + (setq elem (car ring) + ring (cdr ring)) + (push (session-yank-string (car elem) half-str-len + (list 'session-yank (cdr elem)) + active) + menu)) + (session-menu-maybe-accelerator menu-items menu))) + +(defun session-yank-string (string half-len-str callback active) + ;; checkdoc-order: nil + "Return menu item STRING with callback CALLBACK. +If ACTIVE is non-nil, the item is active. HALF-LEN-STR is the length of +the two parts of a abbreviated menu item name." + (let ((beg (or (and session-compact-yank-gap-regexp + (string-match "\\`[ \t\n]+" string) + (match-end 0)) + 0)) + (end (or (and session-compact-yank-gap-regexp + (string-match "[ \t\n]+\\'" string)) + (length string)))) + (vector (if (> (- end beg) session-edit-menu-max-string) + (let ((gap (and session-compact-yank-gap-regexp + (string-match session-compact-yank-gap-regexp + string (- end half-len-str)) + (match-end 0)))) + (if (and gap (< gap (- end 3))) + (setq half-len-str (- (+ half-len-str half-len-str gap) + end)) + (setq gap (- end half-len-str))) + (concat (session-subst-char-in-string + ?\t ?\ (substring string beg (+ beg half-len-str)) t) + " ... " + (session-subst-char-in-string + ?\t ?\ (substring string gap end) t))) + (session-subst-char-in-string + ?\t ?\ (substring string beg end) t)) + callback + active))) + +;; from EMACS-20.4/lisp/subr.el: +(defunx session-subst-char-in-string (fromchar tochar string &optional inplace) + :try subst-char-in-string + "Replace FROMCHAR with TOCHAR in STRING each time it occurs. +Unless optional argument INPLACE is non-nil, return a new string." + (let ((i (length string)) + (newstr (if inplace string (copy-sequence string)))) + (while (> i 0) + (setq i (1- i)) + (if (eq (aref newstr i) fromchar) + (aset newstr i tochar))) + newstr)) + + +;;;=========================================================================== +;;; Menu filters +;;;=========================================================================== + +(defun session-file-opened-recompute () + "Move file name of all live buffers to top of the menu of visited files." + (interactive) + (session-file-changed-recompute t)) + +(defun session-file-changed-recompute (&optional for-opened) + "Store buffer places for all live buffers. +Works like \\[session-file-opened-recompute] if FOR-OPENED is non-nil." + (interactive) + (let ((session-use-package t)) ;#dynamic + (save-excursion + (dolist (buffer (nreverse (buffer-list))) + (set-buffer buffer) + (when buffer-file-name + (condition-case nil ; potential errors with remote files + (if for-opened + (session-set-file-name-history) + (session-store-buffer-places 1)) + (error nil))))))) + +(defun session-file-opened-menu-filter (menu-items) + ;; checkdoc-params: (menu-items) + "This is the menu filter for \"File >> Open...recently visited\". +See `session-file-changed-menu-filter'." + (session-file-changed-menu-filter menu-items file-name-history)) + +(defun session-file-changed-menu-filter (menu-items &optional files find-fn) + ;; checkdoc-params: (menu-items) + "This is the menu filter for \"File >> Open...recently changed\". +It dynamically creates a list of files to use as the contents of the +menu. The files are taken from FILES or `session-file-alist'. It +doesn't show the same name twice and shows `session-menu-max-size' names +at most. FIND-FN or \\[find-file] is the function to use when selecting +a file in the menu." + (or files (setq files session-file-alist)) + (or find-fn (setq find-fn 'session-find-file)) + (let ((excl nil) + (menu nil) + (i session-menu-max-size) + (max-string (max (cond ((natnump session-file-menu-max-string) + session-file-menu-max-string) + ((integerp session-file-menu-max-string) + (- 0 session-file-menu-max-string + (length (buffer-name)))) + ((consp session-file-menu-max-string) + (- (car session-file-menu-max-string) + (max (- (length (buffer-name)) + (cdr session-file-menu-max-string)) + 0))) + (t 50)) + 16))) + (while (and files (> i 0)) + (let ((name (pop files)) desc) + (when (consp name) + (setq desc name + name (car name))) + (setq name (session-abbrev-file-name name t)) + (unless (member name excl) + (setq i (1- i)) + (push name excl) + (let ((buf (if (session-abbrev-inhibit-function name) + nil + (get-file-buffer name)))) + (push (vector (or (session-file-prune-name name max-string) name) + (list find-fn name) + :keys (concat (and (sixth desc) "p") + (when buf + (with-current-buffer buf + (if (consp buffer-undo-list) + (if (buffer-modified-p) + "c" "s") + (if buffer-read-only + "r" "v")))))) + menu))))) + (session-menu-maybe-accelerator menu-items (nreverse menu)))) + +(defun session-file-prune-name (elem max-string) + "Prune name íf menu entry ELEM to use a miximum string length MAX-STRING." + (when (> (length elem) max-string) + (let* ((sep-string (char-to-string session-directory-sep-char)) + (components (split-string elem (regexp-quote sep-string)))) + (or (cdr components) ; successful split + (eq session-directory-sep-char ?\/) ; already Unix separator + (setq sep-string "/" + components (split-string elem (regexp-quote sep-string)))) + (let* ((prefix (if (< (length (car components)) 3) ; e.g. "~" or "C:" + (concat (pop components) sep-string + (pop components)) + (pop components))) + (len (+ (length prefix) 7)) ; "/ ... /" + postfix) + (when (cdr components) ; more than one remaining dir component + (setq components (nreverse components)) + (incf len (length (car components))) + (push (pop components) postfix) ; always use last one + (while (<= (incf len (1+ (length (car components)))) max-string) + (push (pop components) postfix)) + (concat prefix sep-string " ... " sep-string + (mapconcat 'identity postfix sep-string))))))) + +(defun session-menu-maybe-accelerator (menu-items menu) + "Return menu consisting of items in MENU-ITEMS and MENU. +MENU-ITEMS have the usual format of elements in a menu, except that the +name always starts with a accelerator specification \"%_. \". Also, a +:keys specification will be evaluated if :keys is the first keyword. + +The items in MENU will be modified to add accelerator specifications if +`session-menu-accelerator-support' is non-nil." + (append menu-items + (if session-menu-accelerator-support + (funcall session-menu-accelerator-support menu) + menu))) + +(defun session-abbrev-inhibit-function (name) + "Call function in variable `session-abbrev-inhibit-function'." + (and session-abbrev-inhibit-function + (or (not (functionp session-abbrev-inhibit-function)) + (funcall session-abbrev-inhibit-function name)))) + +(defun session-abbrev-file-name (name &optional call-dfn) + "Return a version of NAME shortened using `directory-abbrev-alist'. +This function does not consider remote file names (see +`session-abbrev-inhibit-function') and substitutes \"~\" for the user's +home directory. +If optional argument CALL-DFN is non-nil and the file is not remote, +remove final slash according to function `directory-file-name'." + (if (session-abbrev-inhibit-function name) + name + (cond-emacs-xemacs + (abbreviate-file-name (if call-dfn (directory-file-name name) name) + :XEMACS t)))) + + +;;;=========================================================================== +;;; Functions in hooks +;;;=========================================================================== + +(defun session-find-file (filename) + "Call \\[find-file] and add FILENAME to `file-name-history'." + ;; also sets history when just switching to existing buffer + (interactive "FFind file: ") + (find-file filename) + (let ((session-use-package t)) ;#dynamic + (session-set-file-name-history))) + +(defun session-set-file-name-history () + ;; TODO later: test `this-command' matching regexo "find-file" or similar + "Add file-name of current buffer to `file-name-history'. +Don't add the file name if it matches +`session-set-file-name-exclude-regexp', or if it is already at the front +of `file-name-history'. This function is useful in `find-file-hooks'." + (and session-use-package + buffer-file-name + (not (string= (car file-name-history) buffer-file-name)) + (not (string= (car file-name-history) buffer-file-truename)) +;; (file-exists-p buffer-file-name) (file-readable-p buffer-file-name) + (let ((name (session-abbrev-file-name buffer-file-name))) + (unless (and session-set-file-name-exclude-regexp + (string-match session-set-file-name-exclude-regexp name)) + (push name file-name-history))))) + +(defun session-find-file-hook () + "Function in `find-file-hooks'. See `session-file-alist'." + (unless (or (eq this-command 'session-disable) + (null session-use-package)) + (let* ((ass (assoc (session-buffer-file-name) session-file-alist)) + (point (second ass)) + (mark (third ass)) + (min (fourth ass)) + (max (fifth ass)) + (alist (nthcdr 7 ass))) + (condition-case nil + (while alist + (if (local-variable-if-set-p (caar alist) (current-buffer)) + (set (caar alist) (cdar alist))) + (setq alist (cdr alist))) + (error nil)) + (setq session-last-change (seventh ass)) + (and mark + (<= (point-min) mark) (<= mark (point-max)) + ;; I had `set-mark' but this function activates mark in Emacs, but + ;; not in XEmacs. `push-mark' is also OK and doesn't activate in + ;; both Emacsen which is better if we use `pending-delete-mode'. + (push-mark mark t)) + (and min max + (<= (point-min) min) (<= max (point-max)) + (narrow-to-region min max)) + (and point + (<= (point-min) point) (<= point (point-max)) + (goto-char point))))) + +(defun session-kill-buffer-hook () + "Function in `kill-buffer-hook'. +See `session-file-alist' and `session-registers'." + (if (and session-use-package buffer-file-name) + (let ((arg (if (memq this-command session-kill-buffer-commands) + (prefix-numeric-value current-prefix-arg) + 1))) + (condition-case nil + (if (> arg -2) + (session-store-buffer-places arg) + (setq file-name-history + (delete buffer-file-truename + (delete buffer-file-name file-name-history))) +;;; (setq session-file-alist +;;; (delete* (session-buffer-file-name) session-file-alist +;;; :key 'car :test 'string=))) ; Emacs CL policy... + (let ((fname (session-buffer-file-name)) + (alist (cons nil session-file-alist))) + (while (cdr alist) + (if (string= (cadr alist) fname) + (setcdr alist (cddr alist)) + (setq alist (cdr alist)))) + (setq session-file-alist (cdr alist)))) + (error nil))))) + + +;;;=========================================================================== +;;; Change register contents from marker to file +;;;=========================================================================== + +(defun session-register-swap-out () + "Turn markers in registers into file references when a buffer is killed. +See variable `session-register-swap-out'." + (and buffer-file-name + (let ((tail register-alist)) + (while tail + (and (markerp (cdr (car tail))) + (eq (marker-buffer (cdr (car tail))) (current-buffer)) + (setcdr (car tail) + (cons 'file buffer-file-name))) + (setq tail (cdr tail)))))) + + + +;;;;########################################################################## +;;;; Save global variables, add functions to hooks +;;;;########################################################################## + + +(defvar session-successful-p nil + "Whether the file `session-save-file' has been loaded successfully.") + + +;;;=========================================================================== +;;; The buffer file name +;;;=========================================================================== + +(defun session-xemacs-buffer-local-mswindows-file-p () + "Return t if the current buffer visits a local file on MS-Windows. +Also returns t if the current buffer does not visit a file. Return nil +of the current buffer visits a file starting with \"\\\\\". Workaround +for XEmacs bug in `file-truename' for file names starting with +\"\\\\\"." + (or (< (length buffer-file-name) 2) + (not (string= (substring buffer-file-name 0 2) "\\\\")))) + +(defun session-buffer-file-name () + "Return the buffer file name according to `session-use-truenames'." + (if (if (functionp session-use-truenames) + (funcall session-use-truenames) + session-use-truenames) + buffer-file-truename + buffer-file-name)) + + +;;;=========================================================================== +;;; Store places and local variables for buffer to be killed +;;;=========================================================================== + +(defun session-toggle-permanent-flag (arg &optional check) + "Toggle the permanent flag of the current buffer. +With ARG, set permanent flag if and only if ARG is positive. If the +permanent flag is set, the places are stored as well. If CHECK is +non-nil, just return the status of the permanent flag: either nil if it +is unset or `session-menu-permanent-string' if it is set." + (interactive "P") + (if buffer-file-name + (let ((permanent (if arg + (> (prefix-numeric-value arg) 0) + (not (nth 5 (assoc (session-buffer-file-name) + session-file-alist)))))) + (if check + (if permanent nil session-menu-permanent-string) + (session-store-buffer-places (if permanent 3 -1)) + (message (if permanent + "Permanent flag is set and places are stored" + "Permanent flag has been unset")))) + (if check nil (error "Buffer is not visiting a file")))) + +(defun session-store-buffer-places (arg) + "Store places and local variables in current buffer. +An entry for the current buffer and its places is added to the front of +`session-file-alist' if the buffer is visiting a file and if it is +mentioned in the list below. ARG is the prefix argument to a command in +`session-kill-buffer-commands' or 1 for any other command. + +ARG=-2: delete current buffer from `session-file-alist' (implemented + in `session-kill-buffer-hook') +ARG=-1: delete PERMANENT flag for buffer, +ARG=0: do nothing, +ARG=1: store buffer places, if the PERMANENT flag is set or the buffer + passes the function in `session-buffer-check-function', +ARG=2: always store buffer places, +ARG=3: set PERMANENT flag and store buffer places. + +See also `session-last-change' and `session-locals-include'. + +Note that not storing buffer places does not mean deleting an old entry +for the same file. It means that there is the danger of the entry +becoming too old to be saved across session. By default, only the first +100 entries of `session-file-alist' are saved, see +`session-globals-include'." + (let ((file-name (session-buffer-file-name))) + (when file-name + (let ((permanent (nthcdr 5 (assoc file-name session-file-alist)))) + (and (< arg 0) (car permanent) + (setcar permanent nil)) ; reset permanent in existing entry + (setq permanent (or (car permanent) (> arg 2))) + (if (or (and permanent (> arg 0)) + (> arg 1) + (and (= arg 1) + (funcall session-buffer-check-function (current-buffer)))) + (let ((locals session-locals-include) + (store nil)) + (while locals + (if (if (functionp session-locals-include) + (funcall session-locals-predicate + (car locals) (current-buffer)) + session-locals-predicate) + (push (cons (car locals) + (symbol-value (car locals))) + store)) + (setq locals (cdr locals))) + (setq store + (nconc (list file-name + (point) (mark t) + (point-min) + (and (<= (point-max) (buffer-size)) + (point-max)) + permanent + (session-undo-position 0 nil nil)) + store)) + (if (equal (caar session-file-alist) file-name) + (setcar session-file-alist store) + (push store session-file-alist)))))))) + +(defun session-find-file-not-found-hook () + "Query the user to delete the permanent flag for a non-existent file. +Always return nil." + (when session-use-package + (let ((file-name (session-buffer-file-name))) + (when file-name + (let ((permanent (nthcdr 5 (assoc file-name session-file-alist)))) + (and (car permanent) + (y-or-n-p "Delete permanent flag for non-existent file? ") + (setcar permanent nil))))))) + + +;;;=========================================================================== +;;; Default standard check for buffers to be killed +;;;=========================================================================== + +(defun session-default-buffer-check-p (buffer) + "Default function for `session-buffer-check-function'. +Argument BUFFER should be the current buffer." + (and + ;; undo check ------------------------------------------------------------- + (or (and (eq (cdr-safe session-undo-check) 'or) + session-last-change) + (and (or (not (eq (cdr-safe session-undo-check) 'and)) + session-last-change) + (>= (if (listp buffer-undo-list) (length buffer-undo-list) -1) + (if (consp session-undo-check) + (car session-undo-check) + session-undo-check)))) + ;; mode and name check ---------------------------------------------------- + (let ((file (buffer-file-name buffer))) + (and (or (session-abbrev-inhibit-function file) + (and (file-exists-p file) (file-readable-p file))) + (if (if session-auto-store + (not (memq major-mode session-mode-disable-list)) + (memq major-mode session-mode-enable-list)) + (not (and session-name-disable-regexp + (string-match session-name-disable-regexp file))) + (and session-name-enable-regexp + (string-match session-name-enable-regexp file))))))) + + +;;;=========================================================================== +;;; Save session file +;;;=========================================================================== + +(defunx session-true-list-p (object) + :try true-list-p + "Return t if OBJECT is an acyclic, nil-terminated (ie, not dotted), list." + (null (if (consp object) + (cdr (last object)) ; in Emacs-24.1: safe against cyclic lists + object))) + +(defun session-make-ring (size seq) + ;; similar to `ring-convert-sequence-to-ring' from Emacs-24.1 + "Return ring of length LENGTH containing the elements of sequence SEQ." + (let ((ring (make-ring size))) + (dotimes (count (length seq)) + (when (or (ring-empty-p ring) + (not (equal (ring-ref ring 0) (elt seq count)))) + (ring-insert-at-beginning ring (elt seq count)))) + ring)) + +(defun session-save-session (&optional force) + "Save session: file places, *-ring, *-history, registers. +Save some global variables and registers into file `session-save-file' +with coding system `session-save-file-coding-system'. Run functions in +`session-before-save-hook' before writing the file. + +See also `session-globals-regexp', `session-globals-include' and +`session-registers'. + +This function does not save the session if `session-use-package' is nil +and FORCE is nil. When called interactively, FORCE is non-nil. + +This command is automatically executed when exiting Emacs, except when +using \\[save-buffers-kill-emacs] with prefix argument 0." + (interactive "p") + (and (or force session-use-package) + session-save-file + (not (and (eq this-command 'save-buffers-kill-emacs) + (equal current-prefix-arg 0))) + (or session-successful-p + (not (file-exists-p session-save-file)) + (y-or-n-p "Overwrite old session file (not loaded)? ")) + (save-excursion + ;; `kill-emacs' doesn't kill the buffers ---------------------------- + (dolist (buffer (nreverse (buffer-list))) + (set-buffer buffer) + (when buffer-file-name + (condition-case nil ; potential errors with remote files + (session-store-buffer-places 1) + (error nil)) + (if session-register-swap-out + (funcall session-register-swap-out)))) + ;; create header of session file ------------------------------------ + (set-buffer (get-buffer-create " session ")) + (erase-buffer) + (let ((coding-system-for-write ;#dynamic + (and session-save-file-coding-system + (condition-case nil + (check-coding-system session-save-file-coding-system) + (error nil))))) + (when coding-system-for-write + (insert (format ";;; -*- coding: %S; -*-\n" + session-save-file-coding-system))) + (insert ";;; Automatically generated on " (current-time-string) + "\n;;; Invoked by " (user-login-name) "@" (system-name) + " using " emacs-version "\n") + ;; save global variables and registers ---------------------------- + (let (globals-exclude exclude-regexp) + (dolist (incl session-globals-include) + (let ((symbol (if (consp incl) (car incl) incl))) + (push symbol globals-exclude) + (when (default-boundp symbol) + (session-save-insert-variable symbol + (default-value symbol) + (cdr-safe incl))))) + (dolist (excl session-globals-exclude) + (if (stringp excl) + (push excl exclude-regexp) + (push excl globals-exclude))) + (when exclude-regexp + (setq exclude-regexp + (concat "\\(?:" + (mapconcat 'identity exclude-regexp "\\)\\|\\(?:") + "\\)"))) + (dolist (symbol (apropos-internal session-globals-regexp 'boundp)) + (unless (or (memq symbol globals-exclude) + (and exclude-regexp + (string-match exclude-regexp + (symbol-name symbol)))) + (push symbol globals-exclude) + (when (default-boundp symbol) + (session-save-insert-variable symbol + (default-value symbol)))))) + (session-save-registers) + ;; write session file --------------------------------------------- + (run-hooks 'session-before-save-hook) + (condition-case var + (progn + (if (file-exists-p session-save-file) + (delete-file session-save-file)) + (make-directory (file-name-directory session-save-file) t) + (write-region (point-min) (point-max) session-save-file) + (if session-save-file-modes + (set-file-modes session-save-file + session-save-file-modes))) + (error ; efs would signal `ftp-error' + (or (y-or-n-p "Could not write session file. Exit anyway? ") + (cond-emacs-xemacs + (:EMACS signal :XEMACS signal-error :BOTH + (car var) (cdr var)))))) + (kill-buffer (current-buffer)))))) + +(defun session-save-insert-variable (symbol val &optional spec) + "Print SYMBOL and its value VAL into the current buffer. +Argument SPEC looks like (MAX-SIZE ASSOC-P), see variable +`session-globals-include' for details. + +Only true lists are printed which do not have a level-1 recursion. +Elements in the list are not printed if one of the following is true: + - the element is no cons if ASSO-P is non-nil, + - strings which are too long, see `session-globals-max-string', + - non-readable elements, or + - elements which are not fully represented according to the + specification in `session-save-print-spec'." + (when (consp val) + (let ((print-circle (car session-save-print-spec)) ;#dynamic + (print-level (cadr session-save-print-spec)) ;#dynamic + (print-length (caddr session-save-print-spec)) ;#dynamic + (len (or (car spec) session-globals-max-size)) + (ass-p (cadr spec)) + (ring-size (and (ring-p val) (ring-size val))) + (slist nil) klist clist) + (when ring-size + (setq val (ring-elements val))) + (while (and (consp val) (> len 0)) + (if (memq val clist) + (setq val t) ; don't print recursive lists + (push val clist) + (let* ((elem (pop val)) + (estr (prin1-to-string (if (stringp elem) + (substring-no-properties elem) + elem)))) + ;; some text properties cannot be read, e.g. buffer positions + ;; read/load isn't clever: ignore non-readable elements + (unless (cond (ass-p + (or (atom elem) + (member (car elem) klist) + (condition-case nil + (prog1 nil + (read estr) + (push (car elem) klist)) + (error t)))) + ((member estr slist)) + ((stringp elem) + (> (length elem) session-globals-max-string)) + ((condition-case nil + (prog1 nil + (read estr)) + (error t)))) + (push estr slist) + (decf len))))) + (when (and slist (session-true-list-p val)) + (insert (if ring-size + (format "(setq-default %S (session-make-ring %d '(" + symbol ring-size) + (format "(setq-default %S '(" symbol))) + (setq slist (nreverse slist)) + (while slist + (insert (pop slist) + (if slist " " (if ring-size ")))\n" "))\n")))))))) + +(defunx session-next-range-char (char) + ;; XEmacs register functions should handle integers as chars better... + :emacs-only 1+ + "Return the next char after CHAR." + (int-to-char (1+ char))) + +(defun session-save-registers () + "Save registers in `session-registers'." + (let ((chars session-registers) + (type 'file) + register from to) + (while chars + (if (symbolp (car chars)) + (setq type (car chars) + chars (cdr chars)) + (setq from (car chars) + chars (cdr chars)) + (if (consp from) + (setq to (cdr from) + from (car from)) + (setq to from)) + (while (<= from to) + (setq register (get-register from)) + (cond ((null register)) + ((and (memq type '(file t)) + (consp register) + (memq (car register) '(file file-query))) + (insert (if (eq (car register) 'file) + (format "(set-register %S '(file . %S))\n" + from (cdr register)) + (format "(set-register %S '(file-query %S %d))\n" + from (cadr register) (caddr register))))) + ((and (memq type '(region t)) + (stringp register) + (< (length register) session-registers-max-string)) + (insert (format "(set-register %S %S)\n" from register)))) + (setq from (session-next-range-char from))))))) + + +;;;=========================================================================== +;;; Minibuffer history completion, see XEmacs' list-mode +;;;=========================================================================== + +(defvar session-history-help-string + '(concat (if (device-on-window-system-p) + (substitute-command-keys "Click \\<list-mode-map>\\[list-mode-item-mouse-selected] on a history element to select it.\n") "") + (substitute-command-keys "In this buffer, type RET to select the element near point.\n\n")) + "Form the evaluate to get a help string for history elements.") + +(defun session-minibuffer-history-help () + "List history of current minibuffer type. +In Emacs, the *History* buffer talks about \"completions\" instead +\"history elements\". In XEmacs before 21.4.9, selecting an entry might +not work if the minibuffer is non-empty." + (interactive) + (let ((history (symbol-value minibuffer-history-variable))) + (message nil) + (if history + (with-output-to-temp-buffer "*History*" + (cond-emacs-xemacs + (display-completion-list + (sort history #'string-lessp) + :XEMACS + :help-string session-history-help-string + :completion-string "Elements in the history are:")) + (save-excursion + (set-buffer standard-output) + (setq completion-base-size 0))) + (ding) + (session-minibuffer-message " [Empty history]")))) + +(defunx session-minibuffer-message (string) + :emacs-only minibuffer-message + :xemacs-only temp-minibuffer-message) + + +;;;=========================================================================== +;;; Set hooks, load session file +;;;=========================================================================== + +;; easymenu.el is for top-level menus only... both Emacs and XEmacs could +;; profit from a better menu interface... +(defunx session-add-submenu (menu) + "Add the menu MENU to the beginning of the File menu in the menubar. +If the \"File\" menu does not exist, no submenu is added. See +`easy-menu-define' for the format of MENU." + (and menu + :EMACS + (>= emacs-major-version 21) + (boundp 'menu-bar-files-menu) + (let ((keymap (easy-menu-create-menu (car menu) (cdr menu)))) + ;; `easy-menu-get-map' doesn't get the right one => use hard-coded + (define-key menu-bar-files-menu (vector (intern (car menu))) + (cons 'menu-item + (cons (car menu) + (if (not (symbolp keymap)) + (list keymap) + (cons (symbol-function keymap) + (get keymap 'menu-prop))))))) + :XEMACS + (featurep 'menubar) + (let ((current-menubar default-menubar) ;#dynamic + (first (cadar (find-menu-item default-menubar '("File"))))) + (when first + ;; XEmacs-20.4 `add-submenu' does not have 4th arg IN-MENU + (add-submenu '("File") menu + ;; arg BEFORE cannot be retrieved by any + ;; menubar function -- great... + (cond ((vectorp first) (aref first 0)) + ((consp first) (car first)))))))) + +(defunx session-initialize-keys () + "Define some key bindings for package session." + (define-key ctl-x-map [(undo)] 'session-jump-to-last-change) + (define-key ctl-x-map [(control ?\/)] 'session-jump-to-last-change) + (define-key minibuffer-local-map [(meta ?\?)] + 'session-minibuffer-history-help) + (when session-yank-menu-binding + ;; C-down-mouse-3 pops up mode menu under Emacs + (define-key global-map session-yank-menu-binding 'session-popup-yank-menu)) + :EMACS + ;; Emacs doesn't seem to have keymap inheritance... + (define-key minibuffer-local-completion-map [(meta ?\?)] + 'session-minibuffer-history-help) + (define-key minibuffer-local-must-match-map [(meta ?\?)] + 'session-minibuffer-history-help) + (define-key minibuffer-local-ns-map [(meta ?\?)] + 'session-minibuffer-history-help)) + +(defunx session-initialize-menus () + "Add some submenus for package session." + (session-add-submenu '("Open...Recently Visited" + :included file-name-history + :filter session-file-opened-menu-filter + ["Move File Names Of All Buffers To Top" + session-file-opened-recompute] + "---")) + (session-add-submenu '("Open...Recently Changed" + ;;:included session-file-alist + :filter session-file-changed-menu-filter + ["Perform Evaluation For All Buffers" + session-file-changed-recompute] + ["Permanently List Current Buffer " + session-toggle-permanent-flag + ;; :keys must be at third position! + ;;:keys (session-toggle-permanent-flag nil t) + :active buffer-file-name + :style toggle + :selected (session-toggle-permanent-flag nil t)] + "---")) + :XEMACS + (and (featurep 'menubar) + (find-menu-item default-menubar '("Edit")) + (let ((current-menubar default-menubar)) + ;; XEmacs-20.4 `add-submenu' does not have 4th arg IN-MENU + (add-submenu '("Edit") + '("Select and Paste" + :included kill-ring + :filter session-yank-menu-filter) + (cond ((find-menu-item default-menubar + '("Edit" "Delete")) + "Delete") ; why just BEFORE, not AFTER + ((find-menu-item default-menubar + '("Edit" "Paste")) + "Paste") + ((find-menu-item default-menubar + '("Edit" "Undo")) + "Undo")))))) + +(defun session-initialize-do () + "Initialize package session and read previous session file. +Setup hooks and load `session-save-file', see variable `session-initialize'. At +best, this function is called at the end of the Emacs startup, i.e., add +this function to `after-init-hook'." + (when (and session-use-package + (null (get 'session-initialize :initilized-with))) + (when (or (eq session-initialize t) + (memq 'de-saveplace session-initialize)) + ;; Features of package saveplace, which has an auto-init, are covered by + ;; this package. + (when (functionp 'eval-after-load) + (eval-after-load "saveplace" + '(progn + (remove-hook 'find-file-hooks 'save-place-find-file-hook) + (remove-hook 'kill-emacs-hook 'save-place-kill-emacs-hook) + (remove-hook 'kill-buffer-hook 'save-place-to-alist))))) + (when (or (eq session-initialize t) + (memq 'places session-initialize)) + ;; `session-find-file-hook' should be *very* late in `find-file-hooks', + ;; esp. if some package, e.g. crypt, iso-cvt, change the buffer contents: + (add-hook 'find-file-hooks 'session-find-file-hook t) + (add-hook 'find-file-not-found-hooks 'session-find-file-not-found-hook t) + (add-hook 'kill-buffer-hook 'session-kill-buffer-hook) + (if session-register-swap-out + (add-hook 'kill-buffer-hook session-register-swap-out))) + (when (or (eq session-initialize t) (memq 'keys session-initialize)) + (condition-case nil + (session-initialize-keys) + (error nil))) + (when (or (eq session-initialize t) (memq 'menus session-initialize)) + (unless (memq 'session-set-file-name-history find-file-hooks) + ;; already initialized (probably not a good idea to redo for menus) + (add-hook 'find-file-hooks 'session-set-file-name-history) + (session-initialize-menus))) + (when (or (eq session-initialize t) + (memq 'session session-initialize)) + ;; Although desktop and savehist also add its function to + ;; kill-emacs-hook, it might be better to add this function to + ;; `kill-emacs-query-functions' instead. People who want to exit Emacs + ;; without saving the session file might simply call M-x kill-emacs + (add-hook 'kill-emacs-hook 'session-save-session) + (or session-successful-p + (setq session-successful-p + (and session-save-file + (condition-case nil + (progn + ;; load might fail with coding-system = emacs-mule + (unless (load session-save-file t nil t) + (and session-old-save-file + (load session-old-save-file t nil t))) + (run-hooks 'session-after-load-save-file-hook) + t) + (error nil)))))) + (put 'session-initialize :initilized-with session-initialize))) + +(defun session-initialize-and-set (symbol value) + "Custom :set function for `session-use-package'. +It sets the default value of SYMBOL to VALUE and initializes this +package." + (set-default symbol value) ; symbol should be `session-use-package' + (when value + (if (cond-emacs-xemacs + :EMACS (and (boundp 'after-init-time) (null after-init-time)) + :XEMACS (null init-file-loaded)) + ;; in the meantime,`session-use-package' could have been reset to nil + ;; (e.g. when using custom theme with t, user setting with nil) + (add-hook 'after-init-hook 'session-initialize-do) + (session-initialize-do)))) + +;; This must be late in this file as set function is called during loading. +(defcustom session-use-package nil + "Non-nil if package session is in use. +If in use, + - the file name history is updated when visiting a file, + - buffer places are stored when killing a buffer, + - the session file is saved when exiting Emacs, and + - keybindings are menus are added the first time the package is put + into use, see variable `session-initialize'." + :group 'session + :type '(boolean :on "in use" :off "not yet initialized or turned off" + :help-echo "Use package Session, initialize if necessary.") + :require 'session + :set 'session-initialize-and-set) + +;;;###autoload +(defun session-initialize () + "Initialize package session and read previous session file. +Setup hooks and load `session-save-file', see variable `session-initialize'. At +best, this function is called at the end of the Emacs startup, i.e., add +this function to `after-init-hook'." + (interactive) + (put 'session-initialize :initilized-with nil) + (custom-set-variables '(session-use-package t nil (session)))) + +(provide 'session) + +;;; session.el ends here |