; -*- Mode:LISP; Package:STEVE; Readtable:CL; Base:10 -*- ; ;Sunday the twenty-fourth of July, 1983; 12:00:15 am ;Copyright (c) July 1983 by Christopher Eliot ; and Massachusetts Institute of Technology. ;Permission to copy all or part of this material is granted, provided ; that the copies are not made or distributed for resale, the MIT ; copyright notice and reference to the source file and the software ; distribution version appear, and that notice is given that copying ; is by permission of Massachusetts Institute of Technology. ;DIRED. (defvar dired-alist nil) (defvar dired-delete-list nil) (defvar dired-previous-bindings) (defparameter dired-bindings nil) ;(editor-bind-key '(#\control-x #\D) dired) (define-binding '(#\control-x #\D) dired 'DIRED ("Fundamental") "Directory editing subsystem") (defun dired (&optional (path nil) &aux (file (buffer-file-name *editor-buffer*))) (cond ((null path) (cond ((and (argument?) (=& *argument* 1)) (setq path (send (send file :new-type :wild) :new-version :wild))) ((and (argument?) (=& *argument* 4)) (setq path (pathname (mx-prompter #'read-file-name "Dired: ")))) (t (setq path (send (send (send file :new-name :wild) :new-type :wild) :new-version :wild))))) (t (setq path (pathname path)))) (unless (send path :device) (setq path (send path :new-device (send file :device)))) (unless (send path :directory) (setq path (send path :new-directory (send file :directory)))) (unless (send path :name) (setq path (send path :new-name :wild))) (unless (send path :type) (setq path (send path :new-type :wild))) (unless (send path :version) (setq path (send path :new-version :wild))) (let ((buffer) (point)) (when (not (null dired-alist)) (ed-lose "Dired does not work recursively")) (unwind-protect (progn (setq buffer (buffer "Dired buffer" :create t)) (setq point (point buffer)) (setup-dired-buffer buffer path) (with-many-key-bindings-named (dired-previous-bindings dired-bindings) (loop do (send point :move (line-next (buffer-content buffer)) 0) do (unless (recursive-editor "Dired" :point point) (ed-warn "Aborted Dired")) until (read-dired-buffer buffer)))) (%kill-buffer-primitive buffer) (setq dired-alist nil)))) (defun setup-dired-buffer (buffer path) (declare (special bp)) (let ((bp (make-bp buffer (buffer-content buffer) 0)) (month-names (time:mode-language-fetch :short time:*month-strings* "month name"))) (declare (special bp month-names)) (send bp :insert-string "DIRED ") (send bp :insert-string (or (send (send path :host) :name-as-file-computer) "")) (send bp :insert-string "::") (send bp :insert-string (or (send path :device) "")) (send bp :insert-string ":") (send bp :insert-string (or (send path :directory-namestring) "")) (send bp :insert-char #\return) (mapdirectory #'(lambda (dirinfo) (push (list (bp-line bp) (car dirinfo)) dired-alist) (send bp :insert-string (directory-display-line-string dirinfo month-names)) (send bp :insert-char #\return)) path '(:creation-date :author :protection :blocks)) (setf (buffer-access buffer) buffer-access-buffer-read-only))) (defmacro dired-line-as-pathname (line) `(cadr (assoc ,line dired-alist))) (defun read-dired-buffer (buffer) (setq dired-delete-list nil) (loop for line first (line-next (buffer-content buffer)) then (line-next line) while line do (mung-one-dired-line line)) (if (null dired-delete-list) (progn (ed-warning "No Files Deleted") t) (dired-delete-files-carefully dired-delete-list))) (defun dired-delete-files-carefully (files-list) (overwrite-start) (oustr "Deleting these files:" overwrite-stream) (loop for path in files-list for i upfrom 0 if (0p (\\& i 4)) do (terpri overwrite-stream) do (format overwrite-stream "~a ~16a " (if (0p (dired-count-later-versions path)) #\> #\space) (send path :file-namestring))) (terpri overwrite-stream) (oustr "OK? " overwrite-stream) (cond ((dired-yes-or-no-p) (loop with flag = nil for file in files-list for message = (delete-file file nil) unless (null message) do (progn (setq flag t) (format overwrite-stream "~&Error deleting file ~a~%~a" file message)) finally (when flag ;;synchronize. (send terminal-io :peek-char))) t) (t nil))) (defun dired-yes-or-no-p () (clear-input terminal-io) (loop for compare in '(#\Y #\E #\S) for char = (read-char terminal-io) if (not (char-equal char compare)) do (case char (#\bell (ed-warn "Aborted")) (t (return nil))) do (write-char compare terminal-io) finally (return t))) (defun mung-one-dired-line (line) (unless (empty-line? line) (case (char (line-chars line) 0) (#\space nil) (#\D (push (dired-line-as-pathname line) dired-delete-list)) (t (ed-warning "Bad Dired Line ~s" (line-chars line)))))) ; ;Dired mapping functions which select some set of lines to work on. ; (defun dired-map-over-lines (function) ;; Map over lines as specified by the normal handling of arguments. (let ((access (buffer-access *editor-buffer*))) (setf (buffer-access *editor-buffer*) buffer-access-any) (cond ((-p *argument*) (not-first-line) (loop for i from 1 to (-& *argument*) do (send *editor-cursor* :to-end-of-previous-line) do (send *editor-cursor* :set-pos 0) while (not (first-line?)) if (not (empty-line? (bp-line *editor-cursor*))) do (funcall function (bp-line *editor-cursor*)))) (t (loop for i from 1 to *argument* if (not (empty-line? (bp-line *editor-cursor*))) do (funcall function (bp-line *editor-cursor*)) while (not (last-line?)) do (send *editor-cursor* :to-beginning-of-next-line)))) (setf (buffer-access *editor-buffer*) access) (send *editor-cursor* :set-pos 0)) nil) (defun dired-map-by-file-name (function) (let ((pathname (dired-line-as-pathname (bp-line *editor-cursor*)))) (when (null pathname) (ed-lose "Current line not a Dired filename")) (let ((file-name (send pathname :name)) (access (buffer-access *editor-buffer*))) (setf (buffer-access *editor-buffer*) buffer-access-any) (loop for line first (bp-line *editor-cursor*) then (line-previous line) while (and line (line-previous line) ;First line is a header. (not (empty-line? line)) (string-equal (send (dired-line-as-pathname line) :name) file-name)) do (funcall function line)) (loop for line first (line-next (bp-line *editor-cursor*)) then (line-next line) while (and line (not (empty-line? line)) (string-equal (send (dired-line-as-pathname line) :name) file-name)) do (funcall function line)) (setf (buffer-access *editor-buffer*) access))) (send *editor-cursor* :set-pos 0) nil) (defun dired-map-over-buffer (function) (let ((access (buffer-access *editor-buffer*))) (setf (buffer-access *editor-buffer*) buffer-access-any) (loop for line first (line-next (buffer-content *editor-buffer*)) ;;Skip the header line. then (line-next line) while line if (not (empty-line? line)) do (funcall function line)) (send *editor-cursor* :set-pos 0) (setf (buffer-access *editor-buffer*) access)) nil) (defun dired-map-possibly-by-file-name (function) (if (or argument-supplied? (not (=& 1 *argument*))) (dired-map-over-buffer function) (dired-map-by-file-name function))) ; ;Now the special dired functions. ; (defun dired-mark-for-deletion () (dired-map-over-lines #'(lambda (line) (send line :replace-char 0 #\D)))) (defun dired-space () (dired-map-over-lines #'null)) (defun dired-delete () (let ((*argument* (-& *argument*))) (dired-map-over-lines #'(lambda (line) (send line :replace-char 0 #\space))))) (defun dired-undo-mark-for-deletion () (dired-map-over-lines #'(lambda (line) (send line :replace-char 0 #\space)))) (defun dired-up-line () (let ((*argument* (-& *argument*))) (dired-map-over-lines #'null))) (defun dired-help () (dired-map-possibly-by-file-name #'dired-auto-delete-line)) (defun dired-auto-delete-line (line) (let ((path nil)) (when (and (not (empty-line? line)) (setq path (dired-line-as-pathname line)) (or (member (send path :type) *temp-file-fn2-list*) (>=& (dired-count-later-versions path) *file-versions-kept*))) (send line :replace-char 0 #\D)))) (defun dired-count-later-versions (pathname) (loop with name = (send pathname :name) with type = (send pathname :type) with version = (send pathname :version) for tail on dired-alist when (string-equal name (send (cadar tail) :name)) return (loop for ((line path) . end) on tail while (string-equal (send path :name) name) counting (and (>& (send path :version) version) (string-equal (send path :type) type))))) (defun dired-view-file () (let ((file (dired-line-as-pathname (bp-line *editor-cursor*)))) (when (null file) (ed-lose "Current line is not a filename")) (view-file file)) nil) (defun dired-recursive-edit () (let ((file (dired-line-as-pathname (bp-line *editor-cursor*)))) (when (null file) (ed-lose "Current line is not a filename")) (with-many-key-bindings dired-previous-bindings (recursive-editor "Edit" :buffer (buffer file :create t)))) nil) (bindmetax dired-command-help dired-command-help) (defun dired-command-help () (overwrite-start) (loop for (key binding doc-string . doc-args) in dired-bindings do (format overwrite-stream "~%~%Key ~s is bound to ~s~%" key binding) do (apply #'format overwrite-stream doc-string doc-args)) (more-aborter "*done*") nil) ;The actual bindings. (defmacro dired-bind (key binding documentation &rest doc-args) `(progn (setq dired-bindings (nconc (delq (assoc ,key dired-bindings) dired-bindings) (list (list ,key ',binding ,documentation ,@doc-args)))) nil)) (dired-bind #\H dired-help "Automatically select files for deletion. See variables: *file-versions-kept* (now ~a) and *temp-file-fn2-list* (now ~a)" *file-versions-kept* *temp-file-fn2-list*) (dired-bind #\? dired-command-help "Print documention on DIRED commands") (dired-bind #\rubout dired-delete "Remove deletion markings from previous line(s)") (dired-bind #\space dired-space "Move down one or more lines") (dired-bind #\return dired-space "Move down one or more lines") (dired-bind #\control-n dired-space "Move down one or more lines") (dired-bind #\control-p dired-up-line "Move up one or more lines") (dired-bind #\D dired-mark-for-deletion "Mark current file (or several) for deletion") (dired-bind #\control-d dired-mark-for-deletion "Mark current file (or several) for deletion") (dired-bind #\K dired-mark-for-deletion "Mark current file (or several) for deletion") (dired-bind #\U dired-undo-mark-for-deletion "Remove deletion mark from current (or several) files") (dired-bind #\V dired-view-file "View current file by screenfulls") (dired-bind #\Control-R dired-recursive-edit "Recursively edit the current file") (dired-bind #\E dired-recursive-edit "Recursively edit the current file") (dired-bind #\Q exit-editing-level "Exit dired and delete marked files after verification To verify type Y-E-S. Control-G at this point will abort Dired without doing anything. Any other character will return to Dired.")