Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: When RPROMPT != RPS1



I went on vacation for a while immediately after this discussion, so it
got left sitting for a bit.

Here's a patch which does several things for the prompt theme system:

- introduces a standard way for themes to clean up after themselves,
  the prompt_cleanup callback (aka hook).  I've modified "prompt bart"
  to use this by way of example.

- added the "default" and "restore" themes.  The "restore" theme is a
  special case that calls the cleanup hooks to put everything back as
  it was before you experimented with some other prompt theme.

- adds documentation for the above plus a new subsection about how to
  write a new theme.

There are still a few tweaks possible, I expect.  There's an awful lot
of state to keep track of here.

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 35ab100..35ce915 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -1920,8 +1920,9 @@ subsect(Installation)
 You should make sure all the functions from the tt(Functions/Prompts)
 directory of the source distribution are available; they all begin with
 the string `tt(prompt_)' except for the special function`tt(promptinit)'.
-You also need the `tt(colors)' function from tt(Functions/Misc).  All of
-these functions may already have been installed on your system; if not,
+You also need the `tt(colors)' and `tt(add-zsh-hook)' functions from
+tt(Functions/Misc).
+All these functions may already be installed on your system; if not,
 you will need to find them and copy them.  The directory should appear as
 one of the elements of the tt(fpath) array (this should already be the
 case if they were installed), and at least the function tt(promptinit)
@@ -1975,6 +1976,75 @@ normally call a theme's setup function directly.
 )
 enditem()
 
