mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 18:10:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1120 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			1120 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| # Function to install startup files for a new user.
 | |
| # Currently it only creates or edits .zshrc.
 | |
| #
 | |
| # It can be run again by giving it the option "-f".
 | |
| 
 | |
| # Sanitize environment.
 | |
| emulate -L zsh
 | |
| setopt extendedglob nonomatch warncreateglobal
 | |
| 
 | |
| # How the function will be referred to.
 | |
| local myname=zsh-newuser-install
 | |
| 
 | |
| # Quick test not requiring any setting up.
 | |
| # Don't run if we're root.  (These variables are provided by the shell.)
 | |
| if (( EUID == 0 || UID == 0 )); then
 | |
|   if [[ $1 = -f ]]; then
 | |
|     print -r "$myname: won't run as root.  Read the manual." >&2
 | |
|   fi
 | |
|   return 1
 | |
| fi
 | |
| 
 | |
| # clear is missing in some Cygwin configurations (lacking ncurses)
 | |
| if ! ( clear >/dev/null 2>/dev/null ); then
 | |
|   if zmodload zsh/termcap 2>/dev/null; then
 | |
|     clear() { echotc cl; }
 | |
|   else
 | |
|     clear() { print -n "\e[H\e[J"; }
 | |
|   fi
 | |
| fi
 | |
| 
 | |
| # The directory in which to look for and save .zshrc.
 | |
| local zd=${ZDOTDIR:-$HOME}
 | |
| # The same directory in a user friendly form, i.e. with ~ replacement.
 | |
| # (We don't want to use glob_subst since that has other side effects.)
 | |
| local zdmsg
 | |
| # The message used if an other blank .zshrc is created.
 | |
| local msg="# Created by newuser for $ZSH_VERSION"
 | |
| # The lines marking the start and end of the section edited.
 | |
| local startline="# Lines configured by $myname"
 | |
| local endline="# End of lines configured by $myname"
 | |
| # Prompts used for reading a key.  The initial "?" is required.
 | |
| local shortprompt="?--- Type a key --- "
 | |
| local longprompt="?--- Type one of the keys in parentheses --- "
 | |
| # Prefix for all temporary files.  Any files starting with this
 | |
| # will be removed at the end of the script.
 | |
| local tmpfile=${TMPPREFIX:-/tmp/zsh}-zni-$$
 | |
| # Report of the state of settings for the top-level menu.
 | |
| local -A install_state
 | |
| # Values of all parameters etc. to be saved (including
 | |
| # those read in from the existing file.)
 | |
| local -A parsed_parameters parsed_options parsed_bindings parsed_keymaps
 | |
| # Corresponding state in a user-readable form.
 | |
| local -A state_parameters state_options state_bindings state_keymaps
 | |
| # Indicate whether an option defaults on or off.
 | |
| local -A default_options
 | |
| # Lines read in from between $startline and $endline which were
 | |
| # not understood.  These are retained but moved out of that section
 | |
| # with a message.
 | |
| local -a unparsed
 | |
| # Lines used in submenus: the setting to output in a form
 | |
| # that can be exeucuted (an assignment, setopt or unsetopt), a brief message
 | |
| # about the setting, and the state copied from and to state_parameters or
 | |
| # state_options.  Elements of all three arrays must correspond.
 | |
| local -a output_lines display_lines state_lines
 | |
| # Variable indicating some of the lines in the above variables
 | |
| # have been read in, i.e. the user has already configured the
 | |
| # particular set of settings.
 | |
| integer lines_read
 | |
| # Lines to set up completion.  This is special as it is only
 | |
| # edited by compinstall, not this function.
 | |
| local -a completion_lines
 | |
| # Utility variables
 | |
| local -a reply match mbegin mend
 | |
| # Key read from user, used all over the place.
 | |
| local key
 | |
| # For default replies from read
 | |
| local REPLY
 | |
| integer save lines_found
 | |
| 
 | |
| install_state[history]=Recommended
 | |
| install_state[completion]=Recommended
 | |
| install_state[bindkey]=Recommended
 | |
| 
 | |
| # Don't save anything if interrupted.
 | |
| trap 'save=0' HUP INT QUIT
 | |
| 
 | |
| # Substitute an initial ~ for human consumption.
 | |
