]> git.eshelyaron.com Git - emacs.git/commitdiff
*** empty log message ***
authorGerd Moellmann <gerd@gnu.org>
Fri, 23 Jun 2000 04:39:18 +0000 (04:39 +0000)
committerGerd Moellmann <gerd@gnu.org>
Fri, 23 Jun 2000 04:39:18 +0000 (04:39 +0000)
lisp/ChangeLog
lisp/eshell/esh-mode.el [new file with mode: 0644]
lisp/eshell/esh-util.el [new file with mode: 0644]

index 655ddd13c536288fd7e2fd81b0205333ae3266b0..61460d51a29236b8cd4aa5578cea2b566633394d 100644 (file)
@@ -1,3 +1,11 @@
+2000-06-23  Gerd Moellmann  <gerd@gnu.org>
+
+       * subdirs.el: Add eshell subdirectory.
+
+       * eshell: New subdirectory containing the Eshell package.
+
+       * pcomplete.el: New file.
+
 2000-06-22  Eli Zaretskii  <eliz@is.elta.co.il>
 
        * files.el (make-backup-file-name-1): On DOS/Windows, run the
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
new file mode 100644 (file)
index 0000000..6df4602
--- /dev/null
@@ -0,0 +1,1006 @@
+;;; esh-mode --- user interface
+
+;; Copyright (C) 1999, 2000 Free Sofware Foundation
+
+;; 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 2, 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, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+(provide 'esh-mode)
+
+(eval-when-compile (require 'esh-maint))
+
+(defgroup eshell-mode nil
+  "This module contains code for handling input from the user."
+  :tag "User interface"
+  :group 'eshell)
+
+;;; Commentary:
+
+;; Basically, Eshell is used just like shell mode (<M-x shell>).  The
+;; keystrokes for navigating the buffer, and accessing the command
+;; history, are identical.  Unlike shell mode, however, Eshell mode's
+;; governing process is Emacs itself.  With shell mode, an inferior
+;; shell process is executed that communicates with Emacs via comint
+;; -- a mode for handling sub-process interaction.  Eshell mode, on
+;; the other hand, is a truly native Emacs shell.  No subprocess are
+;; invoked except the ones requested by the user at the prompt.
+;;
+;; After entering a command, use <RET> to invoke it ([Command
+;; invocation]) .  If there is a command on disk, it will be executed
+;; as in a normal shell.  If there is no command by that name on disk,
+;; but a Lisp function with that name is defined, the Lisp function
+;; will be called, using the arguments passed on the command line.
+;;
+;; Some of the other features of the command interaction mode are:
+;;
+;; @ <M-RET> can be used to accumulate further commands while a
+;;   command is currently running.  Since all input is passed to the
+;;   subprocess being executed, there is no automatic input queueing
+;;   as there is with other shells.
+;;
+;; @ <C-c C-t> can be used to truncate the buffer if it grows too
+;;   large.
+;;
+;; @ <C-c C-r> will move point to the beginning of the output of the
+;;   last command.  With a prefix argument, it will narrow to view
+;;   only that output.
+;;
+;; @ <C-c C-o> will delete the output from the last command.
+;;
+;; @ <C-c C-f> will move forward a complete shell argument.
+;;
+;; @ <C-c C-b> will move backward a complete shell argument.
+
+(require 'esh-module)
+(require 'esh-cmd)
+(require 'esh-io)
+(require 'esh-var)
+
+;;; User Variables:
+
+(defcustom eshell-mode-unload-hook nil
+  "*A hook that gets run when `eshell-mode' is unloaded."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-mode-hook nil
+  "*A hook that gets run when `eshell-mode' is entered."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-first-time-mode-hook nil
+  "*A hook that gets run the first time `eshell-mode' is entered.
+That is to say, the first time during an Emacs session."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-exit-hook '(eshell-query-kill-processes)
+  "*A hook that is run whenever `eshell' is exited.
+This hook is only run if exiting actually kills the buffer."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-kill-on-exit t
+  "*If non-nil, kill the Eshell buffer on the `exit' command.
+Otherwise, the buffer will simply be buried."
+  :type 'boolean
+  :group 'eshell-mode)
+
+(defcustom eshell-input-filter-functions nil
+  "*Functions to call before input is processed.
+The input is contained in the region from `eshell-last-input-start' to
+`eshell-last-input-end'."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-expand-input-functions nil
+  "*Functions to call before input is parsed.
+Each function is passed two arguments, which bounds the region of the
+current input text."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-scroll-to-bottom-on-input nil
+  "*Controls whether input to interpreter causes window to scroll.
+If nil, then do not scroll.  If t or `all', scroll all windows showing
+buffer.  If `this', scroll only the selected window.
+
+See `eshell-preinput-scroll-to-bottom'."
+  :type '(radio (const :tag "Do not scroll Eshell windows" nil)
+               (const :tag "Scroll all windows showing the buffer" all)
+               (const :tag "Scroll only the selected window" this))
+  :group 'eshell-mode)
+
+(defcustom eshell-scroll-to-bottom-on-output nil
+  "*Controls whether interpreter output causes window to scroll.
+If nil, then do not scroll.  If t or `all', scroll all windows showing
+buffer.  If `this', scroll only the selected window.  If `others',
+scroll only those that are not the selected window.
+
+See variable `eshell-scroll-show-maximum-output' and function
+`eshell-postoutput-scroll-to-bottom'."
+  :type '(radio (const :tag "Do not scroll Eshell windows" nil)
+               (const :tag "Scroll all windows showing the buffer" all)
+               (const :tag "Scroll only the selected window" this)
+               (const :tag "Scroll all windows other than selected" this))
+  :group 'eshell-mode)
+
+(defcustom eshell-scroll-show-maximum-output t
+  "*Controls how interpreter output causes window to scroll.
+If non-nil, then show the maximum output when the window is scrolled.
+
+See variable `eshell-scroll-to-bottom-on-output' and function
+`eshell-postoutput-scroll-to-bottom'."
+  :type 'boolean
+  :group 'eshell-mode)
+
+(defcustom eshell-buffer-maximum-lines 1024
+  "*The maximum size in lines for eshell buffers.
+Eshell buffers are truncated from the top to be no greater than this
+number, if the function `eshell-truncate-buffer' is on
+`eshell-output-filter-functions'."
+  :type 'integer
+  :group 'eshell-mode)
+
+(defcustom eshell-output-filter-functions
+  '(eshell-handle-control-codes
+    eshell-watch-for-password-prompt)
+  "*Functions to call before output is displayed.
+These functions are only called for output that is displayed
+interactively, and not for output which is redirected."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-preoutput-filter-functions nil
+  "*Functions to call before output is inserted into the buffer.
+These functions get one argument, a string containing the text to be
+inserted.  They return the string as it should be inserted."
+  :type 'hook
+  :group 'eshell-mode)
+
+(defcustom eshell-password-prompt-regexp
+  "\\(\\([Oo]ld \\|[Nn]ew \\|Kerberos \\|CVS \\|'s \\|login \\|^\\)\
+[Pp]assword\\|pass phrase\\|\\(Enter\\|Repeat\\) passphrase\\)\
+\\( for [^@ \t\n]+@[^@ \t\n]+\\)?:\\s *\\'"
+  "*Regexp matching prompts for passwords in the inferior process.
+This is used by `eshell-watch-for-password-prompt'."
+  :type 'regexp
+  :group 'eshell-mode)
+
+(defcustom eshell-skip-prompt-function nil
+  "*A function called from beginning of line to skip the prompt."
+  :type 'function
+  :group 'eshell-mode)
+
+(defcustom eshell-status-in-modeline t
+  "*If non-nil, let the user know a command is running in the modeline."
+  :type 'boolean
+  :group 'eshell-mode)
+
+(defvar eshell-non-interactive-p nil
+  "A variable which is non-nil when Eshell is not running interactively.
+Modules should use this variable so that they don't clutter non-interactive
+sessions, such as when using `eshell-command'.")
+
+(defvar eshell-first-time-p t
+  "A variable which is non-nil the first time Eshell is loaded.")
+
+;; Internal Variables:
+
+;; these are only set to `nil' initially for the sake of the
+;; byte-compiler, when compiling other files which `require' this one
+(defvar eshell-mode nil)
+(defvar eshell-mode-map nil)
+(defvar eshell-command-running-string "--")
+(defvar eshell-command-map nil)
+(defvar eshell-command-prefix nil)
+(defvar eshell-last-input-start nil)
+(defvar eshell-last-input-end nil)
+(defvar eshell-last-output-start nil)
+(defvar eshell-last-output-block-begin nil)
+(defvar eshell-last-output-end nil)
+
+(defvar eshell-currently-handling-window nil)
+(defvar eshell-mode-syntax-table nil)
+(defvar eshell-mode-abbrev-table nil)
+
+(define-abbrev-table 'eshell-mode-abbrev-table ())
+
+(eval-when-compile
+  (unless (eshell-under-xemacs-p)
+    (defalias 'characterp 'ignore)
+    (defalias 'char-int 'ignore)))
+
+(if (not eshell-mode-syntax-table)
+    (let ((i 0))
+      (setq eshell-mode-syntax-table (make-syntax-table))
+      (while (< i ?0)
+       (modify-syntax-entry i "_   " eshell-mode-syntax-table)
+       (setq i (1+ i)))
+      (setq i (1+ ?9))
+      (while (< i ?A)
+       (modify-syntax-entry i "_   " eshell-mode-syntax-table)
+       (setq i (1+ i)))
+      (setq i (1+ ?Z))
+      (while (< i ?a)
+       (modify-syntax-entry i "_   " eshell-mode-syntax-table)
+       (setq i (1+ i)))
+      (setq i (1+ ?z))
+      (while (< i 128)
+       (modify-syntax-entry i "_   " eshell-mode-syntax-table)
+       (setq i (1+ i)))
+      (modify-syntax-entry ?  "    " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\t "    " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\f "    " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\n ">   " eshell-mode-syntax-table)
+      ;; Give CR the same syntax as newline, for selective-display.
+      (modify-syntax-entry ?\^m ">   " eshell-mode-syntax-table)
+;;;   (modify-syntax-entry ?\; "<   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?` "'   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?' "'   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?, "'   " eshell-mode-syntax-table)
+      ;; Used to be singlequote; changed for flonums.
+      (modify-syntax-entry ?. "_   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?- "_   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?| ".   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?# "'   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\" "\"    " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\\ "/   " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\( "()  " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\) ")(  " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\{ "(}  " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\} "){  " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\[ "(]  " eshell-mode-syntax-table)
+      (modify-syntax-entry ?\] ")[  " eshell-mode-syntax-table)
+      ;; All non-word multibyte characters should be `symbol'.
+      (if (eshell-under-xemacs-p)
+         (map-char-table
+          (function
+           (lambda (key val)
+             (and (characterp key)
+                  (>= (char-int key) 256)
+                  (/= (char-syntax key) ?w)
+                  (modify-syntax-entry key "_   "
+                                       eshell-mode-syntax-table))))
+          (standard-syntax-table))
+       (map-char-table
+        (function
+         (lambda (key val)
+           (and (>= key 256)
+                (/= (char-syntax key) ?w)
+                (modify-syntax-entry key "_   "
+                                     eshell-mode-syntax-table))))
+        (standard-syntax-table)))))
+
+;;; User Functions:
+
+;;;###autoload
+(defun eshell-mode ()
+  "Emacs shell interactive mode.
+
+\\{eshell-mode-map}"
+  (kill-all-local-variables)
+
+  (setq major-mode 'eshell-mode)
+  (setq mode-name "EShell")
+  (set (make-local-variable 'eshell-mode) t)
+
+  (make-local-variable 'eshell-mode-map)
+  (setq eshell-mode-map (make-sparse-keymap))
+  (use-local-map eshell-mode-map)
+
+  (when eshell-status-in-modeline
+    (make-local-variable 'eshell-command-running-string)
+    (let ((fmt (copy-list mode-line-format)))
+      (make-local-variable 'mode-line-format)
+      (setq mode-line-format fmt))
+    (let ((modeline (memq 'mode-line-modified mode-line-format)))
+      (if modeline
+         (setcar modeline 'eshell-command-running-string))))
+
+  (define-key eshell-mode-map [return] 'eshell-send-input)
+  (define-key eshell-mode-map [(control ?m)] 'eshell-send-input)
+  (define-key eshell-mode-map [(control ?j)] 'eshell-send-input)
+  (define-key eshell-mode-map [(meta return)] 'eshell-queue-input)
+  (define-key eshell-mode-map [(meta control ?m)] 'eshell-queue-input)
+  (define-key eshell-mode-map [(meta control ?l)] 'eshell-show-output)
+
+  (set (make-local-variable 'eshell-command-prefix)
+       (make-symbol "eshell-command-prefix"))
+  (fset eshell-command-prefix (make-sparse-keymap))
+  (set (make-local-variable 'eshell-command-map)
+       (symbol-function eshell-command-prefix))
+  (define-key eshell-mode-map [(control ?c)] eshell-command-prefix)
+
+  (define-key eshell-command-map [(meta ?o)] 'eshell-mark-output)
+
+  (define-key eshell-command-map [(control ?a)] 'eshell-bol)
+  (define-key eshell-command-map [(control ?b)] 'eshell-backward-argument)
+  (define-key eshell-command-map [(control ?e)] 'eshell-show-maximum-output)
+  (define-key eshell-command-map [(control ?f)] 'eshell-forward-argument)
+  (define-key eshell-command-map [return]       'eshell-copy-old-input)
+  (define-key eshell-command-map [(control ?m)] 'eshell-copy-old-input)
+  (define-key eshell-command-map [(control ?o)] 'eshell-kill-output)
+  (define-key eshell-command-map [(control ?r)] 'eshell-show-output)
+  (define-key eshell-command-map [(control ?t)] 'eshell-truncate-buffer)
+  (define-key eshell-command-map [(control ?u)] 'eshell-kill-input)
+  (define-key eshell-command-map [(control ?w)] 'backward-kill-word)
+
+  (setq local-abbrev-table eshell-mode-abbrev-table)
+  (set-syntax-table eshell-mode-syntax-table)
+
+  (set (make-local-variable 'dired-directory) default-directory)
+  (set (make-local-variable 'list-buffers-directory)
+       (expand-file-name default-directory))
+
+  ;; always set the tab width to 8 in Eshell buffers, since external
+  ;; commands which do their own formatting almost always expect this
+  (set (make-local-variable 'tab-width) 8)
+
+  ;; always display everything from a return value
+  (if (boundp 'print-length)
+      (set (make-local-variable 'print-length) nil))
+  (if (boundp 'print-level)
+      (set (make-local-variable 'print-level) nil))
+
+  ;; set require-final-newline to nil; otherwise, all redirected
+  ;; output will end with a newline, whether or not the source
+  ;; indicated it!
+  (set (make-local-variable 'require-final-newline) nil)
+
+  (set (make-local-variable 'max-lisp-eval-depth)
+       (max 3000 max-lisp-eval-depth))
+  (set (make-local-variable 'max-specpdl-size)
+       (max 6000 max-lisp-eval-depth))
+
+  (set (make-local-variable 'eshell-last-input-start) (point-marker))
+  (set (make-local-variable 'eshell-last-input-end) (point-marker))
+  (set (make-local-variable 'eshell-last-output-start) (point-marker))
+  (set (make-local-variable 'eshell-last-output-end) (point-marker))
+  (set (make-local-variable 'eshell-last-output-block-begin) (point))
+
+  (let ((modules-list (copy-list eshell-modules-list)))
+    (make-local-variable 'eshell-modules-list)
+    (setq eshell-modules-list modules-list))
+
+  ;; load extension modules into memory.  This will cause any global
+  ;; variables they define to be visible, since some of the core
+  ;; modules sometimes take advantage of their functionality if used.
+  (eshell-for module eshell-modules-list
+    (let ((module-fullname (symbol-name module))
+         module-shortname)
+      (if (string-match "^eshell-\\(.*\\)" module-fullname)
+         (setq module-shortname
+               (concat "em-" (match-string 1 module-fullname))))
+      (unless module-shortname
+       (error "Invalid Eshell module name: %s" module-fullname))
+      (unless (featurep (intern module-shortname))
+       (load module-shortname))))
+
+  (unless (file-exists-p eshell-directory-name)
+    (eshell-make-private-directory eshell-directory-name t))
+
+  ;; load core Eshell modules for this session
+  (eshell-for module (eshell-subgroups 'eshell)
+    (run-hooks (intern-soft (concat (symbol-name module)
+                                   "-load-hook"))))
+
+  ;; load extension modules for this session
+  (eshell-for module eshell-modules-list
+    (let ((load-hook (intern-soft (concat (symbol-name module)
+                                         "-load-hook"))))
+      (if (and load-hook (boundp load-hook))
+         (run-hooks load-hook))))
+
+  (when eshell-scroll-to-bottom-on-input
+    (make-local-hook 'pre-command-hook)
+    (add-hook 'pre-command-hook 'eshell-preinput-scroll-to-bottom t t))
+
+  (when eshell-scroll-show-maximum-output
+    (set (make-local-variable 'scroll-conservatively) 1000))
+
+  (when eshell-status-in-modeline
+    (make-local-hook 'eshell-pre-command-hook)
+    (add-hook 'eshell-pre-command-hook 'eshell-command-started nil t)
+    (make-local-hook 'eshell-post-command-hook)
+    (add-hook 'eshell-post-command-hook 'eshell-command-finished nil t))
+
+  (make-local-hook 'kill-buffer-hook)
+  (add-hook 'kill-buffer-hook
+           (function
+            (lambda ()
+              (run-hooks 'eshell-exit-hook))) t t)
+
+  (if eshell-first-time-p
+      (run-hooks 'eshell-first-time-mode-hook))
+  (run-hooks 'eshell-mode-hook)
+  (run-hooks 'eshell-post-command-hook))
+
+(put 'eshell-mode 'mode-class 'special)
+
+(eshell-deftest mode major-mode
+  "Major mode is correct"
+  (eq major-mode 'eshell-mode))
+
+(eshell-deftest mode eshell-mode-variable
+  "`eshell-mode' is true"
+  (eq eshell-mode t))
+
+(eshell-deftest var window-height
+  "LINES equals window height"
+  (eshell-command-result-p "= $LINES (window-height)" "t\n"))
+
+(defun eshell-command-started ()
+  "Indicate in the modeline that a command has started."
+  (setq eshell-command-running-string "**")
+  (force-mode-line-update))
+
+(defun eshell-command-finished ()
+  "Indicate in the modeline that a command has finished."
+  (setq eshell-command-running-string "--")
+  (force-mode-line-update))
+
+(eshell-deftest mode command-running-p
+  "Modeline shows no command running"
+  (or (eshell-under-xemacs-p)
+      (not eshell-status-in-modeline)
+      (and (memq 'eshell-command-running-string mode-line-format)
+          (equal eshell-command-running-string "--"))))
+
+;;; Internal Functions:
+
+(defun eshell-move-argument (limit func property arg)
+  "Move forward ARG arguments."
+  (catch 'eshell-incomplete
+    (eshell-parse-arguments (save-excursion (eshell-bol) (point))
+                           (line-end-position)))
+  (let ((pos
+        (save-excursion
+          (funcall func 1)
+          (while (and (> arg 0)
+                      (not (= (point) limit)))
+            (if (get-text-property (point) property)
+                (setq arg (1- arg)))
+            (if (> arg 0)
+                (funcall func 1)))
+          (point))))
+    (goto-char pos)
+    (if (and (eq func 'forward-char)
+            (= (1+ pos) limit))
+       (forward-char 1))))
+
+(eshell-deftest arg forward-arg
+  "Move across command arguments"
+  (eshell-insert-command "echo $(+ 1 (- 4 3)) \"alpha beta\" file" 'ignore)
+  (let ((here (point)) begin valid)
+    (eshell-bol)
+    (setq begin (point))
+    (eshell-forward-argument 4)
+    (setq valid (= here (point)))
+    (eshell-backward-argument 4)
+    (prog1
+       (and valid (= begin (point)))
+      (eshell-bol)
+      (delete-region (point) (point-max)))))
+
+(defun eshell-forward-argument (&optional arg)
+  "Move forward ARG arguments."
+  (interactive "p")
+  (eshell-move-argument (point-max) 'forward-char 'arg-end arg))
+
+(defun eshell-backward-argument (&optional arg)
+  "Move backward ARG arguments."
+  (interactive "p")
+  (eshell-move-argument (point-min) 'backward-char 'arg-begin arg))
+
+(defun eshell-bol ()
+  "Goes to the beginning of line, then skips past the prompt, if any."
+  (interactive)
+  (beginning-of-line)
+  (and eshell-skip-prompt-function
+       (funcall eshell-skip-prompt-function)))
+
+(defsubst eshell-push-command-mark ()
+  "Push a mark at the end of the last input text."
+  (push-mark (1- eshell-last-input-end) t))
+
+(custom-add-option 'eshell-pre-command-hook 'eshell-push-command-mark)
+
+(defsubst eshell-goto-input-start ()
+  "Goto the start of the last command input.
+Putting this function on `eshell-pre-command-hook' will mimic Plan 9's
+9term behavior."
+  (goto-char eshell-last-input-start))
+
+(custom-add-option 'eshell-pre-command-hook 'eshell-push-command-mark)
+
+(defsubst eshell-interactive-print (string)
+  "Print STRING to the eshell display buffer."
+  (eshell-output-filter nil string))
+
+(defsubst eshell-begin-on-new-line ()
+  "This function outputs a newline if not at beginning of line."
+  (save-excursion
+    (goto-char eshell-last-output-end)
+    (or (bolp)
+       (eshell-interactive-print "\n"))))
+
+(defsubst eshell-reset (&optional no-hooks)
+  "Output a prompt on a new line, aborting any current input.
+If NO-HOOKS is non-nil, then `eshell-post-command-hook' won't be run."
+  (goto-char (point-max))
+  (setq eshell-last-input-start (point-marker)
+       eshell-last-input-end (point-marker)
+       eshell-last-output-start (point-marker)
+       eshell-last-output-block-begin (point)
+       eshell-last-output-end (point-marker))
+  (eshell-begin-on-new-line)
+  (unless no-hooks
+    (run-hooks 'eshell-post-command-hook)
+    (goto-char (point-max))))
+
+(defun eshell-parse-command-input (beg end &optional args)
+  "Parse the command input from BEG to END.
+The difference is that `eshell-parse-command' expects a complete
+command string (and will error if it doesn't get one), whereas this
+function will inform the caller whether more input is required.
+
+If nil is returned, more input is necessary (probably because a
+multi-line input string wasn't terminated properly).  Otherwise, it
+will return the parsed command."
+  (let (command)
+    (unless (catch 'eshell-incomplete
+             (ignore
+              (setq command
+                    (eshell-parse-command (cons beg end) args t))))
+      command)))
+
+(defun eshell-update-markers (pmark)
+  "Update the input and output markers relative to point and PMARK."
+  (set-marker eshell-last-input-start pmark)
+  (set-marker eshell-last-input-end (point))
+  (set-marker eshell-last-output-end (point)))
+
+(defun eshell-queue-input (&optional use-region)
+  "Queue the current input text for execution by Eshell.
+Particularly, don't send the text to the current process, even if it's
+waiting for input."
+  (interactive "P")
+  (eshell-send-input use-region t))
+
+(eshell-deftest mode queue-input
+  "Queue command input"
+  (eshell-insert-command "sleep 2")
+  (eshell-insert-command "echo alpha" 'eshell-queue-input)
+  (let ((count 10))
+    (while (and eshell-current-command
+               (> count 0))
+      (sit-for 1 0)
+      (setq count (1- count))))
+  (eshell-match-result "alpha\n"))
+
+(defun eshell-send-input (&optional use-region queue-p no-newline)
+  "Send the input received to Eshell for parsing and processing..
+After `eshell-last-output-end', sends all text from that marker to
+point as input.  Before that marker, calls `eshell-get-old-input' to
+retrieve old input, copies it to the end of the buffer, and sends it.
+
+If USE-REGION is non-nil, the current region (between point and mark)
+will be used as input.
+
+If QUEUE-P is non-nil, input will be queued until the next prompt,
+rather than sent to the currently active process.  If no process, the
+input is processed immediately.
+
+If NO-NEWLINE is non-nil, the input is sent without an implied final
+newline."
+  (interactive "P")
+  ;; Note that the input string does not include its terminal newline.
+  (let ((proc-running-p (and (eshell-interactive-process)
+                            (not queue-p)))
+       (inhibit-point-motion-hooks t)
+       after-change-functions)
+    (unless (and proc-running-p
+                (not (eq (process-status
+                          (eshell-interactive-process)) 'run)))
+      (if (or proc-running-p
+             (>= (point) eshell-last-output-end))
+         (goto-char (point-max))
+       (let ((copy (eshell-get-old-input use-region)))
+         (goto-char eshell-last-output-end)
+         (insert-and-inherit copy)))
+      (unless no-newline
+       (insert-before-markers-and-inherit ?\n))
+      (if proc-running-p
+         (progn
+           (eshell-update-markers eshell-last-output-end)
+           (if (= eshell-last-input-start eshell-last-input-end)
+               (unless no-newline
+                 (process-send-string (eshell-interactive-process) "\n"))
+             (process-send-region (eshell-interactive-process)
+                                  eshell-last-input-start
+                                  eshell-last-input-end)))
+       (if (= eshell-last-output-end (point))
+           (run-hooks 'eshell-post-command-hook)
+         (let (input)
+           (eshell-condition-case err
+               (progn
+                 (setq input (buffer-substring-no-properties
+                              eshell-last-output-end (1- (point))))
+                 (run-hook-with-args 'eshell-expand-input-functions
+                                     eshell-last-output-end (1- (point)))
+                 (let ((cmd (eshell-parse-command-input
+                             eshell-last-output-end (1- (point)))))
+                   (when cmd
+                     (eshell-update-markers eshell-last-output-end)
+                     (setq input (buffer-substring-no-properties
+                                  eshell-last-input-start
+                                  (1- eshell-last-input-end)))
+                     (run-hooks 'eshell-input-filter-functions)
+                     (and (catch 'eshell-terminal
+                            (ignore
+                             (eshell-eval-command cmd input)))
+                          (eshell-life-is-too-much)))))
+             (quit
+              (eshell-reset t)
+              (run-hooks 'eshell-post-command-hook)
+              (signal 'quit nil))
+             (error
+              (eshell-reset t)
+              (eshell-interactive-print
+               (concat (error-message-string err) "\n"))
+              (run-hooks 'eshell-post-command-hook)
+              (insert-and-inherit input)))))))))
+
+(eshell-deftest proc send-to-subprocess
+  "Send input to a subprocess"
+  ;; jww (1999-12-06): what about when bc is unavailable?
+  (if (not (eshell-search-path "bc"))
+      t
+    (eshell-insert-command "bc")
+    (eshell-insert-command "1 + 2")
+    (sit-for 1 0)
+    (forward-line -1)
+    (prog1
+       (looking-at "3\n")
+      (eshell-insert-command "quit")
+      (sit-for 1 0))))
+
+(defsubst eshell-kill-new ()
+  "Add the last input text to the kill ring."
+  (kill-ring-save eshell-last-input-start eshell-last-input-end))
+
+(custom-add-option 'eshell-input-filter-functions 'eshell-kill-new)
+
+(defun eshell-output-filter (process string)
+  "Send the output from PROCESS (STRING) to the interactive display.
+This is done after all necessary filtering has been done."
+  (let ((oprocbuf (if process (process-buffer process)
+                   (current-buffer)))
+       (inhibit-point-motion-hooks t)
+       after-change-functions)
+    (let ((functions eshell-preoutput-filter-functions))
+      (while (and functions string)
+       (setq string (funcall (car functions) string))
+       (setq functions (cdr functions))))
+    (if (and string oprocbuf (buffer-name oprocbuf))
+       (let ((obuf (current-buffer))
+             opoint obeg oend)
+         (set-buffer oprocbuf)
+         (setq opoint (point))
+         (setq obeg (point-min))
+         (setq oend (point-max))
+         (let ((buffer-read-only nil)
+               (nchars (length string))
+               (ostart nil))
+           (widen)
+           (goto-char eshell-last-output-end)
+           (setq ostart (point))
+           (if (<= (point) opoint)
+               (setq opoint (+ opoint nchars)))
+           (if (< (point) obeg)
+               (setq obeg (+ obeg nchars)))
+           (if (<= (point) oend)
+               (setq oend (+ oend nchars)))
+           (insert-before-markers string)
+           (if (= (window-start (selected-window)) (point))
+               (set-window-start (selected-window)
+                                 (- (point) nchars)))
+           (if (= (point) eshell-last-input-end)
+               (set-marker eshell-last-input-end
+                           (- eshell-last-input-end nchars)))
+           (set-marker eshell-last-output-start ostart)
+           (set-marker eshell-last-output-end (point))
+           (force-mode-line-update))
+         (narrow-to-region obeg oend)
+         (goto-char opoint)
+         (eshell-run-output-filters)
+         (set-buffer obuf)))))
+
+(defun eshell-run-output-filters ()
+  "Run the `eshell-output-filter-functions' on the current output."
+  (save-current-buffer
+    (run-hooks 'eshell-output-filter-functions))
+  (setq eshell-last-output-block-begin
+       (marker-position eshell-last-output-end)))
+
+;;; jww (1999-10-23): this needs testing
+(defun eshell-preinput-scroll-to-bottom ()
+  "Go to the end of buffer in all windows showing it.
+Movement occurs if point in the selected window is not after the
+process mark, and `this-command' is an insertion command.  Insertion
+commands recognised are `self-insert-command', `yank', and
+`hilit-yank'.  Depends on the value of
+`eshell-scroll-to-bottom-on-input'.
+
+This function should be a pre-command hook."
+  (if (memq this-command '(self-insert-command yank hilit-yank))
+      (let* ((selected (selected-window))
+            (current (current-buffer))
+            (scroll eshell-scroll-to-bottom-on-input))
+       (if (< (point) eshell-last-output-end)
+           (if (eq scroll 'this)
+               (goto-char (point-max))
+             (walk-windows
+              (function
+               (lambda (window)
+                 (when (and (eq (window-buffer window) current)
+                            (or (eq scroll t) (eq scroll 'all)))
+                   (select-window window)
+                   (goto-char (point-max))
+                   (select-window selected))))
+              nil t))))))
+
+;;; jww (1999-10-23): this needs testing
+(defun eshell-postoutput-scroll-to-bottom ()
+  "Go to the end of buffer in all windows showing it.
+Does not scroll if the current line is the last line in the buffer.
+Depends on the value of `eshell-scroll-to-bottom-on-output' and
+`eshell-scroll-show-maximum-output'.
+
+This function should be in the list `eshell-output-filter-functions'."
+  (let* ((selected (selected-window))
+        (current (current-buffer))
+        (scroll eshell-scroll-to-bottom-on-output))
+    (unwind-protect
+       (walk-windows
+        (function
+         (lambda (window)
+           (if (eq (window-buffer window) current)
+               (progn
+                 (select-window window)
+                 (if (and (< (point) eshell-last-output-end)
+                          (or (eq scroll t) (eq scroll 'all)
+                              ;; Maybe user wants point to jump to end.
+                              (and (eq scroll 'this)
+                                   (eq selected window))
+                              (and (eq scroll 'others)
+                                   (not (eq selected window)))
+                              ;; If point was at the end, keep it at end.
+                              (>= (point) eshell-last-output-start)))
+                     (goto-char eshell-last-output-end))
+                 ;; Optionally scroll so that the text
+                 ;; ends at the bottom of the window.
+                 (if (and eshell-scroll-show-maximum-output
+                          (>= (point) eshell-last-output-end))
+                     (save-excursion
+                       (goto-char (point-max))
+                       (recenter -1)))
+                 (select-window selected)))))
+        nil t)
+      (set-buffer current))))
+
+(custom-add-option 'eshell-output-filter-functions
+                  'eshell-postoutput-scroll-to-bottom)
+
+(defun eshell-beginning-of-input ()
+  "Return the location of the start of the previous input."
+  eshell-last-input-start)
+
+(defun eshell-beginning-of-output ()
+  "Return the location of the end of the previous output block."
+  eshell-last-input-end)
+
+(defun eshell-end-of-output ()
+  "Return the location of the end of the previous output block."
+  (if (eshell-using-module 'eshell-prompt)
+      eshell-last-output-start
+    eshell-last-output-end))
+
+(defun eshell-kill-output ()
+  "Kill all output from interpreter since last input.
+Does not delete the prompt."
+  (interactive)
+  (save-excursion
+    (goto-char (eshell-beginning-of-output))
+    (insert "*** output flushed ***\n")
+    (delete-region (point) (eshell-end-of-output))))
+
+(eshell-deftest io flush-output
+  "Flush previous output"
+  (eshell-insert-command "echo alpha")
+  (eshell-kill-output)
+  (and (eshell-match-result (regexp-quote "*** output flushed ***\n"))
+       (forward-line)
+       (= (point) eshell-last-output-start)))
+
+(defun eshell-show-output (&optional arg)
+  "Display start of this batch of interpreter output at top of window.
+Sets mark to the value of point when this command is run.
+With a prefix argument, narrows region to last command output."
+  (interactive "P")
+  (goto-char (eshell-beginning-of-output))
+  (set-window-start (selected-window)
+                   (save-excursion
+                     (goto-char (eshell-beginning-of-input))
+                     (line-beginning-position)))
+  (if arg
+      (narrow-to-region (eshell-beginning-of-output)
+                       (eshell-end-of-output)))
+  (eshell-end-of-output))
+
+(defun eshell-mark-output (&optional arg)
+  "Display start of this batch of interpreter output at top of window.
+Sets mark to the value of point when this command is run.
+With a prefix argument, narrows region to last command output."
+  (interactive "P")
+  (push-mark (eshell-show-output arg)))
+
+(defun eshell-kill-input ()
+  "Kill all text from last stuff output by interpreter to point."
+  (interactive)
+  (if (> (point) eshell-last-output-end)
+      (kill-region eshell-last-output-end (point))
+    (let ((here (point)))
+      (eshell-bol)
+      (kill-region (point) here))))
+
+(defun eshell-show-maximum-output ()
+  "Put the end of the buffer at the bottom of the window."
+  (interactive)
+  (if (interactive-p)
+      (widen))
+  (goto-char (point-max))
+  (recenter -1))
+
+(defun eshell-get-old-input (&optional use-current-region)
+  "Return the command input on the current line."
+  (if use-current-region
+      (buffer-substring (min (point) (mark))
+                       (max (point) (mark)))
+    (save-excursion
+      (beginning-of-line)
+      (and eshell-skip-prompt-function
+          (funcall eshell-skip-prompt-function))
+      (let ((beg (point)))
+       (end-of-line)
+       (buffer-substring beg (point))))))
+
+(defun eshell-copy-old-input ()
+  "Insert after prompt old input at point as new input to be edited."
+  (interactive)
+  (let ((input (eshell-get-old-input)))
+    (goto-char eshell-last-output-end)
+    (insert-and-inherit input)))
+
+(eshell-deftest mode run-old-command
+  "Re-run an old command"
+  (eshell-insert-command "echo alpha")
+  (goto-char eshell-last-input-start)
+  (string= (eshell-get-old-input) "echo alpha"))
+
+(defun eshell/exit ()
+  "Leave or kill the Eshell buffer, depending on `eshell-kill-on-exit'."
+  (throw 'eshell-terminal t))
+
+(defun eshell-life-is-too-much ()
+  "Kill the current buffer (or bury it).  Good-bye Eshell."
+  (interactive)
+  (if (not eshell-kill-on-exit)
+      (bury-buffer)
+    (kill-buffer (current-buffer))))
+
+(defun eshell-truncate-buffer ()
+  "Truncate the buffer to `eshell-buffer-maximum-lines'.
+This function could be on `eshell-output-filter-functions' or bound to
+a key."
+  (interactive)
+  (save-excursion
+    (goto-char eshell-last-output-end)
+    (let ((lines (count-lines 1 (point)))
+         (inhibit-read-only t))
+      (forward-line (- eshell-buffer-maximum-lines))
+      (beginning-of-line)
+      (let ((pos (point)))
+       (if (bobp)
+           (if (interactive-p)
+               (error "Buffer too short to truncate"))
+         (delete-region (point-min) (point))
+         (if (interactive-p)
+             (message "Truncated buffer from %d to %d lines (%.1fk freed)"
+                      lines eshell-buffer-maximum-lines
+                      (/ pos 1024.0))))))))
+
+(custom-add-option 'eshell-output-filter-functions
+                  'eshell-truncate-buffer)
+
+(defun send-invisible (str)
+  "Read a string without echoing.
+Then send it to the process running in the current buffer."
+  (interactive "P")                     ; Defeat snooping via C-x ESC ESC
+  (let ((str (read-passwd
+             (format "Password: "
+                     (process-name (eshell-interactive-process))))))
+    (if (stringp str)
+       (process-send-string (eshell-interactive-process)
+                            (concat str "\n"))
+      (message "Warning: text will be echoed"))))
+
+(defun eshell-watch-for-password-prompt ()
+  "Prompt in the minibuffer for password and send without echoing.
+This function uses `send-invisible' to read and send a password to the
+buffer's process if STRING contains a password prompt defined by
+`eshell-password-prompt-regexp'.
+
+This function could be in the list `eshell-output-filter-functions'."
+  (when (eshell-interactive-process)
+    (save-excursion
+      (goto-char eshell-last-output-block-begin)
+      (beginning-of-line)
+      (if (re-search-forward eshell-password-prompt-regexp
+                            eshell-last-output-end t)
+         (send-invisible nil)))))
+
+(custom-add-option 'eshell-output-filter-functions
+                  'eshell-watch-for-password-prompt)
+
+(defun eshell-handle-control-codes ()
+  "Act properly when certain control codes are seen."
+  (save-excursion
+    (let ((orig (point)))
+      (goto-char eshell-last-output-block-begin)
+      (unless (eolp)
+       (beginning-of-line))
+      (while (< (point) eshell-last-output-end)
+       (let ((char (char-after)))
+         (cond
+          ((eq char ?\r)
+           (if (< (1+ (point)) eshell-last-output-end)
+               (if (memq (char-after (1+ (point)))
+                         '(?\n ?\r))
+                   (delete-char 1)
+                 (let ((end (1+ (point))))
+                   (beginning-of-line)
+                   (delete-region (point) end)))
+             (add-text-properties (point) (1+ (point))
+                                  '(invisible t))
+             (forward-char)))
+          ((eq char ?\a)
+           (delete-char 1)
+           (beep))
+          ((eq char ?\C-h)
+           (delete-backward-char 1)
+           (delete-char 1))
+          (t
+           (forward-char))))))))
+
+(custom-add-option 'eshell-output-filter-functions
+                  'eshell-handle-control-codes)
+
+;;; Code:
+
+;;; esh-mode.el ends here
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
new file mode 100644 (file)
index 0000000..eb69e5e
--- /dev/null
@@ -0,0 +1,676 @@
+;;; esh-util --- general utilities
+
+;; Copyright (C) 1999, 2000 Free Sofware Foundation
+
+;; 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 2, 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, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+(provide 'esh-util)
+
+(eval-when-compile (require 'esh-maint))
+
+(defgroup eshell-util nil
+  "This is general utility code, meant for use by Eshell itself."
+  :tag "General utilities"
+  :group 'eshell)
+
+;;; Commentary:
+
+(require 'pp)
+
+;;; User Variables:
+
+(defcustom eshell-group-file "/etc/group"
+  "*If non-nil, the name of the group file on your system."
+  :type '(choice (const :tag "No group file" nil) file)
+  :group 'eshell-util)
+
+(defcustom eshell-passwd-file "/etc/passwd"
+  "*If non-nil, the name of the passwd file on your system."
+  :type '(choice (const :tag "No passwd file" nil) file)
+  :group 'eshell-util)
+
+(defcustom eshell-hosts-file "/etc/hosts"
+  "*The name of the /etc/hosts file."
+  :type '(choice (const :tag "No hosts file" nil) file)
+  :group 'eshell-util)
+
+(defcustom eshell-handle-errors t
+  "*If non-nil, Eshell will handle errors itself.
+Setting this to nil is offered as an aid to debugging only."
+  :type 'boolean
+  :group 'eshell-util)
+
+(defcustom eshell-private-file-modes 384 ; umask 177
+  "*The file-modes value to use for creating \"private\" files."
+  :type 'integer
+  :group 'eshell-util)
+
+(defcustom eshell-private-directory-modes 448 ; umask 077
+  "*The file-modes value to use for creating \"private\" directories."
+  :type 'integer
+  :group 'eshell-util)
+
+(defcustom eshell-tar-regexp
+  "\\.t\\(ar\\(\\.\\(gz\\|bz2\\|Z\\)\\)?\\|gz\\|a[zZ]\\|z2\\)\\'"
+  "*Regular expression used to match tar file names."
+  :type 'regexp
+  :group 'eshell-util)
+
+(defcustom eshell-convert-numeric-arguments t
+  "*If non-nil, converting arguments of numeric form to Lisp numbers.
+Numeric form is tested using the regular expression
+`eshell-number-regexp'."
+  :type 'boolean
+  :group 'eshell-util)
+
+(defcustom eshell-number-regexp "\\(0\\|-?[1-9][0-9]*\\(\\.[0-9]+\\)?\\)"
+  "*Regular expression used to match numeric arguments.
+If `eshell-convert-numeric-arguments' is non-nil, and an argument
+matches this regexp, it will be converted to a Lisp number, using the
+function `string-to-number'."
+  :type 'regexp
+  :group 'eshell-util)
+
+;;; Internal Variables:
+
+(defvar eshell-group-names nil
+  "A cache to hold the names of groups.")
+
+(defvar eshell-group-timestamp nil
+  "A timestamp of when the group file was read.")
+
+(defvar eshell-user-names nil
+  "A cache to hold the names of users.")
+
+(defvar eshell-user-timestamp nil
+  "A timestamp of when the user file was read.")
+
+(defvar eshell-host-names nil
+  "A cache the names of frequently accessed hosts.")
+
+(defvar eshell-host-timestamp nil
+  "A timestamp of when the hosts file was read.")
+
+;;; Functions:
+
+(defsubst eshell-under-xemacs-p ()
+  "Return non-nil if we are running under XEmacs."
+  (boundp 'xemacs-logo))
+
+(defsubst eshell-under-windows-p ()
+  "Return non-nil if we are running under MS-DOS/Windows."
+  (memq system-type '(ms-dos windows-nt)))
+
+(defmacro eshell-condition-case (tag form &rest handlers)
+  "Like `condition-case', but only if `eshell-pass-through-errors' is nil."
+  (if eshell-handle-errors
+      `(condition-case ,tag
+          ,form
+        ,@handlers)
+    form))
+
+(put 'eshell-condition-case 'lisp-indent-function 2)
+
+(defmacro eshell-deftest (module name label &rest forms)
+  (if (and (fboundp 'cl-compiling-file) (cl-compiling-file))
+      nil
+    (let ((fsym (intern (concat "eshell-test--" (symbol-name name)))))
+      `(eval-when-compile
+        (ignore
+         (defun ,fsym () ,label
+           (eshell-run-test (quote ,module) (quote ,fsym) ,label
+                            (quote (progn ,@forms)))))))))
+
+(put 'eshell-deftest 'lisp-indent-function 2)
+
+(defun eshell-find-delimiter
+  (open close &optional bound reverse-p backslash-p)
+  "From point, find the CLOSE delimiter corresponding to OPEN.
+The matching is bounded by BOUND.
+If REVERSE-P is non-nil, process the region backwards.
+If BACKSLASH-P is non-nil, and OPEN and CLOSE are the same character,
+then quoting is done by a backslash, rather than a doubled delimiter."
+  (save-excursion
+    (let ((depth 1)
+         (bound (or bound (point-max))))
+      (if (if reverse-p
+             (eq (char-before) close)
+           (eq (char-after) open))
+         (forward-char (if reverse-p -1 1)))
+      (while (and (> depth 0)
+                 (funcall (if reverse-p '> '<) (point) bound))
+       (let ((c (if reverse-p (char-before) (char-after))) nc)
+         (cond ((and (not reverse-p)
+                     (or (not (eq open close))
+                         backslash-p)
+                     (eq c ?\\)
+                     (setq nc (char-after (1+ (point))))
+                     (or (eq nc open) (eq nc close)))
+                (forward-char 1))
+               ((and reverse-p
+                     (or (not (eq open close))
+                         backslash-p)
+                     (or (eq c open) (eq c close))
+                     (eq (char-before (1- (point)))
+                         ?\\))
+                (forward-char -1))
+               ((eq open close)
+                (if (eq c open)
+                    (if (and (not backslash-p)
+                             (eq (if reverse-p
+                                     (char-before (1- (point)))
+                                   (char-after (1+ (point)))) open))
+                        (forward-char (if reverse-p -1 1))
+                      (setq depth (1- depth)))))
+               ((= c open)
+                (setq depth (+ depth (if reverse-p -1 1))))
+               ((= c close)
+                (setq depth (+ depth (if reverse-p 1 -1))))))
+       (forward-char (if reverse-p -1 1)))
+      (if (= depth 0)
+         (if reverse-p (point) (1- (point)))))))
+
+(defun eshell-convert (string)
+  "Convert STRING into a more native looking Lisp object."
+  (if (not (stringp string))
+      string
+    (let ((len (length string)))
+      (if (= len 0)
+         string
+       (if (eq (aref string (1- len)) ?\n)
+           (setq string (substring string 0 (1- len))))
+       (if (string-match "\n" string)
+           (split-string string "\n")
+         (if (and eshell-convert-numeric-arguments
+                  (string-match
+                   (concat "\\`\\s-*" eshell-number-regexp
+                           "\\s-*\\'") string))
+             (string-to-number string)
+           string))))))
+
+(defun eshell-sublist (l &optional n m)
+  "Return from LIST the N to M elements.
+If N or M is nil, it means the end of the list."
+  (let* ((a (copy-list l))
+        result)
+    (if (and m (consp (nthcdr m a)))
+       (setcdr (nthcdr m a) nil))
+    (if n
+       (setq a (nthcdr n a))
+      (setq n (1- (length a))
+           a (last a)))
+    a))
+
+(defun eshell-split-path (path)
+  "Split a path into multiple subparts."
+  (let ((len (length path))
+       (i 0) (li 0)
+       parts)
+    (if (and (eshell-under-windows-p)
+            (> len 2)
+            (eq (aref path 0) directory-sep-char)
+            (eq (aref path 1) directory-sep-char))
+       (setq i 2))
+    (while (< i len)
+      (if (and (eq (aref path i) directory-sep-char)
+              (not (get-text-property i 'escaped path)))
+         (setq parts (cons (if (= li i)
+                               (char-to-string directory-sep-char)
+                             (substring path li (1+ i))) parts)
+               li (1+ i)))
+      (setq i (1+ i)))
+    (if (< li i)
+       (setq parts (cons (substring path li i) parts)))
+    (if (and (eshell-under-windows-p)
+            (string-match "\\`[A-Za-z]:\\'" (car (last parts))))
+       (setcar (last parts)
+               (concat (car (last parts))
+                       (char-to-string directory-sep-char))))
+    (nreverse parts)))
+
+(defun eshell-to-flat-string (value)
+  "Make value a string.  If separated by newlines change them to spaces."
+  (let ((text (eshell-stringify value)))
+    (if (string-match "\n+\\'" text)
+       (setq text (replace-match "" t t text)))
+    (while (string-match "\n+" text)
+      (setq text (replace-match " " t t text)))
+    text))
+
+(defmacro eshell-for (for-var for-list &rest forms)
+  "Iterate through a list"
+  `(let ((list-iter ,for-list))
+     (while list-iter
+       (let ((,for-var (car list-iter)))
+        ,@forms)
+       (setq list-iter (cdr list-iter)))))
+
+(put 'eshell-for 'lisp-indent-function 2)
+
+(defun eshell-flatten-list (args)
+  "Flatten any lists within ARGS, so that there are no sublists."
+  (let ((new-list (list t)))
+    (eshell-for a args
+      (if (and (listp a)
+              (listp (cdr a)))
+         (nconc new-list (eshell-flatten-list a))
+       (nconc new-list (list a))))
+    (cdr new-list)))
+
+(defun eshell-uniqify-list (l)
+  "Remove occurring multiples in L.  You probably want to sort first."
+  (let ((m l))
+    (while m
+      (while (and (cdr m)
+                 (string= (car m)
+                          (cadr m)))
+       (setcdr m (cddr m)))
+      (setq m (cdr m))))
+  l)
+
+(defun eshell-stringify (object)
+  "Convert OBJECT into a string value."
+  (cond
+   ((stringp object) object)
+   ((and (listp object)
+        (not (eq object nil)))
+    (let ((string (pp-to-string object)))
+      (substring string 0 (1- (length string)))))
+   ((numberp object)
+    (number-to-string object))
+   (t
+    (pp-to-string object))))
+
+(defsubst eshell-stringify-list (args)
+  "Convert each element of ARGS into a string value."
+  (mapcar 'eshell-stringify args))
+
+(defsubst eshell-flatten-and-stringify (&rest args)
+  "Flatten and stringify all of the ARGS into a single string."
+  (mapconcat 'eshell-stringify (eshell-flatten-list args) " "))
+
+;; the next two are from GNUS, and really should be made part of Emacs
+;; some day
+(defsubst eshell-time-less-p (t1 t2)
+  "Say whether time T1 is less than time T2."
+  (or (< (car t1) (car t2))
+      (and (= (car t1) (car t2))
+          (< (nth 1 t1) (nth 1 t2)))))
+
+(defsubst eshell-time-to-seconds (time)
+  "Convert TIME to a floating point number."
+  (+ (* (car time) 65536.0)
+     (cadr time)
+     (/ (or (car (cdr (cdr time))) 0) 1000000.0)))
+
+(defsubst eshell-directory-files (regexp &optional directory)
+  "Return a list of files in the given DIRECTORY matching REGEXP."
+  (directory-files (or directory default-directory)
+                  directory regexp))
+
+(defun eshell-regexp-arg (prompt)
+  "Return list of regexp and prefix arg using PROMPT."
+  (let* (;; Don't clobber this.
+        (last-command last-command)
+        (regexp (read-from-minibuffer prompt nil nil nil
+                                      'minibuffer-history-search-history)))
+    (list (if (string-equal regexp "")
+             (setcar minibuffer-history-search-history
+                     (nth 1 minibuffer-history-search-history))
+           regexp)
+         (prefix-numeric-value current-prefix-arg))))
+
+(defun eshell-printable-size (filesize &optional human-readable
+                                      block-size use-colors)
+  "Return a printable FILESIZE."
+  (let ((size (float (or filesize 0))))
+    (if human-readable
+       (if (< size human-readable)
+           (if (= (round size) 0)
+               "0"
+             (if block-size
+                 "1.0k"
+               (format "%.0f" size)))
+         (setq size (/ size human-readable))
+         (if (< size human-readable)
+             (if (<= size 9.94)
+                 (format "%.1fk" size)
+               (format "%.0fk" size))
+           (setq size (/ size human-readable))
+           (if (< size human-readable)
+               (let ((str (if (<= size 9.94)
+                              (format "%.1fM" size)
+                            (format "%.0fM" size))))
+                 (if use-colors
+                     (put-text-property 0 (length str)
+                                        'face 'bold str))
+                 str)
+             (setq size (/ size human-readable))
+             (if (< size human-readable)
+                 (let ((str (if (<= size 9.94)
+                                (format "%.1fG" size)
+                              (format "%.0fG" size))))
+                   (if use-colors
+                       (put-text-property 0 (length str)
+                                          'face 'bold-italic str))
+                   str)))))
+      (if block-size
+         (setq size (/ size block-size)))
+      (format "%.0f" size))))
+
+(defun eshell-winnow-list (entries exclude &optional predicates)
+  "Pare down the ENTRIES list using the EXCLUDE regexp, and PREDICATES.
+The original list is not affected.  If the result is only one element
+long, it will be returned itself, rather than returning a one-element
+list."
+  (let ((flist (list t))
+       valid p listified)
+    (unless (listp entries)
+      (setq entries (list entries)
+           listified t))
+    (eshell-for entry entries
+      (unless (and exclude (string-match exclude entry))
+       (setq p predicates valid (null p))
+       (while p
+         (if (funcall (car p) entry)
+             (setq valid t)
+           (setq p nil valid nil))
+         (setq p (cdr p)))
+       (when valid
+         (nconc flist (list entry)))))
+    (if listified
+       (cadr flist)
+      (cdr flist))))
+
+(defsubst eshell-redisplay ()
+  "Allow Emacs to redisplay buffers."
+  ;; for some strange reason, Emacs 21 is prone to trigger an
+  ;; "args out of range" error in `sit-for', if this function
+  ;; runs while point is in the minibuffer and the users attempt
+  ;; to use completion.  Don't ask me.
+  (ignore-errors (sit-for 0 0)))
+
+(defun eshell-read-passwd-file (file)
+  "Return an alist correlating gids to group names in FILE."
+  (let (names)
+    (when (file-readable-p file)
+      (with-temp-buffer
+       (insert-file-contents file)
+       (goto-char (point-min))
+       (while (not (eobp))
+         (let* ((fields
+                 (split-string (buffer-substring
+                                (point) (progn (end-of-line)
+                                               (point))) ":")))
+           (if (and fields (nth 0 fields) (nth 2 fields))
+               (setq names (cons (cons (string-to-int (nth 2 fields))
+                                       (nth 0 fields))
+                                 names))))
+         (forward-line))))
+    names))
+
+(defun eshell-read-passwd (file result-var timestamp-var)
+  "Read the contents of /etc/passwd for user names."
+  (if (or (not (symbol-value result-var))
+         (not (symbol-value timestamp-var))
+         (eshell-time-less-p
+          (symbol-value timestamp-var)
+          (nth 5 (file-attributes file))))
+      (progn
+       (set result-var (eshell-read-passwd-file file))
+       (set timestamp-var (current-time))))
+  (symbol-value result-var))
+
+(defun eshell-read-group-names ()
+  "Read the contents of /etc/group for group names."
+  (if eshell-group-file
+      (eshell-read-passwd eshell-group-file 'eshell-group-names
+                         'eshell-group-timestamp)))
+
+(defsubst eshell-group-id (name)
+  "Return the user id for user NAME."
+  (car (rassoc name (eshell-read-group-names))))
+
+(defsubst eshell-group-name (gid)
+  "Return the group name for the given GID."
+  (cdr (assoc gid (eshell-read-group-names))))
+
+(defun eshell-read-user-names ()
+  "Read the contents of /etc/passwd for user names."
+  (if eshell-passwd-file
+      (eshell-read-passwd eshell-passwd-file 'eshell-user-names
+                         'eshell-user-timestamp)))
+
+(defsubst eshell-user-id (name)
+  "Return the user id for user NAME."
+  (car (rassoc name (eshell-read-user-names))))
+
+(defalias 'eshell-user-name 'user-login-name)
+
+(defun eshell-read-hosts-file (filename)
+  "Read in the hosts from the /etc/hosts file."
+  (let (hosts)
+    (with-temp-buffer
+      (insert-file-contents eshell-hosts-file)
+      (goto-char (point-min))
+      (while (re-search-forward
+             "^\\(\\S-+\\)\\s-+\\(\\S-+\\)\\(\\s-*\\(\\S-+\\)\\)?" nil t)
+       (if (match-string 1)
+           (add-to-list 'hosts (match-string 1)))
+       (if (match-string 2)
+           (add-to-list 'hosts (match-string 2)))
+       (if (match-string 4)
+           (add-to-list 'hosts (match-string 4)))))
+    (sort hosts 'string-lessp)))
+
+(defun eshell-read-hosts (file result-var timestamp-var)
+  "Read the contents of /etc/passwd for user names."
+  (if (or (not (symbol-value result-var))
+         (not (symbol-value timestamp-var))
+         (eshell-time-less-p
+          (symbol-value timestamp-var)
+          (nth 5 (file-attributes file))))
+      (progn
+       (set result-var (eshell-read-hosts-file file))
+       (set timestamp-var (current-time))))
+  (symbol-value result-var))
+
+(defun eshell-read-host-names ()
+  "Read the contents of /etc/hosts for host names."
+  (if eshell-hosts-file
+      (eshell-read-hosts eshell-hosts-file 'eshell-host-names
+                        'eshell-host-timestamp)))
+
+(unless (fboundp 'line-end-position)
+  (defsubst line-end-position (&optional N)
+    (save-excursion (end-of-line N) (point))))
+
+(unless (fboundp 'line-beginning-position)
+  (defsubst line-beginning-position (&optional N)
+    (save-excursion (beginning-of-line N) (point))))
+
+(unless (fboundp 'subst-char-in-string)
+  (defun subst-char-in-string (fromchar tochar string &optional inplace)
+    "Replace FROMCHAR with TOCHAR in STRING each time it occurs.
+Unless optional argument INPLACE is non-nil, return a new string."
+    (let ((i (length string))
+         (newstr (if inplace string (copy-sequence string))))
+      (while (> i 0)
+       (setq i (1- i))
+       (if (eq (aref newstr i) fromchar)
+           (aset newstr i tochar)))
+      newstr)))
+
+(defsubst eshell-copy-environment ()
+  "Return an unrelated copy of `process-environment'."
+  (mapcar 'concat process-environment))
+
+(defun eshell-subgroups (groupsym)
+  "Return all of the subgroups of GROUPSYM."
+  (let ((subgroups (get groupsym 'custom-group))
+       (subg (list t)))
+    (while subgroups
+      (if (eq (cadr (car subgroups)) 'custom-group)
+         (nconc subg (list (caar subgroups))))
+      (setq subgroups (cdr subgroups)))
+    (cdr subg)))
+
+(defmacro eshell-with-file-modes (modes &rest forms)
+  "Evaluate, with file-modes set to MODES, the given FORMS."
+  `(let ((modes (default-file-modes)))
+     (set-default-file-modes ,modes)
+     (unwind-protect
+        (progn ,@forms)
+       (set-default-file-modes modes))))
+
+(defmacro eshell-with-private-file-modes (&rest forms)
+  "Evaluate FORMS with private file modes set."
+  `(eshell-with-file-modes ,eshell-private-file-modes ,@forms))
+
+(defsubst eshell-make-private-directory (dir &optional parents)
+  "Make DIR with file-modes set to `eshell-private-directory-modes'."
+  (eshell-with-file-modes eshell-private-directory-modes
+                         (make-directory dir parents)))
+
+(defsubst eshell-substring (string sublen)
+  "Return the beginning of STRING, up to SUBLEN bytes."
+  (if string
+      (if (> (length string) sublen)
+         (substring string 0 sublen)
+       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))
+      (mapcar
+       (function
+       (lambda (file)
+         (cons file (file-attributes file))))
+       (directory-files dir full match nosort)))))
+
+(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)))
+    (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)))
+       (mapcar
+        (function
+         (lambda (file)
+           (cons file (funcall fah 'file-attributes file))))
+        files)))))
+
+(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."
+  (if (consp list)
+      (let ((res nil))
+       (while (consp list) (push (pop list) res))
+       (prog1 (nreverse res) (setcdr res list)))
+    (car list)))
+
+(defun eshell-copy-tree (tree &optional vecp)
+  "Make a copy of TREE.
+If TREE is a cons cell, this recursively copies both its car and its cdr.
+Contrast to copy-sequence, which copies only along the cdrs.  With second
+argument VECP, this copies vectors as well as conses."
+  (if (consp tree)
+      (let ((p (setq tree (eshell-copy-list tree))))
+       (while (consp p)
+         (if (or (consp (car p)) (and vecp (vectorp (car p))))
+             (setcar p (eshell-copy-tree (car p) vecp)))
+         (or (listp (cdr p)) (setcdr p (eshell-copy-tree (cdr p) vecp)))
+         (cl-pop p)))
+    (if (and vecp (vectorp tree))
+       (let ((i (length (setq tree (copy-sequence tree)))))
+         (while (>= (setq i (1- i)) 0)
+           (aset tree i (eshell-copy-tree (aref tree i) vecp))))))
+  tree)
+
+; (defun eshell-copy-file
+;   (file newname &optional ok-if-already-exists keep-date)
+;   "Copy FILE to NEWNAME.  See docs for `copy-file'."
+;   (let (copied)
+;     (if (string-match "\\`\\([^:]+\\):\\(.*\\)" file)
+;      (let ((front (match-string 1 file))
+;            (back (match-string 2 file))
+;            buffer)
+;        (if (and front (string-match eshell-tar-regexp front)
+;                   (setq buffer (find-file-noselect front)))
+;          (with-current-buffer buffer
+;            (goto-char (point-min))
+;            (if (re-search-forward (concat " " (regexp-quote back)
+;                                           "$") nil t)
+;                (progn
+;                  (tar-copy (if (file-directory-p newname)
+;                                (expand-file-name
+;                                 (file-name-nondirectory back) newname)
+;                              newname))
+;                  (setq copied t))
+;              (error "%s not found in tar file %s" back front))))))
+;     (unless copied
+;       (copy-file file newname ok-if-already-exists keep-date))))
+
+; (defun eshell-file-attributes (filename)
+;   "Return a list of attributes of file FILENAME.
+; See the documentation for `file-attributes'."
+;   (let (result)
+;     (when (string-match "\\`\\([^:]+\\):\\(.*\\)\\'" filename)
+;       (let ((front (match-string 1 filename))
+;          (back (match-string 2 filename))
+;          buffer)
+;      (when (and front (string-match eshell-tar-regexp front)
+;                 (setq buffer (find-file-noselect front)))
+;        (with-current-buffer buffer
+;          (goto-char (point-min))
+;          (when (re-search-forward (concat " " (regexp-quote back)
+;                                           "\\s-*$") nil t)
+;            (let* ((descrip (tar-current-descriptor))
+;                   (tokens (tar-desc-tokens descrip)))
+;              (setq result
+;                    (list
+;                     (cond
+;                      ((eq (tar-header-link-type tokens) 5)
+;                       t)
+;                      ((eq (tar-header-link-type tokens) t)
+;                       (tar-header-link-name tokens)))
+;                     1
+;                     (tar-header-uid tokens)
+;                     (tar-header-gid tokens)
+;                     (tar-header-date tokens)
+;                     (tar-header-date tokens)
+;                     (tar-header-date tokens)
+;                     (tar-header-size tokens)
+;                     (concat
+;                      (cond
+;                       ((eq (tar-header-link-type tokens) 5) "d")
+;                       ((eq (tar-header-link-type tokens) t) "l")
+;                       (t "-"))
+;                      (tar-grind-file-mode (tar-header-mode tokens)
+;                                           (make-string 9 ? ) 0))
+;                     nil nil nil))))))))
+;     (or result
+;      (file-attributes filename))))
+
+;;; Code:
+
+;;; esh-util.el ends here