2

I use Bash, and my PS1 spans two lines.

╭-ivan·dotfiles (master)
╰ဝ █

Occasionally I'll run a command whose output doesn't have a trailing newline, and my prompt gets misaligned,

╭-ivan·dotfiles (master)
╰ဝ curl localhost:3002/is_alive
{"web_server_status":"success","db_status":"success"}╭-ivan·dotfiles (master)
╰ဝ █

I finally found a solution that conditionally prepends a newline to the prompt if the cursor is beyond the first column:

build_prompt() {
  export PS1="\$(ps1_head)...
}

ps1_head() {
  if (( $(cursor_col) > 1 )); then
    printf '\n╭-'
  else
    printf '╭-'
  fi
}

cursor_col() {
  local _row col
  IFS=';' read -s -dR -p $'\033[6n' _row col
  echo "${col}"
}

This fixed the original problem,

╭-ivan·dotfiles (master)
╰ဝ curl localhost:3002/is_alive
{"web_server_status":"success","db_status":"success"}
╭-ivan·dotfiles (master)
╰ဝ █

but, it's introduced a new one. I rely on typeahead to allow me to type my next command before the current one has completed. The conditional newline seems to break that behavior.

An example without the condition newline logic:

╭-ivan·dotfiles (master)
╰ဝ git status
On branch master
Your branch is up-to-date with 'origin/master'.

lnothing to commit, working tree clean
s╭-ivan·dotfiles (master)
╰ဝ ls
myfile.txt

Notice I typed l (lnothing to commit) before the first command's output had finished printing, and I typed s after the first command had completed but before the prompt had printed. The typeahead gracefully pulled both chars (ls) into the next command.

Now, the same example with the conditional newline logic in my prompt:

╭-ivan·dotfiles (master)
╰ဝ git status
On branch master
Your branch is up-to-date with 'origin/master'.

lnothing to commit, working tree clean
s╭-ivan·dotfiles (master)
╰ဝ s
bash: s: command not found

Notice how this time, typeahead gracefully pulled the s into the second command, but it left the l behind.

Is there any way to handle this more gracefully, or must I finally switch to zsh?

  • Use zsh instead. – Kevin Sep 20 '17 at 23:20
  • 1
    cursor_col() is sending a terminal control to cause the terminal to send the cursor position like typed input and then reading that pseudo-typed input. Any real typed input preceding the pseudo-typed input is included as part of the typed input read by cursor_col() and no longer available for reading (as part of a command) after that. – dave_thompson_085 Sep 21 '17 at 6:01
  • @dave_thompson_085 That makes perfect sense. I don't imagine there's a way around the issue then, unless I can find another way to determine the cursor position. – ivan Sep 21 '17 at 12:53
1

I have dynamic prompt text like this and no issues with typeahead. But instead of embedding process substitution directly in PS1 I use PROMPT_COMMAND to call setup methods. They produce the appropriate dynamic text which is stored in variables and the variables are then embedded in PS1.

So try something like this...

export ps1_head_text

build_prompt() {
  export PS1="\${ps1_head_text}..."
}

ps1_head() {
  if (( $(cursor_col) > 1 )); then
    ps1_head_text=$'\n'╭-
  else
    ps1_head_text=╭-
  fi
}

export PROMPT_COMMAND=ps1_head

I ended up doing it this way precisely because I had issues with process substitution (though I don't remember if it was typeahead or some other problem).

Edit: Now that I think of it you may have timing issues...if PROMPT_COMMAND is invoked at a time that prevents a correct reading of cursor position. If that happens you are probably in the "use zsh" boat. But give it a shot.


Update: According to a comment from dave_thompson_085 reading the response to emitting an escape sequence (for printing cursor position) can conflict with typeahead input.

It makes sense since the read is occurring in the same shell as where the typeahead text is entered. So what if you don't do it in the same shell? Does surrounding it with parens to put it in a subshell help? What about redirecting the terminal's response (e.g. to a file or pipe or whatever) and reading it from there?

Feels like there's a way to overcome the problem so I'm throwing some stuff against the wall to see what sticks.

  • I like the idea, but it doesn't seem to fix the typeahead issue in my case. I also couldn't get the \n to print an actual newline (it was printing a literal \n regardless whether I used single or double quotes). I ran into the same issue when building out my original solution, which led me to use printf. This works though: ps1_head_text=$(printf '\n╭-'). Still trips up typeahead though, at least on my machine. I wonder if I could leverage PROMPT_COMMAND to conditionally print a newline, unrelated to the prompt? I'll try that next. – ivan Sep 21 '17 at 12:49
  • 1
    Sorry @ivan I overlooked the newline issue. I've updated the answer with a fix. As for the rest I'll try to play around with it later. Let me know what happens on your end. – B Layer Sep 21 '17 at 12:58
  • Nice, thanks! I ran with the prompt-command thing, removed the conditional logic from my ps1 itself and just made the prompt command conditionally print a newline. export PROMPT_COMMAND='(( $(cursor_col) > 1 )) && printf "\n"' I like that this approach separates the newline concern from the prompt itself. – ivan Sep 21 '17 at 13:08
  • Someone chimed in on the root cause of broken typeahead though (see the second comment on my question above), pointing out that it stems from my implementation of cursor_col(), which involves sending a terminal control sequence as input, and thus interferes with any manually typed input that preceded it. – ivan Sep 21 '17 at 13:12
  • 1
    You bet! BTW, I just noticed the similarity between these two: gravatar.com/avatar/… and i.stack.imgur.com/pJr5h.jpg?s=328&g=1 Great minds think alike. ;) Cheers. – B Layer Sep 22 '17 at 13:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.