;; -*-Emacs-Lisp-*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; File:         efs-vms.el
;; Release:      $efs release: 1.20 $
;; Version:      #Revision: 1.13 $
;; RCS:          
;; Description:  VMS support for efs
;; Authors:      Andy Norman, Joe Wells, Sandy Rutherford <sandy@itp.ethz.ch>
;; Modified:     Sun Nov 27 18:44:59 1994 by sandy on gandalf
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; This file is part of efs. See efs.el for copyright
;;; (it's copylefted) and warranty (there isn't one) information.

(provide 'efs-vms)
(require 'efs)

(defconst efs-vms-version
  (concat (substring "$efs release: 1.20 $" 14 -2)
	  "/"
	  (substring "#Revision: 1.13 $" 11 -2)))

;;;; ------------------------------------------------------------
;;;; VMS support.
;;;; ------------------------------------------------------------

;;; efs has full support for VMS hosts, including tree dired support.  It
;;; should be able to automatically recognize any VMS machine. However, if it
;;; fails to do this, you can use the command efs-add-vms-host.  As well,
;;; you can set the variable efs-vms-host-regexp in your .emacs file. We
;;; would be grateful if you would report any failures to automatically
;;; recognize a VMS host as a bug.
;;;
;;; Filename Syntax:
;;;
;;; For ease of *implementation*, the user enters the VMS filename syntax in a
;;; UNIX-y way.  For example:
;;;  PUB$:[ANONYMOUS.SDSCPUB.NEXT]README.TXT;1
;;; would be entered as:
;;;  /PUB$$:/ANONYMOUS/SDSCPUB/NEXT/README.TXT;1
;;; i.e. to log in as anonymous on ymir.claremont.edu and grab the file:
;;;  [.CSV.POLICY]RULES.MEM
;;; you would type:
;;;  C-x C-f /anonymous@ymir.claremont.edu:CSV/POLICY/RULES.MEM
;;;
;;; A legal VMS filename is of the form: FILE.TYPE;##
;;; where FILE can be up to 39 characters
;;;       TYPE can be up to 39 characters
;;;       ## is a version number (an integer between 1 and 32,767)
;;; Valid characters in FILE and TYPE are A-Z 0-9 _ - $
;;; $ cannot begin a filename, and - cannot be used as the first or last
;;; character.
;;;
;;; Tips:
;;; 1. To access the latest version of file under VMS, you use the filename
;;;    without the ";" and version number. You should always edit the latest
;;;    version of a file. If you want to edit an earlier version, copy it to a
;;;    new file first. This has nothing to do with efs, but is simply
;;;    good VMS operating practice. Therefore, to edit FILE.TXT;3 (say 3 is
;;;    latest version), do C-x C-f /ymir.claremont.edu:FILE.TXT. If you
;;;    inadvertently do C-x C-f /ymir.claremont.edu:FILE.TXT;3, you will find
;;;    that VMS will not allow you to save the file because it will refuse to
;;;    overwrite FILE.TXT;3, but instead will want to create FILE.TXT;4, and
;;;    attach the buffer to this file. To get out of this situation, M-x
;;;    write-file /ymir.claremont.edu:FILE.TXT will attach the buffer to
;;;    latest version of the file. For this reason, in tree dired "f"
;;;    (dired-find-file), always loads the file sans version, whereas "v",
;;;    (dired-view-file), always loads the explicit version number. The
;;;    reasoning being that it reasonable to view old versions of a file, but
;;;    not to edit them.
;;; 2. EMACS has a feature in which it does environment variable substitution
;;;    in filenames. Therefore, to enter a $ in a filename, you must quote it
;;;    by typing $$. There is a bug in EMACS, in that it neglects to quote the
;;;    $'s in the default directory when it writes it in the minibuffer.  You
;;;    must edit the minibuffer to quote the $'s manually. Hopefully, this bug
;;;    will be fixed in EMACS 19. If you use Sebastian Kremer's gmhist (V 4.26
;;;    or newer), you will not have this problem.


;; Because some VMS ftp servers convert filenames to lower case
;; we allow a-z in the filename regexp.

(defconst efs-vms-filename-regexp
  "\\([_A-Za-z0-9$][-_A-Za-z0-9$]*\\)?\\.\\([-_A-Za-z0-9$]*\\);[0-9]+")
;; Regular expression to match for a valid VMS file name in Dired buffer.

(defvar efs-vms-month-alist
  '(("JAN" . 1) ("FEB". 2) ("MAR" . 3) ("APR" . 4) ("MAY" . 5) ("JUN" . 6)
    ("JUL" . 7) ("AUG" . 8) ("SEP" . 9) ("OCT" . 10)
    ("NOV" . 11) ("DEC" . 12)))

