mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-21 00:01:26 +01:00
a5f418d5f1
in zsh-newuser-install
1098 lines
29 KiB
Text
1098 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
|
|
|
|
# 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.
|
|
"
|
|
|
|
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
|
|
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 $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*
|
|
}
|