about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--Completion/compinstall94
-rw-r--r--Functions/Newuser/zsh-newuser-install1006
-rw-r--r--INSTALL25
-rw-r--r--NEWS4
5 files changed, 1098 insertions, 37 deletions
diff --git a/ChangeLog b/ChangeLog
index bcb33d3d2..4a8d74d3b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2005-11-23  Peter Stephenson  <pws@csr.com>
+
+	* 22013: INSTALL, NEWS, Completion/compinstall,
+	Functions/Newuser/zsh-newuser-install: initial go at a
+	working zsh-newuser-install function.
+
 2005-11-22  Clint Adams  <clint@zsh.org>
 
 	* 22012: Completion/Debian/Command/_mergechanges:
diff --git a/Completion/compinstall b/Completion/compinstall
index f3e01043e..dc030e7a9 100644
--- a/Completion/compinstall
+++ b/Completion/compinstall
@@ -1,3 +1,5 @@
+# Configure the completion system.
+
 emulate -L zsh
 setopt extendedglob
 
@@ -25,11 +27,25 @@ __ci_newline() {
 
 typeset startline='# The following lines were added by compinstall'
 typeset endline='# End of lines added by compinstall'
-typeset ifile line fpath_line compinit_args
+typeset ifile line fpath_line compinit_args opt detect basic line2
 typeset -A styles
-typeset match mbegin mend warn_unknown warn_old warn_comment
+typeset match mbegin mend warn_unknown warn_old warn_comment output
 integer lines_found
 
+while getopts "do" opt; do
+  case $opt in
+    (d)
+    # Detect an existing compinstall setup.
+    detect=1
+    ;;
+
+    (o)
+    # Output basic setup information only.
+    basic=1
+    ;;
+  esac
+done
+
 #
 # Check the user's .zshrc, if any.
 #
@@ -60,6 +76,40 @@ else
   fi
 fi
 
+
+if [[ -n $detect ]]; then
+  __ci_tidyup
+  [[ $foundold = true ]]
+  return
+fi
+
+
+__ci_output() {
+  print -r "$startline"
+  [[ -n $output ]] && print -r "$output"
+  if [[ -n $ifile ]]; then
+    line="zstyle :compinstall filename ${(qq)ifile}"
+    print -r "$line"
+    eval "$line"
+  fi
+
+  [[ -n $fpath_line ]] && print -r "$fpath_line"
+
+  print -r "
+autoload -Uz compinit
+compinit${compinit_args:+ $compinit_args}"
+
+  print -r "$endline"
+}
+
+
+if [[ -n $basic ]]; then
+  __ci_output
+  __ci_tidyup
+  return
+fi
+
+
 local newifile=$ifile
 if [[ $foundold = true ]]; then
   print "I have found completion definitions in $ifile.
@@ -92,8 +142,19 @@ ifile=$newifile
 
 if [[ $foundold = true ]]; then
   sed -n "/^[ 	]*$startline/,/^[ 	]*$endline/p" $ifile |
-  # Use the default read behaviour to handle any continuation lines.
-  while read line; do
+  # We can't use the default read behaviour to handle continuation lines
+  # since it messes up internal backslashes.
+  while read -r line; do
+    # detect real continuation backslashes by checking there are an
+    # odd number together.  i think this is reliable since the only
+    # other way of quoting a backslash involves following it with
+    # a closing quote.
+    while [[ $line = *\\ ]]; do
+      line2=${(M)line%%\\##}
+      (( ${#line2} & 1 )) || break
+      read -r line2 || break
+      line="${line[1,-2]}$line2"
+    done
     (( lines_found++ ))
     if [[ $line = *'$fpath'* ]]; then
       fpath_line=$line
@@ -119,7 +180,7 @@ ${match[3]}"
     then
       compinit_args=$match[1]
     elif [[ $line != [[:blank:]]# &&
-      $line != [[:blank:]]#'autoload -Uz compinit' &&
+      $line != [[:blank:]]#'autoload '*' compinit' &&
       $line != [[:blank:]]#compinit &&
       $line != [[:blank:]]#zstyle[[:blank:]]#:compinstall* ]]; then
       warn_unknown="${warn_unknown:+$warn_unknown
@@ -379,7 +440,7 @@ o.     Set options for _oldlist: when to keep old list.
 m.     Set options for _match: whether to assume a \`*' at the cursor.
 p.     Set options for _prefix: whether to add a space before the suffix.
 
-q.     Return to the without saving.
+q.     Return to the previous menu without saving.
 0.     Done setting completer options.
 "
 
@@ -1848,15 +1909,13 @@ q.  Return without saving.
 done
 
 
-local output
-
 if (( $#styles )); then
   typeset style stylevals context values
   for style in ${(ko)styles}; do
     stylevals=(${(f)styles[$style]})
     while (( $#stylevals )); do
       output="$output
-zstyle ${(qq)stylevals[1]} $style $stylevals[2]"
+zstyle ${(qq)stylevals[1]} $style ${stylevals[2]}"
       shift 2 stylevals
     done
   done
@@ -1875,22 +1934,7 @@ local tmpout=${TMPPREFIX:-/tmp/zsh}compinstall$$
 # Assemble the complete set of lines to
 # insert.
 #
-{ print -r "$startline
-$output"
-  if [[ -n $ifile ]]; then
-    line="zstyle :compinstall filename ${(qq)ifile}"
-    print -r "$line"
-    eval "$line"
-  fi
-
-  [[ -n $fpath_line ]] && print -r "$fpath_line"
-
-  print -r "
-autoload -Uz compinit
-compinit${compinit_args:+ $compinit_args}"
-
-  print -r "$endline"
-} >$tmpout
+__ci_output >$tmpout
 
 if [[ -n $ifile ]]; then
   if [[ $ifile != *(zshrc|zlogin|zshenv) ]]; then 
diff --git a/Functions/Newuser/zsh-newuser-install b/Functions/Newuser/zsh-newuser-install
index 6c54dc509..e042af08a 100644
--- a/Functions/Newuser/zsh-newuser-install
+++ b/Functions/Newuser/zsh-newuser-install
@@ -1,23 +1,1005 @@
 # Function to install startup files for a new user.
-# This dummy version simply creates a new .zshrc with a comment.
-# FIXME: we don't want to distribute a file that does that, it
-# would be preferable to do nothing at all.
+# 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
+# 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
+# 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
+integer save lines_found
 
-# The zsh/newuser module already tests for the following, so this test only
-# triggers if zsh-newuser-install is run by hand.
-#
-# In future we may want to use this mechanism to update startup files.
-if [[ -e $zd/.zshenv || -e $zd/.zprofile || -e $zd/.zshrc || -e $zs/.zlogin ]]
-then
-  print "zsh-newuser-install:  startup files exist, aborting" >&2
+install_state[history]=Recommended
+install_state[completion]=Recommended
+
+# Don't save anything if interrupted.
+trap 'save=0' HUP INT QUIT
+
+# Substitute an initial ~ for human consumption.
+if [[ $zd = $HOME ]]; then
+  zdmsg="~"
+else
+  zdmsg=$zd
+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
 
-echo "# Created by newuser for $ZSH_VERSION" >$zd/.zshrc
+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
+#      ...
+#   -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
+
+  # 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 [[ -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_parameters[$1] = saved ]]; then
+	(( lines_read++ ))
+      fi
+      if [[ $parsed_options[$1] = on ]]; then
+	un=
+      else
+	# display as unsetopt even if no value to save yet
+	un=un
+      fi
+      state_lines+=($state_options[$1])
+      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[@]}")
+
+  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' '(*))
+	while true; do
+	  clear
+	  print "Option $match[2]:  $displays[$i]
+The option is currently ${match[1]:+un}set.
+Type:
+  (s) to set it
+  (u) to unset it
+  (n) not to set or unset it (use shell default)
+  (k) 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"
+	    ;;
+
+	    (s)
+	    (( changes++ ))
+	    outputs[$i]="unsetopt $match[2]"
+	    states[$i]="set but not saved"
+	    ;;
+
+	    (n)
+	    (( changes++ ))
+	    outputs[$i]="unsetopt $match[2]"
+	    states[$i]="no value set"
+	    ;;
+
+	    (k)
+	    ;;
+
+	    (*)
+	    continue
+	    ;;
+	  esac
+	  break;
+	done
+	;;
+
+	((#b)([^=]##)=(*))
+	print -r "Variable ${match[1]}:  $displays[$i]
+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") ]]
+	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")
+      ;;
+    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 \
+    HISTORY 1000 "Number of lines of history kept within 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)
+	compinstall
+	install_state[completion]="Configured"
+	;;
+
+	(0)
+	completion_lines=()
+	install_state[completion]="Recommended"
+	;;
+
+	(*)
+	continue
+	;;
+      esac
+      break
+    done
+  fi
+}
+
+__zni_bindkey_config() {
+  __zni_apply_defaults -B none "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" \
+    nonomatch '' "Pass unmatched patterns to command instead of error" \
+    nobeep '' "Don't 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
+
+
+clear
+print -r "This is the Z Shell configuration function for new users, $myname."
+if [[ $1 != -f ]]; then
+  print -r "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."
+fi
+
+print -r "
+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 main menu.
+"
+
+read -k key$longprompt
+print
+
+case $key in
+  ([qQ])
+  return 0
+  ;;
+
+  (0)
+  print -r $msg >$zd/.zshrc
+  return 0
+  ;;
+
+  (1)
+  ;;
+
+  (*)
+  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
+
+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.
+
+(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 '$zdd/.zshrc' to '$zdd/.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 $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
 
-unfunction zsh-newuser-install
+} always {
+  # Tidy up: always executed unless the shell is stopped dead
+  # in its tracks.
+  unfunction -m $myname __zni_\*
+  rm -f $tmpfile*
+}
diff --git a/INSTALL b/INSTALL
index 1d5c5b423..fb461cfe7 100644
--- a/INSTALL
+++ b/INSTALL
@@ -231,6 +231,31 @@ Note that this is mutually exclusive with using the source directories
 as make can become confused by build files created in the source directories.
 
 
+================================
+AUTOMATIC NEW USER CONFIGURATION
+================================
+
+In the default configuration, the shell comes with a system based around
+the zsh/newuser add-on module that detects when a user first starts the
+shell interactively and has no initialisation files (.zshenv, .zshrc,
+.zprofile or .zlogin).  The shell then executes code in the file
+scripts/newuser in the shared library area (by default
+/usr/local/share/zsh/<VERSION>/scripts/newuser).  This feature can be
+turned off simply by removing this script.  The module can be removed
+entirely from the configured shell by editing the line starting
+"name=zsh/newuser" int the config.modules file, which is generated in the
+top level distribution directory during configuration: change the line to
+include "link=no auto=no".
+
+The supplied script executes the function supplied as
+Functions/Newuser/zsh-newuser-install.  This function is currently under
+development.  It is probably preferable for administrators who wish to
+customize the system their own way to edit the newuser script in
+scripts/newuser.  Also, as there is currently no internationalization
+support, administrators of sites with users who mostly do not speak English
+may wish not to install the zsh/newuser module.
+
+
 =====================
 CONFIGURATION OPTIONS
 =====================
diff --git a/NEWS b/NEWS
index e463d88dd..da605e884 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,10 @@ Major changes between versions 4.2 and 4.3
 - There is support for multibyte character sets in the line editor,
   though not the main shell.  See Multibyte Character Support in INSTALL.
 
+- The shell can now run an installation function for a new user
+  (one with no .zshrc, .zshenv, .zprofile or .zlogin file) without
+  any additional setting up by the administrator.
+
 - New option PROMPT_SP, on by default, to work around the problem that the
   line editor can overwrite output with no newline at the end.