Commit 8c6b1d83 authored by John Wiegley's avatar John Wiegley
Browse files

Added better remote directory support to Eshell, as well as a few bug

fixes.  See the ChangeLog.
parent e2c06b17
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-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
(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.
......@@ -243,7 +243,7 @@ resulting regular expression."
(let ((paths (eshell-split-path glob))
matches message-shown)
matches message-shown ange-cache)
(if (and (cdr paths)
(file-name-absolute-p (car paths)))
......@@ -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))
`(if (numberp (nth 2 ,attrs))
(if (= (user-uid) (nth 2 ,attrs))
(not (eq (aref (nth 8 ,attrs) ,index) ?-))
(,(eval func) ,file)))
(,(eval func) ,file))
(not (eq (aref (nth 8 ,attrs)
(+ ,index (if (member (nth 2 ,attrs)
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)
(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."
(expand-file-name (car fileinfo))))))
(setq attr
(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)
(or (let ((user (nth 2 attrs)))
(and (not numeric-uid-gid)
(user-login-name (nth 2 attrs)) 8))
(if (numberp user)
(user-login-name user)
user) 8)))
(nth 2 attrs)
(or (and (not numeric-uid-gid)
(nth 3 attrs)
(or (let ((group (nth 3 attrs)))
(and (not numeric-uid-gid)
(eshell-group-name (nth 3 attrs)) 8))
(if (numberp group)
(eshell-group-name group)
group) 8)))
(nth 3 attrs)
(let* ((str (eshell-ls-printable-size (nth 7 attrs)))
......@@ -464,7 +464,7 @@ that 'ls -l' will show in the first column of its display. "
(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 ?-)
......@@ -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
(interactive "p")
(let ((proc (get-buffer-process (current-buffer))))
(let ((proc (eshell-interactive-process)))
(if (eobp)
((/= (point) eshell-last-output-end)
......@@ -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))))
......@@ -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
(expand-file-name source))))
(nth 11 (file-attributes
(nth 11 (eshell-file-attributes
(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)
(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)
(setq target (car (last args)))
......@@ -525,10 +533,24 @@ with '--symbolic'. When creating hard links, each TARGET must exist.")
(defun eshell/cat (&rest args)
"Implementation of cat in Lisp."
(if eshell-in-pipeline-p
"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 "*cat" (eshell-flatten-list args)))
(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"))))
"cat" args
......@@ -772,9 +794,19 @@ external command."
(defun eshell/du (&rest args)
"Implementation of \"du\" in Lisp, passing ARGS."
(if (eshell-search-path "du")
(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)
(throw 'have-ange-path t))))))
(throw 'eshell-replace-command
(eshell-parse-command "*du" (eshell-flatten-list args)))
(eshell-parse-command ext-du args))
"du" args
'((?a "all" nil show-all
......@@ -813,20 +845,18 @@ Summarize disk usage of each FILE, recursively for directories.")
;; filesystem support means nothing under Windows
(if (eshell-under-windows-p)
(setq only-one-filesystem nil))
(unless args
(setq args '(".")))
(let ((size 0.0))
(let ((size 0.0) ange-cache)
(while args
(if only-one-filesystem
(setq only-one-filesystem
(nth 11 (file-attributes
(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)
(defvar eshell-time-start nil)
......@@ -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) ?\")
(let* ((end (eshell-find-delimiter ?\" ?\" nil nil t))
(eshell-current-quoted t))
(if (not end)
(throw 'eshell-incomplete ?\")
(narrow-to-region (point) end)
(list 'eshell-escape-arg
......@@ -524,8 +524,9 @@ sessions, such as when using `eshell-command'.")
(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)
......@@ -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)
(lambda (file)
(cons file (file-attributes file))))
(cons file (eshell-file-attributes (expand-file-name file dir)))))
(directory-files dir full match nosort)))))
(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)))
(lambda (file)
(cons file (funcall fah 'file-attributes file))))
(cons file (if fah
(expand-file-name file dir))
(file-attributes (expand-file-name file dir))))))
(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)
(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)))
(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))
(if (string-match "\\(.+\\) -> \\(.+\\)" name)
(setq symlink (match-string 2 name)
name (match-string 1 name)))
(setq entry
(cons name
(list (if (eq (aref perms 0) ?d)
links user group
nil mtime nil
size perms nil nil)) 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))
(if (not handler)
(file-attributes file)
(if (eq (find-file-name-handler (file-name-directory file)
(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)
(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."
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment