AnsiTermHints

AnsiTerm mode is great for terminal emulation. It behaves like a full-featured terminal in term-char mode, and like a regular Emacs buffer in term-line mode. But it does have a few complications and annoyances.

Interaction with Cua-mode and Transient-mark-mode

AnsiTerm mode disables Cua-mode because, as stated in the term.el file, “Cua-mode’s keybindings interfere with the term keybindings”. If you only use Cua-mode for its great column editing and have disabled the usual Cua keys anyway, there would be no such interference. You can re-enable Cua-mode for use in term-line mode so you can still do Cua column editing.

If you use Transient-mark-mode, you probably only want to mark text when in term-line mode, not in term-char mode. In term-char mode, accidently hitting a key combination that results in some visibly marked text is frustrating. The only way to rid of it is to switch to term-line mode with C-j, cancel the mark with C-g, and finally use C-k to return to term-line mode.

The following defadvice functions take care of these two issues:

    ;; enable cua and transient mark modes in term-line-mode
    (defadvice term-line-mode (after term-line-mode-fixes ())
      (set (make-local-variable 'cua-mode) t)
      (set (make-local-variable 'transient-mark-mode) t))
    (ad-activate 'term-line-mode)

    ;; disable cua and transient mark modes in term-char-mode
    (defadvice term-char-mode (after term-char-mode-fixes ())
      (set (make-local-variable 'cua-mode) nil)
      (set (make-local-variable 'transient-mark-mode) nil))
    (ad-activate 'term-char-mode)

M-x shell like behavior

Those who are accustomed to use M-x shell may find M-x ansi-term does not adhere explicit-bash-args (e.g. to make the login-shell), nor .emacs_bash (or .emacs.d/init_bash.sh). Since M-x ansi-term does not support additional arguments to the shell, I’d write small replacement to M-x ansi-term to mimic M-x shell.

    (defun term/shell (program &optional new-buffer-name)
      "Start a terminal-emulator in a new buffer.

    With a prefix argument, it prompts the user for the shell
    executable.

    If there is already existing buffer with the same name, switch to
    that buffer, otherwise it creates new buffer.

    Like `shell', it loads `~/.emacs_SHELLNAME' if exists, or
    `~/.emacs.d/init_SHELLNAME.sh'.

    The shell file name (sans directories) is used to make a symbol
    name such as `explicit-bash-args'.  If that symbol is a variable,
    its value is used as a list of arguments when invoking the
    shell."
      (interactive (let ((default-prog (or explicit-shell-file-name
                                           (getenv "ESHELL")
                                           shell-file-name
                                           (getenv "SHELL")
                                           "/bin/sh")))
                     (list (if (or (null default-prog)
                                   current-prefix-arg)
                               (read-from-minibuffer "Run program: " default-prog)
                             default-prog))))

      ;; Pick the name of the new buffer.
      (setq term-ansi-buffer-name
            (if new-buffer-name
                new-buffer-name
              (if term-ansi-buffer-base-name
                  (if (eq term-ansi-buffer-base-name t)
                      (file-name-nondirectory program)
                    term-ansi-buffer-base-name)
                "shell/term")))

      (setq term-ansi-buffer-name (concat "*" term-ansi-buffer-name "*"))

      ;; In order to have more than one term active at a time
      ;; I'd like to have the term names have the *term-ansi-term<?>* form,
      ;; for now they have the *term-ansi-term*<?> form but we'll see...
      (when current-prefix-arg
        (setq term-ansi-buffer-name 
              (generate-new-buffer-name term-ansi-buffer-name)))

      (let* ((name (file-name-nondirectory program))
             (startfile (concat "~/.emacs_" name))
             (xargs-name (intern-soft (concat "explicit-" name "-args"))))
        (unless (file-exists-p startfile)
          (setq startfile (concat user-emacs-directory "init_" name ".sh")))

        (setq term-ansi-buffer-name
              (apply 'term-ansi-make-term term-ansi-buffer-name program
                     (if (file-exists-p startfile) startfile)
                     (if (and xargs-name (boundp xargs-name))
                         ;; `term' does need readline support.
                         (remove "--noediting" (symbol-value xargs-name))
                       '("-i")))))

      (set-buffer term-ansi-buffer-name)
      (term-mode)
      (term-line-mode)                      ; (term-char-mode) if you want

      ;; I wanna have find-file on C-x C-f -mm
      ;; your mileage may definitely vary, maybe it's better to put this in your
      ;; .emacs ...

      (term-set-escape-char ?\C-x)

      (switch-to-buffer term-ansi-buffer-name))

    (global-set-key "\C-cd" 'term/shell)

It seems that the author of ansi-term likes the behavior which Emacs create new terminal each time ansi-term is invoked. Since I prefer that Emacs raises the terminal buffer if it existed, M-x term/shell does not create additional terminal buffer by default. Instead, if you give a prefix argument, it will create additional buffer. – CinSK

Localhost

For terminal emulation on the local host, this simple function names the buffers *localhost*, *localhost<2>*, etc…

    (defun open-localhost ()
      (interactive)
      (ansi-term "bash" "localhost"))

Remote hosts

For remote hosts, an additional function was required since the ansi-term command doesn’t support additional arguments:

    ;; Use this for remote so I can specify command line arguments
    (defun remote-term (new-buffer-name cmd &rest switches)
      (setq term-ansi-buffer-name (concat "*" new-buffer-name "*"))
      (setq term-ansi-buffer-name (generate-new-buffer-name term-ansi-buffer-name))
      (setq term-ansi-buffer-name (apply 'make-term term-ansi-buffer-name cmd nil switches))
      (set-buffer term-ansi-buffer-name)
      (term-mode)
      (term-char-mode)
      (term-set-escape-char ?\C-x)
      (switch-to-buffer term-ansi-buffer-name))

You can then define a new function in your init file for each host you frequently visit with it’s own buffer name and connection parameters:

    (defun open-cmdev2 ()
      (interactive) 
      (remote-term "cmdev2" "ssh" "kreg@cmdev2.dsa.int"))

…which results in a new buffers named *cmdev2*, *cmdev2<2>*, etc…

Remote Directory Tracking

One of the best features of AnsiTerm mode is its remote directory tracking. Whatever remote directory your terminal is in, that will be the default directory that tramp uses if you choose to edit a file with C-x C-f. But you have to set up the following in your .bash_profile on each remote host first:

    function set-eterm-dir {
        echo -e "\033AnSiTu" "$LOGNAME" # $LOGNAME is more portable than using whoami.
        echo -e "\033AnSiTc" "$(pwd)"
        if [ $(uname) = "SunOS" ]; then
	    # The -f option does something else on SunOS and is not needed anyway.
       	    hostname_options="";
        else
            hostname_options="-f";
        fi
        echo -e "\033AnSiTh" "$(hostname $hostname_options)" # Using the -f option can
                                                             # cause problems on some OSes.
        history -a # Write history to disk.
    }

    # Track directory, username, and cwd for remote logons.
    if [ "$TERM" = "eterm-color" ]; then
        PROMPT_COMMAND=set-eterm-dir
    fi

Note that the above code may not work if /etc/hosts does not contain the fully qualified domain name (eg. it contains example instead of www.example.com). This is the case by default on Ubuntu when it is installed using the live CD and can be fixed by editing /etc/hosts. Tested on FreeBSD?, Ubuntu and SunOS?.

If you are using Zsh, you will notice setting the variable PROMPT_COMMAND doesn’t work. You can define the precmd function instead :

    precmd() {
        echo -e "\033AnSiTu" "$LOGNAME" # $LOGNAME is more portable than using whoami.
        echo -e "\033AnSiTc" "$(pwd)"
        if [ $(uname) = "SunOS" ]; then
	    # The -f option does something else on SunOS and is not needed anyway.
       	    hostname_options="";
        else
            hostname_options="-f";
        fi
        echo -e "\033AnSiTh" "$(hostname $hostname_options)" # Using the -f option can
                                                             # cause problems on some OSes.
    }

On some systems using precmd in the manner above may cause errors. In this case try the following:

    function eterm-update {
        # Only set the full hostname for ssh sessions.
        if [[ -n "$SSH_CONNECTION" ]]; then
            # For ssh connections, use the hostname (it is assumed here
            # that there is an ssh alias on the connecting machine that has
            # the same name as the ssh alias).
            echo -e "\033AnSiTh" "$(hostname)"
        else
            # For local sessions, use the full host name so tramp will know
            # that the path is not remote.
            echo -e "\033AnSiTh" "$(hostname -f)"
        fi
        
        echo -e "\033AnSiTu" "$LOGNAME" # $LOGNAME is more portable than using whoami.
        echo -e "\033AnSiTc" "$(pwd)"
    }

    function chpwd {
        eterm-update
    }

    cd "$(pwd)"

Remote Term Type

The local eterm-color term type provided with emacs works great for applications such as less that require more than a dumb terminal, but what about using those apps on a remote host which may not have emacs installed and know nothing of this term type? For these remote hosts, you’ll need to copy the eterm-color and eterm-color.ti files from the /usr/share/emacs/22.1/etc/e directory on your local system to the following remote directory:

    ~/.terminfo/e/eterm-color
    ~/.terminfo/e/eterm-color.ti

Terminfo

The above information relating to Remote Term Type can also apply to your local terminal. On some systems eterm-color might not be defined, or, perhaps even worse, be not as well defined as it could be. If you copy (or symlink to) the /usr/share/emacs/<emacs version number>/etc/e/eterm-color terminfo file into (in) /usr/share/terminfo/e/ you may find more useful behaviour (for example of display handling when using cursor keys in bash). For more information see http://www.cygwin.com/ml/cygwin/2009-06/msg00259.html

Meh. It’s still not perfect. Cursor jumps everywhere when the cursor up key (for last command in bash) is pressed, uses only half the available window for output, etc.

On debian based systems you administer, you can ‘apt-get install ncurses-term’, which includes /usr/share/terminfo/e/eterm-color.