source: branches/1.1/ccl/cocoa-ide/hemlock/unused/archive/diredcoms.lisp

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

Move lots of (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: 32.1 KB
Line 
1;;; -*- Log: hemlock.log; Package: Hemlock -*-
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;;; Simple directory editing support.
13;;; This file contains site dependent calls.
14;;;
15;;; Written by Blaine Burks and Bill Chiles.
16;;;
17
18(in-package :hemlock)
19
20
21(defmode "Dired" :major-p t
22 :documentation
23 "Dired permits convenient directory browsing and file operations including
24 viewing, deleting, copying, renaming, and wildcard specifications.")
25
26
27(defstruct (dired-information (:print-function print-dired-information)
28 (:conc-name dired-info-))
29 pathname ; Pathname of directory.
30 pattern ; FILE-NAMESTRING with wildcard possibly.
31 dot-files-p ; Whether to include UNIX dot files.
32 write-date ; Write date of directory.
33 files ; Simple-vector of dired-file structures.
34 file-list) ; List of pathnames for files, excluding directories.
35
36(defun print-dired-information (obj str n)
37 (declare (ignore n))
38 (format str "#<Dired Info ~S>" (namestring (dired-info-pathname obj))))
39
40
41(defstruct (dired-file (:print-function print-dired-file)
42 (:constructor make-dired-file (pathname)))
43 pathname
44 (deleted-p nil)
45 (write-date nil))
46
47(defun print-dired-file (obj str n)
48 (declare (ignore n))
49 (format str "#<Dired-file ~A>" (namestring (dired-file-pathname obj))))
50
51
52
53
54;;;; "Dired" command.
55
56;;; *pathnames-to-dired-buffers* is an a-list mapping directory namestrings to
57;;; buffers that display their contents.
58;;;
59(defvar *pathnames-to-dired-buffers* ())
60
61(make-modeline-field
62 :name :dired-cmds :width 20
63 :function
64 #'(lambda (buffer window)
65 (declare (ignore buffer window))
66 " Type ? for help. "))
67
68(defcommand "Dired" (p &optional directory)
69 "Prompts for a directory and edits it. If a dired for that directory already
70 exists, go to that buffer, otherwise create one. With an argument, include
71 UNIX dot files."
72 "Prompts for a directory and edits it. If a dired for that directory already
73 exists, go to that buffer, otherwise create one. With an argument, include
74 UNIX dot files."
75 (let ((info (if (hemlock-bound-p 'dired-information)
76 (value dired-information))))
77 (dired-guts nil
78 ;; Propagate dot-files property to subdirectory edits.
79 (or (and info (dired-info-dot-files-p info))
80 p)
81 directory)))
82
83(defcommand "Dired with Pattern" (p)
84 "Do a dired, prompting for a pattern which may include a single *. With an
85 argument, include UNIX dit files."
86 "Do a dired, prompting for a pattern which may include a single *. With an
87 argument, include UNIX dit files."
88 (dired-guts t p nil))
89
90(defun dired-guts (patternp dot-files-p directory)
91 (let* ((dpn (value pathname-defaults))
92 (directory (dired-directorify
93 (or directory
94 (prompt-for-file
95 :prompt "Edit Directory: "
96 :help "Pathname to edit."
97 :default (make-pathname
98 :device (pathname-device dpn)
99 :directory (pathname-directory dpn))
100 :must-exist nil))))
101 (pattern (if patternp
102 (prompt-for-string
103 :prompt "Filename pattern: "
104 :help "Type a filename with a single asterisk."
105 :trim t)))
106 (full-name (namestring (if pattern
107 (merge-pathnames directory pattern)
108 directory)))
109 (name (concatenate 'simple-string "Dired " full-name))
110 (buffer (cdr (assoc full-name *pathnames-to-dired-buffers*
111 :test #'string=))))
112 (declare (simple-string full-name))
113 (setf (value pathname-defaults) (merge-pathnames directory dpn))
114 (change-to-buffer
115 (cond (buffer
116 (when (and dot-files-p
117 (not (dired-info-dot-files-p
118 (variable-value 'dired-information
119 :buffer buffer))))
120 (setf (dired-info-dot-files-p (variable-value 'dired-information
121 :buffer buffer))
122 t)
123 (update-dired-buffer directory pattern buffer))
124 buffer)
125 (t
126 (let ((buffer (make-buffer
127 name :modes '("Dired")
128 :modeline-fields
129 (append (value default-modeline-fields)
130 (list (modeline-field :dired-cmds)))
131 :delete-hook (list 'dired-buffer-delete-hook))))
132 (unless (initialize-dired-buffer directory pattern
133 dot-files-p buffer)
134 (delete-buffer-if-possible buffer)
135 (editor-error "No entries for ~A." full-name))
136 (push (cons full-name buffer) *pathnames-to-dired-buffers*)
137 buffer))))))
138
139;;; INITIALIZE-DIRED-BUFFER gets a dired in the buffer and defines some
140;;; variables to make it usable as a dired buffer. If there are no file
141;;; satisfying directory, then this returns nil, otherwise t.
142;;;
143(defun initialize-dired-buffer (directory pattern dot-files-p buffer)
144 (multiple-value-bind (pathnames dired-files)
145 (dired-in-buffer directory pattern dot-files-p buffer)
146 (if (zerop (length dired-files))
147 nil
148 (defhvar "Dired Information"
149 "Contains the information neccessary to manipulate dired buffers."
150 :buffer buffer
151 :value (make-dired-information :pathname directory
152 :pattern pattern
153 :dot-files-p dot-files-p
154 :write-date (file-write-date directory)
155 :files dired-files
156 :file-list pathnames)))))
157
158;;; CALL-PRINT-DIRECTORY gives us a nice way to report PRINT-DIRECTORY errors
159;;; to the user and to clean up the dired buffer.
160;;;
161(defun call-print-directory (directory mark dot-files-p)
162 (handler-case (with-output-to-mark (s mark :full)
163 (print-directory directory s
164 :all dot-files-p :verbose t :return-list t))
165 (error (condx)
166 (delete-buffer-if-possible (line-buffer (mark-line mark)))
167 (editor-error "~A" condx))))
168
169;;; DIRED-BUFFER-DELETE-HOOK is called on dired buffers upon deletion. This
170;;; removes the buffer from the pathnames mapping, and it deletes and buffer
171;;; local variables referring to it.
172;;;
173(defun dired-buffer-delete-hook (buffer)
174 (setf *pathnames-to-dired-buffers*
175 (delete buffer *pathnames-to-dired-buffers* :test #'eq :key #'cdr)))
176
177
178
179
180;;;; Dired deletion and undeletion.
181
182(defcommand "Dired Delete File" (p)
183 "Marks a file for deletion; signals an error if not in a dired buffer.
184 With an argument, this prompts for a pattern that may contain at most one
185 wildcard, an asterisk, and all names matching the pattern will be flagged
186 for deletion."
187 "Marks a file for deletion; signals an error if not in a dired buffer."
188 (dired-frob-deletion p t))
189
190(defcommand "Dired Undelete File" (p)
191 "Removes a mark for deletion; signals and error if not in a dired buffer.
192 With an argument, this prompts for a pattern that may contain at most one
193 wildcard, an asterisk, and all names matching the pattern will be unflagged
194 for deletion."
195 "Removes a mark for deletion; signals and error if not in a dired buffer."
196 (dired-frob-deletion p nil))
197
198(defcommand "Dired Delete File and Down Line" (p)
199 "Marks file for deletion and moves down a line.
200 See \"Dired Delete File\"."
201 "Marks file for deletion and moves down a line.
202 See \"Dired Delete File\"."
203 (declare (ignore p))
204 (dired-frob-deletion nil t)
205 (dired-down-line (current-point)))
206
207(defcommand "Dired Undelete File and Down Line" (p)
208 "Marks file undeleted and moves down a line.
209 See \"Dired Delete File\"."
210 "Marks file undeleted and moves down a line.
211 See \"Dired Delete File\"."
212 (declare (ignore p))
213 (dired-frob-deletion nil nil)
214 (dired-down-line (current-point)))
215
216(defcommand "Dired Delete File with Pattern" (p)
217 "Prompts for a pattern and marks matching files for deletion.
218 See \"Dired Delete File\"."
219 "Prompts for a pattern and marks matching files for deletion.
220 See \"Dired Delete File\"."
221 (declare (ignore p))
222 (dired-frob-deletion t t)
223 (dired-down-line (current-point)))
224
225(defcommand "Dired Undelete File with Pattern" (p)
226 "Prompts for a pattern and marks matching files undeleted.
227 See \"Dired Delete File\"."
228 "Prompts for a pattern and marks matching files undeleted.
229 See \"Dired Delete File\"."
230 (declare (ignore p))
231 (dired-frob-deletion t nil)
232 (dired-down-line (current-point)))
233
234;;; DIRED-FROB-DELETION takes arguments indicating whether to prompt for a
235;;; pattern and whether to mark the file deleted or undeleted. This uses
236;;; CURRENT-POINT and CURRENT-BUFFER, and if not in a dired buffer, signal
237;;; an error.
238;;;
239(defun dired-frob-deletion (patternp deletep)
240 (unless (hemlock-bound-p 'dired-information)
241 (editor-error "Not in Dired buffer."))
242 (with-mark ((mark (current-point) :left-inserting))
243 (let* ((dir-info (value dired-information))
244 (files (dired-info-files dir-info))
245 (del-files
246 (if patternp
247 (dired:pathnames-from-pattern
248 (prompt-for-string
249 :prompt "Filename pattern: "
250 :help "Type a filename with a single asterisk."
251 :trim t)
252 (dired-info-file-list dir-info))
253 (list (dired-file-pathname
254 (array-element-from-mark mark files)))))
255 (note-char (if deletep #\D #\space)))
256 (with-writable-buffer ((current-buffer))
257 (dolist (f del-files)
258 (let* ((pos (position f files :test #'equal
259 :key #'dired-file-pathname))
260 (dired-file (svref files pos)))
261 (buffer-start mark)
262 (line-offset mark pos 0)
263 (setf (dired-file-deleted-p dired-file) deletep)
264 (if deletep
265 (setf (dired-file-write-date dired-file)
266 (file-write-date (dired-file-pathname dired-file)))
267 (setf (dired-file-write-date dired-file) nil))
268 (setf (next-character mark) note-char)))))))
269
270(defun dired-down-line (point)
271 (line-offset point 1)
272 (when (blank-line-p (mark-line point))
273 (line-offset point -1)))
274
275
276
277
278;;;; Dired file finding and going to dired buffers.
279
280(defcommand "Dired Edit File" (p)
281 "Read in file or recursively \"Dired\" a directory."
282 "Read in file or recursively \"Dired\" a directory."
283 (declare (ignore p))
284 (let ((point (current-point)))
285 (when (blank-line-p (mark-line point)) (editor-error "Not on a file line."))
286 (let ((pathname (dired-file-pathname
287 (array-element-from-mark
288 point (dired-info-files (value dired-information))))))
289 (if (directoryp pathname)
290 (dired-command nil (directory-namestring pathname))
291 (change-to-buffer (find-file-buffer pathname))))))
292
293(defcommand "Dired View File" (p)
294 "Read in file as if by \"View File\" or recursively \"Dired\" a directory.
295 This associates the file's buffer with the dired buffer."
296 "Read in file as if by \"View File\".
297 This associates the file's buffer with the dired buffer."
298 (declare (ignore p))
299 (let ((point (current-point)))
300 (when (blank-line-p (mark-line point)) (editor-error "Not on a file line."))
301 (let ((pathname (dired-file-pathname
302 (array-element-from-mark
303 point (dired-info-files (value dired-information))))))
304 (if (directoryp pathname)
305 (dired-command nil (directory-namestring pathname))
306 (let* ((dired-buf (current-buffer))
307 (buffer (view-file-command nil pathname)))
308 (push #'(lambda (buffer)
309 (declare (ignore buffer))
310 (setf dired-buf nil))
311 (buffer-delete-hook dired-buf))
312 (setf (variable-value 'view-return-function :buffer buffer)
313 #'(lambda ()
314 (if dired-buf
315 (change-to-buffer dired-buf)
316 (dired-from-buffer-pathname-command nil)))))))))
317
318(defcommand "Dired from Buffer Pathname" (p)
319 "Invokes \"Dired\" on the directory part of the current buffer's pathname.
320 With an argument, also prompt for a file pattern within that directory."
321 "Invokes \"Dired\" on the directory part of the current buffer's pathname.
322 With an argument, also prompt for a file pattern within that directory."
323 (let ((pathname (buffer-pathname (current-buffer))))
324 (if pathname
325 (dired-command p (directory-namestring pathname))
326 (editor-error "No pathname associated with buffer."))))
327
328(defcommand "Dired Up Directory" (p)
329 "Invokes \"Dired\" on the directory up one level from the current Dired
330 buffer."
331 "Invokes \"Dired\" on the directory up one level from the current Dired
332 buffer."
333 (declare (ignore p))
334 (unless (hemlock-bound-p 'dired-information)
335 (editor-error "Not in Dired buffer."))
336 (let ((dirs (or (pathname-directory
337 (dired-info-pathname (value dired-information)))
338 '(:relative))))
339 (dired-command nil
340 (truename (make-pathname :directory (nconc dirs '(:UP)))))))
341
342
343
344
345;;;; Dired misc. commands -- update, help, line motion.
346
347(defcommand "Dired Update Buffer" (p)
348 "Recompute the contents of a dired buffer.
349 This maintains delete flags for files that have not been modified."
350 "Recompute the contents of a dired buffer.
351 This maintains delete flags for files that have not been modified."
352 (declare (ignore p))
353 (unless (hemlock-bound-p 'dired-information)
354 (editor-error "Not in Dired buffer."))
355 (let ((buffer (current-buffer))
356 (dir-info (value dired-information)))
357 (update-dired-buffer (dired-info-pathname dir-info)
358 (dired-info-pattern dir-info)
359 buffer)))
360
361;;; UPDATE-DIRED-BUFFER updates buffer with a dired of directory, deleting
362;;; whatever is in the buffer already. This assumes buffer was previously
363;;; used as a dired buffer having necessary variables bound. The new files
364;;; are compared to the old ones propagating any deleted flags if the name
365;;; and the write date is the same for both specifications.
366;;;
367(defun update-dired-buffer (directory pattern buffer)
368 (with-writable-buffer (buffer)
369 (delete-region (buffer-region buffer))
370 (let ((dir-info (variable-value 'dired-information :buffer buffer)))
371 (multiple-value-bind (pathnames new-dired-files)
372 (dired-in-buffer directory pattern
373 (dired-info-dot-files-p dir-info)
374 buffer)
375 (let ((point (buffer-point buffer))
376 (old-dired-files (dired-info-files dir-info)))
377 (declare (simple-vector old-dired-files))
378 (dotimes (i (length old-dired-files))
379 (let ((old-file (svref old-dired-files i)))
380 (when (dired-file-deleted-p old-file)
381 (let ((pos (position (dired-file-pathname old-file)
382 new-dired-files :test #'equal
383 :key #'dired-file-pathname)))
384 (when pos
385 (let* ((new-file (svref new-dired-files pos))
386 (write-date (file-write-date
387 (dired-file-pathname new-file))))
388 (when (= (dired-file-write-date old-file) write-date)
389 (setf (dired-file-deleted-p new-file) t)
390 (setf (dired-file-write-date new-file) write-date)
391 (setf (next-character
392 (line-offset (buffer-start point) pos 0))
393 #\D))))))))
394 (setf (dired-info-files dir-info) new-dired-files)
395 (setf (dired-info-file-list dir-info) pathnames)
396 (setf (dired-info-write-date dir-info)
397 (file-write-date directory))
398 (move-mark point (buffer-start-mark buffer)))))))
399
400;;; DIRED-IN-BUFFER inserts a dired listing of directory in buffer returning
401;;; two values: a list of pathnames of files only, and an array of dired-file
402;;; structures. This uses FILTER-REGION to insert a space for the indication
403;;; of whether the file is flagged for deletion. Then we clean up extra header
404;;; and trailing lines known to be in the output (into every code a little
405;;; slime must fall).
406;;;
407(defun dired-in-buffer (directory pattern dot-files-p buffer)
408 (let ((point (buffer-point buffer)))
409 (with-writable-buffer (buffer)
410 (let* ((pathnames (call-print-directory
411 (if pattern
412 (merge-pathnames directory pattern)
413 directory)
414 point
415 dot-files-p))
416 (dired-files (make-array (length pathnames))))
417 (declare (list pathnames) (simple-vector dired-files))
418 (filter-region #'(lambda (str)
419 (concatenate 'simple-string " " str))
420 (buffer-region buffer))
421 (delete-characters point -2)
422 (delete-region (line-to-region (mark-line (buffer-start point))))
423 (delete-characters point)
424 (do ((p pathnames (cdr p))
425 (i 0 (1+ i)))
426 ((null p))
427 (setf (svref dired-files i) (make-dired-file (car p))))
428 (values (delete-if #'directoryp pathnames) dired-files)))))
429
430
431(defcommand "Dired Help" (p)
432 "How to use dired."
433 "How to use dired."
434 (declare (ignore p))
435 (describe-mode-command nil "Dired"))
436
437(defcommand "Dired Next File" (p)
438 "Moves to next undeleted file."
439 "Moves to next undeleted file."
440 (unless (dired-line-offset (current-point) (or p 1))
441 (editor-error "Not enough lines.")))
442
443(defcommand "Dired Previous File" (p)
444 "Moves to previous undeleted file."
445 "Moves to next undeleted file."
446 (unless (dired-line-offset (current-point) (or p -1))
447 (editor-error "Not enough lines.")))
448
449;;; DIRED-LINE-OFFSET moves mark n undeleted file lines, returning mark. If
450;;; there are not enough lines, mark remains unmoved, this returns nil.
451;;;
452(defun dired-line-offset (mark n)
453 (with-mark ((m mark))
454 (let ((step (if (plusp n) 1 -1)))
455 (dotimes (i (abs n) (move-mark mark m))
456 (loop
457 (unless (line-offset m step 0)
458 (return-from dired-line-offset nil))
459 (when (blank-line-p (mark-line m))
460 (return-from dired-line-offset nil))
461 (when (char= (next-character m) #\space)
462 (return)))))))
463
464
465
466
467;;;; Dired user interaction functions.
468
469(defun dired-error-function (string &rest args)
470 (apply #'editor-error string args))
471
472(defun dired-report-function (string &rest args)
473 (clear-echo-area)
474 (apply #'message string args))
475
476(defun dired-yesp-function (string &rest args)
477 (prompt-for-y-or-n :prompt (cons string args) :default t))
478
479
480
481
482;;;; Dired expunging and quitting.
483
484(defcommand "Dired Expunge Files" (p)
485 "Expunges files marked for deletion.
486 Query the user if value of \"Dired File Expunge Confirm\" is non-nil. Do
487 the same with directories and the value of \"Dired Directory Expunge
488 Confirm\"."
489 "Expunges files marked for deletion.
490 Query the user if value of \"Dired File Expunge Confirm\" is non-nil. Do
491 the same with directories and the value of \"Dired Directory Expunge
492 Confirm\"."
493 (declare (ignore p))
494 (when (expunge-dired-files)
495 (dired-update-buffer-command nil))
496 (maintain-dired-consistency))
497
498(defcommand "Dired Quit" (p)
499 "Expunges the files in a dired buffer and then exits."
500 "Expunges the files in a dired buffer and then exits."
501 (declare (ignore p))
502 (expunge-dired-files)
503 (delete-buffer-if-possible (current-buffer)))
504
505(defhvar "Dired File Expunge Confirm"
506 "When set (the default), \"Dired Expunge Files\" and \"Dired Quit\" will ask
507 for confirmation before deleting the marked files."
508 :value t)
509
510(defhvar "Dired Directory Expunge Confirm"
511 "When set (the default), \"Dired Expunge Files\" and \"Dired Quit\" will ask
512 for confirmation before deleting each marked directory."
513 :value t)
514
515(defun expunge-dired-files ()
516 (multiple-value-bind (marked-files marked-dirs) (get-marked-dired-files)
517 (let ((dired:*error-function* #'dired-error-function)
518 (dired:*report-function* #'dired-report-function)
519 (dired:*yesp-function* #'dired-yesp-function)
520 (we-did-something nil))
521 (when (and marked-files
522 (or (not (value dired-file-expunge-confirm))
523 (prompt-for-y-or-n :prompt "Really delete files? "
524 :default t
525 :must-exist t
526 :default-string "Y")))
527 (setf we-did-something t)
528 (dolist (file-info marked-files)
529 (let ((pathname (car file-info))
530 (write-date (cdr file-info)))
531 (if (= write-date (file-write-date pathname))
532 (dired:delete-file (namestring pathname) :clobber t
533 :recursive nil)
534 (message "~A has been modified, it remains unchanged."
535 (namestring pathname))))))
536 (when marked-dirs
537 (dolist (dir-info marked-dirs)
538 (let ((dir (car dir-info))
539 (write-date (cdr dir-info)))
540 (if (= write-date (file-write-date dir))
541 (when (or (not (value dired-directory-expunge-confirm))
542 (prompt-for-y-or-n
543 :prompt (list "~a is a directory. Delete it? "
544 (directory-namestring dir))
545 :default t
546 :must-exist t
547 :default-string "Y"))
548 (dired:delete-file (directory-namestring dir) :clobber t
549 :recursive t)
550 (setf we-did-something t))
551 (message "~A has been modified, it remains unchanged.")))))
552 we-did-something)))
553
554
555
556
557;;;; Dired copying and renaming.
558
559(defhvar "Dired Copy File Confirm"
560 "Can be either t, nil, or :update. T means always query before clobbering an
561 existing file, nil means don't query before clobbering an existing file, and
562 :update means only ask if the existing file is newer than the source."
563 :value T)
564
565(defhvar "Dired Rename File Confirm"
566 "When non-nil, dired will query before clobbering an existing file."
567 :value T)
568
569(defcommand "Dired Copy File" (p)
570 "Copy the file under the point"
571 "Copy the file under the point"
572 (declare (ignore p))
573 (let* ((point (current-point))
574 (confirm (value dired-copy-file-confirm))
575 (source (dired-file-pathname
576 (array-element-from-mark
577 point (dired-info-files (value dired-information)))))
578 (dest (prompt-for-file
579 :prompt (if (directoryp source)
580 "Destination Directory Name: "
581 "Destination Filename: ")
582 :help "Name of new file."
583 :default source
584 :must-exist nil))
585 (dired:*error-function* #'dired-error-function)
586 (dired:*report-function* #'dired-report-function)
587 (dired:*yesp-function* #'dired-yesp-function))
588 (dired:copy-file source dest :update (if (eq confirm :update) t nil)
589 :clobber (not confirm)))
590 (maintain-dired-consistency))
591
592(defcommand "Dired Rename File" (p)
593 "Rename the file or directory under the point"
594 "Rename the file or directory under the point"
595 (declare (ignore p))
596 (let* ((point (current-point))
597 (source (dired-namify (dired-file-pathname
598 (array-element-from-mark
599 point
600 (dired-info-files (value dired-information))))))
601 (dest (prompt-for-file
602 :prompt "New Filename: "
603 :help "The new name for this file."
604 :default source
605 :must-exist nil))
606 (dired:*error-function* #'dired-error-function)
607 (dired:*report-function* #'dired-report-function)
608 (dired:*yesp-function* #'dired-yesp-function))
609 ;; ARRAY-ELEMENT-FROM-MARK moves mark to line start.
610 (dired:rename-file source dest :clobber (value dired-rename-file-confirm)))
611 (maintain-dired-consistency))
612
613(defcommand "Dired Copy with Wildcard" (p)
614 "Copy files that match a pattern containing ONE wildcard."
615 "Copy files that match a pattern containing ONE wildcard."
616 (declare (ignore p))
617 (let* ((dir-info (value dired-information))
618 (confirm (value dired-copy-file-confirm))
619 (pattern (prompt-for-string
620 :prompt "Filename pattern: "
621 :help "Type a filename with a single asterisk."
622 :trim t))
623 (destination (namestring
624 (prompt-for-file
625 :prompt "Destination Spec: "
626 :help "Destination spec. May contain ONE asterisk."
627 :default (dired-info-pathname dir-info)
628 :must-exist nil)))
629 (dired:*error-function* #'dired-error-function)
630 (dired:*yesp-function* #'dired-yesp-function)
631 (dired:*report-function* #'dired-report-function))
632 (dired:copy-file pattern destination :update (if (eq confirm :update) t nil)
633 :clobber (not confirm)
634 :directory (dired-info-file-list dir-info)))
635 (maintain-dired-consistency))
636
637(defcommand "Dired Rename with Wildcard" (p)
638 "Rename files that match a pattern containing ONE wildcard."
639 "Rename files that match a pattern containing ONE wildcard."
640 (declare (ignore p))
641 (let* ((dir-info (value dired-information))
642 (pattern (prompt-for-string
643 :prompt "Filename pattern: "
644 :help "Type a filename with a single asterisk."
645 :trim t))
646 (destination (namestring
647 (prompt-for-file
648 :prompt "Destination Spec: "
649 :help "Destination spec. May contain ONE asterisk."
650 :default (dired-info-pathname dir-info)
651 :must-exist nil)))
652 (dired:*error-function* #'dired-error-function)
653 (dired:*yesp-function* #'dired-yesp-function)
654 (dired:*report-function* #'dired-report-function))
655 (dired:rename-file pattern destination
656 :clobber (not (value dired-rename-file-confirm))
657 :directory (dired-info-file-list dir-info)))
658 (maintain-dired-consistency))
659
660(defcommand "Delete File" (p)
661 "Delete a file. Specify directories with a trailing slash."
662 "Delete a file. Specify directories with a trailing slash."
663 (declare (ignore p))
664 (let* ((spec (namestring
665 (prompt-for-file
666 :prompt "Delete File: "
667 :help '("Name of File or Directory to delete. ~
668 One wildcard is permitted.")
669 :must-exist nil)))
670 (directoryp (directoryp spec))
671 (dired:*error-function* #'dired-error-function)
672 (dired:*report-function* #'dired-report-function)
673 (dired:*yesp-function* #'dired-yesp-function))
674 (when (or (not directoryp)
675 (not (value dired-directory-expunge-confirm))
676 (prompt-for-y-or-n
677 :prompt (list "~A is a directory. Delete it? "
678 (directory-namestring spec))
679 :default t :must-exist t :default-string "Y")))
680 (dired:delete-file spec :recursive t
681 :clobber (or directoryp
682 (value dired-file-expunge-confirm))))
683 (maintain-dired-consistency))
684
685(defcommand "Copy File" (p)
686 "Copy a file, allowing ONE wildcard."
687 "Copy a file, allowing ONE wildcard."
688 (declare (ignore p))
689 (let* ((confirm (value dired-copy-file-confirm))
690 (source (namestring
691 (prompt-for-file
692 :prompt "Source Filename: "
693 :help "Name of File to copy. One wildcard is permitted."
694 :must-exist nil)))
695 (dest (namestring
696 (prompt-for-file
697 :prompt (if (directoryp source)
698 "Destination Directory Name: "
699 "Destination Filename: ")
700 :help "Name of new file."
701 :default source
702 :must-exist nil)))
703 (dired:*error-function* #'dired-error-function)
704 (dired:*report-function* #'dired-report-function)
705 (dired:*yesp-function* #'dired-yesp-function))
706 (dired:copy-file source dest :update (if (eq confirm :update) t nil)
707 :clobber (not confirm)))
708 (maintain-dired-consistency))
709
710(defcommand "Rename File" (p)
711 "Rename a file, allowing ONE wildcard."
712 "Rename a file, allowing ONE wildcard."
713 (declare (ignore p))
714 (let* ((source (namestring
715 (prompt-for-file
716 :prompt "Source Filename: "
717 :help "Name of file to rename. One wildcard is permitted."
718 :must-exist nil)))
719 (dest (namestring
720 (prompt-for-file
721 :prompt (if (directoryp source)
722 "Destination Directory Name: "
723 "Destination Filename: ")
724 :help "Name of new file."
725 :default source
726 :must-exist nil)))
727 (dired:*error-function* #'dired-error-function)
728 (dired:*report-function* #'dired-report-function)
729 (dired:*yesp-function* #'dired-yesp-function))
730 (dired:rename-file source dest
731 :clobber (not (value dired-rename-file-confirm))))
732 (maintain-dired-consistency))
733
734(defun maintain-dired-consistency ()
735 (dolist (info *pathnames-to-dired-buffers*)
736 (let* ((directory (directory-namestring (car info)))
737 (buffer (cdr info))
738 (dir-info (variable-value 'dired-information :buffer buffer))
739 (write-date (file-write-date directory)))
740 (unless (= (dired-info-write-date dir-info) write-date)
741 (update-dired-buffer directory (dired-info-pattern dir-info) buffer)))))
742
743
744
745
746;;;; Dired utilities.
747
748;;; GET-MARKED-DIRED-FILES returns as multiple values a list of file specs
749;;; and a list of directory specs that have been marked for deletion. This
750;;; assumes the current buffer is a "Dired" buffer.
751;;;
752(defun get-marked-dired-files ()
753 (let* ((files (dired-info-files (value dired-information)))
754 (length (length files))
755 (marked-files ())
756 (marked-dirs ()))
757 (unless files (editor-error "Not in Dired buffer."))
758 (do ((i 0 (1+ i)))
759 ((= i length) (values (nreverse marked-files) (nreverse marked-dirs)))
760 (let* ((thing (svref files i))
761 (pathname (dired-file-pathname thing)))
762 (when (and (dired-file-deleted-p thing) ; file marked for delete
763 (probe-file pathname)) ; file still exists
764 (if (directoryp pathname)
765 (push (cons pathname (file-write-date pathname)) marked-dirs)
766 (push (cons pathname (file-write-date pathname))
767 marked-files)))))))
768
769;;; ARRAY-ELEMENT-FROM-MARK -- Internal Interface.
770;;;
771;;; This counts the lines between it and the beginning of the buffer. The
772;;; number is used to index vector as if each line mapped to an element
773;;; starting with the zero'th element (lines are numbered starting at 1).
774;;; This must use AREF since some modes use this with extendable vectors.
775;;;
776(defun array-element-from-mark (mark vector
777 &optional (error-msg "Invalid line."))
778 (when (blank-line-p (mark-line mark)) (editor-error error-msg))
779 (aref vector
780 (1- (count-lines (region
781 (buffer-start-mark (line-buffer (mark-line mark)))
782 mark)))))
783
784;;; DIRED-NAMIFY and DIRED-DIRECTORIFY are implementation dependent slime.
785;;;
786(defun dired-namify (pathname)
787 (let* ((string (namestring pathname))
788 (last (1- (length string))))
789 (if (char= (schar string last) #\/)
790 (subseq string 0 last)
791 string)))
792;;;
793;;; This is necessary to derive a canonical representation for directory
794;;; names, so "Dired" can map various strings naming one directory to that
795;;; one directory.
796;;;
797(defun dired-directorify (pathname)
798 (let ((directory (ext:unix-namestring pathname)))
799 (if (directoryp directory)
800 directory
801 (pathname (concatenate 'simple-string (namestring directory) "/")))))
802
803
804
805
806;;;; View Mode.
807
808(defmode "View" :major-p nil
809 :setup-function 'setup-view-mode
810 :cleanup-function 'cleanup-view-mode
811 :precedence 5.0
812 :documentation
813 "View mode scrolls forwards and backwards in a file with the buffer read-only.
814 Scrolling off the end optionally deletes the buffer.")
815
816(defun setup-view-mode (buffer)
817 (defhvar "View Return Function"
818 "Function that gets called when quitting or returning from view mode."
819 :value nil
820 :buffer buffer)
821 (setf (buffer-writable buffer) nil))
822;;;
823(defun cleanup-view-mode (buffer)
824 (delete-variable 'view-return-function :buffer buffer)
825 (setf (buffer-writable buffer) t))
826
827(defcommand "View File" (p &optional pathname)
828 "Reads a file in as if by \"Find File\", but read-only. Commands exist
829 for scrolling convenience."
830 "Reads a file in as if by \"Find File\", but read-only. Commands exist
831 for scrolling convenience."
832 (declare (ignore p))
833 (let* ((pn (or pathname
834 (prompt-for-file
835 :prompt "View File: " :must-exist t
836 :help "Name of existing file to read into its own buffer."
837 :default (buffer-default-pathname (current-buffer)))))
838 (buffer (make-buffer (format nil "View File ~A" (gensym)))))
839 (visit-file-command nil pn buffer)
840 (setf (buffer-minor-mode buffer "View") t)
841 (change-to-buffer buffer)
842 buffer))
843
844(defcommand "View Return" (p)
845 "Return to a parent buffer, if it exists."
846 "Return to a parent buffer, if it exists."
847 (declare (ignore p))
848 (unless (call-view-return-fun)
849 (editor-error "No View return method for this buffer.")))
850
851(defcommand "View Quit" (p)
852 "Delete a buffer in view mode."
853 "Delete a buffer in view mode, invoking VIEW-RETURN-FUNCTION if it exists for
854 this buffer."
855 (declare (ignore p))
856 (let* ((buf (current-buffer))
857 (funp (call-view-return-fun)))
858 (delete-buffer-if-possible buf)
859 (unless funp (editor-error "No View return method for this buffer."))))
860
861;;; CALL-VIEW-RETURN-FUN returns nil if there is no current
862;;; view-return-function. If there is one, it calls it and returns t.
863;;;
864(defun call-view-return-fun ()
865 (if (hemlock-bound-p 'view-return-function)
866 (let ((fun (value view-return-function)))
867 (cond (fun
868 (funcall fun)
869 t)))))
870
871
872(defhvar "View Scroll Deleting Buffer"
873 "When this is set, \"View Scroll Down\" deletes the buffer when the end
874 of the file is visible."
875 :value t)
876
877(defcommand "View Scroll Down" (p)
878 "Scroll the current window down through its buffer.
879 If the end of the file is visible, then delete the buffer if \"View Scroll
880 Deleting Buffer\" is set. If the buffer is associated with a dired buffer,
881 this returns there instead of to the previous buffer."
882 "Scroll the current window down through its buffer.
883 If the end of the file is visible, then delete the buffer if \"View Scroll
884 Deleting Buffer\" is set. If the buffer is associated with a dired buffer,
885 this returns there instead of to the previous buffer."
886 (if (and (not p)
887 (displayed-p (buffer-end-mark (current-buffer))
888 (current-window))
889 (value view-scroll-deleting-buffer))
890 (view-quit-command nil)
891 (scroll-window-down-command p)))
892
893(defcommand "View Edit File" (p)
894 "Turn off \"View\" mode in this buffer."
895 "Turn off \"View\" mode in this buffer."
896 (declare (ignore p))
897 (let ((buf (current-buffer)))
898 (setf (buffer-minor-mode buf "View") nil)
899 (warn-about-visit-file-buffers buf)))
900
901(defcommand "View Help" (p)
902 "Shows \"View\" mode help message."
903 "Shows \"View\" mode help message."
904 (declare (ignore p))
905 (describe-mode-command nil "View"))
Note: See TracBrowser for help on using the repository browser.