From b780b2ce34f482b8d22492018507ef582ec01598 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Wed, 11 Sep 2024 19:08:14 +0200 Subject: [PATCH] Improve bash/zsh/terminal consistency --- bash/ext | 37 +++++++++++++++++++++++++------------ bash/history | 18 +++++++++++++----- bash/prompt | 25 +++++++++++++++++++++++++ bash/rc | 11 +++++++---- bash/term_title | 23 +++++++++++++++-------- zsh/history | 20 ++++++++++++++++++-- zsh/prompt | 26 ++++++++++++++++++++++++++ zsh/rc | 5 ++++- zsh/term_title | 45 +++++++++++++++++++++++++++------------------ 9 files changed, 160 insertions(+), 50 deletions(-) diff --git a/bash/ext b/bash/ext index 729f5ce..a645710 100644 --- a/bash/ext +++ b/bash/ext @@ -66,8 +66,17 @@ __debug_invoke_preexec () { local this_command; - # TODO: hack: doesn't work when command is not added to history - this_command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`; + [ -n "$COMP_LINE" ] && return # completion + [ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return # precmd + + # get current command by reading last history item + # contrary to zsh, the command needs to be inserted in the history + # so it may not work reliably depending on HISTCONTROL/HISTIGNORE + # - ignoredups is fine: last command stays the same + # - erasedups is fine: last command is inserted, older duplicates are removed + # - ignorespace is not fine (and therefore so is ignoreboth): never inserts + # - HISTIGNORE is not fine: never inserts + this_command="$(history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g")"; preexec "$this_command" } @@ -126,7 +135,7 @@ clear_incomplete_line() { fi } -function sub_prompt_colors_unsized() { +function sub_zprompt_unsized() { sed \ -e 's#%F{black}#\\033[30m#g' \ -e 's#%F{red}#\\033[31m#g' \ @@ -136,10 +145,11 @@ function sub_prompt_colors_unsized() { -e 's#%F{magenta}#\\033[35m#g' \ -e 's#%F{cyan}#\\033[36m#g' \ -e 's#%F{white}#\\033[37m#g' \ - -e 's#%f#\\033[00m#g' + -e 's#%f#\\033[00m#g' \ + -e 's#%{\(\([^%]\)*\)%}#\1#g' } -function sub_prompt_colors_sized() { +function sub_zprompt_sized() { sed \ -e 's#%F{black}#\\[\\033[30m\\]#g' \ -e 's#%F{red}#\\[\\033[31m\\]#g' \ @@ -149,10 +159,11 @@ function sub_prompt_colors_sized() { -e 's#%F{magenta}#\\[\\033[35m\\]#g' \ -e 's#%F{cyan}#\\[\\033[36m\\]#g' \ -e 's#%F{white}#\\[\\033[37m\\]#g' \ - -e 's#%f#\\[\\033[00m\\]#g' + -e 's#%f#\\[\\033[00m\\]#g' \ + -e 's#%{\(\([^%]\)*\)%}#\\[\1\\]#g' } -function strip_prompt_colors() { +function strip_zprompt() { sed \ -e 's#%F{black}##g' \ -e 's#%F{red}##g' \ @@ -162,15 +173,17 @@ function strip_prompt_colors() { -e 's#%F{magenta}##g' \ -e 's#%F{cyan}##g' \ -e 's#%F{white}##g' \ - -e 's#%f##g' + -e 's#%f##g' \ + -e 's#%{\(\([^%]\)*\)%}##g' } # right prompt support and PROMPT/RPROMPT vars function apply_prompt_rprompt() { - local rprompt=$(echo "${RPROMPT}" | sub_prompt_colors_unsized) - local prompt=$(echo "${PROMPT}" | sub_prompt_colors_sized) - #local rprompt=$(echo "${RPROMPT}" | strip_prompt_colors) - #local prompt=$(echo "${PROMPT}" | strip_prompt_colors) + local rprompt=$(echo "${RPROMPT}" | sub_zprompt_unsized) + local prompt=$(echo "${PROMPT}" | sub_zprompt_sized) + #local rprompt=$(echo "${RPROMPT}" | strip_zprompt) + #local prompt=$(echo "${PROMPT}" | strip_zprompt) + if [[ -n "${RPROMPT}" ]]; then PS1="$(printf "\[%*s\r\]%s" "${COLUMNS}" "${rprompt:-}" "${prompt:-}")" else diff --git a/bash/history b/bash/history index 425f97f..5150107 100644 --- a/bash/history +++ b/bash/history @@ -1,16 +1,24 @@ # ignore repeated, space-started, and casual commands -export HISTIGNORE="&:[ ]*:l[sl]:[bf]g:exit:cd .." +# export HISTIGNORE="&:[ ]*:l[sl]:[bf]g:exit:cd .." +# ^ disabled until bash/ext preexec is fixed +export HISTIGNORE="" # enable multiline historization as a single line shopt -s cmdhist -# enable appending to histfile +# enable appending to histfile on exit shopt -s histappend # ignore sequential duplicates -export HISTCONTROL=ignoreboth +export HISTCONTROL=ignoredups -# more! -export HISTSIZE=10000 +# more! (live) +export HISTSIZE=100000 + +# all! (persisted) +export HISTFILESIZE=1000000 + +# share with zsh +export HISTFILE=~/.history # vim: ft=bash diff --git a/bash/prompt b/bash/prompt index ef9008f..c891971 100644 --- a/bash/prompt +++ b/bash/prompt @@ -132,4 +132,29 @@ set_prompt() { fi } +mark_prompt() { + local mark_a='%{\e]133;A\a%}' + local mark_b='%{\e]133;B\a%}' + + PROMPT="${mark_a}${PROMPT}${mark_b}" +} + +mark_command_exec() { + if [[ $# -eq 0 ]]; then + local mark_c="\e]133;C\a" + printf "${mark_c}" "${command}" + else + local command="${1:-}" + local mark_c="\e]133;C;cmdline=%q\a" + printf "${mark_c}" "${command}" + fi +} + +mark_command_exit() { + local rc="${1:-}" + local mark_d="\e]133;D${rc};\a" + + printf "${mark_d}" +} + # vim: ft=bash diff --git a/bash/rc b/bash/rc index bc831fa..72e615d 100644 --- a/bash/rc +++ b/bash/rc @@ -18,7 +18,7 @@ source $DOTFILES_SHELL_DIR/chruby source $DOTFILES_BASH_DIR/fzf source $DOTFILES_SHELL_DIR/direnv source $DOTFILES_SHELL_DIR/kd -source $DOTFILES_BASH_DIR/kitty +#source $DOTFILES_BASH_DIR/kitty source $DOTFILES_SHELL_DIR/git_prompt_info GIT_PS1_SHOWDIRTYSTATE=1 @@ -28,6 +28,8 @@ GIT_PS1_SHOWUNTRACKEDFILES=1 precmd() { CMD_RC=$? + mark_command_exit "${CMD_RC}" + # if [[ -n ${CMD_START} ]]; then # CMD_END="${EPOCHREALTIME}" # CMD_DURATION=$(bc <<<"${CMD_END} - ${CMD_START}") @@ -38,20 +40,21 @@ precmd() { _direnv_hook #clear_incomplete_line set_prompt - apply_prompt_rprompt - update_terminal_cwd set_term_title + mark_prompt + apply_prompt_rprompt } preexec() { set_term_title CMD_START="${EPOCHREALTIME}" + mark_command_exec "$1" } chpwd() { __git_ps1_gitdir _gopath - _auto-chruby + if _has-chruby; then _auto-chruby; fi } chpwd diff --git a/bash/term_title b/bash/term_title index 443c915..9d739e5 100644 --- a/bash/term_title +++ b/bash/term_title @@ -1,4 +1,4 @@ -# Tell the terminal about the working directory at each prompt. +# Tell Apple Terminal about the working directory at each prompt. if [ "$TERM_PROGRAM" == "Apple_Terminal" ] && [ -z "$INSIDE_EMACS" ]; then update_terminal_cwd() { # Identify the directory using a "file:" scheme URL, @@ -14,15 +14,22 @@ else fi set_term_title() { + if [[ -n "${SSH_CLIENT}" ]]; then + local title="$USER@${HOSTNAME%%.*}:${PWD/#$HOME/\~}" + else + local title="${PWD/#$HOME/\~}" + fi + + update_terminal_cwd + case $TERM in - xterm*|rxvt*|alacritty) - local title="\033]2;$USER@${HOSTNAME%%.*}:${PWD/#$HOME/~}\a" - ;; - *) - local title="" - ;; + screen*|xterm*|rxvt*|alacritty) + printf "\e]2;%s\a" "${title}" + ;; + *) + : # NOOP + ;; esac - printf "$title" } # vim: ft=bash diff --git a/zsh/history b/zsh/history index 0a253a6..7bc26e6 100644 --- a/zsh/history +++ b/zsh/history @@ -1,13 +1,29 @@ # ignore sequential duplicates setopt hist_ignore_dups + +# ignore space-started setopt hist_ignore_space setopt hist_reduce_blanks + +# append on exit setopt append_history + +# perform history expansion setopt hist_verify + +# do not load from persistence on every invocation unsetopt share_history -HISTSIZE=100000 -SAVEHIST=100000 +# more! (live) +export HISTSIZE=100000 + +# all! (persisted) +export SAVEHIST=1000000 + +# share with bash +export HISTFILE=~/.history + +# ignore repeated, space-started, and casual commands HISTORY_IGNORE="(^ +|ls|bg|fg|pwd|exit|cd ..)" # vim: ft=zsh diff --git a/zsh/prompt b/zsh/prompt index 804ecce..4f74320 100644 --- a/zsh/prompt +++ b/zsh/prompt @@ -184,6 +184,7 @@ set_prompt() { [[ -n "${IN_NIX_SHELL}" ]] && buffer="${buffer} %F{yellow}nix" buffer="${buffer}%f> " + PROMPT="${buffer}" local rbuffer="" @@ -203,4 +204,29 @@ set_prompt() { fi } +mark_prompt() { + local mark_a=$'%{\e]133;A\a%}' + local mark_b=$'%{\e]133;B\a%}' + + PROMPT="${mark_a}${PROMPT}${mark_b}" +} + +mark_command_exec() { + if [[ $# -eq 0 ]]; then + local mark_c="\e]133;C\a" + printf "${mark_c}" "${command}" + else + local command="${1:-}" + local mark_c="\e]133;C;cmdline=%q\a" + printf "${mark_c}" "${command}" + fi +} + +mark_command_exit() { + local rc="${1:-}" + local mark_d="\e]133;D${rc};\a" + + printf "${mark_d}" +} + # vim: ft=zsh diff --git a/zsh/rc b/zsh/rc index 97deea4..6a2af26 100644 --- a/zsh/rc +++ b/zsh/rc @@ -14,7 +14,6 @@ source $DOTFILES_ZSH_DIR/prompt source $DOTFILES_ZSH_DIR/fzf source $DOTFILES_SHELL_DIR/go source $DOTFILES_SHELL_DIR/direnv -source $DOTFILES_ZSH_DIR/kitty set -o ignoreeof unsetopt BEEP @@ -30,6 +29,8 @@ zmodload zsh/datetime precmd() { CMD_RC=$? + mark_command_exit "${CMD_RC}" + if [[ -n ${CMD_START} ]]; then CMD_END="${EPOCHREALTIME}" CMD_DURATION=$(( ${CMD_END} - ${CMD_START} )) @@ -41,11 +42,13 @@ precmd() { psvar=() set_prompt set_term_title + mark_prompt } preexec() { set_term_title CMD_START="${EPOCHREALTIME}" + mark_command_exec "$1" } chpwd() { diff --git a/zsh/term_title b/zsh/term_title index de1bf30..690801d 100644 --- a/zsh/term_title +++ b/zsh/term_title @@ -1,28 +1,37 @@ +# Tell Apple Terminal about the working directory at each prompt. +if [[ "$TERM_PROGRAM" == "Apple_Terminal" ]] && [[ -z "$INSIDE_EMACS" ]]; then + update_terminal_cwd() { + # Identify the directory using a "file:" scheme URL, + # including the host name to disambiguate local vs. + # remote connections. Percent-escape spaces. + local SEARCH=' ' + local REPLACE='%20' + local PWD_URL="file://$HOSTNAME${PWD//$SEARCH/$REPLACE}" + printf '\e]7;%s\a' "$PWD_URL" + } +else + update_terminal_cwd() { :; } +fi + # Set terminal title set_term_title() { [[ -o interactive ]] || return - # Bubble information up to the terminal - case $TERM_PROGRAM in - Apple_Terminal) - local SEARCH=' ' - local REPLACE='%20' - local PWD_URL="file://$HOSTNAME${PWD//$SEARCH/$REPLACE}" - printf '\e]7;%s\a' "$PWD_URL" - ;; - *) - # NOOP - ;; - esac + + if [[ -n "${SSH_CLIENT}" ]]; then + local title="$USER@${HOSTNAME%%.*}:${PWD/#$HOME/~}" + else + local title="${PWD/#$HOME/~}" + fi + + update_terminal_cwd + case $TERM in - screen*) + screen*|xterm*|rxvt*|alacritty) #print -Pn "\ek%n@%m: %~\e\\" #breaks tmux - print -Pn "\e]2;%n@%m: %~\a" - ;; - xterm*|*rxvt*) - print -Pn "\e]2;%n@%m: %~\a" + printf "\e]2;%s\a" "${title}" ;; *) - # NOOP + : # NOOP ;; esac }