-
Notifications
You must be signed in to change notification settings - Fork 65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
major changes to bash_startup.in #10
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,257 +1,95 @@ | ||
#!/bin/bash | ||
# This is based on "preexec.bash" but is customized for iTerm2. | ||
# This is inspired by "preexec.bash" but is customized for iTerm2. Sourced, so | ||
# no shebang. This module requires 2 bash features you must not otherwise be | ||
# using: the DEBUG trap and the PROMPT_COMMAND variable. | ||
|
||
# Note: this module requires 2 bash features which you must not otherwise be | ||
# using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. iterm2_preexec_install | ||
# will override these and if you override one or the other this _will_ break. | ||
osc='\033]'; cso='\007' # Beginning, ending escape sequences. | ||
|
||
# This is known to support bash3, as well as *mostly* support bash2.05b. It | ||
# has been tested with the default shells on MacOS X 10.4 "Tiger", Ubuntu 5.10 | ||
# "Breezy Badger", Ubuntu 6.06 "Dapper Drake", and Ubuntu 6.10 "Edgy Eft". | ||
# tmux / screen are not supported; I think this is the theoretical tmux hack? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, this is the correct hack. |
||
[ $TERM = screen ] && osc='\033Ptmux;\033\033]' && cso='\007\033\\' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, It was suggested to me that this would be a better test for TERM:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: That only works on A posix #!/bin/sh
is_tmux_or_screen=f
if [ -n "$TMUX" ]; then
is_tmux_or_screen=t
fi
case "$TERM" in
screen*)
is_tmux_or_screen=t
;;
esac
if [ "$is_tmux_or_screen" = f ]; then
...
fi There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@docwhat Also, while its extremely common, [ -z "$TMUX" ] && case "$TERM" in
screen*) is_tmux_or_screen=t ;; # or, in our use case here, do nothing
''|*) is_tmux_or_screen=f ;; # or, in our case, put our commands here
esac || is_tmux_or_screen=t which is much faster, because we don't execute additional tests if they're not needed. |
||
|
||
orig_ps1=$PS1; prev_ps1=$PS1 # orig is the user-set PS1; prev is our last PS1. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we put an iterm2_ prefix on variables like this to avoid collisions with other scripts? Or does bash scope this sanely? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. You can scope them but |
||
preexec_interactive_mode= # If set: "just executed prompt; waiting for input." | ||
|
||
# Copy screen-run variables from the remote host, if they're available. | ||
[ -z "$iterm2_hostname" ] && iterm2_hostname=$(uname -n) # hostname -f == slow | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ooh, this is good! What are the odds that uname -n won't equal hostname -f? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On most systems it should be identical, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, tried this on my linux machine and was disappointed with the result:
Better keep it as |
||
|
||
# Saved copy of your PS1. This is used to detect if the user changes PS1 | ||
# directly. prev_ps1 will hold the last value that this script set PS1 to | ||
# (including various custom escape sequences). orig_ps1 always holds the last | ||
# user-set value of PS1. | ||
orig_ps1="$PS1" | ||
prev_ps1="$PS1" | ||
# Usage: iterm2_set_user_var key value | ||
iterm2_set_user_var(){ | ||
var=$1; shift; val="$*" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. val needs to be passed through base64 (see the original version, this was lost) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm I had that in there, must have dropped it. |
||
printf $osc'1337;SetUserVar=%s=%s'$cso "$var" "$val" | ||
unset var; unset val | ||
} | ||
|
||
# This variable describes whether we are currently in "interactive mode"; | ||
# i.e. whether this shell has just executed a prompt and is waiting for user | ||
# input. It documents whether the current command invoked by the trace hook is | ||
# run interactively by the user; it's set immediately after the prompt hook, | ||
# and unset as soon as the trace hook is run. | ||
preexec_interactive_mode="" | ||
# Users can write their own version of this method. It should call | ||
# iterm2_set_user_var but not produce any other output. # [ Huh? ] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More details on how to use this at http://iterm2.com/badges.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. To me There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's set_user_vars, not get_user_vars. I agree print would be the opposite of get :) You should be able to do and in your profile set the badge to |
||
iterm2_print_user_vars() { :; } | ||
|
||
# tmux and screen are not supported; even using the tmux hack to get escape | ||
# codes passed through, ncurses interferes and the cursor isn't in the right | ||
# place at the time it's passed through. | ||
if ( [ x"$TERM" != xscreen ] ); then | ||
# Default do-nothing implementation of preexec. | ||
function preexec () { | ||
true | ||
} | ||
iterm2_print_version_number() { | ||
printf $osc'1337;ShellIntegrationVersion=1'$cso | ||
} | ||
|
||
# Default do-nothing implementation of precmd. | ||
function precmd () { | ||
true | ||
} | ||
iterm2_print_state_data() { | ||
printf $osc'1337;RemoteHost=%s@%s'$cso "$USER" "$iterm2_hostname" | ||
printf $osc'1337;CurrentDir=%s'$cso "$PWD" | ||
iterm2_print_user_vars | ||
} | ||
|
||
# This function is installed as the PROMPT_COMMAND; it is invoked before each | ||
# interactive prompt display. It sets a variable to indicate that the prompt | ||
# was just displayed, to allow the DEBUG trap, below, to know that the next | ||
# command is likely interactive. | ||
function iterm2_preexec_invoke_cmd () { | ||
local s=$? | ||
last_hist_ent="$(history 1)"; | ||
precmd; | ||
# This is an iTerm2 addition to try to work around a problem in the | ||
# original preexec.bash. | ||
# When the PS1 has command substitutions, this gets invoked for each | ||
# substitution and each command that's run within the substitution, which | ||
# really adds up. It would be great if we could do something like this at | ||
# the end of this script: | ||
# PS1="$(iterm2_prompt_prefix)$PS1($iterm2_prompt_suffix)" | ||
# and have iterm2_prompt_prefix set a global variable that tells precmd not to | ||
# output anything and have iterm2_prompt_suffix reset that variable. | ||
# Unfortunately, command substitutions run in subshells and can't | ||
# communicate to the outside world. | ||
# Instead, we have this workaround. We save the original value of PS1 in | ||
# $orig_ps1. Then each time this function is run (it's called from | ||
# PROMPT_COMMAND just before the prompt is shown) it will change PS1 to a | ||
# string without any command substitutions by doing eval on orig_ps1. At | ||
# this point preexec_interactive_mode is still the empty string, so preexec | ||
# won't produce output for command substitutions. | ||
|
||
if [[ "$PS1" != "$prev_ps1" ]] | ||
then | ||
export orig_ps1="$PS1" | ||
fi | ||
|
||
# Get the value of the prompt prefix, which will change $? | ||
local iterm2_prompt_prefix_value="$(iterm2_prompt_prefix)" | ||
|
||
# Reset $? to its saved value, which might be used in $orig_ps1. | ||
sh -c "exit $s" | ||
|
||
# Set PS1 to various escape sequences, the user's preferred prompt, and more escape sequences. | ||
export PS1="\[$iterm2_prompt_prefix_value\]$orig_ps1\[$(iterm2_prompt_suffix)\]" | ||
|
||
# Save the value we just set PS1 to so if the user changes PS1 we'll know and we can update orig_ps1. | ||
export prev_ps1="$PS1" | ||
sh -c "exit $s" | ||
|
||
# This must be the last line in this function, or else | ||
# iterm2_preexec_invoke_exec will do its thing at the wrong time. | ||
preexec_interactive_mode="yes"; | ||
} | ||
|
||
# This function is installed as the DEBUG trap. It is invoked before each | ||
# interactive prompt display. Its purpose is to inspect the current | ||
# environment to attempt to detect if the current command is being invoked | ||
# interactively, and invoke 'preexec' if so. | ||
function iterm2_preexec_invoke_exec () { | ||
if [ ! -t 1 ] | ||
then | ||
# We're in a piped subshell (STDOUT is not a TTY) like | ||
# (echo -n A; sleep 1; echo -n B) | wc -c | ||
# ...which should return "2". | ||
return | ||
fi | ||
if [[ -n "$COMP_LINE" ]] | ||
then | ||
# We're in the middle of a completer. This obviously can't be | ||
# an interactively issued command. | ||
return | ||
fi | ||
if [[ -z "$preexec_interactive_mode" ]] | ||
then | ||
# We're doing something related to displaying the prompt. Let the | ||
# prompt set the title instead of me. | ||
return | ||
else | ||
# If we're in a subshell, then the prompt won't be re-displayed to put | ||
# us back into interactive mode, so let's not set the variable back. | ||
# In other words, if you have a subshell like | ||
# (sleep 1; sleep 2) | ||
# You want to see the 'sleep 2' as a set_command_title as well. | ||
if [[ 0 -eq "$BASH_SUBSHELL" ]] | ||
then | ||
preexec_interactive_mode="" | ||
fi | ||
fi | ||
if [[ "iterm2_preexec_invoke_cmd" == "$BASH_COMMAND" ]] | ||
then | ||
# Sadly, there's no cleaner way to detect two prompts being displayed | ||
# one after another. This makes it important that PROMPT_COMMAND | ||
# remain set _exactly_ as below in iterm2_preexec_install. Let's switch back | ||
# out of interactive mode and not trace any of the commands run in | ||
# precmd. | ||
|
||
# Given their buggy interaction between BASH_COMMAND and debug traps, | ||
# versions of bash prior to 3.1 can't detect this at all. | ||
preexec_interactive_mode="" | ||
return | ||
fi | ||
|
||
# In more recent versions of bash, this could be set via the "BASH_COMMAND" | ||
# variable, but using history here is better in some ways: for example, "ps | ||
# auxf | less" will show up with both sides of the pipe if we use history, | ||
# but only as "ps auxf" if not. | ||
hist_ent="$(history 1)"; | ||
local prev_hist_ent="${last_hist_ent}"; | ||
last_hist_ent="${hist_ent}"; | ||
if [[ "${prev_hist_ent}" != "${hist_ent}" ]]; then | ||
local this_command="$(echo "${hist_ent}" | sed -e "s/^[ ]*[0-9]*[ ]*//g")"; | ||
else | ||
local this_command=""; | ||
fi; | ||
|
||
# If none of the previous checks have earlied out of this function, then | ||
# the command is in fact interactive and we should invoke the user's | ||
# preexec hook with the running command as an argument. | ||
preexec "$this_command"; | ||
} | ||
|
||
# Execute this to set up preexec and precmd execution. | ||
function iterm2_preexec_install () { | ||
|
||
# *BOTH* of these options need to be set for the DEBUG trap to be invoked | ||
# in ( ) subshells. This smells like a bug in bash to me. The null stackederr | ||
# redirections are to quiet errors on bash2.05 (i.e. OSX's default shell) | ||
# where the options can't be set, and it's impossible to inherit the trap | ||
# into subshells. | ||
|
||
set -o functrace > /dev/null 2>&1 | ||
shopt -s extdebug > /dev/null 2>&1 | ||
|
||
# Finally, install the actual traps. | ||
if ( [ x"$PROMPT_COMMAND" = x ]); then | ||
PROMPT_COMMAND="iterm2_preexec_invoke_cmd"; | ||
else | ||
# If there's a trailing semicolon folowed by spaces, remove it (issue 3358). | ||
PROMPT_COMMAND="$(echo -n $PROMPT_COMMAND | sed -e 's/; *$//'); iterm2_preexec_invoke_cmd"; | ||
fi | ||
trap 'iterm2_preexec_invoke_exec' DEBUG; | ||
} | ||
|
||
# -- begin iTerm2 customization | ||
|
||
function iterm2_begin_osc { | ||
printf "\033]" | ||
} | ||
|
||
function iterm2_end_osc { | ||
printf "\007" | ||
} | ||
|
||
# Runs after interactively edited command but before execution | ||
function preexec() { | ||
iterm2_begin_osc | ||
printf "133;C" | ||
iterm2_end_osc | ||
} | ||
|
||
function iterm2_print_state_data() { | ||
iterm2_begin_osc | ||
printf "1337;RemoteHost=%s@%s" "$USER" "$iterm2_hostname" | ||
iterm2_end_osc | ||
|
||
iterm2_begin_osc | ||
printf "1337;CurrentDir=%s" "$PWD" | ||
iterm2_end_osc | ||
|
||
iterm2_print_user_vars | ||
} | ||
|
||
# Usage: iterm2_set_user_var key value | ||
function iterm2_set_user_var() { | ||
iterm2_begin_osc | ||
printf "1337;SetUserVar=%s=%s" "$1" $(printf "%s" "$2" | base64) | ||
iterm2_end_osc | ||
} | ||
|
||
# Users can write their own version of this method. It should call | ||
# iterm2_set_user_var but not produce any other output. | ||
function iterm2_print_user_vars() { | ||
true | ||
} | ||
|
||
function iterm2_prompt_prefix() { | ||
iterm2_begin_osc | ||
printf "133;D;\$?" | ||
iterm2_end_osc | ||
|
||
iterm2_print_state_data | ||
|
||
iterm2_begin_osc | ||
printf "133;A" | ||
iterm2_end_osc | ||
} | ||
|
||
function iterm2_prompt_suffix() { | ||
iterm2_begin_osc | ||
printf "133;B" | ||
iterm2_end_osc | ||
} | ||
|
||
function iterm2_print_version_number() { | ||
iterm2_begin_osc | ||
printf "1337;ShellIntegrationVersion=1" | ||
iterm2_end_osc | ||
} | ||
|
||
|
||
# If hostname -f is slow on your system, set iterm2_hostname before sourcing this script. | ||
if [ -z "$iterm2_hostname" ]; then | ||
iterm2_hostname=$(hostname -f) | ||
fi | ||
iterm2_preexec_install | ||
|
||
# This is necessary so the first command line will have a hostname and current directory. | ||
iterm2_prompt_prefix () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency with functions above, this should be named iterm2_print_prompt_prefix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right |
||
printf $osc'133;D;'$?$cso | ||
iterm2_print_state_data | ||
iterm2_print_version_number | ||
fi | ||
printf $osc'133;A'$cso | ||
} | ||
|
||
iterm2_prompt_suffix() { | ||
printf $osc'133;B'$cso | ||
} | ||
|
||
preexec() { | ||
printf $osc'133;C'$cso | ||
} | ||
|
||
# This function is installed as bash PROMPT_COMMAND; invoked before each | ||
# interactive prompt display. It sets preexec_interactive_mode for DEBUG trap. | ||
iterm2_preexec_invoke_cmd() { | ||
status=$? | ||
last_command="$(echo $(fc -ln | tail -1))"; (exit $status) # Reset $? back. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this different than $(history 1)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its basically equivalent, but it's in POSIX. In shells like |
||
|
||
PS1="\[$(iterm2_prompt_prefix)\]$orig_ps1\[$(iterm2_prompt_suffix)\]" | ||
prev_ps1=$PS1; (exit $status) # Save PS1 in case user changes PS1; reset $?. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prev_ps1 is assigned to but never read. If the user changes PS1 their change will get lost since orig_ps1 won't ever be changed. |
||
|
||
preexec_interactive_mode=yes # This must be the last line in this function. | ||
} | ||
|
||
# This function is the DEBUG trap. It inspects the current environment, and | ||
# if it is ready to be invoked interactively, it 'preexec's the command line. | ||
iterm2_preexec_invoke_exec() { | ||
[ ! -t 1 ] && return # We're in a piped subshell (STDOUT is not a TTY). | ||
[ -n "$COMP_LINE" ] && return # We're in the middle of a completion. | ||
[ -z "$preexec_interactive_mode" ] && return # We're displaying prompt. | ||
[ -"$BASH_SUBSHELL"- = -"0"- ] && preexec_interactive_mode= | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a comment |
||
[ -"iterm2_preexec_invoke_cmd"- = -"$BASH_COMMAND"- ] && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original comment for this one didn't make much sense. There's a somewhat more lucid and modern version of it here, although it's still confusing to me (maybe you'll understand it?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, but its definitely confusing. |
||
preexec_interactive_mode= && return | ||
|
||
fc=$(echo $(fc -ln | tail -1)); prev_fc=$last_fc; last_fc=$fc | ||
[ x"$prev_fc" != x"$fc" ] && this_command=$fc || this_command= | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bit of logic is of dubious value. It comes from the original bash_preexec code (http://www.twistedmatrix.com/users/glyph/preexec.bash.txt) but the github.com/rcaloras/bash-preexec version does not perform this test. Take a look at what he's doing: he avoids calling preexec with a null string, which might be good to do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I actually didn't particularly look at this one very hard, I was too busy just removing all the |
||
|
||
preexec "$this_command" | ||
} | ||
|
||
iterm2_preexec_install() { | ||
[ -z "$BASH" ] && set -o functrace >/dev/null 2>&1 && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If $BASH is unset that means the shell is not bash, I presume, but then how did we get this far? What's the right thing to do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Precisely. Again, I'm trying to make some of this reusable for other shells. I think it would be ideal if there weren't a need for a separate file for other Almquist/Korn compatible shells. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. I haven't seen a Korn shell in use since the late 90s but it would be cool to support other shells, esp. if it doesn't require maintaining more versions of this script. But please add a prominent comment to the top of the file indicating which shells any change should be tested with so we don't break anything in the future. |
||
shopt -s extdebug >/dev/null 2>&1 | ||
|
||
PROMPT_COMMAND=iterm2_preexec_invoke_cmd | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to respect the existing PROMPT_COMMAND (the comment at the top about us owning it is wrong); in doing so, please preserve the behavior noted in the original about trailing semicolons and the reference to issue 3358. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll look at this. As it was written before, in everything I traced, it never actually got to that line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something may have broken. The test case is this: |
||
trap 'iterm2_preexec_invoke_exec' DEBUG | ||
} | ||
|
||
[ $TERM != screen ] && # This needs to be disabled? Won't it just, not work? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It works OK in tmux integration mode but fails badly in regular tmux mode. Marks get left all over the screen. So it should be off by default but easy to turn back on for users who only use tmux integration mode. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gnachman Got it. Give me a bit to make some of these changes and I'll push another commit to this same PR. |
||
case $- in # Portable way to check if we're in an interactive login shell. | ||
*i*) iterm2_preexec_install | ||
iterm2_print_state_data | ||
iterm2_print_version_number | ||
;; | ||
*) : | ||
;; | ||
esac | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not use more than one command per line. I find it harder to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't seen \007 called "CSO" before. Where does the acronym come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough.
I just wanted a short variable name and couldn't think of one. It's just OSC backwards. Was kinda thinking like
case esac
,if fi
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BEL is the "official" name for \007, may as well use that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. right. Probably use
esc
instead ofosc
then too?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's actually an OSC (Operating System Command).
ESC + ]
= OSC, per ECMA 48 and various other standards. I use the xterm docs as the most convenient almanac for this stuff: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html