1 ;;; desktop.el --- save partial status of Emacs when killed
3 ;; Copyright (C) 1993, 1994, 1995, 1997, 2000, 2001
4 ;; Free Software Foundation, Inc.
6 ;; Author: Morten Welinder <terra@diku.dk>
7 ;; Keywords: convenience
8 ;; Favourite-brand-of-beer: None, I hate beer.
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
29 ;; Save the Desktop, i.e.,
30 ;; - some global variables
31 ;; - the list of buffers with associated files. For each buffer also
33 ;; - the default directory
35 ;; - the mark & mark-active
37 ;; - some local variables
39 ;; To use this, first put these two lines in the bottom of your .emacs
40 ;; file (the later the better):
42 ;; (desktop-load-default)
45 ;; Between these two lines you may wish to add something that updates the
46 ;; variables `desktop-globals-to-save' and/or `desktop-locals-to-save'. If
47 ;; for instance you want to save the local variable `foobar' for every buffer
48 ;; in which it is local, you could add the line
50 ;; (setq desktop-locals-to-save (cons 'foobar desktop-locals-to-save))
52 ;; To avoid saving excessive amounts of data you may also wish to add
53 ;; something like the following
55 ;; (add-hook 'kill-emacs-hook
57 ;; (desktop-truncate search-ring 3)
58 ;; (desktop-truncate regexp-search-ring 3)))
60 ;; which will make sure that no more than three search items are saved. You
61 ;; must place this line *after* the `(desktop-load-default)' line. See also
62 ;; the variable `desktop-save-hook'.
64 ;; Start Emacs in the root directory of your "project". The desktop saver
65 ;; is inactive by default. You activate it by M-x desktop-save RET. When
66 ;; you exit the next time the above data will be saved. This ensures that
67 ;; all the files you were editing will be reloaded the next time you start
68 ;; Emacs from the same directory and that points will be set where you
69 ;; left them. If you save a desktop file in your home directory it will
70 ;; act as a default desktop when you start Emacs from a directory that
71 ;; doesn't have its own. I never do this, but you may want to.
73 ;; Some words on minor modes: Most minor modes are controlled by
74 ;; buffer-local variables, which have a standard save / restore
75 ;; mechanism. To handle all minor modes, we take the following
76 ;; approach: (1) check whether the variable name from
77 ;; `minor-mode-alist' is also a function; and (2) use translation
78 ;; table `desktop-minor-mode-table' in the case where the two names
81 ;; By the way: don't use desktop.el to customize Emacs -- the file .emacs
82 ;; in your home directory is used for that. Saving global default values
83 ;; for buffers is an example of misuse.
85 ;; PLEASE NOTE: The kill ring can be saved as specified by the variable
86 ;; `desktop-globals-to-save' (by default it isn't). This may result in saving
87 ;; things you did not mean to keep. Use M-x desktop-clear RET.
89 ;; Thanks to hetrick@phys.uva.nl (Jim Hetrick) for useful ideas.
90 ;; avk@rtsg.mot.com (Andrew V. Klein) for a dired tip.
91 ;; chris@tecc.co.uk (Chris Boucher) for a mark tip.
92 ;; f89-kam@nada.kth.se (Klas Mellbourn) for a mh-e tip.
93 ;; kifer@sbkifer.cs.sunysb.edu (M. Kifer) for a bug hunt.
94 ;; treese@lcs.mit.edu (Win Treese) for ange-ftp tips.
95 ;; pot@cnuce.cnr.it (Francesco Potorti`) for misc. tips.
96 ;; ---------------------------------------------------------------------------
99 ;; Save window configuration.
100 ;; Recognize more minor modes.
102 ;; Start-up with buffer-menu???
106 ;; Make the compilation more silent
108 ;; We use functions from these modules
109 ;; We can't (require 'mh-e) since that wants to load something.
110 (mapcar 'require
'(info dired reporter
)))
111 ;; ----------------------------------------------------------------------------
112 ;; USER OPTIONS -- settings you might want to play with.
113 ;; ----------------------------------------------------------------------------
115 (defgroup desktop nil
116 "Save status of Emacs when you exit."
119 (defcustom desktop-enable nil
120 "*Non-nil enable Desktop to save the state of Emacs when you exit."
124 :initialize
'custom-initialize-default
127 (defcustom desktop-basefilename
128 (convert-standard-filename ".emacs.desktop")
129 "File for Emacs desktop, not including the directory name."
133 (defcustom desktop-missing-file-warning nil
134 "*If non-nil then desktop warns when a file no longer exists.
135 Otherwise it simply ignores that file."
139 (defvar desktop-globals-to-save
140 (list 'desktop-missing-file-warning
141 ;; Feature: saving kill-ring implies saving kill-ring-yank-pointer
148 ;; 'desktop-globals-to-save ; Itself!
150 "List of global variables to save when killing Emacs.
151 An element may be variable name (a symbol)
152 or a cons cell of the form (VAR . MAX-SIZE),
153 which means to truncate VAR's value to at most MAX-SIZE elements
154 \(if the value is a list) before saving the value.")
156 (defvar desktop-locals-to-save
157 (list 'desktop-locals-to-save
; Itself! Think it over.
163 'change-log-default-name
166 "List of local variables to save for each buffer.
167 The variables are saved only when they really are local.")
168 (make-variable-buffer-local 'desktop-locals-to-save
)
170 ;; We skip .log files because they are normally temporary.
171 ;; (ftp) files because they require passwords and whatnot.
172 ;; TAGS files to save time (tags-file-name is saved instead).
173 (defcustom desktop-buffers-not-to-save
174 "\\(^nn\\.a[0-9]+\\|\\.log\\|(ftp)\\|^tags\\|^TAGS\\)$"
175 "Regexp identifying buffers that are to be excluded from saving."
179 ;; Skip ange-ftp files
180 (defcustom desktop-files-not-to-save
182 "Regexp identifying files whose buffers are to be excluded from saving."
186 (defcustom desktop-buffer-modes-to-save
187 '(Info-mode rmail-mode
)
188 "If a buffer is of one of these major modes, save the buffer name.
189 It is up to the functions in `desktop-buffer-handlers' to decide
190 whether the buffer should be recreated or not, and how."
191 :type
'(repeat symbol
)
194 (defcustom desktop-modes-not-to-save nil
195 "List of major modes whose buffers should not be saved."
196 :type
'(repeat symbol
)
199 (defcustom desktop-buffer-major-mode nil
200 "When desktop creates a buffer, this holds the desired Major mode."
204 (defcustom desktop-buffer-file-name nil
205 "When desktop creates a buffer, this holds the file name to visit."
206 :type
'(choice file
(const nil
))
209 (defcustom desktop-buffer-name nil
210 "When desktop creates a buffer, this holds the desired buffer name."
211 :type
'(choice string
(const nil
))
214 (defvar desktop-buffer-misc nil
215 "When desktop creates a buffer, this holds a list of misc info.
216 It is used by the `desktop-buffer-handlers' functions.")
218 (defcustom desktop-buffer-misc-functions
219 '(desktop-buffer-info-misc-data
220 desktop-buffer-dired-misc-data
)
221 "*Functions used to determine auxiliary information for a buffer.
222 These functions are called in order, with no arguments. If a function
223 returns non-nil, its value is saved along with the desktop buffer for
224 which it was called; no further functions will be called.
226 Later, when desktop.el restores the buffers it has saved, each of the
227 `desktop-buffer-handlers' functions will have access to a buffer local
228 variable, named `desktop-buffer-misc', whose value is what the
229 \"misc\" function returned previously."
230 :type
'(repeat function
)
233 (defcustom desktop-buffer-handlers
234 '(desktop-buffer-dired
239 "*List of functions to call in order to create a buffer.
240 The functions are called without explicit parameters but can use the
241 variables `desktop-buffer-major-mode', `desktop-buffer-file-name',
242 `desktop-buffer-name'.
243 If one function returns non-nil, no further functions are called.
244 If the function returns a buffer, then the saved mode settings
245 and variable values for that buffer are copied into it."
246 :type
'(repeat function
)
249 (put 'desktop-buffer-handlers
'risky-local-variable t
)
251 (defvar desktop-create-buffer-form
"(desktop-create-buffer 205"
252 "Opening of form for creation of new buffers.")
254 (defcustom desktop-save-hook nil
255 "Hook run before desktop saves the state of Emacs.
256 This is useful for truncating history lists, for example."
260 (defcustom desktop-minor-mode-table
261 '((auto-fill-function auto-fill-mode
)
263 "Table mapping minor mode variables to minor mode functions.
264 Each entry has the form (NAME RESTORE-FUNCTION).
265 NAME is the name of the buffer-local variable indicating that the minor
266 mode is active. RESTORE-FUNCTION is the function to activate the minor mode.
267 called. RESTORE-FUNCTION nil means don't try to restore the minor mode.
268 Only minor modes for which the name of the buffer-local variable
269 and the name of the minor mode function are different have to added to
274 ;; ----------------------------------------------------------------------------
275 (defvar desktop-dirname nil
276 "The directory in which the current desktop file resides.")
278 (defconst desktop-header
279 ";; --------------------------------------------------------------------------
280 ;; Desktop File for Emacs
281 ;; --------------------------------------------------------------------------
282 " "*Header to place in Desktop file.")
284 (defvar desktop-delay-hook nil
285 "Hooks run after all buffers are loaded; intended for internal use.")
287 ;; ----------------------------------------------------------------------------
288 (defun desktop-truncate (l n
)
289 "Truncate LIST to at most N elements destructively."
290 (let ((here (nthcdr (1- n
) l
)))
293 ;; ----------------------------------------------------------------------------
294 (defcustom desktop-clear-preserve-buffers
295 '("*scratch*" "*Messages*")
296 "*Buffer names that `desktop-clear' should not delete."
297 :type
'(repeat string
)
300 (defun desktop-clear ()
302 This kills all buffers except for internal ones
303 and those listed in `desktop-clear-preserve-buffers'."
306 kill-ring-yank-pointer nil
308 search-ring-yank-pointer nil
309 regexp-search-ring nil
310 regexp-search-ring-yank-pointer nil
)
311 (let ((buffers (buffer-list)))
313 (or (member (buffer-name (car buffers
)) desktop-clear-preserve-buffers
)
314 (null (buffer-name (car buffers
)))
315 ;; Don't kill buffers made for internal purposes.
316 (and (not (equal (buffer-name (car buffers
)) ""))
317 (eq (aref (buffer-name (car buffers
)) 0) ?\
))
318 (kill-buffer (car buffers
)))
319 (setq buffers
(cdr buffers
))))
320 (delete-other-windows))
321 ;; ----------------------------------------------------------------------------
322 (add-hook 'kill-emacs-hook
'desktop-kill
)
324 (defun desktop-kill ()
327 (desktop-save desktop-dirname
)
329 (if (yes-or-no-p "Error while saving the desktop. Quit anyway? ")
331 (signal (car err
) (cdr err
)))))))
332 ;; ----------------------------------------------------------------------------
333 (defun desktop-list* (&rest args
)
334 (if (null (cdr args
))
336 (setq args
(nreverse args
))
337 (let ((value (cons (nth 1 args
) (car args
))))
338 (setq args
(cdr (cdr args
)))
340 (setq value
(cons (car args
) value
))
341 (setq args
(cdr args
)))
344 (defun desktop-internal-v2s (val)
345 "Convert VALUE to a pair (QUOTE . TXT); (eval (read TXT)) gives VALUE.
346 TXT is a string that when read and evaluated yields value.
347 QUOTE may be `may' (value may be quoted),
348 `must' (values must be quoted), or nil (value may not be quoted)."
350 ((or (numberp val
) (null val
) (eq t val
))
351 (cons 'may
(prin1-to-string val
)))
353 (let ((copy (copy-sequence val
)))
354 (set-text-properties 0 (length copy
) nil copy
)
355 ;; Get rid of text properties because we cannot read them
356 (cons 'may
(prin1-to-string copy
))))
358 (cons 'must
(prin1-to-string val
)))
363 (let ((res (desktop-internal-v2s el
)))
369 (cons nil
(concat "(vector "
370 (mapconcat (lambda (el)
371 (if (eq (car el
) 'must
)
372 (concat "'" (cdr el
))
377 (cons 'may
(concat "[" (mapconcat 'cdr pass1
" ") "]")))))
384 (let ((q.txt
(desktop-internal-v2s (car p
))))
385 (or anynil
(setq anynil
(null (car q.txt
))))
386 (setq newlist
(cons q.txt newlist
)))
389 (let ((last (desktop-internal-v2s p
))
391 (or anynil
(setq anynil
(null (car last
))))
393 (setq newlist
(cons '(must .
".") newlist
)))
395 (setq newlist
(cons last newlist
))))
396 (setq newlist
(nreverse newlist
))
399 (concat (if use-list
* "(desktop-list* " "(list ")
400 (mapconcat (lambda (el)
401 (if (eq (car el
) 'must
)
402 (concat "'" (cdr el
))
408 (concat "(" (mapconcat 'cdr newlist
" ") ")")))))
410 (cons nil
(concat "(symbol-function '"
411 (substring (prin1-to-string val
) 7 -
1)
414 (let ((pos (prin1-to-string (marker-position val
)))
415 (buf (prin1-to-string (buffer-name (marker-buffer val
)))))
416 (cons nil
(concat "(let ((mk (make-marker)))"
417 " (add-hook 'desktop-delay-hook"
418 " (list 'lambda '() (list 'set-marker mk "
419 pos
" (get-buffer " buf
")))) mk)"))))
421 (cons 'may
"\"Unprintable entity\""))))
423 (defun desktop-value-to-string (val)
424 "Convert VALUE to a string that when read evaluates to the same value.
425 Not all types of values are supported."
426 (let* ((print-escape-newlines t
)
427 (float-output-format nil
)
428 (quote.txt
(desktop-internal-v2s val
))
429 (quote (car quote.txt
))
430 (txt (cdr quote.txt
)))
434 ;; ----------------------------------------------------------------------------
435 (defun desktop-outvar (varspec)
436 "Output a setq statement for variable VAR to the desktop file.
437 The argument VARSPEC may be the variable name VAR (a symbol),
438 or a cons cell of the form (VAR . MAX-SIZE),
439 which means to truncate VAR's value to at most MAX-SIZE elements
440 \(if the value is a list) before saving the value."
443 (setq var
(car varspec
) size
(cdr varspec
))
447 (if (and (integerp size
)
450 (desktop-truncate (eval var
) size
))
454 (desktop-value-to-string (symbol-value var
))
456 ;; ----------------------------------------------------------------------------
457 (defun desktop-save-buffer-p (filename bufname mode
&rest dummy
)
458 "Return t if the desktop should record a particular buffer for next startup.
459 FILENAME is the visited file name, BUFNAME is the buffer name, and
460 MODE is the major mode."
461 (let ((case-fold-search nil
))
462 (and (not (string-match desktop-buffers-not-to-save bufname
))
463 (not (memq mode desktop-modes-not-to-save
))
465 (not (string-match desktop-files-not-to-save filename
)))
466 (and (eq mode
'dired-mode
)
468 (set-buffer (get-buffer bufname
))
469 (not (string-match desktop-files-not-to-save
470 default-directory
))))
472 (memq mode desktop-buffer-modes-to-save
))))))
473 ;; ----------------------------------------------------------------------------
474 (defcustom desktop-relative-file-names nil
475 "*Store relative file names in the desktop file."
479 (defun desktop-save (dirname)
480 "Save the Desktop file. Parameter DIRNAME specifies where to save desktop."
481 (interactive "DDirectory to save desktop file in: ")
482 (run-hooks 'desktop-save-hook
)
484 (let ((filename (expand-file-name desktop-basefilename dirname
))
491 (let ((bn (buffer-file-name)))
493 (if desktop-relative-file-names
494 (file-relative-name bn dirname
)
505 (cons (let ((special (assq mim desktop-minor-mode-table
)))
510 (mapcar #'car minor-mode-alist
))
513 (list (mark t
) mark-active
)
515 (run-hook-with-args-until-success
516 'desktop-buffer-misc-functions
)
517 (let ((locals desktop-locals-to-save
)
518 (loclist (buffer-local-variables))
521 (let ((here (assq (car locals
) loclist
)))
523 (setq ll
(cons here ll
))
524 (if (member (car locals
) loclist
)
525 (setq ll
(cons (car locals
) ll
)))))
526 (setq locals
(cdr locals
)))
530 (buf (get-buffer-create "*desktop*")))
534 (insert ";; -*- coding: emacs-mule; -*-\n"
536 ";; Created " (current-time-string) "\n"
537 ";; Emacs version " emacs-version
"\n\n"
538 ";; Global section:\n")
539 (mapcar (function desktop-outvar
) desktop-globals-to-save
)
540 (if (memq 'kill-ring desktop-globals-to-save
)
541 (insert "(setq kill-ring-yank-pointer (nthcdr "
543 (- (length kill-ring
) (length kill-ring-yank-pointer
)))
546 (insert "\n;; Buffer section:\n")
548 (function (lambda (l)
549 (if (apply 'desktop-save-buffer-p l
)
551 (insert desktop-create-buffer-form
)
553 (function (lambda (e)
555 (desktop-value-to-string e
))))
559 (setq default-directory dirname
)
560 (if (file-exists-p filename
) (delete-file filename
))
561 (let ((coding-system-for-write 'emacs-mule
))
562 (write-region (point-min) (point-max) filename nil
'nomessage
))))
563 (setq desktop-dirname dirname
))
564 ;; ----------------------------------------------------------------------------
565 (defun desktop-remove ()
566 "Delete the Desktop file and inactivate the desktop system."
569 (let ((filename (concat desktop-dirname desktop-basefilename
)))
570 (setq desktop-dirname nil
)
571 (if (file-exists-p filename
)
572 (delete-file filename
)))))
573 ;; ----------------------------------------------------------------------------
575 (defun desktop-read ()
576 "Read the Desktop file and the files it specifies.
577 This is a no-op when Emacs is running in batch mode."
581 (let ((dirs '("./" "~/")))
583 (not (file-exists-p (expand-file-name
586 (setq dirs
(cdr dirs
)))
587 (setq desktop-dirname
(and dirs
(expand-file-name (car dirs
))))
589 (let ((desktop-last-buffer nil
))
590 ;; `load-with-code-conversion' calls `eval-buffer' which
591 ;; contains a `save-excursion', so we end up with the same
592 ;; buffer before and after the load. This is a problem
593 ;; when the desktop is read initially when Emacs starts up
594 ;; because, if we still are in *scratch* after running
595 ;; `after-init-hook', the splash screen will be displayed.
596 (load (expand-file-name desktop-basefilename desktop-dirname
)
598 (when desktop-last-buffer
599 (switch-to-buffer desktop-last-buffer
))
600 (run-hooks 'desktop-delay-hook
)
601 (setq desktop-delay-hook nil
)
602 (message "Desktop loaded."))
604 ;; ----------------------------------------------------------------------------
606 (defun desktop-load-default ()
607 "Load the `default' start-up library manually.
608 Also inhibit further loading of it. Call this from your `.emacs' file
609 to provide correct modes for autoloaded files."
610 (if (not inhibit-default-init
) ; safety check
613 (setq inhibit-default-init t
))))
614 ;; ----------------------------------------------------------------------------
615 ;; Note: the following functions use the dynamic variable binding in Lisp.
617 (defun desktop-buffer-info-misc-data ()
618 (if (eq major-mode
'Info-mode
)
619 (list Info-current-file
622 (defun desktop-buffer-dired-misc-data ()
623 (if (eq major-mode
'dired-mode
)
625 (expand-file-name dired-directory
)
630 dired-subdir-alist
))))))
632 (defun desktop-buffer-info () "Load an info file."
633 (if (eq 'Info-mode desktop-buffer-major-mode
)
635 (let ((first (nth 0 desktop-buffer-misc
))
636 (second (nth 1 desktop-buffer-misc
)))
637 (when (and first second
)
639 (Info-find-node first second
)
640 (current-buffer))))))
641 ;; ----------------------------------------------------------------------------
642 (defun desktop-buffer-rmail () "Load an RMAIL file."
643 (if (eq 'rmail-mode desktop-buffer-major-mode
)
644 (condition-case error
645 (progn (rmail-input desktop-buffer-file-name
)
646 (if (eq major-mode
'rmail-mode
)
650 (kill-buffer (current-buffer))
652 ;; ----------------------------------------------------------------------------
653 (defun desktop-buffer-mh () "Load a folder in the mh system."
654 (if (eq 'mh-folder-mode desktop-buffer-major-mode
)
658 (mh-visit-folder desktop-buffer-name
)
660 ;; ----------------------------------------------------------------------------
661 (defun desktop-buffer-dired () "Load a directory using dired."
662 (if (eq 'dired-mode desktop-buffer-major-mode
)
663 (if (file-directory-p (file-name-directory (car desktop-buffer-misc
)))
665 (dired (car desktop-buffer-misc
))
666 (mapcar 'dired-maybe-insert-subdir
(cdr desktop-buffer-misc
))
668 (message "Directory %s no longer exists." (car desktop-buffer-misc
))
671 ;; ----------------------------------------------------------------------------
672 (defun desktop-buffer-file () "Load a file."
673 (if desktop-buffer-file-name
674 (if (or (file-exists-p desktop-buffer-file-name
)
675 (and desktop-missing-file-warning
677 "File \"%s\" no longer exists. Re-create? "
678 desktop-buffer-file-name
))))
679 (let ((buf (find-file-noselect desktop-buffer-file-name
)))
681 (switch-to-buffer buf
)
682 (error (pop-to-buffer buf
)))
685 ;; ----------------------------------------------------------------------------
686 ;; Create a buffer, load its file, set is mode, ...; called from Desktop file
689 (defvar desktop-last-buffer nil
690 "Last buffer read. Dynamically bound in `desktop-read'.")
692 (defun desktop-create-buffer (ver desktop-buffer-file-name desktop-buffer-name
693 desktop-buffer-major-mode
694 mim pt mk ro desktop-buffer-misc
696 (let ((hlist desktop-buffer-handlers
)
699 (while (and (not result
) hlist
)
700 (setq handler
(car hlist
))
701 (setq result
(funcall handler
))
702 (setq hlist
(cdr hlist
)))
703 (when (bufferp result
)
704 (setq desktop-last-buffer result
)
706 (if (not (equal (buffer-name) desktop-buffer-name
))
707 (rename-buffer desktop-buffer-name
))
709 (cond ((equal '(t) mim
) (auto-fill-mode 1)) ; backwards compatible
710 ((equal '(nil) mim
) (auto-fill-mode 0))
711 (t (mapcar #'(lambda (minor-mode)
712 (when (functionp minor-mode
)
713 (funcall minor-mode
1)))
719 (setq mark-active
(car (cdr mk
))))
721 ;; Never override file system if the file really is read-only marked.
722 (if ro
(setq buffer-read-only ro
))
724 (let ((this (car locals
)))
726 ;; an entry of this form `(symbol . value)'
728 (make-local-variable (car this
))
729 (set (car this
) (cdr this
)))
730 ;; an entry of the form `symbol'
731 (make-local-variable this
)
733 (setq locals
(cdr locals
))))))
735 ;; Backward compatibility -- update parameters to 205 standards.
736 (defun desktop-buffer (desktop-buffer-file-name desktop-buffer-name
737 desktop-buffer-major-mode
738 mim pt mk ro tl fc cfs cr desktop-buffer-misc
)
739 (desktop-create-buffer 205 desktop-buffer-file-name desktop-buffer-name
740 desktop-buffer-major-mode
(cdr mim
) pt mk ro
742 (list (cons 'truncate-lines tl
)
743 (cons 'fill-column fc
)
744 (cons 'case-fold-search cfs
)
745 (cons 'case-replace cr
)
746 (cons 'overwrite-mode
(car mim
)))))
747 ;; ----------------------------------------------------------------------------
749 ;; If the user set desktop-enable to t with Custom,
750 ;; do the rest of what it takes to use desktop,
751 ;; but do it after finishing loading the init file.
752 (add-hook 'after-init-hook
755 (desktop-load-default)
760 ;;; desktop.el ends here