The fix
Turn off the prompt_cr option in a dumb terminal. In your .zshrc:
case $TERM in
dumb) setopt no_prompt_cr;;
esac
Or actually, disable zsh's line editor altogether when in M-x shell, since Emacs takes care of the command line editing.
## Turn off line editing when Emacs is doing it
if [[ $TERM = emacs || ( $TERM = dumb && -n $EMACS ) ]]; then
unsetopt zle
fi
Alternatively, if you want to have zsh do the line edition, give it a proper terminal (M-x term or the like) instead of running it in a shell buffer.
Explanation
The difference between M-x shell and most other terminals is that M-x shell is a “dumb terminal” (indicated through the environment variable TERM=dumb). This terminal can basically only write characters and break lines, it doesn't support cursor movement and other “fancy” features.
Zsh has three possible behaviors when it displays a prompt, depending on the values of the options prompt_cr and prompt_sp. They only make a difference after a partial output line, i.e. when the cursor is not on the left margin when zsh starts displaying the prompt.
prompt_cr disabled: zsh just starts displaying the prompt. This may cause a garbled display after editing, because zsh doesn't know that the prompt isn't on the left margin. For example:
$ zsh -f
darkstar% setopt no_prompt_cr
darkstar% printf foobar
foobardarkstar% aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbb
So far so good. I've started typing aaaa…bbbbb on the command line, in an 80-column terminal, so that the prompt plus my input is 79 characters. Since the prompt is at column 6, the last 5 characters that I typed (the b's) are on the next line. If I insert one more character c, zsh thinks that it's reached the left margin and moves the cursor and redisplays the last line accordingly, but it thinks that the prompt plus my input so far fit on the first line, so I end up with this:
foobardarkstar% aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Oops, the last 6 characters of my input are gone! And if I run M-x redisplay, I get:
darkstar% aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbc
My input is visible again, and the foobar that zsh doesn't know about is gone.
prompt_cr enabled, prompt_sp disabled: to remedy this display corruption, zsh moves the cursor to the beginning of the line before displaying the prompt, by printing a CR (carriage return). This has a downside: the prompt overwrites whatever text was there before. In a dumb terminal simulated in a capable terminal:
$ TERM=dumb zsh -f
darkstar% setopt prompt_cr no_prompt_sp
darkstar% print -n some text without a newline
darkstar% without a newline
I haven't typed anything after running the print command. without a newline is the part of the output that wasn't overwritten by the prompt. The cursor is on the w of without.
In Emacs M-x shell, which is a “genuine” dumb terminal, the effect of CR is to clear the partial line, not just to move the cursor. (This terminal just logs the output, so it doesn't have any way for the application to move the cursor. What you type appears where the point is in Emacs terminology, while output from the application is inserted at the end of the buffer.)
darkstar% setopt no_prompt_sp prompt_cr
darkstar% print -n text without a newline
darkstar%
You can observe the line clearing effect by injecting a delay before the next prompt:
darkstar% setopt no_prompt_sp prompt_cr
darkstar% print -n text without a newline; sleep 2
text without a newline
changes after 2 seconds to
darkstar% setopt no_prompt_sp prompt_cr
darkstar% print -n text without a newline; sleep 2
darkstar%
On a normally working non-dumb terminal, zsh clears the rest of the line before displaying the prompt, so the whole partial line of output disappears (for a different reason from M-x shell, but the visual result is the same).
$ zsh -f
darkstar% setopt no_prompt_sp prompt_cr
darkstar% print -n text without a newline
darkstar%
prompt_cr and prompt_sp both enabled: this is the default, because it's usually much better than the other two possible behaviors. Zsh checks the cursor position before displaying a prompt. If the cursor isn't on the left margin, zsh displays an inverse-video % (configurable through PROMPT_EOL_MARK) and moves the cursor to the beginning of the next line. This way the partial line of output remains visible and the next command line doesn't get visually garbled.
$ zsh -f
darkstar% PROMPT_EOL_MARK=¶
darkstar% printf foobar
foobar¶
darkstar%
However, on a dumb terminal, it's impossible to check the cursor position. So prompt_sp has no effect, and the behavior degrades to the sole effect of prompt_cr, which is that zsh just emits a CR before the prompt.