(defvar efs-vms-date-regexp
  (concat
   "\\([0-3]?[0-9]\\)-"
   "\\(JAN\\|FEB\\|MAR\\|APR\\|MAY\\|JUN\\|"
   "JUL\\|AUG\\|SEP\\|OCT\\|NOV\\|DEC\\)-"
   "\\([0-9][0-9][0-9]?[0-9]?\\) \\(\\([0-5][0-9]\\):\\([0-5][0-9]\\)"
   "\\(:[0-5][0-9]\\)?\\)? "))


;;; The following two functions are entry points to this file.
;;; They are defined as efs-autoloads in efs.el

(efs-defun efs-fix-path vms (path &optional reverse)
  ;; Convert PATH from UNIX-ish to VMS.
  ;; If REVERSE given then convert from VMS to UNIX-ish.
  (efs-save-match-data
    (if reverse
	(if (string-match
	     "^\\([^:]+:\\)?\\(\\[[^]]+\\]\\)?\\([^][]*\\)$" path)
	    (let (drive dir file)
	      (if (match-beginning 1)
		  (setq drive (substring path
					 (match-beginning 1)
					 (match-end 1))))
	      (if (match-beginning 2)
		  (setq dir
			(substring path (match-beginning 2) (match-end 2))))
	      (if (match-beginning 3)
		  (setq file
			(substring path (match-beginning 3) (match-end 3))))
	      (and dir
		   (setq dir (apply (function concat)
				    (mapcar (function
					     (lambda (char)
					       (if (= char ?.)
						   (vector ?/)
						 (vector char))))
					    (substring dir 1 -1)))))
	      (concat (and drive
			   (concat "/" drive "/"))
		      dir (and dir "/")
		      file))
	  (error "path %s didn't match" path))
      (let (drive dir file)
	(if (string-match "^/[^:/]+:/" path)
	    (setq drive (substring path 1 (1- (match-end 0)))
		  path (substring path (1- (match-end 0)))))
	(setq dir (file-name-directory path)
	      file (efs-internal-file-name-nondirectory path))
	(if dir
	    (let ((len (1- (length dir)))
		  (n 0))
	      (if (<= len 0)
		  (setq dir nil)
		(while (<= n len)
		  (and (char-equal (aref dir n) ?/)
		       (cond
			((zerop n) (aset dir n ?\[))
			((= n len) (aset dir n ?\]))
			(t (aset dir n ?.))))
		  (setq n (1+ n))))))
	(concat drive dir file)))))

;; It is important that this function barf for directories for which we know
;; that we cannot possibly get a directory listing, such as "/" and "/DEV:/".
;; This is because it saves an unnecessary FTP error, or possibly the listing
;; might succeed, but give erroneous info. This last case is particularly
;; likely for OS's (like MTS) for which we need to use a wildcard in order
;; to list a directory.

