% which zsh-newuser-install
zsh-newuser-install () {
emulate -L zsh
setopt extendedglob nonomatch warncreateglobal
local myname=zsh-newuser-install
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
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
local zd=${ZDOTDIR:-$HOME}
local zdmsg
local msg="# Created by newuser for $ZSH_VERSION"
local startline="# Lines configured by $myname"
local endline="# End of lines configured by $myname"
local shortprompt="?--- Type a key --- "
local longprompt="?--- Type one of the keys in parentheses --- "
local tmpfile=${TMPPREFIX:-/tmp/zsh}-zni-$$
local -A install_state
local -A parsed_parameters parsed_options parsed_bindings parsed_keymaps
local -A state_parameters state_options state_bindings state_keymaps
local -A default_options
local -a unparsed
local -a output_lines display_lines state_lines
integer lines_read
local -a completion_lines
local -a reply match mbegin mend
local key
local REPLY
integer save lines_found
install_state[history]=Recommended
install_state[completion]=Recommended
install_state[bindkey]=Recommended
trap 'save=0' HUP INT QUIT
if [[ $zd = $HOME(#b)(|/*) ]]
then
zdmsg="~$match[1]"
else
zdmsg=$zd
fi
if [[ ! -w $zd ]]
then
if [[ $1 = -f ]]
then
print -r "$myname: can't write to $zdmsg." >&2
fi
return 1
fi
if [[ ! -t 0 || ! -t 1 ]]
then
if [[ $1 = -f ]]
then
print -r "$myname: can only be used interactively." >&2
fi
return 1
fi
if (( ${LINES:-0} < 15 || ${COLUMNS:-0} < 72 ))
then
return 1
fi
if [[ $1 != -f ]]
then
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
{
__zni_retrieve_lines () {
local line
reply=()
lines_found=0
[[ -f $zd/.zshrc ]] || return 1
grep "$startline" $zd/.zshrc > /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
}
__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:]]##(*)) 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]})
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
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
}
__zni_apply_defaults () {
local un suf
state_lines=()
display_lines=()
output_lines=()
lines_read=0
case $1 in
(-p) shift
while [[ $# -gt 0 && $1 != -* ]]
do
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
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
un=un
suf=
fi
state_lines+=("$state_options[$1]$suf")
display_lines+=("$3")
output_lines+=("${un}setopt $1")
shift 3
done ;;
(-b) shift
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")
if [[ $parsed_keymaps[main] = vi ]]
then
output_lines+=("bindkey -v")
else
output_lines+=("bindkey -e")
fi
shift 2 ;;
esac
}
__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
(( changes++ ))
fi
while true
do
clear
print -r $1
print -r ${(l.${#1}..=.):-}
print
if (( $# > 1 ))
then
print -rl $argv[2,-1]
print
fi
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' '(*)) 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 -M emacs -p "$match[1]> " -h edval
then
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
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' '*) ;;
(*) print "*** Internal error: bad setting '$outputs[$i]' ***" >&2
read -k key'?--- Type a key in forlorn hope --- ' ;;
esac
elif [[ $key = 0 ]]
then
state_lines=("${states[@]}")
display_lines=("${displays[@]}")
output_lines=("${outputs[@]}")
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
}
__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
}
__zni_save () {
local key optline newline
local -a on_opts off_opts lines lines2
integer i
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
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
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
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
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
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
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 > /dev/null 2>&1
then
sed -e "/^[ ]*$endline/r $tmpfile
/^[ ]*$startline/,/^[ ]*$endline/d" $zd/.zshrc > ${tmpfile}.repl && cp ${tmpfile}.repl $zd/.zshrc
else
cat $tmpfile >> $zd/.zshrc
fi
}
__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"
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
print -rl $completion_lines >> $zd/.zshrc
fi
}
__zni_options_config () {
__zni_apply_defaults -o autocd '' "Change directory given just path." extendedglob '' "Use additional pattern matching features." '!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
}
__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
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 {
unfunction -m $myname __zni_\*
rm -f $tmpfile*
}
}