3

(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).

lursyy
  • 375
  • 2
  • 9
  • Is the "(re)initializing" case the only one you are concerned with? I was thinking you also wanted to suppress the extra newline when the previous command had no output (e.g. `export PATH` or anything like that). – NotTheDr01ds Feb 13 '21 at 01:25
  • @NotTheDr01ds Yes, currently that is the only case. I want every prompt prefixed by a newline _except_ after (re)initializing. – lursyy Feb 15 '21 at 14:15

3 Answers3

1

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.

root
  • 5,528
  • 1
  • 7
  • 15
  • Is the DSR behavior dependent on the shell, terminal, or OS? When using fish, `echo -en "\E[6n"` just gets echoed literally. In bash it always produces `;1R`, whether it's the first command or not. – lursyy Jan 16 '21 at 12:15
  • Post edited, but it sounds like your terminal is not reporting correctly, does it happen in other terminals as well? – root Jan 16 '21 at 19:06
  • Also, how are you testing what it always produces? You need to issue the DSR when the position is not 1 (to verify...) and to read it from the terminal into something like `hexdump` so that the first ESC in the response is not swallowed by the shell or the terminal. – root Jan 16 '21 at 19:21
  • @Luis-Beaucamp For starters, the fish `echo` equivalent is `echo -en "\e[6n"`. However, once we get past that, I'm still left with the blocker that fish's `read` does not seem to be able to handle this case. It's "silent" mode does not suppress, but instead (from the man page) *"masks characters written to the terminal, replacing them with asterisks. This is useful for reading things like passwords or other sensitive information."* It also seems as if bash's `read` automatically returns when it gets the ANSI return result, but fish's does not. I haven't been able to get around this yet. – NotTheDr01ds Jan 16 '21 at 20:21
  • Oops - Missed @root's edit. \033 is also portable to fish and correctly returns the position. Still haven't figured out the `read` issue, though. – NotTheDr01ds Jan 16 '21 at 20:25
  • Thanks to both of you, the uppercase `E` was the culprit. Both `e` and `033` in fish give me line and column numbers. I still have to try the reading part though, will update once done! – lursyy Jan 17 '21 at 20:20
  • It appears that bash' built-in `read` does some interesting heavy lifting, I might add it to my answer since the question is tagged with `bash`, but it seems like fish automatically inserts a newline if it's missing prior to calling `fish_prompt`, so I don't think they apply equally as initially presumed. – root Jan 18 '21 at 00:55
1

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.

faho
  • 14,470
  • 2
  • 37
  • 47
  • Please re-read the original question. This is about creating a *custom* prompt where an *extra* newline is *added* (to separate the output from the next prompt) when there is output from the previous command, but is *not added* when there is no output from the previous command. The default fish prompt never adds an *extra* (separator) newline; it only adds a single newline if, as you said, the previous output did not end in one. This makes sure that the prompt always begins on a separate line, but does not create any visual separation that the OP is looking for. – NotTheDr01ds Feb 12 '21 at 18:47
  • Exactly, sorry for the confusion. I added some clarification. – lursyy Feb 13 '21 at 00:08
1

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.

matt
  • 26
  • 3
  • I think this is the cleanest solution, the errant newline is now gone in newly opened sessions. Because I am using `printf "\033c"` to clear the terminal, a newline is still inserted there. But this can probably be fixed, either by preventing this function to run, or including some condition. – lursyy Jan 11 '22 at 05:50