7

A friend of mine posted this on StackOverflow, and I thought we might be more likely to get an answer here. His post refers to speed, but we now know that the python script we are using to parse git status is slow, and at some point we intend to rewrite it to be faster. however, the question of settign RPROMPT asynchronously is still interesting to me, so I thought I’d quote his question here:

Since I started using git I've had my RPROMPT setup to show the current branch. I've recently been using some of the "fancy" scripts to show un/staged file counts and other useful at a glance things. (https://github.com/olivierverdier/zsh-git-prompt/tree/master)

After using this for a week or two, its performance started to bother me.

Are there faster ways of getting this info or are there maybe ways to asynchronously write the RPROMPT? I don't want to wait to type a command while the RPROMPT is computed and would be perfectly happy with it popping in slightly later then my main PROMPT.

No offense to the aforementioned script; it's great. I'm just impatient.

5 Answers 5

4

Here's a solution that uses a background job and signals to asynchronously update the prompt.

The idea is to have your prompt function spawn off a background job that builds the prompt, writes it to a file, then sends a signal to the parent shell that it's done. When the parent shell gets the signal it reads the prompt from the file and redraws the prompt.

In your prompt function, put this:

function async-build-prompt {
    # Simulate a function that takes a couple seconds to build the prompt.
    # Replace this line with your actual function.
    sleep 2 && RPROMPT=$(date)

    # Save the prompt in a temp file so the parent shell can read it.
    printf "%s" $RPROMPT > ${TMPPREFIX}/prompt.$$

    # Signal the parent shell to update the prompt.
    kill --signal USR2 $$
}

# Build the prompt in a background job.
async-build-prompt &!

And in your .zshrc, put this:

function TRAPUSR2 {
    RPROMPT=$(cat "${TMPPREFIX}/prompt.$$")

    # Force zsh to redisplay the prompt.
    zle && zle reset-prompt
}
2
1

I'd have the git_super_status() read from a file (say something like /tmp/rprompt.$$). The file would contain the "contents" of what git_super_status() would usually return. Then git_super_status() could fire off a background script to do the "asynchronous" part (i.e do the normal processing, but instead off displaying it's output, write the output to that file).

It'd mean the prompt would take a few seconds to pick up new git changes, and would only be updated the next time a command was issued, which is not perfect. I can't see an alternative, as I don't think there is a callback or poll type mechanism offered with RPROMPT.

2
  • Tricky: there's only one cursor position; if zsh decides to move the cursor while git is printing the output could end up all over the place. Sep 17, 2013 at 22:45
  • Good ideas all, but as you say, it’s not really what I’m looking for. Sep 18, 2013 at 15:18
0

I have a piece of code updating the prompt asynchronously, but it has some issues:

  1. occasionally an error message is printed, saying it can't find the background job.
  2. sometimes after executing a command, the last line of the output is overwritten (so you can't see it), or the prompt isn't re-printed.

The code:

# git branch in prompt {{{3

_setup_current_branch_async () { # {{{4
  typeset -g _current_branch= vcs_info_fd=
  zmodload zsh/zselect 2>/dev/null

  _vcs_update_info () {
    eval $(read -rE -u$1)
    zle -F $1 && vcs_info_fd=
    exec {1}>&-
    # update prompt only when necessary to avoid double first line
    [[ -n $_current_branch ]] && zle reset-prompt
  }

  _set_current_branch () {
    _current_branch=
    [[ -n $vcs_info_fd ]] && zle -F $vcs_info_fd
    cwd=$(pwd -P)
    for p in $_nogit_dir; do
      if [[ $cwd == $p* ]]; then
        return
      fi
    done

    setopt localoptions no_monitor
    coproc {
      _br=$(git branch --no-color 2>/dev/null)
      if [[ $? -eq 0 ]]; then
        _current_branch=$(echo $_br|awk '$1 == "*" {print "%{\x1b[33m%} (" substr($0, 3) ")"}')
      fi
      # always gives something for reading, or _vcs_update_info won't be
      # called, fd not closed
      #
      # "typeset -p" won't add "-g", so reprinting prompt (e.g. after status
      # of a bg job is printed) would miss it
      #
      # need to substitute single ' with double ''
      print "typeset -g _current_branch='${_current_branch//''''/''}'"
    }
    disown %{\ _br
    exec {vcs_info_fd}<&p
    # wait 0.1 seconds before showing up to avoid unnecessary double update
    # precmd functions are called *after* prompt is expanded, and we can't call
    # zle reset-prompt outside zle, so turn to zselect
    zselect -r -t 10 $vcs_info_fd 2>/dev/null
    zle -F $vcs_info_fd _vcs_update_info
  }
}

_setup_current_branch_async
typeset -gaU precmd_functions
precmd_functions+=_set_current_branch

Then setopt PROMPT_SUBST and use \$_current_branch in PS1.

Supported versions are zsh 5.0.6+.

0

The following is a method for Zsh, by Simple async prompt : zsh. I've modified the code slightly to remove useless parts, and so it's a bit more legible.

fd_on_load() {
  PROMPT="$(<&$1)"
  zle reset-prompt
  zle -F $1
}

prompt_foo_precmd() {
  PROMPT="waiting..."

  exec {FD}< <(
    sleep 1
    echo -n "foo > "
  )

  zle -F $FD fd_on_load
}

add-zsh-hook precmd prompt_foo_precmd

The benefit is that it does not need a temporary file.

It suffers from the same problem as other async prompts I've tried, in that the shell command that we just entered previously might not have finished running by the time fd_on_load gets called. In this case, waiting... will not be removed from the shell.

0

If you're open to using an external prompt, try powerlevel10k.

It uses its own optimized gitstatus as backend, instead of gits commands, and it calls gitstatus asynchronously in the background.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .