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 t then the buffer is considered created."
245 :type
'(repeat function
)
248 (put 'desktop-buffer-handlers
'risky-local-variable t
)
250 (defvar desktop-create-buffer-form
"(desktop-create-buffer 205"
251 "Opening of form for creation of new buffers.")
253 (defcustom desktop-save-hook nil
254 "Hook run before desktop saves the state of Emacs.
255 This is useful for truncating history lists, for example."
259 (defcustom desktop-minor-mode-table
260 '((auto-fill-function auto-fill-mode
)
262 "Table mapping minor mode variables to minor mode functions.
263 Each entry has the form (NAME RESTORE-FUNCTION).
264 NAME is the name of the buffer-local variable indicating that the minor
265 mode is active. RESTORE-FUNCTION is the function to activate the minor mode.
266 called. RESTORE-FUNCTION nil means don't try to restore the minor mode.
267 Only minor modes for which the name of the buffer-local variable
268 and the name of the minor mode function are different have to added to
273 ;; ----------------------------------------------------------------------------
274 (defvar desktop-dirname nil
275 "The directory in which the current desktop file resides.")
277 (defconst desktop-header
278 ";; --------------------------------------------------------------------------
279 ;; Desktop File for Emacs
280 ;; --------------------------------------------------------------------------
281 " "*Header to place in Desktop file.")
283 (defvar desktop-delay-hook nil
284 "Hooks run after all buffers are loaded; intended for internal use.")
286 ;; ----------------------------------------------------------------------------
287 (defun desktop-truncate (l n
)
288 "Truncate LIST to at most N elements destructively."
289 (let ((here (nthcdr (1- n
) l
)))
292 ;; ----------------------------------------------------------------------------
293 (defcustom desktop-clear-preserve-buffers
294 '("*scratch*" "*Messages*")
295 "*Buffer names that `desktop-clear' should not delete."
296 :type
'(repeat string
)
299 (defun desktop-clear ()
301 This kills all buffers except for internal ones
302 and those listed in `desktop-clear-preserve-buffers'."
305 kill-ring-yank-pointer nil
307 search-ring-yank-pointer nil
308 regexp-search-ring nil
309 regexp-search-ring-yank-pointer nil
)
310 (let ((buffers (buffer-list)))
312 (or (member (buffer-name (car buffers
)) desktop-clear-preserve-buffers
)
313 (null (buffer-name (car buffers
)))
314 ;; Don't kill buffers made for internal purposes.
315 (and (not (equal (buffer-name (car buffers
)) ""))
316 (eq (aref (buffer-name (car buffers
)) 0) ?\
))
317 (kill-buffer (car buffers
)))
318 (setq buffers
(cdr buffers
))))
319 (delete-other-windows))
320 ;; ----------------------------------------------------------------------------
321 (add-hook 'kill-emacs-hook
'desktop-kill
)
323 (defun desktop-kill ()
326 (desktop-save desktop-dirname
)
328 (if (yes-or-no-p "Error while saving the desktop. Quit anyway? ")
330 (signal (car err
) (cdr err
)))))))
331 ;; ----------------------------------------------------------------------------
332 (defun desktop-list* (&rest args
)
333 (if (null (cdr args
))
335 (setq args
(nreverse args
))
336 (let ((value (cons (nth 1 args
) (car args
))))
337 (setq args
(cdr (cdr args
)))
339 (setq value
(cons (car args
) value
))
340 (setq args
(cdr args
)))
343 (defun desktop-internal-v2s (val)
344 "Convert VALUE to a pair (QUOTE . TXT); (eval (read TXT)) gives VALUE.
345 TXT is a string that when read and evaluated yields value.
346 QUOTE may be `may' (value may be quoted),
347 `must' (values must be quoted), or nil (value may not be quoted)."
349 ((or (numberp val
) (null val
) (eq t val
))
350 (cons 'may
(prin1-to-string val
)))
352 (let ((copy (copy-sequence val
)))
353 (set-text-properties 0 (length copy
) nil copy
)
354 ;; Get rid of text properties because we cannot read them
355 (cons 'may
(prin1-to-string copy
))))
357 (cons 'must
(prin1-to-string val
)))
362 (let ((res (desktop-internal-v2s el
)))
368 (cons nil
(concat "(vector "
369 (mapconcat (lambda (el)
370 (if (eq (car el
) 'must
)
371 (concat "'" (cdr el
))
376 (cons 'may
(concat "[" (mapconcat 'cdr pass1
" ") "]")))))
383 (let ((q.txt
(desktop-internal-v2s (car p
))))
384 (or anynil
(setq anynil
(null (car q.txt
))))
385 (setq newlist
(cons q.txt newlist
)))
388 (let ((last (desktop-internal-v2s p
))
390 (or anynil
(setq anynil
(null (car last
))))
392 (setq newlist
(cons '(must .
".") newlist
)))
394 (setq newlist
(cons last newlist
))))
395 (setq newlist
(nreverse newlist
))
398 (concat (if use-list
* "(desktop-list* " "(list ")
399 (mapconcat (lambda (el)
400 (if (eq (car el
) 'must
)
401 (concat "'" (cdr el
))
407 (concat "(" (mapconcat 'cdr newlist
" ") ")")))))
409 (cons nil
(concat "(symbol-function '"
410 (substring (prin1-to-string val
) 7 -
1)
413 (let ((pos (prin1-to-string (marker-position val
)))
414 (buf (prin1-to-string (buffer-name (marker-buffer val
)))))
415 (cons nil
(concat "(let ((mk (make-marker)))"
416 " (add-hook 'desktop-delay-hook"
417 " (list 'lambda '() (list 'set-marker mk "
418 pos
" (get-buffer " buf
")))) mk)"))))
420 (cons 'may
"\"Unprintable entity\""))))
422 (defun desktop-value-to-string (val)
423 "Convert VALUE to a string that when read evaluates to the same value.
424 Not all types of values are supported."
425 (let* ((print-escape-newlines t
)
426 (float-output-format nil
)
427 (quote.txt
(desktop-internal-v2s val
))
428 (quote (car quote.txt
))
429 (txt (cdr quote.txt
)))
433 ;; ----------------------------------------------------------------------------
434 (defun desktop-outvar (varspec)
435 "Output a setq statement for variable VAR to the desktop file.
436 The argument VARSPEC may be the variable name VAR (a symbol),
437 or a cons cell of the form (VAR . MAX-SIZE),
438 which means to truncate VAR's value to at most MAX-SIZE elements
439 \(if the value is a list) before saving the value."
442 (setq var
(car varspec
) size
(cdr varspec
))
446 (if (and (integerp size
)
449 (desktop-truncate (eval var
) size
))
453 (desktop-value-to-string (symbol-value var
))
455 ;; ----------------------------------------------------------------------------
456 (defun desktop-save-buffer-p (filename bufname mode
&rest dummy
)
457 "Return t if the desktop should record a particular buffer for next startup.
458 FILENAME is the visited file name, BUFNAME is the buffer name, and
459 MODE is the major mode."
460 (let ((case-fold-search nil
))
461 (and (not (string-match desktop-buffers-not-to-save bufname
))
462 (not (memq mode desktop-modes-not-to-save
))
464 (not (string-match desktop-files-not-to-save filename
)))
465 (and (eq mode
'dired-mode
)
467 (set-buffer (get-buffer bufname
))
468 (not (string-match desktop-files-not-to-save
469 default-directory
))))
471 (memq mode desktop-buffer-modes-to-save
))))))
472 ;; ----------------------------------------------------------------------------
473 (defun desktop-save (dirname)
474 "Save the Desktop file. Parameter DIRNAME specifies where to save desktop."
475 (interactive "DDirectory to save desktop file in: ")
476 (run-hooks 'desktop-save-hook
)
478 (let ((filename (expand-file-name desktop-basefilename dirname
))
495 (cons (let ((special (assq mim desktop-minor-mode-table
)))
500 (mapcar #'car minor-mode-alist
))
503 (list (mark t
) mark-active
)
505 (run-hook-with-args-until-success
506 'desktop-buffer-misc-functions
)
507 (let ((locals desktop-locals-to-save
)
508 (loclist (buffer-local-variables))
511 (let ((here (assq (car locals
) loclist
)))
513 (setq ll
(cons here ll
))
514 (if (member (car locals
) loclist
)
515 (setq ll
(cons (car locals
) ll
)))))
516 (setq locals
(cdr locals
)))
520 (buf (get-buffer-create "*desktop*")))
524 (insert ";; -*- coding: emacs-mule; -*-\n"
526 ";; Created " (current-time-string) "\n"
527 ";; Emacs version " emacs-version
"\n\n"
528 ";; Global section:\n")
529 (mapcar (function desktop-outvar
) desktop-globals-to-save
)
530 (if (memq 'kill-ring desktop-globals-to-save
)
531 (insert "(setq kill-ring-yank-pointer (nthcdr "
533 (- (length kill-ring
) (length kill-ring-yank-pointer
)))
536 (insert "\n;; Buffer section:\n")
538 (function (lambda (l)
539 (if (apply 'desktop-save-buffer-p l
)
541 (insert desktop-create-buffer-form
)
543 (function (lambda (e)
545 (desktop-value-to-string e
))))
549 (setq default-directory dirname
)
550 (if (file-exists-p filename
) (delete-file filename
))
551 (let ((coding-system-for-write 'emacs-mule
))
552 (write-region (point-min) (point-max) filename nil
'nomessage
))))
553 (setq desktop-dirname dirname
))
554 ;; ----------------------------------------------------------------------------
555 (defun desktop-remove ()
556 "Delete the Desktop file and inactivate the desktop system."
559 (let ((filename (concat desktop-dirname desktop-basefilename
)))
560 (setq desktop-dirname nil
)
561 (if (file-exists-p filename
)
562 (delete-file filename
)))))
563 ;; ----------------------------------------------------------------------------
565 (defun desktop-read ()
566 "Read the Desktop file and the files it specifies.
567 This is a no-op when Emacs is running in batch mode."
571 (let ((dirs '("./" "~/")))
573 (not (file-exists-p (expand-file-name
576 (setq dirs
(cdr dirs
)))
577 (setq desktop-dirname
(and dirs
(expand-file-name (car dirs
))))
579 (let ((desktop-last-buffer nil
))
580 ;; `load-with-code-conversion' calls `eval-buffer' which
581 ;; contains a `save-excursion', so we end up with the same
582 ;; buffer before and after the load. This is a problem
583 ;; when the desktop is read initially when Emacs starts up
584 ;; because, if we still are in *scratch* after running
585 ;; `after-init-hook', the splash screen will be displayed.
586 (load (expand-file-name desktop-basefilename desktop-dirname
)
588 (when desktop-last-buffer
589 (switch-to-buffer desktop-last-buffer
))
590 (run-hooks 'desktop-delay-hook
)
591 (setq desktop-delay-hook nil
)
592 (message "Desktop loaded."))
594 ;; ----------------------------------------------------------------------------
596 (defun desktop-load-default ()
597 "Load the `default' start-up library manually.
598 Also inhibit further loading of it. Call this from your `.emacs' file
599 to provide correct modes for autoloaded files."
600 (if (not inhibit-default-init
) ; safety check
603 (setq inhibit-default-init t
))))
604 ;; ----------------------------------------------------------------------------
605 ;; Note: the following functions use the dynamic variable binding in Lisp.
607 (defun desktop-buffer-info-misc-data ()
608 (if (eq major-mode
'Info-mode
)
609 (list Info-current-file
612 (defun desktop-buffer-dired-misc-data ()
613 (if (eq major-mode
'dired-mode
)
615 (expand-file-name dired-directory
)
620 dired-subdir-alist
))))))
622 (defun desktop-buffer-info () "Load an info file."
623 (if (eq 'Info-mode desktop-buffer-major-mode
)
625 (let ((first (nth 0 desktop-buffer-misc
))
626 (second (nth 1 desktop-buffer-misc
)))
627 (when (and first second
)
629 (Info-find-node first second
)
630 (current-buffer))))))
631 ;; ----------------------------------------------------------------------------
632 (defun desktop-buffer-rmail () "Load an RMAIL file."
633 (if (eq 'rmail-mode desktop-buffer-major-mode
)
634 (condition-case error
635 (progn (rmail-input desktop-buffer-file-name
)
636 (if (eq major-mode
'rmail-mode
)
640 (kill-buffer (current-buffer))
642 ;; ----------------------------------------------------------------------------
643 (defun desktop-buffer-mh () "Load a folder in the mh system."
644 (if (eq 'mh-folder-mode desktop-buffer-major-mode
)
648 (mh-visit-folder desktop-buffer-name
)
650 ;; ----------------------------------------------------------------------------
651 (defun desktop-buffer-dired () "Load a directory using dired."
652 (if (eq 'dired-mode desktop-buffer-major-mode
)
653 (if (file-directory-p (file-name-directory (car desktop-buffer-misc
)))
655 (dired (car desktop-buffer-misc
))
656 (mapcar 'dired-maybe-insert-subdir
(cdr desktop-buffer-misc
))
658 (message "Directory %s no longer exists." (car desktop-buffer-misc
))
661 ;; ----------------------------------------------------------------------------
662 (defun desktop-buffer-file () "Load a file."
663 (if desktop-buffer-file-name
664 (if (or (file-exists-p desktop-buffer-file-name
)
665 (and desktop-missing-file-warning
667 "File \"%s\" no longer exists. Re-create? "
668 desktop-buffer-file-name
))))
669 (let ((buf (find-file-noselect desktop-buffer-file-name
)))
671 (switch-to-buffer buf
)
672 (error (pop-to-buffer buf
))))
674 ;; ----------------------------------------------------------------------------
675 ;; Create a buffer, load its file, set is mode, ...; called from Desktop file
678 (defvar desktop-last-buffer nil
679 "Last buffer read. Dynamically bound in `desktop-read'.")
681 (defun desktop-create-buffer (ver desktop-buffer-file-name desktop-buffer-name
682 desktop-buffer-major-mode
683 mim pt mk ro desktop-buffer-misc
685 (let ((hlist desktop-buffer-handlers
)
688 (while (and (not result
) hlist
)
689 (setq handler
(car hlist
))
690 (setq result
(funcall handler
))
691 (setq hlist
(cdr hlist
)))
692 (when (bufferp result
)
693 (setq desktop-last-buffer result
)
695 (if (not (equal (buffer-name) desktop-buffer-name
))
696 (rename-buffer desktop-buffer-name
))
698 (cond ((equal '(t) mim
) (auto-fill-mode 1)) ; backwards compatible
699 ((equal '(nil) mim
) (auto-fill-mode 0))
700 (t (mapcar #'(lambda (minor-mode)
701 (when (functionp minor-mode
)
702 (funcall minor-mode
1)))
708 (setq mark-active
(car (cdr mk
))))
710 ;; Never override file system if the file really is read-only marked.
711 (if ro
(setq buffer-read-only ro
))
713 (let ((this (car locals
)))
715 ;; an entry of this form `(symbol . value)'
717 (make-local-variable (car this
))
718 (set (car this
) (cdr this
)))
719 ;; an entry of the form `symbol'
720 (make-local-variable this
)
722 (setq locals
(cdr locals
))))))
724 ;; Backward compatibility -- update parameters to 205 standards.
725 (defun desktop-buffer (desktop-buffer-file-name desktop-buffer-name
726 desktop-buffer-major-mode
727 mim pt mk ro tl fc cfs cr desktop-buffer-misc
)
728 (desktop-create-buffer 205 desktop-buffer-file-name desktop-buffer-name
729 desktop-buffer-major-mode
(cdr mim
) pt mk ro
731 (list (cons 'truncate-lines tl
)
732 (cons 'fill-column fc
)
733 (cons 'case-fold-search cfs
)
734 (cons 'case-replace cr
)
735 (cons 'overwrite-mode
(car mim
)))))
736 ;; ----------------------------------------------------------------------------
738 ;; If the user set desktop-enable to t with Custom,
739 ;; do the rest of what it takes to use desktop,
740 ;; but do it after finishing loading the init file.
741 (add-hook 'after-init-hook
744 (desktop-load-default)
749 ;;; desktop.el ends here