view lisp/eshell/esh-mode.el @ 91817:f0b22bbb77fb

;;; smtpmail.el --- simple SMTP protocol (RFC 821) for sending mail ;; Copyright (C) 1995, 1996, 2001, 2002, 2003, 2004, 2005, ;; 2006, 2007, 2008 Free Software Foundation, Inc. ;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp> ;; Maintainer: Simon Josefsson <simon@josefsson.org> ;; w32 Maintainer: Brian D. Carlstrom <bdc@ai.mit.edu> ;; ESMTP support: Simon Leinen <simon@switch.ch> ;; Hacked by Mike Taylor, 11th October 1999 to add support for ;; automatically appending a domain to RCPT TO: addresses. ;; AUTH=LOGIN support: Stephen Cranefield <scranefield@infoscience.otago.ac.nz> ;; Keywords: mail ;; 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 3, 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., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Commentary: ;; Send Mail to smtp host from smtpmail temp buffer. ;; Please add these lines in your .emacs(_emacs) or use customize. ;; ;;(setq send-mail-function 'smtpmail-send-it) ; if you use `mail' ;;(setq message-send-mail-function 'smtpmail-send-it) ; if you use message/Gnus ;;(setq smtpmail-default-smtp-server "YOUR SMTP HOST") ;;(setq smtpmail-local-domain "YOUR DOMAIN NAME") ;;(setq smtpmail-sendto-domain "YOUR DOMAIN NAME") ;;(setq smtpmail-debug-info t) ; only to debug problems ;;(setq smtpmail-auth-credentials ; or use ~/.authinfo ;; '(("YOUR SMTP HOST" 25 "username" "password"))) ;;(setq smtpmail-starttls-credentials ;; '(("YOUR SMTP HOST" 25 "~/.my_smtp_tls.key" "~/.my_smtp_tls.cert"))) ;; Where the 25 equals the value of `smtpmail-smtp-service', it can be an ;; integer or a string, just as long as they match (eq). ;; To queue mail, set smtpmail-queue-mail to t and use ;; smtpmail-send-queued-mail to send. ;; Modified by Stephen Cranefield <scranefield@infoscience.otago.ac.nz>, ;; 22/6/99, to support SMTP Authentication by the AUTH=LOGIN mechanism. ;; See http://help.netscape.com/products/server/messaging/3x/info/smtpauth.html ;; Rewritten by Simon Josefsson to use same credential variable as AUTH ;; support below. ;; Modified by Simon Josefsson <jas@pdc.kth.se>, 22/2/99, to support SMTP ;; Authentication by the AUTH mechanism. ;; See http://www.ietf.org/rfc/rfc2554.txt ;; Modified by Simon Josefsson <simon@josefsson.org>, 2000-10-07, to support ;; STARTTLS. Requires external program ;; ftp://ftp.opaopa.org/pub/elisp/starttls-*.tar.gz. ;; See http://www.ietf.org/rfc/rfc2246.txt, http://www.ietf.org/rfc/rfc2487.txt ;;; Code: (require 'sendmail) (autoload 'starttls-open-stream "starttls") (autoload 'starttls-negotiate "starttls") (autoload 'mail-strip-quoted-names "mail-utils") (autoload 'message-make-date "message") (autoload 'message-make-message-id "message") (autoload 'rfc2104-hash "rfc2104") (autoload 'netrc-parse "netrc") (autoload 'netrc-machine "netrc") (autoload 'netrc-get "netrc") ;;; (defgroup smtpmail nil "SMTP protocol for sending mail." :group 'mail) (defcustom smtpmail-default-smtp-server nil "*Specify default SMTP server. This only has effect if you specify it before loading the smtpmail library." :type '(choice (const nil) string) :group 'smtpmail) (defcustom smtpmail-smtp-server (or (getenv "SMTPSERVER") smtpmail-default-smtp-server) "*The name of the host running SMTP server." :type '(choice (const nil) string) :group 'smtpmail) (defcustom smtpmail-smtp-service 25 "*SMTP service port number. The default value would be \"smtp\" or 25." :type '(choice (integer :tag "Port") (string :tag "Service")) :group 'smtpmail) (defcustom smtpmail-local-domain nil "*Local domain name without a host name. If the function `system-name' returns the full internet address, don't define this value." :type '(choice (const nil) string) :group 'smtpmail) (defcustom smtpmail-sendto-domain nil "*Local domain name without a host name. This is appended (with an @-sign) to any specified recipients which do not include an @-sign, so that each RCPT TO address is fully qualified. \(Some configurations of sendmail require this.) Don't bother to set this unless you have get an error like: Sending failed; SMTP protocol error when sending mail, and the *trace of SMTP session to <somewhere>* buffer includes an exchange like: RCPT TO: <someone> 501 <someone>: recipient address must contain a domain " :type '(choice (const nil) string) :group 'smtpmail) (defcustom smtpmail-debug-info nil "Whether to print info in buffer *trace of SMTP session to <somewhere>*. See also `smtpmail-debug-verb' which determines if the SMTP protocol should be verbose as well." :type 'boolean :group 'smtpmail) (defcustom smtpmail-debug-verb nil "Whether this library sends the SMTP VERB command or not. The commands enables verbose information from the SMTP server." :type 'boolean :group 'smtpmail) (defcustom smtpmail-code-conv-from nil ;; *junet* "*smtpmail code convert from this code to *internal*..for tiny-mime.." :type 'boolean :group 'smtpmail) (defcustom smtpmail-queue-mail nil "*If set, mail is queued; otherwise it is sent immediately. If queued, it is stored in the directory `smtpmail-queue-dir' and sent with `smtpmail-send-queued-mail'." :type 'boolean :group 'smtpmail) (defcustom smtpmail-queue-dir "~/Mail/queued-mail/" "*Directory where `smtpmail.el' stores queued mail." :type 'directory :group 'smtpmail) (defcustom smtpmail-auth-credentials "~/.authinfo" "Specify username and password for servers, directly or via .netrc file. This variable can either be a filename pointing to a file in netrc(5) format, or list of four-element lists that contain, in order, `servername' (a string), `port' (an integer), `user' (a string) and `password' (a string, or nil to query the user when needed). If you need to enter a `realm' too, add it to the user string, so that it looks like `user@realm'." :type '(choice file (repeat (list (string :tag "Server") (integer :tag "Port") (string :tag "Username") (choice (const :tag "Query when needed" nil) (string :tag "Password"))))) :version "22.1" :group 'smtpmail) (defcustom smtpmail-starttls-credentials '(("" 25 "" "")) "Specify STARTTLS keys and certificates for servers. This is a list of four-element list with `servername' (a string), `port' (an integer), `key' (a filename) and `certificate' (a filename). If you do not have a certificate/key pair, leave the `key' and `certificate' fields as `nil'. A key/certificate pair is only needed if you want to use X.509 client authenticated connections." :type '(repeat (list (string :tag "Server") (integer :tag "Port") (file :tag "Key") (file :tag "Certificate"))) :version "21.1" :group 'smtpmail) (defcustom smtpmail-warn-about-unknown-extensions nil "*If set, print warnings about unknown SMTP extensions. This is mainly useful for development purposes, to learn about new SMTP extensions that might be useful to support." :type 'boolean :version "21.1" :group 'smtpmail) (defvar smtpmail-queue-index-file "index" "File name of queued mail index. This is relative to `smtpmail-queue-dir'.") (defvar smtpmail-address-buffer) (defvar smtpmail-recipient-address-list) (defvar smtpmail-queue-counter 0) ;; Buffer-local variable. (defvar smtpmail-read-point) (defvar smtpmail-queue-index (concat smtpmail-queue-dir smtpmail-queue-index-file)) (defconst smtpmail-auth-supported '(cram-md5 plain login) "List of supported SMTP AUTH mechanisms.") ;;; ;;; ;;; (defvar smtpmail-mail-address nil "Value to use for envelope-from address for mail from ambient buffer.") ;;;###autoload (defun smtpmail-send-it () (let ((errbuf (if mail-interactive (generate-new-buffer " smtpmail errors") 0)) (tembuf (generate-new-buffer " smtpmail temp")) (case-fold-search nil) delimline (mailbuf (current-buffer)) ;; Examine this variable now, so that ;; local binding in the mail buffer will take effect. (smtpmail-mail-address (or (and mail-specify-envelope-from (mail-envelope-from)) user-mail-address)) (smtpmail-code-conv-from (if enable-multibyte-characters (let ((sendmail-coding-system smtpmail-code-conv-from)) (select-message-coding-system))))) (unwind-protect (save-excursion (set-buffer tembuf) (erase-buffer) ;; Use the same buffer-file-coding-system as in the mail ;; buffer, otherwise any write-region invocations (e.g., in ;; mail-do-fcc below) will annoy with asking for a suitable ;; encoding. (set-buffer-file-coding-system smtpmail-code-conv-from nil t) (insert-buffer-substring mailbuf) (goto-char (point-max)) ;; require one newline at the end. (or (= (preceding-char) ?\n) (insert ?\n)) ;; Change header-delimiter to be what sendmail expects. (mail-sendmail-undelimit-header) (setq delimline (point-marker)) ;; (sendmail-synch-aliases) (if mail-aliases (expand-mail-aliases (point-min) delimline)) (goto-char (point-min)) ;; ignore any blank lines in the header (while (and (re-search-forward "\n\n\n*" delimline t) (< (point) delimline)) (replace-match "\n")) (let ((case-fold-search t)) ;; We used to process Resent-... headers here, ;; but it was not done properly, and the job ;; is done correctly in smtpmail-deduce-address-list. ;; Don't send out a blank subject line (goto-char (point-min)) (if (re-search-forward "^Subject:\\([ \t]*\n\\)+\\b" delimline t) (replace-match "") ;; This one matches a Subject just before the header delimiter. (if (and (re-search-forward "^Subject:\\([ \t]*\n\\)+" delimline t) (= (match-end 0) delimline)) (replace-match ""))) ;; Put the "From:" field in unless for some odd reason ;; they put one in themselves. (goto-char (point-min)) (if (not (re-search-forward "^From:" delimline t)) (let* ((login smtpmail-mail-address) (fullname (user-full-name))) (cond ((eq mail-from-style 'angles) (insert "From: " fullname) (let ((fullname-start (+ (point-min) 6)) (fullname-end (point-marker))) (goto-char fullname-start) ;; Look for a character that cannot appear unquoted ;; according to RFC 822. (if (re-search-forward "[^- !#-'*+/-9=?A-Z^-~]" fullname-end 1) (progn ;; Quote fullname, escaping specials. (goto-char fullname-start) (insert "\"") (while (re-search-forward "[\"\\]" fullname-end 1) (replace-match "\\\\\\&" t)) (insert "\"")))) (insert " <" login ">\n")) ((eq mail-from-style 'parens) (insert "From: " login " (") (let ((fullname-start (point))) (insert fullname) (let ((fullname-end (point-marker))) (goto-char fullname-start) ;; RFC 822 says \ and nonmatching parentheses ;; must be escaped in comments. ;; Escape every instance of ()\ ... (while (re-search-forward "[()\\]" fullname-end 1) (replace-match "\\\\\\&" t)) ;; ... then undo escaping of matching parentheses, ;; including matching nested parentheses. (goto-char fullname-start) (while (re-search-forward "\\(\\=\\|[^\\]\\(\\\\\\\\\\)*\\)\\\\(\\(\\([^\\]\\|\\\\\\\\\\)*\\)\\\\)" fullname-end 1) (replace-match "\\1(\\3)" t) (goto-char fullname-start)))) (insert ")\n")) ((null mail-from-style) (insert "From: " login "\n"))))) ;; Insert a `Message-Id:' field if there isn't one yet. (goto-char (point-min)) (unless (re-search-forward "^Message-Id:" delimline t) (insert "Message-Id: " (message-make-message-id) "\n")) ;; Insert a `Date:' field if there isn't one yet. (goto-char (point-min)) (unless (re-search-forward "^Date:" delimline t) (insert "Date: " (message-make-date) "\n")) ;; Possibly add a MIME header for the current coding system (let (charset) (goto-char (point-min)) (and (eq mail-send-nonascii 'mime) (not (re-search-forward "^MIME-version:" delimline t)) (progn (skip-chars-forward "\0-\177") (/= (point) (point-max))) smtpmail-code-conv-from (setq charset (coding-system-get smtpmail-code-conv-from 'mime-charset)) (goto-char delimline) (insert "MIME-version: 1.0\n" "Content-type: text/plain; charset=" (symbol-name charset) "\nContent-Transfer-Encoding: 8bit\n"))) ;; Insert an extra newline if we need it to work around ;; Sun's bug that swallows newlines. (goto-char (1+ delimline)) (if (eval mail-mailer-swallows-blank-line) (newline)) ;; Find and handle any FCC fields. (goto-char (point-min)) (if (re-search-forward "^FCC:" delimline t) ;; Force mail-do-fcc to use the encoding of the mail ;; buffer to encode outgoing messages on FCC files. (let ((coding-system-for-write smtpmail-code-conv-from)) (mail-do-fcc delimline))) (if mail-interactive (with-current-buffer errbuf (erase-buffer)))) ;; ;; ;; (setq smtpmail-address-buffer (generate-new-buffer "*smtp-mail*")) (setq smtpmail-recipient-address-list (smtpmail-deduce-address-list tembuf (point-min) delimline)) (kill-buffer smtpmail-address-buffer) (smtpmail-do-bcc delimline) ; Send or queue (if (not smtpmail-queue-mail) (if (not (null smtpmail-recipient-address-list)) (if (not (smtpmail-via-smtp smtpmail-recipient-address-list tembuf)) (error "Sending failed; SMTP protocol error")) (error "Sending failed; no recipients")) (let* ((file-data (expand-file-name (format "%s_%i" (format-time-string "%Y-%m-%d_%H:%M:%S") (setq smtpmail-queue-counter (1+ smtpmail-queue-counter))) smtpmail-queue-dir)) (file-data (convert-standard-filename file-data)) (file-elisp (concat file-data ".el")) (buffer-data (create-file-buffer file-data)) (buffer-elisp (create-file-buffer file-elisp)) (buffer-scratch "*queue-mail*")) (unless (file-exists-p smtpmail-queue-dir) (make-directory smtpmail-queue-dir t)) (with-current-buffer buffer-data (erase-buffer) (set-buffer-file-coding-system smtpmail-code-conv-from nil t) (insert-buffer-substring tembuf) (write-file file-data) (set-buffer buffer-elisp) (erase-buffer) (insert (concat "(setq smtpmail-recipient-address-list '" (prin1-to-string smtpmail-recipient-address-list) ")\n")) (write-file file-elisp) (set-buffer (generate-new-buffer buffer-scratch)) (insert (concat file-data "\n")) (append-to-file (point-min) (point-max) smtpmail-queue-index) ) (kill-buffer buffer-scratch) (kill-buffer buffer-data) (kill-buffer buffer-elisp)))) (kill-buffer tembuf) (if (bufferp errbuf) (kill-buffer errbuf))))) ;;;###autoload (defun smtpmail-send-queued-mail () "Send mail that was queued as a result of setting `smtpmail-queue-mail'." (interactive) (with-temp-buffer ;;; Get index, get first mail, send it, update index, get second ;;; mail, send it, etc... (let ((file-msg "")) (insert-file-contents smtpmail-queue-index) (goto-char (point-min)) (while (not (eobp)) (setq file-msg (buffer-substring (point) (line-end-position))) (load file-msg) ;; Insert the message literally: it is already encoded as per ;; the MIME headers, and code conversions might guess the ;; encoding wrongly. (with-temp-buffer (let ((coding-system-for-read 'no-conversion)) (insert-file-contents file-msg)) (let ((smtpmail-mail-address (or (and mail-specify-envelope-from (mail-envelope-from)) user-mail-address))) (if (not (null smtpmail-recipient-address-list)) (if (not (smtpmail-via-smtp smtpmail-recipient-address-list (current-buffer))) (error "Sending failed; SMTP protocol error")) (error "Sending failed; no recipients")))) (delete-file file-msg) (delete-file (concat file-msg ".el")) (delete-region (point-at-bol) (point-at-bol 2))) (write-region (point-min) (point-max) smtpmail-queue-index)))) ;(defun smtpmail-via-smtp (host,port,sender,destination,smtpmail-text-buffer) (defun smtpmail-fqdn () (if smtpmail-local-domain (concat (system-name) "." smtpmail-local-domain) (system-name))) (defsubst smtpmail-cred-server (cred) (nth 0 cred)) (defsubst smtpmail-cred-port (cred) (nth 1 cred)) (defsubst smtpmail-cred-key (cred) (nth 2 cred)) (defsubst smtpmail-cred-user (cred) (nth 2 cred)) (defsubst smtpmail-cred-cert (cred) (nth 3 cred)) (defsubst smtpmail-cred-passwd (cred) (nth 3 cred)) (defun smtpmail-find-credentials (cred server port) (catch 'done (let ((l cred) el) (while (setq el (pop l)) (when (and (equal server (smtpmail-cred-server el)) (equal port (smtpmail-cred-port el))) (throw 'done el)))))) (defun smtpmail-maybe-append-domain (recipient) (if (or (not smtpmail-sendto-domain) (string-match "@" recipient)) recipient (concat recipient "@" smtpmail-sendto-domain))) (defun smtpmail-intersection (list1 list2) (let ((result nil)) (dolist (el2 list2) (when (memq el2 list1) (push el2 result))) (nreverse result))) (defvar starttls-extra-args) (defvar starttls-extra-arguments) (defun smtpmail-open-stream (process-buffer host port) (let ((cred (smtpmail-find-credentials smtpmail-starttls-credentials host port))) (if (null (and cred (condition-case () (with-no-warnings (require 'starttls) (call-process (if starttls-use-gnutls starttls-gnutls-program starttls-program))) (error nil)))) ;; The normal case. (open-network-stream "SMTP" process-buffer host port) (let* ((cred-key (smtpmail-cred-key cred)) (cred-cert (smtpmail-cred-cert cred)) (starttls-extra-args (append starttls-extra-args (when (and (stringp cred-key) (stringp cred-cert) (file-regular-p (setq cred-key (expand-file-name cred-key))) (file-regular-p (setq cred-cert (expand-file-name cred-cert)))) (list "--key-file" cred-key "--cert-file" cred-cert)))) (starttls-extra-arguments (append starttls-extra-arguments (when (and (stringp cred-key) (stringp cred-cert) (file-regular-p (setq cred-key (expand-file-name cred-key))) (file-regular-p (setq cred-cert (expand-file-name cred-cert)))) (list "--x509keyfile" cred-key "--x509certfile" cred-cert))))) (starttls-open-stream "SMTP" process-buffer host port))))) (defun smtpmail-try-auth-methods (process supported-extensions host port) (let* ((mechs (cdr-safe (assoc 'auth supported-extensions))) (mech (car (smtpmail-intersection smtpmail-auth-supported mechs))) (cred (if (stringp smtpmail-auth-credentials) (let* ((netrc (netrc-parse smtpmail-auth-credentials)) (port-name (format "%s" (or port "smtp"))) (hostentry (netrc-machine netrc host port-name port-name))) (when hostentry (list host port (netrc-get hostentry "login") (netrc-get hostentry "password")))) (smtpmail-find-credentials smtpmail-auth-credentials host port))) (passwd (when cred (or (smtpmail-cred-passwd cred) (read-passwd (format "SMTP password for %s:%s: " (smtpmail-cred-server cred) (smtpmail-cred-port cred)))))) ret) (when (and cred mech) (cond ((eq mech 'cram-md5) (smtpmail-send-command process (upcase (format "AUTH %s" mech))) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil)) (when (eq (car ret) 334) (let* ((challenge (substring (cadr ret) 4)) (decoded (base64-decode-string challenge)) (hash (rfc2104-hash 'md5 64 16 passwd decoded)) (response (concat (smtpmail-cred-user cred) " " hash)) ;; Osamu Yamane <yamane@green.ocn.ne.jp>: ;; SMTP auth fails because the SMTP server identifies ;; only the first part of the string (delimited by ;; new line characters) as a response from the ;; client, and the rest as distinct commands. ;; In my case, the response string is 80 characters ;; long. Without the no-line-break option for ;; base64-encode-sting, only the first 76 characters ;; are taken as a response to the server, and the ;; authentication fails. (encoded (base64-encode-string response t))) (smtpmail-send-command process (format "%s" encoded)) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil))))) ((eq mech 'login) (smtpmail-send-command process "AUTH LOGIN") (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil)) (smtpmail-send-command process (base64-encode-string (smtpmail-cred-user cred) t)) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil)) (smtpmail-send-command process (base64-encode-string passwd t)) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil))) ((eq mech 'plain) ;; We used to send an empty initial request, and wait for an ;; empty response, and then send the password, but this ;; violate a SHOULD in RFC 2222 paragraph 5.1. Note that this ;; is not sent if the server did not advertise AUTH PLAIN in ;; the EHLO response. See RFC 2554 for more info. (smtpmail-send-command process (concat "AUTH PLAIN " (base64-encode-string (concat "\0" (smtpmail-cred-user cred) "\0" passwd) t))) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (not (equal (car ret) 235))) (throw 'done nil))) (t (error "Mechanism %s not implemented" mech))) ;; Remember the password. (when (and (not (stringp smtpmail-auth-credentials)) (null (smtpmail-cred-passwd cred))) (setcar (cdr (cdr (cdr cred))) passwd))))) (defun smtpmail-via-smtp (recipient smtpmail-text-buffer) (let ((process nil) (host (or smtpmail-smtp-server (error "`smtpmail-smtp-server' not defined"))) (port smtpmail-smtp-service) ;; smtpmail-mail-address should be set to the appropriate ;; buffer-local value by the caller, but in case not: (envelope-from (or smtpmail-mail-address (and mail-specify-envelope-from (mail-envelope-from)) user-mail-address)) response-code greeting process-buffer (supported-extensions '())) (unwind-protect (catch 'done ;; get or create the trace buffer (setq process-buffer (get-buffer-create (format "*trace of SMTP session to %s*" host))) ;; clear the trace buffer of old output (with-current-buffer process-buffer (setq buffer-undo-list t) (erase-buffer)) ;; open the connection to the server (setq process (smtpmail-open-stream process-buffer host port)) (and (null process) (throw 'done nil)) ;; set the send-filter (set-process-filter process 'smtpmail-process-filter) (with-current-buffer process-buffer (set-buffer-process-coding-system 'raw-text-unix 'raw-text-unix) (make-local-variable 'smtpmail-read-point) (setq smtpmail-read-point (point-min)) (if (or (null (car (setq greeting (smtpmail-read-response process)))) (not (integerp (car greeting))) (>= (car greeting) 400)) (throw 'done nil) ) (let ((do-ehlo t) (do-starttls t)) (while do-ehlo ;; EHLO (smtpmail-send-command process (format "EHLO %s" (smtpmail-fqdn))) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (progn ;; HELO (smtpmail-send-command process (format "HELO %s" (smtpmail-fqdn))) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil))) (dolist (line (cdr (cdr response-code))) (let ((name (with-case-table ascii-case-table (mapcar (lambda (s) (intern (downcase s))) (split-string (substring line 4) "[ ]"))))) (and (eq (length name) 1) (setq name (car name))) (and name (cond ((memq (if (consp name) (car name) name) '(verb xvrb 8bitmime onex xone expn size dsn etrn enhancedstatuscodes help xusr auth=login auth starttls)) (setq supported-extensions (cons name supported-extensions))) (smtpmail-warn-about-unknown-extensions (message "Unknown extension %s" name))))))) (if (and do-starttls (smtpmail-find-credentials smtpmail-starttls-credentials host port) (member 'starttls supported-extensions) (numberp (process-id process))) (progn (smtpmail-send-command process (format "STARTTLS")) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil)) (starttls-negotiate process) (setq do-starttls nil)) (setq do-ehlo nil)))) (smtpmail-try-auth-methods process supported-extensions host port) (if (or (member 'onex supported-extensions) (member 'xone supported-extensions)) (progn (smtpmail-send-command process (format "ONEX")) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil)))) (if (and smtpmail-debug-verb (or (member 'verb supported-extensions) (member 'xvrb supported-extensions))) (progn (smtpmail-send-command process (format "VERB")) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil)))) (if (member 'xusr supported-extensions) (progn (smtpmail-send-command process (format "XUSR")) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil)))) ;; MAIL FROM:<sender> (let ((size-part (if (or (member 'size supported-extensions) (assoc 'size supported-extensions)) (format " SIZE=%d" (with-current-buffer smtpmail-text-buffer ;; size estimate: (+ (- (point-max) (point-min)) ;; Add one byte for each change-of-line ;; because of CR-LF representation: (count-lines (point-min) (point-max))))) "")) (body-part (if (member '8bitmime supported-extensions) ;; FIXME: ;; Code should be added here that transforms ;; the contents of the message buffer into ;; something the receiving SMTP can handle. ;; For a receiver that supports 8BITMIME, this ;; may mean converting BINARY to BASE64, or ;; adding Content-Transfer-Encoding and the ;; other MIME headers. The code should also ;; return an indication of what encoding the ;; message buffer is now, i.e. ASCII or ;; 8BITMIME. (if nil " BODY=8BITMIME" "") ""))) ; (smtpmail-send-command process (format "MAIL FROM:%s@%s" (user-login-name) (smtpmail-fqdn))) (smtpmail-send-command process (format "MAIL FROM:<%s>%s%s" envelope-from size-part body-part)) (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil) )) ;; RCPT TO:<recipient> (let ((n 0)) (while (not (null (nth n recipient))) (smtpmail-send-command process (format "RCPT TO:<%s>" (smtpmail-maybe-append-domain (nth n recipient)))) (setq n (1+ n)) (setq response-code (smtpmail-read-response process)) (if (or (null (car response-code)) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil) ) )) ;; DATA (smtpmail-send-command process "DATA") (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil) ) ;; Mail contents (smtpmail-send-data process smtpmail-text-buffer) ;;DATA end "." (smtpmail-send-command process ".") (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil) ) ;;QUIT ; (smtpmail-send-command process "QUIT") ; (and (null (car (smtpmail-read-response process))) ; (throw 'done nil)) t )) (if process (with-current-buffer (process-buffer process) (smtpmail-send-command process "QUIT") (smtpmail-read-response process) ; (if (or (null (car (setq response-code (smtpmail-read-response process)))) ; (not (integerp (car response-code))) ; (>= (car response-code) 400)) ; (throw 'done nil) ; ) (delete-process process) (unless smtpmail-debug-info (kill-buffer process-buffer))))))) (defun smtpmail-process-filter (process output) (with-current-buffer (process-buffer process) (goto-char (point-max)) (insert output))) (defun smtpmail-read-response (process) (let ((case-fold-search nil) (response-strings nil) (response-continue t) (return-value '(nil ())) match-end) (catch 'done (while response-continue (goto-char smtpmail-read-point) (while (not (search-forward "\r\n" nil t)) (unless (memq (process-status process) '(open run)) (throw 'done nil)) (accept-process-output process) (goto-char smtpmail-read-point)) (setq match-end (point)) (setq response-strings (cons (buffer-substring smtpmail-read-point (- match-end 2)) response-strings)) (goto-char smtpmail-read-point) (if (looking-at "[0-9]+ ") (let ((begin (match-beginning 0)) (end (match-end 0))) (if smtpmail-debug-info (message "%s" (car response-strings))) (setq smtpmail-read-point match-end) ;; ignore lines that start with "0" (if (looking-at "0[0-9]+ ") nil (setq response-continue nil) (setq return-value (cons (string-to-number (buffer-substring begin end)) (nreverse response-strings))))) (if (looking-at "[0-9]+-") (progn (if smtpmail-debug-info (message "%s" (car response-strings))) (setq smtpmail-read-point match-end) (setq response-continue t)) (progn (setq smtpmail-read-point match-end) (setq response-continue nil) (setq return-value (cons nil (nreverse response-strings))))))) (setq smtpmail-read-point match-end)) return-value)) (defun smtpmail-send-command (process command) (goto-char (point-max)) (if (= (aref command 0) ?P) (insert "PASS <omitted>\r\n") (insert command "\r\n")) (setq smtpmail-read-point (point)) (process-send-string process command) (process-send-string process "\r\n")) (defun smtpmail-send-data-1 (process data) (goto-char (point-max)) (if (and (multibyte-string-p data) smtpmail-code-conv-from) (setq data (string-as-multibyte (encode-coding-string data smtpmail-code-conv-from)))) (if smtpmail-debug-info (insert data "\r\n")) (setq smtpmail-read-point (point)) ;; Escape "." at start of a line (if (eq (string-to-char data) ?.) (process-send-string process ".")) (process-send-string process data) (process-send-string process "\r\n") ) (defun smtpmail-send-data (process buffer) (let ((data-continue t) sending-data) (with-current-buffer buffer (goto-char (point-min))) (while data-continue (with-current-buffer buffer (setq sending-data (buffer-substring (point-at-bol) (point-at-eol))) (end-of-line 2) (setq data-continue (not (eobp)))) (smtpmail-send-data-1 process sending-data)))) (defun smtpmail-deduce-address-list (smtpmail-text-buffer header-start header-end) "Get address list suitable for smtp RCPT TO: <address>." (unwind-protect (with-current-buffer smtpmail-address-buffer (erase-buffer) (let ((case-fold-search t) (simple-address-list "") this-line this-line-end addr-regexp) (insert-buffer-substring smtpmail-text-buffer header-start header-end) (goto-char (point-min)) ;; RESENT-* fields should stop processing of regular fields. (save-excursion (setq addr-regexp (if (re-search-forward "^Resent-\\(to\\|cc\\|bcc\\):" header-end t) "^Resent-\\(to\\|cc\\|bcc\\):" "^\\(To:\\|Cc:\\|Bcc:\\)"))) (while (re-search-forward addr-regexp header-end t) (replace-match "") (setq this-line (match-beginning 0)) (forward-line 1) ;; get any continuation lines (while (and (looking-at "^[ \t]+") (< (point) header-end)) (forward-line 1)) (setq this-line-end (point-marker)) (setq simple-address-list (concat simple-address-list " " (mail-strip-quoted-names (buffer-substring this-line this-line-end)))) ) (erase-buffer) (insert " " simple-address-list "\n") (subst-char-in-region (point-min) (point-max) 10 ? t);; newline --> blank (subst-char-in-region (point-min) (point-max) ?, ? t);; comma --> blank (subst-char-in-region (point-min) (point-max) 9 ? t);; tab --> blank (goto-char (point-min)) ;; tidyness in case hook is not robust when it looks at this (while (re-search-forward "[ \t]+" header-end t) (replace-match " ")) (goto-char (point-min)) (let (recipient-address-list) (while (re-search-forward " \\([^ ]+\\) " (point-max) t) (backward-char 1) (setq recipient-address-list (cons (buffer-substring (match-beginning 1) (match-end 1)) recipient-address-list)) ) (setq smtpmail-recipient-address-list recipient-address-list)) ) ) ) ) (defun smtpmail-do-bcc (header-end) "Delete [Resent-]BCC: and their continuation lines from the header area. There may be multiple BCC: lines, and each may have arbitrarily many continuation lines." (let ((case-fold-search t)) (save-excursion (goto-char (point-min)) ;; iterate over all BCC: lines (while (re-search-forward "^\\(RESENT-\\)?BCC:" header-end t) (delete-region (match-beginning 0) (progn (forward-line 1) (point))) ;; get rid of any continuation lines (while (and (looking-at "^[ \t].*\n") (< (point) header-end)) (replace-match "")))))) (provide 'smtpmail) ;;; arch-tag: a76992df-6d71-43b7-9e72-4bacc6c05466 ;;; smtpmail.el ends here
author Bastien Guerry <bzg@altern.org>
date Wed, 13 Feb 2008 20:58:26 +0000
parents 606f2d163a64
children 1e3a407766b9
line wrap: on
line source

;;; esh-mode.el --- user interface

;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004,
;;   2005, 2006, 2007, 2008 Free Software Foundation, Inc.

;; Author: John Wiegley <johnw@gnu.org>

;; 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 3, 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., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; 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.

(provide 'esh-mode)

(eval-when-compile (require 'esh-util))
(require 'esh-module)
(require 'esh-cmd)
(require 'esh-io)
(require 'esh-var)

(defgroup eshell-mode nil
  "This module contains code for handling input from the user."
  :tag "User interface"
  :group 'eshell)

;;; 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-send-direct-to-subprocesses nil
  "*If t, send any input immediately to a subprocess."
  :type 'boolean
  :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-postoutput-scroll-to-bottom
    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
  "[Pp]ass\\(word\\|phrase\\).*:\\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 '(choice (const nil) 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-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 ())

(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 (featurep 'xemacs)
	  (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 (if (consp key)
		     (and (>= (car key) 128)
			  (/= (char-syntax (car key)) ?w))
		   (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-sequence 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)

  ;; without this, find-tag complains about read-only text being
  ;; modified
  (if (eq (key-binding [(meta ?.)]) 'find-tag)
      (define-key eshell-mode-map [(meta ?.)] 'eshell-find-tag))
  (define-key eshell-command-map [(meta ?o)] 'eshell-mark-output)
  (define-key eshell-command-map [(meta ?d)] 'eshell-toggle-direct-send)

  (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)
  (define-key eshell-command-map [(control ?y)] 'eshell-repeat-argument)

  (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)

  ;; don't ever use auto-fill in Eshell buffers
  (setq auto-fill-function nil)

  ;; 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-sequence 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))))

  (if eshell-send-direct-to-subprocesses
      (add-hook 'pre-command-hook 'eshell-intercept-commands t t))

  (if eshell-scroll-to-bottom-on-input
      (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
    (add-hook 'eshell-pre-command-hook 'eshell-command-started nil t)
    (add-hook 'eshell-post-command-hook 'eshell-command-finished nil t))

  (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-mode-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"
  (let ((eshell-stringify-t t))
    (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 (featurep 'xemacs)
      (not eshell-status-in-modeline)
      (and (memq 'eshell-command-running-string mode-line-format)
	   (equal eshell-command-running-string "--"))))

;;; Internal Functions:

(defun eshell-toggle-direct-send ()
  (interactive)
  (if eshell-send-direct-to-subprocesses
      (progn
	(setq eshell-send-direct-to-subprocesses nil)
	(remove-hook 'pre-command-hook 'eshell-intercept-commands t)
	(message "Sending subprocess input on RET"))
    (setq eshell-send-direct-to-subprocesses t)
    (add-hook 'pre-command-hook 'eshell-intercept-commands t t)
    (message "Sending subprocess input directly")))

(defun eshell-self-insert-command (N)
  (interactive "i")
  (process-send-string
   (eshell-interactive-process)
   (char-to-string (if (symbolp last-command-char)
		       (get last-command-char 'ascii-character)
		     last-command-char))))

(defun eshell-intercept-commands ()
  (when (and (eshell-interactive-process)
	     (not (and (integerp last-input-event)
		       (memq last-input-event '(?\C-x ?\C-c)))))
    (let ((possible-events (where-is-internal this-command))
	  (name (symbol-name this-command))
	  (intercept t))
      ;; Assume that any multikey combination which does NOT target an
      ;; Eshell command, is a combo the user wants invoked rather than
      ;; sent to the underlying subprocess.
      (unless (and (> (length name) 7)
		   (equal (substring name 0 7) "eshell-"))
	(while possible-events
	  (if (> (length (car possible-events)) 1)
	      (setq intercept nil possible-events nil)
	    (setq possible-events (cdr possible-events)))))
      (if intercept
	  (setq this-command 'eshell-self-insert-command)))))

(defun eshell-find-tag (&optional tagname next-p regexp-p)
  "A special version of `find-tag' that ignores read-onlyness."
  (interactive)
  (require 'etags)
  (let ((inhibit-read-only t)
	(no-default (eobp))
	(find-tag-default-function 'ignore))
    (with-no-warnings
      (setq tagname (car (find-tag-interactive "Find tag: "))))
    (find-tag tagname next-p regexp-p)))

(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) (/= (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-repeat-argument (&optional arg)
  (interactive "p")
  (let ((begin (save-excursion
		 (eshell-backward-argument arg)
		 (point))))
    (kill-ring-save begin (point))
    (yank)))

(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 (delim command)
    (if (setq delim
	      (catch 'eshell-incomplete
		(ignore
		 (setq command (eshell-parse-command (cons beg end)
						     args t)))))
	(ignore
	 (message "Expecting completion of delimeter %c ..."
		  (if (listp delim)
		      (car delim)
		    delim)))
      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 (or no-newline
		  (and eshell-send-direct-to-subprocesses
		       proc-running-p))
	(insert-before-markers-and-inherit ?\n))
      (if proc-running-p
	  (progn
	    (eshell-update-markers eshell-last-output-end)
	    (if (or eshell-send-direct-to-subprocesses
		    (= 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
			      (if (eshell-invoke-directly cmd input)
				  (eval cmd)
				(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 (opoint obeg oend)
	  (with-current-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))))))

(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 recognized 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))))

(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 (&optional interactive)
  "Put the end of the buffer at the bottom of the window.
When run interactively, widen the buffer first."
  (interactive "p")
  (if interactive
      (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)
		(message "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 eshell-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 "%s 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 `eshell-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)
	  (eshell-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)

(defun eshell-handle-ansi-color ()
  "Handle ANSI color codes."
  (eval-and-compile (require 'ansi-color))
  (ansi-color-apply-on-region eshell-last-output-start
                              eshell-last-output-end))

(custom-add-option 'eshell-output-filter-functions
		   'eshell-handle-ansi-color)

;;; Code:

;;; arch-tag: ec65bc2b-da14-4547-81d3-a32af3a4dc57
;;; esh-mode.el ends here