From 8c6b1d83116d00e27f22e501e5ac4fea9a7ba182 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 13 Oct 2000 09:02:39 +0000 Subject: [PATCH] Added better remote directory support to Eshell, as well as a few bug fixes. See the ChangeLog. --- lisp/ChangeLog | 84 ++++++++++++++++++++- lisp/eshell/em-glob.el | 2 +- lisp/eshell/em-ls.el | 43 +++++++---- lisp/eshell/em-pred.el | 6 +- lisp/eshell/em-rebind.el | 2 +- lisp/eshell/em-unix.el | 158 +++++++++++++++++++++++---------------- lisp/eshell/esh-arg.el | 2 +- lisp/eshell/esh-mode.el | 5 +- lisp/eshell/esh-util.el | 120 +++++++++++++++++++++++++++-- 9 files changed, 324 insertions(+), 98 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 7be8f0c6da3..7077ad7b588 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,82 @@ +2000-10-13 John Wiegley + + * eshell/esh-util.el: Added a global form which declares an + autoload for `parse-time-string', if that function is not already + defined, and if parse-time.el is available on the user's system. + + * eshell/em-ls.el (eshell-ls-applicable): Extended this function + to be aware of ange-ftp user info. + (eshell-do-ls): Bind `ange-cache'. Also, use + `eshell-file-attributes'. + (eshell-ls-annotate): Use `eshell-file-attributes'. + (eshell-ls-file): Made the user-id printing code a bit smarter. + + * eshell/esh-util.el (eshell-ange-ls-uids): Added variable, to + allow identification of alias user ids in remote directories. + It's manual, but there's no other way to know when the current + user on the local machine, is also the owning user on the remote + machine. + (fboundp): Bind `ange-cache'. + (eshell-directory-files-and-attributes): Re-organized the logic a + bit to use `eshell-file-attributes' instead of `file-attributes'. + The former is more sensitive to directories that are read via FTP, + and knows how to use ange-ftp to determine full attribute + information, instead of just the name and last modtime. + (eshell-current-ange-uids): Return the current user id when in a + remote directory. + (eshell-parse-ange-ls): Parse a full directory listing that has + been returned by ange-ftp. + (eshell-file-attributes): This beefed up version of + `file-attributes' is only special if the user is currently in a + remote directory, in which case it does a lot of work to find out + what the real attributes of a file are, as they appear on the + remote machine. This makes usage of remote directories (i.e., + ange-ftp pathnames) much more useful. You can now use Eshell as a + full-fledged FTP client, with much more manipulation ability than + most other clients. + + * eshell/em-unix.el (eshell-du-prefer-over-ange): Added a new + variable, which means that Eshell's du should always be preferred + in remote directories. + (eshell-shuffle-files): Use `eshell-file-attributes', rather than + just `file-attributes'. + (eshell-mvcp-template): Bind `ange-cache', to improve performance + when reading remote directories. This is an Eshell-specific + variable (not part of ange-ftp). + (eshell/ln): Bind `ange-cache'. + (eshell/du): Added some extra logic for determining when to use + Eshell's du (which is slow), and when to use the external version + (which may or may not exist). + + * eshell/em-rebind.el (eshell-delchar-or-maybe-eof): Call + `eshell-interactive-process', rather than using + `get-buffer-process', since backgrounded processes don't count in + the context of this function's logic. + + * eshell/esh-arg.el (eshell-parse-double-quote): Moved a call to + `forward-char', so that null strings are parsed correctly. + +2000-09-10 John Wiegley + + * eshell/em-pred.el (eshell-pred-file-type, + eshell-pred-file-links, eshell-pred-file-size): Use + `eshell-file-attributes'. This is more correct over ange-ftp. + + * eshell/em-glob.el (eshell-extended-glob): Bind `ange-cache', so + that remote file globbing is more efficient. + + * eshell/em-ls.el (eshell-ls-dir): Use `expand-file-name' when + gathering the files and attributes within a directory. + + * eshell/em-unix.el (eshell/cat): If any of the files passed on + the command line is a special file (not a regular file, directory + or symlink), always attempt to call the external version of cat. + +2000-09-06 John Wiegley + + * eshell/esh-mode.el (eshell-find-tag): Corrections to the + Eshell-friendly version of find-tag. + 2000-10-13 Miles Bader * image-file.el (image-file-name-extensions) @@ -347,6 +426,8 @@ * files.el (set-auto-mode): Ignore unknown -*- mode -*- rather than raise an error. This way it can still default to a sane value. +2000-10-06 Stefan Monnier + * startup.el (fancy-splash-screens): Use local rather than global map. Don't use `update-menu-bindings' any more. Get rid of assumptions about keymap representation. @@ -1362,9 +1443,6 @@ 2000-09-16 Andrew Innes - * makefile.nt (compile-files): No need to make .elc files - read-only, since they aren't under VC now. - * makefile.w32-in (compile-files-CMD): No need to make .elc files read-only, since they aren't under VC now. diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el index 4cb3d1e40ce..ec8c1b2c37a 100644 --- a/lisp/eshell/em-glob.el +++ b/lisp/eshell/em-glob.el @@ -243,7 +243,7 @@ resulting regular expression." (INCLUDE-REGEXP EXCLUDE-REGEXP (PRED-FUNC-LIST) (MOD-FUNC-LIST))" (let ((paths (eshell-split-path glob)) - matches message-shown) + matches message-shown ange-cache) (unwind-protect (if (and (cdr paths) (file-name-absolute-p (car paths))) diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el index cd16f049815..77e5d5577dc 100644 --- a/lisp/eshell/em-ls.el +++ b/lisp/eshell/em-ls.el @@ -192,9 +192,15 @@ really need to stick around for very long." "Test whether, for ATTRS, the user UID can do what corresponds to INDEX. This is really just for efficiency, to avoid having to stat the file yet again." - `(if (= (user-uid) (nth 2 ,attrs)) - (not (eq (aref (nth 8 ,attrs) ,index) ?-)) - (,(eval func) ,file))) + `(if (numberp (nth 2 ,attrs)) + (if (= (user-uid) (nth 2 ,attrs)) + (not (eq (aref (nth 8 ,attrs) ,index) ?-)) + (,(eval func) ,file)) + (not (eq (aref (nth 8 ,attrs) + (+ ,index (if (member (nth 2 ,attrs) + (eshell-current-ange-uids)) + 0 6))) + ?-)))) (defcustom eshell-ls-highlight-alist nil "*This alist correlates test functions to color. @@ -265,7 +271,8 @@ instead." (defvar show-all) (defvar show-recursive) (defvar show-size) - (defvar sort-method)) + (defvar sort-method) + (defvar ange-cache)) (defun eshell-do-ls (&rest args) "Implementation of \"ls\" in Lisp, passing ARGS." @@ -328,7 +335,7 @@ Sort entries alphabetically across.") (setq listing-style 'by-columns)) (unless args (setq args (list "."))) - (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp)) + (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp) ange-cache) (when ignore-pattern (unless (eshell-using-module 'eshell-glob) (error (concat "-I option requires that `eshell-glob'" @@ -347,7 +354,7 @@ Sort entries alphabetically across.") (file-name-absolute-p arg)) (expand-file-name arg) arg) - (file-attributes arg)))) args) + (eshell-file-attributes arg)))) args) t (expand-file-name default-directory))) (funcall flush-func))) @@ -379,7 +386,7 @@ name should be displayed as, etc. Think of it as cooking a FILEINFO." (file-name-directory (expand-file-name (car fileinfo)))))) (setq attr - (file-attributes + (eshell-file-attributes (let ((target (if dir (expand-file-name (cadr fileinfo) dir) (cadr fileinfo)))) @@ -425,16 +432,22 @@ whose cdr is the list of file attributes." "%s%4d %-8s %-8s " (or (nth 8 attrs) "??????????") (or (nth 1 attrs) 0) - (or (and (not numeric-uid-gid) - (nth 2 attrs) - (eshell-substring - (user-login-name (nth 2 attrs)) 8)) + (or (let ((user (nth 2 attrs))) + (and (not numeric-uid-gid) + user + (eshell-substring + (if (numberp user) + (user-login-name user) + user) 8))) (nth 2 attrs) "") - (or (and (not numeric-uid-gid) - (nth 3 attrs) - (eshell-substring - (eshell-group-name (nth 3 attrs)) 8)) + (or (let ((group (nth 3 attrs))) + (and (not numeric-uid-gid) + group + (eshell-substring + (if (numberp group) + (eshell-group-name group) + group) 8))) (nth 3 attrs) "")) (let* ((str (eshell-ls-printable-size (nth 7 attrs))) diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el index c3e3de5adbd..9708e2d793a 100644 --- a/lisp/eshell/em-pred.el +++ b/lisp/eshell/em-pred.el @@ -464,7 +464,7 @@ that 'ls -l' will show in the first column of its display. " (forward-char) (setq type ?%))) `(lambda (file) - (let ((attrs (file-attributes (directory-file-name file)))) + (let ((attrs (eshell-file-attributes (directory-file-name file)))) (if attrs (memq (aref (nth 8 attrs) 0) ,(if (eq type ?%) @@ -489,7 +489,7 @@ that 'ls -l' will show in the first column of its display. " (setq amount (string-to-number (match-string 0))) (goto-char (match-end 0)) `(lambda (file) - (let ((attrs (file-attributes file))) + (let ((attrs (eshell-file-attributes file))) (if attrs (,(if (eq qual ?-) '< @@ -518,7 +518,7 @@ that 'ls -l' will show in the first column of its display. " (setq amount (* (string-to-number (match-string 0)) quantum)) (goto-char (match-end 0)) `(lambda (file) - (let ((attrs (file-attributes file))) + (let ((attrs (eshell-file-attributes file))) (if attrs (,(if (eq qual ?-) '< diff --git a/lisp/eshell/em-rebind.el b/lisp/eshell/em-rebind.el index 0463a78ffb4..4caae2da425 100644 --- a/lisp/eshell/em-rebind.el +++ b/lisp/eshell/em-rebind.el @@ -232,7 +232,7 @@ lock it at that." Sends an EOF only if point is at the end of the buffer and there is no input." (interactive "p") - (let ((proc (get-buffer-process (current-buffer)))) + (let ((proc (eshell-interactive-process))) (if (eobp) (cond ((/= (point) eshell-last-output-end) diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el index 17e110f65ed..07f14c6e30a 100644 --- a/lisp/eshell/em-unix.el +++ b/lisp/eshell/em-unix.el @@ -122,6 +122,12 @@ Otherwise, `rmdir' is required." :type 'boolean :group 'eshell-unix) +(defcustom eshell-du-prefer-over-ange nil + "*Use Eshell's du in ange-ftp remote directories. +Otherwise, Emacs will attempt to use rsh to invoke du the machine." + :type 'boolean + :group 'eshell-unix) + (require 'esh-opt) ;;; Functions: @@ -296,7 +302,7 @@ Remove the DIRECTORY(ies), if they are empty.") "Shuffle around some filesystem entries, using FUNC to do the work." (if (null target) (error "%s: missing destination file" command)) - (let ((attr-target (file-attributes target)) + (let ((attr-target (eshell-file-attributes target)) (is-dir (or (file-directory-p target) (and preview (not eshell-warn-dot-directories)))) attr) @@ -315,8 +321,10 @@ Remove the DIRECTORY(ies), if they are empty.") ((and attr-target (or (not (eshell-under-windows-p)) (eq system-type 'ms-dos)) - (setq attr (file-attributes (car files))) + (setq attr (eshell-file-attributes (car files))) + (nth 10 attr-target) (nth 10 attr) (= (nth 10 attr-target) (nth 10 attr)) + (nth 11 attr-target) (nth 11 attr) (= (nth 11 attr-target) (nth 11 attr))) (eshell-error (format "%s: `%s' and `%s' are the same file\n" command (car files) target))) @@ -339,10 +347,10 @@ Remove the DIRECTORY(ies), if they are empty.") (let (eshell-warn-dot-directories) (if (and (not deep) (eq func 'rename-file) - (= (nth 11 (file-attributes + (= (nth 11 (eshell-file-attributes (file-name-directory (expand-file-name source)))) - (nth 11 (file-attributes + (nth 11 (eshell-file-attributes (file-name-directory (expand-file-name target)))))) (apply 'eshell-funcalln func source target args) @@ -415,7 +423,7 @@ Remove the DIRECTORY(ies), if they are empty.") (or (not no-dereference) (not (file-symlink-p (car args))))))) (eshell-shorthand-tar-command ,command args) - (let (target) + (let (target ange-cache) (if (> (length args) 1) (progn (setq target (car (last args))) @@ -508,7 +516,7 @@ Create a link to the specified TARGET with optional LINK_NAME. If there is more than one TARGET, the last argument must be a directory; create links in DIRECTORY to each TARGET. Create hard links by default, symbolic links with '--symbolic'. When creating hard links, each TARGET must exist.") - (let (target no-dereference) + (let (target no-dereference ange-cache) (if (> (length args) 1) (progn (setq target (car (last args))) @@ -525,10 +533,24 @@ with '--symbolic'. When creating hard links, each TARGET must exist.") nil)) (defun eshell/cat (&rest args) - "Implementation of cat in Lisp." - (if eshell-in-pipeline-p - (throw 'eshell-replace-command - (eshell-parse-command "*cat" (eshell-flatten-list args))) + "Implementation of cat in Lisp. +If in a pipeline, or the file is not a regular file, directory or +symlink, then revert to the system's definition of cat." + (setq args (eshell-flatten-list args)) + (if (or eshell-in-pipeline-p + (catch 'special + (eshell-for arg args + (unless (let ((attrs (eshell-file-attributes arg))) + (and attrs (memq (aref (nth 8 attrs) 0) + '(?d ?l ?-)))) + (throw 'special t))))) + (let ((ext-cat (eshell-search-path "cat"))) + (if ext-cat + (throw 'eshell-replace-command + (eshell-parse-command ext-cat args)) + (if eshell-in-pipeline-p + (error "Eshell's `cat' does not work in pipelines") + (error "Eshell's `cat' cannot display one of the files given")))) (eshell-init-print-buffer) (eshell-eval-using-options "cat" args @@ -772,61 +794,69 @@ external command." (defun eshell/du (&rest args) "Implementation of \"du\" in Lisp, passing ARGS." - (if (eshell-search-path "du") - (throw 'eshell-replace-command - (eshell-parse-command "*du" (eshell-flatten-list args))) - (eshell-eval-using-options - "du" args - '((?a "all" nil show-all - "write counts for all files, not just directories") - (nil "block-size" t block-size - "use SIZE-byte blocks (i.e., --block-size SIZE)") - (?b "bytes" nil by-bytes - "print size in bytes") - (?c "total" nil grand-total - "produce a grand total") - (?d "max-depth" t max-depth - "display data only this many levels of data") - (?h "human-readable" 1024 human-readable - "print sizes in human readable format") - (?H "is" 1000 human-readable - "likewise, but use powers of 1000 not 1024") - (?k "kilobytes" 1024 block-size - "like --block-size 1024") - (?L "dereference" nil dereference-links - "dereference all symbolic links") - (?m "megabytes" 1048576 block-size - "like --block-size 1048576") - (?s "summarize" 0 max-depth - "display only a total for each argument") - (?x "one-file-system" nil only-one-filesystem - "skip directories on different filesystems") - (nil "help" nil nil - "show this usage screen") - :external "du" - :usage "[OPTION]... FILE... + (setq args (if args + (eshell-flatten-list args) + '("."))) + (let ((ext-du (eshell-search-path "du"))) + (if (and ext-du + (not (catch 'have-ange-path + (eshell-for arg args + (if (eq (find-file-name-handler (expand-file-name arg) + 'directory-files) + 'ange-ftp-hook-function) + (throw 'have-ange-path t)))))) + (throw 'eshell-replace-command + (eshell-parse-command ext-du args)) + (eshell-eval-using-options + "du" args + '((?a "all" nil show-all + "write counts for all files, not just directories") + (nil "block-size" t block-size + "use SIZE-byte blocks (i.e., --block-size SIZE)") + (?b "bytes" nil by-bytes + "print size in bytes") + (?c "total" nil grand-total + "produce a grand total") + (?d "max-depth" t max-depth + "display data only this many levels of data") + (?h "human-readable" 1024 human-readable + "print sizes in human readable format") + (?H "is" 1000 human-readable + "likewise, but use powers of 1000 not 1024") + (?k "kilobytes" 1024 block-size + "like --block-size 1024") + (?L "dereference" nil dereference-links + "dereference all symbolic links") + (?m "megabytes" 1048576 block-size + "like --block-size 1048576") + (?s "summarize" 0 max-depth + "display only a total for each argument") + (?x "one-file-system" nil only-one-filesystem + "skip directories on different filesystems") + (nil "help" nil nil + "show this usage screen") + :external "du" + :usage "[OPTION]... FILE... Summarize disk usage of each FILE, recursively for directories.") - (unless by-bytes - (setq block-size (or block-size 1024))) - (if (and max-depth (stringp max-depth)) - (setq max-depth (string-to-int max-depth))) - ;; filesystem support means nothing under Windows - (if (eshell-under-windows-p) - (setq only-one-filesystem nil)) - (unless args - (setq args '("."))) - (let ((size 0.0)) - (while args - (if only-one-filesystem - (setq only-one-filesystem - (nth 11 (file-attributes - (file-name-as-directory (car args)))))) - (setq size (+ size (eshell-du-sum-directory - (directory-file-name (car args)) 0))) - (setq args (cdr args))) - (if grand-total - (eshell-print (concat (eshell-du-size-string size) - "total\n"))))))) + (unless by-bytes + (setq block-size (or block-size 1024))) + (if (and max-depth (stringp max-depth)) + (setq max-depth (string-to-int max-depth))) + ;; filesystem support means nothing under Windows + (if (eshell-under-windows-p) + (setq only-one-filesystem nil)) + (let ((size 0.0) ange-cache) + (while args + (if only-one-filesystem + (setq only-one-filesystem + (nth 11 (eshell-file-attributes + (file-name-as-directory (car args)))))) + (setq size (+ size (eshell-du-sum-directory + (directory-file-name (car args)) 0))) + (setq args (cdr args))) + (if grand-total + (eshell-print (concat (eshell-du-size-string size) + "total\n")))))))) (defvar eshell-time-start nil) diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el index 8364ca9a7f6..985401fbe37 100644 --- a/lisp/eshell/esh-arg.el +++ b/lisp/eshell/esh-arg.el @@ -328,13 +328,13 @@ special character that is not itself a backslash." (defun eshell-parse-double-quote () "Parse a double quoted string, which allows for variable interpolation." (when (eq (char-after) ?\") - (forward-char) (let* ((end (eshell-find-delimiter ?\" ?\" nil nil t)) (eshell-current-quoted t)) (if (not end) (throw 'eshell-incomplete ?\") (prog1 (save-restriction + (forward-char) (narrow-to-region (point) end) (list 'eshell-escape-arg (eshell-parse-argument))) diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el index 1d79c8af701..4d32da81f05 100644 --- a/lisp/eshell/esh-mode.el +++ b/lisp/eshell/esh-mode.el @@ -524,8 +524,9 @@ sessions, such as when using `eshell-command'.") (interactive) (require 'etags) (let ((inhibit-read-only t) - (no-default (eobp))) - (setq tagname (find-tag-interactive "Find tag: " no-default)) + (no-default (eobp)) + (find-tag-default-function 'ignore)) + (setq tagname (car (find-tag-interactive "Find tag: "))) (find-tag tagname next-p regexp-p))) (defun eshell-move-argument (limit func property arg) diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el index 3d650928c62..84416893107 100644 --- a/lisp/eshell/esh-util.el +++ b/lisp/eshell/esh-util.el @@ -86,6 +86,15 @@ function `string-to-number'." :type 'regexp :group 'eshell-util) +(defcustom eshell-ange-ls-uids nil + "*List of user/host/id strings, used to determine remote ownership." + :type '(list (cons :tag "Host/User Pair" + (string :tag "Hostname") + (repeat (cons :tag "User/UID List" + (string :tag "Username") + (repeat :tag "UIDs" string))))) + :group 'eshell-util) + ;;; Internal Variables: (defvar eshell-group-names nil @@ -558,28 +567,123 @@ Unless optional argument INPLACE is non-nil, return a new string." (unless (fboundp 'directory-files-and-attributes) (defun directory-files-and-attributes (dir &optional full match nosort) (documentation 'directory-files) - (let* ((dir (expand-file-name dir)) - (default-directory dir)) + (let ((dir (expand-file-name dir)) ange-cache) (mapcar (function (lambda (file) - (cons file (file-attributes file)))) + (cons file (eshell-file-attributes (expand-file-name file dir))))) (directory-files dir full match nosort))))) +(eval-when-compile + (defvar ange-cache)) + (defun eshell-directory-files-and-attributes (dir &optional full match nosort) "Make sure to use the handler for `directory-file-and-attributes'." - (let ((dfh (find-file-name-handler dir 'directory-files))) + (let* ((dir (expand-file-name dir)) + (dfh (find-file-name-handler dir 'directory-files))) (if (not dfh) (directory-files-and-attributes dir full match nosort) - (let* ((files (funcall dfh 'directory-files dir full match nosort)) - (fah (find-file-name-handler dir 'file-attributes)) - (default-directory (expand-file-name dir))) + (let ((files (funcall dfh 'directory-files dir full match nosort)) + (fah (find-file-name-handler dir 'file-attributes))) (mapcar (function (lambda (file) - (cons file (funcall fah 'file-attributes file)))) + (cons file (if fah + (eshell-file-attributes + (expand-file-name file dir)) + (file-attributes (expand-file-name file dir)))))) files))))) +(defun eshell-current-ange-uids () + (if (string-match "/\\([^@]+\\)@\\([^:]+\\):" default-directory) + (let* ((host (match-string 2 default-directory)) + (user (match-string 1 default-directory)) + (host-users (assoc host eshell-ange-ls-uids))) + (when host-users + (setq host-users (cdr host-users)) + (cdr (assoc user host-users)))))) + +;; Add an autoload for parse-time-string +(if (and (not (fboundp 'parse-time-string)) + (locate-library "parse-time")) + (autoload 'parse-time-string "parse-time")) + +(defun eshell-parse-ange-ls (dir) + (let (entry) + (with-temp-buffer + (insert (ange-ftp-ls dir "-la" nil)) + (goto-char (point-min)) + (if (looking-at "^total [0-9]+$") + (forward-line 1)) + ;; Some systems put in a blank line here. + (if (eolp) (forward-line 1)) + (while (looking-at + `,(concat "\\([dlscb-][rwxst-]+\\)" + "\\s-*" "\\([0-9]+\\)" "\\s-+" + "\\(\\S-+\\)" "\\s-+" + "\\(\\S-+\\)" "\\s-+" + "\\([0-9]+\\)" "\\s-+" "\\(.*\\)")) + (let* ((perms (match-string 1)) + (links (string-to-number (match-string 2))) + (user (match-string 3)) + (group (match-string 4)) + (size (string-to-number (match-string 5))) + (mtime + (if (fboundp 'parse-time-string) + (let ((moment (parse-time-string + (match-string 6)))) + (if (nth 0 moment) + (setcar (nthcdr 5 moment) + (nth 5 (decode-time (current-time)))) + (setcar (nthcdr 0 moment) 0) + (setcar (nthcdr 1 moment) 0) + (setcar (nthcdr 2 moment) 0)) + (apply 'encode-time moment)) + (ange-ftp-file-modtime (expand-file-name name dir)))) + (name (ange-ftp-parse-filename)) + symlink) + (if (string-match "\\(.+\\) -> \\(.+\\)" name) + (setq symlink (match-string 2 name) + name (match-string 1 name))) + (setq entry + (cons + (cons name + (list (if (eq (aref perms 0) ?d) + t + symlink) + links user group + nil mtime nil + size perms nil nil)) entry))) + (forward-line))) + entry)) + +(defun eshell-file-attributes (file) + "Return the attributes of FILE, playing tricks if it's over ange-ftp." + (let* ((file (expand-file-name file)) + (handler (find-file-name-handler file 'file-attributes)) + entry) + (if (not handler) + (file-attributes file) + (if (eq (find-file-name-handler (file-name-directory file) + 'directory-files) + 'ange-ftp-hook-function) + (let ((base (file-name-nondirectory file)) + (dir (file-name-directory file))) + (if (boundp 'ange-cache) + (setq entry (cdr (assoc base (cdr (assoc dir ange-cache)))))) + (unless entry + (setq entry (eshell-parse-ange-ls dir)) + (if (boundp 'ange-cache) + (setq ange-cache + (cons (cons dir entry) + ange-cache))) + (if entry + (let ((fentry (assoc base (cdr entry)))) + (if fentry + (setq entry (cdr fentry)) + (setq entry nil))))))) + (or entry (funcall handler 'file-attributes file))))) + (defun eshell-copy-list (list) "Return a copy of a list, which may be a dotted list. The elements of the list are not copied, just the list structure itself."