I’ve been using Xonsh, a shell that combines a shell REPL with a Python REPL, for years now. I’ve also been using Emacs for years, but I was never able to marry the two in a satisfactory way. But finally, after being frustrated for long enough, I solved the puzzle. This article is written to help like one or two other people on this world who use both Emacs and Xonsh.

vterm, probably the best terminal emulator in Emacs, requires some shell-side configuration to make a shell integrate cleanly into Emacs. Specifically, an improved clear experience and directory- and prompt-tracking. vterm can also do message passing, but I’m not very interested in running Elisp in my terminal emulator—I have the rest of Emacs for that.

The idea is to print some invisible/hidden strings to the terminal that vterm can subsequently read, but that the user is unbothered by. The code to achieve this in .xonshrc is:

# You can modify this however you want.
$PROMPT = "{env_name}🐚 {BOLD_GREEN}{user}{RESET} {BOLD_BLUE}{cwd_base}{RESET}{branch_color}{curr_branch: {}}{RESET} {BOLD_BLUE}{prompt_end}{RESET} "

def _vterm_printf(text):
    def _term_is(value):
        return $TERM.split("-")[0] == value
    if ${...}.get("TMUX") and (_term_is("tmux") or _term_is("screen")):
        return $(printf r"\ePtmux;\e\e]%s\007\e\\" @(text))
    elif _term_is("screen"):
        return $(printf r"\eP\e]%s\007\e\\" @(text))
        return $(printf r"\e]%s\e\\" @(text))

def _vterm_prompt_end():
    return _vterm_printf("51;A{user}@{hostname}:{cwd}")

if ${...}.get("INSIDE_EMACS"):
    $SHELL_TYPE = "readline"

    def _clear(args, stdin=None):
        print(_vterm_printf("51;Evterm-clear-scrollback"), end="")
        tput clear @(args)
    aliases["clear"] = _clear

    $PROMPT += _vterm_prompt_end()

One important thing to note is that this only works in readline mode. prompt-toolkit is much fancier, but for reasons that are unknown to me, modifying $PROMPT as above does not produce the desired result. I’ve also considered monkey-patching print_color as a work-around, but there exists no xonsh.built_ins.XSH.shell inside of .xonshrc to monkey-patch.

After implementing the above code in .xonshrc, you can do the following things in vterm:

And that’s it. I’ll see about upstreaming some of this knowledge to vterm some day soon after some more hacking/testing.