--- /dev/null
+;;; 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
--- /dev/null
+;;; 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