| if [[ $zd = $HOME(#b)(|/*) ]]; then
 | |
|   zdmsg="~$match[1]"
 | |
| else
 | |
|   zdmsg=$zd
 | |
| fi
 | |
| 
 | |
| # Don't run if we can't write to $zd.
 | |
| # Assume this is a temporary condition and exit silently---
 | |
| # if this really is a new user this probably isn't the right
 | |
| # time for screeds of explanation.
 | |
| if [[ ! -w $zd ]]; then
 | |
|   if [[ $1 = -f ]]; then
 | |
|     print -r "$myname: can't write to $zdmsg." >&2
 | |
|   fi
 | |
|   return 1
 | |
| fi
 | |
| 
 | |
| # Don't run unless we can talk to the user.
 | |
| if [[ ! -t 0 || ! -t 1 ]]; then
 | |
|   if [[ $1 = -f ]]; then
 | |
|     print -r "$myname: can only be used interactively." >&2
 | |
|   fi
 | |
|   return 1
 | |
| fi
 | |
| 
 | |
| # Don't run unless terminal is sane.
 | |
| if (( ${LINES:-0} < 15 || ${COLUMNS:-0} < 72 )); then
 | |
|   return 1
 | |
| fi
 | |
| 
 | |
| if [[ $1 != -f ]]; then
 | |
|   # The zsh/newuser module already tests for the following, so this test only
 | |
|   # triggers if zsh-newuser-install is run by hand.
 | |
|   if [[ -e $zd/.zshenv || -e $zd/.zprofile || \
 | |
|         -e $zd/.zshrc || -e $zd/.zlogin ]]; then
 | |
|     print -r "$myname:  startup files exist, aborting.
 | |
| 
 | |
| Use the argument -f if you want to force the function to be run again." >&2
 | |
|     return 1
 | |
|   fi
 | |
| fi
 | |
| 
 | |
| # start of try block for tidy-up in always block
 | |
| {
 | |
| 
 | |
| ########################################################################
 | |
| # Utility functions
 | |
| ########################################################################
 | |
| 
 | |
| # All internal functions start with __zni_.  These will be removed
 | |
| # when the main function exits.
 | |
| 
 | |
| # Read existing lines from .zshrc, if any.
 | |
| __zni_retrieve_lines() {
 | |
|   local line
 | |
| 
 | |
|   reply=()
 | |
| 
 | |
|   lines_found=0
 | |
| 
 | |
|   [[ -f $zd/.zshrc ]] || return 1
 | |
| 
 | |
|   grep "$startline" $zd/.zshrc 1>/dev/null 2>&1 || return 1
 | |
| 
 | |
|   lines_found=1
 | |
| 
 | |
|   sed -n "/^[	]*$startline/,/^[	]*$endline/p" $zd/.zshrc |
 | |
|   while read -r line; do
 | |
|     reply+=($line)
 | |
|   done
 | |
| 
 | |
|   return 0
 | |
| }
 | |
| 
 | |
| 
 | |
| # First argument is a state; other arguments are lines
 | |
| # to parse.  They should either contain single assignments or
 | |
| # setopt or unsetopt statements.  The state for each parameter
 | |
| # or option so parsed is set to the value given by the first argument.
 | |
| __zni_parse_lines() {
 | |
|   local line opt warned first
 | |
|   local -a args
 | |
|   local state=$1
 | |
| 
 | |
|   shift
 | |
| 
 | |
|   for line in "$@"; do
 | |
|     case $line in
 | |
|       ((#b)[[:blank:]]#([[:IDENT:]]##)=(*))
 | |
|       parsed_parameters[$match[1]]=$match[2]
 | |
|       state_parameters[$match[1]]=$state
 | |
|       ;;
 | |
| 
 | |
|       ((#b)[[:blank:]]#(un|)setopt[[:blank:]]##(*))
 | |
|       # TBD: handle setopt noX / unsetopt X
 | |
|       for opt in ${=match[2]}; do
 | |
| 	opt=${${opt//(#m)[[:upper:]]/${(L)MATCH}}//_}
 | |
| 	if [[ $match[1] = un ]]; then
 | |
| 	  parsed_options[$opt]=off
 | |
| 	else
 | |
| 	  parsed_options[$opt]=on
 | |
| 	fi
 | |
| 	state_options[$opt]=$state
 | |
|       done
 | |
|       ;;
 | |
| 
 | |
|       ((#b)[[:blank:]]#bindkey[[:blank:]]##(*))
 | |
|       args=(${(z)match[1]})
 | |
|       # store keys unquoted: will need quoting for output.
 | |
|       first=${(Q)args[1]}
 | |
|       shift args
 | |
|       if [[ $first = -[ev] && ${#args} -eq 0 ]]; then
 | |
| 	case $first in
 | |
| 	  (-e)
 | |
| 	  parsed_keymaps[main]=emacs
 | |
| 	  ;;
 | |
| 
 | |
| 	  (-v)
 | |
| 	  parsed_keymaps[main]=vi
 | |
| 	  ;;
 | |
| 	esac
 | |
| 	state_keymaps[main]=$state
 | |
|       else
 | |
| 	# TODO: handling keymap options
 | |
| 	parsed_bindings[first]=${args[2,-1]}
 | |
| 	state_bindings[first]=$state
 | |
|       fi
 | |
|       ;;
 | |
| 
 | |
|       ([[:blank:]]#($startline|$endline|))
 | |
|       ;;
 | |
| 
 | |
|       (*)
 | |
|       unparsed+=($line)
 | |
|       print -r "WARNING: failed to understand line:
 | |
|   $line
 | |
| which will be retained but not edited."
 | |
|       warned=1
 | |
|       ;;
 | |
|     esac
 | |
|   done
 | |
| 
 | |
|   if [[ -n $warned ]]; then
 | |
|     read -k key$shortprompt
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # Apply defaults.  Arguments in the form
 | |
| #   -p parameter_name default_value description
 | |
| #      ...
 | |
| #   -o option_name default=on|off description
 | |
| #      ...
 | |
| # Options on by default should begin !, e.g. !nomatch.  They
 | |
| # will still appear under the base option but with an indication that
 | |
| # the default is on.  The default applies to the base option.  Hack, sorry.
 | |
| #   -b bindkey_string default_value description
 | |
| #      ...
 | |
| #   -B default_keymap=emacs|vi|none description
 | |
| #
 | |
| # They're not really defaults (they're not the same as the
 | |
| # builtin defaults), so the description output is "not yet saved".
 | |
| #
 | |
| # All variables to be edited in this section must be mentioned,
 | |
| # though defaults can be blank in which case nothing will be
 | |
| # saved unless the variable is set by the user.  The description
 | |
| # is then "no value set".
 | |
| #
 | |
| # -B is a bit strange: it's simply designed to allow the user to
 | |
| # select "bindkey -e" for Emacs or "bindkey -v" for vi.  It only
 | |
| # takes a single argument.  Real key bindings use -b.
 | |
| #
 | |
| # This operation transfers some subset of settings from the parsed_*
 | |
| # and state_* variables to the *_lines variables for editing.
 | |
| __zni_apply_defaults() {
 | |
|   local un suf
 | |
| 
 | |
|   # Reset the lines to be edited.
 | |
|   state_lines=()
 | |
|   display_lines=()
 | |
|   output_lines=()
 | |
|   lines_read=0
 | |
| 
 | |
|   case $1 in
 | |
|     (-p)
 | |
|     shift
 | |
|     while [[ $# -gt 0 && $1 != -* ]]; do
 | |
|       # skip default if it was read in
 | |
|       if [[ -z $state_parameters[$1] ]]; then
 | |
| 	parsed_parameters[$1]=$2
 | |
| 	if [[ -n $2 ]]; then
 | |
| 	  state_parameters[$1]="not yet saved"
 | |
| 	else
 | |
| 	  state_parameters[$1]="no value set"
 | |
| 	fi
 | |
|       elif [[ $state_parameters[$1] = saved ]]; then
 | |
| 	(( lines_read++ ))
 | |
|       fi
 | |
|       state_lines+=($state_parameters[$1])
 | |
|       display_lines+=("$3")
 | |
|       output_lines+=("$1=$parsed_parameters[$1]")
 | |
| 
 | |
|       shift 3
 | |
|     done
 | |
|     ;;
 | |
| 
 | |
|     (-o)
 | |
|     shift
 | |
|     while [[ $# -gt 0 && $1 != -* ]]; do
 | |
|       # skip default if there was a setting
 | |
|       if [[ $1 != ${1##!} ]]; then
 | |
| 	argv[1]=${1##!}
 | |
| 	default_options[$1]=on
 | |
|       else
 | |
| 	default_options[$1]=off
 | |
|       fi
 | |
|       if [[ -z $state_options[$1] ]]; then
 | |
| 	parsed_options[$1]=$2
 | |
| 	if [[ -n $2 ]]; then
 | |
| 	  state_options[$1]="not yet saved"
 | |
| 	else
 | |
| 	  state_options[$1]="no value set"
 | |
| 	fi
 | |
|       elif [[ $state_options[$1] = saved ]]; then
 | |
| 	(( lines_read++ ))
 | |
|       fi
 | |
|       if [[ $parsed_options[$1] = on ]]; then
 | |
| 	un=
 | |
| 	suf=
 | |
|       elif [[ -z $parsed_options[$1] && $default_options[$1] = on ]]
 | |
|       then
 | |
| 	un=
 | |
| 	suf=", default on"
 | |
|       else
 | |
| 	# display as unsetopt even if no value to save yet
 | |
| 	un=un
 | |
| 	suf=
 | |
|       fi
 | |
|       state_lines+=("$state_options[$1]$suf")
 | |
|       display_lines+=("$3")
 | |
|       output_lines+=("${un}setopt $1")
 | |
| 
 | |
|       shift 3
 | |
|     done
 | |
|     ;;
 | |
| 
 | |
|     (-b)
 | |
|     shift
 | |
|     # this will barf on bindings beginning -; there's no good
 | |
|     # reason to rebind that, even in vi command mode, so perhaps
 | |
|     # we just add it to the sanity checks when we get around to them.
 | |
|     while [[ $# -gt 0 && $1 != -* ]]; do
 | |
|       if [[ -z $state_bindings[$1] ]]; then
 | |
| 	parsed_bindings[$1]=$2
 | |
| 	if [[ -n $2 ]]; then
 | |
| 	  state_bindings[$1]="not yet saved"
 | |
| 	else
 | |
| 	  state_bindings[$1]="no value set"
 | |
| 	fi
 | |
|       elif [[ $state_bindings[$1] = saved ]]; then
 | |
| 	(( lines_read++ ))
 | |
|       fi
 | |
|       state_lines+=($state_bindings[$1])
 | |
|       display_lines+=("$3")
 | |
|       output_lines+=("bindkey ${(qq)1}${2:+ $2}")
 | |
| 
 | |
|       shift 3
 | |
|     done
 | |
|     ;;
 | |
| 
 | |
|     (-B)
 | |
|     shift
 | |
|     if [[ -z $state_keymaps[main] ]]; then
 | |
|       parsed_keymaps[main]=$1
 | |
|       if [[ $1 = none ]]; then
 | |
| 	state_keymaps[main]="no value set"
 | |
|       else
 | |
| 	state_keymaps[main]="not yet saved"
 | |
|       fi
 | |
|     elif [[ $state_keymaps[main] = saved ]]; then
 | |
|       (( lines_read++ ))
 | |
|     fi
 | |
|     state_lines+=($state_keymaps[main])
 | |
|     display_lines+=("$2")
 | |
|     # display as -e even if no value to save yet
 | |
|     if [[ $parsed_keymaps[main] = vi ]]; then
 | |
|       output_lines+=("bindkey -v")
 | |
|     else
 | |
|       output_lines+=("bindkey -e")
 | |
|     fi
 | |
| 
 | |
|     shift 2
 | |
|     ;;
 | |
|   esac
 | |
| }
 | |
| 
 | |
| 
 | |
| # Display and edit the settings given by the set of *_lines arrays.
 | |
| # If requested by the user, apply the settings, updating the
 | |
| # parsed_* and state_* variables.
 | |
| __zni_display_and_edit() {
 | |
|   integer i changes
 | |
|   local default edval ldisp rdisp
 | |
|   local -a states displays outputs tstval
 | |
| 
 | |
|   states=("${state_lines[@]}")
 | |
|   displays=("${display_lines[@]}")
 | |
|   outputs=("${output_lines[@]}")
 | |
| 
 | |
|   if [[ -n ${states[(r)not yet saved]} ]]; then
 | |
|     # default should be installed, unless user says otherwise
 | |
|     (( changes++ ))
 | |
|   fi
 | |
| 
 | |
|   while true; do
 | |
|     clear
 | |
|     print -r $1
 | |
|     # snicker...
 | |
|     print -r ${(l.${#1}..=.):-}
 | |
|     print
 | |
|     if (( $# > 1 )); then
 | |
|       print -rl $argv[2,-1]
 | |
|       print
 | |
|     fi
 | |
| 
 | |
|     # Output each setting with a description and state.
 | |
|     for (( i = 1; i <= ${#output_lines}; i++ )); do
 | |
|       default=${states[$i]%%,*}
 | |
|       if [[ $default = ("no value set"|"not to be saved"*) ]]; then
 | |
| 	ldisp="# $outputs[$i]"
 | |
|       else
 | |
| 	ldisp=$outputs[$i]
 | |
|       fi
 | |
|       rdisp=${default:+($default)}
 | |
|       print -r "# ($i) $displays[$i]
 | |
| $ldisp${(l.$COLUMNS-${#ldisp}-${#rdisp}-1.):-}$rdisp"
 | |
|     done
 | |
| 
 | |
|     if (( changes )); then
 | |
|       print -r "
 | |
| # (0)  Remember edits and return to main menu (does not save file yet)
 | |
| # (q)  Abandon edits and return to main menu
 | |
| "
 | |
|     else
 | |
|       print -r "
 | |
| # (0) or (q)  Return to main menu (no changes made yet)
 | |
| "
 | |
|     fi
 | |
|     read -k key$longprompt
 | |
|     print
 | |
| 
 | |
|     if [[ $key = <-> && $key -ge 1 && $key -le ${#outputs} ]]; then
 | |
|       (( i = key ))
 | |
|       case $outputs[$i] in
 | |
| 	((#b)(|un)setopt' '(*))
 | |
| 	# Try to locate the appropriate section in the manual.
 | |
| 	# I personally have no wish whatsoever to make this
 | |
| 	# use sed or awk.  Suggestions welcome.
 | |
| 	if [[ -s $tmpfile-man-options ]]; then
 | |
| 	  perl -ne 's/^(\s*)([A-Z]+)_?([A-Z]*)_?([A-Z]*)(\s*\(.+\)|\s*\<.+\>)*\s*$/\L$1$2$3$4\n/ and "'$match[2]'" =~ /^(|no)\L$2$3$4$/ and $print = 1 and next; next unless $print; exit if /^\s*$/; print; ' <$tmpfile-man-options >$tmpfile-man 2>/dev/null
 | |
| 	else
 | |
| 	  rm -f $tmpfile-man
 | |
| 	fi
 | |
| 	while true; do
 | |
| 	  clear
 | |
| 	  if [[ -s $tmpfile-man ]]; then
 | |
| 	    read <$tmpfile-man
 | |
| 	    print "Option $match[2]:"
 | |
| 	    cat $tmpfile-man
 | |
| 	    print
 | |
| 	  else
 | |
| 	    print "Option $match[2]:  $displays[$i]"
 | |
| 	  fi
 | |
| 	  print "The option $match[2] is currently ${match[1]:+un}set.
 | |
| Type:
 | |
|   (s) to set it (turn it on)
 | |
|   (u) to unset it (turn it off)
 | |
|   (n) neither to set or unset it (use shell default: \
 | |
| $default_options[$match[2]])
 | |
|   (k) or (q) to keep the current setting:"
 | |
| 	  read -k key$shortprompt
 | |
| 	  print
 | |
| 
 | |
| 	  case $key in
 | |
| 	    (s)
 | |
| 	    (( changes++ ))
 | |
| 	    outputs[$i]="setopt $match[2]"
 | |
| 	    states[$i]="set but not saved"
 | |
| 	    ;;
 | |
| 
 | |
| 	    (u)
 | |
| 	    (( changes++ ))
 | |
| 	    outputs[$i]="unsetopt $match[2]"
 | |
| 	    states[$i]="set but not saved"
 | |
| 	    ;;
 | |
| 
 | |
| 	    (n)
 | |
| 	    (( changes++ ))
 | |
| 	    outputs[$i]="unsetopt $match[2]"
 | |
| 	    states[$i]="no value set"
 | |
| 	    ;;
 | |
| 
 | |
| 	    ([kq])
 | |
| 	    ;;
 | |
| 
 | |
| 	    (*)
 | |
| 	    continue
 | |
| 	    ;;
 | |
| 	  esac
 | |
| 	  break;
 | |
| 	done
 | |
| 	;;
 | |
| 
 | |
| 	((#b)([^=]##)=(*))
 | |
| 	if [[ -s $tmpfile-man-param ]]; then
 | |
| 	  perl -ne 's/^(\s*)([A-Z]+)(\s*\<.+\>)*\s*$/$1$2\n/ and "$2" eq "'$match[1]'" and $print = 1; next unless $print; exit if /^\s*$/; print;' <$tmpfile-man-param >$tmpfile-man 2>/dev/null
 | |
| 	else
 | |
| 	  rm -f $tmpfile-man
 | |
|         fi
 | |
| 	if [[ -s $tmpfile-man ]]; then
 | |
| 	  print -n Variable
 | |
| 	  cat $tmpfile-man
 | |
| 	  print
 | |
| 	else
 | |
| 	  print -r "Variable ${match[1]}:  $displays[$i]"
 | |
| 	fi
 | |
| 	print -r "Edit a value.  If it is left blank, nothing will be saved:"
 | |
| 	edval=$match[2]
 | |
| 	if vared -p "$match[1]> " -h edval; then
 | |
| 	  # check this assignment doesn't produce multiple words
 | |
| 	  # e.g. "HISTFILE=never rm -f ~" does produce multiple words...
 | |
| 	  # this isn't perfect, e.g. "(this would get split on assignment)",
 | |
| 	  # but that's fairly benign.
 | |
| 	  tstval=(${=edval})
 | |
| 	  if (( ${#tstval} > 1 )); then
 | |
| 	    print "Error: value isn't a single word.
 | |
| Use quotes or backslashes if your value contains spaces.
 | |
| Note that you shouldn't quote an initial ~ in file names." >&2
 | |
| 	    read -k key$shortprompt
 | |
| 	    # now check the assignment works...
 | |
| 	    # don't suppress any errors, they may be useful.
 | |
| 	    # this means we need to suppress warncreateglobal.
 | |
| 	  elif ! ( typeset -g $match[1]; eval "$match[1]=$edval" ); then
 | |
| 	    print "Error: bad shell syntax in value.
 | |
| The value will be assigned to the variable exactly as you enter it.
 | |
| Make sure all quotes are paired." >&2
 | |
| 	    read -k key$shortprompt
 | |
| 	  else
 | |
| 	    outputs[$i]="$match[1]=$edval"
 | |
| 	    if [[ -n $edval ]]; then
 | |
| 	      states[$i]="set but not saved"
 | |
| 	    else
 | |
| 	      states[$i]="no value set"
 | |
| 	    fi
 | |
| 	    (( changes++ ))
 | |
| 	  fi
 | |
| 	else
 | |
| 	  read -k key'?--- Edit abandoned, type a key --- '
 | |
| 	fi
 | |
| 	;;
 | |
| 
 | |
| 	(bindkey' '-[ev])
 | |
| 	while true; do
 | |
| 	  print -nr "Pick a keymap (set of keys) to use when editing.
 | |
| Type:
 | |
|   (e) for Emacs keymap (recommended unless you are vi user)
 | |
|   (v) for Vi keymap
 | |
|   (n) not to set a keymap (allow shell to choose)
 | |
|   (k) to keep the current setting, "
 | |
| 	  if [[ ${state_lines[$i]%%,*} = ("no value set"|"not to be saved") ]]
 | |
| 	  then
 | |
| 	    print -r "(n):"
 | |
| 	  elif [[ $output_lines[$i] = *-v ]]; then
 | |
| 	    print -r "(v):"
 | |
| 	  else
 | |
| 	    print -r "(e):"
 | |
| 	  fi
 | |
| 	  read -k key$longprompt
 | |
| 	  case $key in
 | |
| 	    (e)
 | |
| 	    (( changes++ ))
 | |
| 	    outputs[$i]="bindkey -e"
 | |
| 	    states[$i]="set but not saved"
 | |
| 	    ;;
 | |
| 
 | |
| 	    (v)
 | |
| 	    (( changes++ ))
 | |
| 	    outputs[$i]="bindkey -v"
 | |
| 	    states[$i]="set but not saved"
 | |
| 	    ;;
 | |
| 
 | |
| 	    (n)
 | |
| 	    (( changes++ ))
 | |
| 	    outputs[$i]="bindkey -e"
 | |
| 	    states[$i]="not to be saved"
 | |
| 	    ;;
 | |
| 
 | |
| 	    (k)
 | |
| 	    ;;
 | |
| 
 | |
| 	    (*)
 | |
| 	    continue
 | |
| 	    ;;
 | |
| 	  esac
 | |
| 	  break
 | |
| 	done
 | |
| 	;;
 | |
| 
 | |
| 	(bindkey' '*)
 | |
| 	# TODO: this needs writing.  We need to be able to read
 | |
| 	# keys and translate them, sanity check them, and ideally
 | |
| 	# handle keymaps, at least vi command and insert.
 | |
| 	;;
 | |
| 
 | |
| 	(*)
 | |
| 	print "*** Internal error: bad setting '$outputs[$i]' ***" >&2
 | |
| 	read -k key'?--- Type a key in forlorn hope --- '
 | |
| 	;;
 | |
|       esac
 | |
|     elif [[ $key = 0 ]]; then
 | |
|       # Update the *_lines variables
 | |
|       state_lines=("${states[@]}")
 | |
|       display_lines=("${displays[@]}")
 | |
|       output_lines=("${outputs[@]}")
 | |
| 
 | |
|       # Also save any lines suitably marked to parsed_* and state_*
 | |
|       # by rerunning __zni_parse_lines on each such line.
 | |
|       for (( i = 1; i <= ${#output_lines}; i++ )); do
 | |
| 	if [[ ${state_lines[$i]%%,*} = \
 | |
| 	  ("set but not saved"|"not to be saved"|"not yet saved") ]]
 | |
| 	then
 | |
| 	  __zni_parse_lines ${state_lines[$i]%%,*} $output_lines[$i]
 | |
| 	fi
 | |
|       done
 | |
| 
 | |
|       return $(( changes == 0 ))
 | |
|     elif [[ $key = [qQ] ]]; then
 | |
|       return 1
 | |
|     fi
 | |
|   done
 | |
| }
 | |
| 
 | |
| 
 | |
| # Print and despatch a submenu.
 | |
| # The first argument is the title.  The remaining arguments
 | |
| # are pairs of descriptions and functions to execute.
 | |
| # There shouldn't be more than 9 entries.
 | |
| # The usual entries 0 and q are added automatically.
 | |
| __zni_submenu() {
 | |
|   local title=$1
 | |
|   local desc func
 | |
|   local -a descs funcs
 | |
|   integer i
 | |
| 
 | |
|   shift
 | |
| 
 | |
|   clear
 | |
|   print -r $title
 | |
|   print -r ${(l.${#title}..=.):-}
 | |
| 
 | |
|   for desc func; do
 | |
|     if [[ -z $func ]]; then
 | |
|       print "*** Internal error: bad argument set for __zni_submenu ***" >&2
 | |
|       read -k key'?--- Type a key in forlorn hope --- '
 | |
|       return 1
 | |
|     fi
 | |
| 
 | |
|     descs+=($desc)
 | |
|     funcs+=($func)
 | |
|   done
 | |
| 
 | |
|   while true; do
 | |
|     for (( i = 1; i <= ${#descs}; i++ )); do
 | |
|       print -r "
 | |
| ($i)  $descs[$i]"
 | |
|     done
 | |
|     print -r "
 | |
| (0) or (q)  Return to previous menu"
 | |
| 
 | |
|     read -k key$longprompt
 | |
| 
 | |
|     if [[ $key = [0qQ] ]]; then
 | |
|       return 1
 | |
|     elif (( key >= 1 && key <= ${#funcs} )); then
 | |
|       $funcs[$key]
 | |
|     fi
 | |
|   done
 | |
| }
 | |
| 
 | |
| 
 | |
| # Save all values that have been edited to .zshrc.
 | |
| __zni_save() {
 | |
|   local key optline newline
 | |
|   local -a on_opts off_opts lines lines2
 | |
|   integer i
 | |
| 
 | |
|   # Record lines containing parameter settings, sorted.
 | |
|   for key in ${(ok)parsed_parameters}; do
 | |
|     if [[ $state_parameters[$key] != ("no value set"|"not to be saved") ]]
 | |
|     then
 | |
|       lines+=("$key=$parsed_parameters[$key]")
 | |
|     fi
 | |
|   done
 | |
| 
 | |
|   # Search through sorted options, make list of those to
 | |
|   # be turned on and off.  Those marked "no value set" aren't
 | |
|   # to be output.
 | |
|   for key in ${(ok)parsed_options}; do
 | |
|     if [[ $state_options[$key] != ("no value set"|"not to be saved") ]]; then
 | |
|       if [[ $parsed_options[$key] = on ]]; then
 | |
| 	on_opts+=($key)
 | |
|       else
 | |
| 	off_opts+=($key)
 | |
|       fi
 | |
|     fi
 | |
|   done
 | |
| 
 | |
|   # Construct lines of options to turn on, keeping them short.
 | |
|   optline="setopt"
 | |
|   for (( i = 1; i <= ${#on_opts}; i++ )); do
 | |
|     newline="$optline $on_opts[$i]"
 | |
|     if [[ ${#newline} -ge 72 ]]; then
 | |
|       lines+=($optline)
 | |
|       optline="setopt $on_opts[$i]"
 | |
|     else
 | |
|       optline=$newline
 | |
|     fi
 | |
|     if (( i == ${#on_opts} )); then
 | |
|       lines+=($optline)
 | |
|     fi
 | |
|   done
 | |
| 
 | |
|   # Construct lines of options to turn off, keeping them short.
 | |
|   optline="unsetopt"
 | |
|   for (( i = 1; i <= ${#off_opts}; i++ )); do
 | |
|     newline="$optline $off_opts[$i]"
 | |
|     if [[ ${#newline} -ge 72 ]]; then
 | |
|       lines+=($optline)
 | |
|       optline="unsetopt $off_opts[$i]"
 | |
|     else
 | |
|       optline=$newline
 | |
|     fi
 | |
|     if (( i == ${#off_opts} )); then
 | |
|       lines+=($optline)
 | |
|     fi
 | |
|   done
 | |
| 
 | |
|   # Construct lines of bindkey commands.  First the keymap.
 | |
|   if [[ $state_keymaps[main] != (|"no value set"|"not to be saved") ]]; then
 | |
|     case $parsed_keymaps[main] in
 | |
|       (emacs)
 | |
|       lines+=("bindkey -e")
 | |
|       ;;
 | |
| 
 | |
|       (vi)
 | |
|       lines+=("bindkey -v")
 | |
|       ;;
 | |
| 
 | |
|       (none)
 | |
|       ;;
 | |
| 
 | |
|       (*)
 | |
|       print -r "\
 | |
| *** Internal error: bad type $parsed_keymaps[main] for keymap ***" >&2
 | |
|       read -k key'?--- Type a key in forlorn hope --- '
 | |
|       ;;
 | |
|     esac
 | |
|   fi
 | |
|   # Now bindings.
 | |
|   for key in ${(ok)parsed_bindings}; do
 | |
|     if [[ $state_bindings[$key] != ("no value set"|"not to be saved") ]]; then
 | |
|       lines+=("bindkey ${(qq)key} ${parsed_bindings[$key]}")
 | |
|     fi
 | |
|   done
 | |
| 
 | |
|   # Save the lines with a start and end marker to a temporary file.
 | |
|   print -rl $startline $lines $endline >$tmpfile
 | |
| 
 | |
|   if (( ${#unparsed} )); then
 | |
|     print "# The following lines were read by $myname.
 | |
| # They were moved here as they could not be understood.
 | |
| # $(date)
 | |
| ${(F)unparsed}
 | |
| # End of lines moved by $myname." >>$tmpfile
 | |
|   fi
 | |
| 
 | |
|   if grep "$startline"  $zd/.zshrc 1>/dev/null 2>&1; then
 | |
|     # Found the start line; replace the section.
 | |
|     # We could this by reading the lines in zsh, but in case
 | |
|     # the .zshrc is huge it's perhaps better to use sed.
 | |
|     sed -e "/^[		]*$endline/r $tmpfile
 | |
| /^[	]*$startline/,/^[	]*$endline/d" $zd/.zshrc >${tmpfile}.repl &&
 | |
|     cp ${tmpfile}.repl $zd/.zshrc
 | |
|   else
 | |
|     # No current start marker; just append.
 | |
|     cat $tmpfile >>$zd/.zshrc
 | |
|   fi
 | |
| }
 | |
| 
 | |
| 
 | |
| ########################################################################
 | |
| # Specific configurations
 | |
| ########################################################################
 | |
| 
 | |
| __zni_history_config() {
 | |
|   __zni_apply_defaults -p \
 | |
|     HISTSIZE 1000 "Number of lines of history kept within the shell." \
 | |
|     HISTFILE $zdmsg/.histfile "File where history is saved." \
 | |
|     SAVEHIST 1000 "Number of lines of history to save to \$HISTFILE."
 | |
| 
 | |
|   if __zni_display_and_edit "History configuration"; then
 | |
|     install_state[history]="Unsaved changes"
 | |
|     save=1
 | |
|   fi
 | |
| }
 | |
| 
 | |
| 
 | |
| __zni_completion_config() {
 | |
|   autoload -Uz compinstall
 | |
|   if compinstall -d; then
 | |
|     print "The completion system has already been activated.
 | |
| You can run the configuration tool (compinstall) at any time by typing
 | |
|    autoload -Uz compinstall
 | |
|    compinstall
 | |
| Do you wish to run it now [y/n]?"
 | |
|     read -k key$shortprompt
 | |
|     if [[ $key = [yY] ]]; then
 | |
|       compinstall
 | |
|     fi
 | |
|     print
 | |
|   else
 | |
|     while true; do
 | |
|       clear
 | |
|       print "The new completion system (compsys) allows you to complete
 | |
| commands, arguments and special shell syntax such as variables.  It provides
 | |
| completions for a wide range of commonly used commands in most cases simply
 | |
| by typing the TAB key.  Documentation is in the zshcompsys manual page.
 | |
| If it is not turned on, only a few simple completions such as filenames
 | |
| are available but the time to start the shell is slightly shorter.
 | |
| 
 | |
| You can:
 | |
|   (1)  Turn on completion with the default options.
 | |
| 
 | |
|   (2)  Run the configuration tool (compinstall).  You can also run
 | |
|        this from the command line with the following commands:
 | |
|         autoload -Uz compinstall
 | |
|         compinstall
 | |
|        if you don't want to configure completion now.
 | |
| 
 | |
|   (0)  Don't turn on completion.
 | |
| "
 | |
|       read -k key$longprompt
 | |
|       case $key in
 | |
| 	(1)
 | |
| 	completion_lines=${(f)"$(compinstall -o)"}
 | |
| 	install_state[completion]="Unsaved changes"
 | |
| 	save=1
 | |
| 	;;
 | |
| 
 | |
| 	(2)
 | |
| 	if compinstall; then
 | |
| 	  install_state[completion]="Configured"
 | |
| 	  # compinstall has done it's thing, so we don't need
 | |
| 	  # to write anything.
 | |
| 	  completion_lines=()
 | |
| 	fi
 | |
| 	;;
 | |
| 
 | |
| 	(0)
 | |
| 	completion_lines=()
 | |
| 	install_state[completion]="Recommended"
 | |
| 	;;
 | |
| 
 | |
| 	(*)
 | |
| 	continue
 | |
| 	;;
 | |
|       esac
 | |
|       break
 | |
|     done
 | |
|   fi
 | |
| }
 | |
| 
 | |
| __zni_bindkey_config() {
 | |
|   __zni_apply_defaults -B emacs "Change default editing configuration"
 | |
| 
 | |
|   if __zni_display_and_edit "Default editing configuration" \
 | |
|     "The keys in the shell's line editor can be made to behave either" \
 | |
|     "like Emacs or like Vi, two common Unix editors.  If you have no" \
 | |
|     "experience of either, Emacs is recommended.  If you don't pick one," \
 | |
|     "the shell will try to guess based on the EDITOR environment variable." \
 | |
|     "Usually it's better to pick one explicitly."; then
 | |
|     install_state[bindkey]="Unsaved changes"
 | |
|     save=1
 | |
|   fi
 | |
| }
 | |
| 
 | |
| __zni_completion_save() {
 | |
|   if (( ${#completion_lines} )); then
 | |
|     # We don't try to replace existing lines of completion configuration ---
 | |
|     # that's up to compinstall.  We should already have tested that
 | |
|     # there was no existing completion set up.
 | |
|     print -rl $completion_lines >>$zd/.zshrc
 | |
|   fi
 | |
| }
 | |
| 
 | |
| 
 | |
| __zni_options_config() {
 | |
|   # when we have enough, should use:
 | |
|   #   __zni_submenu "Common shell options"
 | |
| 
 | |
|   # This is deliberately just a tiny selection.
 | |
|   # Feel free to extend it, but if you do, consider using __zni_submenu.
 | |
|   # The "no" prefix is used to indicate options on by default.
 | |
|   __zni_apply_defaults -o autocd '' "Change directory given just path." \
 | |
|     extendedglob '' "Use additional pattern matching features." \
 | |
|     appendhistory '' "Append new history lines instead of overwriting." \
 | |
|     '!nomatch' '' "Unmatched patterns cause an error." \
 | |
|     '!beep' '' "Beep on errors." \
 | |
|     notify '' "Immediately report changes in background job status."
 | |
| 
 | |
|   if __zni_display_and_edit "Common shell options" \
 | |
|   "The following are some of the shell options that are most often used." \
 | |
|   "The descriptions are very brief; if you would like more information," \
 | |
|   "read the zshoptions manual page (type \"man zshoptions\")."; then
 | |
|     install_state[options]="Unsaved changes"
 | |
|     save=1
 | |
|   fi
 | |
| }
 | |
| 
 | |
| 
 | |
| ########################################################################
 | |
| # Main function
 | |
| ########################################################################
 | |
| 
 | |
| # Read and parse any existing lines, in case the function
 | |
| # was called again.
 | |
| __zni_retrieve_lines &&
 | |
|   __zni_parse_lines saved "$reply[@]"
 | |
| 
 | |
| if [[ $state_parameters[HISTORY] = saved ]]; then
 | |
|   install_state[history]="Saved"
 | |
| fi
 | |
| autoload -Uz compinstall
 | |
| zstyle :compinstall filename $zd/.zshrc
 | |
| if compinstall -d; then
 | |
|   install_state[completion]="Saved"
 | |
| fi
 | |
| 
 | |
| 
 | |
| # skip initial screen if the function was deliberately run by the user.
 | |
| if [[ $1 != -f ]]; then
 | |
|   clear
 | |
|   print -r "This is the Z Shell configuration function for new users,
 | |
| $myname.
 | |
| You are seeing this message because you have no zsh startup files
 | |
| (the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
 | |
| $zdmsg).  This function can help you with a few settings that should
 | |
| make your use of the shell easier.
 | |
| 
 | |
| You can:
 | |
| 
 | |
| (q)  Quit and do nothing.  The function will be run again next time."
 | |
|   if [[ ! -f $zd/.zshrc ]]; then
 | |
|     print -r "
 | |
| (0)  Exit, creating the file $zdmsg/.zshrc containing just a comment.
 | |
|      That will prevent this function being run again."
 | |
|   fi
 | |
|   print -r "
 | |
| (1)  Continue to the main menu.
 | |
| "
 | |
|   if [[ -f /etc/zsh/newuser.zshrc.recommended ]]; then
 | |
|     print -r "(2)  Populate your $zdmsg/.zshrc with the configuration recommended
 | |
|      by the system administrator and exit (you will need to edit
 | |
|      the file by hand, if so desired).
 | |
| "
 | |
|   fi
 | |
| 
 | |
|   read -k key$longprompt
 | |
|   print
 | |
| 
 | |
|   case $key in
 | |
|     ([qQ])
 | |
|     return 0
 | |
|     ;;
 | |
| 
 | |
|     (0)
 | |
|     print -r $msg >$zd/.zshrc
 | |
|     return 0
 | |
|     ;;
 | |
| 
 | |
|     (1)
 | |
|     ;;
 | |
| 
 | |
|     (2)
 | |
|     cp /etc/zsh/newuser.zshrc.recommended $zd/.zshrc
 | |
|     source $zd/.zshrc
 | |
|     return 0
 | |
|     ;;
 | |
| 
 | |
|     (*)
 | |
|     print -r "Aborting."
 | |
|     if [[ $1 != -f ]]; then
 | |
|       print "\
 | |
| The function will be run again next time.  To prevent this, execute:
 | |
|   touch $zdmsg/.zshrc"
 | |
|     fi
 | |
|     return 1
 | |
|     ;;
 | |
|   esac
 | |
| fi
 | |
| 
 | |
| 
 | |
| print -r "Attempting to extract information from manual pages..."
 | |
| (man zshoptions | col -b > $tmpfile-man-options;
 | |
|   man zshparam | col -b > $tmpfile-man-param) 2>/dev/null
 | |
| 
 | |
| while true; do
 | |
|   clear
 | |
|   print -nr "Please pick one of the following options:
 | |
| 
 | |
| (1)  Configure settings for history, i.e. command lines remembered
 | |
|      and saved by the shell.\
 | |
| ${install_state[history]:+  ($install_state[history].)}
 | |
| 
 | |
| (2)  "
 | |
|   if [[ $install_state[completion] = Recommended ]]; then
 | |
|     print -nr "Configure"
 | |
|   else
 | |
|     print -nr "Use"
 | |
|   fi
 | |
|   print -r " the new completion system.\
 | |
| ${install_state[completion]:+  ($install_state[completion].)}
 | |
| 
 | |
| (3)  Configure how keys behave when editing command lines.\
 | |
| ${install_state[bindkey]:+  ($install_state[bindkey].)}
 | |
| 
 | |
| (4)  Pick some of the more common shell options.  These are simple \"on\"
 | |
|      or \"off\" switches controlling the shell's features.  \
 | |
| ${install_state[options]:+  ($install_state[options].)}
 | |
| "
 | |
|   print -nr "(0)  Exit, "
 | |
|   if (( save )); then
 | |
|     print -r "saving the new settings.  They will take effect immediately."
 | |
|   elif [[ -f $zd/.zshrc ]]; then
 | |
|     print -r "leaving the existing $zdmsg/.zshrc alone."
 | |
|   else
 | |
|     print -r "creating a blank $zdmsg/.zshrc file."
 | |
|   fi
 | |
|   print -r "
 | |
| (a)  Abort all settings and start from scratch.  Note this will overwrite
 | |
|      any settings from $myname already in the startup file.
 | |
|      It will not alter any of your other settings, however."
 | |
|   if [[ $1 = -f ]]; then
 | |
|     print -r "
 | |
| (q)  Quit and do nothing else."
 | |
|   else
 | |
|     print -r "
 | |
| (q)  Quit and do nothing else.  The function will be run again next time."
 | |
|   fi
 | |
| 
 | |
|   read -k key$longprompt
 | |
|   print
 | |
| 
 | |
|   case $key in
 | |
|     ([qQ])
 | |
|     break
 | |
|     ;;
 | |
| 
 | |
|     ([aA])
 | |
|     parsed_parameters=()
 | |
|     state_parameters=()
 | |
|     parsed_options=()
 | |
|     state_options=()
 | |
|     parsed_keymaps=()
 | |
|     state_keymaps=()
 | |
|     parsed_bindings=()
 | |
|     state_bindings=()
 | |
|     unparsed=()
 | |
|     ;;
 | |
| 
 | |
|     (0)
 | |
|     clear
 | |
|     if (( save )); then
 | |
|       if [[ -f $zd/.zshrc ]]; then
 | |
| 	cp $zd/.zshrc $zd/.zshrc.zni &&
 | |
| 	print -r "Copied old '$zdmsg/.zshrc' to '$zdmsg/.zshrc.zni'.
 | |
| "
 | |
|       fi
 | |
| 
 | |
|       __zni_save
 | |
|       __zni_completion_save
 | |
|     elif [[ ! -f $zd/.zshrc ]]; then
 | |
|       print -r $msg >$zd/.zshrc
 | |
|     fi
 | |
|     if [[ $1 != -f ]]; then
 | |
|       print -r "The function will not be run in future, but you can run
 | |
| it yourself as follows:
 | |
|   autoload -Uz $myname
 | |
|   $myname -f
 | |
| 
 | |
| The code added to $zdmsg/.zshrc is marked by the lines
 | |
| $startline
 | |
| $endline
 | |
| You should not edit anything between these lines if you intend to
 | |
| run $myname again.  You may, however, edit any other part
 | |
| of the file."
 | |
|     fi
 | |
|     break
 | |
|     ;;
 | |
| 
 | |
|     (1)
 | |
|     __zni_history_config
 | |
|     ;;
 | |
| 
 | |
|     (2)
 | |
|     __zni_completion_config
 | |
|     ;;
 | |
| 
 | |
|     (3)
 | |
|     __zni_bindkey_config
 | |
|     ;;
 | |
| 
 | |
|     (4)
 | |
|     __zni_options_config
 | |
|     ;;
 | |
|   esac
 | |
| done
 | |
| 
 | |
| } always {
 | |
|   # Tidy up: always executed unless the shell is stopped dead
 | |
|   # in its tracks.
 | |
|   unfunction -m $myname __zni_\*
 | |
|   rm -f $tmpfile*
 | |
| }
 |