+subsect(Utility Themes)
+
+startitem()
+item(tt(prompt off))(
+The theme `tt(off)' sets all the prompt variables to minimal values with
+no special effects.
+)
+item(tt(prompt default))(
+The theme `tt(default)' sets all prompt variables to the same state as
+if an interactive zsh was started with no initialization files.
+)
+item(tt(prompt restore))(
+The special theme `tt(restore)' erases all theme settings and sets prompt
+variables to their state before the first time the `tt(prompt)' function
+was run, provided each theme has properly defined its cleanup (see below).
+
+Note that you can undo `tt(prompt off)' and `tt(prompt default)' with
+`tt(prompt restore)', but a second restore does not undo the first.
+)
+enditem()
+
+subsect(Writing Themes)
+
+The first step for adding your own theme is to choose a name for it,
+and create a file `tt(prompt_var(name)_setup)' in a directory in your
+tt(fpath), such as tt(~/myfns) in the example above.  The file should
+at minimum contain assignments for the prompt variables that your
+theme wishes to modify.  By convention, themes use tt(PS1), tt(PS2),
+tt(RPS1), etc., rather than the longer tt(PROMPT) and tt(RPROMPT).
+
+The file is autoloaded as a function in the current shell context, so
+it may contain any necessary commands to customize your theme, including
+defining additional functions.  To make some complex tasks easier, your
+setup function may also do any of the following:
+
+startitem()
+item(Assign tt(prompt_opts))(
+The array tt(prompt_opts) may be assigned any of tt("bang"), tt("cr"),
+tt("percent"), tt("sp"), and/or tt("subst") as values.  The corresponding
+setopts (tt(promptbang), etc.) are turned on, all other prompt-related
+options are turned off.  The tt(prompt_opts) array preserves setopts even
+beyond the scope of tt(localoptions), should your function need that.
+)
+item(Modify precmd and preexec)(
+Use of tt(add-zsh-hook) is recommended.  The tt(precmd) and tt(preexec)
+hooks are automatically adjusted if the prompt theme changes or is
+disabled.
+)
+item(Declare cleanup)(
+If your function makes any other changes that should be undone when the
+theme is disabled, your setup function may call
+example(prompt_cleanup var(command))
+where var(command) should be suitably quoted.  If your theme is ever
+disabled or replaced by another, var(command) is executed with tt(eval).
+You may declare more than one such cleanup hook.
+)
+item(Define preview)(
+Define or autoload a function tt(prompt_var(name)_preview) to display
+a simulated version of your prompt.  A simple default previewer is
+defined by tt(promptinit) for themes that do not define their own.
+This preview function is called by `tt(prompt -p)'.
+)
+item(Provide help)(
+Define or autoload a function tt(prompt_var(name)_help) to display
+documentation or help text for your theme.
+This help function is called by `tt(prompt -h)'.
+)
+enditem()
+
 texinode(ZLE Functions)(Exception Handling)(Prompt Themes)(User Contributions)
 sect(ZLE Functions)
 
diff --git a/Functions/Prompts/prompt_bart_setup b/Functions/Prompts/prompt_bart_setup
index cb032de..6de4122 100644
--- a/Functions/Prompts/prompt_bart_setup
+++ b/Functions/Prompts/prompt_bart_setup
@@ -16,9 +16,13 @@ prompt_bart_help () {
 	blue, and the default foreground) are used if no arguments are
 	given.  The defaults look best on a light background.
 
-	The "off" token temporarily disables the theme; "on" restores it.
 	No background colors or hardwired cursor motion escapes are used,
 	and it is not necessary to setopt promptsubst.
+
+	The "off" token temporarily disables the theme; "on" restores it.
+	Note, this does NOT fully reset to the original prompt state, it
+	only hides/reveals the extra lines above the command line and
+	removes	the supporting hooks.
 	EOF
     [[ $(read -sek 1 "?${(%):-%S[press return]%s}") == [Qq] ]] &&
 	print -nP '\r%E' && return
@@ -183,7 +187,7 @@ prompt_bart_setup () {
 	add-zsh-hook -D preexec "prompt_*_preexec"
 	functions[TRAPWINCH]="${functions[TRAPWINCH]//prompt_bart_winch}"
 	[[ $prompt_theme[1] = bart ]] && PS1=${${(f)PS1}[-1]}
-	return 1
+	return 1	# Prevent change of $prompt_theme
 	;;
       (on|enable)
 	shift
@@ -224,6 +228,8 @@ prompt_bart_setup () {
     
     add-zsh-hook precmd prompt_bart_precmd
     add-zsh-hook preexec prompt_bart_preexec
+    prompt_cleanup \
+        'functions[TRAPWINCH]="${functions[TRAPWINCH]//prompt_bart_winch}"'
     functions[TRAPWINCH]="${functions[TRAPWINCH]//prompt_bart_winch}
 	prompt_bart_winch"
 
diff --git a/Functions/Prompts/prompt_default_setup b/Functions/Prompts/prompt_default_setup
new file mode 100644
index 0000000..aed74eb
--- /dev/null
+++ b/Functions/Prompts/prompt_default_setup
@@ -0,0 +1,7 @@
+PS1='%m%# '
+PS2='%_> '
+PS3='?# '
+PS4='+%N:%i> '
+unset RPS1 RPS2 RPROMPT RPROMPT2
+
+prompt_opts=( cr percent sp )
diff --git a/Functions/Prompts/prompt_off_setup b/Functions/Prompts/prompt_off_setup
index f604b47..e6d16bf 100644
--- a/Functions/Prompts/prompt_off_setup
+++ b/Functions/Prompts/prompt_off_setup
@@ -1,9 +1,10 @@
 # Very simple prompt
-prompt_off_setup () {
-  PS1="%# "
-  PS2="> "
 
-  prompt_opts=( cr percent )
-}
+prompt_default_setup 2>/dev/null
 
-prompt_off_setup "$@"
+PS1="%# "
+PS2="> "
+PS3='?# '
+PS4='+> '
+
+prompt_opts=( cr percent sp )
diff --git a/Functions/Prompts/prompt_restore_setup b/Functions/Prompts/prompt_restore_setup
new file mode 100644
index 0000000..54c4adb
--- /dev/null
+++ b/Functions/Prompts/prompt_restore_setup
@@ -0,0 +1,2 @@
+# Damn that was easy
+zstyle -t :prompt-theme cleanup
diff --git a/Functions/Prompts/promptinit b/Functions/Prompts/promptinit
index 5872489..e27b877 100644
--- a/Functions/Prompts/promptinit
+++ b/Functions/Prompts/promptinit
@@ -47,20 +47,36 @@ prompt_preview_safely() {
     return
   fi
 
-  local -a psv; psv=($psvar); local -a +h psvar; psvar=($psv) # Ick
-  local +h PS1=$PS1 PS2=$PS2 PS3=$PS3 PS4=$PS4 RPS1=$RPS1
-  local -a precmd_functions preexec_functions
-
-  # The next line is a bit ugly.  It (perhaps unnecessarily)
-  # runs the prompt theme setup function to ensure that if
-  # the theme has a _preview function that it's been autoloaded.
-  prompt_${1}_setup
-
-  if typeset +f prompt_${1}_preview >&/dev/null; then
-    prompt_${1}_preview "$@[2,-1]"
-  else
-    prompt_preview_theme "$@"
-  fi
+  # This handles all the stuff from the default :prompt-theme cleanup
+  local +h PS1=$PS1 PS2=$PS2 PS3=$PS3 PS4=$PS4 RPS1=$RPS1 RPS2=$RPS2
+  local +h PROMPT=$PROMPT RPROMPT=$RPOMPT RPROMPT2=$RPROMPT2 PSVAR=$PSVAR
+  local -a precmd_functions preexec_functions prompt_preview_cleanup
+  local -aLl +h zle_highlight
+
+  {
+    # Save and clear current restore-point if any
+    zstyle -g prompt_preview_cleanup :prompt-theme cleanup
+    {
+      zstyle -d :prompt-theme cleanup
+
+      # The next line is a bit ugly.  It (perhaps unnecessarily)
+      # runs the prompt theme setup function to ensure that if
+      # the theme has a _preview function that it's been autoloaded.
+      prompt_${1}_setup
+
+      if typeset +f prompt_${1}_preview >&/dev/null; then
+        prompt_${1}_preview "$@[2,-1]"
+      else
+        prompt_preview_theme "$@"
+      fi
+    } always {
+      # Run any theme-specific cleanup, then reset restore point
+      zstyle -t :prompt-theme cleanup
+    }
+  } always {
+    (( $#prompt_preview_cleanup )) &&
+      zstyle -e :prompt-theme cleanup "${prompt_preview_cleanup[@]}"
+  }
 }
 
 set_prompt() {
@@ -84,9 +100,9 @@ Use prompt -h <theme> for help on specific themes.'
       setopt localtraps
       if [[ -z "$prompt_theme[1]" ]]; then
         # Not using a prompt theme; save settings
-        local -a psv; psv=($psvar); local -a +h psvar; psvar=($psv) # Ick
-	local +h PS1=$PS1 PS2=$PS2 PS3=$PS3 PS4=$PS4 RPS1=$RPS1
-	local precmd_functions preexec_functions
+        local +h PS1=$PS1 PS2=$PS2 PS3=$PS3 PS4=$PS4 RPS1=$RPS1 RPS2=$RPS2
+        local +h PROMPT=$PROMPT RPROMPT=$RPOMPT RPROMPT2=$RPROMPT2 PSVAR=$PSVAR
+        local -a precmd_functions preexec_functions
       else
         trap 'prompt_${prompt_theme[1]}_setup "${(@)prompt_theme[2,-1]}"' 0
       fi
@@ -104,11 +120,11 @@ Use prompt -h <theme> for help on specific themes.'
        ;;
     h) if [[ -n "$2" && -n $prompt_themes[(r)$2] ]]; then
          if functions prompt_$2_setup >/dev/null; then
-	   # The next line is a bit ugly.  It (perhaps unnecessarily)
-	   # runs the prompt theme setup function to ensure that if
-	   # the theme has a _help function that it's been autoloaded.
-	   prompt_$2_setup
-	 fi
+           # The next line is a bit ugly.  It (perhaps unnecessarily)
+           # runs the prompt theme setup function to ensure that if
+           # the theme has a _help function that it's been autoloaded.
+           prompt_$2_setup
+         fi
          if functions prompt_$2_help >/dev/null; then
            print "Help for $2 theme:\n"
            prompt_$2_help
@@ -168,28 +184,74 @@ Use prompt -h <theme> for help on specific themes.'
   esac
 }
 
+prompt_cleanup () {
+  local -a cleanup_hooks
+  if zstyle -g cleanup_hooks :prompt-theme cleanup
+  then
+    cleanup_hooks+=(';' "$@")
+    zstyle -e :prompt-theme cleanup "${cleanup_hooks[@]}"
+  elif (( $+prompt_preview_cleanup == 0 ))
+  then
+    print -u2 "prompt_cleanup: no prompt theme active"
+    return 1
+  fi
+}
+
 prompt () {
-  local prompt_opts
+  local -a prompt_opts theme_active
 
+  zstyle -g theme_active :prompt-theme cleanup || {
+    # This is done here rather than in set_prompt so that it
+    # is safe and sane for set_prompt to setopt localoptions,
+    # which will be cleared before we arrive back here again.
+    # This is also why we pass around the prompt_opts array.
+    [[ -o promptbang ]] && prompt_opts+=(bang)
+    [[ -o promptcr ]] && prompt_opts+=(cr)
+    [[ -o promptpercent ]] && prompt_opts+=(percent)
+    [[ -o promptsp ]] && prompt_opts+=(sp)
+    [[ -o promptsubst ]] && prompt_opts+=(subst)
+    zstyle -e :prompt-theme cleanup \
+        'zstyle -d :prompt-theme cleanup;' \
+	'prompt_default_setup;' \
+        ${PS1+PS1="${(q)PS1}"} \
+        ${PS2+PS2="${(q)PS2}"} \
+        ${PS3+PS3="${(q)PS3}"} \
+        ${PS4+PS4="${(q)PS4}"} \
+        ${RPS1+RPS1="${(q)RPS1}"} \
+        ${RPS2+RPS2="${(q)RPS2}"} \
+        ${RPROMPT+RPROMPT="${(q)RPROMPT}"} \
+        ${RPROMPT2+RPROMPT2="${(q)RPROMPT2}"} \
+        ${PSVAR+PSVAR="${(q)PSVAR}"} \
+        "precmd_functions=(${(q)precmd_functions[@]})" \
+        "preexec_functions=(${(q)preexec_functions[@]})" \
+        "prompt_opts=( ${prompt_opts[*]} )" \
+        'reply=(yes)'
+  }
   set_prompt "$@"
 
-  (( $#prompt_opts )) &&
-      setopt noprompt{bang,cr,percent,subst} "prompt${^prompt_opts[@]}"
+  (( ${#prompt_opts} )) &&
+      setopt noprompt{bang,cr,percent,sp,subst} "prompt${^prompt_opts[@]}"
 
   true
 }
 
 prompt_preview_theme () {
   emulate -L zsh
-  local -a psv; psv=($psvar); local -a +h psvar; psvar=($psv) # Ick
-  local +h PS1=$PS1 PS2=$PS2 PS3=$PS3 PS4=$PS4 RPS1=$RPS1
-  local precmd_functions preexec_functions prompt_opts
-  local -aLl +h zle_highlight
 
+  # Check for proper state handling
+  (( $+prompt_preview_cleanup )) || {
+    prompt_preview_safely "$@"
+    return
+  }
+
+  # Minimal preview for prompts that don't supply one
+  local -a prompt_opts
   print -n "$1 theme"
   (( $#* > 1 )) && print -n " with parameters \`$*[2,-1]'"
   print ":"
   prompt_${1}_setup "$@[2,-1]"
+  (( ${#prompt_opts} )) &&
+      setopt noprompt{bang,cr,percent,sp,subst} "prompt${^prompt_opts[@]}"
   [[ -n ${precmd_functions[(r)prompt_${1}_precmd]} ]] &&
     prompt_${1}_precmd
   [[ -o promptcr ]] && print -n $'\r'; :



Messages sorted by: Reverse Date, Date, Thread, Author