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

PATCH: newuser system



The main thing I've been wanting before making test builds for the first
4.3 release is a working zsh-newuser-install, otherwise it's not worth
distributing the zsh/newuser module.

Here's a first pass.  As this will be seen by a great many new users it
really needs to be tested as widely as possible.  I may well have done
lots of things that will confuse new users and which should therefore be
done some completely different way.  Consequently it would be very
helpful to have guinea pigs.

It's designed to be running assuming .zshrc doesn't initially exist and
doesn't assume (nor probe) any other shell settings.  You can run it
again with the argument -f to edit the settings it has added, but again
it assumes nothing else is trying to set the same things.

It's written to be relatively easy to extend (more so than compinstall,
with more generic functions).  This should be done with submenus: many
of the existing menus are already close to filling the screen and the
option setting menu is probably a bit too full.

Internationalization is another matter: the function as it stands is
useless for anyone who doesn't speak English, and I've warned about this
in the INSTALL file, but I haven't even begun to provide hooks for
translations.  Given that this is all shell code, I don't know whether
that would be easier with an existing system or with an ad-hoc text
database.

It uses compinstall to provide the completion set-up.  I've tweaked that
to make the functions co-operate better, but zsh-newuser-install needs
to run compinstall interactively to edit completion settings.  I've
fixed one bug in compinstall which was screwing up backslashes read in
from the previous configuration.

Index: INSTALL
===================================================================
RCS file: /cvsroot/zsh/zsh/INSTALL,v
retrieving revision 1.19
diff -u -r1.19 INSTALL
--- INSTALL	3 Oct 2005 09:00:44 -0000	1.19
+++ INSTALL	23 Nov 2005 10:56:32 -0000
@@ -231,6 +231,31 @@
 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
 =====================
Index: NEWS
===================================================================
RCS file: /cvsroot/zsh/zsh/NEWS,v
retrieving revision 1.7
diff -u -r1.7 NEWS
--- NEWS	7 Nov 2005 10:56:34 -0000	1.7
+++ NEWS	23 Nov 2005 10:56:33 -0000
@@ -8,6 +8,10 @@
 - 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.
 
Index: Completion/compinstall
===================================================================
RCS file: /cvsroot/zsh/zsh/Completion/compinstall,v
retrieving revision 1.8
diff -u -r1.8 compinstall
--- Completion/compinstall	20 Apr 2004 12:11:15 -0000	1.8
+++ Completion/compinstall	23 Nov 2005 10:56:36 -0000
@@ -1,3 +1,5 @@
+# Configure the completion system.
+
 emulate -L zsh
 setopt extendedglob
 
@@ -25,11 +27,25 @@
 
 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 @@
   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 @@
 
 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 @@
     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 @@
 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 @@
 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 @@
 # 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 
Index: Functions/Newuser/zsh-newuser-install
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/Newuser/zsh-newuser-install,v
retrieving revision 1.1
diff -u -r1.1 zsh-newuser-install
--- Functions/Newuser/zsh-newuser-install	26 Jul 2005 10:35:26 -0000	1.1
+++ Functions/Newuser/zsh-newuser-install	23 Nov 2005 10:56:39 -0000
@@ -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*
+}

-- 
Peter Stephenson <pws@xxxxxxx>                  Software Engineer
CSR PLC, Churchill House, Cambridge Business Park, Cowley Road
Cambridge, CB4 0WZ, UK                          Tel: +44 (0)1223 692070


This message has been scanned for viruses by BlackSpider MailControl - www.blackspider.com



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