citadel

My dotfiles, scripts and nix configs
git clone git://jb55.com/citadel
Log | Files | Refs | README | LICENSE

preexec.bash (6317B)


      1 # Copyright (c) 2008-2012 undistract-me developers. See LICENSE for details.
      2 #
      3 # preexec.bash -- Bash support for ZSH-like 'preexec' and 'precmd' functions.
      4 
      5 # The 'preexec' function is executed before each interactive command is
      6 # executed, with the interactive command as its argument.  The 'precmd'
      7 # function is executed before each prompt is displayed.
      8 
      9 # To use, in order:
     10 
     11 #  1. source this file
     12 #  2. define 'preexec' and/or 'precmd' functions (AFTER sourcing this file),
     13 #  3. as near as possible to the end of your shell setup, run 'preexec_install'
     14 #     to kick everything off.
     15 
     16 # Note: this module requires 2 bash features which you must not otherwise be
     17 # using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable.  preexec_install
     18 # will override these and if you override one or the other this _will_ break.
     19 
     20 # This is known to support bash3, as well as *mostly* support bash2.05b.  It
     21 # has been tested with the default shells on MacOS X 10.4 "Tiger", Ubuntu 5.10
     22 # "Breezy Badger", Ubuntu 6.06 "Dapper Drake", and Ubuntu 6.10 "Edgy Eft".
     23 
     24 
     25 # Copy screen-run variables from the remote host, if they're available.
     26 
     27 if [[ "$SCREEN_RUN_HOST" == "" ]]
     28 then
     29     SCREEN_RUN_HOST="$LC_SCREEN_RUN_HOST"
     30     SCREEN_RUN_USER="$LC_SCREEN_RUN_USER"
     31 fi
     32 
     33 # Default do-nothing implementation of preexec.
     34 function preexec () {
     35     true
     36 }
     37 
     38 # Default do-nothing implementation of precmd.
     39 function precmd () {
     40     true
     41 }
     42 
     43 # This function is installed as the PROMPT_COMMAND; it is invoked before each
     44 # interactive prompt display.  It sets a variable to indicate that the prompt
     45 # was just displayed, to allow the DEBUG trap, below, to know that the next
     46 # command is likely interactive.
     47 function preexec_invoke_cmd () {
     48     precmd
     49     trap 'preexec_invoke_exec' DEBUG
     50 }
     51 
     52 # This function is installed as the DEBUG trap.  It is invoked before each
     53 # interactive prompt display.  Its purpose is to inspect the current
     54 # environment to attempt to detect if the current command is being invoked
     55 # interactively, and invoke 'preexec' if so.
     56 function preexec_invoke_exec () {
     57     if [[ -n "$COMP_LINE" ]]
     58     then
     59         # We're in the middle of a completer.  This obviously can't be
     60         # an interactively issued command.
     61         return
     62     fi
     63     trap '' DEBUG
     64 
     65     if [[ "preexec_invoke_cmd" == "$BASH_COMMAND" ]]
     66     then
     67         # Sadly, there's no cleaner way to detect two prompts being displayed
     68         # one after another.  This makes it important that PROMPT_COMMAND
     69         # remain set _exactly_ as below in preexec_install.  Let's switch back
     70         # out of interactive mode and not trace any of the commands run in
     71         # precmd.
     72 
     73         # Given their buggy interaction between BASH_COMMAND and debug traps,
     74         # versions of bash prior to 3.1 can't detect this at all.
     75         return
     76     fi
     77 
     78     # In more recent versions of bash, this could be set via the "BASH_COMMAND"
     79     # variable, but using history here is better in some ways: for example, "ps
     80     # auxf | less" will show up with both sides of the pipe if we use history,
     81     # but only as "ps auxf" if not.
     82     local this_command=`HISTTIMEFORMAT= history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`;
     83 
     84     # If none of the previous checks have earlied out of this function, then
     85     # the command is in fact interactive and we should invoke the user's
     86     # preexec hook with the running command as an argument.
     87     preexec "$this_command"
     88 }
     89 
     90 function preexec_set_exit () {
     91     __preexec_exit_status=$?
     92 }
     93 
     94 # Execute this to set up preexec and precmd execution.
     95 function preexec_install () {
     96 
     97     # *BOTH* of these options need to be set for the DEBUG trap to be invoked
     98     # in ( ) subshells.  This smells like a bug in bash to me.  The null stderr
     99     # redirections are to quiet errors on bash2.05 (i.e. OSX's default shell)
    100     # where the options can't be set, and it's impossible to inherit the trap
    101     # into subshells.
    102 
    103     set -o functrace > /dev/null 2>&1
    104     shopt -s extdebug > /dev/null 2>&1
    105 
    106     # Finally, install the actual traps.
    107     if [ -n "$PROMPT_COMMAND" ]; then
    108         PROMPT_COMMAND="preexec_set_exit;${PROMPT_COMMAND} preexec_invoke_cmd";
    109     else
    110         PROMPT_COMMAND="preexec_set_exit;preexec_invoke_cmd";
    111     fi
    112 }
    113 
    114 # Since this is the reason that 99% of everybody is going to bother with a
    115 # pre-exec hook anyway, we'll include it in this module.
    116 
    117 # Change the title of the xterm.
    118 function preexec_xterm_title () {
    119     local title="$1"
    120     echo -ne "\033]0;$title\007" > /dev/stderr
    121 }
    122 
    123 function preexec_screen_title () {
    124     local title="$1"
    125     echo -ne "\033k$1\033\\" > /dev/stderr
    126 }
    127 
    128 # Abbreviate the "user@host" string as much as possible to preserve space in
    129 # screen titles.  Elide the host if the host is the same, elide the user if the
    130 # user is the same.
    131 function preexec_screen_user_at_host () {
    132     local RESULT=""
    133     if [[ "$SCREEN_RUN_HOST" == "$SCREEN_HOST" ]]
    134     then
    135         return
    136     else
    137         if [[ "$SCREEN_RUN_USER" == "$USER" ]]
    138         then
    139             echo -n "@${SCREEN_HOST}"
    140         else
    141             echo -n "${USER}@${SCREEN_HOST}"
    142         fi
    143     fi
    144 }
    145 
    146 function preexec_xterm_title_install () {
    147     # These functions are defined here because they only make sense with the
    148     # preexec_install below.
    149     function precmd () {
    150         preexec_xterm_title "${TERM} - ${USER}@${SCREEN_HOST} `dirs -0` $PROMPTCHAR"
    151         if [[ "${TERM}" == screen ]]
    152         then
    153             preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}"
    154         fi
    155     }
    156 
    157     function preexec () {
    158         preexec_xterm_title "${TERM} - $1 {`dirs -0`} (${USER}@${SCREEN_HOST})"
    159         if [[ "${TERM}" == screen ]]
    160         then
    161             local cutit="$1"
    162             local cmdtitle=`echo "$cutit" | cut -d " " -f 1`
    163             if [[ "$cmdtitle" == "exec" ]]
    164             then
    165                 local cmdtitle=`echo "$cutit" | cut -d " " -f 2`
    166             fi
    167             if [[ "$cmdtitle" == "screen" ]]
    168             then
    169                 # Since stacked screens are quite common, it would be nice to
    170                 # just display them as '$$'.
    171                 local cmdtitle="${PROMPTCHAR}"
    172             else
    173                 local cmdtitle=":$cmdtitle"
    174             fi
    175             preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}$cmdtitle"
    176         fi
    177     }
    178 
    179     preexec_install
    180 }