5

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.

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
}
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.

  • 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. – Gilles Sep 17 '13 at 22:45
  • Good ideas all, but as you say, it’s not really what I’m looking for. – Zev Eisenberg Sep 18 '13 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+.

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.