2

When running zsh under Emacs shell (M-x shell) with the stock zsh config (zsh -f), partial lines are hidden from the output. Example:

Start emacs without config file:

$ emacs -q

Set

(setq explicit-shell-file-name "zsh")
(setq explicit-zsh-args '("-f"))

Then run M-x shell. In the shell, try printf "foobar". I see no output. If I run the same command in a terminal emulator, like M-x term, I do see the output foobar% as expected (where % is zsh's default partial line indicator).

How can I get my partial lines back? I've noticed that one solution might be unsetting PROMPT_CR (with unsetopt PROMPT_CR in zsh). The disadvantage is that that partial lines are not handled as nicely as in stock zsh: now we get the prompt and the partial line on the same line.

1 Answer 1

2

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.

3
  • Hi, thanks for the detailed reply. setopt no_prompt_cr indeed solves the problem (with the drawbacks discussed in my updated question). On the other hand, unsetopt zle does not seem to make any difference in my setup. Commented Apr 9 at 7:14
  • What do you think about setting an emacs-specific PROMPT_EOL_MARK value, that sends a specific code that an comint filter would treat by inserting a newline in the output? Commented Apr 9 at 7:24
  • 1
    @arvidj I haven't tried the comint filter approach, but yes, I think that should work. Commented Apr 9 at 9:29

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.