(Resizing Windows): Document the `preserve-before' argument of the
[bpt/emacs.git] / lisp / desktop.el
CommitLineData
6343ed61
RS
1;;; desktop.el --- save partial status of Emacs when killed
2
291e3b68
GM
3;; Copyright (C) 1993, 1994, 1995, 1997, 2000, 2001
4;; Free Software Foundation, Inc.
6343ed61
RS
5
6;; Author: Morten Welinder <terra@diku.dk>
16906a65 7;; Maintainter: Lars Hansen <larsh@math.ku.dk>
3ea96cac 8;; Keywords: convenience
b6105c76 9;; Favourite-brand-of-beer: None, I hate beer.
6343ed61
RS
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
b578f267
EN
24;; along with GNU Emacs; see the file COPYING. If not, write to the
25;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26;; Boston, MA 02111-1307, USA.
6343ed61
RS
27
28;;; Commentary:
29
0b9bd504
RS
30;; Save the Desktop, i.e.,
31;; - some global variables
32;; - the list of buffers with associated files. For each buffer also
33;; - the major mode
34;; - the default directory
35;; - the point
36;; - the mark & mark-active
37;; - buffer-read-only
ec4c6f22 38;; - some local variables
6343ed61 39
c5b31c4d
LH
40;; To use this, use customize to turn on desktop-save-mode or add the
41;; following line somewhere in your .emacs file:
0b9bd504 42;;
c5b31c4d 43;; (desktop-save-mode 1)
0b9bd504 44;;
c5b31c4d
LH
45;; For further usage information, look at the section
46;; "Saving Emacs Sessions" in the GNU Emacs Manual.
47
48;; When the desktop module is loaded, the function `desktop-kill' is
49;; added to the `kill-emacs-hook'. This function is responsible for
50;; saving the desktop when Emacs is killed. Furthermore an anonymous
51;; function is added to the `after-init-hook'. This function is
52;; responsible for loading the desktop when Emacs is started.
ec4c6f22 53
47640244
GM
54;; Some words on minor modes: Most minor modes are controlled by
55;; buffer-local variables, which have a standard save / restore
56;; mechanism. To handle all minor modes, we take the following
57;; approach: (1) check whether the variable name from
58;; `minor-mode-alist' is also a function; and (2) use translation
59;; table `desktop-minor-mode-table' in the case where the two names
60;; are not the same.
61
ec4c6f22
RS
62;; By the way: don't use desktop.el to customize Emacs -- the file .emacs
63;; in your home directory is used for that. Saving global default values
64;; for buffers is an example of misuse.
65
0b9bd504
RS
66;; PLEASE NOTE: The kill ring can be saved as specified by the variable
67;; `desktop-globals-to-save' (by default it isn't). This may result in saving
68;; things you did not mean to keep. Use M-x desktop-clear RET.
ec4c6f22 69
fa379636
KH
70;; Thanks to hetrick@phys.uva.nl (Jim Hetrick) for useful ideas.
71;; avk@rtsg.mot.com (Andrew V. Klein) for a dired tip.
72;; chris@tecc.co.uk (Chris Boucher) for a mark tip.
73;; f89-kam@nada.kth.se (Klas Mellbourn) for a mh-e tip.
74;; kifer@sbkifer.cs.sunysb.edu (M. Kifer) for a bug hunt.
f4c73d07 75;; treese@lcs.mit.edu (Win Treese) for ange-ftp tips.
253406b9 76;; pot@cnuce.cnr.it (Francesco Potorti`) for misc. tips.
0b9bd504
RS
77;; ---------------------------------------------------------------------------
78;; TODO:
79;;
80;; Save window configuration.
81;; Recognize more minor modes.
82;; Save mark rings.
6343ed61
RS
83
84;;; Code:
85
4a2fce7a 86(defvar desktop-file-version "206"
05f1c4ec 87 "Version number of desktop file format.
4a2fce7a
RS
88Written into the desktop file and used at desktop read to provide
89backward compatibility.")
90
ec4c6f22 91;; ----------------------------------------------------------------------------
0b9bd504
RS
92;; USER OPTIONS -- settings you might want to play with.
93;; ----------------------------------------------------------------------------
bbf5eb28
RS
94
95(defgroup desktop nil
96 "Save status of Emacs when you exit."
97 :group 'frames)
98
c5b31c4d
LH
99;;;###autoload
100(define-minor-mode desktop-save-mode
101 "Toggle desktop saving mode.
102With numeric ARG, turn desktop saving on if ARG is positive, off
103otherwise. See variable `desktop-save' for a description of when the
104desktop is saved."
105 :global t
106 :group 'desktop)
107
108;; Maintained for backward compatibility
109(defvaralias 'desktop-enable 'desktop-save-mode)
110(make-obsolete-variable 'desktop-enable 'desktop-save-mode)
813dbb2d 111
4a2fce7a 112(defcustom desktop-save 'ask-if-new
c5b31c4d
LH
113 "*Specifies whether the desktop should be saved when it is killed.
114A desktop is killed when the user changes desktop or quits Emacs.
115Possible values are:
4a2fce7a
RS
116 t -- always save.
117 ask -- always ask.
118 ask-if-new -- ask if no desktop file exists, otherwise just save.
119 ask-if-exists -- ask if desktop file exists, otherwise don't save.
120 if-exists -- save if desktop file exists, otherwise don't save.
121 nil -- never save.
c5b31c4d
LH
122The desktop is never saved when `desktop-save-mode' is nil.
123The variables `desktop-directory' and `desktop-base-file-name'
124determine where the desktop is saved."
4a2fce7a
RS
125 :type '(choice
126 (const :tag "Always save" t)
127 (const :tag "Always ask" ask)
128 (const :tag "Ask if desktop file is new, else do save" ask-if-new)
129 (const :tag "Ask if desktop file exists, else don't save" ask-if-exists)
130 (const :tag "Save if desktop file exists, else don't" if-exists)
131 (const :tag "Never save" nil))
132 :group 'desktop)
133
134(defcustom desktop-base-file-name
fc715501 135 (convert-standard-filename ".emacs.desktop")
c5b31c4d 136 "Name of file for Emacs desktop, excluding the directory part."
813dbb2d
RS
137 :type 'file
138 :group 'desktop)
7065d42f 139(defvaralias 'desktop-basefilename 'desktop-base-file-name)
6343ed61 140
4a2fce7a
RS
141(defcustom desktop-path '("." "~")
142 "List of directories to search for the desktop file.
143The base name of the file is specified in `desktop-base-file-name'."
144 :type '(repeat directory)
145 :group 'desktop)
146
bbf5eb28 147(defcustom desktop-missing-file-warning nil
ebb39555
LH
148 "*If non-nil then `desktop-read' asks if a non-existent file should be recreated.
149Also pause for a moment to display message about errors signaled in
150`desktop-buffer-mode-handlers'.
151
152If nil, just print error messages in the message buffer."
bbf5eb28
RS
153 :type 'boolean
154 :group 'desktop)
6343ed61 155
4a2fce7a 156(defcustom desktop-no-desktop-file-hook nil
c5b31c4d 157 "Normal hook run when `desktop-read' can't find a desktop file.
4a2fce7a
RS
158May e.g. be used to show a dired buffer."
159 :type 'hook
160 :group 'desktop)
161
162(defcustom desktop-after-read-hook nil
05f1c4ec 163 "Normal hook run after a successful `desktop-read'.
4a2fce7a
RS
164May e.g. be used to show a buffer list."
165 :type 'hook
166 :group 'desktop)
167
168(defcustom desktop-save-hook nil
c5b31c4d 169 "Normal hook run before the desktop is saved in a desktop file.
4a2fce7a
RS
170This is useful for truncating history lists, for example."
171 :type 'hook
172 :group 'desktop)
173
340db502
LH
174(defcustom desktop-globals-to-save
175 '(desktop-missing-file-warning
176 tags-file-name
177 tags-table-list
178 search-ring
179 regexp-search-ring
180 register-alist)
c5b31c4d
LH
181 "List of global variables saved by `desktop-save'.
182An element may be variable name (a symbol) or a cons cell of the form
183\(VAR . MAX-SIZE), which means to truncate VAR's value to at most
184MAX-SIZE elements (if the value is a list) before saving the value.
4a2fce7a
RS
185Feature: Saving `kill-ring' implies saving `kill-ring-yank-pointer'."
186 :type '(repeat (restricted-sexp :match-alternatives (symbolp consp)))
187 :group 'desktop)
188
340db502
LH
189(defcustom desktop-globals-to-clear
190 '(kill-ring
191 kill-ring-yank-pointer
192 search-ring
193 search-ring-yank-pointer
194 regexp-search-ring
195 regexp-search-ring-yank-pointer)
c5b31c4d 196 "List of global variables to clear by `desktop-clear'.
4a2fce7a
RS
197An element may be variable name (a symbol) or a cons cell of the form
198\(VAR . FORM). Symbols are set to nil and for cons cells VAR is set
199to the value obtained by evaluateing FORM."
200 :type '(repeat (restricted-sexp :match-alternatives (symbolp consp)))
201 :group 'desktop)
202
203(defcustom desktop-clear-preserve-buffers-regexp
c5b31c4d 204 "^\\(\\*scratch\\*\\|\\*Messages\\*\\|\\*tramp/.+\\*\\)$"
340db502
LH
205 "Regexp identifying buffers that `desktop-clear' should not delete.
206See also `desktop-clear-preserve-buffers'."
4a2fce7a
RS
207 :type 'regexp
208 :group 'desktop)
209
c5b31c4d
LH
210(defcustom desktop-clear-preserve-buffers nil
211 "*List of buffer names that `desktop-clear' should not delete.
340db502 212See also `desktop-clear-preserve-buffers-regexp'."
4a2fce7a
RS
213 :type '(repeat string)
214 :group 'desktop)
340db502
LH
215
216(defcustom desktop-locals-to-save
217 '(desktop-locals-to-save ; Itself! Think it over.
218 truncate-lines
219 case-fold-search
220 case-replace
221 fill-column
222 overwrite-mode
223 change-log-default-name
224 line-number-mode
225 buffer-file-coding-system)
577ed2b2 226 "List of local variables to save for each buffer.
c5b31c4d
LH
227The variables are saved only when they really are local."
228 :type '(repeat symbol)
229 :group 'desktop)
de9e2828 230(make-variable-buffer-local 'desktop-locals-to-save)
ec4c6f22 231
0b9bd504 232;; We skip .log files because they are normally temporary.
a7acbbe4 233;; (ftp) files because they require passwords and whatnot.
0b9bd504 234;; TAGS files to save time (tags-file-name is saved instead).
bbf5eb28 235(defcustom desktop-buffers-not-to-save
4a2fce7a
RS
236 "\\(^nn\\.a[0-9]+\\|\\.log\\|(ftp)\\|^tags\\|^TAGS\\)$"
237 "Regexp identifying buffers that are to be excluded from saving."
238 :type 'regexp
239 :group 'desktop)
6343ed61 240
c5b31c4d 241;; Skip tramp and ange-ftp files
bbf5eb28 242(defcustom desktop-files-not-to-save
fa379636 243 "^/[^/:]*:"
bbf5eb28
RS
244 "Regexp identifying files whose buffers are to be excluded from saving."
245 :type 'regexp
246 :group 'desktop)
fa379636 247
80f9f3db
SM
248(defcustom desktop-modes-not-to-save nil
249 "List of major modes whose buffers should not be saved."
250 :type '(repeat symbol)
251 :group 'desktop)
252
4a2fce7a
RS
253(defcustom desktop-file-name-format 'absolute
254 "*Format in which desktop file names should be saved.
255Possible values are:
256 absolute -- Absolute file name.
257 tilde -- Relative to ~.
258 local -- Relative to directory of desktop file."
259 :type '(choice (const absolute) (const tilde) (const local))
bbf5eb28 260 :group 'desktop)
569c754e 261
e5780ae1 262;;;###autoload
ebb39555
LH
263(defvar desktop-save-buffer nil
264 "When non-nil, save buffer status in desktop file.
e5780ae1 265This variable becomes buffer local when set.
ebb39555
LH
266
267If the value is a function, it called by `desktop-save' with argument
268DESKTOP-DIRNAME to obtain auxiliary information to saved in the desktop
269file along with the state of the buffer for which it was called.
b6b70cda 270
c5b31c4d 271When file names are returned, they should be formatted using the call
e5780ae1 272\"(desktop-file-name FILE-NAME DESKTOP-DIRNAME)\".
4a2fce7a 273
e5780ae1 274Later, when `desktop-read' calls a function in `desktop-buffer-mode-handlers'
ebb39555
LH
275to restore the buffer, the auxiliary information is passed as the argument
276DESKTOP-BUFFER-MISC.")
277(make-variable-buffer-local 'desktop-save-buffer)
278(make-obsolete-variable 'desktop-buffer-modes-to-save
279 'desktop-save-buffer)
e5780ae1 280(make-obsolete-variable 'desktop-buffer-misc-functions
ebb39555 281 'desktop-save-buffer)
b6b70cda 282
340db502
LH
283(defcustom desktop-buffer-mode-handlers
284 '((dired-mode . dired-restore-desktop-buffer)
285 (rmail-mode . rmail-restore-desktop-buffer)
286 (mh-folder-mode . mh-restore-desktop-buffer)
287 (Info-mode . Info-restore-desktop-buffer))
e5780ae1
LH
288 "Alist of major mode specific functions to restore a desktop buffer.
289Functions are called by `desktop-read'. List elements must have the form
55775448 290\(MAJOR-MODE . RESTORE-BUFFER-FUNCTION).
e5780ae1
LH
291
292Buffers with a major mode not specified here, are restored by the default
293handler `desktop-restore-file-buffer'.
294
55775448 295Handlers are called with argument list
4a2fce7a 296
9bcabb45 297 (DESKTOP-BUFFER-FILE-NAME DESKTOP-BUFFER-NAME DESKTOP-BUFFER-MISC)
e5780ae1
LH
298
299Furthermore, they may use the following variables:
300
301 desktop-file-version
4a2fce7a
RS
302 desktop-buffer-major-mode
303 desktop-buffer-minor-modes
304 desktop-buffer-point
305 desktop-buffer-mark
306 desktop-buffer-read-only
4a2fce7a
RS
307 desktop-buffer-locals
308
e5780ae1 309If a handler returns a buffer, then the saved mode settings
0a8c8225 310and variable values for that buffer are copied into it."
e5780ae1 311 :type 'alist
bbf5eb28 312 :group 'desktop)
ec4c6f22 313
e5780ae1
LH
314(put 'desktop-buffer-mode-handlers 'risky-local-variable t)
315(make-obsolete-variable 'desktop-buffer-handlers
316 'desktop-buffer-mode-handlers)
b6b70cda 317
47640244
GM
318(defcustom desktop-minor-mode-table
319 '((auto-fill-function auto-fill-mode)
320 (vc-mode nil))
321 "Table mapping minor mode variables to minor mode functions.
322Each entry has the form (NAME RESTORE-FUNCTION).
323NAME is the name of the buffer-local variable indicating that the minor
324mode is active. RESTORE-FUNCTION is the function to activate the minor mode.
325called. RESTORE-FUNCTION nil means don't try to restore the minor mode.
326Only minor modes for which the name of the buffer-local variable
7bfa55b3 327and the name of the minor mode function are different have to be added to
47640244
GM
328this table."
329 :type 'sexp
330 :group 'desktop)
331
0b9bd504
RS
332;; ----------------------------------------------------------------------------
333(defvar desktop-dirname nil
c5b31c4d 334 "The directory in which the desktop file should be saved.")
6343ed61
RS
335
336(defconst desktop-header
0b9bd504
RS
337";; --------------------------------------------------------------------------
338;; Desktop File for Emacs
339;; --------------------------------------------------------------------------
6343ed61 340" "*Header to place in Desktop file.")
de9e2828
RS
341
342(defvar desktop-delay-hook nil
343 "Hooks run after all buffers are loaded; intended for internal use.")
813dbb2d 344
0b9bd504 345;; ----------------------------------------------------------------------------
05f1c4ec 346(defun desktop-truncate (list n)
ec4c6f22 347 "Truncate LIST to at most N elements destructively."
05f1c4ec 348 (let ((here (nthcdr (1- n) list)))
ec4c6f22 349 (if (consp here)
de9e2828 350 (setcdr here nil))))
f9be4574 351
4a2fce7a 352;; ----------------------------------------------------------------------------
f9be4574
RS
353(defun desktop-clear ()
354 "Empty the Desktop.
c5b31c4d
LH
355This kills all buffers except for internal ones and those matching
356`desktop-clear-preserve-buffers-regexp' or listed in
357`desktop-clear-preserve-buffers'. Furthermore, it clears the
4a2fce7a 358variables listed in `desktop-globals-to-clear'."
6343ed61 359 (interactive)
4a2fce7a
RS
360 (dolist (var desktop-globals-to-clear)
361 (if (symbolp var)
362 (eval `(setq-default ,var nil))
363 (eval `(setq-default ,(car var) ,(cdr var)))))
f9be4574
RS
364 (let ((buffers (buffer-list)))
365 (while buffers
4a2fce7a
RS
366 (let ((bufname (buffer-name (car buffers))))
367 (or
368 (null bufname)
369 (string-match desktop-clear-preserve-buffers-regexp bufname)
370 (member bufname desktop-clear-preserve-buffers)
371 ;; Don't kill buffers made for internal purposes.
372 (and (not (equal bufname "")) (eq (aref bufname 0) ?\ ))
373 (kill-buffer (car buffers))))
f9be4574 374 (setq buffers (cdr buffers))))
b6105c76 375 (delete-other-windows))
4a2fce7a 376
0b9bd504 377;; ----------------------------------------------------------------------------
de9e2828 378(add-hook 'kill-emacs-hook 'desktop-kill)
ec4c6f22 379
6343ed61 380(defun desktop-kill ()
c5b31c4d 381 "If `desktop-save-mode' is non-nil, do what `desktop-save' says to do.
4a2fce7a
RS
382If the desktop should be saved and `desktop-dirname'
383is nil, ask the user where to save the desktop."
384 (when
385 (and
c5b31c4d 386 desktop-save-mode
73b0b745 387 (let ((exists (file-exists-p (expand-file-name desktop-base-file-name desktop-dirname))))
4a2fce7a 388 (or
d12d9396 389 (eq desktop-save t)
4a2fce7a
RS
390 (and exists (memq desktop-save '(ask-if-new if-exists)))
391 (and
392 (or
393 (memq desktop-save '(ask ask-if-new))
394 (and exists (eq desktop-save 'ask-if-exists)))
395 (y-or-n-p "Save desktop? ")))))
396 (unless desktop-dirname
397 (setq desktop-dirname
73b0b745
JB
398 (file-name-as-directory
399 (expand-file-name
400 (call-interactively
401 (lambda (dir) (interactive "DDirectory for desktop file: ") dir))))))
4a2fce7a
RS
402 (condition-case err
403 (desktop-save desktop-dirname)
404 (file-error
405 (unless (yes-or-no-p "Error while saving the desktop. Ignore? ")
406 (signal (car err) (cdr err)))))))
407
0b9bd504 408;; ----------------------------------------------------------------------------
ed2f7fc8
RS
409(defun desktop-list* (&rest args)
410 (if (null (cdr args))
411 (car args)
412 (setq args (nreverse args))
413 (let ((value (cons (nth 1 args) (car args))))
414 (setq args (cdr (cdr args)))
415 (while args
416 (setq value (cons (car args) value))
417 (setq args (cdr args)))
418 value)))
419
4a2fce7a 420;; ----------------------------------------------------------------------------
05f1c4ec 421(defun desktop-internal-v2s (value)
577ed2b2
RS
422 "Convert VALUE to a pair (QUOTE . TXT); (eval (read TXT)) gives VALUE.
423TXT is a string that when read and evaluated yields value.
424QUOTE may be `may' (value may be quoted),
425`must' (values must be quoted), or nil (value may not be quoted)."
de9e2828 426 (cond
05f1c4ec
JB
427 ((or (numberp value) (null value) (eq t value))
428 (cons 'may (prin1-to-string value)))
429 ((stringp value)
430 (let ((copy (copy-sequence value)))
95bf6435
RS
431 (set-text-properties 0 (length copy) nil copy)
432 ;; Get rid of text properties because we cannot read them
433 (cons 'may (prin1-to-string copy))))
05f1c4ec
JB
434 ((symbolp value)
435 (cons 'must (prin1-to-string value)))
436 ((vectorp value)
de9e2828
RS
437 (let* ((special nil)
438 (pass1 (mapcar
439 (lambda (el)
440 (let ((res (desktop-internal-v2s el)))
441 (if (null (car res))
442 (setq special t))
443 res))
05f1c4ec 444 value)))
de9e2828
RS
445 (if special
446 (cons nil (concat "(vector "
447 (mapconcat (lambda (el)
448 (if (eq (car el) 'must)
449 (concat "'" (cdr el))
450 (cdr el)))
451 pass1
452 " ")
453 ")"))
454 (cons 'may (concat "[" (mapconcat 'cdr pass1 " ") "]")))))
05f1c4ec
JB
455 ((consp value)
456 (let ((p value)
b4929f75 457 newlist
ed2f7fc8 458 use-list*
b4929f75
RS
459 anynil)
460 (while (consp p)
461 (let ((q.txt (desktop-internal-v2s (car p))))
462 (or anynil (setq anynil (null (car q.txt))))
463 (setq newlist (cons q.txt newlist)))
464 (setq p (cdr p)))
465 (if p
466 (let ((last (desktop-internal-v2s p))
467 (el (car newlist)))
ed2f7fc8
RS
468 (or anynil (setq anynil (null (car last))))
469 (or anynil
470 (setq newlist (cons '(must . ".") newlist)))
471 (setq use-list* t)
472 (setq newlist (cons last newlist))))
b4929f75
RS
473 (setq newlist (nreverse newlist))
474 (if anynil
475 (cons nil
ed2f7fc8 476 (concat (if use-list* "(desktop-list* " "(list ")
b4929f75
RS
477 (mapconcat (lambda (el)
478 (if (eq (car el) 'must)
479 (concat "'" (cdr el))
480 (cdr el)))
481 newlist
482 " ")
483 ")"))
484 (cons 'must
485 (concat "(" (mapconcat 'cdr newlist " ") ")")))))
05f1c4ec 486 ((subrp value)
de9e2828 487 (cons nil (concat "(symbol-function '"
05f1c4ec 488 (substring (prin1-to-string value) 7 -1)
de9e2828 489 ")")))
05f1c4ec
JB
490 ((markerp value)
491 (let ((pos (prin1-to-string (marker-position value)))
492 (buf (prin1-to-string (buffer-name (marker-buffer value)))))
de9e2828
RS
493 (cons nil (concat "(let ((mk (make-marker)))"
494 " (add-hook 'desktop-delay-hook"
495 " (list 'lambda '() (list 'set-marker mk "
496 pos " (get-buffer " buf ")))) mk)"))))
497 (t ; save as text
fa379636 498 (cons 'may "\"Unprintable entity\""))))
de9e2828 499
4a2fce7a 500;; ----------------------------------------------------------------------------
05f1c4ec 501(defun desktop-value-to-string (value)
577ed2b2
RS
502 "Convert VALUE to a string that when read evaluates to the same value.
503Not all types of values are supported."
de9e2828
RS
504 (let* ((print-escape-newlines t)
505 (float-output-format nil)
05f1c4ec 506 (quote.txt (desktop-internal-v2s value))
de9e2828
RS
507 (quote (car quote.txt))
508 (txt (cdr quote.txt)))
509 (if (eq quote 'must)
510 (concat "'" txt)
511 txt)))
4a2fce7a 512
ec4c6f22 513;; ----------------------------------------------------------------------------
0e7c8611
RS
514(defun desktop-outvar (varspec)
515 "Output a setq statement for variable VAR to the desktop file.
516The argument VARSPEC may be the variable name VAR (a symbol),
517or a cons cell of the form (VAR . MAX-SIZE),
518which means to truncate VAR's value to at most MAX-SIZE elements
519\(if the value is a list) before saving the value."
520 (let (var size)
521 (if (consp varspec)
522 (setq var (car varspec) size (cdr varspec))
523 (setq var varspec))
524 (if (boundp var)
525 (progn
526 (if (and (integerp size)
527 (> size 0)
528 (listp (eval var)))
47640244 529 (desktop-truncate (eval var) size))
0e7c8611
RS
530 (insert "(setq "
531 (symbol-name var)
532 " "
533 (desktop-value-to-string (symbol-value var))
534 ")\n")))))
4a2fce7a 535
0b9bd504 536;; ----------------------------------------------------------------------------
ec4c6f22 537(defun desktop-save-buffer-p (filename bufname mode &rest dummy)
ebb39555 538 "Return t if buffer should have its state saved in the desktop file.
0b9bd504 539FILENAME is the visited file name, BUFNAME is the buffer name, and
6343ed61 540MODE is the major mode."
fa379636 541 (let ((case-fold-search nil))
80f9f3db 542 (and (not (string-match desktop-buffers-not-to-save bufname))
ebb39555
LH
543 (not (memq mode desktop-modes-not-to-save))
544 (or (and filename
545 (not (string-match desktop-files-not-to-save filename)))
546 (and (eq mode 'dired-mode)
547 (with-current-buffer bufname
548 (not (string-match desktop-files-not-to-save
549 default-directory))))
550 (and (null filename)
551 (with-current-buffer bufname desktop-save-buffer))))))
4a2fce7a 552
0b9bd504 553;; ----------------------------------------------------------------------------
4a2fce7a
RS
554(defun desktop-file-name (filename dirname)
555 "Convert FILENAME to format specified in `desktop-file-name-format'.
556DIRNAME must be the directory in which the desktop file will be saved."
557 (cond
558 ((not filename) nil)
559 ((eq desktop-file-name-format 'tilde)
560 (let ((relative-name (file-relative-name (expand-file-name filename) "~")))
561 (cond
562 ((file-name-absolute-p relative-name) relative-name)
563 ((string= "./" relative-name) "~/")
564 ((string= "." relative-name) "~")
565 (t (concat "~/" relative-name)))))
566 ((eq desktop-file-name-format 'local) (file-relative-name filename dirname))
567 (t (expand-file-name filename))))
e5714620 568
4a2fce7a 569;; ----------------------------------------------------------------------------
6343ed61 570(defun desktop-save (dirname)
c5b31c4d
LH
571 "Save the desktop in a desktop file.
572Parameter DIRNAME specifies where to save the desktop file.
573See also `desktop-base-file-name'."
6343ed61 574 (interactive "DDirectory to save desktop file in: ")
fa379636 575 (run-hooks 'desktop-save-hook)
7bcbf3c2 576 (setq dirname (file-name-as-directory (expand-file-name dirname)))
6343ed61 577 (save-excursion
73b0b745 578 (let ((filename (expand-file-name desktop-base-file-name dirname))
7bcbf3c2
JB
579 (info
580 (mapcar
340db502 581 #'(lambda (b)
7bcbf3c2
JB
582 (set-buffer b)
583 (list
584 (desktop-file-name (buffer-file-name) dirname)
585 (buffer-name)
586 major-mode
587 ;; minor modes
588 (let (ret)
7bfa55b3
LH
589 (mapc
590 #'(lambda (minor-mode)
7bcbf3c2 591 (and
7bfa55b3
LH
592 (boundp minor-mode)
593 (symbol-value minor-mode)
594 (let ((special (assq minor-mode desktop-minor-mode-table)))
595 (when (or special (functionp minor-mode))
596 (setq ret
597 (cons
598 (if special (cadr special) minor-mode)
599 ret))))))
7bcbf3c2
JB
600 (mapcar #'car minor-mode-alist))
601 ret)
602 (point)
603 (list (mark t) mark-active)
604 buffer-read-only
e5780ae1 605 ;; Auxiliary information
ebb39555
LH
606 (when (functionp desktop-save-buffer)
607 (funcall desktop-save-buffer dirname))
7bcbf3c2
JB
608 (let ((locals desktop-locals-to-save)
609 (loclist (buffer-local-variables))
610 (ll))
611 (while locals
612 (let ((here (assq (car locals) loclist)))
613 (if here
614 (setq ll (cons here ll))
615 (when (member (car locals) loclist)
616 (setq ll (cons (car locals) ll)))))
617 (setq locals (cdr locals)))
340db502 618 ll)))
7bcbf3c2
JB
619 (buffer-list)))
620 (buf (get-buffer-create "*desktop*")))
6343ed61
RS
621 (set-buffer buf)
622 (erase-buffer)
fa379636 623
4a2fce7a
RS
624 (insert
625 ";; -*- coding: emacs-mule; -*-\n"
626 desktop-header
627 ";; Created " (current-time-string) "\n"
628 ";; Desktop file format version " desktop-file-version "\n"
629 ";; Emacs version " emacs-version "\n\n"
630 ";; Global section:\n")
6343ed61
RS
631 (mapcar (function desktop-outvar) desktop-globals-to-save)
632 (if (memq 'kill-ring desktop-globals-to-save)
4a2fce7a
RS
633 (insert
634 "(setq kill-ring-yank-pointer (nthcdr "
635 (int-to-string (- (length kill-ring) (length kill-ring-yank-pointer)))
636 " kill-ring))\n"))
6343ed61 637
4a2fce7a 638 (insert "\n;; Buffer section -- buffers listed in same order as in buffer list:\n")
340db502
LH
639 (mapcar #'(lambda (l)
640 (if (apply 'desktop-save-buffer-p l)
641 (progn
642 (insert "(desktop-create-buffer " desktop-file-version)
643 (mapcar #'(lambda (e)
644 (insert "\n " (desktop-value-to-string e)))
645 l)
646 (insert ")\n\n"))))
647 info)
6343ed61 648 (setq default-directory dirname)
4a2fce7a 649 (when (file-exists-p filename) (delete-file filename))
7642acca 650 (let ((coding-system-for-write 'emacs-mule))
4a2fce7a 651 (write-region (point-min) (point-max) filename nil 'nomessage))))
6343ed61 652 (setq desktop-dirname dirname))
4a2fce7a 653
0b9bd504 654;; ----------------------------------------------------------------------------
6343ed61 655(defun desktop-remove ()
c5b31c4d
LH
656 "Delete desktop file in `desktop-dirname'.
657This function also sets `desktop-dirname' to nil."
6343ed61 658 (interactive)
c5b31c4d
LH
659 (when desktop-dirname
660 (let ((filename (expand-file-name desktop-base-file-name desktop-dirname)))
661 (setq desktop-dirname nil)
662 (when (file-exists-p filename)
73b0b745 663 (delete-file filename)))))
c5b31c4d 664
0b9bd504 665;; ----------------------------------------------------------------------------
478653c9 666;;;###autoload
c5b31c4d
LH
667(defun desktop-read (&optional dirname)
668 "Read and process the desktop file in directory DIRNAME.
669Look for a desktop file in DIRNAME, or if DIRNAME is omitted, look in
670directories listed in `desktop-path'. If a desktop file is found, it
671is processed and `desktop-after-read-hook' is run. If no desktop file
672is found, clear the desktop and run `desktop-no-desktop-file-hook'.
673This function is a no-op when Emacs is running in batch mode.
674It returns t if a desktop file was loaded, nil otherwise."
6343ed61 675 (interactive)
4a2fce7a 676 (unless noninteractive
c5b31c4d
LH
677 (setq desktop-dirname
678 (file-name-as-directory
679 (expand-file-name
680 (or
681 ;; If DIRNAME is specified, use it.
682 (and (< 0 (length dirname)) dirname)
683 ;; Otherwise search desktop file in desktop-path.
684 (let ((dirs desktop-path))
685 (while
686 (and
687 dirs
688 (not
689 (file-exists-p (expand-file-name desktop-base-file-name (car dirs)))))
690 (setq dirs (cdr dirs)))
691 (and dirs (car dirs)))
692 ;; If not found and `desktop-path' is non-nil, use its first element.
693 (and desktop-path (car desktop-path))
694 ;; Default: Home directory.
695 "~"))))
696 (if (file-exists-p (expand-file-name desktop-base-file-name desktop-dirname))
697 ;; Desktop file found, process it.
e5780ae1
LH
698 (let ((desktop-first-buffer nil)
699 (desktop-buffer-ok-count 0)
700 (desktop-buffer-fail-count 0))
c5b31c4d
LH
701 ;; Evaluate desktop buffer.
702 (load (expand-file-name desktop-base-file-name desktop-dirname) t t t)
703 ;; `desktop-create-buffer' puts buffers at end of the buffer list.
704 ;; We want buffers existing prior to evaluating the desktop (and not reused)
705 ;; to be placed at the end of the buffer list, so we move them here.
706 (mapcar 'bury-buffer
707 (nreverse (cdr (memq desktop-first-buffer (nreverse (buffer-list))))))
708 (switch-to-buffer (car (buffer-list)))
709 (run-hooks 'desktop-delay-hook)
710 (setq desktop-delay-hook nil)
711 (run-hooks 'desktop-after-read-hook)
e5780ae1
LH
712 (message "Desktop: %d buffer%s restored%s."
713 desktop-buffer-ok-count
714 (if (= 1 desktop-buffer-ok-count) "" "s")
715 (if (< 0 desktop-buffer-fail-count)
716 (format ", %d failed to restore" desktop-buffer-fail-count)
717 ""))
c5b31c4d
LH
718 t)
719 ;; No desktop file found.
720 (desktop-clear)
721 (let ((default-directory desktop-dirname))
722 (run-hooks 'desktop-no-desktop-file-hook))
723 (message "No desktop file.")
724 nil)))
4a2fce7a 725
0b9bd504 726;; ----------------------------------------------------------------------------
c5b31c4d 727;; Maintained for backward compatibility
478653c9 728;;;###autoload
6343ed61 729(defun desktop-load-default ()
577ed2b2 730 "Load the `default' start-up library manually.
c5b31c4d 731Also inhibit further loading of it."
0b9bd504 732 (if (not inhibit-default-init) ; safety check
6343ed61
RS
733 (progn
734 (load "default" t t)
735 (setq inhibit-default-init t))))
c5b31c4d 736(make-obsolete 'desktop-load-default 'desktop-save-mode)
4a2fce7a
RS
737
738;; ----------------------------------------------------------------------------
739;;;###autoload
c5b31c4d
LH
740(defun desktop-change-dir (dirname)
741 "Change to desktop saved in DIRNAME.
742Kill the desktop as specified by variables `desktop-save-mode' and
743`desktop-save', then clear the desktop and load the desktop file in
744directory DIRNAME."
745 (interactive "DChange to directory: ")
746 (setq dirname (file-name-as-directory (expand-file-name dirname desktop-dirname)))
4a2fce7a
RS
747 (desktop-kill)
748 (desktop-clear)
c5b31c4d
LH
749 (desktop-read dirname))
750
751;; ----------------------------------------------------------------------------
4a2fce7a 752;;;###autoload
c5b31c4d
LH
753(defun desktop-save-in-desktop-dir ()
754 "Save the desktop in directory `desktop-dirname'."
4a2fce7a
RS
755 (interactive)
756 (if desktop-dirname
757 (desktop-save desktop-dirname)
758 (call-interactively 'desktop-save))
759 (message "Desktop saved in %s" desktop-dirname))
760
761;; ----------------------------------------------------------------------------
762;;;###autoload
763(defun desktop-revert ()
764 "Revert to the last loaded desktop."
765 (interactive)
c5b31c4d
LH
766 (unless desktop-dirname
767 (error "Unknown desktop directory"))
768 (unless (file-exists-p (expand-file-name desktop-base-file-name desktop-dirname))
769 (error "No desktop file found"))
770 (desktop-clear)
771 (desktop-read desktop-dirname))
4a2fce7a 772
0b9bd504 773;; ----------------------------------------------------------------------------
e5780ae1
LH
774(defun desktop-restore-file-buffer (desktop-buffer-file-name
775 desktop-buffer-name
776 desktop-buffer-misc)
777 "Restore a file buffer."
778 (eval-when-compile ; Just to silence the byte compiler
779 (defvar desktop-buffer-major-mode)
780 (defvar desktop-buffer-locals))
569c754e
RS
781 (if desktop-buffer-file-name
782 (if (or (file-exists-p desktop-buffer-file-name)
e5780ae1
LH
783 (let ((msg (format "Desktop: File \"%s\" no longer exists."
784 desktop-buffer-file-name)))
785 (if desktop-missing-file-warning
786 (y-or-n-p (concat msg " Re-create? "))
787 (message msg)
788 nil)))
bd382a27
LH
789 (let* ((auto-insert nil) ; Disable auto insertion
790 (coding-system-for-read
791 (or coding-system-for-read
792 (cdr (assq 'buffer-file-coding-system
793 desktop-buffer-locals))))
794 (buf (find-file-noselect desktop-buffer-file-name)))
80f9f3db
SM
795 (condition-case nil
796 (switch-to-buffer buf)
0a8c8225 797 (error (pop-to-buffer buf)))
84b538ec
JB
798 (and (not (eq major-mode desktop-buffer-major-mode))
799 (functionp desktop-buffer-major-mode)
800 (funcall desktop-buffer-major-mode))
0a8c8225 801 buf)
e5780ae1 802 nil)))
4a2fce7a 803
0b9bd504 804;; ----------------------------------------------------------------------------
eb982898
JL
805;; Create a buffer, load its file, set its mode, ...;
806;; called from Desktop file only.
1d500ca6 807
4a2fce7a
RS
808(eval-when-compile ; Just to silence the byte compiler
809 (defvar desktop-first-buffer) ;; Dynamically bound in `desktop-read'
810)
811
340db502
LH
812(defun desktop-create-buffer
813 (desktop-file-version
814 desktop-buffer-file-name
815 desktop-buffer-name
816 desktop-buffer-major-mode
817 desktop-buffer-minor-modes
818 desktop-buffer-point
819 desktop-buffer-mark
820 desktop-buffer-read-only
821 desktop-buffer-misc
822 &optional
823 desktop-buffer-locals)
e5780ae1
LH
824 ;; Just to silence the byte compiler. Bound locally in `desktop-read'.
825 (eval-when-compile
826 (defvar desktop-buffer-ok-count)
827 (defvar desktop-buffer-fail-count))
4a2fce7a
RS
828 ;; To make desktop files with relative file names possible, we cannot
829 ;; allow `default-directory' to change. Therefore we save current buffer.
830 (save-current-buffer
340db502
LH
831 (let ((buffer-list (buffer-list))
832 (result
833 (condition-case err
834 (funcall (or (cdr (assq desktop-buffer-major-mode
835 desktop-buffer-mode-handlers))
836 'desktop-restore-file-buffer)
837 desktop-buffer-file-name
838 desktop-buffer-name
839 desktop-buffer-misc)
840 (error
841 (message "Desktop: Can't load buffer %s: %s"
842 desktop-buffer-name
843 (error-message-string err))
844 (when desktop-missing-file-warning (sit-for 1))
845 nil))))
e5780ae1
LH
846 (if (bufferp result)
847 (setq desktop-buffer-ok-count (1+ desktop-buffer-ok-count))
848 (setq desktop-buffer-fail-count (1+ desktop-buffer-fail-count))
849 (setq result nil))
4a2fce7a 850 (unless (bufferp result) (setq result nil))
7bcbf3c2
JB
851 ;; Restore buffer list order with new buffer at end. Don't change
852 ;; the order for old desktop files (old desktop module behaviour).
4a2fce7a 853 (unless (< desktop-file-version 206)
7bcbf3c2
JB
854 (mapcar 'bury-buffer buffer-list)
855 (when result (bury-buffer result)))
4a2fce7a 856 (when result
7bcbf3c2
JB
857 (unless (or desktop-first-buffer (< desktop-file-version 206))
858 (setq desktop-first-buffer result))
4a2fce7a
RS
859 (set-buffer result)
860 (unless (equal (buffer-name) desktop-buffer-name)
861 (rename-buffer desktop-buffer-name))
862 ;; minor modes
340db502
LH
863 (cond ((equal '(t) desktop-buffer-minor-modes) ; backwards compatible
864 (auto-fill-mode 1))
865 ((equal '(nil) desktop-buffer-minor-modes) ; backwards compatible
866 (auto-fill-mode 0))
867 (t
868 (mapcar #'(lambda (minor-mode)
869 (when (functionp minor-mode) (funcall minor-mode 1)))
870 desktop-buffer-minor-modes)))
4a2fce7a 871 ;; Even though point and mark are non-nil when written by `desktop-save'
73b0b745 872 ;; they may be modified by handlers wanting to set point or mark themselves.
e5780ae1
LH
873 (when desktop-buffer-point
874 (goto-char
875 (condition-case err
876 ;; Evaluate point. Thus point can be something like '(search-forward ...
877 (eval desktop-buffer-point)
878 (error (message "%s" (error-message-string err)) 1))))
4a2fce7a
RS
879 (when desktop-buffer-mark
880 (if (consp desktop-buffer-mark)
881 (progn
882 (set-mark (car desktop-buffer-mark))
883 (setq mark-active (car (cdr desktop-buffer-mark))))
884 (set-mark desktop-buffer-mark)))
885 ;; Never override file system if the file really is read-only marked.
886 (if desktop-buffer-read-only (setq buffer-read-only desktop-buffer-read-only))
887 (while desktop-buffer-locals
888 (let ((this (car desktop-buffer-locals)))
889 (if (consp this)
890 ;; an entry of this form `(symbol . value)'
891 (progn
892 (make-local-variable (car this))
893 (set (car this) (cdr this)))
894 ;; an entry of the form `symbol'
895 (make-local-variable this)
896 (makunbound this)))
897 (setq desktop-buffer-locals (cdr desktop-buffer-locals)))))))
ec4c6f22 898
4a2fce7a 899;; ----------------------------------------------------------------------------
ec4c6f22 900;; Backward compatibility -- update parameters to 205 standards.
569c754e
RS
901(defun desktop-buffer (desktop-buffer-file-name desktop-buffer-name
902 desktop-buffer-major-mode
2699e23e 903 mim pt mk ro tl fc cfs cr desktop-buffer-misc)
569c754e 904 (desktop-create-buffer 205 desktop-buffer-file-name desktop-buffer-name
2699e23e
RS
905 desktop-buffer-major-mode (cdr mim) pt mk ro
906 desktop-buffer-misc
ec4c6f22
RS
907 (list (cons 'truncate-lines tl)
908 (cons 'fill-column fc)
909 (cons 'case-fold-search cfs)
910 (cons 'case-replace cr)
911 (cons 'overwrite-mode (car mim)))))
84b538ec 912
0b9bd504 913;; ----------------------------------------------------------------------------
c5b31c4d 914;; When `desktop-save-mode' is non-nil and "--no-desktop" is not specified on the
4a2fce7a
RS
915;; command line, we do the rest of what it takes to use desktop, but do it
916;; after finishing loading the init file.
917;; We cannot use `command-switch-alist' to process "--no-desktop" because these
918;; functions are processed after `after-init-hook'.
919(add-hook
920 'after-init-hook
921 '(lambda ()
922 (let ((key "--no-desktop"))
923 (if (member key command-line-args)
924 (delete key command-line-args)
c5b31c4d 925 (when desktop-save-mode (desktop-read))))))
813dbb2d 926
ab1d55ea 927(provide 'desktop)
6343ed61 928
ab5796a9 929;;; arch-tag: 221907c3-1771-4fd3-9c2e-c6f700c6ede9
80f9f3db 930;;; desktop.el ends here