lursyy
lursyy

Reputation: 405

fish shell: add newline before prompt only when previous output exists

(Disclaimer: I am using fish, but this should apply equally to bash)
My current shell prints a newline before the prompt so I can easily find it between command outputs.

# [...]
echo # newline before prompt
echo -s $arrow ' ' $cwd $git_info
echo -n -s '❯ '

However, the newline is also printed when there is no previous output, e.g. after clearing the terminal with printf "\033c" (or when the terminal is first opened):

                           <--- bad newline: no previous output
➜ /some/dir            
❯ command1            
output...             
                           <--- good newline
➜ /some/dir          
❯ command2             

Question: is there any way I can get rid of this small aesthetic annoyance?



Edit #1:

For clarification: By "no previous output" I meant the contents of my console are empty, i.e. after (re-)initializing the terminal (because that's all printf "\033c" does).

Upvotes: 6

Views: 2596

Answers (3)

matt
matt

Reputation: 66

exec events were merged into fish-shell a few years ago. I think you can use these here. They work like event lifecycle hooks in other languages. https://github.com/fish-shell/fish-shell/pull/1666

As a test I made a file called postexec-newline.fish with the following:

function postexec_test --on-event fish_postexec
   echo
end

After issuing source postexec-newline.fish, the behavior you are describing is observed. The newline is also not visible when the screen is cleared with C-l, which I think is how such a feature should behave.

This function can live in config.fish if you want the change permanently.

Upvotes: 5

faho
faho

Reputation: 15934

Stock fish already handles this (and has for years), there is no need for the prompt to do it.

When the output didn't end in a newline, you'll see a "missing newline symbol" like "¶" or "⏎", followed by a newline, and only then your prompt.

Upvotes: 1

root
root

Reputation: 6058

EDIT: post edited to be more portable.

Here's how I do it in bash:

__PROMPT_NEWLINE=$'\nVVV '
__set_missing_newline_fix()
{
    local CURPOS
    echo -en "\033[6n" # ANSI DSR
    read -s -d R CURPOS
    CURPOS=${CURPOS#*;}
    if [ $CURPOS -eq 1 ]; then
        __MISSING_NEWLINE_FIX=""
    else
        __MISSING_NEWLINE_FIX="$__PROMPT_NEWLINE"
    fi
}

PROMPT_COMMAND=__set_missing_newline_fix
PS1="\${__MISSING_NEWLINE_FIX}\w > "

Note that it's configured to prefix my prompt with VVV to let me know that the last command did not end with a newline.

Demo:

$ source bashrc.sh
/tmp/so/newline > echo hello
hello
/tmp/so/newline > echo -n hello
hello
VVV /tmp/so/newline > 

The schtick:

ANSI DSR will cause the terminal to write its current cursor position as input. Since we're an interactive shell, that input is available to the shell's stdin, so we just read -s it (with no echoing).

In the above link you'll see that the response is of the form CSI Pl; Pc R, so we tell read to read up to and including the R with -d R.

Then we extract that Pc part using bash' "remove matching prefix" syntax ${CURPOS#*;} which removes everything up to and including the semicolon ;.

Then, if the cursor position is not 1, i.e. we're not at the beginning of a newline, we manually add a newline to the prompt.

ANSI DSR should work with every terminal that is ANSI compatible, but if you follow the link you'll see that it doesn't literally say \033[6n, but rather CSI 6 n. CSI is the beginning of an escape sequence. Escape sequences begin with ASCII 27 ESC character (octal 033).

In my original answer I used \E, which bash' built-in echo -e command parses the same as \033, hence the edit above.

Upvotes: 1

Related Questions