source: branches/acode-rewrite/source/cocoa-ide/hemlock/src/filecoms.lisp

Last change on this file was 16082, checked in by Gary Byers, 11 years ago

Merge trunk changes into this branch. Expect some things to explode.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 25.5 KB
RevLine 
[6]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;;; This file contains file/buffer manipulating commands.
13;;;
14
15(in-package :hemlock)
16
17
18
19
20;;;; PROCESS-FILE-OPTIONS.
21
22(defvar *mode-option-handlers* ()
23 "Do not modify this; use Define-File-Option instead.")
24
25(defvar *file-type-hooks* ()
26 "Do not modify this; use Define-File-Type-Hook instead.")
27
28(defun trim-subseq (string start end)
29 (declare (simple-string string))
30 (string-trim '(#\Space #\Tab) (subseq string start end)))
31
32;;; PROCESS-FILE-OPTIONS checks the first line of buffer for the file options
33;;; indicator "-*-". IF it finds this, then it enters a do-file-options block.
34;;; If any parsing errors occur while picking out options, we return from this
35;;; block. Staying inside this function at this point, allows us to still set
36;;; a major mode if no file option specified one.
37;;;
38;;; We also cater to old style mode comments:
39;;; -*- Lisp -*-
40;;; -*- Text -*-
41;;; This kicks in if we find no colon on the file options line.
[8428]42;;;
43(defun process-file-options (&optional (buffer (current-buffer))
[6]44 (pathname (buffer-pathname buffer)))
45 "Checks for file options and invokes handlers if there are any. If no
46 \"Mode\" mode option is specified, then this tries to invoke the appropriate
47 file type hook."
48 (let* ((string
49 (line-string (mark-line (buffer-start-mark buffer))))
50 (found (search "-*-" string))
51 (no-major-mode t)
52 (type (if pathname (pathname-type pathname))))
53 (declare (simple-string string))
54 (when found
55 (block do-file-options
56 (let* ((start (+ found 3))
57 (end (search "-*-" string :start2 start)))
58 (unless end
59 (loud-message "No closing \"-*-\". Aborting file options.")
60 (return-from do-file-options))
61 (cond
[15536]62 ((find #\: string :start start :end end)
[6]63 (do ((opt-start start (1+ semi)) colon semi real-semi)
64 (nil)
65 (setq colon (position #\: string :start opt-start :end end))
[15536]66 (unless colon
67 (unless real-semi
[6]68 (loud-message "Missing \":\". Aborting file options."))
[15536]69 (return-from do-file-options))
[6]70 (setq semi (or (setq real-semi (position #\; string :start colon :end end)) end))
71 (let* ((option (nstring-downcase
72 (trim-subseq string opt-start colon)))
73 (handler (assoc option *mode-option-handlers*
74 :test #'string=)))
75 (declare (simple-string option))
76 (cond
77 (handler
78 (let ((result (funcall (cdr handler) buffer
79 (trim-subseq string (1+ colon) semi))))
80 (when (string= option "mode")
81 (setq no-major-mode (not result)))))
82 (t (message "Unknown file option: ~S" option)))
83 (when (= semi end) (return nil)))))
84 (t
85 ;; Old style mode comment.
86 (setq no-major-mode nil)
87 (funcall (cdr (assoc "mode" *mode-option-handlers* :test #'string=))
88 buffer (trim-subseq string start end)))))))
89 (when (and no-major-mode type)
90 (let ((hook (assoc (string-downcase type) *file-type-hooks*
91 :test #'string=)))
92 (when hook (funcall (cdr hook) buffer type))))))
93
94
95
96
97;;;; File options and file type hooks.
98
99(defmacro define-file-option (name lambda-list &body body)
100 "Define-File-Option Name (Buffer Value) {Form}*
101 Defines a new file option to be user in the -*- line at the top of a file.
102 The body is evaluated with Buffer bound to the buffer the file has been read
103 into and Value to the string argument to the option."
104 (let ((name (string-downcase name)))
105 `(setf (cdr (or (assoc ,name *mode-option-handlers* :test #'string=)
106 (car (push (cons ,name nil) *mode-option-handlers*))))
107 #'(lambda ,lambda-list ,@body))))
108
109(define-file-option "Mode" (buffer str)
110 (let ((seen-major-mode-p nil)
111 (lastpos 0))
112 (loop
113 (let* ((pos (position #\, str :start lastpos))
114 (substr (trim-subseq str lastpos pos)))
115 (cond ((getstring substr *mode-names*)
116 (cond ((mode-major-p substr)
117 (when seen-major-mode-p
118 (loud-message
119 "Major mode already processed. Using ~S now."
120 substr))
121 (setf seen-major-mode-p t)
122 (setf (buffer-major-mode buffer) substr))
123 (t
124 (setf (buffer-minor-mode buffer substr) t))))
125 (t
126 (loud-message "~S is not a defined mode -- ignored." substr)))
127 (unless pos
128 (return seen-major-mode-p))
[12146]129 (setf lastpos (1+ pos))))))
130
[6]131(define-file-option "log" (buffer string)
[15536]132 (declare (ignore buffer string)))
133
[12146]134(define-file-option "base" (buffer string)
[15536]135 (declare (ignore buffer string)))
136
[12146]137(define-file-option "syntax" (buffer string)
[15536]138 (declare (ignore buffer string)))
139
140(define-file-option "coding" (buffer string)
141 (hemlock-ext:set-buffer-external-format buffer string))
142
143
[6]144
145
146(defmacro define-file-type-hook (type-list (buffer type) &body body)
147 "Define-File-Type-Hook ({Type}*) (Buffer Type) {Form}*
148 Define some code to be evaluated when a file having one of the specified
149 Types is read by a file command. Buffer is bound to the buffer the
150 file is in, and Type is the actual type read."
151 (let ((fun (gensym)) (str (gensym)))
152 `(flet ((,fun (,buffer ,type) ,@body))
153 (dolist (,str ',(mapcar #'string-downcase type-list))
154 (setf (cdr (or (assoc ,str *file-type-hooks* :test #'string=)
155 (car (push (cons ,str nil) *file-type-hooks*))))
156 #',fun)))))
157
158(define-file-type-hook ("pas" "pasmac" "macro" "defs" "spc" "bdy")
159 (buffer type)
160 (declare (ignore type))
[6770]161 (setf (buffer-major-mode buffer) "Pascal"))
[6]162
163(define-file-type-hook ("lisp" "slisp" "l" "lsp" "mcl" "cl") (buffer type)
164 (declare (ignore type))
165 (setf (buffer-major-mode buffer) "Lisp"))
166
167(define-file-type-hook ("txt" "text" "tx") (buffer type)
168 (declare (ignore type))
169 (setf (buffer-major-mode buffer) "Text"))
170
171
172
173
174;;;; Support for file hacking commands:
175
176(defhvar "Pathname Defaults"
177 "This variable contains a pathname which is used to supply defaults
178 when we don't have anything better."
179 :value (pathname "gazonk.del"))
180
181(defhvar "Last Resort Pathname Defaults"
182 "This variable contains a pathname which is used to supply defaults when
183 we don't have anything better, but unlike \"Pathname Defaults\", this is
184 never set to some buffer's pathname."
185 :value (pathname "gazonk"))
186
187(defhvar "Last Resort Pathname Defaults Function"
188 "This variable contains a function that is called when a default pathname is
189 needed, the buffer has no pathname, and the buffer's name is not entirely
190 composed of alphanumerics. The default value is a function that simply
191 returns \"Last Resort Pathname Defaults\". The function must take a buffer
192 as an argument, and it must return some pathname."
193 :value #'(lambda (buffer)
194 (declare (ignore buffer))
195 (merge-pathnames (value last-resort-pathname-defaults)
196 (value pathname-defaults))))
197
198(defun buffer-default-pathname (buffer)
199 "Returns \"Buffer Pathname\" if it is bound. If it is not, and buffer's name
200 is composed solely of alphnumeric characters, then return a pathname formed
201 from the buffer's name. If the buffer's name has other characters in it,
202 then return the value of \"Last Resort Pathname Defaults Function\" called
203 on buffer."
204 (or (buffer-pathname buffer)
205 (if (every #'alphanumericp (the simple-string (buffer-name buffer)))
206 (merge-pathnames (make-pathname :name (buffer-name buffer))
207 (value pathname-defaults))
208 (funcall (value last-resort-pathname-defaults-function) buffer))))
209
210
211(defun pathname-to-buffer-name (pathname)
212 "Returns a simple-string using components from pathname."
213 (let ((pathname (pathname pathname)))
214 (concatenate 'simple-string
215 (file-namestring pathname)
216 " "
217 (directory-namestring pathname))))
218
219
220
221
222;;;; File hacking commands.
223
[8428]224(defcommand "Process File Options" (p)
[6]225 "Reprocess this buffer's file options."
[569]226 "Reprocess this buffer's file options."
227 (declare (ignore p))
228 (process-file-options))
229
230(defcommand "Ensure File Options Line" (p)
231 "Insert a default file options line at the beginning of the buffer, unless such a line already exists."
232 "Insert a default file options line at the beginning of the buffer, unless such a line already exists."
233 (declare (ignore p))
234 (let* ((buffer (current-buffer))
235 (string
236 (line-string (mark-line (buffer-start-mark buffer))))
237 (found (search "-*-" string))
238 (end (if found (search "-*-" string :start2 (+ found 3)))))
239 (unless end
240 (let* ((mode (buffer-major-mode buffer)))
241 (unless mode
242 ;; Try to derive the buffer's major mode from its pathname's
243 ;; type.
244 (let* ((pathname (buffer-pathname buffer))
245 (type (if pathname (pathname-type pathname)))
246 (hook (if type
247 (assoc (string-downcase type) *file-type-hooks*
[587]248 :test #'string=))))
[569]249 (when hook
250 (funcall (cdr hook) buffer type)
251 (setq mode (buffer-major-mode buffer)))))
252 (with-mark ((mark (buffer-start-mark buffer) :left-inserting))
253 (if (string-equal mode "Lisp")
[15536]254 (let* ((package-name
255 (if (hemlock-bound-p 'current-package :buffer buffer)
256 (variable-value 'hemlock::current-package
257 :buffer buffer)
258 "CL-USER"))
259 (encoding-string (let* ((string (hemlock-ext:buffer-encoding-name buffer))
260 (suffix (case (hi::buffer-line-termination buffer)
261 (:cr "mac")
[569]262 (:crlf "dos"))))
263 (if suffix
[15536]264 (concatenate 'string string "-" suffix)
[569]265 string))))
266 (insert-string
267 mark
268 (format nil ";;; -*- Mode: Lisp; Package: ~a; Coding: ~a; -*-" package-name encoding-string)))
269 (insert-string
270 mark
271 (format nil ";;; -*- Mode: ~a -*-" (or mode "Fundamental"))))
272 (insert-character mark #\NewLine))))
273 (buffer-start (buffer-point buffer))))
274
275
276
277
278
279
280
281
282
[6]283
284
285
286
287(defcommand "Insert File" (p &optional pathname (buffer (current-buffer)))
288 "Inserts a file which is prompted for into the current buffer at the point.
289 The prefix argument is ignored."
290 "Inserts the file named by Pathname into Buffer at the point."
291 (declare (ignore p))
292 (let* ((pn (or pathname
293 (prompt-for-file :default (buffer-default-pathname buffer)
294 :prompt "Insert File: "
295 :help "Name of file to insert")))
296 (point (buffer-point buffer))
297 ;; start and end will be deleted by undo stuff
[8428]298 (start (copy-mark point :right-inserting))
[6]299 (end (copy-mark point :left-inserting))
300 (region (region start end)))
301 (setv pathname-defaults pn)
302 (push-new-buffer-mark end)
303 (read-file pn end)
304 (make-region-undo :delete "Insert File" region)))
305
306(defcommand "Write Region" (p &optional pathname)
307 "Writes the current region to a file. "
308 "Writes the current region to a file. "
309 (declare (ignore p))
310 (let ((region (current-region))
311 (pn (or pathname
312 (prompt-for-file :prompt "File to Write: "
313 :help "The name of the file to write the region to. "
314 :default (buffer-default-pathname
315 (current-buffer))
316 :must-exist nil))))
317 (write-file region pn)
318 (message "~A written." (namestring (truename pn)))))
319
[12860]320
[6]321
322
323;;;; Visiting and reverting files.
324
325#+No ;; Dubious semantics in a document-centered model. Also, doesn't work, see bug #476.
326(defcommand "Visit File" (p &optional pathname (buffer (current-buffer)))
327 "Replaces the contents of Buffer with the file Pathname. The prefix
328 argument is ignored. The buffer is set to be writable, so its region
329 can be deleted."
330 "Replaces the contents of the current buffer with the text in the file
331 which is prompted for. The prefix argument is, of course, ignored p times."
332 (declare (ignore p))
333 (when (and (buffer-modified buffer)
334 (prompt-for-y-or-n :prompt "Buffer is modified, save it? "))
335 (save-file-command () buffer))
336 (let ((pn (or pathname
337 (prompt-for-file :prompt "Visit File: "
338 :must-exist nil
339 :help "Name of file to visit."
340 :default (buffer-default-pathname buffer)))))
341 (setf (buffer-writable buffer) t)
342 (read-buffer-file pn buffer)
343 (let ((n (pathname-to-buffer-name (buffer-pathname buffer))))
344 (unless (getstring n *buffer-names*)
345 (setf (buffer-name buffer) n))
346 (warn-about-visit-file-buffers buffer))))
347
348(defun warn-about-visit-file-buffers (buffer)
349 (let ((buffer-pn (buffer-pathname buffer)))
350 (dolist (b *buffer-list*)
351 (unless (eq b buffer)
352 (let ((bpn (buffer-pathname b)))
353 (when (equal bpn buffer-pn)
354 (loud-message "Buffer ~A also contains ~A."
355 (buffer-name b) (namestring buffer-pn))
356 (return)))))))
357
358
359(defhvar "Revert File Confirm"
360 "If this is true, Revert File will prompt before reverting."
361 :value t)
362
363(defcommand "Revert File" (p)
364 "Unless in Save Mode, reads in the last saved version of the file in
365 the current buffer. When in Save Mode, reads in the last checkpoint or
366 the last saved version, whichever is more recent. An argument will always
367 force Revert File to use the last saved version. In either case, if the
368 buffer has been modified and \"Revert File Confirm\" is true, then Revert
[7077]369 File will ask for confirmation beforehand. An attempt is made to maintain
[12859]370 the point's relative position."
[7077]371 "With an argument reverts to the last saved version of the file in the
[6]372 current buffer. Without, reverts to the last checkpoint or last saved
373 version, whichever is more recent."
374 (declare (ignore p))
375 (hemlock-ext:revert-hemlock-buffer (current-buffer))
[810]376 (clear-echo-area))
[8428]377
[6]378
379;;;; Find file.
380
381
[8437]382(defcommand "Find File" (p)
[12234]383 "Visit a file in its own buffer.
[12859]384 If the file is already in some buffer, select that buffer,
[810]385 otherwise make a new buffer with the same name as the file and
386 read the file into it."
[8428]387 (declare (ignore p))
[6]388 (hi::allowing-buffer-display ((current-buffer))
[8428]389 (hemlock-ext:open-hemlock-buffer :pathname :prompt)))
[6]390
391
392#|
393(defun find-file-buffer (pathname)
394 "Return a buffer associated with the file Pathname, reading the file into a
395 new buffer if necessary. The second value is T if we created a buffer, NIL
[8428]396 otherwise. If the file has already been read, we check to see if the file
[6]397 has been modified on disk since it was read, giving the user various
398 recovery options."
399 (let* ((pathname (pathname pathname))
400 (trial-pathname (or (probe-file pathname)
401 (merge-pathnames pathname (default-directory))))
402 (found (find trial-pathname (the list *buffer-list*)
403 :key #'buffer-pathname :test #'equal)))
404 (cond ((not found)
405 (if (and (null (pathname-name trial-pathname))
406 (null (pathname-type trial-pathname))
407 (pathname-directory trial-pathname))
408 ;; This looks like a directory -- make dired buffer
409 (dired-guts nil nil trial-pathname)
410
411 (let* ((name (pathname-to-buffer-name trial-pathname))
412 (found (getstring name *buffer-names*))
413 (use (if found
414 (prompt-for-buffer
415 :prompt "Buffer to use: "
416 :help
417 "Buffer name in use; give another buffer name, or confirm to reuse."
418 :default found
419 :must-exist nil)
420 (make-buffer name)))
421 (buffer (if (stringp use) (make-buffer use) use)))
422 (when (and (buffer-modified buffer)
423 (prompt-for-y-or-n :prompt
424 "Buffer is modified, save it? "))
425 (save-file-command () buffer))
426 (read-buffer-file pathname buffer)
427 (values buffer (stringp use)))))
[8428]428 ((check-disk-version-consistent pathname found)
[6]429 (values found nil))
430 (t
431 (read-buffer-file pathname found)
432 (values found nil)))))
433|#
434
435;;; Check-Disk-Version-Consistent -- Internal
436;;;
437;;; Check that Buffer contains a valid version of the file Pathname,
438;;; harrassing the user if not. We return true if the buffer is O.K., and
439;;; false if the file should be read.
440;;;
441(defun check-disk-version-consistent (pathname buffer)
442 (let ((ndate (file-write-date pathname))
443 (odate (buffer-write-date buffer)))
444 (cond ((not (and ndate odate (/= ndate odate)))
445 t)
446 ((buffer-modified buffer)
447 (beep)
448 (clear-input)
449 (command-case (:prompt (list
450 "File has been changed on disk since it was read and you have made changes too!~
451 ~%Read in the disk version of ~A? [Y] " (namestring pathname))
452 :help
453 "The file in disk has been changed since Hemlock last saved it, meaning that
454 someone else has probably overwritten it. Since the version read into Hemlock
455 has been changed as well, the two versions may have inconsistent changes. If
456 this is the case, it would be a good idea to save your changes in another file
457 and compare the two versions.
458
459 Type one of the following commands:")
460 ((:confirm :yes)
461 "Prompt for a file to write the buffer out to, then read in the disk version."
462 (write-buffer-file
463 buffer
464 (prompt-for-file
465 :prompt "File to save changes in: "
466 :help (list "Save buffer ~S to this file before reading ~A."
467 (buffer-name buffer) (namestring pathname))
468 :must-exist nil
469 :default (buffer-default-pathname buffer)))
470 nil)
471 (:no
472 "Change to the buffer without reading the new version."
473 t)
474 (#\r
475 "Read in the new version, clobbering the changes in the buffer."
476 nil)))
477 (t
478 (not (prompt-for-yes-or-no :prompt
479 (list
480 "File has been changed on disk since it was read.~
481 ~%Read in the disk version of ~A? "
482 (namestring pathname))
483 :help
484 "Type Y to read in the new version or N to just switch to the buffer."
485 :default t))))))
486
487
488(defhvar "Read File Hook"
489 "These functions are called when a file is read into a buffer. Each function
490 must take two arguments -- the buffer the file was read into and whether the
491 file existed (non-nil) or not (nil).")
492
493(defun read-buffer-file (pathname buffer)
494 "Delete the buffer's region, and uses READ-FILE to read pathname into it.
495 If the file exists, set the buffer's write date to the file's; otherwise,
496 MESSAGE that this is a new file and set the buffer's write date to nil.
497 Move buffer's point to the beginning, set the buffer unmodified. If the
498 file exists, set the buffer's pathname to the probed pathname; else, set it
[798]499 to pathname merged with DEFAULT-DIRECTORY. Set \"Pathname Defaults\" to the
[7595]500 same thing. Process the file options, and then invoke \"Read File Hook\"."
[6]501 (setf (buffer-writable buffer) t)
502 (delete-region (buffer-region buffer))
503 (let* ((pathname (pathname pathname))
504 (probed-pathname (probe-file pathname))
505 (hi::*current-buffer* buffer))
506 (cond (probed-pathname
507 (read-file probed-pathname (buffer-point buffer))
508 (setf (buffer-write-date buffer) (file-write-date probed-pathname)))
509 (t
[8428]510 (message "(New File)")
[6]511 (setf (buffer-write-date buffer) nil)))
512 (buffer-start (buffer-point buffer))
513 (setf (buffer-modified buffer) nil)
514 (let ((stored-pathname (or probed-pathname
515 (merge-pathnames pathname (default-directory)))))
516 (setf (buffer-pathname buffer) stored-pathname)
517 (setf (value pathname-defaults) stored-pathname)
518 (process-file-options buffer stored-pathname)
519 (invoke-hook read-file-hook buffer probed-pathname))))
520
521
522
523
524;;;; File writing.
525
526(defhvar "Add Newline at EOF on Writing File"
527 "This controls whether WRITE-BUFFER-FILE adds a newline at the end of the
528 file when it ends at the end of a non-empty line. When set, this may be
529 :ask-user and WRITE-BUFFER-FILE will prompt; otherwise, just add one and
530 inform the user. When nil, never add one and don't ask."
531 :value :ask-user)
532
533(defhvar "Keep Backup Files"
534 "When set, .BAK files will be saved upon file writing. This defaults to nil."
535 :value nil)
536
537(defhvar "Write File Hook"
538 "These functions are called when a buffer has been written. Each function
539 must take the buffer as an argument.")
540
541(defun write-buffer-file (buffer pathname)
542 "Write's buffer to pathname. This assumes pathname is somehow related to
543 the buffer's pathname, and if the buffer's write date is not the same as
544 pathname's, then this prompts the user for confirmation before overwriting
545 the file. This consults \"Add Newline at EOF on Writing File\" and
546 interacts with the user if necessary. This sets \"Pathname Defaults\", and
547 the buffer is marked unmodified. The buffer's pathname and write date are
548 updated, and the buffer is renamed according to the new pathname if possible.
549 This invokes \"Write File Hook\"."
550 (let ((buffer-pn (buffer-pathname buffer)))
551 (let ((date (buffer-write-date buffer))
552 (file-date (when (probe-file pathname) (file-write-date pathname))))
553 (when (and buffer-pn date file-date
554 (equal (make-pathname :version nil :defaults buffer-pn)
555 (make-pathname :version nil :defaults pathname))
556 (/= date file-date))
557 (unless (prompt-for-yes-or-no :prompt (list
558 "File has been changed on disk since it was read.~%Overwrite ~A anyway? "
559 (namestring buffer-pn))
560 :help
561 "Type No to abort writing the file or Yes to overwrite the disk version."
562 :default nil)
563 (editor-error "Write aborted."))))
564 (let ((val (value add-newline-at-eof-on-writing-file)))
565 (when val
566 (let ((end (buffer-end-mark buffer)))
567 (unless (start-line-p end)
568 (when (if (eq val :ask-user)
569 (prompt-for-y-or-n
570 :prompt
571 (list "~A~%File does not have a newline at EOF, add one? "
572 (buffer-name buffer))
573 :default t)
574 t)
575 (insert-character end #\newline)
576 (message "Added newline at EOF."))))))
577 (setv pathname-defaults pathname)
578 (write-file (buffer-region buffer) pathname)
579 (let ((tn (truename pathname)))
580 (message "~A written." (namestring tn))
581 (setf (buffer-modified buffer) nil)
582 (unless (equal tn buffer-pn)
583 (setf (buffer-pathname buffer) tn))
[810]584 (setf (buffer-write-date buffer) (file-write-date tn))
[12859]585 (let ((name (pathname-to-buffer-name tn)))
[6]586 (unless (getstring name *buffer-names*)
587 (setf (buffer-name buffer) name)))))
[12859]588 (invoke-hook write-file-hook buffer))
[6]589
[7502]590(defcommand "Write File" (p &optional (buffer (current-buffer)))
[12859]591 "Prompts for a filename, changes the buffer pathname to it and saves it.
592 The prefix argument is ignored."
[7502]593 (declare (ignore p))
594 (hemlock-ext:save-hemlock-buffer buffer :pathname :prompt))
[12859]595
[7502]596(defcommand "Save To File" (p &optional (buffer (current-buffer)))
[6]597 "Prompts for a filename and writes a copy of the buffer to it. Buffer's
598 pathname (and modified state) is unchanged.
[798]599 The prefix argument is ignored."
[6]600 (declare (ignore p))
[8428]601 (hemlock-ext:save-hemlock-buffer buffer :pathname :prompt :copy t))
[12859]602
[6]603(defcommand "Save File" (p &optional (buffer (current-buffer)))
604 "Writes the contents of the current buffer to the associated file. If there
605 is no associated file, one is prompted for."
606 (declare (ignore p))
607 (when (buffer-modified buffer)
608 (hemlock-ext:save-hemlock-buffer buffer)))
609
610(defhvar "Save All Files Confirm"
611 "When non-nil, prompts for confirmation before writing each modified buffer."
612 :value t)
613
614(defcommand "Save All Files" (p)
615 "Saves all modified buffers in their associated files.
616 If a buffer has no associated file it is ignored even if it is modified.."
617 "Saves each modified buffer that has a file."
618 (declare (ignore p))
619 (let ((saved-count 0))
620 (dolist (b *buffer-list*)
621 (let ((pn (buffer-pathname b))
622 (name (buffer-name b)))
623 (when
624 (and (buffer-modified b)
625 pn
626 (or (not (value save-all-files-confirm))
627 (prompt-for-y-or-n
628 :prompt (list
629 "Write ~:[buffer ~A as file ~S~;file ~*~S~], ~
630 Y or N: "
631 (string= (pathname-to-buffer-name pn) name)
632 name (namestring pn))
633 :default t)))
634 (write-buffer-file b pn)
635 (incf saved-count))))
636 (if (zerop saved-count)
637 (message "No files were saved.")
638 (message "Saved ~S file~:P." saved-count))))
639
640(defcommand "Backup File" (p)
641 "Write the buffer to a file without changing the associated name."
642 "Write the buffer to a file without changing the associated name."
643 (declare (ignore p))
644 (let ((file (prompt-for-file :prompt "Backup to File: "
645 :help
646 "Name of a file to backup the current buffer in."
647 :default (buffer-default-pathname (current-buffer))
648 :must-exist nil)))
649 (write-file (buffer-region (current-buffer)) file)
650 (message "~A written." (namestring (truename file)))))
651
652
653
654
655;;;; Buffer hacking commands:
656
657
658(defcommand "Buffer Not Modified" (p)
[6572]659 "Make the current buffer not modified."
[6]660 "Make the current buffer not modified."
661 (declare (ignore p))
662 (setf (buffer-modified (current-buffer)) nil)
663 (message "Buffer marked as unmodified."))
664
665
666
667(defcommand "Set Buffer Read-Only" (p)
668 "Toggles the read-only flag for the current buffer."
669 "Toggles the read-only flag for the current buffer."
670 (declare (ignore p))
671 (let ((buffer (current-buffer)))
672 (message "Buffer ~S is now ~:[read-only~;writable~]."
673 (buffer-name buffer)
674 (setf (buffer-writable buffer) (not (buffer-writable buffer))))))
675
676(defcommand "Set Buffer Writable" (p)
677 "Make the current buffer modifiable."
678 "Make the current buffer modifiable."
679 (declare (ignore p))
680 (let ((buffer (current-buffer)))
681 (setf (buffer-writable buffer) t)
682 (message "Buffer ~S is now writable." (buffer-name buffer))))
683
Note: See TracBrowser for help on using the repository browser.