(efs-defun efs-fix-dir-path vms (dir-path)
  ;; Convert path from UNIX-ish to VMS ready for a DIRectory listing.
  ;; Should there be entries for .. -> [-] and . -> [] below. Don't
  ;; think so, because expand-filename should have already short-circuited
  ;; them.
  (cond ((string-equal dir-path "/")
	 (error "Cannot get listing for fictitious \"/\" directory."))
	((string-match "^/[-A-Z0-9_$]+:/$" dir-path)
	 (error "Cannot get listing for device."))
	((efs-fix-path 'vms dir-path))))
  
;; These parsing functions are as general as possible because the syntax
;; of ftp listings from VMS hosts is a bit erratic. What saves us is that
;; the VMS filename syntax is so rigid. If they bomb on a listing in the
;; standard VMS Multinet format, then this is a bug. If they bomb on a listing
;; from vms.weird.net, then too bad.

(defmacro efs-parse-vms-filename ()
  "Extract the next filename from a VMS dired-like listing."
  (` (if (re-search-forward
	  efs-vms-filename-regexp
	  nil t)
	 (buffer-substring (match-beginning 0) (match-end 0)))))

(defun efs-parse-vms-listing ()
  ;; Parse the current buffer which is assumed to be a VMS DIR
  ;; listing (either a short (NLIST) or long listing).
  ;; Assumes that point is at the beginning of the buffer.
  (let ((tbl (efs-make-hashtable))
	file)
    (goto-char (point-min))
    (efs-save-match-data
      (while (setq file (efs-parse-vms-filename))
	(if (string-match "\\.\\(DIR\\|dir\\);[0-9]+" file)
	    ;; deal with directories
	    (efs-put-hash-entry
	     (substring file 0 (match-beginning 0)) '(t) tbl)
	  (efs-put-hash-entry file '(nil) tbl)
	  (if (string-match ";[0-9]+$" file) ; deal with extension
	      ;; sans extension
	      (efs-put-hash-entry
	       (substring file 0 (match-beginning 0)) '(nil) tbl)))
	(forward-line 1))
      ;; Would like to look for a "Total" line, or a "Directory" line to
      ;; make sure that the listing isn't complete garbage before putting
      ;; in "." and "..", but we can't even count on all VAX's giving us
      ;; either of these.
      (efs-put-hash-entry "." '(t) tbl)
      (efs-put-hash-entry ".." '(t) tbl))
    tbl))

(efs-defun efs-parse-listing vms
  (host user dir path &optional switches)
  ;; Parse the current buffer which is assumed to be a VMS FTP dir
  ;; format, and return a hashtable as the result. SWITCHES are never used,
  ;; but they must be specified in the argument list for compatibility
  ;; with the unix version of this function.
  ;; HOST = remote host name
  ;; USER = user name
  ;; DIR = directory in as a full remote path
  ;; PATH = directory in full efs path syntax
  ;; SWITCHES = ls switches (not relevant here)
  (goto-char (point-min))
  (efs-save-match-data
    ;; check for a DIR/FULL monstrosity
    (if (search-forward "\nSize:" nil t)
	(progn
	  (efs-add-listing-type 'vms:full host user)
	  ;; This will cause the buffer to be refilled with an NLIST
	  (let ((efs-ls-uncache t))
	    (efs-ls path nil (format "Relisting %s"
				     (efs-relativize-filename path))
		    t))
	  (goto-char (point-min))
	  (efs-parse-vms-listing))
      (efs-parse-vms-listing))))


;;;; Sorting of listings

(efs-defun efs-t-converter vms (&optional regexp reverse)
  (if regexp
      nil
    (goto-char (point-min))
    (efs-save-match-data
      (if (re-search-forward efs-vms-filename-regexp nil t)
	  (let (list-start start end list)
	    (beginning-of-line)
	    (setq list-start (point))
	    (while (and (looking-at efs-vms-filename-regexp)
			(progn
			  (setq start (point))
			  (goto-char (match-end 0))
			  (forward-line (if (eolp) 2 1))
			  (setq end (point))
			  (goto-char (match-end 0))
			  (re-search-forward efs-vms-date-regexp nil t)))
	      (setq list
		    (cons
		     (cons
		      (nconc
		       (list (string-to-int (buffer-substring
					     (match-beginning 3)
					     (match-end 3))) ; year
			     (cdr (assoc
				   (buffer-substring (match-beginning 2)
						     (match-end 2))
				   efs-vms-month-alist)) ; month
			     (string-to-int (buffer-substring
					     (match-beginning 1)
					     (match-end 1)))) ;day
		       (if (match-beginning 4)
			   (list
			    (string-to-int (buffer-substring
					    (match-beginning 5)
					    (match-end 5))) ; hour
			    (string-to-int (buffer-substring
					    (match-beginning 6)
					    (match-end 6))) ; minute
			    (if (match-beginning 7)
				(string-to-int (buffer-substring
						(1+ (match-beginning 7))
						(match-end 7))) ; seconds
			      0))
			 (list 0 0 0)))
		      (buffer-substring start end))
		     list))
	      (goto-char end))
	    (if list
		(progn
		  (setq list
			(mapcar 'cdr
				(sort list 'efs-vms-t-converter-sort-pred)))
		  (if reverse (setq list (nreverse list)))
		  (delete-region list-start (point))
		  (apply 'insert list)))
	    t)))))

(defun efs-vms-t-converter-sort-pred (elt1 elt2)
  (let* ((data1 (car elt1))
	 (data2 (car elt2))
	 (year1 (car data1))
	 (year2 (car data2))
	 (month1 (nth 1 data1))
	 (month2 (nth 1 data2))
	 (day1 (nth 2 data1))
	 (day2 (nth 2 data2))
	 (hour1 (nth 3 data1))
	 (hour2 (nth 3 data2))
	 (minute1 (nth 4 data1))
	 (minute2 (nth 4 data2)))
    (or (> year1 year2)
	(and (= year1 year2)
	     (or (> month1 month2)
		 (and (= month1 month2)
		      (or (> day1 day2)
			  (and (= day1 day2)
			       (or (> hour1 hour2)
				   (and (= hour1 hour2)
					(or (> minute1 minute2)
					    (and (= minute1 minute2)
						 (or (> (nth 5 data1)
							(nth 5 data2)))
						 ))))))))))))


(efs-defun efs-X-converter vms (&optional regexp reverse)
  ;; Sorts by extension
  (if regexp
      nil
    (goto-char (point-min))
    (efs-save-match-data
      (if (re-search-forward efs-vms-filename-regexp nil t)
	  (let (list-start start list)
	    (beginning-of-line)
	    (setq list-start (point))
	    (while (looking-at efs-vms-filename-regexp)
	      (setq start (point))
	      (goto-char (match-end 0))
	      (forward-line (if (eolp) 2 1))
	      (setq list
		    (cons
		     (cons (buffer-substring (match-beginning 2)
					     (match-end 2))
			   (buffer-substring start (point)))
		     list)))
	    (setq list
		  (mapcar 'cdr
			  (sort list
				(if reverse
				    (function
				     (lambda (x y)
					(string< (car y) (car x))))
				  (function
				   (lambda (x y)
				     (string< (car x) (car y))))))))
	    (delete-region list-start (point))
	    (apply 'insert list)
	    t)))))

;; This version only deletes file entries which have
;; explicit version numbers, because that is all VMS allows.

(efs-defun efs-delete-file-entry vms (path &optional dir-p)
  (let ((ignore-case (memq 'vms efs-case-insensitive-host-types)))
    (if dir-p
	(let ((path (file-name-as-directory path))
	      files)
	  (efs-del-hash-entry path efs-files-hashtable ignore-case)
	  (setq path (directory-file-name path)
		files (efs-get-hash-entry (file-name-directory path)
					       efs-files-hashtable
					       ignore-case))
	  (if files
	      (efs-del-hash-entry (efs-get-file-part path)
				       files ignore-case)))
      (efs-save-match-data
	(let ((file (efs-get-file-part path)))
	  (if (string-match ";[0-9]+$" file)
	      ;; In VMS you can't delete a file without an explicit	
	      ;; version number, or wild-card (e.g. FOO;*)
	      ;; For now, we give up on wildcards.
	      (let ((files (efs-get-hash-entry
			    (file-name-directory path)
			    efs-files-hashtable ignore-case)))
		(if files
		    (let ((root (substring file 0
					   (match-beginning 0)))
			  (completion-ignore-case ignore-case)
			  (len (match-beginning 0)))
		      (efs-del-hash-entry file files ignore-case)
		      ;; Now we need to check if there are any
		      ;; versions left. If not, then delete the
		      ;; root entry.
		      (or (all-completions
			   root files
			   (function
			    (lambda (sym)
			      (string-match ";[0-9]+$"
					    (symbol-name sym) len))))
			  (efs-del-hash-entry root files
						   ignore-case)))))))))
    (efs-del-from-ls-cache path t ignore-case)))

(efs-defun efs-add-file-entry vms (path dir-p size owner
						  &optional modes nlinks mdtm)
  ;; The vms version of this function needs to keep track
  ;; of vms's file versions.
  (let ((ignore-case (memq 'vms efs-case-insensitive-host-types))
	(ent (let ((dir-p (null (null dir-p))))
	       (if mdtm
		   (list dir-p size owner nil nil mdtm)
		 (list dir-p size owner)))))
    (if dir-p
	(let* ((path (directory-file-name path))
	       (files (efs-get-hash-entry  (file-name-directory path)
						efs-files-hashtable
						ignore-case)))
	  (if files
	      (efs-put-hash-entry (efs-get-file-part path)
				       ent files ignore-case)))
      (let ((files (efs-get-hash-entry
		    (file-name-directory path)
		    efs-files-hashtable ignore-case)))
	(if files
	    (let ((file (efs-get-file-part path)))
	      (efs-save-match-data
		;; In VMS files must have an extension. If there isn't
		;; one, it will be added.
		(or (string-match "^[^;]*\\." file)
		    (if (string-match ";" file)
			(setq file (concat
				    (substring file 0 (match-beginning 0))
				    ".;"
				    (substring file (match-end 0))))
		      (setq file (concat file "."))))
		(if (string-match ";[0-9]+$" file)
		    (efs-put-hash-entry
		     (substring file 0 (match-beginning 0))
		     ent files ignore-case)
		  ;; Need to figure out what version of the file
		  ;; is being added.
		  (let* ((completion-ignore-case ignore-case)
			 (len (length file))
			 (versions (all-completions
				    file files
				    (function
				     (lambda (sym)
				       (string-match ";[0-9]+$"
						     (symbol-name sym) len)))))
			 (N (1+ len))
			 (max (apply
			       'max
			       (cons 0 (mapcar
					(function
					 (lambda (x)
					   (string-to-int (substring x N))))
					versions)))))
		    ;; No need to worry about case here.
		    (efs-put-hash-entry
		     (concat file ";" (int-to-string (1+ max))) ent files))))
	      (efs-put-hash-entry file ent files ignore-case)))))
    (efs-del-from-ls-cache path t ignore-case)))

(efs-defun efs-really-file-p vms (file ent)
  ;; Returns whether the hash entry FILE with entry ENT is a real file.
  (or (car ent) ; file-directory-p
      (efs-save-match-data
	(string-match ";" file))))

(efs-defun efs-internal-file-name-as-directory vms (name)
  (efs-save-match-data
    (if (string-match "\\.\\(DIR\\|dir\\)\\(;[0-9]+\\)?$" name)
	(setq name (substring name 0 (match-beginning 0))))
    (let (file-name-handler-alist)
      (file-name-as-directory name))))

(efs-defun efs-remote-directory-file-name vms (dir)
  ;; Returns the VMS filename in unix directory syntax for directory DIR.
  ;; This is something like /FM/SANDY/FOOBAR.DIR;1
  (efs-save-match-data
    (setq dir (directory-file-name dir))
    (concat dir
	    (if (string-match "[a-z]" (nth 2 (efs-ftp-path dir)))
		".dir;1"
	      ".DIR;1"))))

(efs-defun efs-allow-child-lookup vms (host user dir file)
  ;; Returns t if FILE in directory DIR could possibly be a subdir
  ;; according to its file-name syntax, and therefore a child listing should
  ;; be attempted.

  ;; Subdirs in VMS can't have an extension (other than .DIR, which we
  ;; have truncated).
  (not (or (string-match "\\." file)
	   (and (boundp 'dired-local-variables-file)
		(stringp dired-local-variables-file)
		(string-equal dired-local-variables-file file)))))

;;; Tree dired support:

;; For this code I have borrowed liberally from Sebastian Kremer's
;; dired-vms.el


;; These regexps must be anchored to beginning of line.
;; Beware that the ftpd may put the device in front of the filename.

(defconst efs-dired-vms-re-exe
  "^. [^ \t.]+\\.\\(EXE\\|exe\\)[; ]")

(or (assq 'vms efs-dired-re-exe-alist)
    (setq efs-dired-re-exe-alist
	  (cons (cons 'vms  efs-dired-vms-re-exe)
		efs-dired-re-exe-alist)))

(defconst efs-dired-vms-re-dir
  "^. [^ \t.]+\\.\\(DIR\\|dir\\)[; ]")

(or (assq 'vms efs-dired-re-dir-alist)
    (setq efs-dired-re-dir-alist
	  (cons (cons 'vms  efs-dired-vms-re-dir)
		efs-dired-re-dir-alist)))

(efs-defun efs-dired-insert-headerline vms (dir)
  ;; VMS inserts a headerline. I would prefer the headerline
  ;; to be in efs format. This version tries to
  ;; be careful, because we can't count on a headerline
  ;; over ftp, and we wouldn't want to delete anything
  ;; important.
  (save-excursion
    (if (looking-at "^  \\(list \\)?wildcard ")
	(forward-line 1))
    ;; This is really aggressive. Too aggressive?
    (let ((start (point)))
      (skip-chars-forward " \t\n")
      (if (looking-at efs-vms-filename-regexp)
	  (beginning-of-line)
	(forward-line 1)
	(skip-chars-forward " \t\n")
	(beginning-of-line))
      (delete-region start (point)))
    (insert " \n"))
  (efs-real-dired-insert-headerline dir))

(efs-defun efs-dired-fixup-listing vms (file path &optional switches wildcard)
  ;; Some vms machines list the entire path. Scrape this off.
  (setq path (efs-fix-path
	      'vms
	      ;; Need the file-name-directory, in case of widcards.
	      ;; Note that path is a `local' path rel. the remote host.
	      ;; Lose on wildcards in parent dirs. Fix if somebody complains.
	      (let (file-name-handler-alist)
		(file-name-directory path))))
  ;; Some machines put a Node name down too.
  (let ((regexp (concat "^\\([_A-Za-z0-9][-_A-Za-z0-9]*\\$\\)?"
			(regexp-quote path))))
    (goto-char (point-min))
    (while (re-search-forward regexp nil t)
      (delete-region (match-beginning 0) (match-end 0))))
  ;; Now need to deal with continuation lines.
  (goto-char (point-min))
  (let (col start end)
    (while (re-search-forward
	    ";[0-9]+[ \t]*\\(\n[ \t]+\\)[^; \t\n]+[^\n;]*\n" nil t)
      (setq start (match-beginning 1)
	    end (match-end 1))
	;; guess at the column dimensions
      (or col
	  (save-excursion
	    (goto-char (point-min))
	    (if (re-search-forward
		 (concat efs-vms-filename-regexp
			 "[ \t]+[^ \t\n\r]") nil t)
		(setq col (- (goto-char (match-end 0))
			     (progn (beginning-of-line) (point))
			     1))
	      (setq col 0))))
      ;; join cont. lines.
      (delete-region start end)
      (goto-char start)
      (insert-char ?   (max (- col (current-column)) 2))))
  ;; Some vms dir listings put a triple null line before the total line.
  (goto-char (point-min))
  (skip-chars-forward "\n")
  (if (search-forward "\n\n\n" nil t)
      (delete-char -1)))

(efs-defun efs-dired-manual-move-to-filename vms
  (&optional raise-error bol eol)
  ;; In dired, move to first char of filename on this line.
  ;; Returns position (point) or nil if no filename on this line.
  ;; This is the VMS version.
  (or eol (setq eol (save-excursion (skip-chars-forward "^\r\n") (point))))
  (let (case-fold-search)
    (if bol
	(goto-char bol)
      (skip-chars-backward "^\n\r"))
    (if (re-search-forward efs-vms-filename-regexp eol t)
	(goto-char (match-beginning 0))
      (and raise-error (error "No file on this line")))))

(efs-defun efs-dired-manual-move-to-end-of-filename vms
  (&optional no-error bol eol)
  ;; Assumes point is at beginning of filename.
  ;; 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.
  ;; This is the VMS version.
  (let ((opoint (point)))
    (and selective-display
	 (null no-error)
	 (eq (char-after
	      (1- (or bol (save-excursion
			    (skip-chars-backward "^\r\n")
			    (point)))))
	     ?\r)
	 ;; File is hidden or omitted.
	 (cond
	  ((dired-subdir-hidden-p (dired-current-directory))
	   (error
	    (substitute-command-keys
	     "File line is hidden. Type \\[dired-hide-subdir] to unhide.")))
	  ((error
	    (substitute-command-keys
	     "File line is omitted. Type \\[dired-omit-toggle] to un-omit."
	     )))))
    (skip-chars-forward "-_A-Za-z0-9$.;")
    (if (or (= opoint (point)) (not (memq (following-char) '(?\  ?\t ?\n ?\r))))
	(if no-error
	    nil
	    (error "No file on this line"))
      (point))))

(efs-defun efs-dired-ls-trim vms ()
  (goto-char (point-min))
  (let ((case-fold-search nil))
    (re-search-forward  efs-vms-filename-regexp))
  (beginning-of-line)
  (delete-region (point-min) (point))
  (forward-line 1)
  (delete-region (point) (point-max)))

(efs-defun efs-internal-file-name-sans-versions vms
  (name &optional keep-backup-version)
  (efs-save-match-data
    (if (string-match ";[0-9]+$" name)
	(substring name 0 (match-beginning 0))
      name)))

(efs-defun efs-dired-collect-file-versions vms ()
  ;; If it looks like file FN has versions, return a list of the versions.
  ;; That is a list of strings which are file names.
  ;; The caller may want to flag some of these files for deletion.
  (let ((completion-ignore-case (memq 'vms efs-case-insensitive-host-types))
	result)
    (dired-map-dired-file-lines
     (function
      (lambda (fn)
	(if (string-match ";[0-9]+$" fn)
	    (let* ((base-fn (substring fn 0 (match-beginning 0)))
		   (base-version (file-name-nondirectory
				  (substring fn 0 (1+ (match-beginning 0)))))
		   (bv-length (length base-version))
		   (possibilities (and
				   (null (assoc base-fn result))
				   (file-name-all-completions
				    base-version
				    (file-name-directory fn)))))
	      (if possibilities
		  (setq result
			(cons (cons base-fn
				    ;; code this explicitly
				    ;; using backup-extract-version has a
				    ;; lot of function-call overhead.
				    (mapcar (function
					     (lambda (fn)
					       (string-to-int
						(substring fn bv-length))))
					    possibilities)) result))))))))
    result))

(efs-defun efs-dired-flag-backup-files vms (&optional unflag-p)
  (interactive "P")
  (let ((dired-kept-versions 1)
	(kept-old-versions 0)
	marker msg)
    (if unflag-p
	(setq marker ?\040 msg "Unflagging old versions")
      (setq marker dired-del-marker msg "Purging old versions"))
    (dired-clean-directory 1 marker msg)))

(efs-defun efs-internal-diff-latest-backup-file vms (fn)
  ;; For FILE;#, returns the filename FILE;N, where N
  ;; is the largest number less than #, for which this file exists.
  ;; Returns nil if none found.
  (efs-save-match-data
    (and (string-match ";[0-9]+$" fn)
	 (let ((base (substring fn 0 (1+ (match-beginning 0))))
	       (num (1- (string-to-int (substring fn
						  (1+ (match-beginning 0))))))
	       found file)
	   (while (and (setq found (> num 0))
		       (not (file-exists-p
			     (setq file
				   (concat base (int-to-string num))))))
	     (setq num (1- num)))
	   (and found file)))))

;;;;--------------------------------------------------------------
;;;; Support for VMS DIR/FULL listings. (listing type vms:full)
;;;;--------------------------------------------------------------

(efs-defun efs-parse-listing vms:full
  (host user dir path &optional switches)
  ;; Parse the current buffer which is assumed to be a VMS FTP dir
  ;; format, and return a hashtable as the result. SWITCHES are never used,
  ;; but they must be specified in the argument list for compatibility
  ;; with the unix version of this function.
  ;; HOST = remote host name
  ;; USER = user name
  ;; DIR = directory in as a full remote path
  ;; PATH = directory in full efs path syntax
  ;; SWITCHES = ls switches (not relevant here)
  (goto-char (point-min))
  (efs-save-match-data
    (efs-parse-vms-listing)))

;;; Tree Dired

(or (assq 'vms:full efs-dired-re-exe-alist)
    (setq efs-dired-re-exe-alist
	  (cons (cons 'vms:full efs-dired-vms-re-exe)
		efs-dired-re-exe-alist)))

(or (assq 'vms:full efs-dired-re-dir-alist)
    (setq efs-dired-re-dir-alist
	  (cons (cons 'vms:full efs-dired-vms-re-dir)
		efs-dired-re-dir-alist)))

(efs-defun efs-dired-insert-headerline vms:full (dir)
  ;; Insert a blank line for aesthetics.
  (insert " \n")
  (forward-char -2)
  (efs-real-dired-insert-headerline dir))

(efs-defun efs-dired-manual-move-to-filename vms:full
  (&optional raise-error bol eol)
  (let ((efs-dired-listing-type 'vms))
    (efs-dired-manual-move-to-filename raise-error bol eol)))

(efs-defun efs-dired-manual-move-to-end-of-filename vms:full
  (&optional no-error bol eol)
  (let ((efs-dired-listing-type 'vms))
    (efs-dired-manual-move-to-end-of-filename no-error bol eol)))

;;; end of efs-vms.el
