;; DIRED commands for Emacs.  $Revision: 4.53 $
;; Copyright (C) 1985, 1986, 1990 Free Software Foundation, Inc.

;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 1, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

;; Enhanced from 18.55 dired by Sebastian Kremer.
;; Send bug reports to <sk@thp.uni-koeln.de>.

(provide 'dired)

(defconst dired-version (substring "$Revision: 4.53 $" 11 -2)
  "The revision number of dired (as string).  The complete RCS id is:

  $Id: dired.el,v 4.53 90/12/21 12:09:56 sk Exp $

Don't forget to mention this when reporting bugs.")

;; compatibility package when using Emacs 18.55
(require 'emacs-19)

;; can now contain even `F', but still not `i'.
;In loaddefs.el
;(defvar dired-listing-switches "-al"
;  "Switches passed to ls for dired. MUST contain the `l' option.
;CANNOT contain the `F' option.")

;;; patched by Manabu Higashida for demacs-1.1 91/10/28
(defvar dired-chmod-program
  "chmod"
  "Pathname of chmod command.")

(defvar dired-chgrp-program
  "chgrp"
  "Pathname of chgrp command.")
;;; end of patch

(defvar dired-chown-program
  (if (memq system-type '(hpux usg-unix-v)) "/bin/chown" "/etc/chown")
  "Pathname of chown command.")

(defvar dired-ls-program "ls"
  ;; GNU ls has no way to suppress the group, so one might prefer /bin/ls.
  "*Absolute or relative name of the ls program used by dired.")

(defvar dired-ls-F-marks-symlinks nil
  "*Set this to t if dired-ls-program with -lF marks the symbolic link
itself with a trailing @ (usually the case under Ultrix).

Example: if `ln -s foo bar; ls -F bar' gives `bar -> foo', set it to
nil (the default), if it gives `bar@ -> foo', set it to t.

Dired checks if there is really a @ appended.  Thus, if you have a
marking ls program on one host and a non-marking on another host, and
don't care about symbolic links which really contain a trailing @, you
can always set this variable to t.")

(defvar dired-directory nil
  "The directory name or shell wildcard passed as argument to ls.
Local to each dired buffer.")

(defvar dired-actual-switches nil
  "The actual (buffer-local) value of dired-listing-switches.")

;; This makes matches rather slow - perhaps -is should be forbidden.
;; If you don't use -is, you can set this to "".
(defvar dired-re-inode-size ;;"\\(\\s *[0-9]*\\s *[0-9]* \\)?"
  "\\s *[0-9]*\\s *[0-9]* ?" ; this seems to be slightly faster
  ;;"Regexp for optional initial inode and file size as produced
  ;;by ls' -i and -s flags."
)

;; These regexps must be tested at beginning-of-line, but are also
;; used to search for next matches, so omitting "^" won't do.
;; Replacing "^" by "\n" might be faster, but fails on the first line,
;; thus excluding the possibility to mark subdir lines.

(defconst dired-re-mark "^[^ \n]")
;; "Regexp matching a marked line.
;; Important: the match ends just after the marker."
;; "\n[^ \n]" 
(defconst dired-re-maybe-mark "^. ")
;;; patched by Manabu Higashida for demacs-1.1 91/10/28 
;;; original lines are
;(defconst dired-re-dir (concat dired-re-maybe-mark dired-re-inode-size "d"))
;(defconst dired-re-sym (concat dired-re-maybe-mark dired-re-inode-size "l"))
;;; and new lines are
(defconst dired-re-dir (concat dired-re-maybe-mark dired-re-inode-size "d[-r]"))
(defconst dired-re-sym (concat dired-re-maybe-mark dired-re-inode-size "l[-r]"))
;;; end of patch
(defconst dired-re-exe
  (mapconcat (function
	      (lambda (x)
		(concat dired-re-maybe-mark dired-re-inode-size x)))
	     '("-[-r][-w][xs][-r][-w].[-r][-w]." 
	       "-[-r][-w].[-r][-w][xs][-r][-w]."
	       "-[-r][-w].[-r][-w].[-r][-w][xst]")
	     "\\|"))
(defconst dired-re-dot "^.* \\.\\.?$")

;;; Customizable variables:

;; Might use {,} for bash or csh:
(defvar dired-mark-prefix "" "*Prepended to marked files.")
(defvar dired-mark-postfix "" "*Appended to marked files.")
(defvar dired-mark-separator " " "*Separates marked files.")

;; User might like the shorter "! on %s: " and "& on %s: " to save screen space:
(defvar dired-background-prompt "Background shell command on %s: "
  "*Format string for \\[dired-mark-background-shell-command] prompt.")

(defvar dired-shell-prompt "Shell command on %s: "
  "*Format string for \\[dired-mark-shell-command] prompt.")

(defvar shell-maximum-command-length 10000
  ;; 10K is a reasonable length to give the user a chance for second
  ;; thoughts.
  ;; SunOS 4.1 csh(1) mentions
  ;; 	1048576 as system limit on argument lists (that's a meg!)
  ;;	max. 1706 arguments to a command using file name expansion
  ;; 	1024 as maximum word length
  ;; Assuming 10 chars per filename, about 17000 should be OK.
  "*If non-nil, maximum number of bytes a dired shell command can have
before the user is asked for confirmation.")

(defvar dired-print-command "print %s"
  "Format string for shell command to print files in dired.
Can actually be used for any special purpose shell command to be run
by \\[dired-mark-print].")

(defvar dired-trivial-filenames "^\\.\\.?$\\|^#"
  "*Regexp of files to skip when moving point to the first file of a
new directory listing.
Nil means move to the subdir line, t means move to first file.")

(defvar dired-basename-regexp "\\(.+\\)\\.\\(.+\\)$")

;; user might prefer 'y-or-n-p or even 'identity, in effect disabling
;; all confirmation upon deletion. 
(or (fboundp 'dired-yes)
    (fset 'dired-yes 'yes-or-no-p))

;;; Hook variables

(defvar dired-load-hook nil
  "Run after loading dired.
You can customize key bindings or load extensions with this.")

(defvar dired-mode-hook nil
  "Run in each new dired buffer.")

(defvar dired-readin-hook nil
  "After each listing of a file or directory, this hook is run
with the buffer narrowed to the listing.")

;; An example filter to squeeze spaces:
;(setq dired-readin-hook
;      '(lambda () (goto-char (point-min))
;	 (while (re-search-forward " +" nil t) (replace-match " "))))
;
;  See dired-extra.el for an example on how to use it for sorting on
;  file size.   It also supports use of several different markers
;  (other than `D' and `*') in parallel and a minibuffer history for
;  shell commands.  Email if you want to try it.  It is about 20K.

;;; Global internal variables

;; next two used by function dired-mark-prompt
(defvar dired-mark-count 0
  "Count of marked files as determined by the last dired-mark-get-files.")
(defvar dired-mark-files nil
  "List of marked files as determined by the last dired-mark-get-files.")

(defvar dired-flagging-regexp nil
  "Last regexp used in flagging files.")

;;; Macros must be defined before they are used - for the byte compiler.

(defmacro dired-count-up ()
  ;; Increment variable dired-mark-count.
  '(setq dired-mark-count (1+ dired-mark-count)))

(defun dired-plural-s ()
  (if (= 1 dired-mark-count) "" "s"))

(defmacro dired-mark-if (predicate msg)
  (` (let ((buffer-read-only nil))
       (save-excursion
	 (setq dired-mark-count 0)
	 (message "0 %ss..." (, msg))
	 (goto-char (point-min))
	 (while (not (eobp))
	   (if (, predicate)
	       (progn
		 (delete-char 1)
		 (insert dired-marker-char)
		 (setq dired-mark-count (1+ dired-mark-count))))
	   (forward-line 1))
	 (message "%s %s%s %s%s."
		  dired-mark-count
		  (, msg)
		  (dired-plural-s)
		  (if (eq dired-marker-char ?\ ) "un" "")
		  (if (eq dired-marker-char ?D) "flagged" "marked"))))))

(defmacro dired-mark-map (body arg)
;  "Macro: Perform BODY with point on each marked line and
;mark it again (so BODY can call dired-redisplay without losing markers).
;If no file was marked, execute BODY on the current line.
;If ARG is non-nil, use current file instead."
  ;; BODY should not be too long as it is expanded three times.
  (` (let (buffer-read-only found)
       (if arg
	   (, body)
	 (let (opoint (regexp (dired-marker-regexp)))
	   (save-excursion
	     (goto-char (point-min))
	     (while (re-search-forward regexp nil t)
	       ;; If body contains dired-redisplay, the deletion (and
	       ;; new insertion) of the line confuses save-excursion.
	       (setq opoint (point))	; column 1 stays, however
	       (, body)
	       (goto-char opoint)
	       (setq found t))))
	 (or found (, body))))))

;; The following functions are redefinable for VMS or ange-ftp
;; - or for customization.

(defun dired-ls (file &optional switches wildcard full-directory-p)
;  "Insert ls output of FILE, optionally formatted with SWITCHES.
;Optional third arg WILDCARD means treat FILE as shell wildcard.
;Optional fourth arg FULL-DIRECTORY-P means file is a directory and
;switches do not contain `d'.
;
;SWITCHES default to dired-listing-switches.
;Uses dired-ls-program and maybe shell-file-name to do the work."
  (if (not dired-ls-program)
      (dos-dired-ls file switches wildcard full-directory-p)
    (progn
      (or switches (setq switches dired-listing-switches))
      (if wildcard
	  (let ((default-directory (file-name-directory file)))
	    (call-process shell-file-name nil t nil
			  (if (eq system-type 'ms-dos) "\/c" "-c")
			  (concat dired-ls-program " -d " switches " "
				  (file-name-nondirectory file))))
	(call-process dired-ls-program nil t nil switches
		      (if (eq system-type 'ms-dos)
			  (directory-file-name file)
			file))))))

(defun dired-call-process (program discard &rest arguments)
;  "Run PROGRAM with output to current buffer unless DISCARD is t.
;Remaining arguments are strings passed as command arguments to PROGRAM."
;;; patched by Manabu Higashida for demacs-1.1 91/10/28
;;; original line is
;  (apply 'call-process program nil (not discard) nil arguments))
;;; and new lines are
  (if (file-exists-p program)
      (apply 'call-process program nil (not discard) nil arguments)
    (and (not discard)
	 (insert "Command not found."))))
;;; end of patch

;; A "why" command (`W'?) could pop-up this:
(defconst dired-log-buf "*Dired log*")

(defun dired-why ()
  "Pop up a buffer with error log output from Dired's last subprocesses."
  (interactive)
  (pop-to-buffer dired-log-buf))

(defun dired-check-process-handler ()
  ;;"Run from function dired-check-process if there is output.
  ;; Insert output in a log buffer and returns nil."
  ;;- Old version raised error and aborted:
  ;;-(progn (display-buffer err-buffer) (error "%s... error!" msg))
  ;; Could cons up a list of failed args as with deleted files.
  (let ((log-buf dired-log-buf))
    (save-excursion
      (set-buffer (get-buffer-create log-buf))
      (goto-char (point-max))
      (insert "\n\t" (current-time-string) "\t("  program ")\n")
      (insert-buffer err-buffer))
    (message "%s... error - type W or see buffer %s" msg log-buf)
    ;;(ding t)				; annoying
    (sit-for 1)
    nil))

(defun dired-check-process (program msg &rest arguments)
;  "Run PROGRAM, display MSG while running, and check for output.
;Remaining arguments are strings passed as command arguments to PROGRAM.
;If dired-check-process-checker returns t, call
;dired-check-process-handler and return its value.
;Else returns t for success."
  (let (err-buffer err)
    (message "%s..." msg)
    (save-excursion
      ;; Get a clean buffer for error output:
      (setq err-buffer (get-buffer-create " *dired-check-process output*"))
      (set-buffer err-buffer)
      (erase-buffer)
      (apply 'dired-call-process program nil arguments)
      ;; In Emacs 19 the exit status should be checked instead.
      ;; The following is not The Right Thing as some compress
      ;; programs are verbose by default
      (setq err (/= 0 (buffer-size))))
    ;; Check for errors and display them:
    (if err
	(dired-check-process-handler)
      (kill-buffer err-buffer)
      (message "%s... done." msg)
      t)))

(defun dired-insert-headerline (dir)
  ;; No trailing slash, like ls does:
  (insert "  " (directory-file-name dir) ":")
  ;; put cursor on root subdir line:
  (save-excursion (insert "\n")))

(defun dired-readin (dirname buffer)
  (save-excursion
    (message "Reading directory %s..." dirname)
    (set-buffer buffer)
    (let ((buffer-read-only nil))
      (widen)
      (erase-buffer)
      (setq dirname (expand-file-name dirname))
      (if (eq system-type 'vax-vms)
	  (vms-read-directory dirname dired-actual-switches buffer)
	(if (file-directory-p dirname)
	    (dired-ls dirname dired-actual-switches nil t)
	  (if (not (file-readable-p
		    (directory-file-name (file-name-directory dirname))))
	      (insert "Directory " dirname " inaccessible or nonexistent.\n")
	    ;; else assume it contains wildcards:
	    (dired-ls dirname dired-actual-switches t))))
      (goto-char (point-min))
      (indent-rigidly (point-min) (point-max) 2)
      (run-hooks 'dired-readin-hook)
      ;; We need this to make the root dir have a header line as all
      ;; other subdirs have:
      (goto-char (point-min))
      (dired-insert-headerline default-directory))
    (set-buffer-modified-p nil)
    (message "Reading directory %s...done" dirname)))

;; This differs from dired-buffers in that it does not consider
;; subdirs of default-directory and searches for the _first_ match
(defun dired-find-buffer (dirname)
  (let ((blist (buffer-list))
	found)
    (while blist
      (save-excursion
        (set-buffer (car blist))
	(if (and (eq major-mode 'dired-mode)
		 (equal dired-directory dirname))
	    (setq found (car blist)
		  blist nil)
	  (setq blist (cdr blist)))))
    (or found
	(create-file-buffer (directory-file-name dirname)))))

(defun dired-read-dir-and-switches (str)
  ;; For use in interactive.
  (list
   (read-file-name (format "Dired %s (directory): " str)
		   nil default-directory nil)
   (if current-prefix-arg 
       (read-string "Dired listing switches: "
		    dired-listing-switches))))

(defun dired (dirname &optional switches)
  "`Edit' directory DIRNAME--delete, rename, print, etc. some files in it.
Prefix arg lets you change the buffer local value of dired-actual-switches.
Dired displays a list of files in DIRNAME (which may also have
  shell wildcards appended to select certain files).
You can move around in it with the usual commands.
You can flag files for deletion with C-d and then delete them by
  typing `x'. 
Type `h' after entering dired for more info."
  ;; Cannot use (interactive "D") because of wildcards.
  (interactive (dired-read-dir-and-switches ""))
  (switch-to-buffer (dired-noselect dirname switches)))

(defun dired-other-window (dirname &optional switches)
  "`Edit' directory DIRNAME.  Like M-x dired but selects in another window."
  (interactive (dired-read-dir-and-switches "in other window "))
  (switch-to-buffer-other-window (dired-noselect dirname switches)))

(defun dired-noselect (dirname &optional switches)
  ;; Like M-x dired but returns the dired buffer as value, does not
  ;; select it.
  (or dirname (setq dirname default-directory))
  ;; This loses the distinction between "/foo/*/" and "/foo/*" that
  ;; some shells make:
  (setq dirname (expand-file-name (directory-file-name dirname)))
  (if (file-directory-p dirname)
      (setq dirname (file-name-as-directory dirname)))
  (dired-internal-noselect dirname switches))

(defun dired-internal-noselect (dirname &optional switches)
  (let ((buffer (dired-find-buffer dirname))
	(old-buf (current-buffer)))
    (or switches (setq switches dired-listing-switches))
    (save-excursion
      (set-buffer buffer)
      ;; must be set before dired-readin inserts the root line:
      (setq default-directory (if (file-directory-p dirname)
				  dirname (file-name-directory dirname)))
      (let ((dired-actual-switches switches))
	(dired-readin dirname buffer))
      (dired-mode dirname switches))
    ;; changing point inside a save-excursion is rather pointless... 
    (unwind-protect
	(progn			
	  (set-buffer buffer)
	  (goto-char (point-min))
	  (dired-initial-position))
      (set-buffer old-buf))
    buffer))

(defun dired-remember-marks ()
  ;; Return alist of files and their marks, from point to eob.
  (let (fil chr alist)
    (while (re-search-forward dired-re-mark nil t)
      (if (setq fil (dired-get-filename nil t))
	  (setq chr (preceding-char)
		alist (cons (cons fil chr) alist))))
    alist))

(defun dired-mark-remembered (alist)
  ;; Mark all files remembered in ALIST.
  (let (elt fil chr)
    (while alist
      (setq elt (car alist)
	    alist (cdr alist)
	    fil (car elt)
	    chr (cdr elt))
      (if (dired-goto-file fil)
	  (save-excursion
	    (beginning-of-line)
	    (delete-char 1)
	    (insert chr))))))

(defun dired-revert (&optional arg noconfirm)
  ;; Reread the dired buffer.  Should not fail even on completely
  ;; garbaged buffers.
  ;; All marks/flags are preserved.
  (let ((opoint (point))
	(ofile (dired-get-filename nil t))
	(mark-alist nil)		; save marked files
	;; Save old alist except default-directory:
	(old-subdir-alist (cdr (reverse dired-subdir-alist)))
	(buffer-read-only nil))
    ;; Remember all marks/flags.  Must unhide to make this work.
    (if selective-display
	(subst-char-in-region (point-min) (point-max) ?\r ?\n))
    (goto-char 1)
    (setq mark-alist (dired-remember-marks))
    (dired-readin dired-directory (current-buffer))
    (dired-advertise)			; no harm if already called
    (setq dired-used-F			; ls switches may have changed
	  (string-match "F" dired-actual-switches))
    (dired-build-subdir-alist)		; moving/retrieval cmds work now

    ;; Try to insert all subdirs that were displayed before
    (or (string-match "R" dired-actual-switches)
	(let (elt dir)
	  (while old-subdir-alist
	    (setq elt (car old-subdir-alist)
		  old-subdir-alist (cdr old-subdir-alist)
		  dir (car elt))
	    (condition-case ()
		(dired-insert-subdir dir)
	      (error nil)))))

    ;; Mark files that were marked before
    (dired-mark-remembered mark-alist)

    ;; Move cursor to where it was before
    (or (and ofile (dired-goto-file ofile))
	(goto-char opoint))
    (dired-move-to-filename))

  ;; outside of the let scope:
  (setq buffer-read-only t)		; gets sometimes out of sync
)

(defvar dired-mode-map nil "Local keymap for dired-mode buffers.")
(if dired-mode-map
    nil
  (setq dired-mode-map (make-keymap))
  (suppress-keymap dired-mode-map)
  (define-key dired-mode-map " "  'dired-next-line)
  (define-key dired-mode-map "!" 'dired-mark-shell-command)
  (define-key dired-mode-map "#" 'dired-flag-auto-save-files)
  (define-key dired-mode-map "$" 'dired-hide-subdir)
  (define-key dired-mode-map "&" 'dired-mark-background-shell-command)
  (define-key dired-mode-map "*" 'dired-mark-executables)
  (define-key dired-mode-map "+" 'dired-create-directory)
  (define-key dired-mode-map "." 'dired-clean-directory)
  (define-key dired-mode-map "/" 'dired-mark-dirlines)
  (define-key dired-mode-map "<" 'dired-prev-dirline)
  (define-key dired-mode-map "=" 'dired-hide-all)
  (define-key dired-mode-map ">" 'dired-next-dirline)
  (define-key dired-mode-map "?" 'dired-summary)
  (define-key dired-mode-map "@" 'dired-mark-symlinks)
  (define-key dired-mode-map "B" 'dired-mark-byte-recompile)
  (define-key dired-mode-map "C" 'dired-mark-compress)
  (define-key dired-mode-map "D" 'dired-diff)
  (define-key dired-mode-map "F" 'dired-flag-regexp-files)
  (define-key dired-mode-map "G" 'dired-mark-chgrp)
  (define-key dired-mode-map "K" 'dired-kill-subdir)
  (define-key dired-mode-map "L" 'dired-mark-load)
  (define-key dired-mode-map "M" 'dired-mark-chmod)
  (define-key dired-mode-map "O" 'dired-mark-chown)
  (define-key dired-mode-map "P" 'dired-mark-print)
  (define-key dired-mode-map "R" 'dired-rename-regexp)
  (define-key dired-mode-map "S" 'dired-sort-other)
  (define-key dired-mode-map "U" 'dired-mark-uncompress)
  (define-key dired-mode-map "W" 'dired-why)
  (define-key dired-mode-map "X" 'dired-mark-delete)
  (define-key dired-mode-map "\177" 'dired-backup-unflag)
  (define-key dired-mode-map "\C-_" 'dired-undo)
  (define-key dired-mode-map "\C-d" 'dired-flag-file-deleted)
  (define-key dired-mode-map "\C-n" 'dired-next-line)
  (define-key dired-mode-map "\C-p" 'dired-previous-line)
  (define-key dired-mode-map "\C-xu" 'dired-undo)
  (define-key dired-mode-map "\M-\C-?" 'dired-unflag-all-files)
  (define-key dired-mode-map "\M-g" 'dired-goto-file)
  (define-key dired-mode-map "\M-d" 'dired-down-subdir)
  (define-key dired-mode-map "\M-G" 'dired-goto-subdir)
  (define-key dired-mode-map "\M-k" 'dired-mark-kill)
  (define-key dired-mode-map "\M-m" 'dired-mark-files)
  (define-key dired-mode-map "\M-n" 'dired-next-subdir)
  (define-key dired-mode-map "\M-p" 'dired-prev-subdir)
  (define-key dired-mode-map "\M-u" 'dired-up-subdir)
  (define-key dired-mode-map "\M-~" 'dired-backup-diff)
  (define-key dired-mode-map "^" 'dired-up-directory)
  (define-key dired-mode-map "c" 'dired-mark-copy)
  (define-key dired-mode-map "d" 'dired-flag-file-deleted)
  (define-key dired-mode-map "e" 'dired-find-file)
  (define-key dired-mode-map "f" 'dired-find-file)
  (define-key dired-mode-map "g" 'revert-buffer)
  (define-key dired-mode-map "h" 'describe-mode)
  (define-key dired-mode-map "i" 'dired-insert-subdir)
  (define-key dired-mode-map "k" 'dired-kill-line)
  (define-key dired-mode-map "l" 'dired-mark-redisplay)
;  (define-key dired-mode-map "m" 'dired-mark-file)
  (define-key dired-mode-map "m" 'dired-mark-subdir-or-file)
  (define-key dired-mode-map "n" 'dired-next-line)
  (define-key dired-mode-map "o" 'dired-find-file-other-window)
  (define-key dired-mode-map "p" 'dired-previous-line)
  (define-key dired-mode-map "q" 'kill-buffer)
  (define-key dired-mode-map "r" 'dired-mark-move)
  (define-key dired-mode-map "s" 'dired-sort-toggle)
  (define-key dired-mode-map "u" 'dired-unflag)
  (define-key dired-mode-map "v" 'dired-view-file)
  (define-key dired-mode-map "w" 'dired-copy-filename-as-kill)
  (define-key dired-mode-map "x" 'dired-do-deletions)
  (define-key dired-mode-map "z" 'bury-buffer)
  (define-key dired-mode-map "~" 'dired-flag-backup-files)
)


;; Dired mode is suitable only for specially formatted data.
(put 'dired-mode 'mode-class 'special)

(defun dired-mode (&optional dirname switches)
  "Mode for `editing' directory listings.
In dired, you are `editing' a list of the files in a directory and
\(optionally) its subdirectories.
You can move using the usual cursor motion commands.
Letters no longer insert themselves.  Digits are prefix arguments.
Instead, type d to flag a file for Deletion.
Type m to mark a file or subdirectory for later commands.
  Most commands operate on the marked files and use the current file
  if no files are marked (or a prefix argument is given).
Type u to Unflag a file (remove its D flag or any mark).
  Type DEL to back up one line and unflag.
Type x to eXecute the deletions requested.
Type f to Find the current line's file
  (or dired it in another buffer, if it is a directory).
Type i to dired a subdirectory In situ and K to kill it again or ^ to
  go back.  Type v to view a file or its in situ subdirectory.
Type ^ to go to the parent directory.
Type < and > to move to file lines that are directories.
Type M-n, M-p, M-u, M-d to move to in situ subdirectory headerlines.
Type M-g to go to a file's line, M-G to go to a subdir headerline.
Type o to find file or dired directory in Other window.
Type # to flag temporary files (names beginning with #) for deletion.
Type ~ to flag backup files (names ending with ~) for deletion.
Type . to flag numerical backups for deletion.
  (Spares dired-kept-versions (or prefix argument) recent versions.)
Type + to create a new directory.
Type r to Rename a file or move the marked files to another directory.
Type c to Copy files.
Type D to Diff a file, M-~ to diff it with its backup.
Type l to reList files or subdirectories.
Type s to toggle sorting by name/date, S to set dired-actual-switches.
Type g to read all directories again.  This retains all marks.
Space and Rubout can be used to move down and up by lines.
Also:
 C 	 -- compress files		  U -- uncompress files
 ! 	 -- run shell command on files    & -- background shell command
 M, G, O -- change mode, group or owner of files
 L, B 	 -- load or byte-compile emacs lisp files
 F, M-m  -- flag (`D') or mark (`*') files matching a regexp
 *, @, / -- (un)mark executables, symbolic links, directories
 $, = 	 -- (un)hide this or all subdirectories
 X       -- delete marked files

If dired ever gets confused, you can either type \\[dired-revert] \
to read the
directories again, type \\[dired-mark-redisplay] \
to relist a single file or subdirectory, or
type \\[dired-build-subdir-alist] to parse the buffer again for the
directory tree.

Hooks: dired-load-hook, dired-mode-hook, dired-readin-hook (q.v.)

\\{dired-mode-map}"
  (interactive)
  (kill-all-local-variables)
  (make-local-variable 'revert-buffer-function)
  (setq revert-buffer-function 'dired-revert)
  (setq major-mode 'dired-mode)
  (setq mode-name "Dired")
  (make-local-variable 'dired-directory)
  (setq dired-directory (or dirname default-directory))
  (make-local-variable 'list-buffers-directory)
  (setq list-buffers-directory dired-directory)	; never used!?
  (make-local-variable 'dired-actual-switches)
  (setq dired-actual-switches (or switches
				  dired-listing-switches))
  (set (make-local-variable 'dired-used-F)
       (string-match "F" dired-actual-switches))
  (setq mode-line-buffer-identification
	(list (concat "Dired " dired-version " (beta): %17b")))
  (setq case-fold-search nil)
  (setq buffer-read-only t)
  (use-local-map dired-mode-map)
  (make-local-variable 'minor-mode-alist)
  (setq selective-display t)		; for subdirectory hiding
  (dired-advertise)
  (make-local-variable 'dired-subdir-alist)
  (setq dired-subdir-alist nil)
  (dired-build-subdir-alist)
  (make-local-variable 'dired-sort-mode)
  (dired-sort-mode)
  (setq minor-mode-alist
	(cons '(dired-sort-mode dired-sort-mode)
	      minor-mode-alist))
  (run-hooks 'dired-mode-hook))


(defun dired-repeat-over-lines (arg function)
  ;; This version skips non-file lines.
  (beginning-of-line)
  (while (and (> arg 0) (not (eobp)))
    (setq arg (1- arg))
    (beginning-of-line)
    (while (and (not (eobp)) (dired-between-files)) (forward-line 1))
    (save-excursion (funcall function))
    (forward-line 1)
    (dired-move-to-filename))
  (while (and (< arg 0) (not (bobp)))
    (setq arg (1+ arg))
    (forward-line -1)
    (while (and (not (bobp)) (dired-between-files)) (forward-line -1))
    (beginning-of-line)
    (save-excursion (funcall function))
    (dired-move-to-filename)))

(defun dired-flag-file-deleted (arg)
  "In dired, flag the current line's file for deletion.
With arg, repeat over several lines."
  (interactive "p")
  (dired-repeat-over-lines arg
    '(lambda ()
       (let ((buffer-read-only nil))
	 (delete-char 1)
	 (insert "D")
	 nil))))

(defun dired-read-regexp (prompt)
;; This is an extra function so that gmhist can redefine it.
  (setq dired-flagging-regexp
	(read-string prompt dired-flagging-regexp)))

(defun dired-flag-regexp-files (regexp &optional arg marker-char)
  "In dired, flag all files containing the specified REGEXP for deletion.
Use `^' and `$' if the match should span the whole (non-directory
  part) of the filename.   Exclude subdirs by hiding them.
Directories are not flagged unless a prefix argument is given."
  (interactive (list (dired-read-regexp "Flagging regexp: ")
		     current-prefix-arg))
  (let ((dired-marker-char (or marker-char ?D)))
    (dired-mark-if
     (and (or arg (not (looking-at dired-re-dir)))
	  (not (eolp))
	  (dired-this-file-matches regexp))
     "matching file")))

(defun dired-summary ()
  (interactive)
  ;>> this should check the key-bindings and use substitute-command-keys if non-standard
  (message
   ;;"d-elete, u-ndelete, x-ecute, f-ind, o-ther window, r-ename, c-opy, v-iew"
   "d-elete, u-ndelete, x-ecute, f-ind, o-ther window, r-ename, c-opy, h-elp"
   ;;"m-ark, u-nmark, d-elete, f-ind, o-ther window, r-ename, c-opy, h-elp"
))

(defun dired-unflag (arg)
  "In dired, remove the current line's delete flag then move to next line."
  (interactive "p")
  (dired-repeat-over-lines arg
    '(lambda ()
       (let ((buffer-read-only nil))
	 (delete-char 1)
	 (insert " ")
	 (forward-char -1)
	 nil))))

(defun dired-backup-unflag (arg)
  "In dired, move up a line and remove deletion flag there."
  (interactive "p")
  (dired-unflag (- arg)))

(defun dired-next-line (arg)
  "Move down ARG lines then position at filename."
  (interactive "p")
  (next-line arg)
  (dired-move-to-filename))

(defun dired-previous-line (arg)
  "Move up ARG lines then position at filename."
  (interactive "p")
  (previous-line arg)
  (dired-move-to-filename))

(defun dired-up-directory ()
  "Dired parent directory.  Tries first to find it in this buffer."
  (interactive)
  (let ((fn "..")
	(dir (dired-current-directory)))
    (setq fn (file-name-as-directory (expand-file-name fn dir)))
    (or (dired-goto-file (directory-file-name dir))
	(dired (expand-file-name	; give user a chance to abort
		(read-file-name "Dired: " fn fn t))))))

(defun dired-find-file ()
  "In dired, visit the file or directory named on this line."
  (interactive)
  (find-file (dired-get-filename)))

(defun dired-view-file ()
  "In dired, examine a file in view mode, returning to dired when done.
When file is a directory, tries to go to its in situ subdirectory."
  (interactive)
  (if (file-directory-p (dired-get-filename))
      (or (dired-goto-subdir (dired-get-filename))
	  (message "Directory %s not inserted - type i to insert or f to dired."
		   (dired-get-filename t)))
    (view-file (dired-get-filename))))

(defun dired-find-file-other-window ()
  "In dired, visit this file or directory in another window."
  (interactive)
  (find-file-other-window (dired-get-filename)))

; Now that there is dired-move-to-end-of-filename,
; use it in dired-get-filename.
(defun dired-get-filename (&optional localp no-error-if-not-filep)
  "In dired, return name of file mentioned on this line.
Value returned normally includes the directory name.
A non-nil 1st argument means use path name relative to
  default-directory, which may contain slashes if in a subdirectory.
A non-nil 2nd argument says return nil if no filename on this line,
  otherwise an error occurs."
  (let ((case-fold-search nil) file p1 p2)
    (save-excursion
      (if (setq p1 (dired-move-to-filename (not no-error-if-not-filep)))
	  (setq p2 (dired-move-to-end-of-filename no-error-if-not-filep))))
    ;; nil if no file on this line, but no-error-if-not-filep is t:
    (setq file (and p1 p2 (buffer-substring p1 p2)))
    (and file (dired-make-absolute file (dired-current-directory localp)))))

(defun dired-move-to-filename (&optional raise-error eol)
  "In dired, move to first char of filename on this line.
Returns position (point) or nil if no filename on this line."
  (or eol (setq eol (progn (end-of-line) (point))))
  (beginning-of-line)
  (if (eq system-type 'vax-vms)
      (if (re-search-forward ". [][.A-Z-0-9_$;<>]" eol t)
	  (backward-char 1)
	(if raise-error
	    (error "No file on this line.")
	  nil))
    ;; Unix case
    (if (re-search-forward
	 "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\)[ ]+[0-9]+"
	 eol t)
	(progn
	  (skip-chars-forward " ")	; there is one SPC after day of month
	  (skip-chars-forward "^ " eol)	; move after time of day (or year)
	  (skip-chars-forward " " eol)	; there is one SPC before the file name
	  (point))
      (if raise-error
	  (error "No file on this line.")
	nil))))

(defun dired-move-to-end-of-filename (&optional no-error eol)
  ;; Assumes point is at beginning of filename,
  ;; thus the rwx bit re-search-backward below will succeed in *this* line.
  ;; So, it should be called only after (dired-move-to-filename t).
  ;; case-fold-search must be nil, at least for VMS.
  ;; On failure, signals an error or returns nil.
  (let (opoint flag ex sym hidden)
    (setq opoint (point))
    (or eol (setq eol (save-excursion (end-of-line) (point))))
    (setq hidden (and selective-display
		      (save-excursion (search-forward "\r" eol t))))
    (if hidden
	nil
      (if (eq system-type 'vax-vms)
	  ;; Non-filename lines don't match
	  ;; because they have lower case letters.
	  (re-search-forward "[][.A-Z-0-9_$;<>]+" eol t)
	;; Unix case
	(save-excursion
	  (or (re-search-backward
	       "\\(.\\)[-r][-w]\\(.\\)[-r][-w]\\(.\\)[-r][-w]\\(.\\)"
	       nil t)
	      no-error
	      (error "No file on this line."))
	  (setq flag (buffer-substring (match-beginning 1) (match-end 1))
		sym (string= flag "l")
		;; ex is actually only needed when dired-used-F is t.
		ex (string-match
		    "[xst]";; execute bit set anywhere?
		    (concat
		     (buffer-substring (match-beginning 2) (match-end 2))
		     (buffer-substring (match-beginning 3) (match-end 3))
		     (buffer-substring (match-beginning 4) (match-end 4))))))
	(if sym
	    (if (re-search-forward " ->" eol t)
		(progn
		  (forward-char -3)
		  ;; we check that ls -lF really marks the link
		  (if (and dired-ls-F-marks-symlinks (eq (preceding-char) ?@))
		      (forward-char -1))))
	  (goto-char eol))
	(if (and dired-used-F
		 (or (string= flag "d")
		     (string= flag "s")
		     (and (not sym) ex))) ; ls -lF ignores x bits on symlinks
	    (forward-char -1))))
    (or no-error
	(not (eq opoint (point)))
	(error (if hidden
		   "File line is hidden, type $ to unhide."
		 "No file on this line.")))
    (if (eq opoint (point))
	nil
      (point))))

(defun dired-map-dired-file-lines (fn)
  ;; perform fn with point at the end of each non-directory line:
  ;; arguments are the short and long filename
  (save-excursion
    (let (filename longfilename (buffer-read-only nil))
      (goto-char (point-min))
      (while (not (eobp))
	(save-excursion
	  (and (not (looking-at dired-re-dir))
	       (not (eolp))
	       (setq filename (dired-get-filename t t)
		     longfilename (dired-get-filename nil t))
	       (progn (end-of-line)
		      (funcall fn filename longfilename))))
	(forward-line 1)))))

;; Perhaps something could be done to handle VMS' own backups.

(defun dired-clean-directory (keep)
  "Flag numerical backups for deletion.
Spares dired-kept-versions latest versions, and kept-old-versions oldest.
Positive numeric arg overrides dired-kept-versions;
negative numeric arg overrides kept-old-versions with minus the arg.

To clear the flags on these files, you can use \\[dired-flag-backup-files]
with a prefix argument."
  (interactive "P")
  (setq keep (if keep (prefix-numeric-value keep) dired-kept-versions))
  (let ((early-retention (if (< keep 0) (- keep) kept-old-versions))
	(late-retention (if (<= keep 0) dired-kept-versions keep))
	(file-version-assoc-list ()))
    ;; Look at each file.
    ;; If the file has numeric backup versions,
    ;; put on file-version-assoc-list an element of the form
    ;; (FILENAME . VERSION-NUMBER-LIST)
    (dired-map-dired-file-lines 'dired-collect-file-versions)
    ;; Sort each VERSION-NUMBER-LIST,
    ;; and remove the versions not to be deleted.
    (let ((fval file-version-assoc-list))
      (while fval
	(let* ((sorted-v-list (cons 'q (sort (cdr (car fval)) '<)))
	       (v-count (length sorted-v-list)))
	  (if (> v-count (+ early-retention late-retention))
	      (rplacd (nthcdr early-retention sorted-v-list)
		      (nthcdr (- v-count late-retention)
			      sorted-v-list)))
	  (rplacd (car fval)
		  (cdr sorted-v-list)))
	(setq fval (cdr fval))))
    ;; Look at each file.  If it is a numeric backup file,
    ;; find it in a VERSION-NUMBER-LIST and maybe flag it for deletion.
    (dired-map-dired-file-lines 'dired-trample-file-versions)))

(defun dired-collect-file-versions (ignore fn)
  ;; If it looks like fn has versions, we make a list of the versions.
  ;; We may want to flag some for deletion.
    (let* ((base-versions
	    (concat (file-name-nondirectory fn) ".~"))
	   (bv-length (length base-versions))
	   (possibilities (file-name-all-completions
			   base-versions
			   (file-name-directory fn)))
	   (versions (mapcar 'backup-extract-version possibilities)))
      (if versions
	  (setq file-version-assoc-list (cons (cons fn versions)
					      file-version-assoc-list)))))

(defun dired-trample-file-versions (ignore fn)
  (let* ((start-vn (string-match "\\.~[0-9]+~$" fn))
	 base-version-list)
    (and start-vn
	 (setq base-version-list	; there was a base version to which
	       (assoc (substring fn 0 start-vn)	; this looks like a
		      file-version-assoc-list))	; subversion
	 (not (memq (string-to-int (substring fn (+ 2 start-vn)))
		    base-version-list))	; this one doesn't make the cut
	 (progn (beginning-of-line)
		(delete-char 1)
		(insert "D")))))

(defun dired-flag-backup-and-auto-save-files ()
  "Flag all backup and temporary files for deletion.
Backup files have names ending in `~'.  Auto save file names usually
start with `#'."
  (interactive)
  (dired-flag-backup-files)
  (dired-flag-auto-save-files))

(defun dired-create-directory (directory)
  "Create a directory called DIRECTORY"
  (interactive
   (list (read-file-name "Create directory: " (dired-current-directory))))
  (let ((expanded (directory-file-name (expand-file-name directory))))
    (make-directory expanded)
    (dired-add-entry-all-buffers (file-name-directory expanded)
				 (file-name-nondirectory expanded))
  (dired-next-line 1)))


(defun dired-buffers (dir)
;; Return a list of buffers that dired DIR (possibly as subdir).
;; As a side effect, killed dired buffers for DIR are removed from
;; dired-buffers.
  (setq dir (file-name-as-directory dir))
  (let ((alist dired-buffers) result elt)
    (while alist
      (setq elt (car alist))
      (if (dired-in-this-tree dir (car elt))
	  (let ((buf (cdr elt)))
	    (if (buffer-name buf)
		(setq result (cons buf result))
	      ;; else buffer is killed - clean up:
	      (setq dired-buffers (delq elt dired-buffers)))))
      (setq alist (cdr alist)))
    result))

(defun dired-fun-in-all-buffers (directory fun)
  ;; In all buffers dired'ing DIRECTORY, run FUN.
  ;; FUN returns t for success, nil else.
  (let ((buf-list (dired-buffers directory)) buf success-list)
    (while buf-list
      (setq buf (car buf-list)
	    buf-list (cdr buf-list))
      (save-excursion
	(set-buffer buf)
	(if (funcall fun)
	    (setq success-list (cons (buffer-name buf) success-list)))))
    success-list))

(defun dired-add-entry-all-buffers (directory filename)
  (dired-fun-in-all-buffers
   directory
   (function (lambda () (dired-add-entry directory filename)))))
   
(defun dired-add-entry (directory filename)
  ;; Note that this adds the entry `out of order' if files sorted by
  ;; time, etc.
  ;; At least this version tries to insert in the right subdirectory.
  ;; And it skips "." or ".." (dired-trivial-filenames).
  ;; Hidden subdirs are exposed if a file is added there.
  (setq directory (file-name-as-directory directory))
  (let*
      ((opoint (point))
       (cur-dir (dired-current-directory))
       (reason
	(catch 'not-found
	  (if (string= directory cur-dir)
	      (progn;; unhide if necessary
		(if (dired-subdir-hidden-p cur-dir) (dired-unhide-subdir))
		;; We are already where we should be, except in one case:
		;; If point is before the *root* subdir line or its
		;; total line, inserting there is ugly.
		;; (Everything *before* the rootline is considered as
		;; belonging to the root dir, too - in contrast to other
		;; subdirs)
		(if (string= default-directory cur-dir)
		    (let ((p (save-excursion
			       (dired-goto-next-file)
			       (point))))
		      (if (<= (point) p)
			  (goto-char p)))))
	    ;; else try to find correct place to insert
	    (if (dired-goto-subdir directory)
		(progn;; unhide if necessary
		  (if (looking-at "\r");; point is at end of subdir line
		      (dired-unhide-subdir))
		  ;; found - skip subdir and `total' line
		  ;; and uninteresting files like . and ..
		  (dired-goto-next-nontrivial-file))
	      ;; not found
	      (throw 'not-found "Subdir not found")))
	  ;; found and point is at The Right Place:
	  (let ((buffer-read-only nil))
	    (beginning-of-line)
	    (insert "  ")
	    (dired-ls (dired-make-absolute filename directory)
		      (concat dired-actual-switches "d"))
	    (forward-line -1)
	    (dired-move-to-filename t)	; raise an error if ls output
					; is strange
	    (let* ((beg (point))
		   (end (progn (dired-move-to-end-of-filename) (point))))
	      (setq filename (buffer-substring beg end))
	      (delete-region beg end)
	      (insert (file-name-nondirectory filename)))
	    (beginning-of-line)
	    (if dired-readin-hook
		(save-restriction
		  (narrow-to-region (point)
				    (save-excursion (forward-line 1) (point)))
		  (run-hooks 'dired-readin-hook)))
	    )
	  ;; return nil if all went well
	  nil)))
    (if reason
	(progn
	  (goto-char opoint)		; don't move away on failure
	  ;;-(message "Couldn't add %s%s: %s" directory filename reason)
	  ))
    (not reason)			; return t on succes, nil else
    ))

(defun dired-remove-entry-all-buffers (file)
  (dired-fun-in-all-buffers
   (file-name-directory file)
   (function (lambda () (dired-remove-entry file)))))

(defun dired-remove-entry (file)
  (save-excursion
    (and (dired-goto-file file)
	 (let ((buffer-read-only nil))
	   (delete-region (progn (beginning-of-line) (point))
			  (save-excursion (forward-line 1) (point)))))))


(defun dired-diff (file)
  "Compare this file with another (default: file at mark), by running `diff'.
The other file is the first file given to `diff'.
See the command `diff'."
  (interactive
   (let ((default (if (mark)
		      (save-excursion (goto-char (mark))
				      (dired-get-filename t)))))
     (list (read-file-name (format "Diff %s with: %s"
				   (dired-get-filename t)
				   (if default
				       (concat "(default " default ") ")
				     ""))
			   (dired-current-directory) default t))))
  (diff file (dired-get-filename t)))

(defun dired-backup-diff ()
  "Diff this file with its backup file.
Uses the latest backup, if there are several numerical backups.
If this file is a backup, diff it with its original.
The backup file is the first file given to `diff'."
  (interactive)
  (let (bak ori (file (dired-get-filename)))
    (if (backup-file-name-p file)
	(setq bak file
	      ori (file-name-sans-versions file))
      (setq bak (latest-backup-file file)
	    ori file))
    (diff bak ori)))

;; This function is missing in files.el:
(defun latest-backup-file (fn)
  "Return the latest existing backup of FILE, or nil."
  ;; First try simple backup, then the highest numbered of the
  ;; numbered backups.
  ;; Ignore the value of version-control because we look for existing
  ;; backups, which maybe were made earlier with another value of
  ;; version-control.
  (or
   (let ((bak (make-backup-file-name fn)))
     (if (file-exists-p bak) bak))
   (let* ((base-versions (concat (file-name-nondirectory fn) ".~"))
	  (bv-length (length base-versions)))
     (car (sort
	   (file-name-all-completions base-versions (file-name-directory fn))
	   ;; bv-length is a fluid var for backup-extract-version:
	   (function
	    (lambda (fn1 fn2)
	      (> (backup-extract-version fn1)
		 (backup-extract-version fn2)))))))))

(defun dired-compress ()
  (let* ((buffer-read-only nil)
	 (from-file (dired-get-filename))
	 (to-file (concat from-file ".Z")))
    (if (dired-check-process
;;; patched by Manabu Higashida for demacs-1.1 91/10/29
;;; original line is
;	 "compress" (format "Compressing %s" from-file) "-f" from-file)
;;; and new line is
	 dired-compress-program (format "Compressing %s" from-file) "-f" from-file)
;;; end of patch	 
	(dired-redisplay to-file))))

(defun dired-uncompress ()
  (let* ((buffer-read-only nil)
	 (from-file (dired-get-filename))
	 (to-file (substring from-file 0 -2)))
    (if (dired-check-process
;;; patched by Manabu Higashida for demacs-1.1 91/10/29
;;; original line is
;	 "uncompress" (format "Uncompressing %s" from-file) from-file)
;;; and new line is
	 dired-uncompress-program (format "Uncompressing %s" from-file) from-file)
;;; end of patch
	(dired-redisplay to-file))))

; The (un)compress functions are just mapped over all marked files
; It is not very effective to call many processes if one would suffice,
; but you can use dired-mark-shell-command if necessary,
; This version has the advantage of redisplaying after each
; (un)compress the corresponding (different!) filename.
; And it does not stop if a single file cannot be compressed.

(defun dired-mark-compress (&optional arg)
  "Compress marked files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (dired-mark-map (dired-compress) arg))

(defun dired-mark-uncompress (&optional arg)
  "Uncompress marked files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (dired-mark-map (dired-uncompress) arg))

;; Elisp commands on files

(defun dired-byte-recompile ()
  (let* ((buffer-read-only nil)
	 (from-file (dired-get-filename))
	 (new-file (concat from-file "c")))
    (if (not (string-match "\\.el$" from-file))
	(message "%s is no .el file!" from-file)
      (byte-compile-file from-file)
      (dired-remove-entry-all-buffers new-file)
      (dired-add-entry-all-buffers (file-name-directory new-file)
				   (file-name-nondirectory new-file)))))

(defun dired-mark-byte-recompile (&optional arg)
  "Byte recompile marked Emacs lisp files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (dired-mark-map (dired-byte-recompile) arg))

(defun dired-mark-load (&optional arg)
  "Load the marked Emacs lisp files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (dired-mark-map (load (dired-get-filename)) arg))

;; Change file modes.

; Don't use absolute path for ch{mod,grp} as /bin should be in
; any PATH.  However, chown is special: dired-chown-program.

(defun dired-mark-chmod (&optional arg)
  "Change mode of marked files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (let* ((files (dired-mark-get-files nil t arg))
 	 (mode (read-string (format "Change %s to Mode: "
 				    (dired-mark-prompt)))) )
;;; patched by Manabu Higashida for demacs-1.1 91/10/29
;;; original line is
;    (apply 'dired-check-process "chmod"
;;; and new line is
    (apply 'dired-check-process dired-chmod-program
;;; end of patch
 	   (format "chmod %s " mode) mode files)
    (dired-mark-redisplay arg)))

(defun dired-mark-chgrp (&optional arg)
  "Change group of marked files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (let* ((files (dired-mark-get-files nil t arg))
	 (group (read-string (format "Change %s to Group: "
				    (dired-mark-prompt)))) )
;;; patched by Manabu Higashida for demacs-1.1 91/10/29
;;; original line is
;    (apply 'dired-check-process "chgrp"
;;; and new line is
    (apply 'dired-check-process dired-chgrp-program
;;; end if patch
	   (format "chgrp %s " group) group files)
    (dired-mark-redisplay arg)))

(defun dired-mark-chown (&optional arg)
  "Change owner of marked files
\(or this file if none are marked or a prefix argument is given)."
  (interactive "P")
  (let* ((files (dired-mark-get-files nil t arg))
	 (owner (read-string (format "Change %s to Owner: "
				    (dired-mark-prompt)))) )
    (apply 'dired-check-process dired-chown-program
	   (format "chown %s " owner) owner files)
    (dired-mark-redisplay arg)))

(defun dired-redisplay (file)
  ;; Redisplay the file on this line.
  ;; Keeps any marks that may be present in column one.
  ;; Does not bother to update other dired buffers.
  (beginning-of-line)
  (let ((char (following-char)) (opoint (point)))
    (delete-region (point) (progn (forward-line 1) (point)))
    (if file
	(progn
	  (dired-add-entry (file-name-directory    file)
			   (file-name-nondirectory file))
	  ;; Replace space by old marker without moving point.
	  ;; Faster than goto+insdel inside a save-excursion?
	  (subst-char-in-region opoint (1+ opoint) ?\040 char))))
  (dired-move-to-filename))

(defun dired-mark-redisplay (&optional arg)
  "Redisplay all marked files
\(or this file if none are marked or a prefix argument is given).
If on a subdir line, redisplay that subdirectory."
  (interactive "P")
  (if (dired-get-subdir)
      (dired-insert-subdir (dired-get-subdir))
    (message "Redisplaying ...")
    (dired-mark-map (dired-redisplay (dired-get-filename)) arg)
    (dired-move-to-filename)
    (message "Redisplaying ... done.")))

(defun dired-mark-delete ()
  "Delete all files marked with the current marker char."
  (interactive)
  (dired-do-deletions t))

(defun dired-mark-kill (&optional arg)
  "Kill all marked lines (not files).
With a prefix arg, kill all lines not marked or flagged."
  (interactive "P")
  (save-excursion
    (goto-char (point-min))
    (let ((buffer-read-only nil))
      (if (not arg)
	  (flush-lines (dired-marker-regexp))
	(while (not (eobp))
	  (if (or (dired-between-files)
		  (not (looking-at "^  ")))
	      (forward-line 1)
	    (delete-region (point) (save-excursion
				     (forward-line 1)
				     (point)))))))))

(defun dired-do-deletions (&optional marked)
  "In dired, delete the files flagged for deletion."
  ;; Optional arg MARKED means delete marked instead flagged files.
  (interactive)
  (let ((regexp (if marked (dired-marker-regexp) "^D"))
	delete-list answer)
    (save-excursion
      (goto-char 1)
      (while (re-search-forward regexp nil t)
	(setq delete-list
	      (cons (cons (dired-get-filename t) (1- (point)))
		    delete-list))))
    (if (null delete-list)
	(message "(No deletions requested)")
      ;; Make the `dx' idiom less painful:
      (if (= (length delete-list) 1)
	  (setq answer
		(dired-yes (format "Delete '%s'? " (car (car delete-list)))))
	(save-window-excursion
	  (set-buffer (get-buffer-create " *Deletions*"))
	  (funcall (if (> (length delete-list) (* (window-height) 2))
		       'switch-to-buffer 'switch-to-buffer-other-window)
		   (current-buffer))
	  (erase-buffer)
	  (setq fill-column 70)
	  (let ((l (reverse delete-list)))
	    ;; Files should be in forward order for this loop.
	    (while l
	      (if (> (current-column) 59)
		  (insert ?\n)
		(or (bobp)
		    (indent-to (* (/ (+ (current-column) 19) 20) 20) 1)))
	      (insert (car (car l)))
	      (setq l (cdr l))))
	  ;; let window shrink to fit:
	  (let* ((window (selected-window))
		 (start (window-start window))
		 (window-lines (window-height window)))
	    (goto-char (point-min))
	    (enlarge-window (- (max (+ 2 (vertical-motion window-lines))
				    window-min-height)
			       window-lines))
	    (set-window-start (selected-window) start))
	  (setq answer (dired-yes "Delete these files? "))))
      (if answer
	  (save-excursion
	    (let ((l delete-list)
		  failures)
	      ;; Files better be in reverse order for this loop!
	      ;; That way as changes are made in the buffer
	      ;; they do not shift the lines still to be changed.
	      (while l
		(goto-char (cdr (car l)))
		(let ((buffer-read-only nil))
		  (condition-case ()
		      (let ((fn (dired-make-absolute (car (car l))
						     default-directory)))
			(if (and (file-directory-p fn)
				 (not (file-symlink-p fn)))
			    (remove-directory fn)
			  (delete-file fn))
			(delete-region (point)
				       (progn (forward-line 1) (point)))
			(save-excursion
			  (if (dired-goto-subdir fn)
			      (dired-kill-subdir))))
		    (error (delete-char 1)
			   (insert " ")
			   (setq failures (cons (car (car l)) failures)))))
		(setq l (cdr l)))
	      (if failures
		  (message "Deletions failed: %s"
			   (prin1-to-string failures)))))))))


(defun dired-replace-in-string (regexp to string)
  ;; Replace REGEXP with TO in STRING and return result.
  ;; No \\DIGIT escapes will be recognized in TO.
  (let ((result "") (start 0) mb me)
    (while (string-match regexp string start)
      (setq mb (match-beginning 0)
	    me (match-end 0)
	    result (concat result (substring string start mb) to)
	    start me))
    (concat result (substring string start))))

(defun dired-next-dirline (arg)
  "Goto ARG'th next directory file line."
  (interactive "p")
  (if (if (> arg 0)
	  (re-search-forward dired-re-dir nil t arg)
	(re-search-backward dired-re-dir nil t
			    (if (save-excursion (beginning-of-line)
						(looking-at dired-re-dir))
				(- 1 arg)
			      (- arg))))
      (dired-move-to-filename)		; user may type `i' or `f'
    (error "No more subdirectories.")))

(defun dired-prev-dirline (arg)
  "Goto ARG'th previous directory file line."
  (interactive "p")
  (dired-next-dirline (- arg)))

(defun dired-unflag-all-files (flag)
  "Remove a specific or all flags from every file."
  (interactive "sRemove flag: (default: all flags) ")
  (let ((count 0)
	(re (if (zerop (length flag)) dired-re-mark
	      (concat "^" (regexp-quote flag)))))
    (save-excursion
      (let ((buffer-read-only nil))
	(goto-char (point-min))
	(while (re-search-forward re nil t)
	  (progn (delete-char -1) (insert " ") (setq count (1+ count)))
	  (forward-line 1))))
    (message (format "All flags removed: %d %s" count flag) )))


(defun dired-kill-line (arg)
  "Kill this line (but not this file).
If file is displayed as in situ subdir, kill that as well, unless a
prefix arg is given."
  (interactive "P")
  (let ((buffer-read-only nil) (file (dired-get-filename nil t)))
    (delete-region (progn (beginning-of-line) (point))
		   (progn (forward-line 1) (point)))
    (and (not arg)
	 file
	 (dired-goto-subdir file)
	 (dired-kill-subdir))))

;; This function is missing in simple.el:
(defun copy-string-as-kill (string)
  "Save STRING as if killed in a buffer."
  (setq kill-ring (cons string kill-ring))
  (if (> (length kill-ring) kill-ring-max)
	(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))
  (setq kill-ring-yank-pointer kill-ring))

(defun dired-copy-filename-as-kill (&optional arg)
  "Copy this file (or subdir) name into the kill ring.
With a prefix arg, use the complete pathname of file.
Subdirs are always complete pathnames."
  (interactive "P")
  (copy-string-as-kill
   (or (dired-get-subdir)
       (if arg;; dired-get-filename's localp is not what we usually
	   (dired-get-filename);; want, esp. deep in a tree
	 (file-name-nondirectory (dired-get-filename)))))
  (message "%s" (car kill-ring)))

;; file marking

(defconst dired-marker-char ?*
  ;; so that you can write things like
  ;; (let ((dired-marker-char ?X))
  ;;    ;; great code using X markers ...
  ;;    )
  ;; For example, commands operating on two sets of files, A and B.
  ;; Or marking files with digits 0-9.  This could implicate
  ;; concentric sets or an order for the marked files.
  "In dired, character used to mark files for later commands.")

(defun dired-marker-regexp ()
  (concat "^" (regexp-quote (char-to-string dired-marker-char))))

(defun dired-mark-file (arg)
  "In dired, mark the current line's file for later commands.
With arg, repeat over several lines.
Use \\[dired-unflag-all-files] to remove all flags."
  (interactive "p")
  (let ((buffer-read-only nil))
    (dired-repeat-over-lines
     arg
     (function (lambda () (delete-char 1) (insert dired-marker-char))))))

(defun dired-mark-files (regexp &optional arg)
  "Mark all files matching REGEXP for use in later commands.
Directories are not marked unless a prefix argument is given.

This is an Emacs regexp, not a shell wildcard.	E.g., use \\.o$ for
object files - just .o will mark more than you might think.

An empty string will match all files except directories."
  (interactive
   (list (dired-read-regexp "Mark files (regexp): ")
	 current-prefix-arg))
  (dired-flag-regexp-files regexp arg dired-marker-char))

(defun dired-mark-symlinks (unflag-p)
  "Mark all symbolic links.
With prefix argument, unflag all those files."
  (interactive "P")
  (let ((dired-marker-char (if unflag-p ?\  dired-marker-char)))
    (dired-mark-if (looking-at dired-re-sym) "symbolic link")))

(defun dired-mark-dirlines (unflag-p)
  "Mark all directory file lines except `.' and `..'.
With prefix argument, unflag all those files."
  (interactive "P")
  (let ((dired-marker-char (if unflag-p ?\  dired-marker-char)))
    (dired-mark-if (and (looking-at dired-re-dir)
			(not (looking-at dired-re-dot)))
		   "directory file")))

(defun dired-mark-executables (unflag-p)
  "Mark all executable files.
With prefix argument, unflag all those files."
  (interactive "P")
  (let ((dired-marker-char (if unflag-p ?\  dired-marker-char)))
    (dired-mark-if (looking-at dired-re-exe) "executable file")))

(defun dired-flag-auto-save-files (&optional unflag-p)
  "Flag for deletion files whose names suggest they are auto save files.
A prefix argument says to unflag those files instead."
  (interactive "P")
  (let ((dired-marker-char (if unflag-p ?\  ?D))
	(bound (fboundp 'auto-save-file-name-p)))
    (dired-mark-if
       (and (not (looking-at dired-re-dir))
	    (if bound
		(let ((fn (dired-get-filename t t)))
		  (if fn (auto-save-file-name-p fn)))
	      (if (save-excursion
		    (dired-move-to-filename)
		    (looking-at "#")))))
       "auto save file")))

(defun dired-flag-backup-files (&optional unflag-p)
  "Flag all backup files (names ending with `~') for deletion.
With prefix argument, unflag all those files."
  (interactive "P")
  (let ((dired-marker-char (if unflag-p ?\  ?D))
	(bound (fboundp 'backup-file-name-p)))
    (dired-mark-if
     (and (not (looking-at dired-re-dir))
	  (if bound
	      (let ((fn (dired-get-filename t t)))
		(if fn (backup-file-name-p fn)))
	    (save-excursion
	      (end-of-line)		; symlinks are never backups
	      (forward-char -1)
	      (looking-at "~"))))
     "backup file")))

(defun dired-mark-get-files (&optional localp defaultp this-file)
  "Return the marked files as list of strings.
Values returned normally do include the directory name.
A non-nil first argument LOCALP means do not include it.
A non-nil second argument DEFAULTP means default to list with current
  file as single element if none are marked.  If this happens,
  dired-mark-defaulted is set to t.
A non-nil third argument THIS-FILE forces to use the current file.
Sets the global variables  dired-mark-count and  dired-mark-files."
  (setq dired-mark-defaulted nil)
  (if this-file
      (setq dired-mark-count 1
	    dired-mark-files (list (dired-get-filename localp)))
    (let (the-list (regexp (dired-marker-regexp)))
      (save-excursion
	(setq dired-mark-count 0)
	(goto-char (point-max))		; make list same order
	(while (re-search-backward regexp nil t) ; as in buffer
	  (setq the-list (cons (dired-get-filename localp) the-list))
	  (dired-count-up)))
      (setq dired-mark-defaulted (and defaultp (not the-list))
	    dired-mark-count (if the-list dired-mark-count (if defaultp 1 0))
	    dired-mark-files
	    (or the-list (if defaultp (list (dired-get-filename localp)) nil))))))

(defun dired-rename-regexp (regexp newname)
  "Rename all marked files containing REGEXP to NEWNAME.
See dired-flag-regexp-files for more info on REGEXP.
NEWNAME may contain \\N or \\& as in replace-match (q.v.).
REGEXP defaults to the last regexp used, but with a prefix arg
dired-basename-regexp is provided.  This makes the basename as \\1 and
the extension as \\2 available in NEWNAME."
  (interactive
   (let ((a1 (read-string "Rename from (regexp): "
			  (if current-prefix-arg
			      dired-basename-regexp
			    dired-flagging-regexp))))
     (list a1 (read-string (format "Rename %s to: " a1)))))
  (save-excursion
      (goto-char (point-min))
      (let ((buffer-read-only nil)
	    (dired-mark-count 0)
	    (re (dired-marker-regexp))
	    old new) 
	(while (and (re-search-forward re nil t)
		    (setq old (dired-get-filename)))
	  (if (dired-this-file-matches regexp)
	      (progn
		(replace-match newname t)
		(setq new (dired-get-filename))
		(rename-file old new)
		(dired-count-up))))
	  (message "%d file%s renamed." dired-mark-count (dired-plural-s)))))

(defun dired-this-file-matches (regexp)
;	  (let ((fn (dired-get-filename t t)))
;	    (if fn (string-match regexp fn)))
; fails in subdirs.
; But much worse, we can not use (replace-match) for renaming by
; regexp unless the match was in a buffer (not a string)
  (save-excursion
    (let ((beg (dired-move-to-filename)) end)
      (and beg
	   (setq end (dired-move-to-end-of-filename t))
      (save-restriction			; so that "^" in the
	(narrow-to-region beg end)	; regexp works.
	(goto-char beg)
	;; search is better than looking-at because then it is easy to
	;; replace "frizzle" by "frozzle" anywhere in a name.
	;; "^" and "$" can still be used to constrain a match.
	(re-search-forward regexp end t))))))

;;; Shell commands

(defun shell-quote (filename)
  ;; Quote everything except POSIX filename characters.
  ;; This should be safe enough even for really wierd shells.
  (let ((result "") (start 0) end)
    (while (string-match "[^---0-9a-zA-Z_./]" filename start)
      (setq end (match-beginning 0)
	    result (concat result (substring filename start end)
			   "\\" (substring filename end (1+ end)))
	    start (1+ end)))
    (concat result (substring filename start))))

(defun dired-read-shell-command (prompt)
  "Read a dired shell command prompting with PROMPT (using read-string).
This is an extra function so that you can redefine it, e.g., to use gmhist."
  (read-string prompt))

(defun dired-mark-prompt ()
  ;; Either the current file name or the marker and a count of marked
  ;; files for use in a prompt.
  (if (eq dired-mark-count 1)
      (file-name-nondirectory (car dired-mark-files))
    ;; more than 1 file:
    (format "%c [%d files]" dired-marker-char dired-mark-count)))

(defun dired-mark-background-shell-command (&optional arg)
  "Like \\[dired-mark-shell-command], but starts command in background.
This requires background.el to work."
  (interactive "P")
  (require 'background)
  (dired-mark-shell-command arg t))

(defun dired-mark-shell-command (&optional arg in-background)
  "Run a shell command on the marked files.
If there is output, it goes to a separate buffer.
The list of marked files is appended to the command string unless asterisks
  `*' indicate the place(s) where the list should go.  See variables
  dired-mark-prefix, -separator, -postfix.  If you have a curly brace
  expanding shell, you may want to set these to \"{\",\",\" and \"}\"
  to make commands like `mv *~ bak; compress bak/*~' work.
If no files are marked or a prefix arg is given, uses file on the
  current line. The prompt mentions the file or the marker, as
  appropriate.  See variables dired-shell-prompt, dired-background-prompt.
No automatic redisplay is attempted, as the file names may have
  changed.  Type \\[dired-mark-redisplay] to redisplay the marked files.

Function dired-run-shell-command does the actual work and can be
redefined for customization."
  ;; Bug: There is no way to quote a *
  (interactive "P")
  (let (result command fns
	       (prompt (if in-background dired-background-prompt
			 dired-shell-prompt)))
    (setq fns (mapconcat (function shell-quote)
			 (dired-mark-get-files t t arg)
			 dired-mark-separator))
    (if (> dired-mark-count 1)
	(setq fns (concat dired-mark-prefix fns dired-mark-postfix)))
    ;; Want to give feedback whether this file or marked files are used.
    (setq command (dired-read-shell-command (format prompt
						    (dired-mark-prompt))))
    (setq result (if (string-match "\\*" command)
		     (dired-replace-in-string "\\*" fns command)
		   (concat command " " fns)))
    ;; execute the shell command
    (dired-run-shell-command result in-background)))

;; This is an extra function so that it can be redefined for remote
;; shells or whatever.
(defun dired-run-shell-command (command &optional in-background)
  "Run shell COMMAND, optionally IN-BACKGROUND.
If COMMAND is longer than shell-maximum-command-length, you are asked
for confirmation."
  (if in-background
      (setq command (concat "cd " default-directory "; " command)))
  (if (or (and shell-maximum-command-length
	       (< (length command) shell-maximum-command-length))
	  (yes-or-no-p
	   (format
	    "Dired shell command is %d bytes long - execute anyway? "
	    (length command))))
      (if in-background
	  (background command)
	(shell-command command))))

(defun dired-mark-print (&optional arg)
  "Print the marked files
\(or this file if none are marked or a prefix argument is given).
Uses the shell command in variable dired-print-command as default."
  (interactive "P")
  (let* ((files (mapconcat (function shell-quote)
			   (dired-mark-get-files t t arg)
			   " "))
	 (command (read-string (format "Print %s with cmd: "
				       (dired-mark-prompt))
			       dired-print-command)))
    (setq dired-print-command command)
    (dired-run-shell-command (format command files))))
   

;;; Copy, move and rename

(defun dired-rename-visited (filename to-file)
  ;; Optionally rename the visited file of any buffer visiting this file.
  (and (get-file-buffer filename)
	 (y-or-n-p (message "Change visited file name of buffer %s too? "
			    (buffer-name (get-file-buffer filename))))
	 (save-excursion
	   (set-buffer (get-file-buffer filename))
	   (let ((modflag (buffer-modified-p)))
	     (set-visited-file-name to-file)
	     (set-buffer-modified-p modflag)))))

(defun dired-mark-cp-or-mv (fun fun2 msg msg1 &optional arg)
  (let* ((fn-list (dired-mark-get-files nil t arg))
	 ;; this depends on dired-mark-get-files to be run first:
	 (target (expand-file-name
		  (read-file-name
		   (format "%s %s to: "
			   (if (= 1 dired-mark-count) msg1 msg)
			   (dired-mark-prompt))
		   (dired-current-directory))))
	 (is-dir (file-directory-p target)))
    (if (and (> dired-mark-count 1)
	     (not is-dir))
	(error "Marked %s: target must be a directory: %s" msg target))
    (let (to overwrite (buffer-read-only nil))
      (or is-dir (setq to target))
      (or is-dir			; paranoid
	  (= 1 (length fn-list))
	  (error "Internal error: non-dir and more than 1 file: %s" fn-list))
      (mapcar
       (function
	(lambda (from)
	  (if is-dir			; else to = target
	      (setq to (expand-file-name
			(file-name-nondirectory from) target)))
	  (setq overwrite (file-exists-p to))
	  (funcall fun from to 0)
	  (and fun2 (funcall fun2 from to))
	  (if overwrite;; if we get here, fun hasn't been aborted
	      ;; and the old entry has to be deleted
	      ;; before adding the new entry
	      (dired-remove-entry-all-buffers to))
	  (dired-add-entry-all-buffers (file-name-directory to)
				       (file-name-nondirectory to))))
       fn-list)))
  (dired-move-to-filename))

(defun dired-mark-copy (&optional arg)
 "Copy all marked files (or this file if none are marked or prefix given)."
  (interactive "P")
  (dired-mark-cp-or-mv 'copy-file nil "Copy" "Copy" arg))

(defun dired-mark-move (&optional arg)
  "Move all marked files into a directory
\(or rename this file if none are marked or prefix given)."
  (interactive "P")
  (dired-mark-cp-or-mv
   'rename-file
   (function (lambda (from to)
	       (dired-remove-entry-all-buffers from)
	       (dired-rename-visited from to)))
   "Move" "Rename" arg))

;; tree dired

;;---------------------------------------------------------------------

(defvar dired-buffers nil
  ;; Enlarged/modified by dired-mode and dired-revert
  ;; Queried by function dired-buffers. When this detects a
  ;; killed buffer, it is removed from this list.
  "Alist of directories and their associated dired buffers.")

;;---------------------------------------------------------------------

;;; utility functions

(defconst dired-subdir-regexp "^. \\([^ ]*\\)\\(:\\)[\n\r]"
  "Regexp matching a maybe hidden subdirectory line in ls -lR output.
Subexpression 1 is subdirectory proper, no trailing slash.
The match starts at the beginning of the line and ends after the end
of the line (\\n or \\r).
Subexpression 2 must end right before the \\n or \\r.")

(defun dired-relative-path-p (file)
  ;;"Return t iff FILE is a relative path name.
  ;;Dired uses dired-make-absolute to convert it to an absolute pathname."
  ;; Only used in dired-normalize-subdir, but might perhaps be
  ;; redefined (for VMS?)
  (not (file-name-absolute-p file)))

(defun dired-make-absolute (file dir)
  ;;"Convert FILE (a pathname relative to DIR) to an absolute pathname."
  ;; This should be good enough for ange-ftp, but might easily be
  ;; redefined (for VMS?).
  ;; It should be reasonably fast, though, as it is called in
  ;; dired-get-filename.
  (concat dir file))

(defun dired-make-relative (file dir)
  ;;"Convert FILE (an absolute pathname) to a pathname relative to DIR.
  ;;Else error."
  ;; DIR must be file-name-as-directory, as with all directory args in
  ;; elisp code. 
  (if (string-match (concat "^" (regexp-quote dir)) file)
      (substring file (match-end 0))
    (error  "%s: not in directory tree growing at %s." file dir)))

(defun dired-in-this-tree (file dir)
  ;;"Is FILE part of the directory tree starting at DIR?"
  (string-match (concat "^" (regexp-quote dir)) file))

(defun dired-normalize-subdir (dir)
  ;; prepend default-directory if relative path name
  ;; and make sure it ends in a slash, like default-directory does
  ;; Make this "end in a slash or a colon" for ange-ftp.  The point is
  ;; that dired-make-absolute (i.e. concat) must suffice in
  ;; dired-get-filename to make a valid filename from a file and its
  ;; directory.
  (file-name-as-directory
   (if (dired-relative-path-p dir)
       (dired-make-absolute dir default-directory)
     dir)))

(defun dired-between-files ()
  ;; Point must be at beginning of line
  ;; Should be equivalent to (save-excursion (not (dired-move-to-filename)))
  ;; but faster.
  (or (looking-at "^$")
      (looking-at "^. *$")		; should not be marked
      (looking-at "^. total")		; but may be
      (looking-at dired-subdir-regexp)))

(defun dired-get-subdir ()
  ;;"Return the subdir name on this line, or nil."
  (save-excursion
    (beginning-of-line)
    (if (looking-at dired-subdir-regexp)
	(file-name-as-directory
	 (buffer-substring (match-beginning 1)
			   (match-end 1))))))

;;; We use an alist of directories for speed.

(defconst dired-subdir-alist nil
  "Association list of subdirectories and their buffer positions:

  \((lastdir . lastmarker) ... (default-directory . firstmarker)).

The markers point right at the end of the line, so that the cursor
looks at either \\n or \\r, the latter for a hidden subdir.") 

(defun dired-clear-alist ()
  (while dired-subdir-alist
    (set-marker (cdr (car dired-subdir-alist)) nil)
    (setq dired-subdir-alist (cdr dired-subdir-alist))))

(defun dired-build-subdir-alist ()
  "Build dired-subdir-alist anew and return it's new value."
  (interactive)
  (dired-clear-alist)
  (save-excursion
    (let ((count 0))
      (goto-char (point-min))
      (setq dired-subdir-alist nil)
      (while (re-search-forward dired-subdir-regexp nil t)
	(setq count (1+ count))
	(message "%d" count)
	(dired-alist-add (buffer-substring (match-beginning 1)
					   (match-end 1))
			 (progn
			   (goto-char (match-end 2))
			   (point-marker))))
      (message "%d director%s." count (if (= 1 count) "y" "ies"))
      ;; return new alist:
      dired-subdir-alist)))

(defun dired-alist-add (dir new-marker)
  ;; Add new DIR at NEW-MARKER (at end of buffer, but beginning of alist!)
  ;; Should perhaps use setcar for speed?
  (setq dired-subdir-alist
	(cons (cons (dired-normalize-subdir dir) new-marker)
	      dired-subdir-alist)))

(defun dired-unsubdir (dir)
  ;; Remove DIR from the alist
  (setq dired-subdir-alist
	(delq (assoc dir dired-subdir-alist) dired-subdir-alist)))

(defun dired-goto-next-nontrivial-file ()
  ;; Position point on first nontrivial file after point.
  (dired-goto-next-file);; so there is a file to compare with
  (if (stringp dired-trivial-filenames)
      (while (and (not (eobp))
		  (string-match dired-trivial-filenames
				(file-name-nondirectory
				 (or (dired-get-filename nil t) ""))))
	(forward-line 1)
	(dired-move-to-filename))))

(defun dired-goto-next-file ()
  (while (and (not (dired-move-to-filename)) (not (eobp)))
    (forward-line 1)))

(defun dired-goto-subdir (dir)
  "Goto header line of DIR in this dired buffer."
  ;; Search for DIR (an absolute pathname) in alist and move to it.
  ;; Return buffer position on success, otherwise return nil.
  (interactive (list (expand-file-name
		      ;;(read-file-name "Goto directory: ")
		      (completing-read "Goto directory: " ; prompt
				       dired-subdir-alist ; table
				       nil ; predicate
				       t ; require-match
				       (dired-current-directory)))))
  (let ((elt (assoc (file-name-as-directory dir) dired-subdir-alist)))
    (and elt (goto-char (cdr elt)))))

(defun dired-goto-file (file)
  "Goto file line of FILE in this dired buffer."
  (interactive (list (expand-file-name
		      (read-file-name "Goto file: "
				      (dired-current-directory)))))
  (setq file (directory-file-name file)) ; does no harm if no directory
  (let (found)
    (save-excursion
      (if (dired-goto-subdir (file-name-directory file))
	  (let ((keep-going t)
		(match nil)
		(string (file-name-nondirectory file))
		(boundary (dired-subdir-max)))
	    (while keep-going
	      (setq keep-going
		    (and (< (point) boundary)
			 (setq match (search-forward string nil 'move))))
	      (if (and match (equal file (dired-get-filename nil t)))
		  (setq found (point) keep-going nil)))
	    )))
    (and found (goto-char found))))

(defun dired-initial-position ()
  ;; Where point should go in new listings.
  ;; Point assumed at beginning of new subdir line.
  (end-of-line)
  (if dired-trivial-filenames (dired-goto-next-nontrivial-file)))

;;; moving by subdirectories

(defun dired-subdir-index (dir)
  ;; Return an index into alist for use with nth
  ;; for the sake of subdir moving commands.
  (let (found (index 0) (alist dired-subdir-alist))
    (while alist
      (if (string= dir (car (car alist)))
	  (setq alist nil found t)
	(setq alist (cdr alist) index (1+ index))))
    ;; (message "%s %s" dir (nth index dired-subdir-alist))
    (if found index nil)))

(defun dired-next-subdir (arg &optional no-error-if-not-found)
  "Go to next subdirectory, regardless of level.
Use 0 prefix argument to go to this directory's header line."
  (interactive "p")
  (let ((this-dir (dired-current-directory))
	pos index)
    ;; nth with negative arg does not return nil but the first element
    (setq index (- (dired-subdir-index this-dir) arg))
    (setq pos (if (>= index 0) (cdr (nth index dired-subdir-alist)) nil))
    (if pos
	(goto-char pos)			; exit with non-nil return value
      (if no-error-if-not-found
	  nil				; return nil if not found
	(error "No more directories.")))))

(defun dired-prev-subdir (arg &optional no-error-if-not-found)
  "Go to previous subdirectory, regardless of level.
When called interactively and not on a subdir line, go to subdir line."
  (interactive
   (list (if current-prefix-arg
	     (prefix-numeric-value current-prefix-arg)
	   (if (and (dired-get-subdir) (not (bolp))) 1 0))))
  (dired-next-subdir (- arg) no-error-if-not-found))

(defun dired-up-subdir (arg)
  "Go up ARG levels in the dired tree."
  (interactive "p")
  (let ((dir (concat (dired-current-directory) "..")))
    (while (> arg 1) (setq arg (1- arg) dir (concat dir "/..")))
    (setq dir (expand-file-name dir))
    (or (dired-goto-subdir dir)
	(error "Cannot go up to %s - not in this tree." dir))))

(defun dired-down-subdir (arg)
  "Go down ARG levels in the dired tree."
  (interactive "p")
  (let ((dir (dired-current-directory)) ; has slash
	(tail "[^/]+"))			; at least one more path name component
    (while (> arg 1) (setq arg (1- arg) tail (concat tail "/[^/]+")))
    (if (re-search-forward		; can't use $ searches when
	 (concat "^. " dir tail ":[\n\r]") nil t) ; dir is hidden
	(backward-char 1)
      (error "At the bottom."))))

;;; hiding

(defun dired-subdir-hidden-p (dir)
  (save-excursion
    (and selective-display
	 (dired-goto-subdir dir)
	 (looking-at "\r"))))

(defun dired-unhide-subdir ()
  (let ((buffer-read-only nil))
    (subst-char-in-region (dired-subdir-min) (dired-subdir-max) ?\r ?\n)))

(defun dired-hide-check ()
  (or selective-display
      (error "selective-display must be t for subdir hiding to work!")))

(defun dired-hide-subdir (arg)
  "Hide or unhide the current subdirectory and move to next directory.
Optional prefix arg is a repeat factor.
Use \\[dired-hide-all] to (un)hide all directories."
  (interactive "p")
  (dired-hide-check)
  (let (from-char to-char end-pos (buffer-read-only nil))
    (dired-next-subdir 0)	; to end of subdir line
    (while (> arg 0)
      (setq arg (1- arg))
      (if (looking-at "\n")
	  (setq from-char ?\n to-char ?\r) ; hide
	(setq to-char ?\n from-char ?\r)) ; unhide
      (subst-char-in-region
       (point)
       (save-excursion
	 (or (setq end-pos (dired-next-subdir 1 t))
	     (goto-char (point-max)))
	 ;;(forward-line -1) does work only with \n, not \r
	 ;; search backward for \n or \r:
	 (skip-chars-backward (concat "^" (char-to-string from-char)))
	 ;; this is necessary, else blank lines will be deleted:
	 (if (= from-char ?\n) (backward-char 1))
	 (point))
       from-char to-char)
      (if end-pos (goto-char end-pos)))))

(defun dired-hide-all (arg)
  "Hide all subdirectories, leaving only their header lines.
If there is already something hidden, make everything visible again.
Use \\[dired-hide-subdir] to (un)hide a particular subdirectory."
  (interactive "P")
  (dired-hide-check)
  (let ((buffer-read-only nil))
    (if (save-excursion
	  (goto-char (point-min))
	  (search-forward "\r" nil t))
	;; unhide - bombs on \r in filenames
	(subst-char-in-region (point-min) (point-max) ?\r ?\n)
      ;; hide
      (let ((pos (point-max))		; pos of end of last directory
	    (alist dired-subdir-alist))
	(while alist			; while there are dirs before pos
	  (subst-char-in-region (cdr (car alist)) ; pos of prev dir
				(save-excursion
				  (goto-char pos) ; current dir
				  (forward-line -1)
				  (point))
				?\n ?\r)
	  (setq pos (cdr (car alist)))	; prev dir gets current dir
	  (setq alist (cdr alist)))))))

(defun dired-undo ()
  "Undo in a dired buffer.
This doesn't recover lost files, it is just normal undo with temporarily
writeable buffer.  You can use it to recover killed lines or subdirs.
You might have to do \\[dired-build-subdir-alist] to parse the buffer again."
  (interactive)
  (let ((buffer-read-only nil))
    (undo)))

(defun dired-advertise ()
  "Advertise in  dired-buffers  what directory we dired."
  (if (memq (current-buffer) (dired-buffers default-directory))
      t					; we have already advertised ourselves
    (setq dired-buffers
	  (cons (cons default-directory (current-buffer))
		dired-buffers))))

; unused:
;(defun dired-unadvertise (dir)
;  ;; Remove DIR from the buffer alist in variable dired-buffers.
;  (setq dired-buffers
;      (delq (assoc dir dired-buffers) dired-buffers)))

;; This function is the heart of tree dired
(defun dired-current-directory (&optional relative)
  "Get the subdirectory to which this line belongs.
This returns a string with trailing slash, like default-directory.
Optional argument means return a name relative to default-directory."
  (let (elt
	dir
	(here (point))
	;; Under strange circumstances, when dired-revert calls
	;; dired-get-filename and thus this function, the alist is not
	;; defined.  I don't understand how this can happen.
	(alist (or dired-subdir-alist (dired-build-subdir-alist))))
    (while alist
      (setq elt (car alist)
	    dir (car elt))
      (if (<= (cdr elt) here)		; subdir line is part of subdir
	  ;; found - exit while
	  (setq alist nil)
	;; else have to loop once more
	(setq alist (cdr alist))))
    (if relative
	(dired-make-relative dir default-directory)
      dir)))

(defun dired-subdir-min ()
  (save-excursion
    (if (not (dired-prev-subdir 0 t))
	(error "Not in a subdir!")
      (beginning-of-line)
      (point))))

(defun dired-subdir-max ()
  (save-excursion
    (if (not (dired-next-subdir 1 t))
	(point-max)
      (beginning-of-line)
      (point))))

(defun dired-kill-subdir (&optional no-build)
  "Remove all lines of current subdirectory.
Lower levels are unaffected."
  (interactive)
  (let ((buffer-read-only nil))
    ;;(end-of-line);;  necessary if on a subdir line
    (if (and (interactive-p)
	     (equal (dired-current-directory) default-directory))
	(error "Cannot kill top level directory."))
    (delete-region (dired-subdir-min) (dired-subdir-max))
    ;; leave one blank line when between directories:
    (skip-chars-backward " \n")
    (or (eobp) (forward-char 1))
    (while (and (not (eobp))
		(looking-at "[ \n]"))
      (delete-char 1))
    ;;(insert "\n")
    (or (eobp) (insert "\n  "))
    (or no-build (dired-unsubdir (dired-current-directory)))))

(defun dired-mark-files-in-region (start end &optional arg)
  (let ((buffer-read-only nil))
    (if (> start end)
	(error "start > end"))
    (goto-char start)			; assumed at beginning of line
    (while (< (point) end)
      ;; Skip subdir line and following garbage like the `total' line:
      (while (and (< (point) end) (dired-between-files))
	(forward-line 1))
      (if (and (or arg (not (looking-at dired-re-dir)))
	       (dired-get-filename nil t))
	  (progn
	    (delete-char 1)
	    (insert dired-marker-char)))
      (forward-line 1))))

(defun dired-mark-subdir-files (&optional arg)
  "Mark all files except directories in this subdir.
With prefix arg, mark even directories."
  (interactive "P")
  (let ((buffer-read-only nil)
	(p-min (dired-subdir-min)))
    (dired-mark-files-in-region p-min (dired-subdir-max) arg)
    ;; This only makes sense if marking also works when subdir is hidden.
    ;; But should it work on hidden files?
;    (save-excursion
;      (goto-char p-min)
;      (delete-char 1)
;      (insert dired-marker-char))
    ))

(defun dired-mark-subdir-or-file (arg)
  "If looking at a subdir, mark all its files, else like dired-mark-file."
  (interactive "P")
  (if (dired-get-subdir)
      (save-excursion
	(end-of-line)
	(dired-mark-subdir-files arg))
    (dired-mark-file (prefix-numeric-value arg))))

(defun dired-insert-subdir (dirname &optional switches)
  "Insert this subdirectory into the same dired buffer.
If subdirectory is already present, overwrites previous entry, else
appends at end of buffer.
With a prefix arg, you may edit the ls switches used for this listing."
  ;; This function takes some pains to conform to ls -lR output.
  (interactive
   (list (dired-get-filename)
	 (if current-prefix-arg
	     (read-string "Switches for listing: " dired-actual-switches))))
  (setq dirname (file-name-as-directory (expand-file-name dirname)))
  (dired-make-relative dirname default-directory) ; error on failure
  (let (beg end index old-marker new-marker mark-alist (buffer-read-only nil)) 
    (or (file-directory-p dirname) (error  "Not a directory: %s" dirname))
    (if (setq index (dired-subdir-index dirname))
	(progn
	  (setq old-marker (cdr (nth index dired-subdir-alist)))
	  (goto-char old-marker)
	  (forward-line -1)
	  (setq beg (point))
	  (goto-char old-marker)
	  (setq end (dired-subdir-max))
	  (save-restriction
	    (narrow-to-region old-marker end)
	    ;; Must unhide to make remembering work:
	    (subst-char-in-region (point-min) (point-max) ?\r ?\n)
	    (setq mark-alist (dired-remember-marks)))
	  (delete-region beg end)
	  ;; must make an empty line to
	  ;; separate it from next subdir (if any)
	  (if (not (eobp))
	      (save-excursion (insert "\n"))))
      (goto-char (point-max)))
    (or (bobp) (insert "\n"))
    (setq beg (point))
    (message "Reading directory %s..." dirname)
    (dired-ls dirname
	      (or switches
		  (dired-replace-in-string "R" "" dired-actual-switches))
	      nil t)
    (message "Reading directory %s...done" dirname)
    (indent-rigidly beg (point) 2)
    (if dired-readin-hook
	(save-restriction
	  (narrow-to-region beg (point))
	  (run-hooks 'dired-readin-hook)))
    ;;  call dired-insert-headerline afterwards, as under VMS dired-ls
    ;;  does insert the headerline itself and the insert function just
    ;;  moves point.
    (goto-char beg)
    (dired-insert-headerline dirname)	; must put point where
    (setq new-marker (point-marker))	; dired-build-subdir-alist
					; would
    (if index (set-marker old-marker new-marker))

    (if index				; if already present,
	(set-marker new-marker nil)	; new-marker is unused
      (dired-alist-add dirname new-marker))
    (if (and switches (string-match "R" switches))
	(dired-build-subdir-alist))
    (dired-initial-position)
    (save-excursion
      (goto-char beg)
      (dired-mark-remembered mark-alist))))

;; sorting

(defvar dired-sort-by-date-regexp "^-altR?$"
  "Regexp recognized by dired-sort-mode to set by date mode.")

(defvar dired-sort-by-name-regexp "^-alR?$"
  "Regexp recognized by dired-sort-mode to set by name mode.")

(defun dired-sort-mode ()
  "Set dired-sort-mode according to dired-actual-switches."
  (cond ((string-match dired-sort-by-date-regexp dired-actual-switches)
	 (dired-sort-by-date))
	((string-match dired-sort-by-name-regexp dired-actual-switches)
	 (dired-sort-by-name))
	(t (dired-sort-other dired-actual-switches t))))

(defun dired-sort-toggle ()
  "Toggle between sort by date/name."
  (interactive)
  (if (string-match dired-sort-by-date-regexp dired-actual-switches)
      (dired-sort-by-name)
    (dired-sort-by-date))
  (revert-buffer))

;; We can't preserve arbitrary ls switches because they may override
;; the presence or absence of the `t' option.
;; And we have to make sure to set dired-actual-switches to a legal
;; value.
;; And when displaying `by name' or `by date' in the modeline, this
;; should correspond to a definite listing format.

(defun dired-sort-by-date ()
  ;; Force sort by date, but preserve `R' and `a' ls switches.
  (setq dired-actual-switches
	(concat "-" (if (string-match "a" dired-actual-switches) "a" "")
		"lt" (if (string-match "R" dired-actual-switches) "R" "")))
  (setq dired-sort-mode " by date")
  (set-buffer-modified-p (buffer-modified-p))) ;No-op, but updates mode line.

(defun dired-sort-by-name ()
  ;; Force sort by name, but preserve `R' and `a' ls switches.
  (setq dired-actual-switches
	(concat "-" (if (string-match "a" dired-actual-switches) "a" "")
		"l" (if (string-match "R" dired-actual-switches) "R" "")))
  (setq dired-sort-mode " by name")
  (set-buffer-modified-p (buffer-modified-p))) ;No-op, but updates mode line.

(defun dired-sort-other (switches &optional no-revert)
  "Specify dired-actual-switches for dired-mode.
Values matching dired-sort-by-date-regexp or dired-sort-by-name-regexp
set the minor mode accordingly, others appear literally in the mode line.
With prefix arg, don't revert the buffer afterwards."
  (interactive
   (list (read-string "ls switches (must contain -l): "
		      dired-actual-switches)
	 current-prefix-arg))
  (setq dired-actual-switches switches)
  (setq dired-sort-mode (concat " " dired-actual-switches))
  ;; might really be by name or by date
  (if (string-match dired-sort-by-date-regexp dired-actual-switches)
      (dired-sort-by-date)
    (if (string-match dired-sort-by-name-regexp dired-actual-switches)
	(dired-sort-by-name)))
  (set-buffer-modified-p (buffer-modified-p)) ;No-op, but updates mode line.
  (or no-revert (revert-buffer)))

(if (eq system-type 'vax-vms)
    (load "dired-vms"))

;;; patched by Manabu Higashida for demacs-1.1 91/10/28
(if (eq system-type 'ms-dos)
    (load "direddos"))

(run-hooks 'dired-load-hook)		; for your customizations

;;; debugging:

(defun dired-log (fmt &rest args)
  (save-excursion
    (set-buffer (get-buffer-create "*Dired Log*"))
    (goto-char (point-max))
    (insert "\n" (apply 'format fmt args))))
