blob: b1662a932383a31b0113ae195ec93b061d33e504 [file] [log] [blame]
Eitan Adler3fc60b52012-04-17 00:24:58 -04001#!/usr/bin/env bash
Antono Vasiljevbd61f6e2011-12-17 11:45:59 +03002# http://www.twistedmatrix.com/users/glyph/preexec.bash.txt
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
27if [[ "$SCREEN_RUN_HOST" == "" ]]
28then
29 SCREEN_RUN_HOST="$LC_SCREEN_RUN_HOST"
30 SCREEN_RUN_USER="$LC_SCREEN_RUN_USER"
31fi
32
33# This variable describes whether we are currently in "interactive mode";
34# i.e. whether this shell has just executed a prompt and is waiting for user
35# input. It documents whether the current command invoked by the trace hook is
36# run interactively by the user; it's set immediately after the prompt hook,
37# and unset as soon as the trace hook is run.
38preexec_interactive_mode=""
39
40# Default do-nothing implementation of preexec.
41function preexec () {
42 true
43}
44
45# Default do-nothing implementation of precmd.
46function precmd () {
47 true
48}
49
50# This function is installed as the PROMPT_COMMAND; it is invoked before each
51# interactive prompt display. It sets a variable to indicate that the prompt
52# was just displayed, to allow the DEBUG trap, below, to know that the next
53# command is likely interactive.
54function preexec_invoke_cmd () {
55 precmd
56 preexec_interactive_mode="yes"
57}
58
59# This function is installed as the DEBUG trap. It is invoked before each
60# interactive prompt display. Its purpose is to inspect the current
61# environment to attempt to detect if the current command is being invoked
62# interactively, and invoke 'preexec' if so.
63function preexec_invoke_exec () {
64 if [[ -n "$COMP_LINE" ]]
65 then
66 # We're in the middle of a completer. This obviously can't be
67 # an interactively issued command.
68 return
69 fi
70 if [[ -z "$preexec_interactive_mode" ]]
71 then
72 # We're doing something related to displaying the prompt. Let the
73 # prompt set the title instead of me.
74 return
75 else
76 # If we're in a subshell, then the prompt won't be re-displayed to put
77 # us back into interactive mode, so let's not set the variable back.
78 # In other words, if you have a subshell like
79 # (sleep 1; sleep 2)
80 # You want to see the 'sleep 2' as a set_command_title as well.
81 if [[ 0 -eq "$BASH_SUBSHELL" ]]
82 then
83 preexec_interactive_mode=""
84 fi
85 fi
86 if [[ "preexec_invoke_cmd" == "$BASH_COMMAND" ]]
87 then
88 # Sadly, there's no cleaner way to detect two prompts being displayed
89 # one after another. This makes it important that PROMPT_COMMAND
90 # remain set _exactly_ as below in preexec_install. Let's switch back
91 # out of interactive mode and not trace any of the commands run in
92 # precmd.
93
94 # Given their buggy interaction between BASH_COMMAND and debug traps,
95 # versions of bash prior to 3.1 can't detect this at all.
96 preexec_interactive_mode=""
97 return
98 fi
99
100 # In more recent versions of bash, this could be set via the "BASH_COMMAND"
101 # variable, but using history here is better in some ways: for example, "ps
102 # auxf | less" will show up with both sides of the pipe if we use history,
103 # but only as "ps auxf" if not.
104 local this_command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`;
105
106 # If none of the previous checks have earlied out of this function, then
107 # the command is in fact interactive and we should invoke the user's
108 # preexec hook with the running command as an argument.
109 preexec "$this_command"
110}
111
112# Execute this to set up preexec and precmd execution.
113function preexec_install () {
114
115 # *BOTH* of these options need to be set for the DEBUG trap to be invoked
116 # in ( ) subshells. This smells like a bug in bash to me. The null stderr
117 # redirections are to quiet errors on bash2.05 (i.e. OSX's default shell)
118 # where the options can't be set, and it's impossible to inherit the trap
119 # into subshells.
120
121 set -o functrace > /dev/null 2>&1
122 shopt -s extdebug > /dev/null 2>&1
123
124 # Finally, install the actual traps.
125 PROMPT_COMMAND="${PROMPT_COMMAND};preexec_invoke_cmd"
126 trap 'preexec_invoke_exec' DEBUG
127}
128
129# Since this is the reason that 99% of everybody is going to bother with a
130# pre-exec hook anyway, we'll include it in this module.
131
132# Change the title of the xterm.
133function preexec_xterm_title () {
134 local title="$1"
135 echo -ne "\033]0;$title\007" > /dev/stderr
136}
137
138function preexec_screen_title () {
139 local title="$1"
140 echo -ne "\033k$1\033\\" > /dev/stderr
141}
142
143# Abbreviate the "user@host" string as much as possible to preserve space in
144# screen titles. Elide the host if the host is the same, elide the user if the
145# user is the same.
146function preexec_screen_user_at_host () {
147 local RESULT=""
148 if [[ "$SCREEN_RUN_HOST" == "$SCREEN_HOST" ]]
149 then
150 return
151 else
152 if [[ "$SCREEN_RUN_USER" == "$USER" ]]
153 then
154 echo -n "@${SCREEN_HOST}"
155 else
156 echo -n "${USER}@${SCREEN_HOST}"
157 fi
158 fi
159}
160
161function preexec_xterm_title_install () {
162 # These functions are defined here because they only make sense with the
163 # preexec_install below.
164 function precmd () {
165 preexec_xterm_title "${TERM} - ${USER}@${SCREEN_HOST} `dirs -0` $PROMPTCHAR"
166 if [[ "${TERM}" == screen ]]
167 then
168 preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}"
169 fi
170 }
171
172 function preexec () {
173 preexec_xterm_title "${TERM} - $1 {`dirs -0`} (${USER}@${SCREEN_HOST})"
174 if [[ "${TERM}" == screen ]]
175 then
176 local cutit="$1"
177 local cmdtitle=`echo "$cutit" | cut -d " " -f 1`
178 if [[ "$cmdtitle" == "exec" ]]
179 then
180 local cmdtitle=`echo "$cutit" | cut -d " " -f 2`
181 fi
182 if [[ "$cmdtitle" == "screen" ]]
183 then
184 # Since stacked screens are quite common, it would be nice to
185 # just display them as '$$'.
186 local cmdtitle="${PROMPTCHAR}"
187 else
188 local cmdtitle=":$cmdtitle"
189 fi
190 preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}$cmdtitle"
191 fi
192 }
193
194 preexec_install
195}