source: branches/acl2-egc/source/cocoa-ide/hemlock/unused/archive/auto-save.lisp

Last change on this file was 6569, checked in by Gary Byers, 18 years ago

Move more (currently unused, often unlikely to ever be used) stuff to an
archive directory.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.1 KB
Line 
1;;; -*- Package: Hemlock; Log: hemlock.log -*-
2;;;
3;;; **********************************************************************
4;;; This code was written as part of the CMU Common Lisp project at
5;;; Carnegie Mellon University, and has been placed in the public domain.
6;;;
7#+CMU (ext:file-comment
8 "$Header$")
9;;;
10;;; **********************************************************************
11;;;
12;;; Auto-Save Mode
13;;; Written by Christopher Hoover
14;;;
15
16(in-package :hemlock)
17
18
19
20;;;; Per Buffer State Information
21
22;;;
23;;; The auto-save-state structure is used to store the state information for
24;;; a particular buffer in "Save" mode, namely the buffer-signature at the last
25;;; key stroke, the buffer-signature at the time of the last checkpoint, a count
26;;; of the number of destructive keystrokes which have occured since the time of
27;;; the last checkpoint, and the pathname used to write the last checkpoint. It
28;;; is generally kept in a buffer-local hvar called "Auto Save State".
29;;;
30(defstruct (auto-save-state
31 (:conc-name save-state-)
32 (:print-function print-auto-save-state))
33 "Per buffer state for auto-save"
34 (buffer nil) ; buffer this state is for; for printing
35 (key-signature 0 :type fixnum) ; buffer-signature at last keystroke
36 (last-ckp-signature 0 :type fixnum) ; buffer-signature at last checkpoint
37 (key-count 0 :type fixnum) ; # destructive keystrokes since ckp
38 (pathname nil)) ; pathname used to write last ckp file
39
40(defun print-auto-save-state (auto-save-state stream depth)
41 (declare (ignore depth))
42 (format stream "#<Auto Save Buffer State for buffer ~A>"
43 (buffer-name (save-state-buffer auto-save-state))))
44
45
46;;; GET-AUTO-SAVE-STATE tries to get the auto-save-state for the buffer. If
47;;; the buffer is not in "Save" mode then this function returns NIL.
48;;;
49(defun get-auto-save-state (buffer)
50 (if (hemlock-bound-p 'auto-save-state :buffer buffer)
51 (variable-value 'auto-save-state :buffer buffer)))
52
53;;; RESET-AUTO-SAVE-STATE resets the auto-save-state of the buffer making it
54;;; look as if the buffer was just checkpointed. This is in fact how
55;;; checkpoint-buffer updates the state. If the buffer is not in "Save" mode
56;;; this function punts the attempt and does nothing.
57;;;
58(defun reset-auto-save-state (buffer)
59 (let ((state (get-auto-save-state buffer)))
60 (when state
61 (let ((signature (buffer-signature buffer)))
62 (setf (save-state-key-signature state)
63 signature)
64 (setf (save-state-last-ckp-signature state)
65 signature)
66 (setf (save-state-key-count state)
67 0)))))
68
69
70
71
72;;;; Checkpoint Pathname Interface/Internal Routines
73
74;;; GET-CHECKPOINT-PATHNAME -- Interface
75;;;
76;;; Returns the pathname of the checkpoint file for the specified
77;;; buffer; Returns NIL if no checkpoints have been written thus
78;;; far or if the buffer isn't in "Save" mode.
79;;;
80(defun get-checkpoint-pathname (buffer)
81 "Returns the pathname of the checkpoint file for the specified buffer.
82 If no checkpoints have been written thus far, or if the buffer is not in
83 \"Save\" mode, return nil."
84 (let ((state (get-auto-save-state buffer)))
85 (if state
86 (save-state-pathname state))))
87
88;;; MAKE-UNIQUE-SAVE-PATHNAME is used as the default value for "Auto Save
89;;; Pathname Hook" and is mentioned in the User's manual, so it gets a doc
90;;; doc string.
91;;;
92(defun make-unique-save-pathname (buffer)
93 "Returns a pathname for a non-existing file in DEFAULT-DIRECTORY. Uses
94 GENSYM to for a file name: save-GENSYM.CKP."
95 (declare (ignore buffer))
96 (let ((def-dir (hemlock-ext:default-directory)))
97 (loop
98 (let* ((sym (gensym))
99 (f (merge-pathnames (format nil "save-~A.CKP" sym) def-dir)))
100 (unless (probe-file f)
101 (return f))))))
102
103(defhvar "Auto Save Pathname Hook"
104 "This hook is called by Auto Save to get a checkpoint pathname when there
105 is no pathname associated with a buffer. If this value is NIL, then
106 \"Save\" mode is turned off in the buffer. Otherwise, the function
107 will be called. It should take a buffer as its argument and return either
108 NIL or a pathname. If NIL is returned, then \"Save\" mode is turned off
109 in the buffer; else the pathname returned is used as the checkpoint
110 pathname for the buffer."
111 :value #'make-unique-save-pathname)
112
113
114;;; MAKE-BUFFER-CKP-PATHNAME attempts to form a pathname by using the buffer's
115;;; associated pathname (from buffer-pathname). If there isn't a pathname
116;;; associated with the buffer, the function returns nil. Otherwise, it uses
117;;; the "Auto Save Filename Pattern" and FORMAT to make the checkpoint
118;;; pathname.
119;;;
120(defun make-buffer-ckp-pathname (buffer)
121 (let ((buffer-pn (buffer-pathname buffer)))
122 (if buffer-pn
123 (pathname (format nil
124 (value auto-save-filename-pattern)
125 (directory-namestring buffer-pn)
126 (file-namestring buffer-pn))))))
127
128
129
130
131;;;; Buffer-level Checkpoint Routines
132
133;;;
134;;; write-checkpoint-file -- Internal
135;;;
136;;; Does the low-level write of the checkpoint. Returns T if it succeeds
137;;; and NIL if it fails. Echoes winnage or lossage to the luser.
138;;;
139(defun write-checkpoint-file (pathname buffer)
140 (let ((ns (namestring pathname)))
141 (cond ((hemlock-ext:file-writable pathname)
142 (message "Saving ~A" ns)
143 (handler-case (progn
144 (write-file (buffer-region buffer) pathname
145 :keep-backup nil
146 :access #o600) ;read/write by owner.
147 t)
148 (error (condition)
149 (loud-message "Auto Save failure: ~A" condition)
150 nil)))
151 (t
152 (message "Can't write ~A" ns)
153 nil))))
154
155
156;;;
157;;; To save, or not to save... and to save as what?
158;;;
159;;; First, make-buffer-ckp-pathname is called. It will return either NIL or
160;;; a pathname formed by using buffer-pathname in conjunction with the hvar
161;;; "Auto Save Filename Pattern". If there isn't an associated pathname or
162;;; make-buffer-ckp-pathname returns NIL, then we use the pathname we used
163;;; the last time we checkpointed the buffer. If we've never checkpointed
164;;; the buffer, then we check "Auto Save Pathname Hook". If it is NIL then
165;;; we turn Save mode off for the buffer, else we funcall the function on
166;;; the hook with the buffer as an argument. The function on the hook should
167;;; return either NIL or a pathname. If it returns NIL, we toggle Save mode
168;;; off for the buffer; otherwise, we use the pathname it returned.
169;;;
170
171;;;
172;;; checkpoint-buffer -- Internal
173;;;
174;;; This functions takes a buffer as its argument and attempts to write a
175;;; checkpoint for that buffer. See the notes at the beginning of this page
176;;; for how it determines what pathname to use as the checkpoint pathname.
177;;; Note that a checkpoint is not necessarily written -- instead "Save"
178;;; mode may be turned off for the buffer.
179;;;
180(defun checkpoint-buffer (buffer)
181 (let* ((state (get-auto-save-state buffer))
182 (buffer-ckp-pn (make-buffer-ckp-pathname buffer))
183 (last-pathname (save-state-pathname state)))
184 (cond (buffer-ckp-pn
185 (when (write-checkpoint-file buffer-ckp-pn buffer)
186 (reset-auto-save-state buffer)
187 (setf (save-state-pathname state) buffer-ckp-pn)
188 (when (and last-pathname
189 (not (equal last-pathname buffer-ckp-pn))
190 (probe-file last-pathname))
191 (delete-file last-pathname))))
192 (last-pathname
193 (when (write-checkpoint-file last-pathname buffer)
194 (reset-auto-save-state buffer)))
195 (t
196 (let* ((save-pn-hook (value auto-save-pathname-hook))
197 (new-pn (if save-pn-hook
198 (funcall save-pn-hook buffer))))
199 (cond ((or (not new-pn)
200 (zerop (length
201 (the simple-string (namestring new-pn)))))
202 (setf (buffer-minor-mode buffer "Save") nil))
203 (t
204 (when (write-checkpoint-file new-pn buffer)
205 (reset-auto-save-state buffer)
206 (setf (save-state-pathname state) new-pn)))))))))
207
208;;;
209;;; checkpoint-all-buffers -- Internal
210;;;
211;;; This function looks through the buffer list and checkpoints
212;;; each buffer that is in "Save" mode that has been modified since
213;;; its last checkpoint.
214;;;
215(defun checkpoint-all-buffers (elapsed-time)
216 (declare (ignore elapsed-time))
217 (dolist (buffer *buffer-list*)
218 (let ((state (get-auto-save-state buffer)))
219 (when (and state
220 (buffer-modified buffer)
221 (not (eql
222 (save-state-last-ckp-signature state)
223 (buffer-signature buffer))))
224 (checkpoint-buffer buffer)))))
225
226
227
228;;;; Random Hooks: cleanup, buffer-modified, change-save-freq.
229
230;;;
231;;; cleanup-checkpoint -- Internal
232;;;
233;;; Cleans up checkpoint file for a given buffer if Auto Save Cleanup
234;;; Checkpoints is non-NIL. This is called via "Write File Hook"
235;;;
236(defun cleanup-checkpoint (buffer)
237 (let ((ckp-pathname (get-checkpoint-pathname buffer)))
238 (when (and (value auto-save-cleanup-checkpoints)
239 ckp-pathname
240 (probe-file ckp-pathname))
241 (delete-file ckp-pathname))))
242
243(add-hook write-file-hook 'cleanup-checkpoint)
244
245;;;
246;;; notice-buffer-modified -- Internal
247;;;
248;;; This function is called on "Buffer Modified Hook" to reset
249;;; the Auto Save state. It makes the buffer look like it has just
250;;; been checkpointed.
251;;;
252(defun notice-buffer-modified (buffer flag)
253 ;; we care only when the flag has gone to false
254 (when (not flag)
255 (reset-auto-save-state buffer)))
256
257(add-hook buffer-modified-hook 'notice-buffer-modified)
258
259;;;
260;;; change-save-frequency -- Internal
261;;;
262;;; This keeps us scheduled at the proper interval. It is stuck on
263;;; the hook list for the hvar "Auto Save Checkpoint Frequency" and
264;;; is therefore called whenever this value is set.
265;;;
266(defun change-save-frequency (name kind where new-value)
267 (declare (ignore name kind where))
268 (setq new-value (truncate new-value))
269 (remove-scheduled-event 'checkpoint-all-buffers)
270 (when (and new-value
271 (plusp new-value))
272 (schedule-event new-value 'checkpoint-all-buffers t)))
273
274
275;;; "Save" mode is in "Default Modes", so turn it off in these modes.
276;;;
277
278(defun interactive-modes (buffer on)
279 (when on (setf (buffer-minor-mode buffer "Save") nil)))
280
281#+GBNIL (add-hook typescript-mode-hook 'interactive-modes)
282#+GBNIL (add-hook eval-mode-hook 'interactive-modes)
283
284
285
286
287;;;; Key Count Routine for Input Hook
288
289;;;
290;;; auto-save-count-keys -- Internal
291;;;
292;;; This function sits on the Input Hook to eat cycles. If the current
293;;; buffer is not in Save mode or if the current buffer is the echo area
294;;; buffer, it does nothing. Otherwise, we check to see if we have exceeded
295;;; the key count threshold (and write a checkpoint if we have) and we
296;;; increment the key count for the buffer.
297;;;
298(defun auto-save-count-keys ()
299 #.*fast*
300 (let ((buffer (current-buffer)))
301 (unless (eq buffer *echo-area-buffer*)
302 (let ((state (value auto-save-state))
303 (threshold (value auto-save-key-count-threshold)))
304 (when (and state threshold)
305 (let ((signature (buffer-signature buffer)))
306 (declare (fixnum signature))
307 (when (not (eql signature
308 (save-state-key-signature state)))
309 ;; see if we exceeded threshold last time...
310 (when (>= (save-state-key-count state)
311 (the fixnum threshold))
312 (checkpoint-buffer buffer))
313 ;; update state
314 (setf (save-state-key-signature state) signature)
315 (incf (save-state-key-count state)))))))))
316
317(add-hook input-hook 'auto-save-count-keys)
318
319
320
321;;;; Save Mode Hemlock Variables
322
323;;;
324;;; Hemlock variables/parameters for Auto-Save Mode
325;;;
326
327(defhvar "Auto Save Filename Pattern"
328 "This control-string is used with format to make the filename of the
329 checkpoint file. Format is called with two arguments, the first
330 being the directory namestring and the second being the file
331 namestring of the default buffer pathname."
332 :value "~A~A.CKP")
333
334(defhvar "Auto Save Key Count Threshold"
335 "This value is the number of destructive/modifying keystrokes that will
336 automatically trigger an checkpoint. This value may be NIL to turn this
337 feature off."
338 :value 256)
339
340(defhvar "Auto Save Cleanup Checkpoints"
341 "This variable controls whether or not \"Save\" mode will delete the
342 checkpoint file for a buffer after it is saved. If this value is
343 non-NIL then cleanup will occur."
344 :value t)
345
346(defhvar "Auto Save Checkpoint Frequency"
347 "All modified buffers (in \"Save\" mode) will be checkpointed after this
348 amount of time (in seconds). This value may be NIL (or non-positive)
349 to turn this feature off."
350 :value (* 2 60)
351 :hooks '(change-save-frequency))
352
353(defhvar "Auto Save State"
354 "Shadow magic. This variable is seen when in buffers that are not
355 in \"Save\" mode. Do not change this value or you will lose."
356 :value nil)
357
358
359
360;;;; "Save" mode
361
362(defcommand "Auto Save Mode" (p)
363 "If the argument is zero or negative, turn \"Save\" mode off. If it
364 is positive turn \"Save\" mode on. If there is no argument, toggle
365 \"Save\" mode in the current buffer. When in \"Save\" mode, files
366 are automatically checkpointed every \"Auto Save Checkpoint Frequency\"
367 seconds or every \"Auto Save Key Count Threshold\" destructive
368 keystrokes. If there is a pathname associated with the buffer, the
369 filename used for the checkpoint file is controlled by the hvar \"Auto
370 Save Filename Pattern\". Otherwise, the hook \"Auto Save Pathname Hook\"
371 is used to generate a checkpoint pathname. If the buffer's pathname
372 changes between checkpoints, the checkpoint file will be written under
373 the new name and the old checkpoint file will be deleted if it exists.
374 When a buffer is written out, the checkpoint will be deleted if the
375 hvar \"Auto Save Cleanup Checkpoints\" is non-NIL."
376 "Turn on, turn off, or toggle \"Save\" mode in the current buffer."
377 (setf (buffer-minor-mode (current-buffer) "Save")
378 (if p
379 (plusp p)
380 (not (buffer-minor-mode (current-buffer) "Save")))))
381
382(defun setup-auto-save-mode (buffer)
383 (let* ((signature (buffer-signature buffer))
384 (state (make-auto-save-state
385 :buffer buffer
386 :key-signature (the fixnum signature)
387 :last-ckp-signature (the fixnum signature))))
388 ;; shadow the global value with a variable which will
389 ;; contain our per buffer state information
390 (defhvar "Auto Save State"
391 "This is the \"Save\" mode state information for this buffer."
392 :buffer buffer
393 :value state)))
394
395(defun cleanup-auto-save-mode (buffer)
396 (delete-variable 'auto-save-state
397 :buffer buffer))
398
399(defmode "Save"
400 :setup-function 'setup-auto-save-mode
401 :cleanup-function 'cleanup-auto-save-mode)
Note: See TracBrowser for help on using the repository browser.