summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Completion/.distfiles3
-rw-r--r--Completion/Base/.distfiles6
-rw-r--r--Completion/Base/_brace_parameter5
-rw-r--r--Completion/Base/_command_names3
-rw-r--r--Completion/Base/_condition10
-rw-r--r--Completion/Base/_default13
-rw-r--r--Completion/Base/_equal3
-rw-r--r--Completion/Base/_long_options309
-rw-r--r--Completion/Base/_match_pattern31
-rw-r--r--Completion/Base/_match_test15
-rw-r--r--Completion/Base/_math18
-rw-r--r--Completion/Base/_parameter3
-rw-r--r--Completion/Base/_precommand5
-rw-r--r--Completion/Base/_redirect3
-rw-r--r--Completion/Base/_subscript4
-rw-r--r--Completion/Base/_tilde10
-rw-r--r--Completion/Base/_vars3
-rw-r--r--Completion/Builtins/.distfiles7
-rw-r--r--Completion/Builtins/_aliases3
-rw-r--r--Completion/Builtins/_arrays3
-rw-r--r--Completion/Builtins/_autoload3
-rw-r--r--Completion/Builtins/_bg_jobs3
-rw-r--r--Completion/Builtins/_bindkey7
-rw-r--r--Completion/Builtins/_builtin7
-rw-r--r--Completion/Builtins/_cd3
-rw-r--r--Completion/Builtins/_command7
-rw-r--r--Completion/Builtins/_dirs3
-rw-r--r--Completion/Builtins/_disable6
-rw-r--r--Completion/Builtins/_echotc3
-rw-r--r--Completion/Builtins/_enable6
-rw-r--r--Completion/Builtins/_fc7
-rw-r--r--Completion/Builtins/_functions3
-rw-r--r--Completion/Builtins/_hash13
-rw-r--r--Completion/Builtins/_jobs3
-rw-r--r--Completion/Builtins/_kill11
-rw-r--r--Completion/Builtins/_limits3
-rw-r--r--Completion/Builtins/_sched3
-rw-r--r--Completion/Builtins/_set7
-rw-r--r--Completion/Builtins/_setopt7
-rw-r--r--Completion/Builtins/_source7
-rw-r--r--Completion/Builtins/_trap7
-rw-r--r--Completion/Builtins/_unhash6
-rw-r--r--Completion/Builtins/_unsetopt7
-rw-r--r--Completion/Builtins/_vars_eq3
-rw-r--r--Completion/Builtins/_wait7
-rw-r--r--Completion/Builtins/_which3
-rw-r--r--Completion/Builtins/_zftp50
-rw-r--r--Completion/Builtins/_zle7
-rw-r--r--Completion/Builtins/_zmodload9
-rw-r--r--Completion/Commands/.distfiles3
-rw-r--r--Completion/Commands/_correct_filename37
-rw-r--r--Completion/Commands/_correct_word12
-rw-r--r--Completion/Commands/_most_recent_file4
-rw-r--r--Completion/Core/.distfiles5
-rw-r--r--Completion/Core/_approximate197
-rw-r--r--Completion/Core/_comp_parts147
-rw-r--r--Completion/Core/_compalso13
-rw-r--r--Completion/Core/_complete52
-rw-r--r--Completion/Core/_correct19
-rw-r--r--Completion/Core/_expand149
-rw-r--r--Completion/Core/_files26
-rw-r--r--Completion/Core/_list61
-rw-r--r--Completion/Core/_main_complete48
-rw-r--r--Completion/Core/_match53
-rw-r--r--Completion/Core/_multi_parts201
-rw-r--r--Completion/Core/_normal54
-rw-r--r--Completion/Core/_options5
-rw-r--r--Completion/Core/_parameters8
-rw-r--r--Completion/Core/_path_files311
-rw-r--r--Completion/Core/_sep_parts171
-rw-r--r--Completion/Core/_set_options7
-rw-r--r--Completion/Core/_unset_options7
-rw-r--r--Completion/Core/compdump89
-rw-r--r--Completion/Core/compinit269
-rw-r--r--Completion/README107
-rw-r--r--Completion/User/.distfiles6
-rw-r--r--Completion/User/_a2ps22
-rw-r--r--Completion/User/_compress3
-rw-r--r--Completion/User/_configure12
-rw-r--r--Completion/User/_dd13
-rw-r--r--Completion/User/_dvi3
-rw-r--r--Completion/User/_find21
-rw-r--r--Completion/User/_gunzip3
-rw-r--r--Completion/User/_gzip3
-rw-r--r--Completion/User/_hosts3
-rw-r--r--Completion/User/_make3
-rw-r--r--Completion/User/_man11
-rw-r--r--Completion/User/_mh70
-rw-r--r--Completion/User/_pdf3
-rw-r--r--Completion/User/_ps3
-rw-r--r--Completion/User/_rcs9
-rw-r--r--Completion/User/_rlogin9
-rw-r--r--Completion/User/_strip2
-rw-r--r--Completion/User/_stty16
-rw-r--r--Completion/User/_tar11
-rw-r--r--Completion/User/_tar_archive20
-rw-r--r--Completion/User/_tex3
-rw-r--r--Completion/User/_uncompress3
-rw-r--r--Completion/User/_x_options5
-rw-r--r--Completion/User/_xfig3
-rw-r--r--Doc/Zsh/compsys.yo691
-rw-r--r--Doc/Zsh/compwid.yo428
-rw-r--r--Doc/zshcompsys.yo3
-rw-r--r--Doc/zshcompwid.yo3
-rw-r--r--Functions/Completion/.distfiles11
-rw-r--r--Functions/Completion/__aliases2
-rw-r--r--Functions/Completion/__arrays2
-rw-r--r--Functions/Completion/__autoload2
-rw-r--r--Functions/Completion/__bg_jobs2
-rw-r--r--Functions/Completion/__bindkey7
-rw-r--r--Functions/Completion/__builtin7
-rw-r--r--Functions/Completion/__cd3
-rw-r--r--Functions/Completion/__command7
-rw-r--r--Functions/Completion/__command_names2
-rw-r--r--Functions/Completion/__compress3
-rw-r--r--Functions/Completion/__condition10
-rw-r--r--Functions/Completion/__configure11
-rw-r--r--Functions/Completion/__dd13
-rw-r--r--Functions/Completion/__default13
-rw-r--r--Functions/Completion/__dirs3
-rw-r--r--Functions/Completion/__disable6
-rw-r--r--Functions/Completion/__dvi3
-rw-r--r--Functions/Completion/__echotc2
-rw-r--r--Functions/Completion/__enable6
-rw-r--r--Functions/Completion/__fc7
-rw-r--r--Functions/Completion/__files10
-rw-r--r--Functions/Completion/__find24
-rw-r--r--Functions/Completion/__functions2
-rw-r--r--Functions/Completion/__gunzip3
-rw-r--r--Functions/Completion/__gzip3
-rw-r--r--Functions/Completion/__hash13
-rw-r--r--Functions/Completion/__hosts2
-rw-r--r--Functions/Completion/__jobs2
-rw-r--r--Functions/Completion/__kill8
-rw-r--r--Functions/Completion/__kill_helper3
-rw-r--r--Functions/Completion/__limits2
-rw-r--r--Functions/Completion/__main_complete48
-rw-r--r--Functions/Completion/__main_key_complete6
-rw-r--r--Functions/Completion/__make3
-rw-r--r--Functions/Completion/__man11
-rw-r--r--Functions/Completion/__mh70
-rw-r--r--Functions/Completion/__most_recent_file2
-rw-r--r--Functions/Completion/__normal54
-rw-r--r--Functions/Completion/__path_files272
-rw-r--r--Functions/Completion/__pdf3
-rw-r--r--Functions/Completion/__precommand15
-rw-r--r--Functions/Completion/__ps3
-rw-r--r--Functions/Completion/__rcs9
-rw-r--r--Functions/Completion/__rlogin9
-rw-r--r--Functions/Completion/__sched3
-rw-r--r--Functions/Completion/__set7
-rw-r--r--Functions/Completion/__setopt7
-rw-r--r--Functions/Completion/__source7
-rw-r--r--Functions/Completion/__strip2
-rw-r--r--Functions/Completion/__stty16
-rw-r--r--Functions/Completion/__subscript4
-rw-r--r--Functions/Completion/__tar14
-rw-r--r--Functions/Completion/__tex3
-rw-r--r--Functions/Completion/__trap7
-rw-r--r--Functions/Completion/__uncompress3
-rw-r--r--Functions/Completion/__unhash6
-rw-r--r--Functions/Completion/__unsetopt7
-rw-r--r--Functions/Completion/__vars2
-rw-r--r--Functions/Completion/__vars_eq2
-rw-r--r--Functions/Completion/__wait4
-rw-r--r--Functions/Completion/__which2
-rw-r--r--Functions/Completion/__x_options5
-rw-r--r--Functions/Completion/__xfig3
-rw-r--r--Functions/Completion/__zle7
-rw-r--r--Functions/Completion/__zmodload9
-rw-r--r--Functions/Completion/_aliases3
-rw-r--r--Functions/Completion/_arrays3
-rw-r--r--Functions/Completion/_autoload3
-rw-r--r--Functions/Completion/_bg_jobs3
-rw-r--r--Functions/Completion/_bindkey7
-rw-r--r--Functions/Completion/_builtin7
-rw-r--r--Functions/Completion/_cd3
-rw-r--r--Functions/Completion/_command7
-rw-r--r--Functions/Completion/_command_names3
-rw-r--r--Functions/Completion/_compress3
-rw-r--r--Functions/Completion/_condition10
-rw-r--r--Functions/Completion/_configure12
-rw-r--r--Functions/Completion/_dd13
-rw-r--r--Functions/Completion/_default13
-rw-r--r--Functions/Completion/_dirs3
-rw-r--r--Functions/Completion/_disable6
-rw-r--r--Functions/Completion/_dvi3
-rw-r--r--Functions/Completion/_echotc3
-rw-r--r--Functions/Completion/_enable6
-rw-r--r--Functions/Completion/_fc7
-rw-r--r--Functions/Completion/_files10
-rw-r--r--Functions/Completion/_find21
-rw-r--r--Functions/Completion/_functions3
-rw-r--r--Functions/Completion/_gunzip3
-rw-r--r--Functions/Completion/_gzip3
-rw-r--r--Functions/Completion/_hash13
-rw-r--r--Functions/Completion/_hosts3
-rw-r--r--Functions/Completion/_jobs3
-rw-r--r--Functions/Completion/_kill8
-rw-r--r--Functions/Completion/_kill_helper3
-rw-r--r--Functions/Completion/_limits3
-rw-r--r--Functions/Completion/_main_complete48
-rw-r--r--Functions/Completion/_make3
-rw-r--r--Functions/Completion/_man11
-rw-r--r--Functions/Completion/_mh70
-rw-r--r--Functions/Completion/_most_recent_file3
-rw-r--r--Functions/Completion/_normal56
-rw-r--r--Functions/Completion/_path_files272
-rw-r--r--Functions/Completion/_pdf3
-rw-r--r--Functions/Completion/_precommand5
-rw-r--r--Functions/Completion/_ps3
-rw-r--r--Functions/Completion/_rcs9
-rw-r--r--Functions/Completion/_rlogin9
-rw-r--r--Functions/Completion/_sched3
-rw-r--r--Functions/Completion/_set7
-rw-r--r--Functions/Completion/_setopt7
-rw-r--r--Functions/Completion/_source7
-rw-r--r--Functions/Completion/_strip2
-rw-r--r--Functions/Completion/_stty16
-rw-r--r--Functions/Completion/_subscript4
-rw-r--r--Functions/Completion/_tar11
-rw-r--r--Functions/Completion/_tex3
-rw-r--r--Functions/Completion/_trap7
-rw-r--r--Functions/Completion/_uncompress3
-rw-r--r--Functions/Completion/_unhash6
-rw-r--r--Functions/Completion/_unsetopt7
-rw-r--r--Functions/Completion/_vars3
-rw-r--r--Functions/Completion/_vars_eq3
-rw-r--r--Functions/Completion/_wait4
-rw-r--r--Functions/Completion/_which3
-rw-r--r--Functions/Completion/_x_options5
-rw-r--r--Functions/Completion/_xfig3
-rw-r--r--Functions/Completion/_zftp50
-rw-r--r--Functions/Completion/_zle7
-rw-r--r--Functions/Completion/_zmodload9
-rw-r--r--Functions/Completion/dump89
-rw-r--r--Functions/Completion/init253
-rw-r--r--Functions/allopt29
-rw-r--r--Functions/zless37
-rw-r--r--Misc/new-completion-examples453
-rw-r--r--Misc/zftp-functions1281
-rw-r--r--Src/.cvsignore25
-rw-r--r--Src/.distfiles11
-rw-r--r--Src/.exrc2
-rw-r--r--Src/.lastloc5
-rw-r--r--Src/Builtins/.cvsignore11
-rw-r--r--Src/Builtins/.distfiles5
-rw-r--r--Src/Builtins/.exrc2
-rw-r--r--Src/Builtins/rlimits.awk72
-rw-r--r--Src/Builtins/rlimits.c593
-rw-r--r--Src/Builtins/rlimits.mdd18
-rw-r--r--Src/Builtins/sched.c214
-rw-r--r--Src/Builtins/sched.mdd3
-rw-r--r--Src/Makefile.in195
-rw-r--r--Src/Makemod.in.in180
-rw-r--r--Src/Modules/.cvsignore10
-rw-r--r--Src/Modules/.distfiles8
-rw-r--r--Src/Modules/.exrc2
-rw-r--r--Src/Modules/cap.c141
-rw-r--r--Src/Modules/cap.mdd3
-rw-r--r--Src/Modules/clone.c115
-rw-r--r--Src/Modules/clone.mdd3
-rw-r--r--Src/Modules/example.c76
-rw-r--r--Src/Modules/example.mdd3
-rw-r--r--Src/Modules/files.c528
-rw-r--r--Src/Modules/files.mdd3
-rw-r--r--Src/Modules/stat.c535
-rw-r--r--Src/Modules/stat.mdd3
-rw-r--r--Src/Modules/zftp.c2596
-rw-r--r--Src/Modules/zftp.mdd3
-rw-r--r--Src/Zle/.cvsignore14
-rw-r--r--Src/Zle/.distfiles10
-rw-r--r--Src/Zle/.exrc2
-rw-r--r--Src/Zle/.lastloc10
-rw-r--r--Src/Zle/comp.h137
-rw-r--r--Src/Zle/comp1.c291
-rw-r--r--Src/Zle/comp1.export22
-rw-r--r--Src/Zle/comp1.mdd3
-rw-r--r--Src/Zle/compctl.c1085
-rw-r--r--Src/Zle/compctl.mdd5
-rw-r--r--Src/Zle/deltochar.c91
-rw-r--r--Src/Zle/deltochar.mdd3
-rw-r--r--Src/Zle/iwidgets.list172
-rw-r--r--Src/Zle/zle.export10
-rw-r--r--Src/Zle/zle.h137
-rw-r--r--Src/Zle/zle.mdd70
-rw-r--r--Src/Zle/zle_bindings.c421
-rw-r--r--Src/Zle/zle_hist.c1139
-rw-r--r--Src/Zle/zle_keymap.c1238
-rw-r--r--Src/Zle/zle_main.c907
-rw-r--r--Src/Zle/zle_misc.c816
-rw-r--r--Src/Zle/zle_move.c502
-rw-r--r--Src/Zle/zle_params.c196
-rw-r--r--Src/Zle/zle_refresh.c1116
-rw-r--r--Src/Zle/zle_things.sed9
-rw-r--r--Src/Zle/zle_thingy.c491
-rw-r--r--Src/Zle/zle_tricky.c4015
-rw-r--r--Src/Zle/zle_utils.c650
-rw-r--r--Src/Zle/zle_vi.c925
-rw-r--r--Src/Zle/zle_widget.sed7
-rw-r--r--Src/Zle/zle_word.c477
-rw-r--r--Src/ansi2knr.c413
-rw-r--r--Src/builtin.c3599
-rw-r--r--Src/compat.c285
-rw-r--r--Src/cond.c226
-rw-r--r--Src/exec.c2965
-rw-r--r--Src/glob.c2800
-rw-r--r--Src/hashtable.c1285
-rw-r--r--Src/hashtable.h62
-rw-r--r--Src/hist.c1670
-rw-r--r--Src/init.c936
-rw-r--r--Src/input.c530
-rw-r--r--Src/jobs.c1361
-rw-r--r--Src/lex.c1489
-rw-r--r--Src/linklist.c222
-rw-r--r--Src/loop.c421
-rw-r--r--Src/main.c99
-rw-r--r--Src/makepro.awk146
-rw-r--r--Src/math.c883
-rw-r--r--Src/mem.c1254
-rw-r--r--Src/mkbltnmlst.sh60
-rw-r--r--Src/mkmakemod.sh315
-rw-r--r--Src/mkmodindex.sh43
-rw-r--r--Src/modentry.c15
-rw-r--r--Src/module.c651
-rw-r--r--Src/options.c663
-rw-r--r--Src/params.c2191
-rw-r--r--Src/parse.c1379
-rw-r--r--Src/prompt.c766
-rw-r--r--Src/prototypes.h120
-rw-r--r--Src/signals.c748
-rw-r--r--Src/signals.h94
-rwxr-xr-xSrc/signames.awk98
-rw-r--r--Src/signames1.awk19
-rw-r--r--Src/signames2.awk100
-rw-r--r--Src/subst.c1773
-rw-r--r--Src/system.h598
-rw-r--r--Src/text.c526
-rw-r--r--Src/utils.c3726
-rw-r--r--Src/watch.c586
-rw-r--r--Src/xmods.conf5
-rw-r--r--Src/zsh.export235
-rw-r--r--Src/zsh.h1293
-rw-r--r--Src/zsh.mdd71
-rw-r--r--Src/ztype.h58
-rw-r--r--StartupFiles/.distfiles4
-rw-r--r--StartupFiles/zlogin20
-rw-r--r--StartupFiles/zshenv17
-rw-r--r--StartupFiles/zshrc121
-rw-r--r--Util/.distfiles4
-rwxr-xr-xUtil/helpfiles182
-rwxr-xr-xUtil/mkdisttree.sh76
-rw-r--r--Util/reporter444
-rw-r--r--Util/zsh-development-guide138
354 files changed, 66043 insertions, 0 deletions
diff --git a/Completion/.distfiles b/Completion/.distfiles
new file mode 100644
index 000000000..c50107c61
--- /dev/null
+++ b/Completion/.distfiles
@@ -0,0 +1,3 @@
+DISTFILES_SRC='
+ .distfiles README
+'
diff --git a/Completion/Base/.distfiles b/Completion/Base/.distfiles
new file mode 100644
index 000000000..7e7635fa6
--- /dev/null
+++ b/Completion/Base/.distfiles
@@ -0,0 +1,6 @@
+DISTFILES_SRC='
+ .distfiles
+ _brace_parameter _command_names _condition _default _equal
+ _long_options _match_pattern _match_pattern.orig _match_test _parameter
+ _precommand _redirect _subscript _tilde _vars
+'
diff --git a/Completion/Base/_brace_parameter b/Completion/Base/_brace_parameter
new file mode 100644
index 000000000..092376e78
--- /dev/null
+++ b/Completion/Base/_brace_parameter
@@ -0,0 +1,5 @@
+#defcomp -brace-parameter-
+
+# Simple but without spiffy suffix handling: compgen -v -S '} '
+
+compadd -S '} ' -r '-:?#%+=[/' - "${(@)${${${(f)$(typeset)}%%\=*}##* }:gs/'//}"
diff --git a/Completion/Base/_command_names b/Completion/Base/_command_names
new file mode 100644
index 000000000..d3b8a109a
--- /dev/null
+++ b/Completion/Base/_command_names
@@ -0,0 +1,3 @@
+#defcomp -command-
+
+complist -c
diff --git a/Completion/Base/_condition b/Completion/Base/_condition
new file mode 100644
index 000000000..3e45e1b8f
--- /dev/null
+++ b/Completion/Base/_condition
@@ -0,0 +1,10 @@
+#defcomp -condition-
+
+if [[ -current -1 -o ]]; then
+ complist -o -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}'
+elif [[ -current -1 -nt || -current -1 -ot || -current -1 -ef ]]; then
+ _files
+else
+ _files
+ complist -v
+fi
diff --git a/Completion/Base/_default b/Completion/Base/_default
new file mode 100644
index 000000000..8bcf14f6a
--- /dev/null
+++ b/Completion/Base/_default
@@ -0,0 +1,13 @@
+#defcomp -default-
+
+# We first try the `compctl's. This is without first (-T) and default (-D)
+# completion. If you want them add `-T' and/or `-D' to this command.
+# If there is a `compctl' for the command we are working on, we return
+# immediatly. If you want to use new style completion anyway, remove the
+# `|| return'. Also, you may want to use new style completion if the
+# `compctl' didn't produce any matches. In that case remove the `|| return'
+# and at the line `[[ -nmatches 0 ]] || return' after `compcall'.
+
+compcall || return
+
+_files
diff --git a/Completion/Base/_equal b/Completion/Base/_equal
new file mode 100644
index 000000000..f407014fe
--- /dev/null
+++ b/Completion/Base/_equal
@@ -0,0 +1,3 @@
+#defcomp -equal-
+
+compgen -am
diff --git a/Completion/Base/_long_options b/Completion/Base/_long_options
new file mode 100644
index 000000000..a5d92632c
--- /dev/null
+++ b/Completion/Base/_long_options
@@ -0,0 +1,309 @@
+#autoload
+
+# This function tries to automatically complete long option names. For
+# this it invokes the command from the line with the `--help' option
+# and then parses the output to find possible option names. For
+# options that get an argument after a `=', the function also tries to
+# automatically find out what should be complete as the argument.
+# The possible completions for option-arguments can be described with
+# the arguments to this function. This is done by giving pairs of
+# patterns and actions as consecutive arguments. The actions specify
+# what should be done to complete arguemts of those options that match
+# the pattern. The action may be a list of words in brackets or in
+# parentheses, separated by spaces. A list in brackets denotes
+# possible values for an optional argument, a list in parentheses
+# gives words to complete for mandatory arguments. If the action does
+# not start with a bracket or parentheses, it should be the name of a
+# command (probably with arguments) that should be invoked to complete
+# after the equal sign. E.g.:
+#
+# _long_options '*\*' '(yes no)' \
+# '*=FILE*' '_files' \
+# '*=DIR*' '_files -/'
+#
+# This makes `yes' and `no' be completed as the argument of options
+# whose description ends in a star, file names for options that
+# contain the substring `=FILE' in the description, and paths for
+# options whose description contains `=DIR'. Note the last two
+# patterns are not needed since this function always completes files
+# for option descriptions containing `=FILE' and paths for option
+# descriptions that contain `=DIR' or `=PATH'. These builtin patterns
+# can be overridden by patterns given as arguments, though.
+#
+# This function also accepts the `-X', `-J', and `-V' options which
+# are given to `compadd'. Finally, it accepts the option `-t'. If this
+# is given, completion is only done on words starting with two hyphens.
+
+local opt expl group test i name action ret=1 tmp suffix
+
+setopt extendedglob
+
+# Get the options.
+
+group=()
+expl=()
+if [[ $1 = -*~--* ]]; then
+ while getopts "J:V:X:t" opt; do
+ case "$opt" in
+ [JV]) group=("-$opt" "$OPTARG");;
+ X) expl=(-X "$OPTARG");;
+ t) test=yes;;
+ esac
+ done
+ shift OPTIND-1
+fi
+
+# Test if we are completing after `--' if we were asked to do so.
+
+[[ -n "$test" && "$PREFIX" != --* ]] && return 1
+
+# We cache the information about options and the command name, see if
+# we can use the cache.
+
+if [[ "$words[1]" = (.|..)/* ]]; then
+ tmp="$PWD/$words[1]"
+else
+ tmp="$words[1]"
+fi
+
+if [[ "$tmp" != $_lo_cache_cmd ]]; then
+
+ # No, store the new command name and clear the old parameters.
+
+ _lo_cache_cmd="$tmp"
+ (( $+_lo_cache_actions )) && unset "$_lo_cache_names[@]" _lo_cache_actions _lo_cache_names
+
+ local opts pattern anum=1 tmpo str
+
+ # Now get the long option names by calling the command with `--help'.
+ # The parameter expansion trickery first gets the lines as separate
+ # array elements. Then we select all lines whose first non-blank
+ # character is a hyphen. Since some commands document more than one
+ # option per line, separated by commas, we convert commas int
+ # newlines and then split the result again at newlines after joining
+ # the old array elements with newlines between them. Then we select
+ # those elements that start with two hyphens, remove anything up to
+ # those hyphens and anything from the space or comma after the
+ # option up to the end. Finally all elements with option strings
+ # that contain uppercase letters are removed.
+
+ opts=("--${(@)^${(@)${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$("$words[1]" --help)}:#[ ]#-*}//,/
+}}:#[ ]#--*}#*--}%%[, ]*}:#(*-[A-Z]*|)}")
+
+ # The interpretation of the options is completely table driven. We
+ # use the positional parameters we were given and a few standard
+ # ones. Then we loop through this table.
+
+ set -- "$@" '*=FILE*' '_files' '*=(DIR|PATH)*' '_files -/' '*' ''
+
+ while [[ $# -gt 1 ]]; do
+
+ # First, we get the pattern and the action to use and take them
+ # from the positional parameters.
+
+ pattern="$1"
+ action="$2"
+ shift 2
+
+ # We get all options matching the pattern and take them from the
+ # list we have built. If no option matches the pattern, we
+ # continue with the next.
+
+ tmp=("${(@M)opts:##$~pattern}")
+ opts=("${(@)opts:##$~pattern}")
+
+ (( $#tmp )) || continue
+
+ # Now we collect the options for the pattern in an array. We also
+ # check if the options take an argument after a `=', and if this
+ # argument is optional. The name of the array built contains
+ # `_arg_' for mandatory arguments, `_optarg_' for optional
+ # arguments, and `_simple_' for options that don't get an
+ # argument. In `_lo_cache_names' we save the names of these
+ # arrays and in `_lo_cache_actions' the associated actions.
+
+ # If the action is a list of words in brackets, this denotes
+ # options that get an optional argument. If the action is a list
+ # of words in parentheses, the option has to get an argument.
+ # In both cases we just build the array name to use.
+
+ if [[ "$action[1]" = '[' ]]; then
+ name="_lo_cache_optarg_$anum"
+ elif [[ "$action[1]" = '(' ]]; then
+ name="_lo_cache_arg_$anum"
+ else
+
+ # If there are option strings with a `[=', we take make these
+ # get an optional argument...
+
+ tmpo=("${(@M)tmp:#*\[\=*}")
+ if (( $#tmpo )); then
+
+ # ...by removing them from the option list and storing them in
+ # an array.
+
+ tmp=("${(@)tmp:#*\[\=*}")
+ tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
+ _lo_cache_names[anum]="_lo_cache_optarg_$anum"
+ _lo_cache_actions[anum]="$action"
+ eval "_lo_cache_optarg_${anum}=(\"\$tmpo[@]\")"
+ (( anum++ ))
+ fi
+
+ # Now we do the same for option strings containing `=', these
+ # are options getting an argument.
+
+ tmpo=("${(@M)tmp:#*\=*}")
+ if (( $#tmpo )); then
+ tmp=("${(@)tmp:#*\=*}")
+ tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
+ _lo_cache_names[anum]="_lo_cache_arg_$anum"
+ _lo_cache_actions[anum]="$action"
+ eval "_lo_cache_arg_${anum}=(\"\$tmpo[@]\")"
+ (( anum++ ))
+ fi
+
+ # The name for the options without arguments, if any.
+
+ name="_lo_cache_simple_$anum"
+ fi
+ # Now filter out any option strings we don't like and stuff them
+ # in an array, if there are still some.
+
+ tmp=("${(@)${(@)tmp%%\=*}//[^a-z0-9-]}")
+ if (( $#tmp )); then
+ _lo_cache_names[anum]="$name"
+ _lo_cache_actions[anum]="$action"
+ eval "${name}=(\"\$tmp[@]\")"
+ (( anum++ ))
+ fi
+ done
+fi
+
+# We get the string from the line and and see if it already contains a
+# equal sign.
+
+str="$PREFIX$SUFFIX"
+
+if [[ "$str" = *\=* ]]; then
+
+ # It contains a `=', now we ignore anything up to it, but first save
+ # the old contents of the special parameters we change.
+
+ local oipre opre osuf pre parto parta pat patflags anum=1
+
+ oipre="$IPREFIX"
+ opre="$PREFIX"
+ osuf="$SUFFIX"
+
+ pre="${str%%\=*}"
+ IPREFIX="${IPREFIX}${pre}="
+ PREFIX="${str#*\=}"
+ SUFFIX=""
+
+ # We will check if the arrays contain an option matching what's on
+ # the line. To do this good, we build a pattern.
+
+ [[ -n "$_comp_correct" && $#pre -le _comp_correct ]] && return 1
+
+ pat="${pre}*"
+ patflags=''
+ _match_pattern _long_options pat patflags
+ [[ -n "$_comp_correct" ]] && patflags="$patflags(#a$_comp_correct)"
+
+ # Then we walk through the array names. For each array we test if it
+ # contains the option string. If so, we `invoke' the action stored
+ # with the name. If the action is a list of words, we just add them,
+ # otherwise we invoke the command or function named.
+
+ for name in "$_lo_cache_names[@]"; do
+ action="$_lo_cache_actions[anum]"
+ if (( ${(@)${(@P)name}[(I)$pre]} )); then
+ if [[ "$action[1]" = (\[|\() ]]; then
+ compadd - ${=action[2,-2]}
+ elif (( $#action )); then
+ $=action
+ fi
+
+ # We found the option string, return.
+
+ return
+ fi
+
+ # The array did not contain the full option string, see if it
+ # contains a string matching the string from the line.
+ # If there is one, we store the option string in `parto' and the
+ # element from `_lo_actions' in `parta'. If we find more than one
+ # such option or if we already had one, we set `parto' to `-'.
+
+ tmp=("${(@M)${(@P)name}:#${~pat}}")
+ if [[ $#tmp -eq 1 ]]; then
+ if [[ -z "$parto" ]]; then
+ parto="$tmp[1]"
+ parta="$action"
+ else
+ parto=-
+ fi
+ elif (( $#tmp )); then
+ parto=-
+ fi
+ (( anum++ ))
+ done
+
+ # If we found only one matching option, we accept it and immediatly
+ # try to complete the string after the `='.
+
+ if [[ -n "$parto" && "$parto" != - ]]; then
+ IPREFIX="${parto}="
+
+ if (( $#parta )); then
+ if [[ "$parta[1]" = (\[|\() ]]; then
+ compadd - ${=parta[2,-2]}
+ else
+ $=parta
+ fi
+ else
+ compadd -S '' - "$PREFIX"
+ fi
+ return
+ fi
+
+ # The option string was not found, restore the special parameters.
+
+ IPREFIX="$oipre"
+ PREFIX="$opre"
+ SUFFIX="$osuf"
+fi
+
+# The string on the line did not contain a `=', or we couldn't
+# complete the option string since there were more than one matching
+# what's on the line. So we just ad the option string as possible
+# matches, giving the string from the `=' on as a suffix.
+
+if [[ "$str" = *\=* ]]; then
+ str="=${str#*\=}"
+ PREFIX="${PREFIX%%\=*}"
+ suffix=()
+else
+ str=""
+ suffix=('-S=')
+fi
+
+anum=1
+for name in "$_lo_cache_names[@]"; do
+ action="$_lo_cache_actions[anum]"
+
+ if [[ "$name" = *_optarg_* ]]; then
+ compadd -M 'r:|-=* r:|=*' -Qq "$suffix[@]" -s "$str" - \
+ "${(@P)name}" && ret=0
+ elif [[ "$name" = *_arg_* ]]; then
+ compadd -M 'r:|-=* r:|=*' -Q "$suffix[@]" -s "$str" - \
+ "${(@P)name}" && ret=0
+ elif [[ -z "$str" ]]; then
+ compadd -M 'r:|-=* r:|=*' -Q - \
+ "${(@P)name}" && ret=0
+ fi
+ (( anum++ ))
+done
+
+return ret
diff --git a/Completion/Base/_match_pattern b/Completion/Base/_match_pattern
new file mode 100644
index 000000000..c5debc0b9
--- /dev/null
+++ b/Completion/Base/_match_pattern
@@ -0,0 +1,31 @@
+#autoload
+
+# This function is called from functions that do matching whenever they
+# need to build a pattern that is used to match possible completions.
+# It gets the name of the calling function and two names of parameters
+# as arguments. The first one is used in the calling function to build
+# the pattern used for matching possible completions. The content of this
+# parameter on entry to this function is the string taken from the line.
+# Here it parameter should be changed to a pattern that matches words as
+# the match specs currently in use do.
+# In the calling function this pattern may be changed again or used only
+# in parts. The second parameter whose name is given as the third argument
+# allows to give pattern flags liek `(#l)' that are to be used whenever
+# matching is done.
+#
+# As an example, if you have global match specifications like:
+#
+# compctl -M 'm:{a-z}={A-Z}' 'm:{a-z}={A-Z} r:|[.-]=* r:|=*'
+#
+# This function would look like:
+#
+# eval "${3}='(#l)'"
+# [[ MATCHER -eq 2 ]] && eval "$1='${(P)2:gs/./*./:gs/-/*-/}'"
+#
+# The first line makes sure that matching is done case-insensitive as
+# specified by `m:{a-z}={A-Z}'. The second line replaces dots and hyphens
+# in the given string by patterns matching any characters before them,
+# like the `r:|[.-]=* r:|=*'. To make this work, the function `_match_test'
+# would have to be changed to `(( MATCHERS <= 2 ))'
+#
+# The default implementation of this function is empty.
diff --git a/Completion/Base/_match_test b/Completion/Base/_match_test
new file mode 100644
index 000000000..e8b6e6424
--- /dev/null
+++ b/Completion/Base/_match_test
@@ -0,0 +1,15 @@
+#autoload
+
+# This function is called at the beginning of functions that do matching in
+# shell code. It should test the value of the `MATCHER' special parameter
+# and return non-zero if the calling function should try to generate matches
+# for the global match specification in use.
+#
+# This function gets one argument, the name of the function calling it.
+#
+# If you have a global match specification with more than one set of patterns
+# you may want to modify this function to return non-zero for all of your
+# match specifications and modify the function `_match_pattern' to build the
+# pattern to use in the calling function.
+
+(( MATCHER == 1 ))
diff --git a/Completion/Base/_math b/Completion/Base/_math
new file mode 100644
index 000000000..f7f4c360f
--- /dev/null
+++ b/Completion/Base/_math
@@ -0,0 +1,18 @@
+#defcomp -math-
+
+if [[ "$PREFIX" = *[^a-zA-Z0-9_]* ]]; then
+ IPREFIX="$IPREFIX${PREFIX%%[a-zA-Z0-9_]#}"
+ PREFIX="${PREFIX##*[^a-zA-Z0-9_]}"
+fi
+if [[ "$SUFFIX" = *[^a-zA-Z0-9_]* ]]; then
+ ISUFFIX="${SUFFIX##[a-zA-Z0-9_]#}$ISUFFIX"
+ SUFFIX="${SUFFIX%%[^a-zA-Z0-9_]*}"
+fi
+
+compgen -v
+#defcomp -math-
+
+IPREFIX="$IPREFIX${PREFIX%[a-zA-Z0-9_]*}"
+PREFIX="${PREFIX##*[^a-zA-Z0-9_]}"
+
+compgen -v
diff --git a/Completion/Base/_parameter b/Completion/Base/_parameter
new file mode 100644
index 000000000..2bd66ec93
--- /dev/null
+++ b/Completion/Base/_parameter
@@ -0,0 +1,3 @@
+#defcomp -parameter-
+
+compgen -v
diff --git a/Completion/Base/_precommand b/Completion/Base/_precommand
new file mode 100644
index 000000000..2cf661147
--- /dev/null
+++ b/Completion/Base/_precommand
@@ -0,0 +1,5 @@
+#defcomp - nohup nice eval time rusage noglob nocorrect exec
+
+[[ -position 1 -1 ]]
+
+_normal "$@"
diff --git a/Completion/Base/_redirect b/Completion/Base/_redirect
new file mode 100644
index 000000000..32113ad7c
--- /dev/null
+++ b/Completion/Base/_redirect
@@ -0,0 +1,3 @@
+#defcomp -redirect-
+
+_files
diff --git a/Completion/Base/_subscript b/Completion/Base/_subscript
new file mode 100644
index 000000000..2b827a117
--- /dev/null
+++ b/Completion/Base/_subscript
@@ -0,0 +1,4 @@
+#defcomp -subscript-
+
+_compalso -math- "$@"
+[[ ${(Pt)${COMMAND}} = assoc* ]] && complist -k "( ${(kP)${COMMAND}} )"
diff --git a/Completion/Base/_tilde b/Completion/Base/_tilde
new file mode 100644
index 000000000..aef575e19
--- /dev/null
+++ b/Completion/Base/_tilde
@@ -0,0 +1,10 @@
+#defcomp -tilde-
+
+# We use all named directories and user names here. If this is too slow
+# for you or if there are too many of them, you may want to use
+# `compgen -k friends -qS/' or something like that. To get all user names
+# if there are no matches in the `friends' array, add
+# `(( compstate[nmatches] )) || compgen -nu -qS/'
+# below that.
+
+compgen -nu -qS/
diff --git a/Completion/Base/_vars b/Completion/Base/_vars
new file mode 100644
index 000000000..7153b6f38
--- /dev/null
+++ b/Completion/Base/_vars
@@ -0,0 +1,3 @@
+#defcomp -math- getopts read unset vared
+
+complist -v
diff --git a/Completion/Builtins/.distfiles b/Completion/Builtins/.distfiles
new file mode 100644
index 000000000..97906e91f
--- /dev/null
+++ b/Completion/Builtins/.distfiles
@@ -0,0 +1,7 @@
+DISTFILES_SRC='
+ .distfiles
+ _aliases _arrays _autoload _bg_jobs _bindkey _builtin _cd _command
+ _dirs _disable _echotc _enable _fc _functions _hash _jobs _kill
+ _limits _sched _set _setopt _source _trap _unhash _unsetopt _vars_eq
+ _wait _which _zftp _zle _zmodload
+'
diff --git a/Completion/Builtins/_aliases b/Completion/Builtins/_aliases
new file mode 100644
index 000000000..1038a726e
--- /dev/null
+++ b/Completion/Builtins/_aliases
@@ -0,0 +1,3 @@
+#defcomp unalias
+
+complist -a
diff --git a/Completion/Builtins/_arrays b/Completion/Builtins/_arrays
new file mode 100644
index 000000000..cbeac7118
--- /dev/null
+++ b/Completion/Builtins/_arrays
@@ -0,0 +1,3 @@
+#defcomp shift
+
+complist -A
diff --git a/Completion/Builtins/_autoload b/Completion/Builtins/_autoload
new file mode 100644
index 000000000..4f506baeb
--- /dev/null
+++ b/Completion/Builtins/_autoload
@@ -0,0 +1,3 @@
+#defcomp autoload
+
+complist -s '${^fpath}/*(N:t)'
diff --git a/Completion/Builtins/_bg_jobs b/Completion/Builtins/_bg_jobs
new file mode 100644
index 000000000..511bb8308
--- /dev/null
+++ b/Completion/Builtins/_bg_jobs
@@ -0,0 +1,3 @@
+#defcomp bg
+
+complist -z -P '%'
diff --git a/Completion/Builtins/_bindkey b/Completion/Builtins/_bindkey
new file mode 100644
index 000000000..8eddeb2a8
--- /dev/null
+++ b/Completion/Builtins/_bindkey
@@ -0,0 +1,7 @@
+#defcomp bindkey
+
+if [[ -mword 1 -*[DAN]* || -mcurrent -1 -*M ]]; then
+ complist -s '$(bindkey -l)'
+else
+ complist -b
+fi
diff --git a/Completion/Builtins/_builtin b/Completion/Builtins/_builtin
new file mode 100644
index 000000000..a967932ee
--- /dev/null
+++ b/Completion/Builtins/_builtin
@@ -0,0 +1,7 @@
+#defcomp builtin
+
+if [[ -position 2 -1 ]]; then
+ _normal "$@"
+else
+ complist -eB
+fi
diff --git a/Completion/Builtins/_cd b/Completion/Builtins/_cd
new file mode 100644
index 000000000..f3ce67ec7
--- /dev/null
+++ b/Completion/Builtins/_cd
@@ -0,0 +1,3 @@
+#defcomp cd
+
+_files -W cdpath -g '*(-/)'
diff --git a/Completion/Builtins/_command b/Completion/Builtins/_command
new file mode 100644
index 000000000..b2812de25
--- /dev/null
+++ b/Completion/Builtins/_command
@@ -0,0 +1,7 @@
+#defcomp command
+
+if [[ -position 2 -1 ]]; then
+ _normal "$@"
+else
+ complist -em
+fi
diff --git a/Completion/Builtins/_dirs b/Completion/Builtins/_dirs
new file mode 100644
index 000000000..bc426e322
--- /dev/null
+++ b/Completion/Builtins/_dirs
@@ -0,0 +1,3 @@
+#defcomp rmdir df du dircmp
+
+_files -/
diff --git a/Completion/Builtins/_disable b/Completion/Builtins/_disable
new file mode 100644
index 000000000..063b65a7d
--- /dev/null
+++ b/Completion/Builtins/_disable
@@ -0,0 +1,6 @@
+#defcomp disable
+
+[[ -mcurrent -1 -*a* ]] && complist -ea
+[[ -mcurrent -1 -*f* ]] && complist -eF
+[[ -mcurrent -1 -*r* ]] && complist -ew
+[[ ! -mcurrent -1 -* ]] && complist -eB
diff --git a/Completion/Builtins/_echotc b/Completion/Builtins/_echotc
new file mode 100644
index 000000000..85ebb97ce
--- /dev/null
+++ b/Completion/Builtins/_echotc
@@ -0,0 +1,3 @@
+#defcomp echotc
+
+complist -k '(al dc dl do le up al bl cd ce cl cr dc dl do ho is le ma nd nl se so up)'
diff --git a/Completion/Builtins/_enable b/Completion/Builtins/_enable
new file mode 100644
index 000000000..22ff53ee7
--- /dev/null
+++ b/Completion/Builtins/_enable
@@ -0,0 +1,6 @@
+#defcomp enable
+
+[[ -mcurrent -1 -*a* ]] && complist -da
+[[ -mcurrent -1 -*f* ]] && complist -dF
+[[ -mcurrent -1 -*r* ]] && complist -dw
+[[ ! -mcurrent -1 -* ]] && complist -dB
diff --git a/Completion/Builtins/_fc b/Completion/Builtins/_fc
new file mode 100644
index 000000000..f0d2c03fd
--- /dev/null
+++ b/Completion/Builtins/_fc
@@ -0,0 +1,7 @@
+#defcomp fc
+
+if [[ -mcurrent -1 -*e ]]; then
+ complist -c
+elif [[ -mcurrent -1 -[ARWI]## ]]; then
+ _files
+fi
diff --git a/Completion/Builtins/_functions b/Completion/Builtins/_functions
new file mode 100644
index 000000000..8a352ea08
--- /dev/null
+++ b/Completion/Builtins/_functions
@@ -0,0 +1,3 @@
+#defcomp unfunction
+
+complist -F
diff --git a/Completion/Builtins/_hash b/Completion/Builtins/_hash
new file mode 100644
index 000000000..171c5e2e8
--- /dev/null
+++ b/Completion/Builtins/_hash
@@ -0,0 +1,13 @@
+#defcomp hash
+
+if [[ -mword 1 -*d* ]]; then
+ if [[ -string 1 '=' ]]; then
+ _path_files -g '*(-/)'
+ else
+ complist -n -q -S '='
+ fi
+elif [[ -string 1 '=' ]]; then
+ _files -/g '*(*)'
+else
+ complist -m -q -S '='
+fi
diff --git a/Completion/Builtins/_jobs b/Completion/Builtins/_jobs
new file mode 100644
index 000000000..018883c61
--- /dev/null
+++ b/Completion/Builtins/_jobs
@@ -0,0 +1,3 @@
+#defcomp fg jobs
+
+complist -j -P '%'
diff --git a/Completion/Builtins/_kill b/Completion/Builtins/_kill
new file mode 100644
index 000000000..50796d36f
--- /dev/null
+++ b/Completion/Builtins/_kill
@@ -0,0 +1,11 @@
+#defcomp kill
+
+local list
+
+if [[ -iprefix '-' ]]; then
+ complist -k "($signals[1,-3])"
+else
+ complist -P '%' -j
+ list=("$(ps 2>/dev/null)")
+ complist -y '$list' -s '`ps 2>/dev/null | tail +2 | cut -c1-5`'
+fi
diff --git a/Completion/Builtins/_limits b/Completion/Builtins/_limits
new file mode 100644
index 000000000..35ccbe07e
--- /dev/null
+++ b/Completion/Builtins/_limits
@@ -0,0 +1,3 @@
+#defcomp limit unlimit
+
+complist -k "(${(j: :)${(f)$(limit)}%% *})"
diff --git a/Completion/Builtins/_sched b/Completion/Builtins/_sched
new file mode 100644
index 000000000..1e8ae3445
--- /dev/null
+++ b/Completion/Builtins/_sched
@@ -0,0 +1,3 @@
+#defcomp sched
+
+[[ -position 2 -1 ]] && _normal "$@"
diff --git a/Completion/Builtins/_set b/Completion/Builtins/_set
new file mode 100644
index 000000000..5597025bc
--- /dev/null
+++ b/Completion/Builtins/_set
@@ -0,0 +1,7 @@
+#defcomp set
+
+if [[ -mcurrent -1 [-+]o ]]; then
+ complist -o
+elif [[ -current -1 -A ]]; then
+ complist -A
+fi
diff --git a/Completion/Builtins/_setopt b/Completion/Builtins/_setopt
new file mode 100644
index 000000000..4abb3ccee
--- /dev/null
+++ b/Completion/Builtins/_setopt
@@ -0,0 +1,7 @@
+#defcomp setopt
+
+local nm=$NMATCHES
+
+complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \
+ -s '$({ unsetopt kshoptionprint; unsetopt } 2>/dev/null)'
+[[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Completion/Builtins/_source b/Completion/Builtins/_source
new file mode 100644
index 000000000..aae2c7320
--- /dev/null
+++ b/Completion/Builtins/_source
@@ -0,0 +1,7 @@
+#defcomp source
+
+if [[ -position 2 -1 ]]; then
+ _normal "$@"
+else
+ _files
+fi
diff --git a/Completion/Builtins/_trap b/Completion/Builtins/_trap
new file mode 100644
index 000000000..59e81c589
--- /dev/null
+++ b/Completion/Builtins/_trap
@@ -0,0 +1,7 @@
+#defcomp trap
+
+if [[ -position 1 ]]; then
+ complist -c
+else
+ complist -k signals
+fi
diff --git a/Completion/Builtins/_unhash b/Completion/Builtins/_unhash
new file mode 100644
index 000000000..fe40c25a2
--- /dev/null
+++ b/Completion/Builtins/_unhash
@@ -0,0 +1,6 @@
+#defcomp unhash
+
+[[ -mword 1 -*d* ]] && complist -n
+[[ -mword 1 -*a* ]] && complist -a
+[[ -mword 1 -*f* ]] && complist -F
+[[ ! -mword 1 -* ]] && complist -m
diff --git a/Completion/Builtins/_unsetopt b/Completion/Builtins/_unsetopt
new file mode 100644
index 000000000..90d642b51
--- /dev/null
+++ b/Completion/Builtins/_unsetopt
@@ -0,0 +1,7 @@
+#defcomp unsetopt
+
+local nm=$NMATCHES
+
+complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \
+ -s '$({ unsetopt kshoptionprint; setopt } 2>/dev/null)'
+[[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Completion/Builtins/_vars_eq b/Completion/Builtins/_vars_eq
new file mode 100644
index 000000000..fcbb0148c
--- /dev/null
+++ b/Completion/Builtins/_vars_eq
@@ -0,0 +1,3 @@
+#defcomp declare export integer local readonly typeset
+
+complist -v -q -S '='
diff --git a/Completion/Builtins/_wait b/Completion/Builtins/_wait
new file mode 100644
index 000000000..29a7f6002
--- /dev/null
+++ b/Completion/Builtins/_wait
@@ -0,0 +1,7 @@
+#defcomp wait
+
+local list
+
+complist -P '%' -j
+list=("$(ps 2>/dev/null)")
+complist -y '$list' -s '`ps 2>/dev/null | tail +2 | cut -c1-5`'
diff --git a/Completion/Builtins/_which b/Completion/Builtins/_which
new file mode 100644
index 000000000..324256e3d
--- /dev/null
+++ b/Completion/Builtins/_which
@@ -0,0 +1,3 @@
+#defcomp which whence where type
+
+complist -caF
diff --git a/Completion/Builtins/_zftp b/Completion/Builtins/_zftp
new file mode 100644
index 000000000..9be9c94db
--- /dev/null
+++ b/Completion/Builtins/_zftp
@@ -0,0 +1,50 @@
+#defpatcomp zf*
+
+# Don't try any more completion after this.
+_compskip=1
+
+# Completion for zftp builtin and zf* functions. The functions
+# zfcd_match and zfget_match (used for old-style completion)
+# need to be installed for remote file and directory completion to work.
+
+local subcom
+
+if [[ $COMMAND = zftp ]]; then
+ if [[ $CURRENT -eq 1 ]]; then
+ compadd -m open params user login type ascii binary mode put \
+ putat get getat append appendat ls dir local remote mkdir rmdir
+ return
+ fi
+ subcom=$1
+else
+ subcom=$COMMAND
+fi
+
+case $subcom in
+ *(cd|ls|dir))
+ # complete remote directories; we could be smarter about hiding prefixes
+ zfcd_match $PREFIX $SUFFIX
+ (( $#reply )) && compadd -m -S/ -q $reply
+ ;;
+
+ *(get(|at)|gcp|delete|remote))
+ # complete remote files
+ zfget_match $PREFIX $SUFFIX
+ (( $#reply )) && compadd -F fignore -m $reply
+ ;;
+
+ *(put(|at)|pcp))
+ # complete local files
+ _files
+ ;;
+
+ *(open|anon|params))
+ # complete hosts: should do cleverer stuff with user names
+ complist -k hosts
+ ;;
+
+ *)
+ # dunno... try ordinary completion after all.
+ unset _compskip
+ ;;
+esac
diff --git a/Completion/Builtins/_zle b/Completion/Builtins/_zle
new file mode 100644
index 000000000..bb1102e74
--- /dev/null
+++ b/Completion/Builtins/_zle
@@ -0,0 +1,7 @@
+#defcomp zle
+
+if [[ -word 1 -N && -position 3 ]]; then
+ complist -F
+else
+ complist -b
+fi
diff --git a/Completion/Builtins/_zmodload b/Completion/Builtins/_zmodload
new file mode 100644
index 000000000..112acb57c
--- /dev/null
+++ b/Completion/Builtins/_zmodload
@@ -0,0 +1,9 @@
+#defcomp zmodload
+
+if [[ -mword 1 -*(a*u|u*a)* || -mword 1 -*a* && -position 3 -1 ]]; then
+ complist -B
+elif [[ -mword 1 -*u* ]]; then
+ complist -s '$(zmodload)'
+else
+ complist -s '${^module_path}/*(N:t:r)'
+fi
diff --git a/Completion/Commands/.distfiles b/Completion/Commands/.distfiles
new file mode 100644
index 000000000..f79d69704
--- /dev/null
+++ b/Completion/Commands/.distfiles
@@ -0,0 +1,3 @@
+DISTFILES_SRC='
+ .distfiles _correct_filename _most_recent_file
+'
diff --git a/Completion/Commands/_correct_filename b/Completion/Commands/_correct_filename
new file mode 100644
index 000000000..edf1c65c2
--- /dev/null
+++ b/Completion/Commands/_correct_filename
@@ -0,0 +1,37 @@
+#defkeycomp complete-word \C-xc
+
+# Function to correct a filename. Can be used as a completion widget,
+# or as a function in its own right, in which case it will print the
+# corrected filename to standard output.
+#
+# You can adapt max_approx to the maximum number of mistakes
+# which are allowed in total.
+
+emulate -LR zsh
+setopt extendedglob
+
+local file="$PREFIX$SUFFIX" trylist
+integer approx max_approx=6
+
+[[ -z $WIDGET ]] && file=$1
+
+if [[ -e "$file" ]]; then
+ if [[ -n $WIDGET ]]; then
+ compadd "$file"
+ else
+ print "$file"
+ fi
+ return
+fi
+
+for (( approx = 1; approx <= max_approx; approx++ )); do
+ trylist=( (#a$approx)"$file"(N) )
+ (( $#trylist )) && break
+done
+(( $#trylist )) || return 1
+
+if [[ -n $WIDGET ]]; then
+ compadd -U "${trylist[@]}"
+else
+ print "${trylist[@]}"
+fi
diff --git a/Completion/Commands/_correct_word b/Completion/Commands/_correct_word
new file mode 100644
index 000000000..db3023860
--- /dev/null
+++ b/Completion/Commands/_correct_word
@@ -0,0 +1,12 @@
+#compdef -k complete-word \C-xc
+
+# Simple completion front-end implementing spelling correction.
+# The maximum number of errors is set quite high, and
+# the numeric prefix can be used to specify a different value.
+
+local oca="$compconfig[correct_accept]"
+compconfig[correct_accept]=6n
+
+_main_complete _correct
+
+compconfig[correct_accept]=$oca
diff --git a/Completion/Commands/_most_recent_file b/Completion/Commands/_most_recent_file
new file mode 100644
index 000000000..ff5645de5
--- /dev/null
+++ b/Completion/Commands/_most_recent_file
@@ -0,0 +1,4 @@
+#defkeycomp complete-word \C-xm
+local file
+file=($~PREFIX*$~SUFFIX(om[1]N))
+(( $#file )) && compadd -f $file
diff --git a/Completion/Core/.distfiles b/Completion/Core/.distfiles
new file mode 100644
index 000000000..ddf2a707e
--- /dev/null
+++ b/Completion/Core/.distfiles
@@ -0,0 +1,5 @@
+DISTFILES_SRC='
+ .distfiles
+ _compalso _files _main_complete _multi_parts _normal _path_files
+ _sep_parts compdump compinit
+'
diff --git a/Completion/Core/_approximate b/Completion/Core/_approximate
new file mode 100644
index 000000000..1b40f7cbf
--- /dev/null
+++ b/Completion/Core/_approximate
@@ -0,0 +1,197 @@
+#autoload
+
+# This code will try to correct the string on the line based on the
+# strings generated for the context if `compconfig[correct]' is set.
+# These corrected strings will be shown in a list and one can
+# cycle through them as in a menucompletion or get the corrected prefix.
+#
+# Supported configuration keys:
+#
+# approximate_accept
+# This should be set to a number, specifying the maximum number
+# of errors that should be accepted. If the string also contains
+# a `n' or `N', the code will use the numeric argument as the
+# maximum number of errors if a numeric argument was given. If no
+# numeric argument was given, the number from the value of this
+# key will be used. E.g. with `compconf approximate_accept=2n' two
+# errors will be accepted, but if the user gives another number
+# with the numeric argument, this will be prefered. Also, with
+# `compconf approximate_accept=0n', normally no correction will be
+# tried, but if a numeric argument is given, automatic correction
+# will be used. On the other hand, if the string contains an `!'
+# and a `n' or `N', correction is not attempted if a numeric
+# argument is given. Once the number of errors to accept is
+# determined, the code will repeatedly try to generate matches by
+# allowing one error, two errors, and so on. Independent of the
+# number of errors the user wants to accept, the code will allow
+# only fewer errors than there are characters in the string from
+# the line.
+#
+# approximate_original
+# This value is used to determine if the original string should
+# be included in the list (and thus be presented to the user when
+# cycling through the corrections). If it is set to any non-empty
+# value, the original string will be offered. If it contains the
+# sub-string `last', the original string will appear as the last
+# string when cycling through the corrections, otherwise it will
+# appear as the first one (so that the command line does not
+# change immediately). Also, if the value contains the sub-string
+# `always', the original string will always be included, whereas
+# normally it is included only if more than one possible
+# correction was generated.
+#
+# approximate_prompt
+# This can be set to a string that should be printed before the
+# list of corrected strings when cycling through them. This string
+# may contain the control sequences `%n', `%B', etc. known from
+# the `-X' option of `compctl'. Also, the sequence `%e' will be
+# replaced by the number of errors accepted to generate the
+# corrected strings.
+#
+# approximate_insert
+# If this is set to a string starting with `unambig', the code
+# will try to insert a usable unambiguous string in the command
+# line instead of always cycling through the corrected strings.
+# If such a unambiguous string could be found, the original
+# string is not used, independent of the setting of
+# `approximate_original'. If no sensible string could be found,
+# one can cycle through the corrected strings as usual.
+#
+# If any of these keys is not set, but the the same key with the
+# prefix `correct' instead of `approximate' is set, that value will
+# be used.
+
+local _comp_correct _correct_prompt comax
+local cfgacc cfgorig cfgps cfgins
+
+# Only if all global matchers hav been tried.
+
+[[ compstate[matcher] -ne compstate[total_matchers] ]] && return 1
+
+# We don't try correction if the string is too short.
+
+[[ "${#:-$PREFIX$SUFFIX}" -le 1 ]] && return 1
+
+# Get the configuration values, using either the prefix `correct' or
+# `approximate'.
+
+if [[ "$compstate[pattern_match]" = (|\**) ]]; then
+ cfgacc="${compconfig[approximate_accept]:-$compconfig[correct_accept]}"
+ cfgorig="${compconfig[approximate_original]:-$compconfig[correct_original]}"
+ cfgps="${compconfig[approximate_prompt]:-$compconfig[correct_prompt]}"
+ cfgins="${compconfig[approximate_insert]:-$compconfig[correct_insert]}"
+else
+ cfgacc="$compconfig[correct_accept]"
+ cfgorig="$compconfig[correct_original]"
+ cfgps="$compconfig[correct_prompt]"
+ cfgins="$compconfig[correct_insert]"
+fi
+
+# Get the number of errors to accept.
+
+if [[ "$cfgacc" = *[nN]* && NUMERIC -ne 1 ]]; then
+ # Stop if we also have a `!'.
+
+ [[ "$cfgacc" = *\!* ]] && return 1
+
+ # Prefer the numeric argument if that has a sensible value.
+
+ comax="$NUMERIC"
+else
+ comax="${cfgacc//[^0-9]}"
+fi
+
+# If the number of errors to accept is too small, give up.
+
+[[ "$comax" -lt 1 ]] && return 1
+
+# Otherwise temporarily define functions to use instead of
+# the builtins that add matches. This is used to be able
+# to stick the `(#a...)' into the right place (after an
+# ignored prefix).
+
+compadd() {
+ [[ "$*" != *-([a-zA-Z/]#|)U* &&
+ "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return
+
+ if [[ "$PREFIX" = \~*/* ]]; then
+ PREFIX="${PREFIX%%/*}/(#a${_comp_correct})${PREFIX#*/}"
+ else
+ PREFIX="(#a${_comp_correct})$PREFIX"
+ fi
+ if [[ -n "$_correct_prompt" ]]; then
+ builtin compadd -X "$_correct_prompt" -J _correct "$@"
+ else
+ builtin compadd -J _correct "$@"
+ fi
+}
+
+compgen() {
+ [[ "$*" != *-([a-zA-Z/]#|)U* &&
+ "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return
+
+ if [[ "$PREFIX" = \~*/* ]]; then
+ PREFIX="${PREFIX%%/*}/(#a${_comp_correct})${PREFIX#*/}"
+ else
+ PREFIX="(#a${_comp_correct})$PREFIX"
+ fi
+ if [[ -n "$_correct_prompt" ]]; then
+ builtin compgen "$@" -X "$_correct_prompt" -J _correct
+ else
+ builtin compgen "$@" -J _correct
+ fi
+}
+
+# Now initialise our counter. We also set `compstate[matcher]'
+# to `-1'. This allows completion functions to use the simple
+# `[[ compstate[matcher] -gt 1 ]] && return' to avoid being
+# called for multiple global match specs and still be called
+# again when correction is done. Also, this makes it easy to
+# test if correction is attempted since `compstate[matcher]'
+# will never be set to a negative value by the completion code.
+
+_comp_correct=1
+compstate[matcher]=-1
+
+_correct_prompt="${cfgps//\%e/1}"
+
+# We also need to set `extendedglob' and make the completion
+# code behave as if globcomplete were set.
+
+setopt extendedglob
+
+[[ -z "$compstate[pattern_match]" ]] && compstate[pattern_match]='*'
+
+while [[ _comp_correct -le comax ]]; do
+ if _complete; then
+ if [[ "$cfgins" = unambig* &&
+ "${#compstate[unambiguous]}" -ge "${#:-$PREFIX$SUFFIX}" ]]; then
+ compstate[pattern_insert]=unambiguous
+ elif [[ compstate[nmatches] -gt 1 || "$cfgorig" = *always* ]]; then
+ if [[ "$cfgorig" = *last* ]]; then
+ builtin compadd -U -V _correct_original -nQ - "$PREFIX$SUFFIX"
+ elif [[ -n "$cfgorig" ]]; then
+ builtin compadd -U -nQ - "$PREFIX$SUFFIX"
+ fi
+
+ # If you always want to see the list of possible corrections,
+ # set `compstate[list]=list' here.
+
+ compstate[force_list]=list
+ fi
+ compstate[matcher]="$compstate[total_matchers]"
+ unfunction compadd compgen
+
+ return 0
+ fi
+
+ [[ "${#:-$PREFIX$SUFFIX}" -le _comp_correct+1 ]] && break
+ (( _comp_correct++ ))
+
+ _correct_prompt="${cfgps//\%e/$_comp_correct}"
+done
+
+compstate[matcher]="$compstate[total_matchers]"
+unfunction compadd compgen
+
+return 1
diff --git a/Completion/Core/_comp_parts b/Completion/Core/_comp_parts
new file mode 100644
index 000000000..7c24fd19d
--- /dev/null
+++ b/Completion/Core/_comp_parts
@@ -0,0 +1,147 @@
+#autoload
+
+# This function can be used to separately complete parts of strings
+# where each part may be one of a set of matches and different parts
+# have different sets.
+# Arguments are alternatingly arrays and separator strings. Arrays may
+# be given by name or literally as words separated by white space in
+# parentheses, e.g.:
+#
+# _comp_parts '(foo bar)' @ hosts
+#
+# This will make this function complete the strings in the array
+# `friends'. If the string on the line contains a `@', the substring
+# after it will be completed from the array `hosts'. Of course more
+# arrays may be given, each preceded by another separator string.
+#
+# This function understands the `-J group', `-V group', and
+# `-X explanation' options.
+#
+# This function does part of the matching itself and calls the functions
+# `_match_test' and `_match_pattern' for this.
+
+local str arr sep test testarr tmparr prefix suffixes matchers autosuffix
+local matchflags opt group expl
+
+# Test if we should use this function for the global matcher in use.
+
+_match_test _comp_parts || return
+
+# Get the options.
+
+group=()
+expl=()
+while getopts "J:V:X:" opt; do
+ case "$opt" in
+ [JV]) group=("-$opt" "$OPTARG");;
+ X) expl=(-X "$OPTARG");;
+ esac
+done
+shift OPTIND-1
+
+# Get the string from the line.
+
+str="$PREFIX$SUFFIX"
+prefix=""
+
+# Walk through the arguments to find the longest unambiguous prefix.
+
+while [[ $# -gt 1 ]]; do
+ # Get the next array and separator.
+ arr="$1"
+ sep="$2"
+
+ if [[ "$arr[1]" == '(' ]]; then
+ tmparr=( ${=arr[2,-2]} )
+ arr=tmparr
+ fi
+ # Is the separator on the line?
+ [[ "$str" != *${sep}* ]] && break
+
+ # Build a pattern matching the possible matches and get all these
+ # matches in an array.
+ test="${str%%${sep}*}"
+ matchflags=""
+ _match_pattern _comp_parts test matchflags
+ test="${matchflags}${test}"
+ testarr=( "${(@M)${(@P)arr}:#${~test}*}" )
+
+ # If there are no matches we give up. If there is more than one
+ # match, this is the part we will complete.
+ (( $#testarr )) || return
+ [[ $#testarr -gt 1 ]] && break
+
+ # Only one match, add it to the prefix and skip over it in `str',
+ # continuing with the next array and separator.
+ prefix="${prefix}${testarr[1]}${sep}"
+ str="${str#*${sep}}"
+ shift 2
+done
+
+# Get the array to work upon.
+arr="$1"
+if [[ "$arr[1]" == '(' ]]; then
+ tmparr=( ${=arr[2,-2]} )
+ arr=tmparr
+fi
+if [[ $# -le 1 || "$str" != *${2}* ]]; then
+ # No more separators, build the matches.
+ matchflags=""
+ test="$str"
+ _match_pattern _comp_parts test matchflags
+ test="${matchflags}${test}"
+ testarr=( "${(@M)${(@P)arr}:#${~test}*}" )
+fi
+
+[[ $#testarr -eq 0 || ${#testarr[1]} -eq 0 ]] && return
+
+# Now we build the suffixes to give to the completion code.
+shift
+matchers=()
+suffixes=("")
+autosuffix=()
+
+while [[ $# -gt 0 && "$str" == *${1}* ]]; do
+ # Remove anything up to the the suffix.
+ str="${str#*${1}}"
+
+ # Again, we get the string from the line up to the next separator
+ # and build a pattern from it.
+ if [[ $# -gt 2 ]]; then
+ test="${str%%${3}*}"
+ else
+ test="$str"
+ fi
+ matchflags=""
+ _match_pattern _comp_parts test matchflags
+ test="${matchflags}${test}"
+
+ # We incrementally add suffixes by appending to them the seperators
+ # and the strings from the next array that match the pattern we built.
+
+ arr="$2"
+ if [[ "$arr[1]" == '(' ]]; then
+ tmparr=( ${=arr[2,-2]} )
+ arr=tmparr
+ fi
+ suffixes=("${^suffixes[@]}${1}${(@M)^${(@P)arr}:#${~test}*}")
+
+ # We want the completion code to generate the most specific suffix
+ # for us, so we collect matching specifications that allow partial
+ # word matching before the separators on the fly.
+ matchers=("$matchers[@]" "r:|${1}=*")
+ shift 2
+done
+
+# If we were given at least one more separator we make the completion
+# code offer it by appending it as a autoremovable suffix.
+(( $# )) && autosuffix=(-qS "$1")
+
+# If we have collected matching specifications, we build an array
+# from it that can be used as arguments to `compadd'.
+[[ $#matchers -gt 0 ]] && matchers=(-M "$matchers")
+
+# Add the matches for each of the suffixes.
+for i in "$suffixes[@]"; do
+ compadd "$group[@]" "$expl[@]" "$matchers[@]" "$autosuffix[@]" -p "$prefix" -s "$i" - "$testarr[@]"
+done
diff --git a/Completion/Core/_compalso b/Completion/Core/_compalso
new file mode 100644
index 000000000..23a40e2d0
--- /dev/null
+++ b/Completion/Core/_compalso
@@ -0,0 +1,13 @@
+#autoload
+
+# This searches $1 in the array for normal completions and calls the result.
+# It is used to include completions for another command or special context
+# into the list generated by the calling function.
+# For example the function for `-subscript-' could call this as in
+# `_compalso -math- "$@"' to get the completions that would be generated
+# for a mathematical context.
+
+local tmp
+
+tmp="$_comps[$1]"
+[[ -z "$tmp" ]] || "$tmp" "$@"
diff --git a/Completion/Core/_complete b/Completion/Core/_complete
new file mode 100644
index 000000000..0f4d5ff4b
--- /dev/null
+++ b/Completion/Core/_complete
@@ -0,0 +1,52 @@
+#autoload
+
+# Generate all possible completions. Note that this is not intended as
+# a normal completion function, but as one possible value for the
+# compconfig[completer] parameter.
+
+local comp name
+
+# An entry for `-first-' is the replacement for `compctl -T'
+# Completion functions may set `_compskip' to any value to make the
+# main loops stop calling other completion functions.
+
+comp="$_comps[-first-]"
+if [[ ! -z "$comp" ]]; then
+ "$comp"
+ if (( $+_compskip )); then
+ unset _compskip
+ (( compstate[nmatches] ))
+ return
+ fi
+fi
+
+# For arguments and command names we use the `_normal' function.
+
+if [[ "$compstate[context]" = command ]]; then
+ _normal
+else
+ # Let's see if we have a special completion definition for the other
+ # possible contexts.
+
+ comp=''
+
+ case $compstate[context] in
+ equal) comp="$_comps[-equal-]";;
+ tilde) comp="$_comps[-tilde-]";;
+ redirect) comp="$_comps[-redirect-]";;
+ math) comp="$_comps[-math-]";;
+ subscript) comp="$_comps[-subscript-]";;
+ value) comp="$_comps[-value-]";;
+ array_value) comp="$_comps[-array-value-]";;
+ condition) comp="$_comps[-condition-]";;
+ parameter) comp="$_comps[-parameter-]";;
+ brace_parameter) comp="$_comps[-brace-parameter-]";;
+ esac
+
+ # If not, we use default completion, if any.
+
+ [[ -z "$comp" ]] && comp="$_comps[-default-]"
+ [[ -z "$comp" ]] || "$comp"
+fi
+
+(( compstate[nmatches] ))
diff --git a/Completion/Core/_correct b/Completion/Core/_correct
new file mode 100644
index 000000000..35ab01cf1
--- /dev/null
+++ b/Completion/Core/_correct
@@ -0,0 +1,19 @@
+#autoload
+
+# This is mainly a wrapper around the more general `_approximate.
+# By setting `compstate[pattern_match]' to something unequal to `*' and
+# then calling `_approximate, we get only corrections, not all strings
+# with the corrected prefix and something after it.
+#
+# Supported configuration keys are the same as for `_approximate', only
+# starting with `correct'.
+
+local ret=1 opm="$compstate[pattern_match]"
+
+compstate[pattern_match]='-'
+
+_approximate && ret=0
+
+compstate[pattern_match]="$opm"
+
+return ret
diff --git a/Completion/Core/_expand b/Completion/Core/_expand
new file mode 100644
index 000000000..9172b6cbf
--- /dev/null
+++ b/Completion/Core/_expand
@@ -0,0 +1,149 @@
+#autoload
+
+# This completer function is intended to be used as the first completer
+# function and allows one to say more explicitly when and how the word
+# from the line should be expanded than expand-or-complete.
+# This function will allow other completer functions to be called if
+# the expansions done produce no result or do not change the original
+# word from the line.
+#
+# Configuration keys:
+#
+# expand_substitute
+# If this is unset or set to the empty string, the code will first
+# try to expand all substitutions in the string (such as $(...) and
+# ${...}). If this is set to an non-empty string it should be
+# an expression usable inside a $[...] arithmetical expression.
+# In this case, expansion of substitutions will be done if the
+# expression evaluates to `1'. For example, with
+#
+# compconf expand_substitute='NUMERIC != 1'
+#
+# substitution will be performed only if given an explicit numeric
+# argument other than `1', as by typing ESC 2 TAB.
+#
+# expand_glob
+# If this is unset or set to an empty string, globbing will be
+# attempted on the word resulting from substitution or the
+# original string. The values accepted for this key are the same
+# as for expand_substitute.
+#
+# expand_menu
+# If this is unset or set to the empty string, the words resulting
+# from expansion (if any) will simply be inserted in the ommand line,
+# replacing the original string. However, if this key is set to an
+# non-empty string, the user can cycle through the expansion as in
+# a menucompletion. Unless the value contains the sub-string `only',
+# the user will still be offered all expansions at once as one of
+# the strings to insert in the command line. Also, if the value
+# contains the sub-string `last', the string with all expansion will
+# be offered first, whereas normally it is offered as the last string
+# to insert. Finally, if the value contains the sub-string `sort',
+# the expansions will be sorted alphabetically, normally they are
+# kept in the order the expansion produced them in.
+#
+# expand_original
+# If this is set to an non-empty string, the original string from the
+# line will be included in the list of strings the user can cycle
+# through as in a menucompletion. If the value contains the sub-string
+# `last', the original string will appear as the last string, with
+# other values it is inserted as the first one (so that the command
+# line does not change immediatly).
+#
+# expand_prompt
+# This may be set to a string that should be displayed before the
+# possible expansions. This is given to the -X option and thus may
+# contain the control sequences `%n', `%B', etc. Also, the sequence
+# `%o' in this string will be replaced by the original string.
+
+local exp word="$PREFIX$SUFFIX" group=-V
+
+# Do this only for the first global matcher.
+
+[[ "$compstate[matcher]" -le 1 ]] || return 1
+
+# In exp we will collect the expansion.
+
+exp=("$word")
+
+# First try substitution. That weird thing spanning multiple lines
+# changes quoted spaces, tabs, and newlines into spaces.
+
+[[ -z "$compconfig[expand_substitute]" ||
+ "${(e):-\$[$compconfig[expand_substitute]]}" -eq 1 ]] &&
+ exp=( "${(e)exp//\\[
+]/ }" )
+
+# If the array is empty, store the original string again.
+
+[[ -z "$exp" ]] && exp=("$word")
+
+# Now try globbing.
+
+[[ -z "$compconfig[expand_glob]" ||
+ "${(e):-\$[$compconfig[expand_glob]]}" -eq 1 ]] &&
+ exp=( ${~exp}(N) )
+
+# If we don't have any expansions or only one and that is the same
+# as the original string, we let other completers run.
+
+[[ $#exp -eq 0 ||
+ ( $#exp -eq 1 && "$exp[1]" = "$word" ) ]] && return 1
+
+# We have expansions, should we menucomplete them?
+
+if [[ -z "$compconfig[expand_menu]" ]]; then
+
+ # No, so if the user only wants a list, we add the strings
+ # separately. Otherwise we add the whole array as one string,
+ # probably also adding the original string.
+
+ if [[ -z "$compstate[insert]" ]]; then
+ compadd -U -V _expand -Q - "$exp[@]"
+ else
+ [[ -n "$compconfig[expand_original]" &&
+ "$compconfig[expand_original]" != *last* ]] &&
+ compadd -UnQ -V _expand_original - "$word"
+
+ compadd -UQ -V _expand - "$exp"
+
+ [[ -n "$compconfig[expand_original]" &&
+ "$compconfig[expand_original]" = *last* ]] &&
+ compadd -UnQ -V _expand_original - "$word"
+
+ compstate[insert]=menu
+ fi
+else
+ # Sorting? We just use a different group type then.
+
+ [[ "$compconfig[expand_menu]" = *sort* ]] && group=-J
+
+ # Now add the expansion string, probably also adding the original
+ # and/or the string containing all expanded string.
+
+ [[ -n "$compconfig[expand_original]" &&
+ "$compconfig[expand_original]" != *last* ]] &&
+ compadd -UnQ -V _expand_original - "$word"
+
+ [[ "$compconfig[expand_menu]" = *last* &&
+ "$compconfig[expand_menu]" != *only* ]] &&
+ compadd -UnQ -V _expand_all - "$exp"
+
+ if [[ -z "$compconfig[expand_prompt]" ]]; then
+ compadd -UQ $group _expand - "$exp[@]"
+ else
+ compadd -UQ -X "${compconfig[expand_prompt]//\%o/$word}" \
+ $group _expand - "$exp[@]"
+ fi
+ [[ "$compconfig[expand_menu]" != *last* &&
+ "$compconfig[expand_menu]" != *only* ]] &&
+ compadd -UnQ -V _expand_all - "$exp"
+
+ [[ -n "$compconfig[expand_original]" &&
+ "$compconfig[expand_original]" = *last* ]] &&
+ compadd -UnQ -V _expand_original - "$word"
+
+ compstate[insert]=menu
+fi
+
+return 0
diff --git a/Completion/Core/_files b/Completion/Core/_files
new file mode 100644
index 000000000..d2cce35e7
--- /dev/null
+++ b/Completion/Core/_files
@@ -0,0 +1,26 @@
+#autoload
+
+# Utility function for completing files of a given type or any file.
+# In many cases you will want to call this one instead of _path_files().
+
+local nm=$NMATCHES
+
+_path_files "$@"
+
+if [[ $# -ne 0 && -nmatches nm ]]; then
+ local opt opts
+
+ # We didn't get any matches for those types of files described by
+ # the `-g' or `-/' option. Now we try it again accepting all files.
+ # First we get those options that we have to use even if then. If
+ # we find out that the `-f' option was given, we already accepted
+ # all files and give up immediatly.
+
+ opts=()
+ while getopts "P:S:W:F:J:V:X:f/g:" opt; do
+ [[ "$opt" = f ]] && return
+ [[ "$opt" = [PSWFJVX] ]] && opts=("$opts[@]" "-$opt" "$OPTARG")
+ done
+
+ _path_files "$opts[@]"
+fi
diff --git a/Completion/Core/_list b/Completion/Core/_list
new file mode 100644
index 000000000..099c6bc7b
--- /dev/null
+++ b/Completion/Core/_list
@@ -0,0 +1,61 @@
+#autoload
+
+# This completer function makes the other completer functions used
+# insert possible completions only after once the list has been
+# shown.
+#
+# Configuration keys:
+#
+# list_condition
+# If this key is unset or set to the empty string, this completer
+# will delay the insertion of matches unconditionally. However,
+# if this value is set, it should be set to an expression usable
+# inside a $[...] arithmetical expression. In this case, delaying
+# will be done if the expression evaluates to `1'.
+# For example, with
+#
+# compconf list_condition='NUMERIC != 1'
+#
+# delaying will be done only if given an explicit numeric argument
+# other than `1'.
+#
+# list_word
+# To find out if only listing should be done, the code normally
+# compares the contents of the line with the contents the line
+# had at the time of the last invocation. If this key is set to
+# an non-empty string comparison is done using only the current
+# word. So if it is set, attempting completion on a word equal
+# to the one completion was called on the last time will not
+# delay the generation of matches.
+
+local pre suf
+
+# Get the strings to compare.
+
+if [[ -z "$compconfig[list_word]" ]]; then
+ pre="$HISTNO$LBUFFER"
+ suf="$RBUFFER"
+else
+ pre="$PREFIX"
+ suf="$SUFFIX"
+fi
+
+# Should we only show a list now?
+
+if [[ ( -z "$compconfig[list_condition]" ||
+ "${(e):-\$[$compconfig[expand_glob]]}" -eq 1 ) &&
+ ( "$pre" != "$_list_prefix" || "$suf" != "$_list_suffix" ) ]]; then
+
+ # Yes. Tell the completion code about it and save the new values
+ # to compare the next time.
+
+ compstate[insert]=''
+ compstate[list]=list
+ compstate[force_list]=yes
+ _list_prefix="$pre"
+ _list_suffix="$suf"
+fi
+
+# We always return one, because we don't really do any work here.
+
+return 1
diff --git a/Completion/Core/_main_complete b/Completion/Core/_main_complete
new file mode 100644
index 000000000..c7f5a5a96
--- /dev/null
+++ b/Completion/Core/_main_complete
@@ -0,0 +1,48 @@
+#autoload
+
+# The main loop of the completion code. This is what is called when
+# completion is attempted from the command line.
+# The completion code gives us the special variables and the arguments
+# from the command line are given as positional parameters.
+
+local comp name
+
+setopt localoptions nullglob rcexpandparam globdots
+unsetopt markdirs globsubst shwordsplit nounset
+
+# An entry for `-first-' is the replacement for `compctl -T'
+# Completion functions may set `_compskip' to any value to make the
+# main loops stop calling other completion functions.
+
+comp="$_comps[-first-]"
+if [[ ! -z "$comp" ]]; then
+ "$comp" "$@"
+ if (( $+_compskip )); then
+ unset _compskip
+ return
+ fi
+fi
+
+# For arguments we use the `_normal function.
+
+if [[ $CONTEXT == argument || $CONTEXT == command ]]; then
+ _normal "$@"
+else
+ # Let's see if we have a special completion definition for the other
+ # possible contexts.
+
+ comp=''
+
+ case $CONTEXT in
+ redirect) comp="$_comps[-redirect-]";;
+ math) comp="$_comps[-math-]";;
+ subscript) comp="$_comps[-subscript-]";;
+ value) comp="$_comps[-value-]";;
+ condition) comp="$_comps[-condition-]";;
+ esac
+
+ # If not, we use default completion, if any.
+
+ [[ -z "$comp" ]] && comp="$_comps[-default-]"
+ [[ -z "$comp" ]] || "$comp" "$@"
+fi
diff --git a/Completion/Core/_match b/Completion/Core/_match
new file mode 100644
index 000000000..3c639935c
--- /dev/null
+++ b/Completion/Core/_match
@@ -0,0 +1,53 @@
+#autoload
+
+# This is intended to be used as a completer function after the normal
+# completer as in: `compconf completer=_complete:_match'.
+# It temporarily switches on pattern matching, allowing you to try
+# completion on patterns without having to setopt glob_complete.
+#
+# Note, however, that this is only really useful if you don't use the
+# expand-or-complete function because otherwise the pattern will
+# be expanded using globbing.
+#
+# Configuration key used:
+#
+# match_original
+# If this is set to a `only', pattern matching will only be tried
+# with the string from the line. If it is set to any other non-empty
+# string, the original pattern will be tried first and if that yields
+# no completions, matching will be tried again with a `*' inserted
+# at the cursor position. If this key is not set or set to an empty
+# string, matching will only be attempted with the `*' inserted.
+
+local tmp opm="$compstate[pattern_match]" ret=0
+
+# Do nothing if we don't have a pattern or there are still global
+# match specifications to try.
+
+tmp="${${:-$PREFIX$SUFFIX}#[~=]}"
+[[ "$tmp:q" = "$tmp" ||
+ compstate[matcher] -ne compstate[total_matchers] ]] && return 1
+
+# Try completion without inserting a `*'?
+
+if [[ -n "$compconfig[match_original]" ]]; then
+ compstate[matcher]=-1
+ compstate[pattern_match]='-'
+ _complete && ret=1
+ compstate[pattern_match]="$opm"
+ compstate[matcher]="$compstate[total_matchers]"
+
+ (( ret )) && return 0
+fi
+
+# No completion with inserting `*'?
+
+[[ "$compconfig[match_original]" = only ]] && return 1
+
+compstate[matcher]=-1
+compstate[pattern_match]='*'
+_complete && ret=1
+compstate[pattern_match]="$opm"
+compstate[matcher]="$compstate[total_matchers]"
+
+return 1-ret
diff --git a/Completion/Core/_multi_parts b/Completion/Core/_multi_parts
new file mode 100644
index 000000000..1f51d2f6d
--- /dev/null
+++ b/Completion/Core/_multi_parts
@@ -0,0 +1,201 @@
+#autoload
+
+# This gets two arguments, a separator (which should be only one
+# character) and an array. As usual, the array may be given by it's
+# name or literal as in `(foo bar baz)' (words separated by spaces in
+# parentheses).
+# The parts of words from the array that are separated by the
+# separator character are then completed independently.
+
+local sep matches patstr orig matchflags pref i tmp1 tmp2 nm
+local group expl
+
+_match_test _multi_parts || return 1
+
+# Save the current number of matches to be able to return if we added
+# matches or not.
+
+nm=$compstate[nmatches]
+
+# Get the options.
+
+group=()
+expl=()
+while getopts "J:V:X:" opt; do
+ case "$opt" in
+ [JV]) group=("-$opt" "$OPTARG");;
+ X) expl=(-X "$OPTARG");;
+ esac
+done
+shift OPTIND-1
+
+# Get the arguments, first the separator, then the array. The array is
+# stored in `matches'. Further on this array will always contain those
+# words from the original array that still match everything we have
+# tried to match while we walk through the string from the line.
+
+sep="$1"
+if [[ "${2[1]}" = '(' ]]; then
+ matches=( ${2[2,-2]} )
+else
+ matches=( "${(@P)2}" )
+fi
+
+# Now build the pattern from what we have on the line. We also save
+# the original string in `orig'. The `eval' is used to replace our
+# separator character by `*<sep>'.
+
+if [[ -o globcomplete ]]; then
+ patstr="${PREFIX}*${SUFFIX}*"
+else
+ patstr="${PREFIX:q}*${SUFFIX:q}*"
+fi
+orig="${PREFIX}${SUFFIX}"
+
+matchflags=""
+_match_pattern _path_files patstr matchflags
+[[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)"
+
+patstr="${${patstr//$sep/*$sep}//\*##/*}"
+#eval patstr\="\$patstr:gs-${sep}-\*${sep}-:gs/\*\*/\*/"
+
+# First we will skip over those parts of the matches for which we have
+# exact substrings on the line. In `pref' we will build the
+# unambiguous prefix string.
+
+pref=''
+while [[ "$orig" = *${sep}* ]] do
+
+ # First build the pattern to use, then collect all strings from
+ # `matches' that match the prefix we have and the exact substring in
+ # the array `tmp1'.
+
+ pat="${${${patstr#*${sep}}%${sep}*}//\*/[^${sep}]#}${patstr##*${sep}}"
+ tmp1=( "${(@M)matches:#${~matchflags}${orig%%${sep}*}${sep}${~pat}}" )
+
+ # If there are no words matching the exact substring, stop.
+
+ (( $#tmp1 )) || break
+
+ # Otherwise add the part to the prefix, remove it from the matches
+ # (which will also remove all words not matching the string at all),
+ # and set `patstr' and `orig' to the next component.
+
+ pref="$pref${orig%%${sep}*}${sep}"
+ matches=( "${(@)${(@)matches#${orig%%${sep}*}${sep}}:#}" )
+ orig="${orig#*${sep}}"
+ patstr="${patstr#*${sep}}"
+done
+
+# Now we get all the words that still match in `tmp1'.
+
+if [[ "$patstr" = *${sep}* ]]; then
+ tmp1="${patstr%${sep}*}${sep}"
+ pat="${tmp1//\*/[^${sep}]#}${patstr##*${sep}}"
+else
+ pat="$patstr"
+fi
+tmp1=( "${(@M)matches:#${~matchflags}${~pat}}" )
+
+if (( $#tmp1 )); then
+
+ # There are words that are matched, put them int `matches' and then
+ # move all unambiguous components from the beginning into `pref'.
+
+ matches=( "$tmp1[@]" )
+ while [[ "$matches[1]" = *${sep}* ]]; do
+
+ # We just take the first component of the first match and see if
+ # there are other matches with a different prefix (these are
+ # collected in `tmp2'). If there are any, we give up.
+
+ tmp1="${matches[1]%%${sep}*}${sep}"
+ tmp2=( "${(@)matches:#${tmp1}*}" )
+ (( $#tmp2 )) && break
+
+ # All matches have the same prefix, but it into `pref' and remove
+ # it from the matches.
+
+ pref="$pref$tmp1"
+ matches=( "${(@)${(@)matches#$tmp1}:#}" )
+
+ if [[ "$orig" = *${sep}* ]]; then
+ orig="${orig#*${sep}}"
+ else
+ orig=''
+ fi
+ done
+
+ # Now we can tell the completion code about the things we
+ # found. Strings that have a separator will be added with a suffix.
+
+ if [[ -z "$orig" && "$PREFIX$SUFFIX" != "$pref$orig" ]]; then
+ compadd -QU "$group[@]" "$expl[@]" -i "$IPREFIX" -S '' - "${pref}${orig}"
+ elif [[ $compstate[insert] = *menu ]]; then
+ for i in "$matches[@]" ; do
+ if [[ "$i" = *${sep}* ]]; then
+ compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" \
+ -p "$pref" -qS "$sep" - "${i%%${sep}*}"
+ else
+ compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" \
+ -p "$pref" - "${i%%${sep}*}"
+ fi
+ done
+ else
+ for i in "$matches[@]" ; do
+ if [[ "$i" = *${sep}* ]]; then
+ compadd -U -i "$IPREFIX" -p "$pref" -s "${sep}${i#*${sep}}" \
+ "$group[@]" "$expl[@]" -M "r:|${sep}=*" - "${i%%${sep}*}"
+ else
+ compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -p "$pref" - "$i"
+ fi
+ done
+ fi
+elif [[ "$patstr" = *${sep}* ]]; then
+
+ # We had no words matching the string from the line. But we want to
+ # be friendly and at least expand the prefix as far as we can. So we
+ # will loop through the rest of the string from the line and test
+ # the components one by one.
+
+ while [[ "$patstr" = *${sep}* ]]; do
+
+ # First we get all words matching at least this component in
+ # `tmp1'. If there are none, we give up.
+
+ tmp1=( "${(@M)matches:#${~matchflags}${~patstr%%${sep}*}${sep}*}" )
+ (( $#tmp1 )) || break
+
+ # Then we check if there are words that have a different prefix.
+
+ tmp2=( "${(@)tmp1:#${tmp1[1]%%${sep}*}${sep}*}" )
+ if (( $#tmp2 )); then
+
+ # There are words with another prefix, so we have found an
+ # ambiguous component. So we just give all possible prefixes to
+ # the completion code together with our prefix and the rest of
+ # the string from the line as the suffix.
+
+ compadd -U "$group[@]" "$expl[@]" -S '' -i "$IPREFIX" -p "$pref" \
+ -s "${sep}${orig#*${sep}}" - "${(@)matches%%${sep}*}"
+ return 0
+ fi
+
+ # All words have the same prefix, so add it to `pref' again and
+ # try the next component.
+
+ pref="$pref${tmp1[1]%%${sep}*}${sep}"
+ matches=( "${(@)matches#${tmp1[1]%%${sep}*}${sep}}" )
+ orig="${orig#*${sep}}"
+ patstr="${patstr#*${sep}}"
+ done
+
+ # Finally, add the unambiguous prefix and the rest of the string
+ # from the line.
+
+ compadd -U "$group[@]" "$expl[@]" -S '' -i "$IPREFIX" -p "$pref" - "$orig"
+fi
+
+# This sets the return value to indicate that we added matches (or not).
+
+[[ nm -ne compstate[nmatches] ]]
diff --git a/Completion/Core/_normal b/Completion/Core/_normal
new file mode 100644
index 000000000..19da6d79b
--- /dev/null
+++ b/Completion/Core/_normal
@@ -0,0 +1,54 @@
+#autoload
+
+local comp cmd1 cmd2 pat val name
+
+# Completing in command position? If not we set up `cmd1' and `cmd2' as
+# two strings we have search in the completion definition arrays (e.g.
+# a path and the last path name component).
+
+if [[ $CONTEXT == command ]]; then
+ comp="$_comps[-command-]"
+ [[ -z "$comp" ]] || "$comp" "$@"
+ return
+elif [[ "$COMMAND[1]" == '=' ]]; then
+ eval cmd1\=$COMMAND
+ cmd2="$COMMAND[2,-1]"
+elif [[ "$COMMAND" == */* ]]; then
+ cmd1="$COMMAND"
+ cmd2="${COMMAND:t}"
+else
+ cmd1="$COMMAND"
+ eval cmd2=$(whence -p $COMMAND)
+fi
+
+# See if there are any matching pattern completions.
+
+for i in "$_patcomps[@]"; do
+ pat="${i% *}"
+ val="${i#* }"
+ if [[ "$cmd1" == $~pat || "$cmd2" == $~pat ]]; then
+ "$val" "$@"
+ if (( $+_compskip )); then
+ unset _compskip
+ return
+ fi
+ fi
+done
+
+# Now look up the two names in the normal completion array.
+
+name="$cmd1"
+comp="$_comps[$cmd1]"
+
+if [[ -z "$comp" ]]; then
+ name="$cmd2"
+ comp="$_comps[$cmd2]"
+fi
+
+# And generate the matches, probably using default completion.
+
+if [[ -z "$comp" ]]; then
+ name=-default-
+ comp="$_comps[-default-]"
+fi
+[[ -z "$comp" ]] || "$comp" "$@"
diff --git a/Completion/Core/_options b/Completion/Core/_options
new file mode 100644
index 000000000..0a852e6ce
--- /dev/null
+++ b/Completion/Core/_options
@@ -0,0 +1,5 @@
+#autoload
+
+# This should be used to complete all option names.
+
+compgen "$@" -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Completion/Core/_parameters b/Completion/Core/_parameters
new file mode 100644
index 000000000..0e8c548f7
--- /dev/null
+++ b/Completion/Core/_parameters
@@ -0,0 +1,8 @@
+#autoload
+
+# This should be used to complete parameter names if you need some of the
+# extra options of compadd. It first tries to complete only non-local
+# parameters. All arguments are given to compadd.
+
+compadd "$@" - "${(@)${(@)${(@)${(@f)$(typeset)}:#*local *\=*}%%\=*}##* }" ||
+ compadd "$@" - "${(@)${(@)${(@f)$(typeset)}%%\=*}##* }"
diff --git a/Completion/Core/_path_files b/Completion/Core/_path_files
new file mode 100644
index 000000000..83b6e8a09
--- /dev/null
+++ b/Completion/Core/_path_files
@@ -0,0 +1,311 @@
+#autoload
+
+# Utility function for in-path completion.
+# Supported arguments are: `-f', `-/', `-g <patterns>', `-J <group>',
+# `-V <group>', `-W paths', `-X explanation', and `-F <ignore>'. All but
+# the last have the same syntax and meaning as for `complist'. The
+# `-F <ignore>' option may be used to give a list of suffixes either by
+# giving the name of an array or literally by giving them in a string
+# surrounded by parentheses. Files with one of the suffixes thus given
+# are treated like files with one of the suffixes in the `fignore' array
+# in normal completion.
+#
+# This function uses the helper functions `_match_test' and `_match_pattern'.
+
+# First see if we should generate matches for the global matcher in use.
+
+_match_test _path_files || return
+
+# Yes, so...
+
+local nm prepaths str linepath realpath donepath patstr prepath testpath rest
+local tmp1 collect tmp2 suffixes i ignore matchflags opt group sopt pats gopt
+local addpfx addsfx expl
+
+setopt localoptions nullglob rcexpandparam globdots extendedglob
+unsetopt markdirs globsubst shwordsplit nounset
+
+prepaths=('')
+ignore=()
+group=()
+sopt='-'
+gopt=''
+pats=()
+addpfx=()
+addsfx=()
+expl=()
+
+# Get the options.
+
+while getopts "P:S:W:F:J:V:X:f/g:" opt; do
+ case "$opt" in
+ P) addpfx=(-P "$OPTARG")
+ ;;
+ S) addsfx=(-S "$OPTARG")
+ ;;
+ W) tmp1="$OPTARG"
+ if [[ "$tmp1[1]" = '(' ]]; then
+ prepaths=( ${^=tmp1[2,-2]}/ )
+ else
+ prepaths=( ${(P)=${tmp1}} )
+ (( ! $#prepaths )) && prepaths=( ${tmp1}/ )
+ fi
+ (( ! $#prepaths )) && prepaths=( '' )
+ ;;
+ F) tmp1="$OPTARG"
+ if [[ "$tmp1[1]" = '(' ]]; then
+ ignore=( ${^=tmp1[2,-2]}/ )
+ else
+ ignore=( ${(P)${tmp1}} )
+ fi
+ (( $#ignore )) && ignore=(-F "( $ignore )")
+ ;;
+ [JV]) group=("-$opt" "$OPTARG")
+ ;;
+ X) expl=(-X "$OPTARG")
+ ;;
+ f) sopt="${sopt}f"
+ pats=("$pats[@]" '*')
+ ;;
+ /) sopt="${sopt}/"
+ pats=("$pats[@]" '*(-/)')
+ ;;
+ g) gopt='-g'
+ pats=("$pats[@]" ${=OPTARG})
+ ;;
+ esac
+done
+
+# If we were given no file selection option, we behave as if we were given
+# a `-f'.
+
+if [[ "$sopt" = - ]]; then
+ if [[ -z "$gopt" ]]; then
+ sopt='-f'
+ pats=('*')
+ else
+ unset sopt
+ fi
+fi
+
+# str holds the whole string from the command line with a `*' between
+# the prefix and the suffix.
+
+str="${PREFIX:q}*${SUFFIX:q}"
+
+# If the string began with a `~', the quoting turned this into `\~',
+# remove the slash.
+
+[[ "$str" = \\\~* ]] && str="$str[2,-1]"
+
+# We will first try normal completion called with `complist', but only if we
+# weren't given a `-F' option.
+
+if (( ! $#ignore )); then
+ # First build an array containing the `-W' option, if there is any and we
+ # want to use it. We don't want to use it if the string from the command line
+ # is a absolute path or relative to the current directory.
+
+ if [[ -z "$tmp1[1]" || "$str[1]" = [~/] || "$str" = (.|..)/* ]]; then
+ tmp1=()
+ else
+ tmp1=(-W "( $prepaths )")
+ fi
+
+ # Now call complist.
+
+ nm=$NMATCHES
+ if [[ -z "$gopt" ]]; then
+ complist "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" "$tmp1[@]" $sopt
+ else
+ complist "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" "$tmp1[@]" $sopt -g "$pats"
+ fi
+
+ # If this generated any matches, we don't want to do in-path completion.
+
+ [[ -nmatches nm ]] || return
+
+ # No `-F' option, so we want to use `fignore'.
+
+ ignore=(-F fignore)
+fi
+
+# Now let's have a closer look at the string to complete.
+
+if [[ "$str[1]" = \~ ]]; then
+ # It begins with `~', so remember anything before the first slash to be able
+ # to report it to the completion code. Also get an expanded version of it
+ # (in `realpath'), so that we can generate the matches. Then remove that
+ # prefix from the string to complete, set `donepath' to build the correct
+ # paths and make sure that the loop below is run only once with an empty
+ # prefix path by setting `prepaths'.
+
+ linepath="${str%%/*}/"
+ eval realpath\=$linepath
+ str="${str#*/}"
+ donepath=''
+ prepaths=( '' )
+else
+ # If the string does not start with a `~' we don't remove a prefix from the
+ # string.
+
+ liniepath=''
+ realpath=''
+
+ if [[ "$str[1]" = / ]]; then
+ # If it is a absolut path name, we remove the first slash and put it in
+ # `donepath' meaning that we treat it as the path that was already handled.
+ # Also, we don't use the paths from `-W'.
+
+ str="$str[2,-1]"
+ donepath='/'
+ prepaths=( '' )
+ else
+ # The common case, we just use the string as it is, unless it begins with
+ # `./' or `../' in which case we don't use the paths from `-W'.
+
+ [[ "$str" = (.|..)/* ]] && prepaths=( '' )
+ donepath=''
+ fi
+fi
+
+# First we skip over all pathname components in `str' which really exist in
+# the file-system, so that `/usr/lib/l<TAB>' doesn't offer you `lib' and
+# `lib5'. Pathname components skipped this way are taken from `str' and added
+# to `donepath'.
+
+while [[ "$str" = */* ]] do
+ [[ -e "$realpath$donepath${str%%/*}" ]] || break
+ donepath="$donepath${str%%/*}/"
+ str="${str#*/}"
+done
+
+# Now build the glob pattern by calling `_match_pattern'.
+patstr="$str"
+matchflags=""
+_match_pattern _path_files patstr matchflags
+
+# We almost expect the pattern to have changed `..' into `*.*.', `/.' into
+# `/*.', and probably to contain two or more consecutive `*'s. Since these
+# have special meaning for globbing, we remove them. But before that, we
+# add the pattern for matching any characters before a slash.
+
+patstr="$patstr:gs-/-*/-:gs/*.*.//:gs-/*.-/.-:gs/**/*/"
+
+# Finally, generate the matches. First we loop over all the paths from `-W'.
+# Note that in this loop `str' is used as a modifyable version of `patstr'
+# and `testpath' is a modifyable version of `donepath'.
+
+for prepath in "$prepaths[@]"; do
+ str="$patstr"
+ testpath="$donepath"
+
+ # The second loop tests the components of the path in `str' to get the
+ # possible matches.
+
+ while [[ "$str" = */* ]] do
+ # `rest' is the pathname after the first slash that is left. In `tmp1'
+ # we get the globbing matches for the pathname component currently
+ # handled.
+
+ rest="${str#*/}"
+ tmp1="${prepath}${realpath}${testpath}${~matchflags}${str%%/*}(-/)"
+ tmp1=( $~tmp1 )
+
+ if [[ $#tmp1 -eq 0 ]]; then
+ # If this didn't produce any matches, we don't need to test this path
+ # any further, so continue with the next `-W' path, if any.
+
+ continue 2
+ elif [[ $#tmp1 -gt 1 ]]; then
+ # If it produced more than one match, we want to remove those which
+ # don't have possible following pathname components matching the
+ # rest of the string we are completing. (The case with only one
+ # match is handled below.)
+ # In `collect' we will collect those of the produced pathnames that
+ # have a matching possible path-suffix. In `suffixes' we build an
+ # array containing strings build from the rest of the string to
+ # complete and the glob patterns we were given as arguments.
+
+ collect=()
+ suffixes=( $rest$^pats )
+ suffixes=( "${(@)suffixes:gs.**.*.}" )
+
+ # In the loop the prefixes from the `tmp1' array produced above and
+ # the suffixes we just built are used to produce possible matches
+ # via globbing.
+
+ for i in $tmp1; do
+ tmp2=( ${~i}/${~matchflags}${~suffixes} )
+ [[ $#tmp2 -ne 0 ]] && collect=( $collect $i )
+ done
+
+ # If this test showed that none of the matches from the glob in `tmp1'
+ # has a possible sub-path matching what's on the line, we give up and
+ # continue with the next `-W' path.
+
+ if [[ $#collect -eq 0 ]]; then
+ continue 2
+ elif [[ $#collect -ne 1 ]]; then
+ # If we have more than one possible match, this means that the
+ # pathname component currently handled is ambiguous, so we give
+ # it to the completion code.
+ # First we build the full path prefix in `tmp1'.
+
+ tmp1="$prepath$realpath$testpath"
+
+ # Now produce all matching pathnames in `collect'.
+
+ collect=( ${~collect}/${~matchflags}${~suffixes} )
+
+ # And then remove the common path prefix from all these matches.
+
+ collect=( ${collect#$tmp1} )
+
+ # Finally, we add all these matches with the common (unexpanded)
+ # pathprefix (the `-p' option), the path-prefix (the `-W' option)
+ # to allow the completion code to test file type, and the path-
+ # suffix (the `-s' option). We also tell the completion code that
+ # these are file names and that `fignore' should be used as usual
+ # (the `-f' and `-F' options).
+
+ for i in $collect; do
+ compadd "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" -p "$linepath$testpath" -W "$tmp1" -s "/${i#*/}" -f "$ignore[@]" - "${i%%/*}"
+ done
+
+ # We have just finished handling all the matches from above, so we
+ # can continue with the next `-W' path.
+
+ continue 2
+ fi
+ # We reach this point if only one of the path prefixes in `tmp1'
+ # has a existing path-suffix matching the string from the line.
+ # In this case we accept this match and continue with the next
+ # path-name component.
+
+ tmp1=( "$collect[1]" )
+ fi
+ # This is also reached if the first globbing produced only one match
+ # in this case we just continue with the next pathname component, too.
+
+ tmp1="$tmp1[1]"
+ testpath="$testpath${tmp1##*/}/"
+ str="$rest"
+ done
+
+ # We are here if all pathname components except the last one (which is still
+ # not tested) are unambiguous. So we add matches with the full path prefix,
+ # no path suffix, the `-W' we are currently handling, all the matches we
+ # can produce in this directory, if any.
+
+ tmp1="$prepath$realpath$testpath"
+ suffixes=( $str$^pats )
+ suffixes=( "${(@)suffixes:gs.**.*.}" )
+ tmp2=( ${~tmp1}${~matchflags}${~suffixes} )
+ if [[ $#tmp2 -eq 0 && "$sopt" = */* ]]; then
+ [[ "$testpath[-1]" = / ]] && testpath="$testpath[1,-2]"
+ compadd "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" -f - "$linepath$testpath"
+ else
+ compadd "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" -p "$linepath$testpath" -W "$prepath$realpath$testpath" -f "$ignore[@]" - ${(@)tmp2#$tmp1}
+ fi
+done
diff --git a/Completion/Core/_sep_parts b/Completion/Core/_sep_parts
new file mode 100644
index 000000000..c1cda2b9a
--- /dev/null
+++ b/Completion/Core/_sep_parts
@@ -0,0 +1,171 @@
+#autoload
+
+# This function can be used to separately complete parts of strings
+# where each part may be one of a set of matches and different parts
+# have different sets.
+# Arguments are alternatingly arrays and separator strings. Arrays may
+# be given by name or literally as words separated by white space in
+# parentheses, e.g.:
+#
+# _sep_parts '(foo bar)' @ hosts
+#
+# This will make this function complete the strings in the array
+# `friends'. If the string on the line contains a `@', the substring
+# after it will be completed from the array `hosts'. Of course more
+# arrays may be given, each preceded by another separator string.
+#
+# This function understands the `-J group', `-V group', and
+# `-X explanation' options.
+#
+# This function does part of the matching itself and calls the functions
+# `_match_test' and `_match_pattern' for this.
+
+local str arr sep test testarr tmparr prefix suffixes matchers autosuffix
+local matchflags opt group expl nm=$compstate[nmatches]
+
+# Test if we should use this function for the global matcher in use.
+
+_match_test _sep_parts || return 1
+
+# Get the options.
+
+group=()
+expl=()
+while getopts "J:V:X:" opt; do
+ case "$opt" in
+ [JV]) group=("-$opt" "$OPTARG");;
+ X) expl=(-X "$OPTARG");;
+ esac
+done
+shift OPTIND-1
+
+# Get the string from the line.
+
+str="$PREFIX$SUFFIX"
+[[ $#compstate[pattern_match] -ne 0 ]] || str="$str:q"
+prefix=""
+
+# Walk through the arguments to find the longest unambiguous prefix.
+
+while [[ $# -gt 1 ]]; do
+ # Get the next array and separator.
+ arr="$1"
+ sep="$2"
+
+ if [[ "$arr[1]" == '(' ]]; then
+ tmparr=( ${=arr[2,-2]} )
+ arr=tmparr
+ fi
+ # Is the separator on the line?
+ [[ "$str" != *${sep}* ]] && break
+
+ # Build a pattern matching the possible matches and get all these
+ # matches in an array.
+
+ test="${str%%${sep}*}"
+ [[ -n "$_comp_correct" && $#test -le _comp_correct ]] && return 1
+
+ matchflags=""
+ _match_pattern _sep_parts test matchflags
+ [[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)"
+
+ test="${matchflags}${test}"
+ testarr=( "${(@M)${(@P)arr}:#${~test}*}" )
+ testarr=( "${(@)testarr:#}" )
+
+ # If there are no matches we give up. If there is more than one
+ # match, this is the part we will complete.
+ (( $#testarr )) || return 1
+ [[ $#testarr -gt 1 ]] && break
+
+ # Only one match, add it to the prefix and skip over it in `str',
+ # continuing with the next array and separator.
+ prefix="${prefix}${testarr[1]}${sep}"
+ str="${str#*${sep}}"
+ shift 2
+done
+
+# Get the array to work upon.
+arr="$1"
+if [[ "$arr[1]" == '(' ]]; then
+ tmparr=( ${=arr[2,-2]} )
+ arr=tmparr
+fi
+if [[ $# -le 1 || "$str" != *${2}* ]]; then
+ # No more separators, build the matches.
+
+ test="$str"
+ [[ -n "$_comp_correct" && $#test -le _comp_correct ]] && return 1
+
+ matchflags=""
+ _match_pattern _sep_parts test matchflags
+ [[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)"
+
+ test="${matchflags}${test}"
+ testarr=( "${(@M)${(@P)arr}:#${~test}*}" )
+ testarr=( "${(@)testarr:#}" )
+fi
+
+[[ $#testarr -eq 0 || ${#testarr[1]} -eq 0 ]] && return 1
+
+# Now we build the suffixes to give to the completion code.
+shift
+matchers=()
+suffixes=("")
+autosuffix=()
+
+while [[ $# -gt 0 && "$str" == *${1}* ]]; do
+ # Remove anything up to the the suffix.
+ str="${str#*${1}}"
+
+ # Again, we get the string from the line up to the next separator
+ # and build a pattern from it.
+ if [[ $# -gt 2 ]]; then
+ test="${str%%${3}*}"
+ else
+ test="$str"
+ fi
+
+ [[ -n "$_comp_correct" && $#test -le _comp_correct ]] && return 1
+
+ matchflags=""
+ _match_pattern _sep_parts test matchflags
+ [[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)"
+ test="${matchflags}${test}"
+
+ # We incrementally add suffixes by appending to them the seperators
+ # and the strings from the next array that match the pattern we built.
+
+ arr="$2"
+ if [[ "$arr[1]" == '(' ]]; then
+ tmparr=( ${=arr[2,-2]} )
+ arr=tmparr
+ fi
+ tmparr=( "${(@M)${(@P)arr}:#${~test}*}" )
+ tmparr=( "${(@)tmparr:#}" )
+ suffixes=("${(@)^suffixes[@]}${1}${(@)^tmparr}")
+
+ # We want the completion code to generate the most specific suffix
+ # for us, so we collect matching specifications that allow partial
+ # word matching before the separators on the fly.
+ matchers=("$matchers[@]" "r:|${1:q}=*")
+ shift 2
+done
+
+# If we were given at least one more separator we make the completion
+# code offer it by appending it as a autoremovable suffix.
+(( $# )) && autosuffix=(-qS "$1")
+
+# If we have collected matching specifications, we build an array
+# from it that can be used as arguments to `compadd'.
+[[ $#matchers -gt 0 ]] && matchers=(-M "$matchers")
+
+# Add the matches for each of the suffixes.
+for i in "$suffixes[@]"; do
+ compadd -U "$group[@]" "$expl[@]" "$matchers[@]" "$autosuffix[@]" \
+ -i "$IPREFIX" -p "$prefix" -s "$i" - "$testarr[@]"
+done
+
+# This sets the return value to indicate that we added matches (or not).
+
+[[ nm -ne compstate[nmatches] ]]
diff --git a/Completion/Core/_set_options b/Completion/Core/_set_options
new file mode 100644
index 000000000..5f634a2cd
--- /dev/null
+++ b/Completion/Core/_set_options
@@ -0,0 +1,7 @@
+#autoload
+
+# Complete all set options. This relies on `_main_complete' to store the
+# names of the options that were set when it was called in the array
+# `_set_options'.
+
+compadd "$@" -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' - $=_set_options
diff --git a/Completion/Core/_unset_options b/Completion/Core/_unset_options
new file mode 100644
index 000000000..c5150c2e5
--- /dev/null
+++ b/Completion/Core/_unset_options
@@ -0,0 +1,7 @@
+#autoload
+
+# Complete all unset options. This relies on `_main_complete' to store the
+# names of the options that were set when it was called in the array
+# `_set_options'.
+
+compadd "$@" -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' - $=_unset_options
diff --git a/Completion/Core/compdump b/Completion/Core/compdump
new file mode 100644
index 000000000..8be096f50
--- /dev/null
+++ b/Completion/Core/compdump
@@ -0,0 +1,89 @@
+# This is a file to be sourced to dump the definitions for new-style
+# completion defined by 'compinit' in the same directory. The output
+# should be directed into the "compinit.dump" in the same directory as
+# compinit. If you rename init, just stick .dump onto the end of whatever
+# you have called it and put it in the same directory. This is handled
+# automatically if you invoke compinit with the option -d.
+#
+# You will need to update the dump every time you add a new completion.
+# To do this, simply remove the .dump file, start a new shell, and
+# create the .dump file as before. Again, compinit -d handles this
+# automatically.
+#
+# It relies on KSH_ARRAYS not being set.
+
+# Print the number of files used for completion. This is used in compinit
+# to see if auto-dump should re-dump the dump-file.
+
+_d_file=${COMPDUMP-${0:h}/compinit.dump}
+
+typeset -U _d_files
+_d_files=( ${^~fpath}/_*~*~(N:t) )
+
+print "#files: $#_d_files" > $_d_file
+
+unset _d_files
+
+# First dump the arrays _comps and _patcomps. The quoting hieroglyphyics
+# ensure that a single quote inside a variable is itself correctly quoted.
+
+print "_comps=(" >> $_d_file
+for _d_f in ${(ok)_comps}; do
+ print -r - "'${_d_f//\'/'\\''}'" "'${_comps[$_d_f]//\'/'\\''}'"
+done >> $_d_file
+print ")" >> $_d_file
+
+print "\n_patcomps=(" >> $_d_file
+for _d_f in "$_patcomps[@]"; do
+ print -r - "'${_d_f//\'/'\\''}'"
+done >> $_d_file
+print ")" >> $_d_file
+
+print >> $_d_file
+
+# Now dump the key bindings. We dump all bindings for zle widgets
+# whose names start with a underscore.
+# We need both the zle -C's and the bindkey's to recreate.
+
+_d_bks=()
+zle -lL |
+ while read -rA _d_line; do
+ if [[ ${_d_line[5]} = _* ]]; then
+ print -r - ${_d_line}
+ _d_bks=($_d_bks ${_d_line[3]})
+ fi
+ done >> $_d_file
+bindkey |
+ while read -rA _d_line; do
+ if [[ ${_d_line[2]} = (${(j.|.)~_d_bks}) ]]; then
+ print -r "bindkey '${_d_line[1][2,-2]}' ${_d_line[2]}"
+ fi
+ done >> $_d_file
+
+print >> $_d_file
+
+
+# Autoloads: whence -w produces "_d_foo: function", so look for
+# all functions beginning with `_'.
+
+_d_als=($(whence -wm '_*' | sort |
+while read -rA _d_line; do
+ [[ ${_d_line[2]} = function ]] && print -r - ${_d_line[1]%:}
+done))
+
+# print them out: about five to a line looks neat
+
+while (( $#_d_als )); do
+ print -n autoload
+ for (( _i = 0; _i < 5; _i++ )); do
+ if (( $#_d_als )); then
+ print -n " $_d_als[1]"
+ shift _d_als
+ fi
+ done
+ print
+done >> $_d_file
+
+print >> $_d_file
+
+unset _d_line _d_zle _d_bks _d_als _d_f _f_file
diff --git a/Completion/Core/compinit b/Completion/Core/compinit
new file mode 100644
index 000000000..ec5867838
--- /dev/null
+++ b/Completion/Core/compinit
@@ -0,0 +1,269 @@
+# Initialisation for new style completion. This mainly contains some helper
+# function and aliases. Everything else is split into different files in this
+# directory that will automatically be made autoloaded (see the end of this
+# file).
+# The names of the files that will be considered for autoloading have to
+# start with a underscores (like `_setopt).
+# The first line of these files will be read and has to say what should be
+# done with its contents:
+#
+# `#defcomp <names ...>'
+# if the first line looks like this, the file is
+# autoloaded as a function and that function will
+# be called to generate the matches when completing
+# for one of the commands whose <name> is given
+#
+# `#defpatcomp <pattern>'
+# this defines a function that should be called to generate
+# matches for commands whose name matches <pattern>; note
+# that only one pattern may be given
+#
+# `#defkeycomp <style> [ <key-sequence> ... ]
+# this is used to bind special completions to all the given
+# <key-sequence>(s). The <style> is the name of one of the built-in
+# completion widgets (complete-word, delete-char-or-list,
+# expand-or-complete, expand-or-complete-prefix, list-choices,
+# menu-complete, menu-expand-or-complete, or reverse-menu-complete).
+# This creates a widget behaving like <style> so that the
+# completions are chosen as given in the the rest of the file,
+# rather than by the context. The widget has the same name as
+# the autoload file and can be bound using bindkey in the normal way.
+#
+# `#autoload'
+# this is for helper functions that are not used to
+# generate matches, but should automatically be loaded
+# when they are called
+#
+# Note that no white space is allowed between the `#' and the rest of
+# the string.
+#
+# See the file `compdump' for how to speed up initialiation.
+#
+# If you are using global matching specifications with `compctl -M ...'
+# have a look at the files `_match_test' and `_match_pattern'. To make
+# all the example functions use matching as specified with `-M' these
+# need some editing.
+#
+# If we got the `-d'-flag, we will automatically dump the new state (at
+# the end).
+
+if [[ "$1" = -d ]]; then
+ _i_autodump=1
+else
+ _i_autodump=0
+fi
+
+# The associative array containing the definitions for the commands.
+# Definitions for patterns will be stored in the normal array `_patcomps'.
+
+typeset -A _comps
+_patcomps=()
+
+# This function is used to register or delete completion functions. For
+# registering completion functions, it is invoked with the name of the
+# function as it's first argument (after the options). The other
+# arguments depend on what type of completion function is defined. If
+# none of the `-p' and `-k' options is given a function for a command is
+# defined. The arguments after the function name are then interpreted as
+# the names of the command for which the function generates matches.
+# With the `-p' option a function for a name pattern is defined. This
+# function will be invoked when completing for a command whose name
+# matches the pattern given as argument after the function name (in this
+# case only one argument is accepted).
+# With the `-k' option a function for a special completion keys is
+# defined and immediatly bound to those keys. Here, the extra arguments
+# are the name of one of the builtin completion widgets and any number
+# of key specifications as accepted by the `bindkey' builtin.
+# In any case the `-a' option may be given which makes the function
+# whose name is given as the first argument be autoloaded. When defining
+# a function for command names the `-n' option may be given and keeps
+# the definitions from overriding any previous definitions for the
+# commands.
+# For deleting definitions, the `-d' option must be given. Without the
+# `-p' option, this deletes definitions for functions for the commands
+# whose names are given as arguments. If combined with the `-p' option
+# it deletes the definitions for the patterns given as argument.
+# The `-d' option may not be combined with the `-k' option, i.e.
+# definitions for key function can not be removed.
+#
+# Examples:
+#
+# compdef -a foo bar baz
+# make the completion for the commands `bar' and `baz' use the
+# function `foo' and make this function be autoloaded
+#
+# compdef -p foo 'c*'
+# make completion for all command whose name begins with a `c'
+# generate matches by calling the function `foo' before generating
+# matches defined for the command itself
+#
+# compdef -k foo list-choices '^X^M' '\C-xm'
+# make the function `foo' be invoked when typing `Control-X Control-M'
+# or `Control-X m'; the function should generate matches and will
+# behave like the `list-choices' builtin widget
+#
+# compdef -d bar baz
+# delete the definitions for the command names `bar' and `baz'
+
+compdef() {
+ local opt autol type func delete new i
+
+ # Get the options.
+
+ while getopts "anpkd" opt; do
+ case "$opt" in
+ a) autol=yes;;
+ n) new=yes;;
+ [pk]) if [[ -n "$type" ]]; then
+ # Error if both `-p' and `-k' are given (or one of them
+ # twice).
+ echo "$0: type already set to $type"
+ return 1
+ fi
+ if [[ "$opt" = p ]]; then
+ type=pattern
+ else
+ type=key
+ fi
+ ;;
+ d) delete=yes;;
+ esac
+ done
+ shift OPTIND-1
+
+ if [[ -z "$delete" ]]; then
+ # Adding definitions, first get the name of the function name
+ # and probably do autoloading.
+
+ func="$1"
+ [[ -n "$autol" ]] && autoload "$func"
+ shift
+
+ case "$type" in
+ pattern)
+ if [[ $# -gt 1 ]]; then
+ echo "$0: only one pattern allowed"
+ return 1
+ fi
+ # Patterns are stored in strings like `c* foo', with a space
+ # between the pattern and the function name.
+
+ _patcomps=("$_patcomps[@]" "$1 $func")
+ ;;
+ key)
+ if [[ $# -lt 2 ]]; then
+ echo "$0: missing keys"
+ return 1
+ fi
+
+ # Define the widget.
+ zle -C "$func" "$1" "$func"
+ shift
+
+ # And bind the keys...
+ for i; do
+ bindkey "$i" "$func"
+ done
+ ;;
+ *)
+ # For commands store the function name in the `_comps'
+ # associative array, command names as keys.
+ for i; do
+ [[ -z "$new" || "${+_comps[$i]}" -eq 0 ]] && _comps[$i]="$func"
+ done
+ ;;
+ esac
+ else
+ # Handle the `-d' option, deleting.
+ case "$type" in
+ pattern)
+ # Note the space.
+ for i; do
+ _patcomps=("${(@)patcomps:#$i *}")
+ done
+ ;;
+ key)
+ # Oops, cannot do that yet.
+
+ echo "$0: cannot restore key bindings"
+ return 1
+ ;;
+ *)
+ # Deleting definitons for command is even simpler.
+ for i; do
+ unset "_comps[$i]"
+ done
+ esac
+ fi
+}
+
+# Now we automatically make the definition files autoloaded.
+
+# First we get the name of a dump file if this will be used.
+
+: ${COMPDUMP:=$0.dump}
+
+if [[ ! -o extendedglob ]]; then
+ _i_noextglob=yes
+ setopt extendedglob
+fi
+
+typeset -U _i_files
+_i_files=( ${^~fpath}/_*~*~(N:t) )
+_i_initname=$0
+_i_done=''
+
+# If we have a dump file, load it.
+
+if [[ -f "$COMPDUMP" ]]; then
+ read -rA _i_line < "$COMPDUMP"
+ if [[ _i_autodump -eq 1 && $_i_line[2] -eq $#_i_files ]]; then
+ builtin . "$COMPDUMP"
+ _i_done=yes
+ fi
+ unset _i_line
+fi
+if [[ -z "$_i_done" ]]; then
+ for _i_dir in $fpath; do
+ [[ $_i_dir = . ]] && continue
+ for _i_file in $_i_dir/_*~*~(N); do
+ read -rA _i_line < $_i_file
+ _i_tag=$_i_line[1]
+ shift _i_line
+ if [[ $_i_tag = '#defcomp' ]]; then
+ compdef -na "${_i_file:t}" "${_i_line[@]}"
+ elif [[ $_i_tag = '#defpatcomp' ]]; then
+ compdef -pa "${_i_file:t}" "${_i_line[@]}"
+ elif [[ $_i_tag = '#defkeycomp' ]]; then
+ compdef -ka "${_i_file:t}" "${_i_line[@]}"
+ elif [[ $_i_tag = '#autoload' ]]; then
+ autoload ${_i_file:t}
+ fi
+ done
+ done
+
+ bindkey |
+ while read -rA _i_line; do
+ if [[ "$_i_line[2]" = complete-word ||
+ "$_i_line[2]" = delete-char-or-list ||
+ "$_i_line[2]" = expand-or-complete ||
+ "$_i_line[2]" = expand-or-complete-prefix ||
+ "$_i_line[2]" = list-choices ||
+ "$_i_line[2]" = menu-complete ||
+ "$_i_line[2]" = menu-expand-or-complete ||
+ "$_i_line[2]" = reverse-menu-complete ]]; then
+ zle -C _complete_$_i_line[2] $_i_line[2] _main_complete
+ bindkey "${_i_line[1][2,-2]}" _complete_$_i_line[2]
+ fi
+ done
+
+ unset _i_dir _i_line _i_file _i_tag
+
+ # If autodumping was requested, do it now.
+
+ (( _i_autodump )) && builtin . ${_i_initname:h}/compdump
+fi
+
+[[ -z "$_i_noextglob" ]] || unsetopt extendedglob
+
+unset _i_files _i_initname _i_done _i_autodump _i_noextglob
diff --git a/Completion/README b/Completion/README
new file mode 100644
index 000000000..ac2accfca
--- /dev/null
+++ b/Completion/README
@@ -0,0 +1,107 @@
+The subdirectories contain code for the new function-based completion
+system. Broadly speaking, this uses shell functions defined for each
+command to determine how the arguments of a command should be completed.
+
+You should copy all the files you need or want to a directory of your own,
+which should be included in your autoload path as defined by $fpath. Then
+in your .zshrc you should source the file which appears here in
+Core/compinit. It is recommnded that you use the -d option, which outputs
+a file containing the necessary variables, bindkeys etc., making later
+loading much faster. For example,
+ [[ -f ~/completion/compinit ]] && . ~/completion/compinit -d
+This will rebind any keys which do completion to use the new system.
+For more detailed instructions, including how to add new completions, see
+the top of Core/compinit .
+
+The subdirectories contain:
+
+Core:
+ The basic functions and files to be sourced. You will certainly need
+ these, and will most likely not feel like altering them (or, in some
+ cases, even reading them, unless you are a shell wizard). The files are:
+ compinit
+ As already described, this is not a function, but is sourced once
+ (with the `source' or `.' commands) to set up the completion system.
+ compdump
+ This dumps the completions status for faster initialisation. The
+ easiest way of doing this is to use the -d option to compinit rather
+ than calling compdump directly.
+ _comp_parts
+ Utility used for completing words with multiple separate parts, such as
+ `<user>@<host>'
+ _compalso
+ Utility for calling a function to add additional completions to an
+ already existing set.
+ _files
+ A frontend to _path_files which will default to any old file if the
+ specified file was not found.
+ _main_complete
+ The main entry point called by the key bindings which compinit sets
+ up (the main `completion widget' in zsh jargon).
+ _normal
+ The function called by _main_complete to handle the most common
+ cases, such as completing a command name or its arguments. This
+ function dispatches to the various other functions for individual
+ commands. (Actually, the system is fairly context-sensitive, so
+ it is wider than just command+argument.)
+ _path_files
+ The function usually called to complete filenames and directories. It
+ replaces the standard -f and -/ options for the basic completion
+ commands: it can do various extra tricks, such as expanding a whole
+ path at once, e.g. F/C/C/_p<TAB> -> Functions/Completion/Core/_path_files
+Base:
+ You will almost certainly want these files, too, which handle standard
+ tasks like completing files. However, you may want to edit them for
+ your own particular setup. Files are:
+ _command_names
+ This handles completion of the command word, i.e. the first thing
+ on the command line. You may want to alter this, for example,
+ to complete parameters to assign to.
+ _condition
+ This handles completing inside [[ ... ]] .
+ _default
+ This handles completion of command arguments when no special function
+ exists. Usually this means completing files, but you can modify this
+ as you wish.
+ _match_pattern
+ _match_test
+ These are used by Base/_path_files (and hence also Base/_files) for
+ file completion with control over matching (whether to complete
+ case-insensitively, or to allow insertion before `.', etc.) See
+ _match_test for instructions. Note _path_files expects these files
+ to be present.
+ _precommand
+ Allows completion when the first word on the line has to be ignored,
+ for example `noglob ...' should ignore the noglob and just complete
+ as if it wasn't there. Add other such commands to the top line.
+ _redirect
+ Completes after `<' or `<': this version calls _files.
+ _subscript
+ For completion in subscripts of parameters, e.g $foo[...].
+ _vars
+ Completion for commands which need variables (so this could also be in
+ the Builtins directory), but also in math environments such as ((...)).
+Builtins:
+ Define completions for various shell builtins. The top line of each file
+ says which builtins they apply to; in many cases you can guess from the
+ name. Note in particular that _zftp defines completions for all commands
+ beginning `zf', not just for the module command zftp. This is only
+ really useful if you use zftp with the zf* function suite (zfopen, zfget,
+ ...).
+User:
+ This contains a pot pourri of completions for various external commands.
+ Not all will work unmodified on your system.
+Commands:
+ These functions define separate completion commands which do not use
+ the usual context information, and hence have to be bound separately
+ to keys. As they appear, they have bindings which you can change or
+ delete by altering the top line of the file. To bind a function
+ (strictly speaking, the corresponding completion widget) yourself
+ after completion is loaded, use `bindkey '<key-string>' <_function_name>'.
+ The files are:
+ _correct_filename, bound to \C-xc
+ Correct the word under the cursor as a filename. This is significantly
+ more powerful than the standard \e$ (spell-word) binding.
+ _most_recent_file, bound to \C-xm
+ Insert the name of the most recent file matching the pattern
+ so far on the command line.
diff --git a/Completion/User/.distfiles b/Completion/User/.distfiles
new file mode 100644
index 000000000..ee0017035
--- /dev/null
+++ b/Completion/User/.distfiles
@@ -0,0 +1,6 @@
+DISTFILES_SRC='
+ .distfiles
+ _a2ps _compress _configure _dd _dvi _find _gunzip _gzip _hosts
+ _make _man _mh _pdf _ps _rcs _rlogin _strip _stty _tar _tar_archive
+ _tex _uncompress _x_options _xfig
+'
diff --git a/Completion/User/_a2ps b/Completion/User/_a2ps
new file mode 100644
index 000000000..9aa9d3d99
--- /dev/null
+++ b/Completion/User/_a2ps
@@ -0,0 +1,22 @@
+#defcomp a2ps
+
+if [[ -prefix -- ]]; then
+ _comp_parts '(--borders --compact --truncate-lines --interpret
+ --print-anyway --delegate)' '=' '(yes no)'
+ _comp_parts '(--major)' '=' '(rows columns)'
+ _comp_parts '(--end-of-line)' '=' '(r n nr rn any)'
+
+ complist -S= -k '(--medium --columns --rows --line-numbers
+ --font-size --lines-per-page --chars-per-line
+ --tabsize --non-printable-format --encoding
+ --title --stdin --prologue --highlight-level
+ --strip-level --output --version-control --suffix
+ --printer --copies --sides --page-prefeed
+ --no-page-prefeed)'
+ complist -qS= -k '(--margin --header --underlay --left-title
+ --right-title --left-footer --footer --right-footer
+ --pages --pretty-print)'
+ complist -k '(--landscape --portrait --catman --no-header)'
+else
+ _files -F fignore -g "*~*.ps"
+fi
diff --git a/Completion/User/_compress b/Completion/User/_compress
new file mode 100644
index 000000000..860aeb5b0
--- /dev/null
+++ b/Completion/User/_compress
@@ -0,0 +1,3 @@
+#defcomp compress
+
+_files -g '*~*.Z'
diff --git a/Completion/User/_configure b/Completion/User/_configure
new file mode 100644
index 000000000..de8d5fba5
--- /dev/null
+++ b/Completion/User/_configure
@@ -0,0 +1,12 @@
+#defcomp configure
+
+if [[ $PREFIX = *=* ]]; then
+ # Complete filenames after e.g. --prefix=
+ IPREFIX=${PREFIX%%=*}=
+ PREFIX=${PREFIX#*=}
+ complist -f
+else
+ # Generate a list of options from configure --help
+ complist -s '$($COMMAND --help |
+ sed -n -e '\''s/^ *\(--[-a-z0-9]*\)[ =,].*$/\1/p'\'')'
+fi
diff --git a/Completion/User/_dd b/Completion/User/_dd
new file mode 100644
index 000000000..2458541ea
--- /dev/null
+++ b/Completion/User/_dd
@@ -0,0 +1,13 @@
+#defcomp dd
+
+if [[ -iprefix conv= ]]; then
+ # If there's a comma present, ignore up to the last one. The
+ # test alone will have that effect.
+ [[ -string , ]]
+ complist -S, -q \
+ -k '(ascii ebcdic ibm block unblock lcase ucase swab noerror sync)'
+elif [[ -iprefix 'if=' || -iprefix 'of=' ]]; then
+ _files
+else
+ complist -S '=' -k '(if of ibs obs bs cbs skip files seek count conv)'
+fi
diff --git a/Completion/User/_dvi b/Completion/User/_dvi
new file mode 100644
index 000000000..bb2fc293e
--- /dev/null
+++ b/Completion/User/_dvi
@@ -0,0 +1,3 @@
+#defcomp xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype
+
+_files -g '*.(dvi|DVI)'
diff --git a/Completion/User/_find b/Completion/User/_find
new file mode 100644
index 000000000..ca4f79908
--- /dev/null
+++ b/Completion/User/_find
@@ -0,0 +1,21 @@
+#defcomp find
+
+if [[ -mbetween -(ok|exec) \\\; ]]; then
+ _normal "$@"
+elif [[ -iprefix - ]]; then
+ complist -s 'daystart {max,min,}depth follow noleaf version xdev \
+ {a,c,}newer {a,c,m}{min,time} empty false {fs,x,}type gid inum links \
+ {i,}{l,}name {no,}{user,group} path perm regex size true uid used \
+ exec {f,}print{f,0,} ok prune ls'
+elif [[ -position 1 ]]; then
+ complist -g '. ..'
+ _files -g '(-/)'
+elif [[ -mcurrent -1 -((a|c|)newer|fprint(|0|f)) ]]; then
+ _files
+elif [[ -current -1 -fstype ]]; then
+ complist -k '(ufs 4.2 4.3 nfs tmp mfs S51K S52K)'
+elif [[ -current -1 -group ]]; then
+ complist -k groups
+elif [[ -current -1 -user ]]; then
+ complist -u
+fi
diff --git a/Completion/User/_gunzip b/Completion/User/_gunzip
new file mode 100644
index 000000000..35a27e774
--- /dev/null
+++ b/Completion/User/_gunzip
@@ -0,0 +1,3 @@
+#defcomp gunzip zcat
+
+_files -g '*.[gG][z]'
diff --git a/Completion/User/_gzip b/Completion/User/_gzip
new file mode 100644
index 000000000..3cda1e4ed
--- /dev/null
+++ b/Completion/User/_gzip
@@ -0,0 +1,3 @@
+#defcomp gzip
+
+_files -g '*~*.[gG][zZ]'
diff --git a/Completion/User/_hosts b/Completion/User/_hosts
new file mode 100644
index 000000000..3acc327ac
--- /dev/null
+++ b/Completion/User/_hosts
@@ -0,0 +1,3 @@
+#defcomp ftp ncftp ping rwho rup xping traceroute nslookup
+
+complist -k hosts
diff --git a/Completion/User/_make b/Completion/User/_make
new file mode 100644
index 000000000..d576b0308
--- /dev/null
+++ b/Completion/User/_make
@@ -0,0 +1,3 @@
+#defcomp make gmake pmake
+
+complist -s "\$(awk '/^[a-zA-Z0-9][^/ ]+:/ {print \$1}' FS=: [mM]akefile)"
diff --git a/Completion/User/_man b/Completion/User/_man
new file mode 100644
index 000000000..8204fba0b
--- /dev/null
+++ b/Completion/User/_man
@@ -0,0 +1,11 @@
+#defcomp man
+setopt localoptions rcexpandparam
+
+local rep
+if [[ $2 = (<->*|ln) ]]; then
+ rep=( $manpath/(man|cat)$2/$PREFIX*$SUFFIX.<->*(N:t:r) )
+else
+ rep=( $manpath/(man|cat)*/$PREFIX*$SUFFIX.<->*(N:t:r) )
+fi
+
+(( $#rep )) && compadd -m $rep
diff --git a/Completion/User/_mh b/Completion/User/_mh
new file mode 100644
index 000000000..67ce49fd2
--- /dev/null
+++ b/Completion/User/_mh
@@ -0,0 +1,70 @@
+#defcomp folder comp inc mark refile repl scan show next prev rmm pick whom mhn mhpath mhpatch
+
+# Completion for all possible MH commands.
+# Alter the following two to your own mh directory and the directory
+# where standard mh library files live. (It works anyway, but this
+# will save a little time.)
+local mymhdir=~/Mail
+local mhlib=/usr/lib/mh
+
+# To be on the safe side, check this exists and if not, get it anyway.
+[[ -d $mymhdir ]] || mymhdir=$(mhpath +)
+
+if [[ -iprefix - ]]; then
+ # get list of options, which MH commands can generate themselves
+ # awk is just too icky to use for this, sorry. send me one if
+ # you come up with it.
+ compadd -m $($COMMAND -help | perl -ne 'if (/^\s*-\(?(\S+)/) {
+ $n = $1;
+ $n =~ s/\)//g;
+ print $n =~ s/^\[([a-z]+)\]// ? "$n\n$1$n\n" : "$n\n";
+ }')
+ return
+elif [[ -iprefix '+' || -iprefix '@' || -current -1 -draftfolder ]]; then
+ # Complete folder names.
+ local mhpath
+ if [[ $IPREFIX != '@' ]]; then
+ [[ $IPREFIX = '+' ]] || IPREFIX=+
+ mhpath=$mymhdir
+ else
+ mhpath=$(mhpath)
+ fi
+
+ # painless, or what?
+ complist -W mhpath -/
+elif [[ -mcurrent -1 -(editor|(whatnow|rmm|show|more)proc) ]]; then
+ complist -c
+elif [[ -current -1 -file ]]; then
+ complist -f
+elif [[ -mcurrent -1 -(form|audit|filter) ]]; then
+ # Need some MH template file, which may be in our own MH directory
+ # or with the standard library.
+ local mhfpath
+ # This is the only place we need mhlib, so leave the test till here.
+ [[ -d $mhlib ]] || { mhlib=$(mhparam mhlproc); mhlib=$mhlib:h; }
+ mhfpath=($mymhdir $mhlib)
+
+ complist -W mhfpath -g '*(.)'
+elif [[ -mcurrent -1 -(no|)cc ]]; then
+ compadd -m all to cc me
+elif [[ -mcurrent -1 -[rw]cache ]]; then
+ compadd -m public private never ask
+else
+ # Generate sequences.
+ local foldnam folddir f
+ for f in $argv; do
+ [[ $f = [@+]* ]] && foldnam=$f
+ done
+ if [[ $foldnam = '+'* ]]; then
+ folddir=$mymhdir/${foldnam#+}
+ elif [[ $foldnam = '@'* ]]; then
+ folddir=$(mhpath)/${foldnam#@}
+ else
+ folddir=$(mhpath)
+ # leaving foldnam empty works here
+ fi
+
+ complist -s '$(mark $foldnam | awk -F: '\''{ print $1 }'\'')'
+ compadd -m reply next cur prev first last all unseen
+ complist -W folddir -g '<->'
+fi
diff --git a/Completion/User/_pdf b/Completion/User/_pdf
new file mode 100644
index 000000000..7d7756c3e
--- /dev/null
+++ b/Completion/User/_pdf
@@ -0,0 +1,3 @@
+function acroread
+
+_files -g '*.(pdf|PDF)'
diff --git a/Completion/User/_ps b/Completion/User/_ps
new file mode 100644
index 000000000..6bc0643b2
--- /dev/null
+++ b/Completion/User/_ps
@@ -0,0 +1,3 @@
+#defcomp gs ghostview gview psnup psselect pswrap pstops pstruct lpr
+
+_files -g '*([pP][sS]|eps)'
diff --git a/Completion/User/_rcs b/Completion/User/_rcs
new file mode 100644
index 000000000..537db6278
--- /dev/null
+++ b/Completion/User/_rcs
@@ -0,0 +1,9 @@
+#defcomp co ci rcs
+
+[[ $COMMAND = ci || $COMMAND = rcs ]] && _files
+
+if [[ $NMATCHES -eq 0 && -d RCS && $COMMAND != ci ]]; then
+ local rep
+ rep=(RCS/$PREFIX*$SUFFIX,v(:t:s/\,v//))
+ (( $#rep )) && compadd -m $rep
+fi
diff --git a/Completion/User/_rlogin b/Completion/User/_rlogin
new file mode 100644
index 000000000..e36554f23
--- /dev/null
+++ b/Completion/User/_rlogin
@@ -0,0 +1,9 @@
+#defcomp rlogin rsh ssh
+
+if [[ -position 1 ]]; then
+ complist -k hosts
+elif [[ -position 2 ]]; then
+ complist -k '(-l)'
+else
+ complist -u
+fi
diff --git a/Completion/User/_strip b/Completion/User/_strip
new file mode 100644
index 000000000..6962ac455
--- /dev/null
+++ b/Completion/User/_strip
@@ -0,0 +1,2 @@
+#defcomp strip
+_files -g '*(*)'
diff --git a/Completion/User/_stty b/Completion/User/_stty
new file mode 100644
index 000000000..6b54b5007
--- /dev/null
+++ b/Completion/User/_stty
@@ -0,0 +1,16 @@
+#defcomp stty
+
+if [[ -mcurrent -1 \
+ (*erase|discard|status|dsusp|intr|kill|lnext|quit|reprint|start|s*p) ]]
+then
+ compadd -m -Q '^-' '^h' '^?' '^c' '^u'
+else
+ [[ -string '-' || -string '+' ]]
+ compadd -m rows columns intr quit erase kill eof eol \
+ eol2 start stop susp dsusp reprint discard werase lnext \
+ parenb parodd cs8 cstopb hupcl cread clocal parext \
+ ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl iuclc \
+ ixon ixany ixoff imaxbel isig icanon xcase echo echoe echok \
+ echonl noflsh tostop echoctl echoprt echoke flusho pending iexten \
+ opost olcuc onlcr ocrnl onocr onlret ofill ofdel
+fi
diff --git a/Completion/User/_tar b/Completion/User/_tar
new file mode 100644
index 000000000..91767e44d
--- /dev/null
+++ b/Completion/User/_tar
@@ -0,0 +1,11 @@
+#defcomp tar
+
+local nm=$NMATCHES tf="$2"
+
+if [[ ( -mword 1 *t*f* || -mword 1 *x*f* ) && -position 3 100000 ]]; then
+ complist -k "( $(tar tf $tf) )"
+elif [[ -mword 1 *c*f* && -position 3 100000 ]]; then
+ _files
+elif [[ -mcurrent -1 *f* && -position 2 ]]; then
+ _files -g '*.(tar|TAR)'
+fi
diff --git a/Completion/User/_tar_archive b/Completion/User/_tar_archive
new file mode 100644
index 000000000..58e436c4d
--- /dev/null
+++ b/Completion/User/_tar_archive
@@ -0,0 +1,20 @@
+#autoload
+
+# This is used to generate filenames usable as a tar archive. This may
+# get one argument, a collection of tar option characters that may be
+# used to find out what kind of filename is needed. If no argument is
+# given but the parameter `_tar_cmd' is set, that is used.
+# If your version of `tar' supports this you may want to complete
+# things like `host:file' or `user@host:file' here.
+
+[[ $# -eq 0 && $+_tar_cmd -ne 0 ]] && set "$_tar_cmd"
+
+if [[ "$1" = *[tx]* ]]; then
+ if [[ "$1" = *[zZ]* ]]; then
+ _files -g '*.((tar|TAR).(gz|GZ|Z)|.tgz)'
+ else
+ _files -g '*.(tar|TAR)'
+ fi
+else
+ _files
+fi
diff --git a/Completion/User/_tex b/Completion/User/_tex
new file mode 100644
index 000000000..1f645e2a2
--- /dev/null
+++ b/Completion/User/_tex
@@ -0,0 +1,3 @@
+#defcomp tex latex slitex
+
+_files -g '*.(tex|TEX|texinfo|texi)'
diff --git a/Completion/User/_uncompress b/Completion/User/_uncompress
new file mode 100644
index 000000000..e25805d50
--- /dev/null
+++ b/Completion/User/_uncompress
@@ -0,0 +1,3 @@
+#defcomp uncompress zmore
+
+_files -g '*.Z'
diff --git a/Completion/User/_x_options b/Completion/User/_x_options
new file mode 100644
index 000000000..cc469286d
--- /dev/null
+++ b/Completion/User/_x_options
@@ -0,0 +1,5 @@
+#defpatcomp */X11/*
+
+# A simple pattern completion, just as an example.
+
+complist -J options -k '(-display -name -xrm)'
diff --git a/Completion/User/_xfig b/Completion/User/_xfig
new file mode 100644
index 000000000..fcd2bba9f
--- /dev/null
+++ b/Completion/User/_xfig
@@ -0,0 +1,3 @@
+#defcomp xfig
+
+_files -g '*.fig'
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
new file mode 100644
index 000000000..632cb3195
--- /dev/null
+++ b/Doc/Zsh/compsys.yo
@@ -0,0 +1,691 @@
+texinode(Completion System)()(Zsh Modules)(Top)
+chapter(Completion System)
+cindex(completion, system)
+cindex(completion, programmable)
+cindex(completion, controlling)
+sect(Description)
+
+This describes the shell code for the new completion system. It consists
+of two scripts and a few other files that define shell functions.
+The shell functions which implement completion behaviour and which may
+be bound to keystrokes, are referred to as `widgets'. All are contained
+in the following subdirectories of the tt(Completion) directory of the main
+distribution directory.
+
+startitem()
+item(tt(Core))(
+The core scripts and functions. You will certainly need these, though will
+probably not need to alter them. The contents of this directory is
+described in more detail below.
+)
+item(tt(Base))(
+Other functions you will almost certainly want if you are going to use
+any of the standard completion functions. You may want to edit some of
+these files.
+)
+item(tt(Builtins))(
+Functions for completing arguments of shell builtin commands.
+)
+item(tt(User))(
+Functions for completing arguments of external commands and suites of
+commands. They may need modifying for your system.
+)
+item(tt(Commands))(
+Functions which implement special types of completion to be bound to
+keystrokes rather than called by context.
+)
+enditem()
+
+You should decide which files you will be using and copy them to a
+directory (or multiple directories) of your own which should appear in your
+tt($fpath) variable so that the functions can be autoloaded.
+
+startmenu()
+menu(Initialisation)
+menu(Control Functions)
+menu(Completion Functions)
+endmenu()
+
+texinode(Initialisation)(Control Functions)()(Completion System)
+sect(Initialisation)
+
+To initialise the system, the script tt(compinit) should be sourced with
+`tt(source )var(<path>)tt(/compinit)' or
+`tt(. )var(<path>)tt(/compinit)'. This will define a few utility functions,
+arrange for all the necessary shell functions to be autoloaded, and will
+then re-bind all keys that do completion to use the new system.
+
+subsect(Arguments)
+
+To speed up the running of tt(compinit), it can be made to produce a dumped
+configuration which will be read in on future invocations. The easiest way
+to do this is by adding the option tt(-d) whenever tt(compinit) is sourced.
+In this case the dumped file will have the same name as the sourced file,
+but with tt(.dump) appended to the end; alternatively, an explicit file
+name can be given following the tt(-d). On the next call to tt(compinit
+-d), the dumped file will be read instead.
+
+If the number of completion files changes, tt(compinit) will recognise this
+and produce a new dump file. However, if the name of a function or the
+arguments in the first line of a tt(#compdef) funcion (as described below)
+change, it is easiest to delete the dump file by hand so that the next time
+tt(compinit) will re-create it.
+
+The dumping is actually done by another script, tt(compdump), but you will
+only need to source this yourself if you change the configuration
+(e.g. using tt(compdef)) and then want to dump the new one. The name of
+the old dumped file will be remembered for this.
+
+subsect(Autoloaded files)
+
+The convention for autoloaded functions used in completion is that they
+start with an underscore; as already mentioned, the tt(fpath/FPATH)
+parameter must contain the directory in which they are stored. When
+tt(compinit) is sourced, it searches all such files accessible via
+tt(fpath/FPATH) and reads the first line of each of them. This line should
+contain one of the tags described below. Files whose first line does not
+start with one of these tags are not considered to be part of the
+completion system and will not be treated specially.
+
+The tags are:
+
+startitem()
+item(tt(#compdef) var(names...))(
+The file will be made autoloadable and the function defined
+in it will be called when completing var(names), each of which is
+either the name of a command whose arguments are to be completed or one of
+a number of special contexts in the form tt(-)var(context)tt(-) described
+below for the tt(_complete) function.
+)
+item(tt(#compdef -p) var(pattern))(
+The file will be made autoloadable and the function defined in it will be
+called when completing for a command whose name matches the given
+var(pattern) (a standard globbing pattern). Note that only one
+var(pattern) may be given.
+)
+item(tt(#compdef -k) var(style key-sequences...))(
+This can be used bind special completion functions to the
+var(key-sequences). It creates a widget behaving like the builtin widget
+var(style), which must be one of those that perform completion, namely
+tt(complete-word), tt(delete-char-or-list), tt(expand-or-complete),
+tt(expand-or-complete-prefix), tt(list-choices), tt(menu-complete),
+tt(menu-expand-or-complete), or tt(reverse-menu-complete).
+
+The widget is then bound to all the var(key-sequences) given, if any: when
+one of the var(key-sequences) is typed, the function in the file will
+be invoked to generate the matches. The widget created has the same
+name as the file and can also be bound to other keys using tt(bindkey)
+as usual.
+)
+item(tt(#autoload))(
+This is used for files defining utility function that are not to be
+called directly as completion functions but should be loaded automatically
+when invoked. Typically they are to be called from within one of the
+completion functions.
+)
+enditem()
+
+Note that the tt(#) is part of the tag name and no white space is allowed
+after it. The tt(#compdef) tags use the tt(compdef) function defined
+below; the main difference is that the name of the function is supplied
+implicitly.
+
+subsect(Functions)
+
+The tt(compinit) file defines the following functions, which may
+also be called directly by the user.
+
+startitem()
+xitem(tt(compdef) [ tt(-an) ] var(function names...))
+xitem(tt(compdef -d) var(names...))
+xitem(tt(compdef -p) [ tt(-a) ] var(function pattern))
+item(tt(compdef -k) [ tt(-a) ] var(function style key-sequences...))(
+The first form tells the completion system to call the given
+var(function) when completing for the contexts or commands
+whose var(names) are given: this is like the tt(#compdef) tag. If the
+tt(-n) option is given, any existing completion behaviour for particular
+contexts or commands will not be altered. These definitions can be deleted
+by giving the tt(-d) option as in the second form.
+
+The third form is similar to the first, but var(function) will be called
+for all commands whose name matches the var(pattern); this is like the
+tt(#compdef -p) function tag.
+
+The fourth form defines a widget with the same name as the var(function)
+which will be called for each of the var(key-sequences); this is like the
+tt(#compdef -k) tag. The function should generate the completions needed
+and will otherwise behave like the builtin widget whose name is given as
+the var(style) argument. The widgets usable for this are:
+tt(complete-word), tt(delete-char-or-list), tt(expand-or-complete),
+tt(expand-or-complete-prefix), tt(list-choices), tt(menu-complete),
+tt(menu-expand-or-complete), and tt(reverse-menu-complete).
+
+In each of the forms supporting it the tt(-a) option makes the
+var(function) autoloadable (exactly equivalent to
+tt(autoload )var(function)).
+)
+xitem(tt(compconf) var(definitions...))
+xitem(tt(compconf))
+item(tt(compconf) [ tt(-l) ] var(keys...))(
+Several aspects of the completion system can be configured by the
+user. The configuration values are stored under the keys described
+below in the associative array `tt(compconfig)'. After sourcing
+tt(compinit), configuration values can either be set directly as in
+`tt(compconfig[completer]=_complete)' or by calling this utility function.
+
+Each var(definition) may be either a simple `var(key)', which sets this
+key in the tt(compconfig) array to an empty string, or of the form
+`var(key=value)' which stores the `var(value)' under key `var(key)'.
+
+Since the completion system also uses the array for internal purposes,
+you should not set all values at once by doing `tt(compconfig=(...))'.
+
+In the second form (without arguments), this function lists all keys
+and their values. If given the tt(-l) option as its first argument, as
+in the last form, the other arguments are taken as names of keys and
+the values of these keys are printed one per line.
+)
+enditem()
+
+texinode(Control Functions)(Completion Functions)(Initialisation)(Completion System)
+sect(Control Functions)
+
+The initialisation script tt(compinit) re-binds all the keys which perform
+completion to newly created widgets that all call the supplied widget
+function tt(_main_complete). This function acts as a wrapper calling
+the so-called `completer' functions that generate matches. If
+tt(_main_complete) is
+called with arguments, these are taken as the names of completer
+functions to be called in the order given. If no arguments are given, the
+set of functions to try is taken from the colon-separated list in the
+configuration key tt(completer). For example, to use normal
+completion and correction if that doesn't generate any matches:
+
+indent(
+nofill(tt(compconf completer=_complete:_correct))
+)
+
+after sourcing tt(compinit). The default value for this configuration key
+set up in tt(compinit) is `tt(_complete)', i.e. normally only ordinary
+completion is tried. The tt(_main_complete) function uses the return value
+of the completer functions to decide if other completers should be
+called. If the return value is zero, no other completers are tried and the
+tt(_main_complete) function returns.
+
+The following completer functions are contained in the distribution (users
+may write their own):
+
+startitem()
+item(tt(_complete))(
+This completer generates all possible completions in a context-sensitive
+manner, i.e. using the tt(compdef) function
+explained above and the current settings of all special parameters.
+
+To complete arguments of commands, tt(_complete) uses the utility function
+tt(_normal), which is in turn responsible for finding the particular
+function; it is described below. Various contexts of the form
+tt(-)var(context)tt(-), as mentioned above for the tt(#compdef) tag, are
+handled specially. These are:
+
+startitem()
+item(tt(-equal-))(
+for completion after an equal sign, other than one occurring in a
+shell-variable assignment.
+)
+item(tt(-tilde-))(
+for completion after a tilde (`tt(~)') character, but before a slash.
+)
+item(tt(-redirect-))(
+for completion after a redirection operator.
+)
+item(tt(-math-))(
+for completion inside mathematical contexts, such as
+`tt(LPAR()LPAR())...tt(RPAR()RPAR())'.
+)
+item(tt(-subscript-))(
+for completion inside subscripts.
+)
+item(tt(-value-))(
+for completion on the right hand side of an assignment.
+)
+item(tt(-array-value-))(
+for completion on the right hand side of an array-assignment
+(`tt(foo=LPAR()...RPAR())').
+)
+item(tt(-condition-))(
+for completion inside conditions (`tt([[...]])').
+)
+item(tt(-parameter-))(
+for completing the name of a parameter expansion (`tt($...)').
+)
+item(tt(-brace-parameter-))(
+for completing the name of a parameter expansion within braces
+(`tt(${...})').
+)
+item(tt(-first-))(
+for adding completions before any other other completion functions are
+tried (similar to the `tt(-T)' flag of tt(compctl)); if this
+function sets the tt(_compskip) parameter to any value, the completion
+system will not call any other function to generate matches.
+)
+item(tt(-default-))(
+for generating completions when no special completion function is used
+(similar to the `tt(-D)' option of tt(compctl)).
+)
+item(tt(-command-))(
+for completing in a command position (as with the `tt(-C)' option of
+tt(compctl)).
+)
+enditem()
+
+Default implementations are supplied for each of these
+contexts, in most cases named after the context itself
+(e.g. completion for the `tt(-tilde-)' context is done by the function
+named `tt(_tilde)').
+)
+item(tt(_approximate))(
+This completer function uses the tt(_complete) completer to generate
+a list of strings for the context the cursor is currently in, allowing
+you to specify a maximum number of errors: see the description of
+approximate matching in
+ifzman(\
+zmanref(zshexpn)
+)\
+ifnzman(\
+noderef(Filename Generation)
+)\
+for how errors are
+counted. The resulting list of corrected and completed strings is then
+presented to the user. The intended use of this completer function is to
+try after the normal tt(_complete) completer by setting:
+
+indent(
+nofill(tt(compconf completer=_complete:_approximate))
+)
+
+This will give correcting completion if and only if
+normal completion doesn't yield any possible completions. When
+corrected completions are found, the completer will normally start
+menucompletion allowing you to cycle through these strings.
+
+The exact behavior of this completer can be changed by using the
+following configuration keys:
+
+startitem()
+item(tt(approximate_accept))(
+This should be set to the number of errors the correction code should
+accept. The completer will try to generate completions by first allowing
+one error, then two errors, and so on, until either a match
+was found or the maximum number of errors given by this key has
+been reached.
+
+If the value for this key contains a lower- or upper-case `tt(n)', the
+completer function will take any numeric argument as the
+maximum number of errors allowed. For example, with
+
+indent(
+nofill(tt(compconf approximate_accept=2n))
+)
+
+two errors will be allowed if no numeric argument is given. However,
+with a numeric argument of six (as in `tt(ESC-6 TAB)'), up to six
+errors are accepted. Hence with a value of `tt(0n)', no correcting
+completion will be attempted unless a numeric argument is given.
+
+If the value contains `tt(n)' or `tt(N)' and a exclamation mark
+(`tt(!)'), tt(_approximate) will var(not) try to generate corrected
+completions when given a numeric argument, so in this case the number given
+should be greater than zero. For example, `tt(2n!)' specifies that
+correcting completion with two errors will usually be performed, but if a
+numeric argument is given, correcting completion will not be performed.
+)
+item(tt(approximate_original))(
+This key is used to specify whether the original string on which correcting
+completion was attempted is to be included in the list of possible
+corrections. If it is set to any non-empty string, the original string
+will be offered when cycling through the completions. Normally it will
+appear as the first string, so that the command line does not change
+immediately; consecutive completion attempts will cycle through the
+corrected strings. If the value for this key contains the substring
+`tt(last)', the original string will be the last one in the list, so
+that it appears just before wrapping around to the first corrected
+string again. Also, if the value contains the substring `tt(always)',
+the original string will always be included; normally it is
+included only if more than one possible correction was generated.
+)
+item(tt(approximate_prompt))(
+This can be set to a string to be displayed on top of the
+corrected strings generated when cycling through them. This string
+may contain the control sequences `tt(%n)', `tt(%B)', etc. known from
+the `tt(-X)' option of tt(compctl). Also, the sequence `tt(%e)' will
+be replaced by the number of errors accepted to generate the corrected
+strings.
+)
+item(tt(approximate_insert))(
+If this is set to a string starting with `tt(unambig)', the code will try
+to insert a usable unambiguous string in the command line instead of
+always cycling through the corrected strings. If such a unambiguous
+string could be found, the original string is not used, independent of
+the setting of tt(approximate_original). If no sensible string could be
+found, one can cycle through the corrected strings as usual.
+)
+enditem()
+
+If any of these keys is not set, but the the same key with the prefix
+`tt(correct)' instead of `tt(approximate)' is set, that value will be
+used. The forms beginning with `tt(correct)' are also used by the
+tt(_correct) completer function.
+
+The keys with the `tt(approximate)' prefix have no default values, but
+tt(compinit) defines default values for tt(correct_accept) (which
+is set to `tt(2n)'), and tt(correct_prompt).
+)
+item(tt(_correct))(
+Generate corrections (but not completions) for the current word; this is
+similar to spell-checking. This calls tt(_approximate), but only the
+configuration parameters beginning tt(correct_) are used.
+
+For example, with:
+
+indent(tt(
+nofill(compconf completer=_complete:_correct:_approximate)
+nofill(compconf correct_accept='2n!' approximate_accept=3n))
+)
+
+correction will accept up to two errors. If a numeric argument is
+given, correction will not be performed, but correcting completion will be,
+and will accept as many errors as given by the numeric argument.
+Without a numeric argument, first correction and then correcting
+completion will be tried, with the first one accepting two errors
+and the second one accepting three errors.
+
+This completer function is intended to be used without the
+tt(_approximate) completer or, as in the example, just before
+it. Using it after the tt(_approximate) completer is useless since
+tt(_approximate) will at least generate the corrected strings
+generated by the tt(_correct) completer -- and probably more.
+)
+item(tt(_match))(
+This completer is intended to be used after the tt(_complete)
+completer. It allows one to give patterns on the command line and
+to complete all strings metching these patterns from the set of possible
+completions for the context the cursor is in, without having to set
+the tt(GLOB_COMPLETE) option.
+
+Normally this will be done by taking the pattern from the line,
+inserting a `tt(*)' at the cursor position and comparing the resulting
+pattern with the possible completions generated. However, if the
+configuration key tt(match_original) has a value of `tt(only)', no
+`tt(*)' will be inserted. If tt(match_original) has any other non-empty
+string as its value, this completer will first try to generate matches
+without, then with a `tt(*)' inserted at the cursor position.
+)
+item(tt(_expand))(
+This completer function does not really do completion, but instead
+checks if the word on the command line is eligible for expansion and,
+if it is, gives detailed control over how this expansion is done. When
+using this, one should not use the tt(expand-or-complete) widget, but
+instead use tt(complete-word), as otherwise tt(expand-or-complete)
+will expand the string on the line before the completion widget is
+called. Also, this completer should be called before the tt(_complete)
+completer function.
+
+Control over how the expanded string will be treated is possible with the
+following configuration keys:
+
+startitem()
+item(tt(expand_substitute))(
+If this is unset or set to the empty string, the code will first try
+to expand all substitutions in the string (such as
+`tt($LPAR()...RPAR())' and `tt(${...})'). If this is set to an
+non-empty string it should be an expression usable inside a `tt($((...)))'
+arithmetical expression. In this case, expansion of substitutions will
+be done if the expression evaluates to `tt(1)'. For example, with
+
+indent(
+nofill(tt(compconf expand_substitute='NUMERIC != 1'))
+)
+
+substitution will be performed only if given an explicit numeric
+argument other than `tt(1)', as by typing `tt(ESC 2 TAB)'.
+)
+item(tt(expand_glob))(
+If this is unset or set to an empty string, globbing will be attempted
+on the word resulting from substitution or the original string. The
+values accepted for this key are the same as for tt(expand_substitute).
+)
+item(tt(expand_menu))(
+If this is unset or set to the empty string, the words resulting from
+expansion (if any) will simply be inserted in the command line,
+replacing the original string. However, if this key is set to a
+non-empty string, the user can cycle through the expansion as in
+menucompletion. Unless the value contains the substring `tt(only)',
+the user will still be offered all expansions at once as one of the
+strings to insert in the command line; normally, this possibility is
+offered first, but if the value contains the
+substring `tt(last)', it is offered last. Finally, if the value contains
+the substring `tt(sort)', the expansions will be sorted alphabetically,
+normally they are kept in the order the expansion produced them in.
+)
+item(tt(expand_original))(
+If this is set to an non-empty string, the original string from the
+line will be included in the list of strings the user can cycle
+through as in a menucompletion. If the value contains the substring
+`tt(last)', the original string will appear as the last string, with
+other values it is inserted as the first one (so that the command line
+does not change immediately).
+)
+item(tt(expand_prompt))(
+This may be set to a string that should be displayed before the
+possible expansions. This is passed to the `tt(-X)' option of
+tt(compadd) and thus may contain the control sequences `tt(%n)',
+`tt(%B)', etc. Also, the sequence `tt(%o)' in this string will be
+replaced by the original string.
+)
+enditem()
+
+None of these configuration keys has a default value.
+)
+item(tt(_list))(
+This completer allows one to delay the insertion of matches until
+completion is attempted a second time without the word on the line
+being changed. On the first attempt, only the list of matches will be
+shown. Configuration keys understood are:
+
+startitem()
+item(tt(list_condition))(
+If this key is unset or set to the empty string, the insertion of
+matches will be delayed unconditionally. If this value is set, it
+should be set to an expression usable inside a `tt($((...)))'
+arithmetical expression. In this case, delaying will be done if the
+expression evaluates to `tt(1)'. For example, with
+
+indent(
+nofill(tt(compconf list_condition='NUMERIC != 1'))
+)
+
+delaying will be done only if given an explicit numeric argument
+other than `tt(1)'.
+)
+item(tt(list_word))(
+To find out if listing should be performed on its own, the code normally
+compares the contents of the line with the contents the line had at the
+time of the last invocation. If this key is set to an non-empty string,
+comparison is done using only the current word. So if it is set,
+attempting completion on a word equal to the one when completion was called
+the last time will not delay the generation of matches.
+)
+enditem()
+)
+item(tt(_menu))(
+This completer is a simple example function implemented to show how
+menucompletion can be done in shell code. It should be used as the
+first completer and has the effect of making the code perform
+menucompletion. Note that this is independent of the setting of the
+tt(MENU_COMPLETE) option and does not work with the other
+menucompletion widgets such as tt(reverse-menu-complete), or
+tt(accept-and-menu-complete).
+)
+enditem()
+
+texinode(Completion Functions)()(Control Functions)(Completion System)
+sect(Utility Functions)
+
+Descriptions follow for utility functions that may be
+useful when writing completion functions. Most of these reside in the
+tt(Core) subdirectory except where noted. Like the example
+functions for commands in the distribution, the utility functions
+generating matches all follow the convention of returning zero if they
+generated completions and non-zero if no matching completions could be
+added.
+
+startitem()
+item(tt(_compalso))(
+This function looks up the definitions for the context and command
+names given as arguments and calls the handler functions for them if
+there is a definition (given with the tt(compdef) function). For
+example, the function completing inside subscripts might use
+`tt(_compalso -math-)' to include the completions generated for
+mathematical environments.
+)
+item(tt(_normal))(
+This function is used for normal command completion. If
+completion is attempted on the first word, command names are
+completed. Otherwise, the arguments are completed by calling the
+functions defined for this command, including those functions defined
+for patterns matching the command name. This function can also be
+called by other completion functions if they have to complete a range
+of words as a separate command. For example, the function to complete after
+the pre-command specifiers such as tt(nohup) removes the first word from
+the tt(words) array, decrements the tt(CURRENT) parameter, then calls this
+function.
+
+When calling a function defined for a pattern, this function also
+checks if the parameter tt(_compskip) is set. If it was set by the
+function called, no further completion functions are called. With this
+one can write a pattern completion function that keeps other functions
+from being tried simply by setting this parameter to any value.
+)
+item(tt(_multi_parts))(
+This functions gets two arguments: a separator character and an
+array. As usual, the array may be either the
+name of an array parameter or a literal array in the form
+`tt(LPAR()foo bar)tt(RPAR())' (i.e. a list of words separated by white
+space in parentheses). With these arguments, this function will
+complete to strings from the array where the parts separated by the
+separator character are completed independently. For example, the
+tt(_tar) function from the distribution caches the pathnames from the
+tar file in an array and then calls this function to complete these
+names in the way normal filenames are completed by the
+tt(_path_files) function.
+
+Like other utility functions, this function accepts the `tt(-V)',
+`tt(-J)', and `tt(-X)' options with an argument and passes them to the
+tt(compadd) builtin.
+)
+item(tt(_sep_parts))(
+This function gets as arguments alternating arrays and separators.
+The arrays specify completions for parts of strings to be separated by the
+separators. The arrays may be the names of array parameters or
+a quoted list of words in parentheses. For example, with the array
+`tt(hosts=(ftp news))' the call `tt(_sep_parts '(foo bar)' @ hosts)' will
+complete the string `tt(f)' to `tt(foo)' and the string `tt(b@n)' to
+`tt(bar@news)'.
+
+This function passes the `tt(-V)', `tt(-J)', and `tt(-X)' options and
+their arguments to the tt(compadd) builtin used to add the matches.
+)
+item(tt(_path_files) and tt(_files))(
+The function tt(_path_files) is used throughout the shell code
+to complete filenames. The advantage over the builtin
+completion functions is that it allows completion of partial paths. For
+example, the string `tt(/u/i/s/sig)' may be completed to
+`tt(/usr/include/sys/signal.h)'. The options `tt(-/)', `tt(-f)', `tt(-g)',
+and `tt(-W)' are available as for the tt(compctl)
+and tt(compgen) builtins; tt(-f) is the default. Additionally, the `tt(-F)'
+option from the tt(compadd) builtin is supported, giving direct control
+over which filenames should be ignored as done by the tt(fignore)
+parameter in normal completion.
+
+The function tt(_files) calls tt(_path_files) with all the arguments
+it was passed and, if that generated no matches, call tt(_path_files) again
+without any tt(-g) or tt(-/) option, thus generating all filenames.
+
+These functions also accept the `tt(-J)', `tt(-V)', `tt(-X)', `tt(-P)',
+`tt(-S)', `tt(-q)', `tt(-r)', and `tt(-R)' options from the
+tt(compadd) builtin.
+
+Finally, the tt(_path_files) function supports one configuration key:
+tt(path_expand). If this is set to any non-empty string, the partially
+typed path from the line will be expanded as far as possible even if
+trailing pathname components can not be completed.
+)
+item(tt(_parameters))(
+This should be used to complete parameter names if you need some of the
+extra options of tt(compadd). It first tries to complete only non-local
+parameters. All arguments are passed unchanged to the tt(compadd) builtin.
+)
+item(tt(_options))(
+This can be used to complete option names. The difference to the
+`tt(-o)' option of tt(compgen) is that this function uses a matching
+specification that ignores a leading `tt(no)', ignores underscores and
+allows the user to type upper-case letters, making them match their
+lower-case counterparts. All arguments passed to this function are
+propagated unchanged to the tt(compgen) builtin.
+)
+item(tt(_set_options) and tt(_unset_options))(
+These functions complete only set or unset options, with the same
+matching specification used in the tt(_options) function.
+
+Note that you need to uncomment a few lines in the tt(_main_complete)
+function for these functions to work properly. The lines in question
+are used to store the option settings in effect before the completion
+widget locally sets the options it needs.
+)
+item(tt(_long_options))(
+This function resides in the tt(Base) subdirectory of the example
+completion system because it is not used by the core system.
+
+This function is used to complete long options for commands that
+support the `tt(--help)' option as, for example, most of the GNU
+commands do. For this it invokes the command from the line with the
+`tt(--help)' option and then parses the output to find possible option
+names. Note that this means that you should be careful to make sure
+that this function is not called for a command that does not support
+this option.
+
+For options that get an argument after a `tt(=)', the function also
+automatically tries to find out what should be completed as the argument.
+The possible completions for option-arguments can be described with
+the arguments to this function. This is done by giving pairs of
+patterns and actions as consecutive arguments. The actions specify
+what should be done to complete arguments of those options whose
+description match the pattern. The action may be a list of words in
+brackets or in parentheses, separated by spaces. A list in square brackets
+denotes possible values for an optional argument, a list in parentheses
+gives words to complete for mandatory arguments. If the action does
+not start with a square bracket or parenthesis, it should be the name of a
+command (probably with arguments) that should be invoked to complete
+after the equal sign. Example:
+
+indent(
+nofill(tt(_long_options '*\*' '(yes no)' \))
+nofill(tt( '*=FILE*' '_files' \))
+nofill(tt( '*=DIR*' '_files -/'))
+)
+
+Here, `tt(yes)' and `tt(no)' will be completed as the argument of
+options whose description ends in a star, file names for options that
+contain the substring `tt(=FILE)' in the description, and paths for
+options whose description contains `tt(=DIR)'. In fact, the last two
+patterns are not needed since this function always completes files
+for option descriptions containing `tt(=FILE)' and paths for option
+descriptions that contain `tt(=DIR)' or `tt(=PATH)'. These builtin
+patterns can be overridden by patterns given as arguments, however.
+
+This function also accepts the `tt(-X)', `tt(-J)', and `tt(-V)'
+options which are passed unchanged to `tt(compadd)'. Finally, it
+accepts the option `tt(-t)'; if this is given, completion is only done
+on words starting with two hyphens.
+)
+enditem()
diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo
new file mode 100644
index 000000000..2cb12e2c2
--- /dev/null
+++ b/Doc/Zsh/compwid.yo
@@ -0,0 +1,428 @@
+texinode(Completion Widgets)(Zsh Modules)(Programmable Completion)(Top)
+chapter(Completion Widgets)
+cindex(completion, widgets)
+cindex(completion, programmable)
+cindex(completion, controlling)
+sect(Description)
+Completion widgets are defined using the tt(-C) option to the tt(zle)
+builtin command provided by the tt(zle) module (see
+ifzman(zmanref(zshzle))\
+ifnzman(noderef(The zle Module))\
+). For example, the invocation:
+
+indent(nofill(
+tt(zle -C complete expand-or-complete completer)))
+
+defines a widget named tt(complete). If this widget is bound to a key
+using the tt(bindkey) builtin command defined in the tt(zle) module
+(see
+ifzman(zmanref(zshzle))\
+ifnzman(noderef(Zsh Line Editor))\
+) typing that key will make the completion code call the shell
+function tt(completer). This function is responsible for generating
+the possible matches using the builtins described below. Once the
+function returns, the completion code takes over control again and
+treats the matches the way the builtin widget tt(expand-or-complete)
+would do it. For this second argument, the name of any of the builtin
+widgets that handle completions can be given, i.e. it may be any of
+tt(complete-word), tt(expand-or-complete),
+tt(expand-or-complete-prefix), tt(menu-complete),
+tt(menu-expand-or-complete), tt(reverse-menu-complete),
+tt(list-choices), or tt(delete-char-or-list).
+
+startmenu()
+menu(Special Parameters)
+menu(Builtin Commands)
+menu(Condition Codes)
+menu(Examples)
+endmenu()
+
+texinode(Special Parameters)(Builtin Commands)()(Completion Widgets)
+sect(Special Parameters)
+
+Inside completion widgets some parameters have special meaning. They
+will be used inside the widget function and other shell functions
+called from it. Outside of these function they are not special to the
+shell in any way.
+
+The parameters are used to give information about the internal state
+from the completion code to the completion widget and can be set to
+give information to the completion code from the completion
+widget. Some of the builtin commands and the condition codes use or
+change the current values of these parameters. While the completion
+widget is active, these parameters are reseton each function exit to
+the values they had when the function was entered.
+
+startitem()
+item(tt(argv))(
+The positional parameters are set to the arguments on the command line
+when the widget function is invoked from the completion code.
+)
+item(tt(CURRENT))(
+This is the number of the current word, i.e. the word the cursor is
+currently on in the tt(argv) array.
+)
+item(tt(CONTEXT))(
+This will be set by the completion code to the overall context
+completion is attempted in. Possible values are:
+
+startitem()
+item(tt(command))(
+when completing in a command position, e.g. in the first word on the
+command line
+)
+item(tt(argument))(
+when completing an argument for a command
+)
+item(tt(redirect))(
+when completing after a redirection operator; in this case the
+positional parameters contain not only the arguments but also the
+command name itself as the first element
+)
+item(tt(condition))(
+when completing inside a `tt([[)...tt(]])' conditional expressing; in
+this case the positional parameters are set to the words inside the
+conditional expressions
+)
+item(tt(math))(
+when completing in a mathematical environment such as a
+`tt(LPAR()LPAR())...tt(RPAR()RPAR())' construct
+)
+item(tt(value))(
+when completing the value of a parameter assignment; in case of an
+array value the positional parameters are set to the words in
+parentheses
+)
+item(tt(subscript))(
+when completing inside a parameter expansion subscript
+)
+enditem()
+)
+item(tt(COMMAND))(
+In most cases this is set to name of the command for which completion
+is tried. When completing after a redirection operator it contains the
+string forming that operator. Also, when completing in the value of a
+parameter assignment or in a parameter subscript it is set to the name
+of the parameter.
+)
+item(tt(PREFIX))(
+This should be set to that part of the current word that should be
+taken as the string every possible match has to begin with. Initially
+this will be set to the part of the current word from the beginning of
+the word up to the position of the cursor. When
+)
+item(tt(IPREFIX))(
+When a part of the current word should not be considered part of the
+matches, this part should be taken from the tt(PREFIX) parameter and
+appended to this parameter. This will initially be set to the empty
+string when called from the completion code.
+)
+item(tt(SUFFIX))(
+This should be set to that part of the current word that should be
+taken as the string every possible match has to end with. The
+completion code sets this to the part of the current word from the
+cursor position to the end.
+)
+item(tt(NMATCHES))(
+This is always set to the number of matches generated and accepted by
+the completion code so far.
+)
+item(tt(MATCHER))(
+When completion is used with a global match specification (i.e. a
+tt(compctl) with only a tt(-M) option), this parameter is set to the
+number of the specification string which is currently used.
+)
+enditem()
+
+texinode(Builtin Commands)(Condition Codes)(Special Parameters)(Completion Widgets)
+sect(Builtin Commands)
+startitem()
+findex(complist)
+item(tt(complist) var(flags ...))(
+
+Generate matches according to the given var(flags) which can be any of
+the option flags supported by the tt(compctl) builtin command (see
+ifzman(zmanref(zshcompctl))\
+ifnzman(noderef(Programmable Completion))\
+) except for the tt(-t) and tt(-l) flags. Also, when using the tt(-K)
+flag, the function given as argument to it can not access the command
+line with the tt(read) builtin command.
+
+The matches will be generated in the same way as if the completion code
+generated them directly from a tt(compctl)-definition with the same
+flags. The completion code will consider only those matches as
+possible completions that match the prefix and suffix from the special
+parameters desribed above. These strings will be compared with the
+generated matches using the normal matching rules and any matching
+specifications given with the tt(-M) flag to tt(complist) and the
+global matching specifications given to the tt(compctl) builtin
+command.
+)
+xitem(tt(compadd) [ tt(-qQfnUam) ] [ tt(-F) var(array) ])
+xitem([ tt(-P) var(prefix) ] [ tt(-S) var(suffix) ])
+xitem([ tt(-p) var(hidden-prefix) ] [ tt(-s) var(hidden-suffix) ])
+xitem([ tt(-i) var(ignored-prefix) ] [ tt(-W) var(file-prefix) ])
+xitem([ tt(-J) var(name) ] [ tt(-V) var(name) ])
+xitem([ tt(-r) var(remove-chars) ] [ tt(-R) var(remove-func) ])
+item([ tt(-M) var(match-spec) ] [ tt(--) ] [ var(words) ... ])(
+
+This builtin command can be used to add matches and directly control
+all the information the completion code stores with each possible
+match.
+
+The supported flags are:
+
+startitem()
+item(tt(-P) var(prefix))(
+The same as for tt(compctl) and tt(complist), it gives a string that
+should be inserted before the given words when they are completed. The
+string given is not considered to be part of the match.
+)
+item(tt(-S) var(suffix))(
+Like tt(-P) but gives a string that has to be inserted after the match.
+)
+item(tt(-p) var(hidden-prefix))(
+This gives a string that should be
+...
+)
+item(tt(-s) var(hidden-suffix))(
+...
+)
+item(tt(-i) var(ignored-prefix))(
+...
+)
+item(tt(-J) var(name))(
+As for tt(compctl) and tt(complist) this gives the name of the group
+of matches the words should be stored in.
+)
+item(tt(-V) var(name))(
+Like tt(-J) but naming a unsorted group.
+)
+item(tt(-q))(
+This flag has the same meaning as for tt(compctl) and tt(complist),
+too. It makes the suffix given with tt(-S) be automatically removed if
+the next character typed is a blank or does not insert anything or if
+the suffix consists of only one character and the next character typed
+is the same character.
+)
+item(tt(-r) var(remove-chars))(
+This makes the suffix given with tt(-S) be automatically removed if
+the next character typed inserts one of the characters given in the
+var(remove-chars). This string is parsed as a characters class with
+the usual backslash-sequences understood, e.g. using `tt(-r "a-z\t")'
+removes the suffix if the next character typed inserts one of the
+lower case letters or a TAB, and `tt(-r "^0-9")' removes the suffix if
+the next character typed inserts anything but a digit. One extra
+backslash sequence is understood in this string: `tt(\-)' stands for
+all characters that insert nothing. Thus `tt(-S "=" -q)' is the same
+as `tt(-S "=" -r "= \t\n\-")'.
+)
+item(tt(-R) var(remove-func))(
+For the cases where one wants to remove suffix and the tt(-r) option
+does not give enough control, this option can be used. It stores the
+name of the shell function var(remove-func) in the matches. If one of
+the matches is finally accepted and the tt(-S)-suffix inserted, this
+function will be called after the next character typed. It gets the
+length of the suffix as its argument and can use the special
+parameters available in zle widgets (see
+ifzman(zmanref(zshzle))\
+ifnzman(noderef(Zsh Line Editor))\
+) to analyse and modify the command line.
+)
+item(tt(-f))(
+If this flag is given, the matches build are marked as being the names
+of files. They need not be actual filenames, though. But if they are
+and the option tt(LIST_TYPES) is set, the characters describing the
+types of the files in the completion lists will be shown. This also
+makes a slash automatically be added when the name of a directory is
+completed.
+)
+item(tt(-W) var(file-prefix))(
+This option has the same meaning as for the tt(compctl) and
+tt(complist) builtin commands. Here, however, only one string may be
+given, not an array. This string is used as a pathname that will be
+prepended to the given words and the prefix given with the tt(-p)
+option to perform the file-tests when showing completion
+listings. Hence it is only useful if combined with the tt(-f) flag,
+since the tests will only be performed if that flag is given.
+)
+item(tt(-a))(
+When used by tt(compctl) or tt(complist) the completion code normally
+builds two sets of matches: the normal one where words with one of the
+suffixes in the array parameter tt(fignore) are not considered
+possible matches, and the alternate set where the words excluded
+from the first set are stored. Normally only the matches in the first
+set are used. But if this set is empty, the words from the alternate
+set are used.
+
+The tt(compadd) builtin does not use tt(fignore) parameter and
+normally stores all words in the first set. With the tt(-a)-flag
+given, however, they are all stored in the alternate set unless this
+flag is overridden by the tt(-F) option.
+)
+item(tt(-F) var(array))(
+This can be used to give an array containing suffixes like the
+tt(fignore) parameter. Words with one of these suffixes are stored in
+the alternate set of matches and words without one of these suffixes
+are stored in the normal set.
+
+The var(array) may be the name of an array parameter or a list of
+literal suffixes enclosed in parentheses as in `tt(-F "(.o .h)")'. If
+the name of an array is given, the elements of the array are taken as
+the suffixes.
+)
+item(tt(-Q))(
+As for tt(compctl) and tt(complist) this flag instructs the completion
+code not to quote any metacharacters in the words when inserting them
+in the command line.
+)
+item(tt(-m))(
+Normally the matches added by tt(compadd) will not be compared with
+what is already on the line. If this flag is given, this comparison is
+performed as usual and the match specifications given with the tt(-M)
+option to tt(compadd) and the global match specifications defined with
+tt(compctl) will be used. This means that probably not all the word
+given will be stored as matches since some of them may not match the
+string on the line.
+)
+item(tt(-M) var(match-spec))(
+This option allows one to give local match specifications with the
+same meaning and format as for the tt(compctl) and tt(complist)
+builtin commands. Note that they will only be used if the tt(-m) is
+given, too.
+)
+item(tt(-n))(
+Words added with tt(compadd) with this flag will be used as possible
+matches as usual but they not appear in the completion listing.
+)
+item(tt(-U))(
+If this flag is given to one of the calls to tt(compadd) and the
+option tt(AUTO_MENU) is set, the completion code will immediatly
+switch to menucompletion.
+)
+item(tt(-), tt(--))(
+This flag ends the list of flags and options. All arguments after it
+will be taken as the words to use as matches even if they begin with
+hyphens.
+)
+enditem()
+)
+item(tt(compcall) [ tt(-TD) ])(
+
+This allows one to use completion definitions given with the
+tt(compctl) builtin from within completion widgets. It makes
+completion code complete the current word according to the
+tt(compctl)s defined. Normally only tt(compctl)s given for specific
+commands are used. To make the code use the completion flags given to
+the tt(-T) option of tt(compctl), one can give the tt(-T) flag to
+tt(compctl). Likewise, the tt(-D) flag to tt(compcall) makes the
+default completion flags given to tt(compctl) with the tt(-D) option
+be used.
+)
+enditem()
+
+texinode(Condition Codes)(Examples)(Builtin Commands)(Completion Widgets)
+sect(Condition Codes)
+
+Inside completion widgets not only the builtin commands described
+above can be used, but also some additional condition codes. These
+work on the special parameters and can be used to easily build
+completion functions that generate different matches depending on the
+strings on the line.
+
+The following condition codes are made available inside completion
+widgets:
+
+startitem()
+item(tt(-prefix) var(string))(
+true if the content of tt(PREFIX) starts with var(string)
+)
+item(tt(-iprefix) var(string))(
+like tt(-prefix), but the var(string) is removed from tt(PREFIX) and
+added to tt(IPREFIX)
+)
+item(tt(-position) var(beg) [ var(end) ])(
+true if tt(CURRENT) is equal to var(beg) or, if var(end) is given,
+equal to or greater than var(beg) and equal to or less than var(end);
+both of var(beg) and var(end) may be arithmetic expressions, if they
+are less than zero the number of words in tt(argv) are added to them
+before comparing them to tt(CURRENT); thus, tt(-1) is the last word,
+tt(-2) is the word before that and so on
+)
+item(tt(-word) var(index) var(string))(
+true if the word number var(index) in tt(argv) is equal to
+var(string); again, var(index) may be negative, counting backwards
+)
+item(tt(-mword) var(index) var(pattern))(
+like tt(-word) but using pattern matching
+)
+item(tt(-current) var(offset) var(string))(
+like tt(-word) but var(offset) is relative to the value of
+tt(CURRENT)
+)
+item(tt(-mcurrent) var(offset) var(pattern))(
+like tt(-current) but using pattern matching
+)
+item(tt(-string) [ var(number) ] var(string))(
+true if the current word contains var(string); anything up to the last
+occurrence of this string will be ingnored by removing it from
+tt(PREFIX) and adding it to tt(IPREFIX); if var(number) is given,
+anything up to the var(number)'th occurrence of the var(string) will
+be ignored; again, var(nmuber) may be any arithmetic expression and
+negative values count backward
+)
+item(tt(-class) [ var(number) ] var(class))(
+like tt(-string) but the var(class) is used as a character class so
+that anything up to and including the last or the var(number)'th
+occurrence of any character from the string var(class) is ignored
+)
+item(tt(-words) var(min) [ var(max) ])(
+true if the number of words is equal to var(min); if var(max) is
+given, it is true if the number of words is equal to or greater than
+var(min) and equal to or less than var(max)
+)
+item(tt(-after) var(string))(
+true if the cursor is after a word that is equal to var(string)
+)
+item(tt(-mafter) var(pattern))(
+like tt(-after) but using pattern matching
+)
+item(tt(-between) var(string1) var(string2))(
+true if the cursor is after a word that is equal to var(string1), if
+there is also a word that is equal to va(string2), this is true only
+if the cursor is before it
+)
+item(tt(-mbetween) var(pattern1) var(pattern2))(
+like tt(-between) but using pattern matching
+)
+item(tt(-nmatches) var(number))(
+true if the the value of tt(NMATCHES) is equal to var(number)
+)
+item(tt(-matcher) var(number))(
+true if the value of tt(MATCHER) is equal to var(number)
+)
+enditem()
+
+texinode(Examples)()(Condition Codes)(Completion Widgets)
+sect(Examples)
+
+The first step is to define the widget:
+
+indent(nofill(
+tt(zle -C complete complete-word complete-history)))
+
+Then the widget can be bound to a key using the tt(bindkey) builtin
+command:
+
+indent(nofill(
+tt(bindkey '^X\t' complete)))
+
+After that the shell function tt(complete-history) will be invoked
+after typing control-X and TAB. The function should then generte the
+matches, e.g.:
+
+indent(nofill(
+tt(complete-history LPAR()RPAR() { complist -H 0 '' })))
+
+In this the function will complete words from the history matching the
+current word.
diff --git a/Doc/zshcompsys.yo b/Doc/zshcompsys.yo
new file mode 100644
index 000000000..d640606b6
--- /dev/null
+++ b/Doc/zshcompsys.yo
@@ -0,0 +1,3 @@
+manpage(ZSHCOMPSYS)(1)(date())(zsh version())
+manpagename(zshcompsys)(zsh completion system)
+includefile(Zsh/compsys.yo)
diff --git a/Doc/zshcompwid.yo b/Doc/zshcompwid.yo
new file mode 100644
index 000000000..d876e422d
--- /dev/null
+++ b/Doc/zshcompwid.yo
@@ -0,0 +1,3 @@
+manpage(ZSHCOMPWID)(1)(date())(zsh version())
+manpagename(zshcompwid)(zsh completion widgets)
+includefile(Zsh/compwid.yo)
diff --git a/Functions/Completion/.distfiles b/Functions/Completion/.distfiles
new file mode 100644
index 000000000..12621da29
--- /dev/null
+++ b/Functions/Completion/.distfiles
@@ -0,0 +1,11 @@
+DISTFILES_SRC='
+ .distfiles
+ __aliases __arrays __autoload __bg_jobs __bindkey __builtin __cd __command
+ __command_names __compress __condition __configure __dd __default __dirs
+ __disable __dvi __echotc __enable __fc __files __find __functions __gunzip
+ __gzip __hash __hosts __jobs __kill __kill_helper __limits __main_complete
+ __main_key_complete __make __man __mh __most_recent_file __normal __path_files
+ __pdf __precommand __ps __rcs __rlogin __sched __set __setopt __source __strip
+ __stty __subscript __tar __tex __trap __uncompress __unhash __unsetopt __vars
+ __vars_eq __wait __which __x_options __xfig __zle __zmodload init
+'
diff --git a/Functions/Completion/__aliases b/Functions/Completion/__aliases
new file mode 100644
index 000000000..cbf204e51
--- /dev/null
+++ b/Functions/Completion/__aliases
@@ -0,0 +1,2 @@
+#array unalias
+__aliases=(-a)
diff --git a/Functions/Completion/__arrays b/Functions/Completion/__arrays
new file mode 100644
index 000000000..3f6ec8abe
--- /dev/null
+++ b/Functions/Completion/__arrays
@@ -0,0 +1,2 @@
+#array shift
+__arrays=(-A)
diff --git a/Functions/Completion/__autoload b/Functions/Completion/__autoload
new file mode 100644
index 000000000..9c3bb1f4f
--- /dev/null
+++ b/Functions/Completion/__autoload
@@ -0,0 +1,2 @@
+#array autoload
+__autoload=(-s '${^fpath}/*(N:t)')
diff --git a/Functions/Completion/__bg_jobs b/Functions/Completion/__bg_jobs
new file mode 100644
index 000000000..90e3b7ee0
--- /dev/null
+++ b/Functions/Completion/__bg_jobs
@@ -0,0 +1,2 @@
+#array bg
+__bg_jobs=(-z -P '%')
diff --git a/Functions/Completion/__bindkey b/Functions/Completion/__bindkey
new file mode 100644
index 000000000..c9005347a
--- /dev/null
+++ b/Functions/Completion/__bindkey
@@ -0,0 +1,7 @@
+#function bindkey
+
+if [[ -mword 1 -*[DAN]* || -mcurrent -1 -*M ]]; then
+ complist -s '$(bindkey -l)'
+else
+ complist -b
+fi
diff --git a/Functions/Completion/__builtin b/Functions/Completion/__builtin
new file mode 100644
index 000000000..5b4c777f5
--- /dev/null
+++ b/Functions/Completion/__builtin
@@ -0,0 +1,7 @@
+#function builtin
+
+if [[ -position 2 -1 ]]; then
+ compsub
+else
+ complist -eB
+fi
diff --git a/Functions/Completion/__cd b/Functions/Completion/__cd
new file mode 100644
index 000000000..ad5e36386
--- /dev/null
+++ b/Functions/Completion/__cd
@@ -0,0 +1,3 @@
+#function cd
+
+__files -W cdpath -g '*(-/)'
diff --git a/Functions/Completion/__command b/Functions/Completion/__command
new file mode 100644
index 000000000..deaabdb1f
--- /dev/null
+++ b/Functions/Completion/__command
@@ -0,0 +1,7 @@
+#function command
+
+if [[ -position 2 -1 ]]; then
+ compsub
+else
+ complist -em
+fi
diff --git a/Functions/Completion/__command_names b/Functions/Completion/__command_names
new file mode 100644
index 000000000..7a28fa527
--- /dev/null
+++ b/Functions/Completion/__command_names
@@ -0,0 +1,2 @@
+#array --command--
+__command_names=(-c)
diff --git a/Functions/Completion/__compress b/Functions/Completion/__compress
new file mode 100644
index 000000000..d4e71f531
--- /dev/null
+++ b/Functions/Completion/__compress
@@ -0,0 +1,3 @@
+#function compress
+
+__files -g '*~*.Z'
diff --git a/Functions/Completion/__condition b/Functions/Completion/__condition
new file mode 100644
index 000000000..8ceb969b8
--- /dev/null
+++ b/Functions/Completion/__condition
@@ -0,0 +1,10 @@
+#function --condition--
+
+if [[ -current -1 -o ]]; then
+ complist -o -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}'
+elif [[ -current -1 -nt || -current -1 -ot || -current -1 -ef ]]; then
+ files
+else
+ files
+ complist -v
+fi
diff --git a/Functions/Completion/__configure b/Functions/Completion/__configure
new file mode 100644
index 000000000..a4d00e0f2
--- /dev/null
+++ b/Functions/Completion/__configure
@@ -0,0 +1,11 @@
+#function configure
+if [[ $PREFIX = *=* ]]; then
+ # Complete filenames after e.g. --prefix=
+ IPREFIX=${PREFIX%%=*}=
+ PREFIX=${PREFIX#*=}
+ complist -f
+else
+ # Generate a list of options from configure --help
+ complist -s '$($COMMAND --help |
+ sed -n -e '\''s/^ *\(--[-a-z0-9]*\)[ =,].*$/\1/p'\'')'
+fi
diff --git a/Functions/Completion/__dd b/Functions/Completion/__dd
new file mode 100644
index 000000000..c0e04de75
--- /dev/null
+++ b/Functions/Completion/__dd
@@ -0,0 +1,13 @@
+#function dd
+
+if [[ -iprefix conv= ]]; then
+ # If there's a comma present, ignore up to the last one. The
+ # test alone will have that effect.
+ [[ -string , ]]
+ complist -S, -q \
+ -k '(ascii ebcdic ibm block unblock lcase ucase swab noerror sync)'
+elif [[ -iprefix 'if=' || -iprefix 'of=' ]]; then
+ __files
+else
+ complist -S '=' -k '(if of ibs obs bs cbs skip files seek count conv)'
+fi
diff --git a/Functions/Completion/__default b/Functions/Completion/__default
new file mode 100644
index 000000000..cabe14a36
--- /dev/null
+++ b/Functions/Completion/__default
@@ -0,0 +1,13 @@
+#function --default--
+
+# We first try the `compctl's. This is without first (-T) and default (-D)
+# completion. If you want them add `-T' and/or `-D' to this command.
+# If there is a `compctl' for the command we are working on, we return
+# immediatly. If you want to use new style completion anyway, remove the
+# `|| return'. Also, you may want to use new style completion if the
+# `compctl' didn't produce any matches. In that case remove the `|| return'
+# and at the line `[[ -nmatches 0 ]] || return' after `compcall'.
+
+compcall || return
+
+__files
diff --git a/Functions/Completion/__dirs b/Functions/Completion/__dirs
new file mode 100644
index 000000000..a838168bc
--- /dev/null
+++ b/Functions/Completion/__dirs
@@ -0,0 +1,3 @@
+#function rmdir df du dircmp
+
+__files -/ '*(-/)'
diff --git a/Functions/Completion/__disable b/Functions/Completion/__disable
new file mode 100644
index 000000000..c56ffb7f7
--- /dev/null
+++ b/Functions/Completion/__disable
@@ -0,0 +1,6 @@
+#function disable
+
+[[ -mcurrent -1 -*a* ]] && complist -ea
+[[ -mcurrent -1 -*f* ]] && complist -eF
+[[ -mcurrent -1 -*r* ]] && complist -ew
+[[ ! -mcurrent -1 -* ]] && complist -eB
diff --git a/Functions/Completion/__dvi b/Functions/Completion/__dvi
new file mode 100644
index 000000000..873d5812a
--- /dev/null
+++ b/Functions/Completion/__dvi
@@ -0,0 +1,3 @@
+#function xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype
+
+__files -g '*.(dvi|DVI)'
diff --git a/Functions/Completion/__echotc b/Functions/Completion/__echotc
new file mode 100644
index 000000000..d9b7029c1
--- /dev/null
+++ b/Functions/Completion/__echotc
@@ -0,0 +1,2 @@
+#array echotc
+__echotc=(-k '(al dc dl do le up al bl cd ce cl cr dc dl do ho is le ma nd nl se so up)')
diff --git a/Functions/Completion/__enable b/Functions/Completion/__enable
new file mode 100644
index 000000000..b315611df
--- /dev/null
+++ b/Functions/Completion/__enable
@@ -0,0 +1,6 @@
+#function enable
+
+[[ -mcurrent -1 -*a* ]] && complist -da
+[[ -mcurrent -1 -*f* ]] && complist -dF
+[[ -mcurrent -1 -*r* ]] && complist -dw
+[[ ! -mcurrent -1 -* ]] && complist -dB
diff --git a/Functions/Completion/__fc b/Functions/Completion/__fc
new file mode 100644
index 000000000..9185312b0
--- /dev/null
+++ b/Functions/Completion/__fc
@@ -0,0 +1,7 @@
+#function fc
+
+if [[ -mcurrent -1 -*e ]]; then
+ complist -c
+elif [[ -mcurrent -1 -[ARWI]## ]]; then
+ __files
+fi
diff --git a/Functions/Completion/__files b/Functions/Completion/__files
new file mode 100644
index 000000000..dfade8c7f
--- /dev/null
+++ b/Functions/Completion/__files
@@ -0,0 +1,10 @@
+#helper
+
+# Utility function for completing files of a given type or any file.
+# In many cases you will want to call this one instead of __path_files().
+
+local nm=$NMATCHES
+
+__path_files "$@"
+
+[[ $# -ne 0 && -nmatches nm ]] && __path_files
diff --git a/Functions/Completion/__find b/Functions/Completion/__find
new file mode 100644
index 000000000..b16bb09c7
--- /dev/null
+++ b/Functions/Completion/__find
@@ -0,0 +1,24 @@
+#function find
+
+compsave
+
+if [[ -mbetween -(ok|exec) \\\; ]]; then
+ compsub
+elif [[ -iprefix - ]]; then
+ complist -s 'daystart {max,min,}depth follow noleaf version xdev \
+{a,c,}newer {a,c,m}{min,time} empty false {fs,x,}type gid inum links \
+{i,}{l,}name {no,}{user,group} path perm regex size true uid used \
+exec {f,}print{f,0,} ok prune ls'
+ compreset
+elif [[ -position 1 ]]; then
+ complist -g '. ..'
+ __files -g '(-/)'
+elif [[ -mcurrent -1 -((a|c|)newer|fprint(|0|f)) ]]; then
+ __files
+elif [[ -current -1 -fstype ]]; then
+ complist -k '(ufs 4.2 4.3 nfs tmp mfs S51K S52K)'
+elif [[ -current -1 -group ]]; then
+ complist -k groups
+elif [[ -current -1 -user ]]; then
+ complist -u
+fi
diff --git a/Functions/Completion/__functions b/Functions/Completion/__functions
new file mode 100644
index 000000000..4fd24e379
--- /dev/null
+++ b/Functions/Completion/__functions
@@ -0,0 +1,2 @@
+#array unfunction
+__functions=(-F)
diff --git a/Functions/Completion/__gunzip b/Functions/Completion/__gunzip
new file mode 100644
index 000000000..f14803c62
--- /dev/null
+++ b/Functions/Completion/__gunzip
@@ -0,0 +1,3 @@
+#function gunzip zcat
+
+__files -g '*.[gG][z]'
diff --git a/Functions/Completion/__gzip b/Functions/Completion/__gzip
new file mode 100644
index 000000000..ce4e0787a
--- /dev/null
+++ b/Functions/Completion/__gzip
@@ -0,0 +1,3 @@
+#function gzip
+
+__files -g '*~*.[gG][zZ]'
diff --git a/Functions/Completion/__hash b/Functions/Completion/__hash
new file mode 100644
index 000000000..bd43eb967
--- /dev/null
+++ b/Functions/Completion/__hash
@@ -0,0 +1,13 @@
+#function hash
+
+if [[ -mword 1 -*d* ]]; then
+ if [[ -string 1 '=' ]]; then
+ pfiles -g '*(-/)'
+ else
+ complist -n -q -S '='
+ fi
+elif [[ -string 1 '=' ]]; then
+ files -g '*(*)' '*(-/)'
+else
+ complist -m -q -S '='
+fi
diff --git a/Functions/Completion/__hosts b/Functions/Completion/__hosts
new file mode 100644
index 000000000..a91251581
--- /dev/null
+++ b/Functions/Completion/__hosts
@@ -0,0 +1,2 @@
+#array ftp ncftp ping rwho rup xping traceroute nslookup
+__hosts=(-k hosts)
diff --git a/Functions/Completion/__jobs b/Functions/Completion/__jobs
new file mode 100644
index 000000000..d3e0d7776
--- /dev/null
+++ b/Functions/Completion/__jobs
@@ -0,0 +1,2 @@
+#array fg jobs
+__jobs=(-j -P '%')
diff --git a/Functions/Completion/__kill b/Functions/Completion/__kill
new file mode 100644
index 000000000..09ba248dc
--- /dev/null
+++ b/Functions/Completion/__kill
@@ -0,0 +1,8 @@
+#function kill
+
+if [[ -iprefix '-' ]]; then
+ complist -k "($signals[1,-3])"
+else
+ complist -P '%' -j
+ complist -y __kill_helper -s '`ps 2>/dev/null | tail +2 | cut -c1-5`'
+fi
diff --git a/Functions/Completion/__kill_helper b/Functions/Completion/__kill_helper
new file mode 100644
index 000000000..e1d286938
--- /dev/null
+++ b/Functions/Completion/__kill_helper
@@ -0,0 +1,3 @@
+#helper
+
+reply=( "$(ps 2>/dev/null)" )
diff --git a/Functions/Completion/__limits b/Functions/Completion/__limits
new file mode 100644
index 000000000..e3f97155d
--- /dev/null
+++ b/Functions/Completion/__limits
@@ -0,0 +1,2 @@
+#array limit unlimit
+__limits=(-k "(${(j: :)${(f)$(limit)}%% *})")
diff --git a/Functions/Completion/__main_complete b/Functions/Completion/__main_complete
new file mode 100644
index 000000000..48f2338de
--- /dev/null
+++ b/Functions/Completion/__main_complete
@@ -0,0 +1,48 @@
+#helper
+
+# The main loop of the completion code. This is what is called when
+# completion is attempted from the command line.
+# The completion code gives us the special variables and the arguments
+# from the command line are given as positional parameters.
+
+local comp name
+
+setopt localoptions nullglob rcexpandparam globdots
+unsetopt markdirs globsubst shwordsplit nounset
+
+# An entry for `--first--' is the replacement for `compctl -T'
+# The `|| return 1' is used throughout: if a function producing matches
+# returns non-zero this is interpreted as `do not try to produce more matches'
+# (this is the replacement for `compctl -t').
+
+comp="$comps[--first--]"
+[[ -z "$comp" ]] || callcomplete comps --first-- "$@" || return 1
+
+# For arguments we use the `__normal' function called via the convenience
+# alias `compsub'.
+
+if [[ $CONTEXT == argument || $CONTEXT == command ]]; then
+ compsub
+else
+ # Let's see if we have a special completion definition for the other
+ # possible contexts.
+
+ comp=''
+
+ case $CONTEXT in
+ redirect) name=--redirect--;;
+ math) name=--math--;;
+ subscript) name=--subscript--;;
+ value) name=--value--;;
+ condition) name=--condition--;;
+ esac
+
+ # If not, we use default completion, if any.
+
+ comp="$comps[$name]"
+ if [[ -z "$comp" ]]; then
+ name=--default--
+ comp="$comps[--default--]"
+ fi
+ [[ -z "$comp" ]] || callcomplete comps "$name" "$@" || return 1
+fi
diff --git a/Functions/Completion/__main_key_complete b/Functions/Completion/__main_key_complete
new file mode 100644
index 000000000..35fa1a0a3
--- /dev/null
+++ b/Functions/Completion/__main_key_complete
@@ -0,0 +1,6 @@
+#helper
+
+# The widget name looks like `__complete_key_<num>' where <num> can be
+# used as an index into the `keycomps' array to get at the definition.
+
+callcomplete keycomps "$WIDGET" "$@"
diff --git a/Functions/Completion/__make b/Functions/Completion/__make
new file mode 100644
index 000000000..4a2c5a7c8
--- /dev/null
+++ b/Functions/Completion/__make
@@ -0,0 +1,3 @@
+#function make gmake pmake
+
+complist -s "\$(awk '/^[a-zA-Z0-9][^/ ]+:/ {print \$1}' FS=: [mM]akefile)"
diff --git a/Functions/Completion/__man b/Functions/Completion/__man
new file mode 100644
index 000000000..a0f00f58b
--- /dev/null
+++ b/Functions/Completion/__man
@@ -0,0 +1,11 @@
+#function man
+setopt localoptions rcexpandparam
+
+local rep
+if [[ $2 = (<->*|ln) ]]; then
+ rep=( $manpath/(man|cat)$2/$PREFIX*$SUFFIX.<->*(N:t:r) )
+else
+ rep=( $manpath/(man|cat)*/$PREFIX*$SUFFIX.<->*(N:t:r) )
+fi
+
+(( $#rep )) && compadd -m $rep
diff --git a/Functions/Completion/__mh b/Functions/Completion/__mh
new file mode 100644
index 000000000..2e4738e17
--- /dev/null
+++ b/Functions/Completion/__mh
@@ -0,0 +1,70 @@
+#function folder comp inc mark refile repl scan show next prev rmm pick whom mhn mhpath mhpatch
+
+# Completion for all possible MH commands.
+# Alter the following two to your own mh directory and the directory
+# where standard mh library files live. (It works anyway, but this
+# will save a little time.)
+local mymhdir=~/Mail
+local mhlib=/usr/lib/mh
+
+# To be on the safe side, check this exists and if not, get it anyway.
+[[ -d $mymhdir ]] || mymhdir=$(mhpath +)
+
+if [[ -iprefix - ]]; then
+ # get list of options, which MH commands can generate themselves
+ # awk is just too icky to use for this, sorry. send me one if
+ # you come up with it.
+ compadd -m $($COMMAND -help | perl -ne 'if (/^\s*-\(?(\S+)/) {
+ $n = $1;
+ $n =~ s/\)//g;
+ print $n =~ s/^\[([a-z]+)\]// ? "$n\n$1$n\n" : "$n\n";
+ }')
+ return
+elif [[ -iprefix '+' || -iprefix '@' || -current -1 -draftfolder ]]; then
+ # Complete folder names.
+ local mhpath
+ if [[ $IPREFIX != '@' ]]; then
+ [[ $IPREFIX = '+' ]] || IPREFIX=+
+ mhpath=$mymhdir
+ else
+ mhpath=$(mhpath)
+ fi
+
+ # painless, or what?
+ complist -W mhpath -/
+elif [[ -mcurrent -1 -(editor|(whatnow|rmm|show|more)proc) ]]; then
+ complist -c
+elif [[ -current -1 -file ]]; then
+ complist -f
+elif [[ -mcurrent -1 -(form|audit|filter) ]]; then
+ # Need some MH template file, which may be in our own MH directory
+ # or with the standard library.
+ local mhfpath
+ # This is the only place we need mhlib, so leave the test till here.
+ [[ -d $mhlib ]] || { mhlib=$(mhparam mhlproc); mhlib=$mhlib:h; }
+ mhfpath=($mymhdir $mhlib)
+
+ complist -W mhfpath -g '*(.)'
+elif [[ -mcurrent -1 -(no|)cc ]]; then
+ compadd -m all to cc me
+elif [[ -mcurrent -1 -[rw]cache ]]; then
+ compadd -m public private never ask
+else
+ # Generate sequences.
+ local foldnam folddir f
+ for f in $argv; do
+ [[ $f = [@+]* ]] && foldnam=$f
+ done
+ if [[ $foldnam = '+'* ]]; then
+ folddir=$mymhdir/${foldnam#+}
+ elif [[ $foldnam = '@'* ]]; then
+ folddir=$(mhpath)/${foldnam#@}
+ else
+ folddir=$(mhpath)
+ # leaving foldnam empty works here
+ fi
+
+ complist -s '$(mark $foldnam | awk -F: '\''{ print $1 }'\'')'
+ compadd -m reply next cur prev first last all unseen
+ complist -W folddir -g '<->'
+fi
diff --git a/Functions/Completion/__most_recent_file b/Functions/Completion/__most_recent_file
new file mode 100644
index 000000000..cf1f83282
--- /dev/null
+++ b/Functions/Completion/__most_recent_file
@@ -0,0 +1,2 @@
+#key-array expand-or-complete \C-xm
+__most_recent_file=(-g '*(om[1])')
diff --git a/Functions/Completion/__normal b/Functions/Completion/__normal
new file mode 100644
index 000000000..7750563d1
--- /dev/null
+++ b/Functions/Completion/__normal
@@ -0,0 +1,54 @@
+#helper
+
+local comp cmd1 cmd2 pat val name
+
+# Completing in command position? If not we set up `cmd1' and `cmd2' as
+# two strings we have search in the completion definition arrays (e.g.
+# a path and the last path name component).
+
+if [[ $CONTEXT == command ]]; then
+ comp="$comps[--command--]"
+ [[ -z "$comp" ]] || callcomplete comps --command-- "$@" || return 1
+ return 0
+elif [[ "$COMMAND[1]" == '=' ]]; then
+ eval cmd1\=$COMMAND
+ cmd2="$COMMAND[2,-1]"
+elif [[ "$COMMAND" == */* ]]; then
+ cmd1="$COMMAND"
+ cmd2="${COMMAND:t}"
+else
+ cmd1="$COMMAND"
+ eval cmd2=$(whence -p $COMMAND)
+fi
+
+# See if there are any matching pattern completions.
+
+if (( $#patcomps )); then
+ for i in "$patcomps[@]"; do
+ pat="${i% *}"
+ val="${i#* }"
+ if [[ "$cmd1" == $~pat || "$cmd2" == $~pat ]]; then
+ callcomplete patcomps "$pat" "$@" || return 1
+ fi
+ done
+fi
+
+# Now look up the two names in the normal completion array.
+
+name="$cmd1"
+comp="$comps[$cmd1]"
+
+if [[ -z "$comp" ]]; then
+ name="$cmd2"
+ comp="$comps[$cmd2]"
+fi
+
+# And generate the matches, probably using default completion.
+
+if [[ -z "$comp" ]]; then
+ name=--default--
+ comp="$comps[--default--]"
+fi
+[[ -z "$comp" ]] || callcomplete comps "$name" "$@" || return 1
+
+return 0
diff --git a/Functions/Completion/__path_files b/Functions/Completion/__path_files
new file mode 100644
index 000000000..f9e7c631a
--- /dev/null
+++ b/Functions/Completion/__path_files
@@ -0,0 +1,272 @@
+#helper
+
+# Utility function for in-path completion.
+# First argument should be an complist-option (e.g. -f, -/, -g). The other
+# arguments should be glob patterns, one per argument.
+#
+# E.g.: __path_files -g '*.tex' '*.texi'
+#
+# This is intended as a replacement for `complist -f', `complist -/', and
+# `complist -g ...' (but don't use it with other options).
+#
+# You may also give the `-W <spec>' option as with `compctl' and `complist',
+# but only as the first argument.
+#
+# This function also accepts an optional `-F <string>' option as its first
+# argument or just after the `-W <spec>'. This can be used to define file
+# name extension (a la `fignore'). Files with such an extension will not
+# be considered possible completions.
+#
+# This function behaves as if you have a matcher definition like:
+# compctl -M 'r:|[-.,_/]=* r:|=* m:{a-z}={A-Z} m:-=_ m:.=,' \
+# 'm:{a-z}={A-Z} l:|=* r:|=*'
+# so you may want to modify this.
+
+local nm prepaths str linepath realpath donepath patstr prepath testpath rest
+local tmp1 collect tmp2 suffixes i ignore
+
+setopt localoptions nullglob rcexpandparam globdots extendedglob
+unsetopt markdirs globsubst shwordsplit nounset
+
+# Get the optional `-W' option and its argument.
+if [[ "$1" = -W ]]; then
+ tmp1="$2"
+ if [[ "$tmp1[1]" = '(' ]]; then
+ prepaths=( $tmp1[2,-2]/ )
+ else
+ prepaths=( ${(P)${tmp1}} )
+ [[ $#prepaths -eq 0 ]] && prepaths=( $tmp1/ )
+ fi
+ [[ $#prepaths -eq 0 ]] && prepaths=( '' )
+ shift 2
+else
+ prepaths=( '' )
+fi
+
+# Get the optional `-F' option and its argument.
+if [[ "$1" = -F ]]; then
+ ignore=(-F "$2")
+ shift 2
+else
+ ignore=''
+fi
+
+# str holds the whole string from the command line with a `*' between
+# the prefix and the suffix.
+
+str="${PREFIX:q}*${SUFFIX:q}"
+
+# We will first try normal completion called with `complist', but only if we
+# weren't given a `-F' option.
+
+if [[ -z "$ignore" ]]; then
+ # First build an array containing the `-W' option, if there is any and we
+ # want to use it. We don't want to use it if the string from the command line
+ # is a absolute path or relative to the current directory.
+
+ if [[ -z "$tmp1[1]" || "$str[1]" = [~/] || "$str" = (.|..)/* ]]; then
+ tmp1=()
+ else
+ tmp1=(-W "( $ppres )")
+ fi
+
+ # Now call complist.
+
+ nm=$NMATCHES
+ if [[ $# -eq 0 ]]; then
+ complist "$tmp1[@]" -f
+ elif [[ "$1" = -g ]]; then
+ complist "$tmp1[@]" -g "$argv[2,-1]"
+ shift
+ else
+ complist "$tmp1[@]" $1
+ shift
+ fi
+
+ # If this generated any matches, we don't wnat to do in-path completion.
+
+ [[ -nmatches nm ]] || return
+
+ # No `-F' option, so we want to use `fignore'.
+
+ ignore=(-F fignore)
+fi
+
+# If we weren't given any file patterns as arguments, we trick ourselves
+# into believing that we were given the pattern `*'. This is just to simplify
+# the following code.
+
+[[ -z "$1" ]] && 1='*'
+
+# Now let's have a closer look at the string to complete.
+
+if [[ "$str[1]" = \~ ]]; then
+ # It begins with `~', so remember anything before the first slash to be able
+ # to report it to the completion code. Also get an expanded version of it
+ # (in `realpath'), so that we can generate the matches. Then remove that
+ # prefix from the string to complete, set `donepath' to build the correct
+ # paths and make sure that the loop below is run only once with an empty
+ # prefix path by setting `prepaths'.
+
+ linepath="${str%%/*}/"
+ eval realpath\=path
+ str="${str#*/}"
+ donepath=''
+ prepaths=( '' )
+else
+ # If the string does not start with a `~' we don't remove a prefix from the
+ # string.
+
+ liniepath=''
+ realpath=''
+
+ if [[ "$str[1]" = / ]]; then
+ # If it is a absolut path name, we remove the first slash and put it in
+ # `donepath' meaning that we treat it as the path that was already handled.
+ # Also, we don't use the paths from `-W'.
+
+ str="$str[2,-1]"
+ donepath='/'
+ prepaths=( '' )
+ else
+ # The common case, we just use the string as it is, unless it begins with
+ # `./' or `../' in which case we don't use the paths from `-W'.
+
+ [[ "$str" = (.|..)/* ]] && prepaths=( '' )
+ donepath=''
+ fi
+fi
+
+# First we skip over all pathname components in `str' which really exist in
+# the file-system, so that `/usr/lib/l<TAB>' doesn't offer you `lib' and
+# `lib5'. Pathname components skipped this way are taken from `str' and added
+# to `donepath'.
+
+while [[ "$str" = */* ]] do
+ [[ -e "$realpath$donepath${str%%/*}" ]] || break
+ donepath="$donepath${str%%/*}/"
+ str="${str#*/}"
+done
+
+# Now build the glob pattern. As noted above, this function behaves as if
+# a global matcher with two matching specifications are given.
+
+if [[ -matcher 1 ]]; then
+ patstr="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*."
+else
+ patstr="${str%/*}/*${str##*/}*"
+ patstr="$patstr:gs./.*/.:gs.**.*."
+fi
+
+# Finally, generate the matches. First we loop over all the paths from `-W'.
+# Note that in this loop `str' is used as a modifyable version of `patstr'
+# and `testpath' is a modifyable version of `donepath'.
+
+for prepath in "$prepaths[@]"; do
+ str="$patstr"
+ testpath="$donepath"
+
+ # The second loop tests the components of the path in `str' to get the
+ # possible matches.
+
+ while [[ "$str" = */* ]] do
+ # `rest' is the pathname after the first slash that is left. In `tmp1'
+ # we get the globbing matches for the pathname component currently
+ # handled.
+
+ rest="${str#*/}"
+ tmp1="${prepath}${realpath}${testpath}(#l)${str%%/*}(-/)"
+ tmp1=( $~tmp1 )
+
+ if [[ $#tmp1 -eq 0 ]]; then
+ # If this didn't produce any matches, we don't need to test this path
+ # any further, so continue with the next `-W' path, if any.
+
+ continue 2
+ elif [[ $#tmp1 -gt 1 ]]; then
+ # If it produced more than one match, we want to remove those which
+ # don't have possible following pathname components matching the
+ # rest of the string we are completing. (The case with only one
+ # match is handled below.)
+ # In `collect' we will collect those of the produced pathnames that
+ # have a matching possible path-suffix. In `suffixes' we build an
+ # array containing strings build from the rest of the string to
+ # complete and the glob patterns we were given as arguments.
+
+ collect=()
+ suffixes=( $rest$@ )
+ suffixes=( "${(@)suffixes:gs.**.*.}" )
+
+ # In the loop the prefixes from the `tmp1' array produced above and
+ # the suffixes we just built are used to produce possible matches
+ # via globbing.
+
+ for i in $tmp1; do
+ tmp2=( $~i/(#l)$~suffixes )
+ [[ $#tmp2 -ne 0 ]] && collect=( $collect $i )
+ done
+
+ # If this test showed that none of the matches from the glob in `tmp1'
+ # has a possible sub-path matching what's on the line, we give up and
+ # continue with the next `-W' path.
+
+ if [[ $#collect -eq 0 ]]; then
+ continue 2
+ elif [[ $#collect -ne 1 ]]; then
+ # If we have more than one possible match, this means that the
+ # pathname component currently handled is ambiguous, so we give
+ # it to the completion code.
+ # First we build the full path prefix in `tmp1'.
+
+ tmp1="$prepath$realpath$testpath"
+
+ # Now produce all matching pathnames in `collect'.
+
+ collect=( $~collect/(#l)$~suffixes )
+
+ # And then remove the common path prefix from all these matches.
+
+ collect=( ${collect#$tmp1} )
+
+ # Finally, we add all these matches with the common (unexpanded)
+ # pathprefix (the `-p' option), the path-prefix (the `-W' option)
+ # to allow the completion code to test file type, and the path-
+ # suffix (the `-s' option). We also tell the completion code that
+ # these are file names and that `fignore' should be used as usual
+ # (the `-f' and `-F' options).
+
+ for i in $collect; do
+ compadd -p "$linepath$testpath" -W "$tmp1" -s "/${i#*/}" -f "$ignore[@]" -- "${i%%/*}"
+ done
+
+ # We have just finished handling all the matches from above, so we
+ # can continue with the next `-W' path.
+
+ continue 2
+ fi
+ # We reach this point if only one of the path prefixes in `tmp1'
+ # has a existing path-suffix matching the string from the line.
+ # In this case we accept this match and continue with the next
+ # path-name component.
+
+ tmp1=( "$collect[1]" )
+ fi
+ # This is also reached if the first globbing produced only one match
+ # in this case we just continue with the next pathname component, too.
+
+ tmp1="$tmp1[1]"
+ testpath="$testpath${tmp1##*/}/"
+ str="$rest"
+ done
+
+ # We are here if all pathname components except the last one (which is still
+ # not tested) are unambiguous. So we add matches with the full path prefix,
+ # no path suffix, the `-W' we are currently handling, all the matches we
+ # can produce in this directory, if any.
+
+ tmp1="$prepath$realpath$testpath"
+ suffixes=( $str$@ )
+ suffixes=( "${(@)suffixes:gs.**.*.}" )
+ tmp2=( $~tmp1(#l)$~suffixes )
+ compadd -p "$linepath$testpath" -W "$prepath$realpath$testpath" -f "$ignore[@]" -- ${tmp2#$tmp1}
+done
diff --git a/Functions/Completion/__pdf b/Functions/Completion/__pdf
new file mode 100644
index 000000000..97f656f40
--- /dev/null
+++ b/Functions/Completion/__pdf
@@ -0,0 +1,3 @@
+function acroread
+
+__files -g '*.(pdf|PDF)'
diff --git a/Functions/Completion/__precommand b/Functions/Completion/__precommand
new file mode 100644
index 000000000..a07cae96a
--- /dev/null
+++ b/Functions/Completion/__precommand
@@ -0,0 +1,15 @@
+#function - nohup nice eval time rusage noglob nocorrect exec
+
+# We just change the special completion parameters, to make the first
+# argument be treated as the command name and removing it from the
+# positional parameters.
+
+COMMAND="$1"
+shift
+(( CURRENT-- ))
+if [[ CURRENT -eq 0 ]]; then
+ CONTEXT=command
+else
+ CONTEXT=argument
+fi
+compsub
diff --git a/Functions/Completion/__ps b/Functions/Completion/__ps
new file mode 100644
index 000000000..fa7395113
--- /dev/null
+++ b/Functions/Completion/__ps
@@ -0,0 +1,3 @@
+#function gs ghostview gview psnup psselect pswrap pstops pstruct lpr
+
+__files -g '*([pP][sS]|eps)'
diff --git a/Functions/Completion/__rcs b/Functions/Completion/__rcs
new file mode 100644
index 000000000..8d55cc470
--- /dev/null
+++ b/Functions/Completion/__rcs
@@ -0,0 +1,9 @@
+#function co ci rcs
+
+[[ $COMMAND = ci || $COMMAND = rcs ]] && __files
+
+if [[ $NMATCHES -eq 0 && -d RCS && $COMMAND != ci ]]; then
+ local rep
+ rep=(RCS/$PREFIX*$SUFFIX,v(:t:s/\,v//))
+ (( $#rep )) && compadd -m $rep
+fi
diff --git a/Functions/Completion/__rlogin b/Functions/Completion/__rlogin
new file mode 100644
index 000000000..69fdebe13
--- /dev/null
+++ b/Functions/Completion/__rlogin
@@ -0,0 +1,9 @@
+#function rlogin rsh ssh
+
+if [[ -position 1 ]]; then
+ complist -k hosts
+elif [[ -position 2 ]]; then
+ complist -k '(-l)'
+else
+ complist -u
+fi
diff --git a/Functions/Completion/__sched b/Functions/Completion/__sched
new file mode 100644
index 000000000..dcd7385e1
--- /dev/null
+++ b/Functions/Completion/__sched
@@ -0,0 +1,3 @@
+#function sched
+
+[[ -position 2 -1 ]] && compsub
diff --git a/Functions/Completion/__set b/Functions/Completion/__set
new file mode 100644
index 000000000..e9db6c43a
--- /dev/null
+++ b/Functions/Completion/__set
@@ -0,0 +1,7 @@
+#function set
+
+if [[ -mcurrent -1 [-+]o ]]; then
+ complist -o
+elif [[ -current -1 -A ]]; then
+ complist -A
+fi
diff --git a/Functions/Completion/__setopt b/Functions/Completion/__setopt
new file mode 100644
index 000000000..8a97befc7
--- /dev/null
+++ b/Functions/Completion/__setopt
@@ -0,0 +1,7 @@
+#function setopt
+
+local nm=$NMATCHES
+
+complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \
+ -s '$({ unsetopt kshoptionprint; unsetopt } 2>/dev/null)'
+[[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Functions/Completion/__source b/Functions/Completion/__source
new file mode 100644
index 000000000..3478c9f38
--- /dev/null
+++ b/Functions/Completion/__source
@@ -0,0 +1,7 @@
+#function source
+
+if [[ -position 2 -1 ]]; then
+ compsub
+else
+ __files
+fi
diff --git a/Functions/Completion/__strip b/Functions/Completion/__strip
new file mode 100644
index 000000000..9abd38b63
--- /dev/null
+++ b/Functions/Completion/__strip
@@ -0,0 +1,2 @@
+#function strip
+__files -g '*(*)'
diff --git a/Functions/Completion/__stty b/Functions/Completion/__stty
new file mode 100644
index 000000000..ebf0de68f
--- /dev/null
+++ b/Functions/Completion/__stty
@@ -0,0 +1,16 @@
+#function stty
+
+if [[ -mcurrent -1 \
+ (*erase|discard|status|dsusp|intr|kill|lnext|quit|reprint|start|s*p) ]]
+then
+ compadd -m -Q '^-' '^h' '^?' '^c' '^u'
+else
+ [[ -string '-' || -string '+' ]]
+ compadd -m rows columns intr quit erase kill eof eol \
+ eol2 start stop susp dsusp reprint discard werase lnext \
+ parenb parodd cs8 cstopb hupcl cread clocal parext \
+ ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl iuclc \
+ ixon ixany ixoff imaxbel isig icanon xcase echo echoe echok \
+ echonl noflsh tostop echoctl echoprt echoke flusho pending iexten \
+ opost olcuc onlcr ocrnl onocr onlret ofill ofdel
+fi
diff --git a/Functions/Completion/__subscript b/Functions/Completion/__subscript
new file mode 100644
index 000000000..a10b1202b
--- /dev/null
+++ b/Functions/Completion/__subscript
@@ -0,0 +1,4 @@
+#function --subscript--
+
+compalso --math-- "$@"
+[[ ${(Pt)${COMMAND}} = assoc* ]] && complist -k "( ${(kP)${COMMAND}} )"
diff --git a/Functions/Completion/__tar b/Functions/Completion/__tar
new file mode 100644
index 000000000..4f8b51a00
--- /dev/null
+++ b/Functions/Completion/__tar
@@ -0,0 +1,14 @@
+#function tar
+
+local nm=$NMATCHES tf="$2"
+compsave
+
+if [[ ( -mword 1 *t*f* || -mword 1 *x*f* ) && -position 3 100000 ]]; then
+ complist -k "( $(tar tf $tf) )"
+ compreset
+elif [[ -mword 1 *c*f* && -position 3 100000 ]]; then
+ __files
+ compreset
+elif [[ -mcurrent -1 *f* && -position 2 ]]; then
+ __files -g '*.(tar|TAR)'
+fi
diff --git a/Functions/Completion/__tex b/Functions/Completion/__tex
new file mode 100644
index 000000000..7331bb794
--- /dev/null
+++ b/Functions/Completion/__tex
@@ -0,0 +1,3 @@
+#function tex latex slitex
+
+__files -g '*.(tex|TEX|texinfo|texi)'
diff --git a/Functions/Completion/__trap b/Functions/Completion/__trap
new file mode 100644
index 000000000..0171bdc2b
--- /dev/null
+++ b/Functions/Completion/__trap
@@ -0,0 +1,7 @@
+#function trap
+
+if [[ -position 1 ]]; then
+ complist -c
+else
+ complist -k signals
+fi
diff --git a/Functions/Completion/__uncompress b/Functions/Completion/__uncompress
new file mode 100644
index 000000000..91343521e
--- /dev/null
+++ b/Functions/Completion/__uncompress
@@ -0,0 +1,3 @@
+#function uncompress zmore
+
+__files -g '*.Z'
diff --git a/Functions/Completion/__unhash b/Functions/Completion/__unhash
new file mode 100644
index 000000000..73196ca1b
--- /dev/null
+++ b/Functions/Completion/__unhash
@@ -0,0 +1,6 @@
+#function unhash
+
+[[ -mword 1 -*d* ]] && complist -n
+[[ -mword 1 -*a* ]] && complist -a
+[[ -mword 1 -*f* ]] && complist -F
+[[ ! -mword 1 -* ]] && complist -m
diff --git a/Functions/Completion/__unsetopt b/Functions/Completion/__unsetopt
new file mode 100644
index 000000000..c9a074d9b
--- /dev/null
+++ b/Functions/Completion/__unsetopt
@@ -0,0 +1,7 @@
+#function unsetopt
+
+local nm=$NMATCHES
+
+complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \
+ -s '$({ unsetopt kshoptionprint; setopt } 2>/dev/null)'
+[[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Functions/Completion/__vars b/Functions/Completion/__vars
new file mode 100644
index 000000000..649e347fa
--- /dev/null
+++ b/Functions/Completion/__vars
@@ -0,0 +1,2 @@
+#array --math-- getopts read unset vared
+__vars=(-v)
diff --git a/Functions/Completion/__vars_eq b/Functions/Completion/__vars_eq
new file mode 100644
index 000000000..e0b5d9ae5
--- /dev/null
+++ b/Functions/Completion/__vars_eq
@@ -0,0 +1,2 @@
+#array declare export integer local readonly typeset
+__varseq=(-v -q -S '=')
diff --git a/Functions/Completion/__wait b/Functions/Completion/__wait
new file mode 100644
index 000000000..1c04108f6
--- /dev/null
+++ b/Functions/Completion/__wait
@@ -0,0 +1,4 @@
+#function wait
+
+complist -P '%' -j
+complist -y __kill_helper -s '`ps 2>/dev/null | tail +2 | cut -c1-5`'
diff --git a/Functions/Completion/__which b/Functions/Completion/__which
new file mode 100644
index 000000000..32b95b9c5
--- /dev/null
+++ b/Functions/Completion/__which
@@ -0,0 +1,2 @@
+#array which whence where type
+__which=(-caF)
diff --git a/Functions/Completion/__x_options b/Functions/Completion/__x_options
new file mode 100644
index 000000000..d5f85ea36
--- /dev/null
+++ b/Functions/Completion/__x_options
@@ -0,0 +1,5 @@
+#pattern-function '*/X11/*'
+
+# A simple pattern completion, just as an example.
+
+complist -J options -k '(-display -name -xrm)'
diff --git a/Functions/Completion/__xfig b/Functions/Completion/__xfig
new file mode 100644
index 000000000..7f17c2e42
--- /dev/null
+++ b/Functions/Completion/__xfig
@@ -0,0 +1,3 @@
+#function xfig
+
+__files -g '*.fig'
diff --git a/Functions/Completion/__zle b/Functions/Completion/__zle
new file mode 100644
index 000000000..f677374c8
--- /dev/null
+++ b/Functions/Completion/__zle
@@ -0,0 +1,7 @@
+#function zle
+
+if [[ -word 1 -N && -position 3 ]]; then
+ complist -F
+else
+ complist -b
+fi
diff --git a/Functions/Completion/__zmodload b/Functions/Completion/__zmodload
new file mode 100644
index 000000000..78c7393a3
--- /dev/null
+++ b/Functions/Completion/__zmodload
@@ -0,0 +1,9 @@
+#function zmodload
+
+if [[ -mword 1 -*(a*u|u*a)* || -mword 1 -*a* && -position 3 -1 ]]; then
+ complist -B
+elif [[ -mword 1 -*u* ]]; then
+ complist -s '$(zmodload)'
+else
+ complist -s '${^module_path}/*(N:t:r)'
+fi
diff --git a/Functions/Completion/_aliases b/Functions/Completion/_aliases
new file mode 100644
index 000000000..1038a726e
--- /dev/null
+++ b/Functions/Completion/_aliases
@@ -0,0 +1,3 @@
+#defcomp unalias
+
+complist -a
diff --git a/Functions/Completion/_arrays b/Functions/Completion/_arrays
new file mode 100644
index 000000000..cbeac7118
--- /dev/null
+++ b/Functions/Completion/_arrays
@@ -0,0 +1,3 @@
+#defcomp shift
+
+complist -A
diff --git a/Functions/Completion/_autoload b/Functions/Completion/_autoload
new file mode 100644
index 000000000..4f506baeb
--- /dev/null
+++ b/Functions/Completion/_autoload
@@ -0,0 +1,3 @@
+#defcomp autoload
+
+complist -s '${^fpath}/*(N:t)'
diff --git a/Functions/Completion/_bg_jobs b/Functions/Completion/_bg_jobs
new file mode 100644
index 000000000..511bb8308
--- /dev/null
+++ b/Functions/Completion/_bg_jobs
@@ -0,0 +1,3 @@
+#defcomp bg
+
+complist -z -P '%'
diff --git a/Functions/Completion/_bindkey b/Functions/Completion/_bindkey
new file mode 100644
index 000000000..8eddeb2a8
--- /dev/null
+++ b/Functions/Completion/_bindkey
@@ -0,0 +1,7 @@
+#defcomp bindkey
+
+if [[ -mword 1 -*[DAN]* || -mcurrent -1 -*M ]]; then
+ complist -s '$(bindkey -l)'
+else
+ complist -b
+fi
diff --git a/Functions/Completion/_builtin b/Functions/Completion/_builtin
new file mode 100644
index 000000000..d2b11226b
--- /dev/null
+++ b/Functions/Completion/_builtin
@@ -0,0 +1,7 @@
+#defcomp builtin
+
+if [[ -position 2 -1 ]]; then
+ compsub
+else
+ complist -eB
+fi
diff --git a/Functions/Completion/_cd b/Functions/Completion/_cd
new file mode 100644
index 000000000..f3ce67ec7
--- /dev/null
+++ b/Functions/Completion/_cd
@@ -0,0 +1,3 @@
+#defcomp cd
+
+_files -W cdpath -g '*(-/)'
diff --git a/Functions/Completion/_command b/Functions/Completion/_command
new file mode 100644
index 000000000..b2812de25
--- /dev/null
+++ b/Functions/Completion/_command
@@ -0,0 +1,7 @@
+#defcomp command
+
+if [[ -position 2 -1 ]]; then
+ _normal "$@"
+else
+ complist -em
+fi
diff --git a/Functions/Completion/_command_names b/Functions/Completion/_command_names
new file mode 100644
index 000000000..d3b8a109a
--- /dev/null
+++ b/Functions/Completion/_command_names
@@ -0,0 +1,3 @@
+#defcomp -command-
+
+complist -c
diff --git a/Functions/Completion/_compress b/Functions/Completion/_compress
new file mode 100644
index 000000000..860aeb5b0
--- /dev/null
+++ b/Functions/Completion/_compress
@@ -0,0 +1,3 @@
+#defcomp compress
+
+_files -g '*~*.Z'
diff --git a/Functions/Completion/_condition b/Functions/Completion/_condition
new file mode 100644
index 000000000..3e45e1b8f
--- /dev/null
+++ b/Functions/Completion/_condition
@@ -0,0 +1,10 @@
+#defcomp -condition-
+
+if [[ -current -1 -o ]]; then
+ complist -o -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}'
+elif [[ -current -1 -nt || -current -1 -ot || -current -1 -ef ]]; then
+ _files
+else
+ _files
+ complist -v
+fi
diff --git a/Functions/Completion/_configure b/Functions/Completion/_configure
new file mode 100644
index 000000000..de8d5fba5
--- /dev/null
+++ b/Functions/Completion/_configure
@@ -0,0 +1,12 @@
+#defcomp configure
+
+if [[ $PREFIX = *=* ]]; then
+ # Complete filenames after e.g. --prefix=
+ IPREFIX=${PREFIX%%=*}=
+ PREFIX=${PREFIX#*=}
+ complist -f
+else
+ # Generate a list of options from configure --help
+ complist -s '$($COMMAND --help |
+ sed -n -e '\''s/^ *\(--[-a-z0-9]*\)[ =,].*$/\1/p'\'')'
+fi
diff --git a/Functions/Completion/_dd b/Functions/Completion/_dd
new file mode 100644
index 000000000..2458541ea
--- /dev/null
+++ b/Functions/Completion/_dd
@@ -0,0 +1,13 @@
+#defcomp dd
+
+if [[ -iprefix conv= ]]; then
+ # If there's a comma present, ignore up to the last one. The
+ # test alone will have that effect.
+ [[ -string , ]]
+ complist -S, -q \
+ -k '(ascii ebcdic ibm block unblock lcase ucase swab noerror sync)'
+elif [[ -iprefix 'if=' || -iprefix 'of=' ]]; then
+ _files
+else
+ complist -S '=' -k '(if of ibs obs bs cbs skip files seek count conv)'
+fi
diff --git a/Functions/Completion/_default b/Functions/Completion/_default
new file mode 100644
index 000000000..8bcf14f6a
--- /dev/null
+++ b/Functions/Completion/_default
@@ -0,0 +1,13 @@
+#defcomp -default-
+
+# We first try the `compctl's. This is without first (-T) and default (-D)
+# completion. If you want them add `-T' and/or `-D' to this command.
+# If there is a `compctl' for the command we are working on, we return
+# immediatly. If you want to use new style completion anyway, remove the
+# `|| return'. Also, you may want to use new style completion if the
+# `compctl' didn't produce any matches. In that case remove the `|| return'
+# and at the line `[[ -nmatches 0 ]] || return' after `compcall'.
+
+compcall || return
+
+_files
diff --git a/Functions/Completion/_dirs b/Functions/Completion/_dirs
new file mode 100644
index 000000000..c79080d58
--- /dev/null
+++ b/Functions/Completion/_dirs
@@ -0,0 +1,3 @@
+#defcomp rmdir df du dircmp
+
+_files -/ '*(-/)'
diff --git a/Functions/Completion/_disable b/Functions/Completion/_disable
new file mode 100644
index 000000000..063b65a7d
--- /dev/null
+++ b/Functions/Completion/_disable
@@ -0,0 +1,6 @@
+#defcomp disable
+
+[[ -mcurrent -1 -*a* ]] && complist -ea
+[[ -mcurrent -1 -*f* ]] && complist -eF
+[[ -mcurrent -1 -*r* ]] && complist -ew
+[[ ! -mcurrent -1 -* ]] && complist -eB
diff --git a/Functions/Completion/_dvi b/Functions/Completion/_dvi
new file mode 100644
index 000000000..bb2fc293e
--- /dev/null
+++ b/Functions/Completion/_dvi
@@ -0,0 +1,3 @@
+#defcomp xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype
+
+_files -g '*.(dvi|DVI)'
diff --git a/Functions/Completion/_echotc b/Functions/Completion/_echotc
new file mode 100644
index 000000000..85ebb97ce
--- /dev/null
+++ b/Functions/Completion/_echotc
@@ -0,0 +1,3 @@
+#defcomp echotc
+
+complist -k '(al dc dl do le up al bl cd ce cl cr dc dl do ho is le ma nd nl se so up)'
diff --git a/Functions/Completion/_enable b/Functions/Completion/_enable
new file mode 100644
index 000000000..22ff53ee7
--- /dev/null
+++ b/Functions/Completion/_enable
@@ -0,0 +1,6 @@
+#defcomp enable
+
+[[ -mcurrent -1 -*a* ]] && complist -da
+[[ -mcurrent -1 -*f* ]] && complist -dF
+[[ -mcurrent -1 -*r* ]] && complist -dw
+[[ ! -mcurrent -1 -* ]] && complist -dB
diff --git a/Functions/Completion/_fc b/Functions/Completion/_fc
new file mode 100644
index 000000000..f0d2c03fd
--- /dev/null
+++ b/Functions/Completion/_fc
@@ -0,0 +1,7 @@
+#defcomp fc
+
+if [[ -mcurrent -1 -*e ]]; then
+ complist -c
+elif [[ -mcurrent -1 -[ARWI]## ]]; then
+ _files
+fi
diff --git a/Functions/Completion/_files b/Functions/Completion/_files
new file mode 100644
index 000000000..ab3006e39
--- /dev/null
+++ b/Functions/Completion/_files
@@ -0,0 +1,10 @@
+#autoload
+
+# Utility function for completing files of a given type or any file.
+# In many cases you will want to call this one instead of _path_files().
+
+local nm=$NMATCHES
+
+_path_files "$@"
+
+[[ $# -ne 0 && -nmatches nm ]] && _path_files
diff --git a/Functions/Completion/_find b/Functions/Completion/_find
new file mode 100644
index 000000000..ca4f79908
--- /dev/null
+++ b/Functions/Completion/_find
@@ -0,0 +1,21 @@
+#defcomp find
+
+if [[ -mbetween -(ok|exec) \\\; ]]; then
+ _normal "$@"
+elif [[ -iprefix - ]]; then
+ complist -s 'daystart {max,min,}depth follow noleaf version xdev \
+ {a,c,}newer {a,c,m}{min,time} empty false {fs,x,}type gid inum links \
+ {i,}{l,}name {no,}{user,group} path perm regex size true uid used \
+ exec {f,}print{f,0,} ok prune ls'
+elif [[ -position 1 ]]; then
+ complist -g '. ..'
+ _files -g '(-/)'
+elif [[ -mcurrent -1 -((a|c|)newer|fprint(|0|f)) ]]; then
+ _files
+elif [[ -current -1 -fstype ]]; then
+ complist -k '(ufs 4.2 4.3 nfs tmp mfs S51K S52K)'
+elif [[ -current -1 -group ]]; then
+ complist -k groups
+elif [[ -current -1 -user ]]; then
+ complist -u
+fi
diff --git a/Functions/Completion/_functions b/Functions/Completion/_functions
new file mode 100644
index 000000000..8a352ea08
--- /dev/null
+++ b/Functions/Completion/_functions
@@ -0,0 +1,3 @@
+#defcomp unfunction
+
+complist -F
diff --git a/Functions/Completion/_gunzip b/Functions/Completion/_gunzip
new file mode 100644
index 000000000..35a27e774
--- /dev/null
+++ b/Functions/Completion/_gunzip
@@ -0,0 +1,3 @@
+#defcomp gunzip zcat
+
+_files -g '*.[gG][z]'
diff --git a/Functions/Completion/_gzip b/Functions/Completion/_gzip
new file mode 100644
index 000000000..3cda1e4ed
--- /dev/null
+++ b/Functions/Completion/_gzip
@@ -0,0 +1,3 @@
+#defcomp gzip
+
+_files -g '*~*.[gG][zZ]'
diff --git a/Functions/Completion/_hash b/Functions/Completion/_hash
new file mode 100644
index 000000000..a6109563d
--- /dev/null
+++ b/Functions/Completion/_hash
@@ -0,0 +1,13 @@
+#defcomp hash
+
+if [[ -mword 1 -*d* ]]; then
+ if [[ -string 1 '=' ]]; then
+ _path_files -g '*(-/)'
+ else
+ complist -n -q -S '='
+ fi
+elif [[ -string 1 '=' ]]; then
+ _files -g '*(*)' '*(-/)'
+else
+ complist -m -q -S '='
+fi
diff --git a/Functions/Completion/_hosts b/Functions/Completion/_hosts
new file mode 100644
index 000000000..3acc327ac
--- /dev/null
+++ b/Functions/Completion/_hosts
@@ -0,0 +1,3 @@
+#defcomp ftp ncftp ping rwho rup xping traceroute nslookup
+
+complist -k hosts
diff --git a/Functions/Completion/_jobs b/Functions/Completion/_jobs
new file mode 100644
index 000000000..018883c61
--- /dev/null
+++ b/Functions/Completion/_jobs
@@ -0,0 +1,3 @@
+#defcomp fg jobs
+
+complist -j -P '%'
diff --git a/Functions/Completion/_kill b/Functions/Completion/_kill
new file mode 100644
index 000000000..bf0e1d3f8
--- /dev/null
+++ b/Functions/Completion/_kill
@@ -0,0 +1,8 @@
+#defcomp kill
+
+if [[ -iprefix '-' ]]; then
+ complist -k "($signals[1,-3])"
+else
+ complist -P '%' -j
+ complist -y _kill_helper -s '`ps 2>/dev/null | tail +2 | cut -c1-5`'
+fi
diff --git a/Functions/Completion/_kill_helper b/Functions/Completion/_kill_helper
new file mode 100644
index 000000000..d3e9aaf4a
--- /dev/null
+++ b/Functions/Completion/_kill_helper
@@ -0,0 +1,3 @@
+#autoload
+
+reply=( "$(ps 2>/dev/null)" )
diff --git a/Functions/Completion/_limits b/Functions/Completion/_limits
new file mode 100644
index 000000000..35ccbe07e
--- /dev/null
+++ b/Functions/Completion/_limits
@@ -0,0 +1,3 @@
+#defcomp limit unlimit
+
+complist -k "(${(j: :)${(f)$(limit)}%% *})"
diff --git a/Functions/Completion/_main_complete b/Functions/Completion/_main_complete
new file mode 100644
index 000000000..003a01785
--- /dev/null
+++ b/Functions/Completion/_main_complete
@@ -0,0 +1,48 @@
+#autoload
+
+# The main loop of the completion code. This is what is called when
+# completion is attempted from the command line.
+# The completion code gives us the special variables and the arguments
+# from the command line are given as positional parameters.
+
+local comp name
+
+setopt localoptions nullglob rcexpandparam globdots
+unsetopt markdirs globsubst shwordsplit nounset
+
+# An entry for `-first-' is the replacement for `compctl -T'
+# Completion functions may set `COMPSKIP' to any value to make the
+# main loops stop calling other completion functions.
+
+comp="$comps[-first-]"
+if [[ ! -z "$comp" ]]; then
+ "$comp" "$@"
+ if (( $+COMPSKIP )); then
+ unset COMPSKIP
+ return
+ fi
+fi
+
+# For arguments we use the `_normal function.
+
+if [[ $CONTEXT == argument || $CONTEXT == command ]]; then
+ _normal "$@"
+else
+ # Let's see if we have a special completion definition for the other
+ # possible contexts.
+
+ comp=''
+
+ case $CONTEXT in
+ redirect) comp="$comps[-redirect-]";;
+ math) comp="$comps[-math-]";;
+ subscript) comp="$comps[-subscript-]";;
+ value) comp="$comps[-value-]";;
+ condition) comp="$comps[-condition-]";;
+ esac
+
+ # If not, we use default completion, if any.
+
+ [[ -z "$comp" ]] && comp="$comps[-default-]"
+ [[ -z "$comp" ]] || "$comp" "$@"
+fi
diff --git a/Functions/Completion/_make b/Functions/Completion/_make
new file mode 100644
index 000000000..d576b0308
--- /dev/null
+++ b/Functions/Completion/_make
@@ -0,0 +1,3 @@
+#defcomp make gmake pmake
+
+complist -s "\$(awk '/^[a-zA-Z0-9][^/ ]+:/ {print \$1}' FS=: [mM]akefile)"
diff --git a/Functions/Completion/_man b/Functions/Completion/_man
new file mode 100644
index 000000000..8204fba0b
--- /dev/null
+++ b/Functions/Completion/_man
@@ -0,0 +1,11 @@
+#defcomp man
+setopt localoptions rcexpandparam
+
+local rep
+if [[ $2 = (<->*|ln) ]]; then
+ rep=( $manpath/(man|cat)$2/$PREFIX*$SUFFIX.<->*(N:t:r) )
+else
+ rep=( $manpath/(man|cat)*/$PREFIX*$SUFFIX.<->*(N:t:r) )
+fi
+
+(( $#rep )) && compadd -m $rep
diff --git a/Functions/Completion/_mh b/Functions/Completion/_mh
new file mode 100644
index 000000000..67ce49fd2
--- /dev/null
+++ b/Functions/Completion/_mh
@@ -0,0 +1,70 @@
+#defcomp folder comp inc mark refile repl scan show next prev rmm pick whom mhn mhpath mhpatch
+
+# Completion for all possible MH commands.
+# Alter the following two to your own mh directory and the directory
+# where standard mh library files live. (It works anyway, but this
+# will save a little time.)
+local mymhdir=~/Mail
+local mhlib=/usr/lib/mh
+
+# To be on the safe side, check this exists and if not, get it anyway.
+[[ -d $mymhdir ]] || mymhdir=$(mhpath +)
+
+if [[ -iprefix - ]]; then
+ # get list of options, which MH commands can generate themselves
+ # awk is just too icky to use for this, sorry. send me one if
+ # you come up with it.
+ compadd -m $($COMMAND -help | perl -ne 'if (/^\s*-\(?(\S+)/) {
+ $n = $1;
+ $n =~ s/\)//g;
+ print $n =~ s/^\[([a-z]+)\]// ? "$n\n$1$n\n" : "$n\n";
+ }')
+ return
+elif [[ -iprefix '+' || -iprefix '@' || -current -1 -draftfolder ]]; then
+ # Complete folder names.
+ local mhpath
+ if [[ $IPREFIX != '@' ]]; then
+ [[ $IPREFIX = '+' ]] || IPREFIX=+
+ mhpath=$mymhdir
+ else
+ mhpath=$(mhpath)
+ fi
+
+ # painless, or what?
+ complist -W mhpath -/
+elif [[ -mcurrent -1 -(editor|(whatnow|rmm|show|more)proc) ]]; then
+ complist -c
+elif [[ -current -1 -file ]]; then
+ complist -f
+elif [[ -mcurrent -1 -(form|audit|filter) ]]; then
+ # Need some MH template file, which may be in our own MH directory
+ # or with the standard library.
+ local mhfpath
+ # This is the only place we need mhlib, so leave the test till here.
+ [[ -d $mhlib ]] || { mhlib=$(mhparam mhlproc); mhlib=$mhlib:h; }
+ mhfpath=($mymhdir $mhlib)
+
+ complist -W mhfpath -g '*(.)'
+elif [[ -mcurrent -1 -(no|)cc ]]; then
+ compadd -m all to cc me
+elif [[ -mcurrent -1 -[rw]cache ]]; then
+ compadd -m public private never ask
+else
+ # Generate sequences.
+ local foldnam folddir f
+ for f in $argv; do
+ [[ $f = [@+]* ]] && foldnam=$f
+ done
+ if [[ $foldnam = '+'* ]]; then
+ folddir=$mymhdir/${foldnam#+}
+ elif [[ $foldnam = '@'* ]]; then
+ folddir=$(mhpath)/${foldnam#@}
+ else
+ folddir=$(mhpath)
+ # leaving foldnam empty works here
+ fi
+
+ complist -s '$(mark $foldnam | awk -F: '\''{ print $1 }'\'')'
+ compadd -m reply next cur prev first last all unseen
+ complist -W folddir -g '<->'
+fi
diff --git a/Functions/Completion/_most_recent_file b/Functions/Completion/_most_recent_file
new file mode 100644
index 000000000..69900e6ff
--- /dev/null
+++ b/Functions/Completion/_most_recent_file
@@ -0,0 +1,3 @@
+#defkeycomp expand-or-complete \C-xm
+
+complist -g '*(om[1])'
diff --git a/Functions/Completion/_normal b/Functions/Completion/_normal
new file mode 100644
index 000000000..22cc1decf
--- /dev/null
+++ b/Functions/Completion/_normal
@@ -0,0 +1,56 @@
+#autoload
+
+local comp cmd1 cmd2 pat val name
+
+# Completing in command position? If not we set up `cmd1' and `cmd2' as
+# two strings we have search in the completion definition arrays (e.g.
+# a path and the last path name component).
+
+if [[ $CONTEXT == command ]]; then
+ comp="$comps[-command-]"
+ [[ -z "$comp" ]] || "$comp" "$@"
+ return
+elif [[ "$COMMAND[1]" == '=' ]]; then
+ eval cmd1\=$COMMAND
+ cmd2="$COMMAND[2,-1]"
+elif [[ "$COMMAND" == */* ]]; then
+ cmd1="$COMMAND"
+ cmd2="${COMMAND:t}"
+else
+ cmd1="$COMMAND"
+ eval cmd2=$(whence -p $COMMAND)
+fi
+
+# See if there are any matching pattern completions.
+
+if (( $#patcomps )); then
+ for i in "$patcomps[@]"; do
+ pat="${i% *}"
+ val="${i#* }"
+ if [[ "$cmd1" == $~pat || "$cmd2" == $~pat ]]; then
+ "$val" "$@"
+ if (( $+COMPSKIP )); then
+ unset COMPSKIP
+ return
+ fi
+ fi
+ done
+fi
+
+# Now look up the two names in the normal completion array.
+
+name="$cmd1"
+comp="$comps[$cmd1]"
+
+if [[ -z "$comp" ]]; then
+ name="$cmd2"
+ comp="$comps[$cmd2]"
+fi
+
+# And generate the matches, probably using default completion.
+
+if [[ -z "$comp" ]]; then
+ name=-default-
+ comp="$comps[-default-]"
+fi
+[[ -z "$comp" ]] || "$comp" "$@"
diff --git a/Functions/Completion/_path_files b/Functions/Completion/_path_files
new file mode 100644
index 000000000..7db364b7d
--- /dev/null
+++ b/Functions/Completion/_path_files
@@ -0,0 +1,272 @@
+#autoload
+
+# Utility function for in-path completion.
+# First argument should be an complist-option (e.g. -f, -/, -g). The other
+# arguments should be glob patterns, one per argument.
+#
+# E.g.: _path_files -g '*.tex' '*.texi'
+#
+# This is intended as a replacement for `complist -f', `complist -/', and
+# `complist -g ...' (but don't use it with other options).
+#
+# You may also give the `-W <spec>' option as with `compctl' and `complist',
+# but only as the first argument.
+#
+# This function also accepts an optional `-F <string>' option as its first
+# argument or just after the `-W <spec>'. This can be used to define file
+# name extension (a la `fignore'). Files with such an extension will not
+# be considered possible completions.
+#
+# This function behaves as if you have a matcher definition like:
+# compctl -M 'r:|[-.,_/]=* r:|=* m:{a-z}={A-Z} m:-=_ m:.=,' \
+# 'm:{a-z}={A-Z} l:|=* r:|=*'
+# so you may want to modify this.
+
+local nm prepaths str linepath realpath donepath patstr prepath testpath rest
+local tmp1 collect tmp2 suffixes i ignore
+
+setopt localoptions nullglob rcexpandparam globdots extendedglob
+unsetopt markdirs globsubst shwordsplit nounset
+
+# Get the optional `-W' option and its argument.
+if [[ "$1" = -W ]]; then
+ tmp1="$2"
+ if [[ "$tmp1[1]" = '(' ]]; then
+ prepaths=( $tmp1[2,-2]/ )
+ else
+ prepaths=( ${(P)${tmp1}} )
+ [[ $#prepaths -eq 0 ]] && prepaths=( $tmp1/ )
+ fi
+ [[ $#prepaths -eq 0 ]] && prepaths=( '' )
+ shift 2
+else
+ prepaths=( '' )
+fi
+
+# Get the optional `-F' option and its argument.
+if [[ "$1" = -F ]]; then
+ ignore=(-F "$2")
+ shift 2
+else
+ ignore=''
+fi
+
+# str holds the whole string from the command line with a `*' between
+# the prefix and the suffix.
+
+str="${PREFIX:q}*${SUFFIX:q}"
+
+# We will first try normal completion called with `complist', but only if we
+# weren't given a `-F' option.
+
+if [[ -z "$ignore" ]]; then
+ # First build an array containing the `-W' option, if there is any and we
+ # want to use it. We don't want to use it if the string from the command line
+ # is a absolute path or relative to the current directory.
+
+ if [[ -z "$tmp1[1]" || "$str[1]" = [~/] || "$str" = (.|..)/* ]]; then
+ tmp1=()
+ else
+ tmp1=(-W "( $prepaths )")
+ fi
+
+ # Now call complist.
+
+ nm=$NMATCHES
+ if [[ $# -eq 0 ]]; then
+ complist "$tmp1[@]" -f
+ elif [[ "$1" = -g ]]; then
+ complist "$tmp1[@]" -g "$argv[2,-1]"
+ shift
+ else
+ complist "$tmp1[@]" $1
+ shift
+ fi
+
+ # If this generated any matches, we don't wnat to do in-path completion.
+
+ [[ -nmatches nm ]] || return
+
+ # No `-F' option, so we want to use `fignore'.
+
+ ignore=(-F fignore)
+fi
+
+# If we weren't given any file patterns as arguments, we trick ourselves
+# into believing that we were given the pattern `*'. This is just to simplify
+# the following code.
+
+[[ -z "$1" ]] && 1='*'
+
+# Now let's have a closer look at the string to complete.
+
+if [[ "$str[1]" = \~ ]]; then
+ # It begins with `~', so remember anything before the first slash to be able
+ # to report it to the completion code. Also get an expanded version of it
+ # (in `realpath'), so that we can generate the matches. Then remove that
+ # prefix from the string to complete, set `donepath' to build the correct
+ # paths and make sure that the loop below is run only once with an empty
+ # prefix path by setting `prepaths'.
+
+ linepath="${str%%/*}/"
+ eval realpath\=path
+ str="${str#*/}"
+ donepath=''
+ prepaths=( '' )
+else
+ # If the string does not start with a `~' we don't remove a prefix from the
+ # string.
+
+ liniepath=''
+ realpath=''
+
+ if [[ "$str[1]" = / ]]; then
+ # If it is a absolut path name, we remove the first slash and put it in
+ # `donepath' meaning that we treat it as the path that was already handled.
+ # Also, we don't use the paths from `-W'.
+
+ str="$str[2,-1]"
+ donepath='/'
+ prepaths=( '' )
+ else
+ # The common case, we just use the string as it is, unless it begins with
+ # `./' or `../' in which case we don't use the paths from `-W'.
+
+ [[ "$str" = (.|..)/* ]] && prepaths=( '' )
+ donepath=''
+ fi
+fi
+
+# First we skip over all pathname components in `str' which really exist in
+# the file-system, so that `/usr/lib/l<TAB>' doesn't offer you `lib' and
+# `lib5'. Pathname components skipped this way are taken from `str' and added
+# to `donepath'.
+
+while [[ "$str" = */* ]] do
+ [[ -e "$realpath$donepath${str%%/*}" ]] || break
+ donepath="$donepath${str%%/*}/"
+ str="${str#*/}"
+done
+
+# Now build the glob pattern. As noted above, this function behaves as if
+# a global matcher with two matching specifications are given.
+
+if [[ -matcher 1 ]]; then
+ patstr="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*."
+else
+ patstr="${str%/*}/*${str##*/}*"
+ patstr="$patstr:gs./.*/.:gs.**.*."
+fi
+
+# Finally, generate the matches. First we loop over all the paths from `-W'.
+# Note that in this loop `str' is used as a modifyable version of `patstr'
+# and `testpath' is a modifyable version of `donepath'.
+
+for prepath in "$prepaths[@]"; do
+ str="$patstr"
+ testpath="$donepath"
+
+ # The second loop tests the components of the path in `str' to get the
+ # possible matches.
+
+ while [[ "$str" = */* ]] do
+ # `rest' is the pathname after the first slash that is left. In `tmp1'
+ # we get the globbing matches for the pathname component currently
+ # handled.
+
+ rest="${str#*/}"
+ tmp1="${prepath}${realpath}${testpath}(#l)${str%%/*}(-/)"
+ tmp1=( $~tmp1 )
+
+ if [[ $#tmp1 -eq 0 ]]; then
+ # If this didn't produce any matches, we don't need to test this path
+ # any further, so continue with the next `-W' path, if any.
+
+ continue 2
+ elif [[ $#tmp1 -gt 1 ]]; then
+ # If it produced more than one match, we want to remove those which
+ # don't have possible following pathname components matching the
+ # rest of the string we are completing. (The case with only one
+ # match is handled below.)
+ # In `collect' we will collect those of the produced pathnames that
+ # have a matching possible path-suffix. In `suffixes' we build an
+ # array containing strings build from the rest of the string to
+ # complete and the glob patterns we were given as arguments.
+
+ collect=()
+ suffixes=( $rest$@ )
+ suffixes=( "${(@)suffixes:gs.**.*.}" )
+
+ # In the loop the prefixes from the `tmp1' array produced above and
+ # the suffixes we just built are used to produce possible matches
+ # via globbing.
+
+ for i in $tmp1; do
+ tmp2=( $~i/(#l)$~suffixes )
+ [[ $#tmp2 -ne 0 ]] && collect=( $collect $i )
+ done
+
+ # If this test showed that none of the matches from the glob in `tmp1'
+ # has a possible sub-path matching what's on the line, we give up and
+ # continue with the next `-W' path.
+
+ if [[ $#collect -eq 0 ]]; then
+ continue 2
+ elif [[ $#collect -ne 1 ]]; then
+ # If we have more than one possible match, this means that the
+ # pathname component currently handled is ambiguous, so we give
+ # it to the completion code.
+ # First we build the full path prefix in `tmp1'.
+
+ tmp1="$prepath$realpath$testpath"
+
+ # Now produce all matching pathnames in `collect'.
+
+ collect=( $~collect/(#l)$~suffixes )
+
+ # And then remove the common path prefix from all these matches.
+
+ collect=( ${collect#$tmp1} )
+
+ # Finally, we add all these matches with the common (unexpanded)
+ # pathprefix (the `-p' option), the path-prefix (the `-W' option)
+ # to allow the completion code to test file type, and the path-
+ # suffix (the `-s' option). We also tell the completion code that
+ # these are file names and that `fignore' should be used as usual
+ # (the `-f' and `-F' options).
+
+ for i in $collect; do
+ compadd -p "$linepath$testpath" -W "$tmp1" -s "/${i#*/}" -f "$ignore[@]" - "${i%%/*}"
+ done
+
+ # We have just finished handling all the matches from above, so we
+ # can continue with the next `-W' path.
+
+ continue 2
+ fi
+ # We reach this point if only one of the path prefixes in `tmp1'
+ # has a existing path-suffix matching the string from the line.
+ # In this case we accept this match and continue with the next
+ # path-name component.
+
+ tmp1=( "$collect[1]" )
+ fi
+ # This is also reached if the first globbing produced only one match
+ # in this case we just continue with the next pathname component, too.
+
+ tmp1="$tmp1[1]"
+ testpath="$testpath${tmp1##*/}/"
+ str="$rest"
+ done
+
+ # We are here if all pathname components except the last one (which is still
+ # not tested) are unambiguous. So we add matches with the full path prefix,
+ # no path suffix, the `-W' we are currently handling, all the matches we
+ # can produce in this directory, if any.
+
+ tmp1="$prepath$realpath$testpath"
+ suffixes=( $str$@ )
+ suffixes=( "${(@)suffixes:gs.**.*.}" )
+ tmp2=( $~tmp1(#l)$~suffixes )
+ compadd -p "$linepath$testpath" -W "$prepath$realpath$testpath" -f "$ignore[@]" - ${tmp2#$tmp1}
+done
diff --git a/Functions/Completion/_pdf b/Functions/Completion/_pdf
new file mode 100644
index 000000000..7d7756c3e
--- /dev/null
+++ b/Functions/Completion/_pdf
@@ -0,0 +1,3 @@
+function acroread
+
+_files -g '*.(pdf|PDF)'
diff --git a/Functions/Completion/_precommand b/Functions/Completion/_precommand
new file mode 100644
index 000000000..2cf661147
--- /dev/null
+++ b/Functions/Completion/_precommand
@@ -0,0 +1,5 @@
+#defcomp - nohup nice eval time rusage noglob nocorrect exec
+
+[[ -position 1 -1 ]]
+
+_normal "$@"
diff --git a/Functions/Completion/_ps b/Functions/Completion/_ps
new file mode 100644
index 000000000..6bc0643b2
--- /dev/null
+++ b/Functions/Completion/_ps
@@ -0,0 +1,3 @@
+#defcomp gs ghostview gview psnup psselect pswrap pstops pstruct lpr
+
+_files -g '*([pP][sS]|eps)'
diff --git a/Functions/Completion/_rcs b/Functions/Completion/_rcs
new file mode 100644
index 000000000..537db6278
--- /dev/null
+++ b/Functions/Completion/_rcs
@@ -0,0 +1,9 @@
+#defcomp co ci rcs
+
+[[ $COMMAND = ci || $COMMAND = rcs ]] && _files
+
+if [[ $NMATCHES -eq 0 && -d RCS && $COMMAND != ci ]]; then
+ local rep
+ rep=(RCS/$PREFIX*$SUFFIX,v(:t:s/\,v//))
+ (( $#rep )) && compadd -m $rep
+fi
diff --git a/Functions/Completion/_rlogin b/Functions/Completion/_rlogin
new file mode 100644
index 000000000..e36554f23
--- /dev/null
+++ b/Functions/Completion/_rlogin
@@ -0,0 +1,9 @@
+#defcomp rlogin rsh ssh
+
+if [[ -position 1 ]]; then
+ complist -k hosts
+elif [[ -position 2 ]]; then
+ complist -k '(-l)'
+else
+ complist -u
+fi
diff --git a/Functions/Completion/_sched b/Functions/Completion/_sched
new file mode 100644
index 000000000..ee785879b
--- /dev/null
+++ b/Functions/Completion/_sched
@@ -0,0 +1,3 @@
+#defcomp sched
+
+[[ -position 2 -1 ]] && compsub
diff --git a/Functions/Completion/_set b/Functions/Completion/_set
new file mode 100644
index 000000000..e3069f134
--- /dev/null
+++ b/Functions/Completion/_set
@@ -0,0 +1,7 @@
+#defcomp set
+
+if [[ -mcurrent -1 [-+]o ]]; then
+ complist -o
+elif [[ -current -1 -A ]]; then
+ complist -A
+fi
diff --git a/Functions/Completion/_setopt b/Functions/Completion/_setopt
new file mode 100644
index 000000000..4abb3ccee
--- /dev/null
+++ b/Functions/Completion/_setopt
@@ -0,0 +1,7 @@
+#defcomp setopt
+
+local nm=$NMATCHES
+
+complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \
+ -s '$({ unsetopt kshoptionprint; unsetopt } 2>/dev/null)'
+[[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Functions/Completion/_source b/Functions/Completion/_source
new file mode 100644
index 000000000..94e3fbeb1
--- /dev/null
+++ b/Functions/Completion/_source
@@ -0,0 +1,7 @@
+#defcomp source
+
+if [[ -position 2 -1 ]]; then
+ compsub
+else
+ _files
+fi
diff --git a/Functions/Completion/_strip b/Functions/Completion/_strip
new file mode 100644
index 000000000..6962ac455
--- /dev/null
+++ b/Functions/Completion/_strip
@@ -0,0 +1,2 @@
+#defcomp strip
+_files -g '*(*)'
diff --git a/Functions/Completion/_stty b/Functions/Completion/_stty
new file mode 100644
index 000000000..6b54b5007
--- /dev/null
+++ b/Functions/Completion/_stty
@@ -0,0 +1,16 @@
+#defcomp stty
+
+if [[ -mcurrent -1 \
+ (*erase|discard|status|dsusp|intr|kill|lnext|quit|reprint|start|s*p) ]]
+then
+ compadd -m -Q '^-' '^h' '^?' '^c' '^u'
+else
+ [[ -string '-' || -string '+' ]]
+ compadd -m rows columns intr quit erase kill eof eol \
+ eol2 start stop susp dsusp reprint discard werase lnext \
+ parenb parodd cs8 cstopb hupcl cread clocal parext \
+ ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl iuclc \
+ ixon ixany ixoff imaxbel isig icanon xcase echo echoe echok \
+ echonl noflsh tostop echoctl echoprt echoke flusho pending iexten \
+ opost olcuc onlcr ocrnl onocr onlret ofill ofdel
+fi
diff --git a/Functions/Completion/_subscript b/Functions/Completion/_subscript
new file mode 100644
index 000000000..2b827a117
--- /dev/null
+++ b/Functions/Completion/_subscript
@@ -0,0 +1,4 @@
+#defcomp -subscript-
+
+_compalso -math- "$@"
+[[ ${(Pt)${COMMAND}} = assoc* ]] && complist -k "( ${(kP)${COMMAND}} )"
diff --git a/Functions/Completion/_tar b/Functions/Completion/_tar
new file mode 100644
index 000000000..91767e44d
--- /dev/null
+++ b/Functions/Completion/_tar
@@ -0,0 +1,11 @@
+#defcomp tar
+
+local nm=$NMATCHES tf="$2"
+
+if [[ ( -mword 1 *t*f* || -mword 1 *x*f* ) && -position 3 100000 ]]; then
+ complist -k "( $(tar tf $tf) )"
+elif [[ -mword 1 *c*f* && -position 3 100000 ]]; then
+ _files
+elif [[ -mcurrent -1 *f* && -position 2 ]]; then
+ _files -g '*.(tar|TAR)'
+fi
diff --git a/Functions/Completion/_tex b/Functions/Completion/_tex
new file mode 100644
index 000000000..1f645e2a2
--- /dev/null
+++ b/Functions/Completion/_tex
@@ -0,0 +1,3 @@
+#defcomp tex latex slitex
+
+_files -g '*.(tex|TEX|texinfo|texi)'
diff --git a/Functions/Completion/_trap b/Functions/Completion/_trap
new file mode 100644
index 000000000..59e81c589
--- /dev/null
+++ b/Functions/Completion/_trap
@@ -0,0 +1,7 @@
+#defcomp trap
+
+if [[ -position 1 ]]; then
+ complist -c
+else
+ complist -k signals
+fi
diff --git a/Functions/Completion/_uncompress b/Functions/Completion/_uncompress
new file mode 100644
index 000000000..e25805d50
--- /dev/null
+++ b/Functions/Completion/_uncompress
@@ -0,0 +1,3 @@
+#defcomp uncompress zmore
+
+_files -g '*.Z'
diff --git a/Functions/Completion/_unhash b/Functions/Completion/_unhash
new file mode 100644
index 000000000..fe40c25a2
--- /dev/null
+++ b/Functions/Completion/_unhash
@@ -0,0 +1,6 @@
+#defcomp unhash
+
+[[ -mword 1 -*d* ]] && complist -n
+[[ -mword 1 -*a* ]] && complist -a
+[[ -mword 1 -*f* ]] && complist -F
+[[ ! -mword 1 -* ]] && complist -m
diff --git a/Functions/Completion/_unsetopt b/Functions/Completion/_unsetopt
new file mode 100644
index 000000000..90d642b51
--- /dev/null
+++ b/Functions/Completion/_unsetopt
@@ -0,0 +1,7 @@
+#defcomp unsetopt
+
+local nm=$NMATCHES
+
+complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \
+ -s '$({ unsetopt kshoptionprint; setopt } 2>/dev/null)'
+[[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o
diff --git a/Functions/Completion/_vars b/Functions/Completion/_vars
new file mode 100644
index 000000000..7153b6f38
--- /dev/null
+++ b/Functions/Completion/_vars
@@ -0,0 +1,3 @@
+#defcomp -math- getopts read unset vared
+
+complist -v
diff --git a/Functions/Completion/_vars_eq b/Functions/Completion/_vars_eq
new file mode 100644
index 000000000..fcbb0148c
--- /dev/null
+++ b/Functions/Completion/_vars_eq
@@ -0,0 +1,3 @@
+#defcomp declare export integer local readonly typeset
+
+complist -v -q -S '='
diff --git a/Functions/Completion/_wait b/Functions/Completion/_wait
new file mode 100644
index 000000000..34bc53ee3
--- /dev/null
+++ b/Functions/Completion/_wait
@@ -0,0 +1,4 @@
+#defcomp wait
+
+complist -P '%' -j
+complist -y _kill_helper -s '`ps 2>/dev/null | tail +2 | cut -c1-5`'
diff --git a/Functions/Completion/_which b/Functions/Completion/_which
new file mode 100644
index 000000000..324256e3d
--- /dev/null
+++ b/Functions/Completion/_which
@@ -0,0 +1,3 @@
+#defcomp which whence where type
+
+complist -caF
diff --git a/Functions/Completion/_x_options b/Functions/Completion/_x_options
new file mode 100644
index 000000000..cc469286d
--- /dev/null
+++ b/Functions/Completion/_x_options
@@ -0,0 +1,5 @@
+#defpatcomp */X11/*
+
+# A simple pattern completion, just as an example.
+
+complist -J options -k '(-display -name -xrm)'
diff --git a/Functions/Completion/_xfig b/Functions/Completion/_xfig
new file mode 100644
index 000000000..fcd2bba9f
--- /dev/null
+++ b/Functions/Completion/_xfig
@@ -0,0 +1,3 @@
+#defcomp xfig
+
+_files -g '*.fig'
diff --git a/Functions/Completion/_zftp b/Functions/Completion/_zftp
new file mode 100644
index 000000000..4ff707cc6
--- /dev/null
+++ b/Functions/Completion/_zftp
@@ -0,0 +1,50 @@
+#defpatcomp zf*
+
+# Don't try any more completion after this.
+COMPSKIP=1
+
+# Completion for zftp builtin and zf* functions. The functions
+# zfcd_match and zfget_match (used for old-style completion)
+# need to be installed for remote file and directory completion to work.
+
+local subcom
+
+if [[ $COMMAND = zftp ]]; then
+ if [[ $CURRENT -eq 1 ]]; then
+ compadd -m open params user login type ascii binary mode put \
+ putat get getat append appendat ls dir local remote mkdir rmdir
+ return
+ fi
+ subcom=$1
+else
+ subcom=$COMMAND
+fi
+
+case $subcom in
+ *(cd|ls|dir))
+ # complete remote directories; we could be smarter about hiding prefixes
+ zfcd_match $PREFIX $SUFFIX
+ (( $#reply )) && compadd -m -S/ -q $reply
+ ;;
+
+ *(get(|at)|gcp|delete|remote))
+ # complete remote files
+ zfget_match $PREFIX $SUFFIX
+ (( $#reply )) && compadd -F fignore -m $reply
+ ;;
+
+ *(put(|at)|pcp))
+ # complete local files
+ _files
+ ;;
+
+ *(open|anon|params))
+ # complete hosts: should do cleverer stuff with user names
+ complist -k hosts
+ ;;
+
+ *)
+ # dunno... try ordinary completion after all.
+ unset COMPSKIP
+ ;;
+esac
diff --git a/Functions/Completion/_zle b/Functions/Completion/_zle
new file mode 100644
index 000000000..bb1102e74
--- /dev/null
+++ b/Functions/Completion/_zle
@@ -0,0 +1,7 @@
+#defcomp zle
+
+if [[ -word 1 -N && -position 3 ]]; then
+ complist -F
+else
+ complist -b
+fi
diff --git a/Functions/Completion/_zmodload b/Functions/Completion/_zmodload
new file mode 100644
index 000000000..112acb57c
--- /dev/null
+++ b/Functions/Completion/_zmodload
@@ -0,0 +1,9 @@
+#defcomp zmodload
+
+if [[ -mword 1 -*(a*u|u*a)* || -mword 1 -*a* && -position 3 -1 ]]; then
+ complist -B
+elif [[ -mword 1 -*u* ]]; then
+ complist -s '$(zmodload)'
+else
+ complist -s '${^module_path}/*(N:t:r)'
+fi
diff --git a/Functions/Completion/dump b/Functions/Completion/dump
new file mode 100644
index 000000000..ad5547bea
--- /dev/null
+++ b/Functions/Completion/dump
@@ -0,0 +1,89 @@
+# This is a file to be sourced to dump the definitions for new-style
+# completion defined by 'init' in the same directory. For best results,
+# it should be run immediately after init, before any of the completions
+# have been autoloaded. The output should be directed into the "init.dump"
+# in the same directory as init. If you rename init, just stick .dump onto
+# the end of whatever you have called it and put it in the same directory.
+#
+# You will need to update the dump every time you add a new completion.
+# To do this, simply remove the .dump file, start a new shell, and
+# create the .dump file as before.
+#
+# It relies on KSH_ARRAYS not being set.
+
+# Print the number of files used for completion. This is used in init
+# to see if auto-dump should re-dump the dump-file.
+
+_d_file=${COMPDUMP-${0:h}/init.dump}
+
+_d_files=( ${^~fpath}/_*~*~ )
+
+print "#files: $#_d_files" > $_d_file
+
+unset _d_files
+
+# First dump the arrays comps and patcomps. The quoting hieroglyphyics
+# ensure that a single quote inside a variable is itself correctly quoted.
+
+print "comps=(" >> $_d_file
+for _d_f in ${(k)comps}; do
+ print -r - "'${_d_f//\'/'\\''}'" "'${comps[$_d_f]//\'/'\\''}'"
+done >> $_d_file
+print ")" >> $_d_file
+
+if (( $#patcomps )); then
+ print "\npatcomps=(" >> $_d_file
+ for _d_f in "$patcomps[@]"; do
+ print -r - "'${_d_f//\'/'\\''}'"
+ done >> $_d_file
+ print ")" >> $_d_file
+fi
+
+print >> $_d_file
+
+# Now dump the key bindings. We dump all bindings for zle widgets
+# whose names start with a underscore.
+# We need both the zle -C's and the bindkey's to recreate.
+
+_d_bks=()
+zle -lL |
+ while read -rA _d_line; do
+ if [[ ${_d_line[5]} = _* ]]; then
+ print -r - ${_d_line}
+ _d_bks=($_d_bks ${_d_line[3]})
+ fi
+ done >> $_d_file
+bindkey |
+ while read -rA _d_line; do
+ if [[ ${_d_line[2]} = (${(j.|.)~_d_bks}) ]]; then
+ print -r "bindkey '${_d_line[1][2,-2]}' ${_d_line[2]}"
+ fi
+ done >> $_d_file
+
+print >> $_d_file
+
+
+# Autoloads: whence -w produces "_d_foo: function", so look for
+# all functions beginning with `_'.
+
+_d_als=($(whence -wm '_*' |
+while read -rA _d_line; do
+ [[ ${_d_line[2]} = function ]] && print -r - ${_d_line[1]%:}
+done))
+
+# print them out: about six to a line looks neat
+
+while (( $#_d_als )); do
+ print -n autoload
+ for (( _i = 0; _i < 6; _i++ )); do
+ if (( $#_d_als )); then
+ print -n " $_d_als[1]"
+ shift _d_als
+ fi
+ done
+ print
+done >> $_d_file
+
+print >> $_d_file
+
+unset _d_line _d_zle _d_bks _d_als _d_f _f_file
diff --git a/Functions/Completion/init b/Functions/Completion/init
new file mode 100644
index 000000000..a40c5f61b
--- /dev/null
+++ b/Functions/Completion/init
@@ -0,0 +1,253 @@
+# Initialisation for new style completion. This mainly contains some helper
+# function and aliases. Everything else is split into different files in this
+# directory that will automatically be made autoloaded (see the end of this
+# file).
+# The names of the files that will be considered for autoloading have to
+# start with two underscores (like `__setopt).
+# The first line of these files will be read and has to say what should be
+# done with its contents:
+#
+# `#function <names ...>'
+# if the first line looks like this, the file is
+# autoloaded as a function and that function will
+# be called to generate the matches when completing
+# for one of the commands whose <name> is given
+#
+# `#array <names ...>'
+# with a first line like this, the filename is taken as
+# the name of an array; when trying to generate matches
+# for the command <name>, the file will be sourced and
+# should define this array, the builtin `complist' will
+# then be called with the elements of this array as its
+# arguments; this is intended for simple definitions
+# for which you don't need a shell function
+#
+# `#pattern-function <pattern>'
+# this defines a function that should be called to generate
+# matches for commands whose name matches <pattern>; note
+# that only one pattern may be given
+#
+# `#pattern-array <pattern>'
+# like `#pattern-function' but defining an array
+#
+# `#key-function <style> [ <key-sequence> ... ]
+# this is used to bind special completions to all the given
+# <key-sequence>(s). The <style> is the name of one of the built-in
+# completion widgets (complete-word, delete-char-or-list,
+# expand-or-complete, expand-or-complete-prefix, list-choices,
+# menu-complete, menu-expand-or-complete, or reverse-menu-complete).
+# This creates a widget behaving like <style> so that the
+# completions are chosen as given in the the rest of the file,
+# rather than by the context. The widget has the same name as
+# the autoload file and can be bound using bindkey in the normal way.
+#
+# `#key-array <style> [ <key-sequence> ... ]
+# like `#key-function', but defining an array instead
+#
+# `#helper'
+# this is for helper functions that are not used to
+# generate matches, but should automatically be loaded
+# when they are called
+#
+# Note that no white space is allowed between the `#' and the rest of
+# the string.
+
+
+# An associative array for completions definitions. The keys of the entries
+# are the names of the command, the values are names of functions or variables
+# that are to be used to generate the matches.
+# Pattern completions will be stored in an normal array named `patcomps'.
+# Completion definitions bound directly to keys are stored in an assoc array
+# named `keycomps'.
+
+typeset -A comps
+typeset -A keycomps
+
+
+# This may be used to define completion handlers. The first argument is the
+# name of the function or variable containing the definition, the other
+# arguments are the command names for which this definition should be used.
+# With only one argument the function/variable-name __$1 is used.
+# If given the `-a' option, the function is defined as being autoloaded.
+
+defcomp() {
+ local name autol=''
+
+ if [[ "$1" = -a ]]; then
+ shift
+ autol=yes
+ fi
+ if [[ $# -eq 1 ]]; then
+ comps[$1]="__$1"
+ [[ -z "$autol" ]] || autoload "__$1"
+ else
+ name="$1"
+ shift
+ for i; do
+ comps[$i]="$name"
+ done
+ [[ -z "$autol" ]] || autoload "$name"
+ fi
+}
+
+
+# Almost like `defcomp', but this always gets two arguments: the name of a
+# variable or function describing what should be completed and the pattern
+# that will be compared to the command names for which completion is attempted.
+
+defpatcomp() {
+ if [[ "$1" = -a ]]; then
+ shift
+ autoload "$1"
+ fi
+ if (( $+patcomps )) then
+ patcomps=("$patcomps[@]" "$2 $1" )
+ else
+ patcomps=( "$2 $1" )
+ fi
+}
+
+
+# This is used to define completion handlers directly bound to keys. The
+# first argument is as for `defcomp', giving the handler. The second
+# argument is the name of one of the built-in completion widgets. Any
+# remaining arguments are used as key sequences to bind the widget.
+# Typing that key sequence will complete the word the cursor is on
+# according to the completion definition given and will behave as if the
+# built-in completion widget was used.
+
+defkeycomp() {
+ local name
+
+ if [[ "$1" = -a ]]; then
+ shift
+ autoload "$1"
+ name="$1"
+ elif [[ "${1[1]}" = ' ' ]]; then
+ name="${1:t}"
+ else
+ name="$1"
+ fi
+ keycomps[$name]="$1"
+ shift
+ zle -C "$name" "$1" __main_key_complete
+ shift
+ while (( $# )); do
+ bindkey "$1" "$name"
+ shift
+ done
+}
+
+# These can be used to easily save and restore the state of the special
+# variables used by the completion code.
+
+alias compsave='local _oprefix _oiprefix _oargv _ocurrent; \
+ _oprefix="$PREFIX"; \
+ _oiprefix="$IPREFIX"; \
+ _oargv=( "$@" ); \
+ _ocurrent="$CURRENT"'
+alias compreset='PREFIX="$_oprefix"; \
+ IPREFIX="$_oiprefix"; \
+ argv=( "$_oargv[@]" ); \
+ CURRENT="$_ocur"'
+
+
+# This is an easy way to get completion for sub-commands.
+
+alias compsub='__normal "$@" || return 1'
+
+
+# This searches $1 in the array for normal completions and calls the result.
+
+compalso() {
+ local tmp
+
+ tmp="$comps[$1]"
+ [[ -z "$tmp" ]] || callcomplete comps "$1" "$@"
+}
+
+
+# This generates matches. The first argument is the name of one of the
+# arrays containing completion definitions. The second argument is the index
+# into this array. The other arguments are the positional parameters to give
+# to the completion function (containing the arguments from the command line).
+
+callcomplete() {
+ local file def
+
+ # Get the definition from the array.
+
+ eval "def=\$${1}[${2}]"
+
+ # If the definition starts with a space then this means that we should
+ # source a file to get the definition for an array.
+
+ if [[ "$def[1]" = ' ' ]]; then
+ # The definition starts with a space, so source the file and change
+ # the definition.
+
+ file="$def[2,-1]"
+ builtin . "$file"
+ def="${file:t}"
+ eval "${1}[${2}]=$def"
+ fi
+
+ # Get rid of the array-name and -index.
+
+ shift 2
+ if [[ ${(P)+def} -eq 1 ]]; then
+ # It is a parameter name, call complist directly.
+
+ complist "${(@P)def}"
+ else
+ # Otherwise it's a function name, call this function.
+
+ "$def" "$@"
+ fi
+}
+
+
+# Now we make the files automatically autoloaded.
+
+local dir file line func
+
+for dir in $fpath; do
+ [[ $dir = . ]] && continue
+ for file in $dir/__*~*~(N); do
+ read -rA line < $file
+ func=$line[1]
+ shift line
+ if [[ $func = '#function' ]]; then
+ defcomp -a ${file:t} "${line[@]}"
+ elif [[ $func = '#array' ]]; then
+ defcomp " $file" "${line[@]}"
+ elif [[ $func = '#pattern-function' ]]; then
+ defpatcomp -a ${file:t} "${line[@]}"
+ elif [[ $func = '#pattern-array' ]]; then
+ defcomp " $file" "${line[@]}"
+ elif [[ $func = '#key-function' ]]; then
+ defkeycomp -a "${file:t}" "${line[@]}"
+ elif [[ $func = '#key-array' ]]; then
+ defkeycomp " $file" "${line[@]}"
+ elif [[ $func = '#helper' ]]; then
+ autoload ${file:t}
+ fi
+ done
+done
+
+
+# Finally we make all this be called by changing the key bindings.
+
+bindkey | while read -A line; do
+ if [[ "$line[2]" = complete-word ||
+ "$line[2]" = delete-char-or-list ||
+ "$line[2]" = expand-or-complete ||
+ "$line[2]" = expand-or-complete-prefix ||
+ "$line[2]" = list-choices ||
+ "$line[2]" = menu-complete ||
+ "$line[2]" = menu-expand-or-complete ||
+ "$line[2]" = reverse-menu-complete ]]; then
+ zle -C __complete_$line[2] $line[2] __main_complete
+ bindkey "${line[1][2,-2]}" __complete_$line[2]
+ fi
+ done
diff --git a/Functions/allopt b/Functions/allopt
new file mode 100644
index 000000000..604e76198
--- /dev/null
+++ b/Functions/allopt
@@ -0,0 +1,29 @@
+# This function lists options with the no's in front removed for
+# improved comprehension, i.e. `norcs off' becomes `rcs on'.
+# The format is otherwise like that with `kshoptionprint' set,
+# i.e. you can see all options whether on or off.
+# It can take a list of option names or parts thereof to search for
+# via egrep.
+#
+# Written by Sweth Chandramouli with hacks by Bart Schaefer.
+
+listalloptions () {
+ emulate -R zsh
+ builtin setopt localoptions kshoptionprint
+ local OPT_NAME OPT_PAIR OPT_VALUE
+ for OPT_PAIR in "${(f)$(builtin setopt)}" ; do
+ OPT_VALUE=${OPT_PAIR##* }
+ OPT_NAME=${OPT_PAIR%% *}
+ if [[ ${OPT_NAME#no} != ${OPT_NAME} ]] ; then
+ OPT_VALUE=${(L)${${OPT_VALUE:s/on/OFF}:s/off/on}} &&
+ OPT_NAME=${OPT_NAME#no};
+ fi;
+ echo ${(r:21:)OPT_NAME} ${OPT_VALUE}
+ done
+}
+
+if [[ -n $@ ]]; then
+ listalloptions | egrep "${(j.|.)@}"
+else
+ listalloptions
+fi
diff --git a/Functions/zless b/Functions/zless
new file mode 100644
index 000000000..809ce35c7
--- /dev/null
+++ b/Functions/zless
@@ -0,0 +1,37 @@
+#!/usr/bin/zsh -f
+#
+# zsh function script to run less on various inputs, decompressing as required.
+# Author: Phil Pennock. zsh-hacks@athenaeum.demon.co.uk
+# Modified by Bart Schaefer.
+# Thanks to zefram@fysh.org for a great deal of help in sorting this out,
+# ie wrt syntax for unsetting members of arrays and eval "$(...)" when I
+# asked for something better than . =(...)
+#
+# Use -zforce to pass through a display-formatting command
+# zless -zforce 'bzip2 -dc' foo-no-dotbz2
+# zless -zforce 'od -hc' foo-binfile
+#
+# If you can understand all of this without reference to zshexpn(1)
+# and zshparam(1) then you either have a photographic memory or you
+# need to get out more.
+#
+
+emulate -R zsh
+setopt localoptions
+
+[[ $# -ge 1 ]] || return
+local lessopts
+set -A lessopts
+integer i=1 loi=1
+while ((i <= $#))
+do
+ case $argv[i] in
+ -zforce) argv[i,i+2]=("=($argv[i+1] \"$argv[i+2]\")"); ((++i));;
+ -*) lessopts[loi++]=\"$argv[i]\"; argv[i]=(); continue;;
+ *.(gz|Z)) argv[i]="=(zcat \"$argv[i]\")";;
+ *.bz2) argv[i]="=(bzip2 -dc \"$argv[i]\")";;
+ *.bz) argv[i]="=(bzip -dc \"$argv[i]\")";;
+ esac
+ ((++i))
+done
+eval command less $lessopts $*
diff --git a/Misc/new-completion-examples b/Misc/new-completion-examples
new file mode 100644
index 000000000..659679891
--- /dev/null
+++ b/Misc/new-completion-examples
@@ -0,0 +1,453 @@
+# Define a new widget behaving like `expand-or-complete' but calling the
+# function `main-complete' to generate matches.
+
+zle -c my-comp expand-or-complete main-complete
+
+bindkey '\C-i' my-comp
+
+
+# Below is a proposed main loop and the things it needs.
+
+# One associative array for normal completions and one for patterns.
+
+typeset -A comps
+
+
+# These may be used to define completion handlers. First argument is the
+# name of the function/variable containing the definition, the other
+# arguments are the command names for which this definition should be used.
+# With only one argument the function/variable-name __$1 is used.
+
+defcomp() {
+ local v
+
+ if [[ $# -eq 1 ]] then
+ comps[$1]="__$1"
+ else
+ v="$1"
+ shift
+ for i; do
+ comps[$i]="$v"
+ done
+ fi
+}
+
+defpatcomp() {
+ if [[ ${+patcomps} == 1 ]] then
+ patcomps=("$patcomps[@]" "$2 $1" )
+ else
+ patcomps=( "$2 $1" )
+ fi
+}
+
+
+# These can be used to easily save and restore the state of the special
+# variables used by the completion code.
+
+alias compsave='local _opre _oipre _oargs _ocur;_opre="$PREFIX";_oipre="$IPREFIX";_oargs=( "$@" );_ocur="$CURRENT"'
+alias compreset='PREFIX="$_opre";IPREFIX="$_oipre";argv=( "$_oargs[@]" );CURRENT="$_ocur"'
+
+# This is an easy way to get completion for sub-commands.
+
+alias compsub='do-complete "$@" || return 1'
+
+# This searches $1 in the array for normal completions and calls the result.
+
+compalso() {
+ 1="$comps[$1]"
+ [[ -z "$1" ]] || call-complete "$@"
+}
+
+# This generates matches. The first argument is something we got from one
+# of the associative arrays above. This is expected to be either the name
+# of a variable in which case we use its value as arguments to complist,
+# or it is the name of a function in which case we call this function with
+# the arguments from the command line as its arguments.
+
+call-complete() {
+ local var
+
+ eval var\=\$\{\+$1\}
+ if [[ "$var" == 0 ]] then
+ "$@"
+ else
+ eval complist \$\{${1}\[\@\]\}
+ fi
+}
+
+# The main loop of the competion code. This is what is called when TAB is
+# pressed. The completion code gives us the special variables and the
+# arguments from the command line are gives as positional parameters.
+
+main-complete() {
+ emulate -R zsh
+ local comp
+ setopt localoptions nullglob rcexpandparam globdots
+ unsetopt markdirs globsubst shwordsplit nounset
+
+ # An entry for `--first--' is the replacement for `compctl -T'
+ # The `|| return 1' is used throughout: if a function producing matches
+ # returns non-zero this is interpreted as `do not try to produce more matches'
+ # (this is the replacement for `compctl -t').
+
+ comp="$comps[--first--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+
+ # For arguments we use the `do-complete' function below called via the
+ # convenience alias `compsub'.
+
+ if [[ $CONTEXT == argument || $CONTEXT == command ]] then
+ compsub
+ else
+ # Let's see if we have a special completion definition for the other
+ # possible contexts.
+
+ comp=''
+
+ case $CONTEXT in
+ redirect) comp="$comps[--redir--]";;
+ math) comp="$comps[--math--]";;
+ subscript) comp="$comps[--subscr--]";;
+ value) comp="$comps[--value--]";;
+ condition) comp="$comps[--cond--]";;
+ esac
+
+ # If not, we use default completion, if any.
+
+ [[ -z "$comp" ]] && comp="$comps[--default--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+ fi
+}
+
+# This does completion for a command (in command position and for the
+# arguments).
+
+do-complete() {
+ local comp cmd1 cmd2 pat val
+
+ # Completing in command position? If not we set up `cmd1' and `cmd2' as
+ # two strings we have search in the completion definition arrays (e.g.
+ # a path and the last path name component).
+
+ if [[ $CONTEXT == command ]] then
+ comp="$comps[--command--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+ return 0
+ elif [[ "$COMMAND[1]" == '=' ]] then
+ eval cmd1\=$COMMAND
+ cmd2="$COMMAND[2,-1]"
+ elif [[ "$COMMAND" == */* ]] then
+ cmd1="$COMMAND"
+ cmd2="${COMMAND:t}"
+ else
+ cmd1="$COMMAND"
+ eval cmd2=$(whence -p $COMMAND)
+ fi
+
+ # See if there are any matching pattern completions.
+
+ for i in "$patcomps[@]"; do
+ pat="${i% *}"
+ val="${i#* }"
+ if [[ "$cmd1" == $~pat || "$cmd2" == $~pat ]] then
+ call-complete "$val" "$@" || return 1
+ fi
+ done
+
+ # Now look up the two names in the normal completion array.
+
+ comp="${comps[$cmd1]:-$comps[$cmd2]}"
+
+ # And generate the matches, probably using default completion.
+
+ [[ -z "$comp" ]] && comp="$comps[--default--]"
+ [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1
+}
+
+# Do sub-completion for pre-command modifiers.
+
+defcomp __precmd - noglob nocorrect exec command builtin
+__precmd() {
+ COMMAND="$1"
+ shift
+ (( CURRENT-- ))
+ if [[ CURRENT -eq 0 ]] then
+ CONTEXT=command
+ else
+ CONTEXT=argument
+ fi
+ compsub
+}
+
+# Utility function for in-path completion.
+# First argument should be an complist-option (e.g. -f, -/, -g). The other
+# arguments should be glob patterns, one per argument.
+# E.g.: files -g '*.tex' '*.texi'
+# This is intended as a replacement for `complist -f', `complist -/', and
+# `complist -g ...' (but don't use it with other options).
+# This function behaves as if you have a matcher definition like:
+# compctl -M 'r:|[-.,_/]=* r:|=* m:{a-z}={A-Z} m:-=_ m:.=,'
+# so you may want to modify this.
+
+pfiles() {
+ local nm str pa pre epre a b c s rest
+
+ setopt localoptions nullglob rcexpandparam globdots extendedglob
+ unsetopt markdirs globsubst shwordsplit nounset
+
+ nm=$NMATCHES
+ if [[ $# -eq 0 ]] then
+ complist -f
+ elif [[ "$1" = -g ]] then
+ complist -g "$argv[2,-1]"
+ shift
+ else
+ complist $1
+ shift
+ fi
+ [[ -nmatches nm ]] || return
+
+ str="$PREFIX*$SUFFIX"
+
+ [[ -z "$1" ]] && 1='*'
+ if [[ $str[1] = \~ ]] then
+ pre="${str%%/*}/"
+ eval epre\=$pre
+ str="${str#*/}"
+ pa=''
+ else
+ pre=''
+ epre=''
+ if [[ $str[1] = / ]] then
+ str="$str[2,-1]"
+ pa='/'
+ else
+ pa=''
+ fi
+ fi
+ str="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*."
+ while [[ "$str" = */* ]] do
+ rest="${str#*/}"
+ a="${epre}${pa}(#l)${str%%/*}(-/)"
+ a=( $~a )
+ if [[ $#a -eq 0 ]] then
+ return
+ elif [[ $#a -gt 1 ]] then
+ c=()
+ s=( $rest$@ )
+ s=( "${(@)s:gs.**.*.}" )
+ for i in $a; do
+ b=( $~i/(#l)$~s )
+ eval b\=\( \$\{b:/\*\(${(j:|:)fignore}\)\} \)
+ [[ $#b -ne 0 ]] && c=( $c $i )
+ done
+ if [[ $#c -eq 0 ]] then
+ return
+ elif [[ $#c -ne 1 ]] then
+ a="$epre$pa"
+ c=( $~c/(#l)$~s )
+ eval c\=\( \$\{c:/\*\(${(j:|:)fignore}\)\} \)
+ c=( ${c#$a} )
+ for i in $c; do
+ compadd -p "$pre$pa" -W "$a" -s "/${i#*/}" -f "${i%%/*}"
+ done
+ return
+ fi
+ a=( "$c[1]" )
+ fi
+ a="$a[1]"
+ pa="$pa${a##*/}/"
+ str="$rest"
+ done
+ a="$epre$pa"
+ s=( $str$@ )
+ s=( "${(@)s:gs.**.*.}" )
+ b=( $~a(#l)$~s )
+ eval b\=\( \$\{b:/\*\(${(j:|:)fignore}\)\} \)
+ compadd -p "$pre$pa" -W "$epre$pa" -f ${b#$a}
+}
+
+# Utility function for completing files of a given type or any file.
+# In many cases you will want to call this one instead of pfiles().
+
+files() {
+ local nm
+
+ nm=$NMATCHES
+ pfiles "$@"
+
+ [[ $# -ne 0 && -nmatches nm ]] && pfiles
+}
+
+# Simple default, command, and math completion defined with variables.
+
+defcomp __default --default--
+__default() {
+ files
+}
+
+defcomp __command --command--
+__command=( -c )
+
+defcomp __math --math--
+__math=( -v )
+
+defcomp __subscr --subscr--
+__subscr() {
+ compalso --math-- "$@"
+ # ...probably other stuff
+}
+
+# A simple pattern completion, just as an example.
+
+defpatcomp __x_options '*/X11/*'
+__x_options() {
+ complist -J options -k '(-display -name -xrm)'
+}
+
+# A better example: completion for `find'.
+
+defcomp find
+__find() {
+ compsave
+
+ if [[ -mbetween -(ok|exec) \\\; ]] then
+ compsub
+ elif [[ -iprefix - ]] then
+ complist -s 'daystart {max,min,}depth follow noleaf version xdev \
+ {a,c,}newer {a,c,m}{min,time} empty false {fs,x,}type gid inum links \
+ {i,}{l,}name {no,}{user,group} path perm regex size true uid used \
+ exec {f,}print{f,0,} ok prune ls'
+ compreset
+ elif [[ -position 1 ]] then
+ complist -g '. ..'
+ files -g '(-/)'
+ elif [[ -mcurrent -1 -((a|c|)newer|fprint(|0|f)) ]] then
+ files
+ elif [[ -current -1 -fstype ]] then
+ complist -k '(ufs 4.2 4.3 nfs tmp mfs S51K S52K)'
+ elif [[ -current -1 -group ]] then
+ complist -k groups
+ elif [[ -current -1 -user ]] then
+ complist -u
+ fi
+}
+
+# Various completions...
+
+defcomp __gunzip gunzip zcat
+__gunzip() {
+ files -g '*.[gG][z]'
+}
+
+defcomp gzip
+__gzip() {
+ files -g '*~*.[gG][zZ]'
+}
+
+defcomp xfig
+__xfig() {
+ files -g '*.fig'
+}
+
+defcomp __make make gmake
+__make() {
+ complist -s "\$(awk '/^[a-zA-Z0-9][^/ ]+:/ {print \$1}' FS=: [mM]akefile)"
+}
+
+defcomp __ps gs ghostview gview psnup psselect pswrap pstops pstruct lpr
+__ps() {
+ files -g '*([pP][sS]|eps)'
+}
+
+defcomp __which which whence
+__which=( -caF )
+
+defcomp __rlogin rlogin rsh ssh
+__rlogin() {
+ if [[ -position 1 ]] then
+ complist -k hosts
+ elif [[ -position 2 ]] then
+ complist -k '(-l)'
+ elif [[ -position 3 && -word 1 artus ]] then
+ complist -k '(puck root)'
+ fi
+}
+
+defcomp __dvi xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype
+__dvi() {
+ files -g '*.(dvi|DVI)'
+}
+
+defcomp __dirs rmdir df du dircmp cd
+__dirs() {
+ files -/ '*(-/)'
+}
+
+defcomp __jobs fg bg jobs
+__jobs=(-j -P '%?')
+
+defcomp kill
+__kill() {
+ if [[ -iprefix '-' ]] then
+ complist -k signals
+ else
+ complist -P '%?' -j
+ fi
+}
+
+defcomp __uncompress uncompress zmore
+__uncompress() {
+ files -g '*.Z'
+}
+
+defcomp compress
+__compress() {
+ files -g '*~*.Z'
+}
+
+defcomp __tex tex latex glatex slitex gslitex
+__tex() {
+ files -g '*.(tex|TEX|texinfo|texi)'
+}
+
+defcomp __options setopt unsetopt
+__options=(-M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o)
+
+defcomp __funcs unfunction
+__funcs=(-F)
+
+defcomp __aliases unalias
+__aliases=(-a)
+
+defcomp __vars unset
+__vars=(-v)
+
+defcomp __enabled disable
+__enabled=(-FBwa)
+
+defcomp __disabled enable
+__disabled=(-dFBwa)
+
+defcomp __pdf acroread
+__pdf() {
+ files -g '*.(pdf|PDF)'
+}
+
+defcomp tar
+__tar() {
+ local nm tf
+ compsave
+
+ tf="$2"
+ nm=$NMATCHES
+ if [[ ( -mword 1 *t*f* || -mword 1 *x*f* ) && -position 3 100000 ]] then
+ complist -k "( $(tar tf $tf) )"
+ compreset
+ elif [[ -mword 1 *c*f* && -position 3 100000 ]] then
+ files
+ compreset
+ elif [[ -mcurrent -1 *f* && -position 2 ]] then
+ files -g '*.(tar|TAR)'
+ fi
+}
diff --git a/Misc/zftp-functions b/Misc/zftp-functions
new file mode 100644
index 000000000..a07e46d72
--- /dev/null
+++ b/Misc/zftp-functions
@@ -0,0 +1,1281 @@
+# zftp is a loadable module implementing an FTP client as a builtin
+# command so that you can use the shell command language and line
+# editing to make life easier. If your system has dynamically
+# load libraries and zsh was compiled to use them, it is probably
+# somewhere where it can be loaded at run time. Otherwise, it depends
+# whether the shell was compiled with zftp already built into it.
+#
+# Here is a suite of functions, plus assorted other code, to make
+# zftp work smoothly.
+#
+# Completion is implemented in a fairly natural way, except that
+# very little support has been provided for non-UNIX remote hosts.
+# On such machines, the safest thing to do is only try to complete
+# files in the current directory; this should be OK.
+#
+# Remote globbing for commands which retrieve files is also
+# implemented. This can be done in two different ways. The default
+# is for zsh to do the globbing locally. The advantage is that full
+# zsh pattern matching (respecting the setting of extendedglob) is
+# possible, and no assumption (apart from the restrictions on
+# directory handling noted above) is made about the behaviour of the
+# server. The disadvantage is that the entire filename list for the
+# current directory must be retrieved, and then zsh must laboriously
+# do pattern matching against every file, so it is potentially slow
+# for large directories. Only the non-directory part of file names is
+# globbed.
+#
+# The alternative will be used if $zfrglob has non-zero length.
+# Zsh then sends the pattern to the server for globbing. Best of
+# luck.
+#
+# To support remote globbing, some functions have been aliased
+# with 'noglob' in front. Currently, this has a dire effect on
+# completion unless the completeinaliases option is set, so
+# it is set below. This can conceivably cause you problems
+# if you expect completion for aliases automatically to give you
+# completion for the base command. I suspect that most people
+# don't even know that happens.
+#
+# The following functions are provided.
+#
+# General status changing and displaying functions:
+# zfparams
+# Simple front end to `zftp params', except it will automatically
+# query host, user and password. These are then stored to be
+# used with a `zfopen' with no arguments.
+# zfopen [ host [ user ... ] ]
+# Open a connection and login. Unless the option -1 (once)
+# is given, will store the parameters for the open (including
+# a password which is prompted for and not echoed) so that
+# if you call zfopen subsequently without arguments it will
+# reopen the same connection.
+# zfanon anonftphost
+# Open a connection for anonymous FTP. Tries to guess an
+# email address to use as the password, unless $EMAIL_ADDR is
+# already set. The first time, will tell you what it has guessed.
+# It's rude to set EMAIL_ADDR=mozilla.
+# zfcd [ dir | old new ]
+# Change directory on the server. This tries to mimic the behaviour
+# of the shell's cd. In particular,
+# zfcd change to '~' on server, if it interprets it
+# zfcd - change to previous directory of current connection
+# zfcd OLD NEW change directory from fooOLDbar to fooNEWbar
+# One piece of magic is builtin: an initial part of the directory
+# matching $HOME is translated back to `~'. Most UNIX servers
+# recognise the usual shell convention. So things like `zfcd $PWD'
+# is useful provide you are under your home directory and the
+# structure on the remote machine mirrors that on the local.
+# zfhere
+# Synonym for `zfcd $PWD', see above.
+# zfdir [args]
+# Show a long diretory list of the remote connection. Any
+# arguments are passed on to the server, apart from options.
+# Currently this always uses a pager to show the directory
+# list. Caching is implemented: zfdir on its own always shows
+# the current diretory, which is cached; zfdir with some other
+# directory arguments shows that, which is cached separately
+# and can be reviewed with `zfdir -r'. Other options:
+# -f force reget, overriding the cache, in case something's changed
+# -d delete the cache, but don't show anything.
+# To pass options to the server, use e.g. `zfdir -- -C'.
+# This also has the zfcd ~ hack.
+# zfls [args]
+# Short list of the long directory, depending on what [args]
+# do to the server. No options, no caching, no pager.
+# zftype [ a[scii] | i[mage] | b[inary] ]
+# Set or display the transfer type; currently only ASCII
+# and image (same as binary) types are supported.
+# zfclose
+# Close the connection.
+# zfstat
+# Print the zftp status from local variables; doesn't do any network
+# operations unless -v is supplied, in which case the server is
+# asked for its views on the status, too.
+#
+# Functions for retrieving data:
+# All accept the following options:
+# -G Don't do remote globbing (see above); the default is to do it.
+# -t Try to set local files to the same time as the remote ones.
+# Unfortunately we only know the remote time in GMT, so it's
+# a little tricky and you need perl 5 (installed as `perl')
+# for this to work. Suggestions welcome.
+# zfget file1 file2 ...
+# Retrieve each file from the server. The remote file is the
+# full name given, the local file is the non-directory part of that
+# (assuming UNIX file paths).
+# zfuget file1 file2 ..
+# Get with update. Check remote and local sizes and times and
+# retrieve files which are newer on the server. Will query
+# hard cases, which are where the remote file is newer but a
+# different size, or is older but the same size. With option -s
+# (silent) assumes it's best to retrieve the files in both those
+# cases. With -v (may be combined with -s), print the information
+# about the files being considered.
+# zfcget file1 ...
+# Assuming file1 was incompletely retrieved, try to get the rest of
+# it. This relies on a normal UNIX server behaviour which is not
+# as specified in the FTP standard and hence is not universal.
+# zfgcp file1 file2
+# zfgcp file1 file2 ... dir
+# Get with the behaviour of cp, i.e. copy remote file1 to local
+# file2, or get remote fileN into local diretory dir.
+#
+# Function for sending data:
+# zfput file1 file2 ...
+# Put the local files onto the server under the same name. The
+# local files are exactly as given; the remote files are the
+# non-diretory parts of that.
+# zfuput file1 file2 ..
+# Put the local files onto the server, with update. Works
+# similarly to zfuget.
+#
+# Utility functions:
+# zftp_chpwd
+# Show the new directory when it changes; try to put it into
+# an xterm on shelltool header. Works best alongside chpwd.
+# zftp_progress
+# Show the percentage of a file retrieved as it is coming; if the
+# size is not available show the size transferred so far. The
+# percentage may be wrong if sending data from a local pipe.
+# If you transfer files in the background, you should undefine
+# this before the transfer. It is smart enough not to print
+# anything when stderr is not a terminal.
+# zfcd_match
+# Function for remote directory completion.
+# zfget_match
+# Function for remote filename completion.
+# zfrglob varname
+# This is used for the remote globbing. The pattern resides
+# in $varname (note extra level of indirection), and on return
+# $varname will contain the list of matching files.
+# zfrtime locfile remfile [ time ]
+# This sad thing does the setting of local file times to those
+# of the remote, see horror story above.
+
+zmodload -ia zftp
+
+alias zfcd='noglob zfcd'
+alias zfget='noglob zfget'
+alias zfls='noglob zfls'
+alias zfdir='noglob zfdir'
+alias zfuget='noglob zfuget'
+# only way of getting that noglob out of the way at the moment
+setopt completealiases
+
+#
+# zftp completions
+#
+compctl -f -x 'p[1]' \
+ -k '(open params user login type ascii binary mode put putat
+ get getat append appendat ls dir local remote mkdir rmdir delete
+ close quit)' - \
+ 'w[1,cd][1,ls][1,dir][1,rmdir]' -K zfcd_match -S/ -q - \
+ 'W[1,get*]' -K zfget_match - 'w[1,delete][1,remote]' -K zfget_match - \
+ 'w[1,open][1,params]' -k hosts -- zftp
+compctl -K zfcd_match -S/ -q zfcd zfdir zfls
+compctl -K zfget_match zfget zfgcp zfuget zfcget
+compctl -k hosts zfopen zfparams
+
+function zfanon {
+ local opt optlist once
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ 1) once=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ if [[ -z $EMAIL_ADDR ]]; then
+ # Exercise in futility. There's a poem by Wallace Stevens
+ # called something like `N ways of looking at a blackbird',
+ # where N is somewhere around 0x14 to 0x18. Now zftp is
+ # ashamed to prsent `N ways of looking at a hostname'.
+ local domain host
+ # First, maybe we've already got it. Zen-like.
+ if [[ $HOST = *.* ]]; then
+ # assume this is the full host name
+ host=$HOST
+ elif [[ -f /etc/resolv.conf ]]; then
+ # Next, maybe we've got resolv.conf.
+ domain=$(awk '/domain/ { print $2 }' /etc/resolv.conf)
+ [[ -n $domain ]] && host=$HOST.$domain
+ fi
+ # Next, maybe we've got nlsookup. May not work on LINUX.
+ [[ -z $host ]] && host=$(nslookup $HOST | awk '/Name:/ { print $2 }')
+ if [[ -z $host ]]; then
+ # we're running out of ideas, but this should work.
+ # after all, i wrote it...
+ # don't want user to know about this, too embarrassed.
+ local oldvb=$ZFTP_VERBOSE oldtm=$ZFTP_TMOUT
+ ZFTP_VERBOSE=
+ ZFTP_TMOUT=5
+ if zftp open $host >& /dev/null; then
+ host=$ZFTP_HOST
+ zftp close $host
+ fi
+ ZFTP_VERBOSE=$oldvb
+ ZFTP_TMOUT=$oldtm
+ fi
+ if [[ -z $host ]]; then
+ print "Can't get your hostname. Define \$EMAIL_ADDR by hand."
+ return 1;
+ fi
+ EMAIL_ADDR="$USER@$host"
+ print "Using $EMAIL_ADDR as anonymous FTP password."
+ fi
+
+ if [[ $once = 1 ]]; then
+ zftp open $1 anonymous $EMAIL_ADDR
+ else
+ zftp params $1 anonymous $EMAIL_ADDR
+ zftp open
+ fi
+}
+
+function zfcd {
+ # zfcd: change directory on the remote server.
+ #
+ # Currently has the following features:
+ # --- an initial string matching $HOME in the directory is turned back into ~
+ # to be re-interpreted by the remote server.
+ # --- zfcd with no arguments changes directory to '~'
+ # --- `zfcd old new' and `zfcd -' work analagously to cd
+ # --- if the connection is not currently open, it will try to
+ # re-open it with the stored parameters as set by zfopen.
+ # If the connection timed out, however, it won't know until
+ # too late. In that case, just try the same zfcd command again
+ # (but now `zfcd -' and `zfcd old new' won't work).
+
+ # hack: if directory begins with $HOME, turn it back into ~
+ # there are two reasons for this:
+ # first, a ~ on the command line gets expanded even with noglob.
+ # (I suppose this is correct, but I wouldn't like to swear to it.)
+ # second, we can no do 'zfcd $PWD' and the like, and that will
+ # work just as long as the directory structures under the home match.
+
+ # Autoopen: if not already open, hope there are parameters set up to
+ # do so. If not, we get the right error message, so no harm done.
+ [[ -z $ZFTP_HOST ]] && { zfopen || return 1; }
+
+ if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+
+ if (( $# == 0 )); then
+ # Emulate `cd' behaviour
+ set -- '~'
+ elif [[ $# -eq 1 && $1 = - ]]; then
+ # Emulate `cd -' behaviour.
+ set -- $zflastdir
+ elif [[ $# -eq 2 ]]; then
+ # Emulate `cd old new' behaviour.
+ # We have to find a character not in $1 or $2; ! is a good bet.
+ eval set -- "\${ZFTP_PWD:s!$1!$2!}"
+ fi
+
+ # We have to remember the current directory before changing it
+ # if we want to keep it.
+ local lastdir=$ZFTP_PWD
+
+ zftp cd "$@" && zflastdir=$lastdir
+}
+
+function zfcd_match {
+ # see zfcd for details of this hack
+ if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+
+ # error messages only
+ local ZFTP_VERBOSE=45
+ # should we redirect 2>/dev/null or let the user see it?
+
+ if [[ $ZFTP_SYSTEM = UNIX* ]]; then
+ # hoo, aren't we lucky: this makes things so much easier
+ setopt localoptions rcexpandparam
+ local dir
+ if [[ $1 = ?*/* ]]; then
+ dir=${1%/*}
+ elif [[ $1 = /* ]]; then
+ dir=/
+ fi
+ # If we're using -F, we get away with using a directory
+ # to list, but not a glob. Don't ask me why.
+ # I hate having to rely on awk here.
+ reply=($(zftp ls -F $dir |
+ awk '/\/$/ { print substr($1, 0, length($1)-1) }'))
+ if [[ $dir = / ]]; then
+ reply=(${dir}$reply)
+ elif [[ -n $dir ]]; then
+ reply=($dir/$reply)
+ fi
+ else
+ # I simply don't know what to do here.
+ # Just use the list of files for the current directory.
+ zfget_match $*
+ fi
+
+}
+
+function zfcget {
+ # Continuation get of files from remote server.
+ # For each file, if it's shorter here, try to get the remainder from
+ # over there. This requires the server to support the REST command
+ # in the way many do but RFC959 doesn't specify.
+ # Options:
+ # -G don't to remote globbing, else do
+ # -t update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ setopt localoptions
+ unsetopt ksharrays shwordsplit
+
+ local loc rem stat=0 optlist opt nglob remlist locst remst
+ local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize time
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ if [[ ! -f $loc ]]; then
+ # File does not yet exist
+ zftp get $rem >$loc || stat=$?
+ else
+ # Compare the sizes.
+ locst=($(zftp local $loc))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remst=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not support SIZE command.\n" \
+ "Assuming you know what you're doing..." 2>&1
+ zftp getat $rem $locst[1] >>$loc || stat=$?
+ continue
+ elif [[ $rstat = 1 ]]; then
+ print "Remote file not found: $rem" 2>&1
+ continue
+ fi
+ if [[ $locst[1] -gt $remst[1] ]]; then
+ print "Local file is larger!" 2>&1
+ continue;
+ elif [[ $locst[1] == $remst[1] ]]; then
+ print "Files are already the same size." 2>&1
+ continue
+ else
+ if zftp getat $rem $locst[1] >>$loc; then
+ [[ $time = 1 ]] && zfrtime $loc $rem $remst[2]
+ else
+ stat=1
+ fi
+ fi
+ fi
+ done
+ fi
+ done
+
+ return $stat
+}
+
+function zfclose {
+ zftp close
+}
+
+function zfdir {
+ # Long directory of remote server.
+ # The remote directory is cached. In fact, two caches are kept:
+ # one of the standard listing of the current directory, i.e. zfdir
+ # with no arguments, and another for everything else.
+ # To access the appropriate cache, just use zfdir with the same
+ # arguments as previously. zfdir -r will also re-use the `everything
+ # else' cache; you can always reuse the current directory cache just
+ # with zfdir on its own.
+ #
+ # The current directory cache is emptied when the directory changes;
+ # the other is kept until a new zfdir with a non-empty argument list.
+ # Both are removed when the connection is closed.
+ #
+ # zfdir -f will force the existing cache to be ignored, e.g. if you know
+ # or suspect the directory has changed.
+ # zfdir -d will remove both caches without listing anything.
+ # If you need to pass -r, -f or -d to the dir itself, use zfdir -- -d etc.
+
+ setopt localoptions unset
+ unsetopt shwordsplit ksharrays
+
+ local file opt optlist redir i newargs force
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ r) redir=1
+ ;;
+ f) force=1
+ ;;
+ d) [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+ [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+ zftp_fcache=()
+ return 0
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # directory hack, see zfcd
+ for (( i = 1; i <= $#argv; i++ )); do
+ if [[ $argv[$i] = $HOME || $argv[$i] = $HOME/* ]]; then
+ argv[$i]="~${argv[$i]#$HOME}"
+ fi
+ done
+
+ if [[ $# -eq 0 ]]; then
+ # Cache it in the current directory file. This means that repeated
+ # calls to zfdir with no arguments always use a cached file.
+ [[ -z $zfcurdir ]] && zfcurdir=${TMPPREFIX}zfcurdir$$
+ file=$zfcurdir
+ else
+ # Last directly looked at was not the current one, or at least
+ # had non-standard arguments.
+ [[ -z $zfotherdir ]] && zfotherdir=${TMPPREFIX}zfotherdir$$
+ file=$zfotherdir
+ newargs="$*"
+ if [[ -f $file && $redir != 1 && $force -ne 1 ]]; then
+ # Don't use the cached file if the arguments changed.
+ [[ $newargs = $zfotherargs ]] || rm -f $file
+ fi
+ zfotherargs=$newargs
+ fi
+
+ if [[ $force -eq 1 ]]; then
+ rm -f $file
+ # if it looks like current directory has changed, better invalidate
+ # the filename cache, too.
+ (( $# == 0 )) && zftp_fcache=()
+ fi
+
+ if [[ -n $file && -f $file ]]; then
+ eval ${PAGER:-more} \$file
+ else
+ zftp dir $* | tee $file | eval ${PAGER-:more}
+ fi
+}
+
+function zfgcp {
+ # ZFTP get as copy: i.e. first arguments are remote, last is local.
+ # Supposed to work exactly like a normal copy otherwise, i.e.
+ # zfcp rfile lfile
+ # or
+ # zfcp rfile1 rfile2 rfile3 ... ldir
+ # Options:
+ # -G don't to remote globbing, else do
+ # -t update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ setopt localoptions
+ unsetopt shwordsplit
+
+ local opt optlist nglob remlist rem loc stat=0 time
+
+ while [[ $1 == -* ]]; do
+ if [[ $1 == - || $1 == -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # hmm, we should really check this after expanding the glob,
+ # but we shouldn't expand the last argument remotely anyway.
+ if [[ $# -gt 2 && ! -d $argv[-1] ]]; then
+ print "zfgcp: last argument must be a directory." 2>&1
+ return 1
+ elif [[ $# == 1 ]]; then
+ print "zfgcp: not enough arguments." 2>&1
+ return 1
+ fi
+
+ if [[ -d $argv[-1] ]]; then
+ local dir=$argv[-1]
+ argv[-1]=
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=$dir/${rem:t}
+ if zftp get $rem >$loc; then
+ [[ $time = 1 ]] && zfrtime $rem $loc
+ else
+ stat=1
+ fi
+ done
+ fi
+ done
+ else
+ zftp get $1 >$2 || stat=$?
+ fi
+ return $stat
+}
+
+function zfget {
+ # Get files from remote server. Options:
+ # -G don't to remote globbing, else do
+ # -t update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ local loc rem stat=0 optlist opt nglob remlist time
+
+ while [[ $1 == -* ]]; do
+ if [[ $1 == - || $1 == -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ if zftp get $rem >$loc; then
+ [[ $time = 1 ]] && zfrtime $rem $loc
+ else
+ stat=1
+ fi
+ done
+ fi
+ done
+
+ return $stat
+}
+
+function zfget_match {
+ # the zfcd hack: this may not be necessary here
+ if [[ $1 == $HOME || $1 == $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+
+
+ if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then
+ # On the first argument to ls, we usually get away with a glob.
+ reply=($(zftp ls "$1*$2"))
+ else
+ if (( $#zftp_fcache == 0 )); then
+ # Always cache the current directory and use it
+ # even if the system is UNIX.
+ zftp_fcache=($(zftp ls))
+ fi
+ reply=($zftp_fcache);
+ fi
+}
+
+function zfhere {
+ # Change to the directory corresponding to $PWD on the server.
+ # See zfcd for how this works.
+ zfcd $PWD
+}
+
+function zfls {
+ # directory hack, see zfcd
+ if [[ $1 = $HOME || $1 = $HOME/* ]]; then
+ 1="~${1#$HOME}"
+ fi
+ zftp ls $*
+}
+
+function zfopen {
+ # Use zftp params to set parameters for open, rather than sending
+ # them straight to open. That way they are stored for a future open
+ # command.
+ #
+ # With option -1 (just this 1ce), don't do that.
+
+ local optlist opt once
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ 1) once=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # This is where we should try and do same name-lookupage in
+ # both .netrc and .ncftp/bookmarks . We could even try saving
+ # the info in their for new hosts, like ncftp does.
+
+ if [[ $once = 1 ]]; then
+ zftp open $*
+ else
+ # set parameters, but only if there was at least a host
+ (( $# > 0 )) && zfparams $*
+ # now call with no parameters
+ zftp open
+ fi
+}
+
+function zfparams {
+ # Set to prompt for any user or password if not given.
+ # Don't worry about accounts here.
+ if (( $# > 0 )); then
+ (( $# < 2 )) && 2='?'
+ (( $# < 3 )) && 3='?'
+ fi
+ zftp params $*
+}
+
+function zfput {
+ # Simple put: dump every file under the same name, but stripping
+ # off any directory parts.
+ local loc rem stat=0
+ for loc in $*; do
+ rem=${loc:t}
+ zftp put $rem <$loc
+ [[ $? == 0 ]] || stat=$?
+ done
+ return $stat
+}
+
+function zfrglob {
+ # Do the remote globbing for zfput, etc.
+ # We have two choices:
+ # (1) Get the entire file list and match it one by one
+ # locally against the pattern.
+ # Causes problems if we are globbing directories (rare, presumably).
+ # But: we can cache the current directory, which
+ # we need for completion anyway. Works on any OS if you
+ # stick with a single directory. This is the default.
+ # (2) Use remote globbing, i.e. pass it to ls at the site.
+ # Faster, but only works with UNIX, and only basic globbing.
+ # We do this if $zfrglob is non-null.
+
+ # There is only one argument, the variable containing the
+ # pattern to be globbed. We set this back to an array containing
+ # all the matches.
+ setopt localoptions unset
+ unsetopt ksharrays
+
+ local pat dir nondir files i
+
+ eval pat=\$$1
+
+ # Check if we really need to do anything. Look for standard
+ # globbing characters, and if extendedglob is set and we are
+ # using zsh for the actual pattern matching also look for
+ # extendedglob characters.
+ if [[ $remlist != *[][*?]* &&
+ ( -n $zfrglob || ! -o extendedglob || $remlist != *[(|)~#^]* ) ]]; then
+ return 0
+ fi
+
+ if [[ $zfrglob != '' ]]; then
+ eval "$1=(\$(zftp ls \"$pat\" 2>/dev/null))"
+ else
+ if [[ $ZFTP_SYSTEM = UNIX* && $pat = */* ]]; then
+ # not the current directory and we know how to handle paths
+ if [[ $pat = ?*/* ]]; then
+ # careful not to remove too many slashes
+ dir=${pat%/*}
+ else
+ dir=/
+ fi
+ nondir=${pat##*/}
+ files=($(zftp ls "$dir" 2>/dev/null))
+ else
+ # we just have to do an ls and hope that's right
+ nondir=$pat
+ if (( $#zftp_fcache == 0 )); then
+ zftp_fcache=($(zftp ls))
+ fi
+ files=($zftp_fcache)
+ fi
+ # now we want to see which of the $files match $nondir
+ for (( i = 1; i <= $#files; i++)); do
+ # empty words are elided in array assignment
+ [[ $files[$i] = ${~nondir} ]] || files[$i]=''
+ done
+ eval "$1=(\$files)"
+ fi
+}
+
+function zfrtime {
+ # Set the modification time of file LOCAL to that of REMOTE.
+ # If the optional TIME is passed, it should be in the FTP format
+ # CCYYMMDDhhmmSS, i.e. no dot before the seconds, and in GMT.
+ # This is what both `zftp remote' and `zftp local' return.
+ #
+ # Unfortunately, since the time returned from FTP is GMT and
+ # your file needs to be set in local time, we need to do some
+ # hacking around with time. At the moment this requires perl 5
+ # with the standard library.
+
+ setopt localoptions unset
+ unsetopt ksharrays
+
+ local time gmtime loctime
+
+ if [[ -n $3 ]]; then
+ time=$3
+ else
+ time=($(zftp remote $2 2>/dev/null))
+ [[ -n $time ]] && time=$time[2]
+ fi
+ [[ -z $time ]] && return 1
+
+ # Now's the real *!@**!?!. We have the date in GMT and want to turn
+ # it into local time for touch to handle. It's just too nasty
+ # to handle in zsh; do it in perl.
+ if perl -mTime::Local -e '($file, $t) = @ARGV;
+ $yr = substr($t, 0, 4) - 1900;
+ $mon = substr($t, 4, 2) - 1;
+ $mday = substr($t, 6, 2) + 0;
+ $hr = substr($t, 8, 2) + 0;
+ $min = substr($t, 10, 2) + 0;
+ $sec = substr($t, 12, 2) + 0;
+ $time = Time::Local::timegm($sec, $min, $hr, $mday, $mon, $yr);
+ utime $time, $time, $file and return 0;' $1 $time 2>/dev/null; then
+ print "Setting time for $1 failed. Need perl 5." 2>1
+ fi
+
+ # If it wasn't for the GMT/local time thing, it would be this simple.
+ #
+ # time="${time[1,12]}.${time[13,14]}"
+ #
+ # touch -t $time $1
+
+}
+
+function zfstat {
+ # Give a zftp status report using local variables.
+ # With option -v, connect the remote host and ask it what it
+ # thinks the status is.
+
+ setopt localoptions unset
+ unsetopt ksharrays
+
+ local i stat=0 opt optlist verbose
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $opt in
+ v) verbose=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ # hack: in case the status from a subshell process hasn't been
+ # fixed yet
+ zftp type >&/dev/null
+
+ if [[ -n $ZFTP_HOST ]]; then
+ print "Host:\t\t$ZFTP_HOST"
+ print "IP:\t\t$ZFTP_IP"
+ [[ -n $ZFTP_SYSTEM ]] && print "System type:\t$ZFTP_SYSTEM"
+ if [[ -n $ZFTP_USER ]]; then
+ print "User:\t\t$ZFTP_USER "
+ [[ -n $ZFTP_ACCOUNT ]] && print "Account:\t$AFTP_ACCOUNT"
+ print "Directory:\t$ZFTP_PWD"
+ print -n "Transfer type:\t"
+ if [[ $ZFTP_TYPE = "I" ]]; then
+ print Image
+ elif [[ $ZFTP_TYPE = "A" ]]; then
+ print Ascii
+ else
+ print Unknown
+ fi
+ print -n "Transfer mode:\t"
+ if [[ $ZFTP_MODE = "S" ]]; then
+ print Stream
+ elif [[ $ZFTP_MODE = "B" ]]; then
+ print Block
+ else
+ print Unknown
+ fi
+ else
+ print "No user logged in."
+ fi
+ else
+ print "Not connected."
+ stat=1
+ fi
+
+ # things which may be set even if not connected:
+ [[ -n $ZFTP_REPLY ]] && print "Last reply:\t$ZFTP_REPLY"
+ print "Verbosity:\t$ZFTP_VERBOSE"
+ print -n "Preferences:\t"
+ for (( i = 1; i <= ${#ZFTP_PREFS}; i++ )); do
+ case $ZFTP_PREFS[$i] in
+ [pP]) print -n "Passive "
+ ;;
+ [sS]) print -n "Sendport "
+ ;;
+ [dD]) print -n "Dumb "
+ ;;
+ *) print -n "$ZFTP_PREFS[$i]???"
+ esac
+ done
+ print
+
+ if [[ -n $ZFTP_HOST && $verbose = 1 ]]; then
+ print "Status of remote server:"
+ # make sure we print the reply
+ local ZFTP_VERBOSE=045
+ zftp quote STAT
+ fi
+
+ return $stat
+}
+
+function zftp_chpwd {
+ # You may want to alter chpwd to call this when $ZFTP_USER is set.
+ # If so, call it with a non-zero first argument so it doesn't
+ # print the new FTP directory.
+
+ # Cancel the filename cache for the current directory.
+ zftp_fcache=()
+ # ...and also empty the stored directory listing cache.
+ # As this function is called when we close the connection, this
+ # is the only place we need to do these two things.
+ [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
+
+ if [[ -z $ZFTP_USER ]]; then
+ # last call, after an FTP logout
+
+ # delete the non-current cached directory
+ [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
+ zfotherargs=
+
+ # don't keep zflastdir between opens
+ zflastdir=
+
+ # return the display to standard
+ # uncomment the following line if you have a chpwd which shows directories
+ # chpwd
+ else
+ [[ -z $zflastdir ]] && zflastdir=$ZFTP_PWD
+ local args
+ if [[ -t 1 && -t 2 ]]; then
+ local str="$ZFTP_HOST:$ZFTP_PWD"
+ [[ -z $1 ]] && print $str
+ [[ ${#str} -lt 70 ]] && str="%m: %~ $str"
+ case $TERM in
+ sun-cmd) print -n -P "\033]l$str\033\\"
+ ;;
+ xterm) print -n -P "\033]2;$str\a"
+ ;;
+ esac
+ fi
+ fi
+}
+
+function zftp_progress {
+ # Basic progress metre, showing the percent of the file transferred.
+ # You want growing bars? You gotta write growing bars.
+
+ # Don't show progress unless stderr is a terminal
+ [[ ! -t 2 ]] && return 0
+
+ if [[ $ZFTP_TRANSFER = *F ]]; then
+ print 1>&2
+ elif [[ -n $ZFTP_TRANSFER ]]; then
+ if [[ -n $ZFTP_SIZE ]]; then
+ local frac="$(( ZFTP_COUNT * 100 / ZFTP_SIZE ))%"
+ print -n "\r$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER $frac" 1>&2
+ else
+ print -n "\r$ZFTP_FILE: $ZFTP_TRANSFER $ZFTP_COUNT" 1>&2
+ fi
+ fi
+}
+
+function zftype {
+ local type
+
+ if (( $# == 0 )); then
+ type=$(zftp type)
+ if [[ $type = I ]]; then
+ print "Current type is image (binary)"
+ return 0
+ elif [[ $type = A ]]; then
+ print "Current type is ASCII"
+ return 0
+ else
+ return 1
+ fi
+ else
+ if [[ $1 == [aA]([sS][cC]([iI][iI]|)|) ]]; then
+ type=A
+ elif [[ $1 == [iI]([mM]([aA][gG][eE]|)|) ||
+ $1 == [bB]([iI][nN]([aA][rR][yY]|)|) ]]; then
+ type=I
+ else
+ print "Type not recognised: $1" 2>&1
+ return 1
+ fi
+ zftp type $type
+ fi
+}
+
+function zfuget {
+ # Get a list of files from the server with update.
+ # In other words, only retrieve files which are newer than local
+ # ones. This depends on the clocks being adjusted correctly
+ # (i.e. if one is fifteen minutes out, for the next fifteen minutes
+ # updates may not be correctly calculated). However, difficult
+ # cases --- where the files are the same size, but the remote is newer,
+ # or have different sizes, but the local is newer -- are prompted for.
+ #
+ # Files are globbed on the remote host --- assuming, of course, they
+ # haven't already been globbed local, so use 'noglob' e.g. as
+ # `alias zfuget="noglob zfuget"'.
+ #
+ # Options:
+ # -G Glob: turn off globbing
+ # -v verbose: print more about the files listed.
+ # -s silent: don't ask, just guess. The guesses are:
+ # - if the files have different sizes but remote is older ) grab
+ # - if they have the same size but remote is newer )
+ # which is safe if the remote files are always the right ones.
+ # -t time: update the local file times to the same time as the remote.
+ # Currently this only works if you have the `perl' command,
+ # and that perl is version 5 with the standard library.
+ # See the function zfrtime for more gory details.
+
+ setopt localoptions
+ unsetopt ksharrays shwordsplit
+
+ local loc rem stat=0 locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$
+ local rstat remlist verbose optlist opt bad i silent nglob time
+
+ zfuget_print_time() {
+ local tim=$1
+ print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+ print -n GMT
+ }
+
+ zfuget_print () {
+ print -n "\nremote $rem ("
+ zfuget_print_time $remstats[2]
+ print -n ", $remstats[1] bytes)\nlocal $loc ("
+ zfuget_print_time $locstats[2]
+ print ", $locstats[1] bytes)"
+ }
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ v) verbose=1
+ ;;
+ s) silent=1
+ ;;
+ G) nglob=1
+ ;;
+ t) time=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ [[ -n $bad ]] && return 1
+
+ for remlist in $*; do
+ # zfcd directory hack to put the front back to ~
+ if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
+ remlist="~${remlist#$HOME}"
+ fi
+ if [[ $nglob != 1 ]]; then
+ zfrglob remlist
+ fi
+ if (( $#remlist )); then
+ for rem in $remlist; do
+ loc=${rem:t}
+ doit=y
+ remstats=()
+ if [[ -f $loc ]]; then
+ zftp local $loc >$tmpfile
+ locstats=($(<$tmpfile))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remstats=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not implement full command set required." 1>&2
+ return 1
+ elif [[ $rstat = 1 ]]; then
+ print "File not found on server: $rem" 1>&2
+ stat=1
+ continue
+ fi
+ [[ $verbose = 1 ]] && zfuget_print
+ if (( $locstats[1] != $remstats[1] )); then
+ # Files have different sizes
+ if [[ $locstats[2] > $remstats[2] && $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuget_print
+ print "Local file $loc more recent than remote," 1>&2
+ print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # Files have same size
+ if [[ $locstats[2] < $remstats[2] ]]; then
+ if [[ $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuget_print
+ print "Local file $loc has same size as remote," 1>&2
+ print -n "but local file is older. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # presumably same file, so don't get it.
+ [[ $verbose = 1 ]] && print Not transferring
+ doit=n
+ fi
+ fi
+ else
+ [[ $verbose = 1 ]] && print New file $loc
+ fi
+ if [[ $doit = y ]]; then
+ if zftp get $rem >$loc; then
+ if [[ $time = 1 ]]; then
+ # if $remstats is set, it's second element is the remote time
+ zfrtime $loc $rem $remstats[2]
+ fi
+ else
+ stat=$?
+ fi
+
+ fi
+ done
+ fi
+ done
+ return $stat
+}
+
+function zfuput {
+ # Put a list of files from the server with update.
+ # See zfuget for details.
+ #
+ # Options:
+ # -v verbose: print more about the files listed.
+ # -s silent: don't ask, just guess. The guesses are:
+ # - if the files have different sizes but remote is older ) grab
+ # - if they have the same size but remote is newer )
+ # which is safe if the remote files are always the right ones.
+
+ setopt localoptions
+ unsetopt ksharrays shwordsplit
+
+ local loc rem stat=0 locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$
+ local rstat verbose optlist opt bad i silent
+
+ zfuput_print_time() {
+ local tim=$1
+ print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
+ print -n GMT
+ }
+
+ zfuput_print () {
+ print -n "\nremote $rem ("
+ zfuput_print_time $remstats[2]
+ print -n ", $remstats[1] bytes)\nlocal $loc ("
+ zfuput_print_time $locstats[2]
+ print ", $locstats[1] bytes)"
+ }
+
+ while [[ $1 = -* ]]; do
+ if [[ $1 = - || $1 = -- ]]; then
+ shift;
+ break;
+ fi
+ optlist=${1#-}
+ for (( i = 1; i <= $#optlist; i++)); do
+ opt=$optlist[$i]
+ case $optlist[$i] in
+ v) verbose=1
+ ;;
+ s) silent=1
+ ;;
+ *) print option $opt not recognised >&2
+ ;;
+ esac
+ done
+ shift
+ done
+
+ [[ -n $bad ]] && return 1
+
+ if [[ $ZFTP_VERBOSE = *5* ]]; then
+ # should we turn it off locally?
+ print "Messages with code 550 are harmless." >&2
+ fi
+
+ for loc in $*; do
+ rem=${loc:t}
+ doit=y
+ remstats=()
+ if [[ ! -f $loc ]]; then
+ print "$loc: file not found" >&2
+ stat=1
+ continue
+ fi
+ zftp local $loc >$tmpfile
+ locstats=($(<$tmpfile))
+ zftp remote $rem >$tmpfile
+ rstat=$?
+ remstats=($(<$tmpfile))
+ rm -f $tmpfile
+ if [[ $rstat = 2 ]]; then
+ print "Server does not implement full command set required." 1>&2
+ return 1
+ elif [[ $rstat = 1 ]]; then
+ [[ $verbose = 1 ]] && print New file $loc
+ else
+ [[ $verbose = 1 ]] && zfuput_print
+ if (( $locstats[1] != $remstats[1] )); then
+ # Files have different sizes
+ if [[ $locstats[2] < $remstats[2] && $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuput_print
+ print "Remote file $rem more recent than local," 1>&2
+ print -n "but sizes are different. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # Files have same size
+ if [[ $locstats[2] > $remstats[2] ]]; then
+ if [[ $silent != 1 ]]; then
+ [[ $verbose != 1 ]] && zfuput_print
+ print "Remote file $rem has same size as local," 1>&2
+ print -n "but remote file is older. Transfer anyway [y/n]? " 1>&2
+ read -q doit
+ fi
+ else
+ # presumably same file, so don't get it.
+ [[ $verbose = 1 ]] && print Not transferring
+ doit=n
+ fi
+ fi
+ fi
+ if [[ $doit = y ]]; then
+ zftp put $rem <$loc || stat=$?
+ fi
+ done
+ return $stat
+}
diff --git a/Src/.cvsignore b/Src/.cvsignore
new file mode 100644
index 000000000..edec5401b
--- /dev/null
+++ b/Src/.cvsignore
@@ -0,0 +1,25 @@
+Makefile
+Makemod.in Makemod
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+modules.index
+modules.index.tmp
+modules.stamp
+modules-bltin
+stamp-modobjs
+stamp-modobjs.tmp
+ansi2knr
+zsh
+libzsh.so*
+sigcount.h
+signames.c
+zshpaths.h
+zshxmods.h
+bltinmods.list
+tags TAGS
diff --git a/Src/.distfiles b/Src/.distfiles
new file mode 100644
index 000000000..727c855cc
--- /dev/null
+++ b/Src/.distfiles
@@ -0,0 +1,11 @@
+DISTFILES_SRC='
+ .cvsignore .distfiles .exrc .indent.pro
+ Makefile.in Makemod.in.in
+ ansi2knr.c
+ builtin.c compat.c cond.c exec.c glob.c hashtable.c hashtable.h
+ hist.c init.c input.c jobs.c lex.c linklist.c loop.c main.c makepro.awk
+ math.c mem.c mkbltnmlst.sh mkmakemod.sh mkmodindex.sh
+ module.c options.c params.c parse.c prompt.c prototypes.h
+ signals.c signals.h signames.awk subst.c system.h text.c utils.c
+ watch.c xmods.conf zsh.h zsh.mdd ztype.h
+'
diff --git a/Src/.exrc b/Src/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/.lastloc b/Src/.lastloc
new file mode 100644
index 000000000..b3c6cf0f3
--- /dev/null
+++ b/Src/.lastloc
@@ -0,0 +1,5 @@
+(("/home/user2/pws/src/zsh-3.1.5/Src/glob.c" . 15475)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/zsh.export" . 794)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/utils.c" . 10946)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Makefile" . 4282)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/mkmakemod.sh" . 6832))
diff --git a/Src/Builtins/.cvsignore b/Src/Builtins/.cvsignore
new file mode 100644
index 000000000..ff73d86c4
--- /dev/null
+++ b/Src/Builtins/.cvsignore
@@ -0,0 +1,11 @@
+Makefile
+Makefile.in
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+rlimits.h
diff --git a/Src/Builtins/.distfiles b/Src/Builtins/.distfiles
new file mode 100644
index 000000000..cd36388ef
--- /dev/null
+++ b/Src/Builtins/.distfiles
@@ -0,0 +1,5 @@
+DISTFILES_SRC='
+ .cvsignore .distfiles .exrc
+ rlimits.mdd rlimits.c rlimits.awk
+ sched.mdd sched.c
+'
diff --git a/Src/Builtins/.exrc b/Src/Builtins/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/Builtins/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/Builtins/rlimits.awk b/Src/Builtins/rlimits.awk
new file mode 100644
index 000000000..e2500582a
--- /dev/null
+++ b/Src/Builtins/rlimits.awk
@@ -0,0 +1,72 @@
+#
+# rlimits.awk: {g,n}awk script to generate rlimits.h
+#
+# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
+# Without 0 + hacks some nawks compare numbers as strings
+#
+BEGIN {limidx = 0}
+
+/^[\t ]*(#[\t ]*define[\t _]*RLIMIT_[A-Z]*[\t ]*[0-9][0-9]*|RLIMIT_[A-Z]*,[\t ]*)/ {
+ limindex = index($0, "RLIMIT_")
+ limtail = substr($0, limindex, 80)
+ split(limtail, tmp)
+ limnam = substr(tmp[1], 8, 20)
+ limnum = tmp[2]
+ # in this case I assume GNU libc resourcebits.h
+ if (limnum == "") {
+ limnum = limidx++
+ limindex = index($0, ",")
+ limnam = substr(limnam, 1, limindex-1)
+ }
+ limrev[limnam] = limnum
+ if (lim[limnum] == "") {
+ lim[limnum] = limnam
+ if (limnum ~ /^[0-9]*$/) {
+ if (limnam == "MEMLOCK") { msg[limnum] = "memorylocked" }
+ if (limnam == "RSS") { msg[limnum] = "resident" }
+ if (limnam == "VMEM") { msg[limnum] = "vmemorysize" }
+ if (limnam == "NOFILE") { msg[limnum] = "descriptors" }
+ if (limnam == "OFILE") { msg[limnum] = "descriptors" }
+ if (limnam == "CORE") { msg[limnum] = "coredumpsize" }
+ if (limnam == "STACK") { msg[limnum] = "stacksize" }
+ if (limnam == "DATA") { msg[limnum] = "datasize" }
+ if (limnam == "FSIZE") { msg[limnum] = "filesize" }
+ if (limnam == "CPU") { msg[limnum] = "cputime" }
+ if (limnam == "NPROC") { msg[limnum] = "maxproc" }
+ if (limnam == "AS") { msg[limnum] = "addressspace" }
+ if (limnam == "TCACHE") { msg[limnum] = "cachedthreads" }
+ }
+ }
+}
+/^[\t ]*#[\t ]*define[\t _]*RLIM_NLIMITS[\t ]*[0-9][0-9]*/ {
+ limindex = index($0, "RLIM_")
+ limtail = substr($0, limindex, 80)
+ split(limtail, tmp)
+ nlimits = tmp[2]
+}
+# in case of GNU libc
+/^[\t ]*RLIM_NLIMITS[\t ]*=[\t ]*RLIMIT_NLIMITS/ {
+ nlimits = limidx
+}
+
+END {
+ if (limrev["MEMLOCK"] != "") {
+ irss = limrev["RSS"]
+ msg[irss] = "memoryuse"
+ }
+ ps = "%s"
+
+ printf("%s\n%s\n\n", "/** rlimits.h **/", "/** architecture-customized limits for zsh **/")
+ printf("#define ZSH_NLIMITS %d\n\nstatic char *recs[ZSH_NLIMITS+1] = {\n", 0 + nlimits)
+
+ for (i = 0; i < 0 + nlimits; i++)
+ if (msg[i] == "") {
+ badlimit++
+ printf("\t%c%s%c,\n", 34, lim[i], 34)
+ } else
+ printf("\t%c%s%c,\n", 34, msg[i], 34)
+ print "\tNULL"
+ print "};"
+ print ""
+ exit(badlimit)
+}
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c
new file mode 100644
index 000000000..20b8d663d
--- /dev/null
+++ b/Src/Builtins/rlimits.c
@@ -0,0 +1,593 @@
+/*
+ * rlimits.c - resource limit builtins
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "rlimits.mdh"
+#include "rlimits.pro"
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIM_INFINITY)
+
+/* Generated rec array containing limits required for the limit builtin. *
+ * They must appear in this array in numerical order of the RLIMIT_* macros. */
+
+# include "rlimits.h"
+
+# if defined(RLIM_T_IS_QUAD_T) || defined(RLIM_T_IS_UNSIGNED)
+static rlim_t
+zstrtorlimt(const char *s, char **t, int base)
+{
+ rlim_t ret = 0;
+
+ if (!base)
+ if (*s != '0')
+ base = 10;
+ else if (*++s == 'x' || *s == 'X')
+ base = 16, s++;
+ else
+ base = 8;
+
+ if (base <= 10)
+ for (; *s >= '0' && *s < ('0' + base); s++)
+ ret = ret * base + *s - '0';
+ else
+ for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
+ || (*s >= 'A' && *s < ('A' + base - 10)); s++)
+ ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
+ if (t)
+ *t = (char *)s;
+ return ret;
+}
+# else /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_UNSIGNED */
+# define zstrtorlimt(a, b, c) zstrtol((a), (b), (c))
+# endif /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_UNSIGNED */
+
+/* Display resource limits. hard indicates whether `hard' or `soft' *
+ * limits should be displayed. lim specifies the limit, or may be -1 *
+ * to show all. */
+
+/**/
+static void
+showlimits(int hard, int lim)
+{
+ int rt;
+ rlim_t val;
+
+ /* main loop over resource types */
+ for (rt = 0; rt != ZSH_NLIMITS; rt++)
+ if (rt == lim || lim == -1) {
+ /* display limit for resource number rt */
+ printf("%-16s", recs[rt]);
+ val = (hard) ? limits[rt].rlim_max : limits[rt].rlim_cur;
+ if (val == RLIM_INFINITY)
+ printf("unlimited\n");
+ else if (rt==RLIMIT_CPU)
+ /* time-type resource -- display as hours, minutes and
+ seconds. */
+ printf("%d:%02d:%02d\n", (int)(val / 3600),
+ (int)(val / 60) % 60, (int)(val % 60));
+# ifdef RLIMIT_NPROC
+ else if (rt == RLIMIT_NPROC)
+ /* pure numeric resource */
+ printf("%d\n", (int)val);
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_NOFILE
+ else if (rt == RLIMIT_NOFILE)
+ /* pure numeric resource */
+ printf("%d\n", (int)val);
+# endif /* RLIMIT_NOFILE */
+ else if (val >= 1024L * 1024L)
+ /* memory resource -- display with `K' or `M' modifier */
+# ifdef RLIM_T_IS_QUAD_T
+ printf("%qdMB\n", val / (1024L * 1024L));
+ else
+ printf("%qdkB\n", val / 1024L);
+# else
+ printf("%ldMB\n", val / (1024L * 1024L));
+ else
+ printf("%ldkB\n", val / 1024L);
+# endif /* RLIM_T_IS_QUAD_T */
+ }
+}
+
+/* Display a resource limit, in ulimit style. lim specifies which *
+ * limit should be displayed, and hard indicates whether the hard or *
+ * soft limit should be displayed. */
+
+/**/
+static void
+printulimit(int lim, int hard, int head)
+{
+ rlim_t limit;
+
+ /* get the limit in question */
+ limit = (hard) ? limits[lim].rlim_max : limits[lim].rlim_cur;
+ /* display the appropriate heading */
+ switch (lim) {
+ case RLIMIT_CPU:
+ if (head)
+ printf("cpu time (seconds) ");
+ break;
+ case RLIMIT_FSIZE:
+ if (head)
+ printf("file size (blocks) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 512;
+ break;
+ case RLIMIT_DATA:
+ if (head)
+ printf("data seg size (kbytes) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 1024;
+ break;
+ case RLIMIT_STACK:
+ if (head)
+ printf("stack size (kbytes) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 1024;
+ break;
+ case RLIMIT_CORE:
+ if (head)
+ printf("core file size (blocks) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 512;
+ break;
+# ifdef RLIMIT_RSS
+ case RLIMIT_RSS:
+ if (head)
+ printf("resident set size (kbytes) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 1024;
+ break;
+# endif /* RLIMIT_RSS */
+# ifdef RLIMIT_MEMLOCK
+ case RLIMIT_MEMLOCK:
+ if (head)
+ printf("locked-in-memory size (kb) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 1024;
+ break;
+# endif /* RLIMIT_MEMLOCK */
+# ifdef RLIMIT_NPROC
+ case RLIMIT_NPROC:
+ if (head)
+ printf("processes ");
+ break;
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_NOFILE
+ case RLIMIT_NOFILE:
+ if (head)
+ printf("file descriptors ");
+ break;
+# endif /* RLIMIT_NOFILE */
+# ifdef RLIMIT_VMEM
+ case RLIMIT_VMEM:
+ if (head)
+ printf("virtual memory size (kb) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 1024;
+ break;
+# endif /* RLIMIT_VMEM */
+# if defined RLIMIT_AS && RLIMIT_AS != RLIMIT_VMEM
+ case RLIMIT_AS:
+ if (head)
+ printf("address space (kb) ");
+ if (limit != RLIM_INFINITY)
+ limit /= 1024;
+ break;
+# endif /* RLIMIT_AS */
+# ifdef RLIMIT_TCACHE
+ case RLIMIT_TCACHE:
+ if (head)
+ printf("cached threads ");
+ break;
+# endif /* RLIMIT_TCACHE */
+ }
+ /* display the limit */
+ if (limit == RLIM_INFINITY)
+ printf("unlimited\n");
+ else
+ printf("%ld\n", (long)limit);
+}
+
+/* limit: set or show resource limits. The variable hard indicates *
+ * whether `hard' or `soft' resource limits are being set/shown. */
+
+/**/
+static int
+bin_limit(char *nam, char **argv, char *ops, int func)
+{
+ char *s;
+ int hard, limnum, lim;
+ rlim_t val;
+ int ret = 0;
+
+ hard = ops['h'];
+ if (ops['s'] && !*argv)
+ return setlimits(NULL);
+ /* without arguments, display limits */
+ if (!*argv) {
+ showlimits(hard, -1);
+ return 0;
+ }
+ while ((s = *argv++)) {
+ /* Search for the appropriate resource name. When a name matches (i.e. *
+ * starts with) the argument, the lim variable changes from -1 to the *
+ * number of the resource. If another match is found, lim goes to -2. */
+ for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++)
+ if (!strncmp(recs[limnum], s, strlen(s))) {
+ if (lim != -1)
+ lim = -2;
+ else
+ lim = limnum;
+ }
+ /* lim==-1 indicates that no matches were found. *
+ * lim==-2 indicates that multiple matches were found. */
+ if (lim < 0) {
+ zwarnnam("limit",
+ (lim == -2) ? "ambiguous resource specification: %s"
+ : "no such resource: %s", s, 0);
+ return 1;
+ }
+ /* without value for limit, display the current limit */
+ if (!(s = *argv++)) {
+ showlimits(hard, lim);
+ return 0;
+ }
+ if (lim==RLIMIT_CPU) {
+ /* time-type resource -- may be specified as seconds, or minutes or *
+ * hours with the `m' and `h' modifiers, and `:' may be used to add *
+ * together more than one of these. It's easier to understand from *
+ * the code: */
+ val = zstrtorlimt(s, &s, 10);
+ if (*s)
+ if ((*s == 'h' || *s == 'H') && !s[1])
+ val *= 3600L;
+ else if ((*s == 'm' || *s == 'M') && !s[1])
+ val *= 60L;
+ else if (*s == ':')
+ val = val * 60 + zstrtorlimt(s + 1, &s, 10);
+ else {
+ zwarnnam("limit", "unknown scaling factor: %s", s, 0);
+ return 1;
+ }
+ }
+# ifdef RLIMIT_NPROC
+ else if (lim == RLIMIT_NPROC)
+ /* pure numeric resource -- only a straight decimal number is
+ permitted. */
+ val = zstrtorlimt(s, &s, 10);
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_NOFILE
+ else if (lim == RLIMIT_NOFILE)
+ /* pure numeric resource -- only a straight decimal number is
+ permitted. */
+ val = zstrtorlimt(s, &s, 10);
+# endif /* RLIMIT_NOFILE */
+ else {
+ /* memory-type resource -- `k' and `M' modifiers are permitted,
+ meaning (respectively) 2^10 and 2^20. */
+ val = zstrtorlimt(s, &s, 10);
+ if (!*s || ((*s == 'k' || *s == 'K') && !s[1]))
+ val *= 1024L;
+ else if ((*s == 'M' || *s == 'm') && !s[1])
+ val *= 1024L * 1024;
+ else {
+ zwarnnam("limit", "unknown scaling factor: %s", s, 0);
+ return 1;
+ }
+ }
+ /* new limit is valid and has been interpreted; apply it to the
+ specified resource */
+ if (hard) {
+ /* can only raise hard limits if running as root */
+ if (val > current_limits[lim].rlim_max && geteuid()) {
+ zwarnnam("limit", "can't raise hard limits", NULL, 0);
+ return 1;
+ } else {
+ limits[lim].rlim_max = val;
+ if (val < limits[lim].rlim_cur)
+ limits[lim].rlim_cur = val;
+ }
+ } else if (val > limits[lim].rlim_max) {
+ zwarnnam("limit", "limit exceeds hard limit", NULL, 0);
+ return 1;
+ } else
+ limits[lim].rlim_cur = val;
+ if (ops['s'] && zsetlimit(lim, "limit"))
+ ret++;
+ }
+ return ret;
+}
+
+/* unlimit: remove resource limits. Much of this code is the same as *
+ * that in bin_limit(). */
+
+/**/
+static int
+bin_unlimit(char *nam, char **argv, char *ops, int func)
+{
+ int hard, limnum, lim;
+ int ret = 0;
+ uid_t euid = geteuid();
+
+ hard = ops['h'];
+ /* Without arguments, remove all limits. */
+ if (!*argv) {
+ for (limnum = 0; limnum != RLIM_NLIMITS; limnum++) {
+ if (hard)
+ if (euid && current_limits[limnum].rlim_max != RLIM_INFINITY)
+ ret++;
+ else
+ limits[limnum].rlim_max = RLIM_INFINITY;
+ else
+ limits[limnum].rlim_cur = limits[limnum].rlim_max;
+ }
+ if (ops['s'])
+ ret += setlimits(nam);
+ if (ret)
+ zwarnnam(nam, "can't remove hard limits", NULL, 0);
+ } else {
+ for (; *argv; argv++) {
+ /* Search for the appropriate resource name. When a name *
+ * matches (i.e. starts with) the argument, the lim variable *
+ * changes from -1 to the number of the resource. If another *
+ * match is found, lim goes to -2. */
+ for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++)
+ if (!strncmp(recs[limnum], *argv, strlen(*argv))) {
+ if (lim != -1)
+ lim = -2;
+ else
+ lim = limnum;
+ }
+ /* lim==-1 indicates that no matches were found. *
+ * lim==-2 indicates that multiple matches were found. */
+ if (lim < 0) {
+ zwarnnam(nam,
+ (lim == -2) ? "ambiguous resource specification: %s"
+ : "no such resource: %s", *argv, 0);
+ return 1;
+ }
+ /* remove specified limit */
+ if (hard)
+ if (euid && current_limits[lim].rlim_max != RLIM_INFINITY) {
+ zwarnnam(nam, "can't remove hard limits", NULL, 0);
+ ret++;
+ } else
+ limits[lim].rlim_max = RLIM_INFINITY;
+ else
+ limits[lim].rlim_cur = limits[lim].rlim_max;
+ if (ops['s'] && zsetlimit(lim, nam))
+ ret++;
+ }
+ }
+ return ret;
+}
+
+/* ulimit: set or display resource limits */
+
+/**/
+static int
+bin_ulimit(char *name, char **argv, char *ops, int func)
+{
+ int res, resmask = 0, hard = 0, soft = 0, nres = 0;
+ char *options;
+
+ do {
+ options = *argv;
+ if (options && *options == '-' && !options[1]) {
+ zwarnnam(name, "missing option letter", NULL, 0);
+ return 1;
+ }
+ res = -1;
+ if (options && *options == '-') {
+ argv++;
+ while (*++options) {
+ if(*options == Meta)
+ *++options ^= 32;
+ res = -1;
+ switch (*options) {
+ case 'H':
+ hard = 1;
+ continue;
+ case 'S':
+ soft = 1;
+ continue;
+ case 'a':
+ if (*argv || options[1] || resmask) {
+ zwarnnam(name, "no arguments required after -a",
+ NULL, 0);
+ return 1;
+ }
+ resmask = (1 << RLIM_NLIMITS) - 1;
+ nres = RLIM_NLIMITS;
+ continue;
+ case 't':
+ res = RLIMIT_CPU;
+ break;
+ case 'f':
+ res = RLIMIT_FSIZE;
+ break;
+ case 'd':
+ res = RLIMIT_DATA;
+ break;
+ case 's':
+ res = RLIMIT_STACK;
+ break;
+ case 'c':
+ res = RLIMIT_CORE;
+ break;
+# ifdef RLIMIT_RSS
+ case 'm':
+ res = RLIMIT_RSS;
+ break;
+# endif /* RLIMIT_RSS */
+# ifdef RLIMIT_MEMLOCK
+ case 'l':
+ res = RLIMIT_MEMLOCK;
+ break;
+# endif /* RLIMIT_MEMLOCK */
+# ifdef RLIMIT_NOFILE
+ case 'n':
+ res = RLIMIT_NOFILE;
+ break;
+# endif /* RLIMIT_NOFILE */
+# ifdef RLIMIT_NPROC
+ case 'u':
+ res = RLIMIT_NPROC;
+ break;
+# endif /* RLIMIT_NPROC */
+# ifdef RLIMIT_VMEM
+ case 'v':
+ res = RLIMIT_VMEM;
+ break;
+# endif /* RLIMIT_VMEM */
+ default:
+ /* unrecognised limit */
+ zwarnnam(name, "bad option: -%c", NULL, *options);
+ return 1;
+ }
+ if (options[1]) {
+ resmask |= 1 << res;
+ nres++;
+ }
+ }
+ }
+ if (!*argv || **argv == '-') {
+ if (res < 0)
+ if (*argv || nres)
+ continue;
+ else
+ res = RLIMIT_FSIZE;
+ resmask |= 1 << res;
+ nres++;
+ continue;
+ }
+ if (res < 0)
+ res = RLIMIT_FSIZE;
+ if (strcmp(*argv, "unlimited")) {
+ /* set limit to specified value */
+ rlim_t limit;
+
+ limit = zstrtorlimt(*argv, NULL, 10);
+ /* scale appropriately */
+ switch (res) {
+ case RLIMIT_FSIZE:
+ case RLIMIT_CORE:
+ limit *= 512;
+ break;
+ case RLIMIT_DATA:
+ case RLIMIT_STACK:
+# ifdef RLIMIT_RSS
+ case RLIMIT_RSS:
+# endif /* RLIMIT_RSS */
+# ifdef RLIMIT_MEMLOCK
+ case RLIMIT_MEMLOCK:
+# endif /* RLIMIT_MEMLOCK */
+# ifdef RLIMIT_VMEM
+ case RLIMIT_VMEM:
+# endif /* RLIMIT_VMEM */
+ limit *= 1024;
+ break;
+ }
+ if (hard) {
+ /* can't raise hard limit unless running as root */
+ if (limit > current_limits[res].rlim_max && geteuid()) {
+ zwarnnam(name, "can't raise hard limits", NULL, 0);
+ return 1;
+ }
+ limits[res].rlim_max = limit;
+ if (limit < limits[res].rlim_cur)
+ limits[res].rlim_cur = limit;
+ }
+ if (!hard || soft) {
+ /* can't raise soft limit above hard limit */
+ if (limit > limits[res].rlim_max) {
+ if (limit > current_limits[res].rlim_max && geteuid()) {
+ zwarnnam(name, "value exceeds hard limit", NULL, 0);
+ return 1;
+ }
+ limits[res].rlim_max = limits[res].rlim_cur = limit;
+ } else
+ limits[res].rlim_cur = limit;
+ }
+ } else {
+ /* remove specified limit */
+ if (hard) {
+ /* can't remove hard limit unless running as root */
+ if (current_limits[res].rlim_max != RLIM_INFINITY && geteuid()) {
+ zwarnnam(name, "can't remove hard limits", NULL, 0);
+ return 1;
+ }
+ limits[res].rlim_max = RLIM_INFINITY;
+ }
+ if (!hard || soft)
+ /* `removal' of soft limit means setting it equal to the
+ corresponding hard limit */
+ limits[res].rlim_cur = limits[res].rlim_max;
+ }
+ if (zsetlimit(res, name))
+ return 1;
+ argv++;
+ } while (*argv);
+ for (res = 0; res < RLIM_NLIMITS; res++, resmask >>= 1)
+ if (resmask & 1)
+ printulimit(res, hard, nres > 1);
+ return 0;
+}
+
+#else /* !HAVE_GETRLIMIT || !RLIM_INFINITY */
+
+# define bin_limit bin_notavail
+# define bin_ulimit bin_notavail
+# define bin_unlimit bin_notavail
+
+#endif /* !HAVE_GETRLIMIT || !RLIM_INFINITY */
+
+static struct builtin bintab[] = {
+ BUILTIN("limit", 0, bin_limit, 0, -1, 0, "sh", NULL),
+ BUILTIN("ulimit", 0, bin_ulimit, 0, -1, 0, NULL, NULL),
+ BUILTIN("unlimit", 0, bin_unlimit, 0, -1, 0, "hs", NULL),
+};
+
+/**/
+int
+boot_rlimits(Module m)
+{
+ return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_rlimits(Module m)
+{
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+#endif
diff --git a/Src/Builtins/rlimits.mdd b/Src/Builtins/rlimits.mdd
new file mode 100644
index 000000000..f0e41b73e
--- /dev/null
+++ b/Src/Builtins/rlimits.mdd
@@ -0,0 +1,18 @@
+autobins="limit ulimit unlimit"
+
+objects="rlimits.o"
+
+:<<\Make
+rlimits.o rlimits..o: rlimits.h
+
+# this file will not be made if limits are unavailable:
+# silent so the warning doesn't appear unless necessary
+rlimits.h: rlimits.awk @RLIMITS_INC_H@
+ @echo '$(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ > rlimits.h'; \
+ $(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ > rlimits.h || \
+ echo WARNING: unknown limits: mail rlimits.h to developers
+
+clean-here: clean.rlimits
+clean.rlimits:
+ rm -f rlimits.h
+Make
diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c
new file mode 100644
index 000000000..b4914899e
--- /dev/null
+++ b/Src/Builtins/sched.c
@@ -0,0 +1,214 @@
+/*
+ * sched.c - execute commands at scheduled times
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "sched.mdh"
+#include "sched.pro"
+
+/* node in sched list */
+
+typedef struct schedcmd *Schedcmd;
+
+struct schedcmd {
+ struct schedcmd *next;
+ char *cmd; /* command to run */
+ time_t time; /* when to run it */
+};
+
+/* the list of sched jobs pending */
+
+static struct schedcmd *schedcmds;
+
+/**/
+static int
+bin_sched(char *nam, char **argv, char *ops, int func)
+{
+ char *s = *argv++;
+ time_t t;
+ long h, m;
+ struct tm *tm;
+ struct schedcmd *sch, *sch2, *schl;
+ int sn;
+
+ /* If the argument begins with a -, remove the specified item from the
+ schedule. */
+ if (s && *s == '-') {
+ sn = atoi(s + 1);
+
+ if (!sn) {
+ zwarnnam("sched", "usage for delete: sched -<item#>.", NULL, 0);
+ return 1;
+ }
+ for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds, sn--;
+ sch && sn; sch = (schl = sch)->next, sn--);
+ if (!sch) {
+ zwarnnam("sched", "not that many entries", NULL, 0);
+ return 1;
+ }
+ schl->next = sch->next;
+ zsfree(sch->cmd);
+ zfree(sch, sizeof(struct schedcmd));
+
+ return 0;
+ }
+
+ /* given no arguments, display the schedule list */
+ if (!s) {
+ char tbuf[40];
+
+ for (sn = 1, sch = schedcmds; sch; sch = sch->next, sn++) {
+ t = sch->time;
+ tm = localtime(&t);
+ ztrftime(tbuf, 20, "%a %b %e %k:%M:%S", tm);
+ printf("%3d %s %s\n", sn, tbuf, sch->cmd);
+ }
+ return 0;
+ } else if (!*argv) {
+ /* other than the two cases above, sched *
+ *requires at least two arguments */
+ zwarnnam("sched", "not enough arguments", NULL, 0);
+ return 1;
+ }
+
+ /* The first argument specifies the time to schedule the command for. The
+ remaining arguments form the command. */
+ if (*s == '+') {
+ /* + introduces a relative time. The rest of the argument is an
+ hour:minute offset from the current time. Once the hour and minute
+ numbers have been extracted, and the format verified, the resulting
+ offset is simply added to the current time. */
+ h = zstrtol(s + 1, &s, 10);
+ if (*s != ':') {
+ zwarnnam("sched", "bad time specifier", NULL, 0);
+ return 1;
+ }
+ m = zstrtol(s + 1, &s, 10);
+ if (*s) {
+ zwarnnam("sched", "bad time specifier", NULL, 0);
+ return 1;
+ }
+ t = time(NULL) + h * 3600 + m * 60;
+ } else {
+ /* If there is no +, an absolute time of day must have been given.
+ This is in hour:minute format, optionally followed by a string starting
+ with `a' or `p' (for a.m. or p.m.). Characters after the `a' or `p'
+ are ignored. */
+ h = zstrtol(s, &s, 10);
+ if (*s != ':') {
+ zwarnnam("sched", "bad time specifier", NULL, 0);
+ return 1;
+ }
+ m = zstrtol(s + 1, &s, 10);
+ if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') {
+ zwarnnam("sched", "bad time specifier", NULL, 0);
+ return 1;
+ }
+ t = time(NULL);
+ tm = localtime(&t);
+ t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600;
+ if (*s == 'p' || *s == 'P')
+ h += 12;
+ t += h * 3600 + m * 60;
+ /* If the specified time is before the current time, it must refer to
+ tomorrow. */
+ if (t < time(NULL))
+ t += 3600 * 24;
+ }
+ /* The time has been calculated; now add the new entry to the linked list
+ of scheduled commands. */
+ sch = (struct schedcmd *) zcalloc(sizeof *sch);
+ sch->time = t;
+ PERMALLOC {
+ sch->cmd = zjoin(argv, ' ');
+ } LASTALLOC;
+ sch->next = NULL;
+ for (sch2 = (struct schedcmd *)&schedcmds; sch2->next; sch2 = sch2->next);
+ sch2->next = sch;
+ return 0;
+}
+
+/* Check scheduled commands; call this function from time to time. */
+
+/**/
+static void
+checksched(void)
+{
+ time_t t;
+ struct schedcmd *sch, *schl;
+
+ if(!schedcmds)
+ return;
+ t = time(NULL);
+ for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds; sch;
+ sch = (schl = sch)->next) {
+ if (sch->time <= t) {
+ execstring(sch->cmd, 0, 0);
+ schl->next = sch->next;
+ zsfree(sch->cmd);
+ zfree(sch, sizeof(struct schedcmd));
+ sch = schl;
+ }
+ }
+}
+
+static void (*p_checksched) _((void)) = checksched;
+static struct linknode n_checksched = { NULL, NULL, &p_checksched };
+
+static struct builtin bintab[] = {
+ BUILTIN("sched", 0, bin_sched, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_sched(Module m)
+{
+ if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
+ return 1;
+ uaddlinknode(prepromptfns, &n_checksched);
+ return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_sched(Module m)
+{
+ struct schedcmd *sch, *schn;
+
+ for (sch = schedcmds; sch; sch = schn) {
+ schn = sch->next;
+ zsfree(sch->cmd);
+ zfree(sch, sizeof(*sch));
+ }
+ uremnode(prepromptfns, &n_checksched);
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+
+#endif
diff --git a/Src/Builtins/sched.mdd b/Src/Builtins/sched.mdd
new file mode 100644
index 000000000..6ed749f32
--- /dev/null
+++ b/Src/Builtins/sched.mdd
@@ -0,0 +1,3 @@
+autobins="sched"
+
+objects="sched.o"
diff --git a/Src/Makefile.in b/Src/Makefile.in
new file mode 100644
index 000000000..453159e17
--- /dev/null
+++ b/Src/Makefile.in
@@ -0,0 +1,195 @@
+#
+# Makefile for Src subdirectory
+#
+# Copyright (c) 1995-1997 Richard Coleman
+# All rights reserved.
+#
+# Permission is hereby granted, without written agreement and without
+# license or royalty fees, to use, copy, modify, and distribute this
+# software and to distribute modified versions of this software for any
+# purpose, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+#
+# In no event shall Richard Coleman or the Zsh Development Group be liable
+# to any party for direct, indirect, special, incidental, or consequential
+# damages arising out of the use of this software and its documentation,
+# even if Richard Coleman and the Zsh Development Group have been advised of
+# the possibility of such damage.
+#
+# Richard Coleman and the Zsh Development Group specifically disclaim any
+# warranties, including, but not limited to, the implied warranties of
+# merchantability and fitness for a particular purpose. The software
+# provided hereunder is on an "as is" basis, and Richard Coleman and the
+# Zsh Development Group have no obligation to provide maintenance,
+# support, updates, enhancements, or modifications.
+#
+
+subdir = Src
+dir_top = ..
+SUBDIRS =
+
+@@version.mk@@
+@@defs.mk@@
+
+sdir_src = $(sdir)
+dir_src = .
+
+# ========= DEPENDENCIES FOR BUILDING ==========
+
+LINK = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@
+DLLINK = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@
+
+all: bin modules
+
+bin: zsh
+
+modules: headers
+
+MAIN_OBJS = main.o
+
+LSTMP =
+LLIST =
+NSTMP = stamp-modobjs
+NLIST = `cat stamp-modobjs`
+
+LIBZSH = libzsh-$(VERSION).$(DL_EXT)
+NIBZSH =
+
+LDRUNPATH = LD_RUN_PATH=$(libdir)/zsh
+NDRUNPATH =
+
+zsh: $(@L@IBZSH) $(@L@STMP) $(MAIN_OBJS)
+ rm -f $@
+ $(@L@DRUNPATH) $(LINK) $(MAIN_OBJS) $(@L@LIST) $(@L@IBZSH) $(LIBS)
+
+$(LIBZSH): $(LIBOBJS) $(NSTMP)
+ rm -f $@
+ $(DLLINK) $(LIBOBJS) $(NLIST)
+
+stamp-modobjs: modobjs
+ @if cmp -s stamp-modobjs.tmp stamp-modobjs; then \
+ rm -f stamp-modobjs.tmp; \
+ echo "\`stamp-modobjs' is up to date."; \
+ else \
+ mv -f stamp-modobjs.tmp stamp-modobjs; \
+ echo "Updated \`stamp-modobjs'."; \
+ fi
+
+modobjs: headers rm-modobjs-tmp
+
+rm-modobjs-tmp:
+ rm -f stamp-modobjs.tmp
+
+Makemod modules.index prep: modules-bltin
+ ( cd $(sdir_top) && $(SHELL) $(subdir)/mkmodindex.sh $(subdir) ) \
+ > modules.index.tmp
+ @if cmp -s modules.index.tmp modules.index; then \
+ rm -f modules.index.tmp; \
+ echo "\`modules.index' is up to date."; \
+ else \
+ mv -f modules.index.tmp modules.index; \
+ echo "Updated \`modules.index'."; \
+ fi
+ @case $(sdir_top) in \
+ /*) top_srcdir=$(sdir_top) ;; \
+ *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+ esac; \
+ export top_srcdir; \
+ echo 'cd $(dir_top) && $(SHELL)' \
+ '$$top_srcdir/$(subdir)/mkmakemod.sh $(subdir) Makemod'; \
+ cd $(dir_top) && \
+ $(SHELL) $$top_srcdir/$(subdir)/mkmakemod.sh $(subdir) Makemod
+ @$(MAKE) -f Makemod $(MAKEDEFS) prep || rm -f Makemod
+
+FORCE:
+
+# ========== LINKING IN MODULES ==========
+
+modules-bltin:
+ if test @D@ = N; then \
+ cat $(sdir)/xmods.conf > $@; \
+ elif test @RTLD_GLOBAL_OK@ != yes; then \
+ echo comp1 > $@; \
+ else \
+ echo > $@; \
+ fi
+
+# ========== ANSI TO K&R CONVERSION ==========
+
+ANSI_KNR = ansi2knr
+ANSIKNR =
+
+Makemod: $(ANSI@U@KNR)
+
+ansi2knr.o: ansi2knr.c
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(sdir)/ansi2knr.c
+
+ansi2knr: ansi2knr.o
+ rm -f $@
+ $(CC) $(LDFLAGS) $(EXELDFLAGS) -o $@ ansi2knr.o
+
+# ========== DEPENDENCIES FOR INSTALLING ==========
+
+install: install.bin install.modules
+uninstall: uninstall.bin uninstall.modules
+
+install.bin: install.bin-here
+uninstall.bin: uninstall.bin-here
+
+# install binary, creating install directory if necessary
+install.bin-here: zsh install.bin-@L@
+ $(sdir_top)/mkinstalldirs $(bindir)
+ $(INSTALL_PROGRAM) zsh $(bindir)/zsh-$(VERSION)
+ if test -f $(bindir)/zsh; then \
+ rm -f $(bindir)/zsh.old; \
+ ln $(bindir)/zsh $(bindir)/zsh.old; \
+ else :; fi
+ rm -f $(bindir)/zsh.new
+ ln $(bindir)/zsh-$(VERSION) $(bindir)/zsh.new
+ mv $(bindir)/zsh.new $(bindir)/zsh
+
+install.bin-N:
+install.bin-L: $(LIBZSH)
+ $(sdir_top)/mkinstalldirs $(libdir)/zsh
+ $(INSTALL_PROGRAM) $(LIBZSH) $(libdir)/zsh/$(LIBZSH)
+
+# uninstall binary
+uninstall.bin-here: uninstall.bin-@L@
+ rm -f $(bindir)/zsh-$(VERSION) $(bindir)/zsh
+
+uninstall.bin-N:
+uninstall.bin-L:
+ rm -f $(libdir)/zsh/$(LIBZSH)
+
+# ========== DEPENDENCIES FOR CLEANUP ==========
+
+@@clean.mk@@
+
+mostlyclean-here:
+ rm -f stamp-modobjs stamp-modobjs.tmp
+
+clean-here:
+ rm -f modules.index.tmp modules.stamp zsh ansi2knr.o ansi2knr
+ rm -f libzsh-*.$(DL_EXT)
+
+distclean-here:
+ rm -f TAGS tags
+ rm -f modules.index modules-bltin Makefile
+
+mostlyclean: mostlyclean-modules
+clean: clean-modules
+distclean: distclean-modules
+realclean: realclean-modules
+
+mostlyclean-modules clean-modules distclean-modules realclean-modules: Makemod
+ @$(MAKE) -f Makemod $(MAKEDEFS) `echo $@ | sed 's/-modules//'`
+
+# ========== RECURSIVE MAKES ==========
+
+install.modules uninstall.modules \
+modobjs modules headers proto $(MAIN_OBJS): Makemod
+ @$(MAKE) -f Makemod $(MAKEDEFS) $@
+
+# ========== DEPENDENCIES FOR MAINTENANCE ==========
+
+@@config.mk@@
diff --git a/Src/Makemod.in.in b/Src/Makemod.in.in
new file mode 100644
index 000000000..f27a6bd57
--- /dev/null
+++ b/Src/Makemod.in.in
@@ -0,0 +1,180 @@
+#
+# Makemod.in.in
+#
+# Copyright (c) 1995-1997 Richard Coleman
+# All rights reserved.
+#
+# Permission is hereby granted, without written agreement and without
+# license or royalty fees, to use, copy, modify, and distribute this
+# software and to distribute modified versions of this software for any
+# purpose, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+#
+# In no event shall Richard Coleman or the Zsh Development Group be liable
+# to any party for direct, indirect, special, incidental, or consequential
+# damages arising out of the use of this software and its documentation,
+# even if Richard Coleman and the Zsh Development Group have been advised of
+# the possibility of such damage.
+#
+# Richard Coleman and the Zsh Development Group specifically disclaim any
+# warranties, including, but not limited to, the implied warranties of
+# merchantability and fitness for a particular purpose. The software
+# provided hereunder is on an "as is" basis, and Richard Coleman and the
+# Zsh Development Group have no obligation to provide maintenance,
+# support, updates, enhancements, or modifications.
+#
+
+# ========== OVERRIDABLE VARIABLES ==========
+
+# subdir is done by mkmakemod.sh
+# dir_top is done by mkmakemod.sh
+# SUBDIRS is done by mkmakemod.sh
+
+@@version.mk@@
+@@defs.mk@@
+
+sdir_src = $(sdir_top)/Src
+dir_src = $(dir_top)/Src
+
+# ========== COMPILATION RULES ==========
+
+DNCFLAGS =
+
+COMPILE = $(CC) -c -I. $(CPPFLAGS) $(DEFS) $(CFLAGS) $(D@L@CFLAGS)
+DLCOMPILE = $(CC) -c -I. $(CPPFLAGS) $(DEFS) -DMODULE $(CFLAGS) $(DLCFLAGS)
+LINK = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@
+DLLINK = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@
+
+KNR_OBJ=.o
+KNROBJ=._foo_
+
+ANSIOBJ=.o
+ANSI_OBJ=._foo_
+
+.SUFFIXES: .c .$(DL_EXT) ..o .._foo_ .o ._foo_ .pro
+
+.c$(ANSI@U@OBJ):
+ $(COMPILE) -o $@ $<
+ @rm -f $(dir_src)/stamp-modobjs
+
+.c$(KNR@U@OBJ):
+ $(dir_src)/ansi2knr $< > $@.c
+ $(COMPILE) -o $@ $@.c
+ rm -f $@.c
+ @rm -f $(dir_src)/stamp-modobjs
+
+.c.$(ANSI@U@OBJ):
+ $(DLCOMPILE) -o $@ $<
+
+.c.$(KNR@U@OBJ):
+ $(dir_src)/ansi2knr $< > $@.c
+ $(DLCOMPILE) -o $@ $@.c
+ rm -f $@.c
+
+.c.pro:
+ $(AWK) -f $(sdir_src)/makepro.awk $< $(subdir) > $@
+
+PROTODEPS = $(sdir_src)/makepro.awk
+
+# ========== DEPENDENCIES FOR BUILDING ==========
+
+all: modobjs modules
+
+modobjs: $(MODOBJS)
+modules: $(MODULES)
+headers: $(MDHS)
+proto: $(PROTOS)
+
+prep:
+ @case $(sdir_top) in \
+ /*) top_srcdir=$(sdir_top) ;; \
+ *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+ esac; \
+ export top_srcdir; \
+ cd $(dir_top) || exit 1; \
+ subdirs='$(SUBDIRS)'; \
+ for subdir in $$subdirs; do \
+ dir=$(subdir)/$$subdir; \
+ test -d $$dir || mkdir $$dir; \
+ $(SHELL) $$top_srcdir/Src/mkmakemod.sh $$dir Makefile || exit 1; \
+ ( cd $$dir && $(MAKE) $(MAKEDEFS) $@ ) || exit 1; \
+ done
+
+headers prep: $(dir_src)/modules.stamp
+$(dir_src)/modules.stamp: $(MDDS)
+ echo 'timestamp for *.mdd files' > $@
+
+FORCE:
+
+# ========== DEPENDENCIES FOR INSTALLING ==========
+
+install: install.bin install.modules
+uninstall: uninstall.bin uninstall.modules
+
+install.bin: install.bin-here
+uninstall.bin: uninstall.bin-here
+install.modules: install.modules-here
+uninstall.modules: uninstall.modules-here
+
+install.bin-here uninstall.bin-here:
+
+install.modules-here:
+ $(sdir_top)/mkinstalldirs $(MODDIR)
+ modules='$(MODULES)'; for mod in $$modules; do \
+ $(INSTALL_PROGRAM) $$mod $(MODDIR)/$$mod; \
+ done
+
+uninstall.modules-here:
+ modules='$(MODULES)'; for mod in $$modules; do \
+ if test -f $(MODDIR)/$$mod; then \
+ rm -f $(MODDIR)/$$mod; \
+ else :; fi; \
+ done
+
+# ========== DEPENDENCIES FOR CLEANUP ==========
+
+@@clean.mk@@
+
+mostlyclean-here:
+ rm -f *.o *.$(DL_EXT)
+
+clean-here:
+ rm -f *.o.c *.pro *.mdh *.mdhi *.mdhs *.mdh.tmp
+
+distclean-here:
+ rm -f $(makefile) $(makefile).in
+
+# ========== RECURSIVE MAKES ==========
+
+install.bin uninstall.bin install.modules uninstall.modules \
+modobjs modules headers proto:
+ @subdirs='$(SUBDIRS)'; for subdir in $$subdirs; do \
+ ( cd $$subdir && $(MAKE) $(MAKEDEFS) $@ ) || exit 1; \
+ done
+
+# ========== DEPENDENCIES FOR MAINTENANCE ==========
+
+$(makefile): $(makefile).in $(dir_top)/config.status
+ @case $(sdir_top) in \
+ /*) top_srcdir=$(sdir_top) ;; \
+ *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+ esac; \
+ export top_srcdir; \
+ echo 'cd $(dir_top) && $(SHELL)' \
+ '$$top_srcdir/Src/mkmakemod.sh -m $(subdir) $(makefile)'; \
+ cd $(dir_top) && \
+ $(SHELL) $$top_srcdir/Src/mkmakemod.sh -m $(subdir) $(makefile)
+
+$(makefile).in: $(sdir_src)/mkmakemod.sh $(sdir_src)/Makemod.in.in $(MDDS) $(dir_src)/modules-bltin
+ @case $(sdir_top) in \
+ /*) top_srcdir=$(sdir_top) ;; \
+ *) top_srcdir=$(subdir)/$(sdir_top) ;; \
+ esac; \
+ export top_srcdir; \
+ echo 'cd $(dir_top) && $(SHELL)' \
+ '$$top_srcdir/Src/mkmakemod.sh -i $(subdir) $(makefile)'; \
+ cd $(dir_top) && \
+ $(SHELL) $$top_srcdir/Src/mkmakemod.sh -i $(subdir) $(makefile)
+
+$(dir_src)/modules-bltin:
+ @cd $(dir_src) && $(MAKE) $(MAKEDEFS) modules-bltin
diff --git a/Src/Modules/.cvsignore b/Src/Modules/.cvsignore
new file mode 100644
index 000000000..169be5ef9
--- /dev/null
+++ b/Src/Modules/.cvsignore
@@ -0,0 +1,10 @@
+Makefile
+Makefile.in
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
diff --git a/Src/Modules/.distfiles b/Src/Modules/.distfiles
new file mode 100644
index 000000000..4c98f97ea
--- /dev/null
+++ b/Src/Modules/.distfiles
@@ -0,0 +1,8 @@
+DISTFILES_SRC='
+ .cvsignore .distfiles .exrc
+ cap.mdd cap.c
+ clone.mdd clone.c
+ example.mdd example.c
+ files.mdd files.c
+ stat.mdd stat.c
+'
diff --git a/Src/Modules/.exrc b/Src/Modules/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/Modules/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/Modules/cap.c b/Src/Modules/cap.c
new file mode 100644
index 000000000..008b6932d
--- /dev/null
+++ b/Src/Modules/cap.c
@@ -0,0 +1,141 @@
+/*
+ * cap.c - POSIX.1e (POSIX.6) capability set manipulation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1997 Andrew Main
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Andrew Main or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Andrew Main and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Andrew Main and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Andrew Main and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "cap.mdh"
+#include "cap.pro"
+
+#ifdef HAVE_CAP_GET_PROC
+
+static int
+bin_cap(char *nam, char **argv, char *ops, int func)
+{
+ int ret = 0;
+ cap_t caps;
+ if(*argv) {
+ caps = cap_from_text(*argv);
+ if(!caps) {
+ zwarnnam(nam, "invalid capability string", NULL, 0);
+ return 1;
+ }
+ if(cap_set_proc(caps)) {
+ zwarnnam(nam, "can't change capabilites: %e", NULL, errno);
+ ret = 1;
+ }
+ } else {
+ char *result;
+ ssize_t length;
+ caps = cap_get_proc();
+ if(caps)
+ result = cap_to_text(caps, &length);
+ if(!caps || !result) {
+ zwarnnam(nam, "can't get capabilites: %e", NULL, errno);
+ ret = 1;
+ } else
+ puts(result);
+ }
+ cap_free(&caps);
+ return ret;
+}
+
+static int
+bin_getcap(char *nam, char **argv, char *ops, int func)
+{
+ int ret = 0;
+
+ do {
+ char *result;
+ ssize_t length;
+ cap_t caps = cap_get_file(*argv);
+ if(caps)
+ result = cap_to_text(caps, &length);
+ if (!caps || !result) {
+ zwarnnam(nam, "%s: %e", *argv, errno);
+ ret = 1;
+ } else
+ printf("%s %s\n", *argv, result);
+ cap_free(&caps);
+ } while(*++argv);
+ return ret;
+}
+
+static int
+bin_setcap(char *nam, char **argv, char *ops, int func)
+{
+ cap_t caps;
+ int ret = 0;
+
+ caps = cap_from_text(*argv++);
+ if(!caps) {
+ zwarnnam(nam, "invalid capability string", NULL, 0);
+ return 1;
+ }
+
+ do {
+ if(cap_set_file(*argv, caps)) {
+ zwarnnam(nam, "%s: %e", *argv, errno);
+ ret = 1;
+ }
+ } while(*++argv);
+ cap_free(&caps);
+ return ret;
+}
+
+#else /* !HAVE_CAP_GET_PROC */
+
+# define bin_cap bin_notavail
+# define bin_getcap bin_notavail
+# define bin_setcap bin_notavail
+
+#endif /* !HAVE_CAP_GET_PROC */
+
+/* module paraphernalia */
+
+static struct builtin bintab[] = {
+ BUILTIN("cap", 0, bin_cap, 0, 1, 0, NULL, NULL),
+ BUILTIN("getcap", 0, bin_getcap, 1, -1, 0, NULL, NULL),
+ BUILTIN("setcap", 0, bin_setcap, 2, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_cap(Module m)
+{
+ return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_cap(Module m)
+{
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+#endif
diff --git a/Src/Modules/cap.mdd b/Src/Modules/cap.mdd
new file mode 100644
index 000000000..97f377e9d
--- /dev/null
+++ b/Src/Modules/cap.mdd
@@ -0,0 +1,3 @@
+autobins="cap getcap setcap"
+
+objects="cap.o"
diff --git a/Src/Modules/clone.c b/Src/Modules/clone.c
new file mode 100644
index 000000000..11387fc90
--- /dev/null
+++ b/Src/Modules/clone.c
@@ -0,0 +1,115 @@
+/*
+ * clone.c - start a forked instance of the current shell on a new terminal
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1997 Zoltán Hidvégi
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Zoltán Hidvégi and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/*
+ * The clone builtin can be used to start a forked instance of the current
+ * shell on a new terminal. The only argument to the builtin is the name
+ * of the new terminal. In the new shell the PID, PPID and TTY parameters
+ * are changed appropriately. $! is set to zero in the new instance of the
+ * shell and to the pid of the new instance in the original shell.
+ *
+ */
+
+#include "clone.mdh"
+#include "clone.pro"
+
+/**/
+static int
+bin_clone(char *nam, char **args, char *ops, int func)
+{
+ int ttyfd, pid;
+
+ unmetafy(*args, NULL);
+ ttyfd = open(*args, O_RDWR|O_NOCTTY);
+ if (ttyfd < 0) {
+ zwarnnam(nam, "%s: %e", *args, errno);
+ return 1;
+ }
+ pid = fork();
+ if (!pid) {
+ clearjobtab();
+ ppid = getppid();
+ mypid = getpid();
+#ifdef HAVE_SETSID
+ if (setsid() != mypid) {
+ zwarnnam(nam, "failed to create new session: %e", NULL, errno);
+#endif
+#ifdef TIOCNOTTY
+ if (ioctl(SHTTY, TIOCNOTTY))
+ zwarnnam(nam, "%e", NULL, errno);
+ setpgrp(0L, mypid);
+#endif
+#ifdef HAVE_SETSID
+ }
+#endif
+ if (ttyfd) {
+ close(0);
+ dup(ttyfd);
+ } else
+ ttyfd = -1;
+ close(1);
+ close(2);
+ dup(0);
+ dup(0);
+ closem(0);
+ close(coprocin);
+ close(coprocout);
+ init_io();
+ setsparam("TTY", ztrdup(ttystrname));
+ }
+ close(ttyfd);
+ if (pid < 0) {
+ zerrnam(nam, "fork failed: %e", NULL, errno);
+ return 1;
+ }
+ lastpid = pid;
+ return 0;
+}
+
+static struct builtin bintab[] = {
+ BUILTIN("clone", 0, bin_clone, 1, 1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_clone(Module m)
+{
+ return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_clone(Module m)
+{
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+#endif
diff --git a/Src/Modules/clone.mdd b/Src/Modules/clone.mdd
new file mode 100644
index 000000000..5277d3151
--- /dev/null
+++ b/Src/Modules/clone.mdd
@@ -0,0 +1,3 @@
+autobins="clone"
+
+objects="clone.o"
diff --git a/Src/Modules/example.c b/Src/Modules/example.c
new file mode 100644
index 000000000..45ef3c28f
--- /dev/null
+++ b/Src/Modules/example.c
@@ -0,0 +1,76 @@
+/*
+ * example.c - an example module for zsh
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Zoltán Hidvégi
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Zoltán Hidvégi and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "example.mdh"
+#include "example.pro"
+
+/**/
+static int
+bin_example(char *nam, char **args, char *ops, int func)
+{
+ unsigned char c;
+
+ printf("Options: ");
+ for (c = 32; ++c < 128;)
+ if (ops[c])
+ putchar(c);
+ printf("\nArguments:");
+ for (; *args; args++) {
+ putchar(' ');
+ fputs(*args, stdout);
+ }
+ printf("\nName: %s\n", nam);
+ return 0;
+}
+
+/*
+ * boot_example is executed when the module is loaded.
+ */
+
+static struct builtin bintab[] = {
+ BUILTIN("example", 0, bin_example, 0, -1, 0, "flags", NULL),
+};
+
+/**/
+int
+boot_example(Module m)
+{
+ return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_example(Module m)
+{
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+#endif
diff --git a/Src/Modules/example.mdd b/Src/Modules/example.mdd
new file mode 100644
index 000000000..89f12097c
--- /dev/null
+++ b/Src/Modules/example.mdd
@@ -0,0 +1,3 @@
+autobins="example"
+
+objects="example.o"
diff --git a/Src/Modules/files.c b/Src/Modules/files.c
new file mode 100644
index 000000000..6127c5524
--- /dev/null
+++ b/Src/Modules/files.c
@@ -0,0 +1,528 @@
+/*
+ * files.c - file operation builtins
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Andrew Main
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Andrew Main or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Andrew Main and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Andrew Main and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Andrew Main and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "files.mdh"
+
+typedef int (*MoveFunc) _((char const *, char const *));
+
+#ifndef STDC_HEADERS
+extern int link _((const char *, const char *));
+extern int symlink _((const char *, const char *));
+extern int rename _((const char *, const char *));
+#endif
+
+#include "files.pro"
+
+/**/
+static int
+ask(void)
+{
+ int a = getchar(), c;
+ for(c = a; c != EOF && c != '\n'; )
+ c = getchar();
+ return a == 'y' || a == 'Y';
+}
+
+/* sync builtin */
+
+/**/
+static int
+bin_sync(char *nam, char **args, char *ops, int func)
+{
+ sync();
+ return 0;
+}
+
+/* mkdir builtin */
+
+/**/
+static int
+bin_mkdir(char *nam, char **args, char *ops, int func)
+{
+ mode_t oumask = umask(0);
+ mode_t mode = 0777 & ~oumask;
+ int err = 0;
+
+ umask(oumask);
+ if(ops['m']) {
+ char *str = *args++, *ptr;
+
+ if(!*args) {
+ zwarnnam(nam, "not enough arguments", NULL, 0);
+ return 1;
+ }
+ mode = zstrtol(str, &ptr, 8);
+ if(!*str || *ptr) {
+ zwarnnam(nam, "invalid mode `%s'", str, 0);
+ return 1;
+ }
+ }
+ for(; *args; args++) {
+ char *ptr = strchr(*args, 0);
+
+ while(ptr > *args + (**args == '/') && *--ptr == '/')
+ *ptr = 0;
+ if(ztrlen(*args) > PATH_MAX - 1) {
+ zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
+ err = 1;
+ continue;
+ }
+ if(ops['p']) {
+ char *ptr = *args;
+
+ for(;;) {
+ while(*ptr == '/')
+ ptr++;
+ while(*ptr && *ptr != '/')
+ ptr++;
+ if(!*ptr) {
+ err |= domkdir(nam, *args, mode, 1);
+ break;
+ } else {
+ int e;
+
+ *ptr = 0;
+ e = domkdir(nam, *args, mode | 0300, 1);
+ if(e) {
+ err = 1;
+ break;
+ }
+ *ptr = '/';
+ }
+ }
+ } else
+ err |= domkdir(nam, *args, mode, 0);
+ }
+ return err;
+}
+
+/**/
+static int
+domkdir(char *nam, char *path, mode_t mode, int p)
+{
+ int err;
+ mode_t oumask;
+ char const *rpath = unmeta(path);
+
+ if(p) {
+ struct stat st;
+
+ if(!lstat(rpath, &st) && S_ISDIR(st.st_mode))
+ return 0;
+ }
+ oumask = umask(0);
+ err = mkdir(path, mode) ? errno : 0;
+ umask(oumask);
+ if(!err)
+ return 0;
+ zwarnnam(nam, "cannot make directory `%s': %e", path, err);
+ return 1;
+}
+
+/* rmdir builtin */
+
+/**/
+static int
+bin_rmdir(char *nam, char **args, char *ops, int func)
+{
+ int err = 0;
+
+ for(; *args; args++) {
+ char *rpath = unmeta(*args);
+
+ if(!rpath) {
+ zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
+ err = 1;
+ } else if(rmdir(rpath)) {
+ zwarnnam(nam, "cannot remove directory `%s': %e", *args, errno);
+ err = 1;
+ }
+ }
+ return err;
+}
+
+/* ln and mv builtins */
+
+#define BIN_LN 0
+#define BIN_MV 1
+
+#define MV_NODIRS (1<<0)
+#define MV_FORCE (1<<1)
+#define MV_INTER (1<<2)
+#define MV_ASKNW (1<<3)
+#define MV_ATOMIC (1<<4)
+
+/* bin_ln actually does three related jobs: hard linking, symbolic *
+ * linking, and renaming. If called as mv it renames, otherwise *
+ * it looks at the -s option. If hard linking, it will refuse to *
+ * attempt linking to a directory unless the -d option is given. */
+
+/**/
+static int
+bin_ln(char *nam, char **args, char *ops, int func)
+{
+ MoveFunc move;
+ int flags, space, err = 0;
+ char **a, *ptr, *rp;
+ struct stat st;
+ char buf[PATH_MAX * 2 + 1];
+
+
+ if(func == BIN_MV) {
+ move = rename;
+ flags = ops['f'] ? 0 : MV_ASKNW;
+ flags |= MV_ATOMIC;
+ } else {
+ flags = ops['f'] ? MV_FORCE : 0;
+#ifdef HAVE_LSTAT
+ if(ops['s'])
+ move = symlink;
+ else
+#endif
+ {
+ move = link;
+ if(!ops['d'])
+ flags |= MV_NODIRS;
+ }
+ }
+ if(ops['i'] && !ops['f'])
+ flags |= MV_INTER;
+ for(a = args; a[1]; a++) ;
+ if(a != args) {
+ rp = unmeta(*a);
+ if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode))
+ goto havedir;
+ }
+ if(a > args+1) {
+ zwarnnam(nam, "last of many arguments must be a directory", NULL, 0);
+ return 1;
+ }
+ if(!args[1]) {
+ ptr = strrchr(args[0], '/');
+ if(ptr)
+ args[1] = ptr+1;
+ else
+ args[1] = args[0];
+ }
+ return domove(nam, move, args[0], args[1], flags);
+ havedir:
+ strcpy(buf, *a);
+ *a = NULL;
+ space = PATH_MAX - 1 - ztrlen(buf);
+ rp = strchr(buf, 0);
+ *rp++ = '/';
+ for(; *args; args++) {
+ if(ztrlen(*args) > PATH_MAX) {
+ zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
+ err = 1;
+ continue;
+ }
+ ptr = strrchr(*args, '/');
+ if(ptr)
+ ptr++;
+ else
+ ptr = *args;
+ if(ztrlen(ptr) > space) {
+ zwarnnam(nam, "%s: %e", ptr, ENAMETOOLONG);
+ err = 1;
+ continue;
+ }
+ strcpy(rp, ptr);
+ err |= domove(nam, move, *args, buf, flags);
+ }
+ return err;
+}
+
+/**/
+static int
+domove(char *nam, MoveFunc move, char *p, char *q, int flags)
+{
+ struct stat st;
+ char *qbuf;
+ char pbuf[PATH_MAX + 1];
+ strcpy(pbuf, unmeta(p));
+ qbuf = unmeta(q);
+ if(flags & MV_NODIRS) {
+ errno = EISDIR;
+ if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) {
+ zwarnnam(nam, "%s: %e", p, errno);
+ return 1;
+ }
+ }
+ if(!lstat(qbuf, &st)) {
+ int doit = flags & MV_FORCE;
+ if(S_ISDIR(st.st_mode)) {
+ zwarnnam(nam, "%s: cannot overwrite directory", q, 0);
+ return 1;
+ } else if(flags & MV_INTER) {
+ nicezputs(nam, stderr);
+ fputs(": replace `", stderr);
+ nicezputs(q, stderr);
+ fputs("'? ", stderr);
+ fflush(stderr);
+ if(!ask())
+ return 0;
+ doit = 1;
+ } else if((flags & MV_ASKNW) &&
+ !S_ISLNK(st.st_mode) &&
+ access(qbuf, W_OK)) {
+ nicezputs(nam, stderr);
+ fputs(": replace `", stderr);
+ nicezputs(q, stderr);
+ fprintf(stderr, "', overriding mode %04o? ",
+ mode_to_octal(st.st_mode));
+ fflush(stderr);
+ if(!ask())
+ return 0;
+ doit = 1;
+ }
+ if(doit && !(flags & MV_ATOMIC))
+ unlink(qbuf);
+ }
+ if(move(pbuf, qbuf)) {
+ zwarnnam(nam, "%s: %e", p, errno);
+ return 1;
+ }
+ return 0;
+}
+
+/* rm builtin */
+
+/**/
+static int
+bin_rm(char *nam, char **args, char *ops, int func)
+{
+ int err = 0, len;
+ char *rp, *s;
+ struct dirsav ds;
+
+ ds.ino = ds.dev = 0;
+ ds.dirname = NULL;
+ ds.dirfd = ds.level = -1;
+ if (ops['r'] || ops['s']) {
+ if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 &&
+ zgetdir(&ds) && *ds.dirname != '/')
+ ds.dirfd = open("..", O_RDONLY|O_NOCTTY);
+ }
+ for(; !errflag && !(err & 2) && *args; args++) {
+ rp = ztrdup(*args);
+ unmetafy(rp, &len);
+ if (ops['s']) {
+ s = strrchr(rp, '/');
+ if (s && !s[1]) {
+ while (*s == '/' && s > rp)
+ *s-- = '\0';
+ while (*s != '/' && s > rp)
+ s--;
+ }
+ if (s && s[1]) {
+ int e;
+
+ *s = '\0';
+ e = lchdir(s > rp ? rp : "/", &ds, 1);
+ err |= -e;
+ if (!e) {
+ struct dirsav d;
+
+ d.ino = d.dev = 0;
+ d.dirname = NULL;
+ d.dirfd = d.level = -1;
+ err |= dorm(nam, *args, s + 1, ops, &d, 0);
+ zsfree(d.dirname);
+ if (restoredir(&ds))
+ err |= 2;
+ } else
+ zwarnnam(nam, "%s: %e", *args, errno);
+ } else
+ err |= dorm(nam, *args, rp, ops, &ds, 0);
+ } else
+ err |= dorm(nam, *args, rp, ops, &ds, 1);
+ zfree(rp, len + 1);
+ }
+ if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) {
+ zsfree(pwd);
+ pwd = ztrdup("/");
+ chdir(pwd);
+ }
+ if (ds.dirfd >= 0)
+ close(ds.dirfd);
+ zsfree(ds.dirname);
+ return ops['f'] ? 0 : !!err;
+}
+
+/**/
+static int
+dorm(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first)
+{
+ struct stat st;
+
+ if((!ops['d'] || !ops['f']) && !lstat(rp, &st)) {
+ if(!ops['d'] && S_ISDIR(st.st_mode)) {
+ if(ops['r'])
+ return dormr(nam, arg, rp, ops, ds, first);
+ if(!ops['f'])
+ zwarnnam(nam, "%s: %e", arg, EISDIR);
+ return 1;
+ }
+ if(!ops['f'] && ops['i']) {
+ nicezputs(nam, stderr);
+ fputs(": remove `", stderr);
+ nicezputs(arg, stderr);
+ fputs("'? ", stderr);
+ fflush(stderr);
+ if(!ask())
+ return 0;
+ } else if(!ops['f'] &&
+ !S_ISLNK(st.st_mode) &&
+ access(rp, W_OK)) {
+ nicezputs(nam, stderr);
+ fputs(": remove `", stderr);
+ nicezputs(arg, stderr);
+ fprintf(stderr, "', overriding mode %04o? ",
+ mode_to_octal(st.st_mode));
+ fflush(stderr);
+ if(!ask())
+ return 0;
+ }
+ }
+ if(!unlink(rp))
+ return 0;
+ if(!ops['f'])
+ zwarnnam(nam, "%s: %e", arg, errno);
+ return 1;
+}
+
+/**/
+static int
+dormr(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first)
+{
+ char *fn;
+ DIR *d;
+ int err;
+ struct dirsav dsav;
+ char *files = NULL;
+ int fileslen = 0;
+
+ err = -lchdir(rp, ds, !first);
+ if (err) {
+ if (!ops['f'])
+ zwarnnam(nam, "%s: %e", arg, errno);
+ return err;
+ }
+
+ dsav.ino = dsav.dev = 0;
+ dsav.dirname = NULL;
+ dsav.dirfd = dsav.level = -1;
+ d = opendir(".");
+ if(!d) {
+ if(!ops['f'])
+ zwarnnam(nam, "%s: %e", arg, errno);
+ err = 1;
+ } else {
+ int arglen = strlen(arg) + 1;
+
+ while (!errflag && (fn = zreaddir(d, 1))) {
+ int l = strlen(fn) + 1;
+ files = hrealloc(files, fileslen, fileslen + l);
+ strcpy(files + fileslen, fn);
+ fileslen += l;
+ }
+ closedir(d);
+ for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) {
+ int l = strlen(fn) + 1;
+ VARARR(char, narg, arglen + l);
+
+ strcpy(narg,arg);
+ narg[arglen-1] = '/';
+ strcpy(narg + arglen, fn);
+ unmetafy(fn, NULL);
+ err |= dorm(nam, narg, fn, ops, &dsav, 0);
+ fn += l;
+ }
+ hrealloc(files, fileslen, 0);
+ }
+ zsfree(dsav.dirname);
+ if (err & 2)
+ return 2;
+ if (restoredir(ds)) {
+ if(!ops['f'])
+ zwarnnam(nam, "failed to return to previous directory: %e",
+ NULL, errno);
+ return 2;
+ }
+ if(!ops['f'] && ops['i']) {
+ nicezputs(nam, stderr);
+ fputs(": remove `", stderr);
+ nicezputs(arg, stderr);
+ fputs("'? ", stderr);
+ fflush(stderr);
+ if(!ask())
+ return err;
+ }
+ if(!rmdir(rp))
+ return err;
+ if(!ops['f'])
+ zwarnnam(nam, "%s: %e", arg, errno);
+ return 1;
+}
+
+/* module paraphernalia */
+
+#ifdef HAVE_LSTAT
+# define LN_OPTS "dfis"
+#else
+# define LN_OPTS "dfi"
+#endif
+
+static struct builtin bintab[] = {
+ BUILTIN("ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL),
+ BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0, "pm", NULL),
+ BUILTIN("mv", 0, bin_ln, 2, -1, BIN_MV, "fi", NULL),
+ BUILTIN("rm", 0, bin_rm, 1, -1, 0, "dfirs", NULL),
+ BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0, NULL, NULL),
+ BUILTIN("sync", 0, bin_sync, 0, 0, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_files(Module m)
+{
+ return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_files(Module m)
+{
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+#endif
diff --git a/Src/Modules/files.mdd b/Src/Modules/files.mdd
new file mode 100644
index 000000000..236ca2d5a
--- /dev/null
+++ b/Src/Modules/files.mdd
@@ -0,0 +1,3 @@
+autobins="ln mkdir mv rm rmdir sync"
+
+objects="files.o"
diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c
new file mode 100644
index 000000000..09245b52f
--- /dev/null
+++ b/Src/Modules/stat.c
@@ -0,0 +1,535 @@
+/*
+ * stat.c - stat builtin interface to system call
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Peter Stephenson
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Peter Stephenson or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Peter Stephenson and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Peter Stephenson and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Peter Stephenson and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "stat.mdh"
+#include "stat.pro"
+
+enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID,
+ ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM,
+ ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT };
+enum statflags { STF_NAME = 1, STF_FILE = 2, STF_STRING = 4, STF_RAW = 8,
+ STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64 };
+static char *statelts[] = { "device", "inode", "mode", "nlink",
+ "uid", "gid", "rdev", "size", "atime",
+ "mtime", "ctime", "blksize", "blocks",
+ "link", NULL };
+
+/**/
+static void
+statmodeprint(mode_t mode, char *outbuf, int flags)
+{
+ if (flags & STF_RAW) {
+ sprintf(outbuf, "%lu", (unsigned long)mode);
+ if (flags & STF_STRING)
+ strcat(outbuf, " (");
+ }
+ if (flags & STF_STRING) {
+ static const char *modes = "?rwxrwxrwx";
+ static const mode_t mflags[] = { S_IRUSR, S_IWUSR, S_IXUSR,
+ S_IRGRP, S_IWGRP, S_IXGRP,
+ S_IROTH, S_IWOTH, S_IXOTH };
+ const mode_t *mfp = mflags;
+ char pm[11];
+ int i;
+
+ if (S_ISBLK(mode))
+ *pm = 'b';
+ else if (S_ISCHR(mode))
+ *pm = 'c';
+ else if (S_ISDIR(mode))
+ *pm = 'd';
+ else if (S_ISFIFO(mode))
+ *pm = 'p';
+ else if (S_ISLNK(mode))
+ *pm = 'l';
+ else if (S_ISMPC(mode))
+ *pm = 'm';
+ else if (S_ISNWK(mode))
+ *pm = 'n';
+ else if (S_ISOFD(mode))
+ *pm = 'M';
+ else if (S_ISOFL(mode))
+ *pm = 'M';
+ else if (S_ISREG(mode))
+ *pm = '-';
+ else if (S_ISSOCK(mode))
+ *pm = 's';
+ else
+ *pm = '?';
+
+ for (i = 1; i <= 9; i++)
+ pm[i] = (mode & *mfp++) ? modes[i] : '-';
+
+ if (mode & S_ISUID)
+ pm[3] = (mode & S_IXUSR) ? 's' : 'S';
+ if (mode & S_ISGID)
+ pm[6] = (mode & S_IXGRP) ? 's' : 'S';
+ if (mode & S_ISVTX)
+ pm[9] = (mode & S_IXOTH) ? 't' : 'T';
+
+ pm[10] = 0;
+ strcat(outbuf, pm);
+ if (flags & STF_RAW)
+ strcat(outbuf, ")");
+ }
+}
+
+
+/**/
+static void
+statuidprint(uid_t uid, char *outbuf, int flags)
+{
+ if (flags & STF_RAW) {
+ sprintf(outbuf, "%lu", (unsigned long)uid);
+ if (flags & STF_STRING)
+ strcat(outbuf, " (");
+ }
+ if (flags & STF_STRING) {
+#ifdef HAVE_GETPWUID
+ struct passwd *pwd;
+ pwd = getpwuid(uid);
+ strcat(outbuf, pwd ? pwd->pw_name : "???");
+#else /* !HAVE_GETPWUID */
+ strcat(outbuf, "???");
+#endif /* !HAVE_GETPWUID */
+ if (flags & STF_RAW)
+ strcat(outbuf, ")");
+ }
+}
+
+
+/**/
+static void
+statgidprint(gid_t gid, char *outbuf, int flags)
+{
+ if (flags & STF_RAW) {
+ sprintf(outbuf, "%lu", (unsigned long)gid);
+ if (flags & STF_STRING)
+ strcat(outbuf, " (");
+ }
+ if (flags & STF_STRING) {
+#ifdef HAVE_GETGRGID
+ struct group *gr;
+ gr = getgrgid(gid);
+ strcat(outbuf, gr ? gr->gr_name : "???");
+#else /* !HAVE_GETGRGID */
+ strcat(outbuf, "???");
+#endif /* !HAVE_GETGRGID */
+ if (flags & STF_RAW)
+ strcat(outbuf, ")");
+ }
+}
+
+static char *timefmt;
+
+/**/
+static void
+stattimeprint(time_t tim, char *outbuf, int flags)
+{
+ if (flags & STF_RAW) {
+ sprintf(outbuf, "%ld", (unsigned long)tim);
+ if (flags & STF_STRING)
+ strcat(outbuf, " (");
+ }
+ if (flags & STF_STRING) {
+ char *oend = outbuf + strlen(outbuf);
+ ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) :
+ localtime(&tim));
+ if (flags & STF_RAW)
+ strcat(outbuf, ")");
+ }
+}
+
+
+/**/
+static void
+statulprint(unsigned long num, char *outbuf)
+{
+ sprintf(outbuf, "%lu", num);
+}
+
+
+/**/
+static void
+statlinkprint(struct stat *sbuf, char *outbuf, char *fname)
+{
+ int num;
+
+ /* fname is NULL if we are looking at an fd */
+ if (fname && S_ISLNK(sbuf->st_mode) &&
+ (num = readlink(fname, outbuf, PATH_MAX)) > 0) {
+ /* readlink doesn't terminate the buffer itself */
+ outbuf[num] = '\0';
+ }
+}
+
+
+/**/
+static void
+statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags)
+{
+ char *optr = outbuf;
+
+ if (flags & STF_NAME) {
+ sprintf(outbuf, (flags & (STF_PICK|STF_ARRAY)) ?
+ "%s " : "%-8s", statelts[iwhich]);
+ optr += strlen(outbuf);
+ }
+ *optr = '\0';
+
+ /* cast values to unsigned long as safest bet */
+ switch (iwhich) {
+ case ST_DEV:
+ statulprint((unsigned long)sbuf->st_dev, optr);
+ break;
+
+ case ST_INO:
+ statulprint((unsigned long)sbuf->st_ino, optr);
+ break;
+
+ case ST_MODE:
+ statmodeprint(sbuf->st_mode, optr, flags);
+ break;
+
+ case ST_NLINK:
+ statulprint((unsigned long)sbuf->st_nlink, optr);
+ break;
+
+ case ST_UID:
+ statuidprint(sbuf->st_uid, optr, flags);
+ break;
+
+ case ST_GID:
+ statgidprint(sbuf->st_gid, optr, flags);
+ break;
+
+ case ST_RDEV:
+ statulprint((unsigned long)sbuf->st_rdev, optr);
+ break;
+
+ case ST_SIZE:
+ statulprint((unsigned long)sbuf->st_size, optr);
+ break;
+
+ case ST_ATIM:
+ stattimeprint(sbuf->st_atime, optr, flags);
+ break;
+
+ case ST_MTIM:
+ stattimeprint(sbuf->st_mtime, optr, flags);
+ break;
+
+ case ST_CTIM:
+ stattimeprint(sbuf->st_ctime, optr, flags);
+ break;
+
+ case ST_BLKSIZE:
+ statulprint((unsigned long)sbuf->st_blksize, optr);
+ break;
+
+ case ST_BLOCKS:
+ statulprint((unsigned long)sbuf->st_blocks, optr);
+ break;
+
+ case ST_READLINK:
+ statlinkprint(sbuf, optr, fname);
+ break;
+
+ case ST_COUNT: /* keep some compilers happy */
+ break;
+ }
+}
+
+
+/*
+ *
+ * Options:
+ * -f fd: stat fd instead of file
+ * -g: use GMT rather than local time for time strings (forces -s on).
+ * -n: always print file name of file being statted
+ * -N: never print file name
+ * -l: list stat types
+ * -L: do lstat (else links are implicitly dereferenced by stat)
+ * -t: always print name of stat type
+ * -T: never print name of stat type
+ * -r: print raw alongside string data
+ * -s: string, print mode, times, uid, gid as appropriate strings:
+ * harmless but unnecessary when combined with -r.
+ * -A array: assign results to given array, one stat result per element.
+ * File names and type names are only added if explicitly requested:
+ * file names are returned as a separate array element, type names as
+ * prefix to element. Note the formatting deliberately contains
+ * fewer frills when -A is used.
+ * -F fmt: specify a $TIME-like format for printing times; the default
+ * is the (CTIME-like) "%a %b %e %k:%M:%S". This option implies
+ * -s as it is not useful for numerical times.
+ *
+ * +type selects just element type of stat buffer (-l gives list):
+ * type can be shortened to unambiguous string. only one type is
+ * allowed. The extra type, +link, reads the target of a symbolic
+ * link; it is empty if the stat was not an lstat or if
+ * a file descriptor was stat'd, if the stat'd file is
+ * not a symbolic link, or if symbolic links are not
+ * supported. If +link is explicitly requested, the -L (lstat)
+ * option is set automatically.
+ */
+/**/
+static int
+bin_stat(char *name, char **args, char *ops, int func)
+{
+ char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL;
+ int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0;
+ struct stat statbuf;
+ int found = 0, nargs;
+
+ timefmt = "%a %b %e %k:%M:%S";
+
+ for (; *args && (**args == '+' || **args == '-'); args++) {
+ char *arg = *args+1;
+ if (!*arg || *arg == '-' || *arg == '+') {
+ args++;
+ break;
+ }
+
+ if (**args == '+') {
+ if (found)
+ break;
+ len = strlen(arg);
+ for (aptr = statelts; *aptr; aptr++)
+ if (!strncmp(*aptr, arg, len)) {
+ found++;
+ iwhich = aptr - statelts;
+ }
+ if (found > 1) {
+ zerrnam(name, "%s: ambiguous stat element", arg, 0);
+ return 1;
+ } else if (found == 0) {
+ zerrnam(name, "%s: no such stat element", arg, 0);
+ return 1;
+ }
+ /* if name of link requested, turn on lstat */
+ if (iwhich == ST_READLINK)
+ ops['L'] = 1;
+ flags |= STF_PICK;
+ } else {
+ for (; *arg; arg++) {
+ if (strchr("glLnNrstT", *arg))
+ ops[*arg] = 1;
+ else if (*arg == 'A') {
+ if (arg[1]) {
+ arrnam = arg+1;
+ } else if (!(arrnam = *++args)) {
+ zerrnam(name, "missing parameter name\n",
+ NULL, 0);
+ return 1;
+ }
+ flags |= STF_ARRAY;
+ break;
+ } else if (*arg == 'f') {
+ char *sfd;
+ ops['f'] = 1;
+ if (arg[1]) {
+ sfd = arg+1;
+ } else if (!(sfd = *++args)) {
+ zerrnam(name, "missing file descriptor\n", NULL, 0);
+ return 1;
+ }
+ fd = zstrtol(sfd, &sfd, 10);
+ if (*sfd) {
+ zerrnam(name, "bad file descriptor\n", NULL, 0);
+ return 1;
+ }
+ break;
+ } else if (*arg == 'F') {
+ if (arg[1]) {
+ timefmt = arg+1;
+ } else if (!(timefmt = *++args)) {
+ zerrnam(name, "missing time format\n", NULL, 0);
+ return 1;
+ }
+ /* force string format in order to use time format */
+ ops['s'] = 1;
+ break;
+ } else {
+ zerrnam(name, "bad option: -%c", NULL, *arg);
+ return 1;
+ }
+ }
+ }
+ }
+
+ if (ops['l']) {
+ /* list types and return: can also list to array */
+ if (arrnam) {
+ arrptr = array = (char **)zalloc((ST_COUNT+1)*sizeof(char *));
+ array[ST_COUNT] = NULL;
+ }
+ for (aptr = statelts; *aptr; aptr++) {
+ if (arrnam) {
+ *arrptr++ = ztrdup(*aptr);
+ } else {
+ printf("%s", *aptr);
+ if (aptr[1])
+ putchar(' ');
+ }
+ }
+ if (arrnam) {
+ setaparam(arrnam, array);
+ if (errflag)
+ return 1;
+ } else
+ putchar('\n');
+ return 0;
+ }
+
+ if (!*args && !ops['f']) {
+ zwarnnam(name, "no files given", NULL, 0);
+ return 1;
+ } else if (*args && ops['f']) {
+ zwarnnam(name, "no files allowed with -f", NULL, 0);
+ return 1;
+ }
+
+ nargs = 0;
+ if (ops['f'])
+ nargs = 1;
+ else
+ for (aptr = args; *aptr; aptr++)
+ nargs++;
+
+ if (ops['s'] || ops['r'])
+ flags |= STF_STRING;
+ if (ops['r'] || !ops['s'])
+ flags |= STF_RAW;
+ if (ops['n'])
+ flags |= STF_FILE;
+ if (ops['t'])
+ flags |= STF_NAME;
+ if (ops['g'])
+ flags |= STF_GMT;
+
+ if (!arrnam) {
+ if (nargs > 1)
+ flags |= STF_FILE;
+ if (!(flags & STF_PICK))
+ flags |= STF_NAME;
+ }
+
+ if (ops['N'] || ops['f'])
+ flags &= ~STF_FILE;
+ if (ops['T'])
+ flags &= ~STF_NAME;
+
+ if (arrnam) {
+ arrsize = (flags & STF_PICK) ? 1 : ST_COUNT;
+ if (flags & STF_FILE)
+ arrsize++;
+ arrsize *= nargs;
+ arrptr = array = (char **)zcalloc((arrsize+1)*sizeof(char *));
+ }
+
+ for (; ops['f'] || *args; args++) {
+ char outbuf[PATH_MAX + 9]; /* "link " + link name + NULL */
+ int rval = ops['f'] ? fstat(fd, &statbuf) :
+ ops['L'] ? lstat(*args, &statbuf) : stat(*args, &statbuf);
+ if (rval) {
+ if (ops['f'])
+ sprintf(outbuf, "%d", fd);
+ zwarnnam(name, "%s: %e", ops['f'] ? outbuf : *args, errno);
+ ret = 1;
+ if (ops['f'] || arrnam)
+ break;
+ else
+ continue;
+ }
+
+ if (flags & STF_FILE)
+ if (arrnam)
+ *arrptr++ = ztrdup(*args);
+ else
+ printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n");
+ if (iwhich > -1) {
+ statprint(&statbuf, outbuf, *args, iwhich, flags);
+ if (arrnam)
+ *arrptr++ = ztrdup(outbuf);
+ else
+ printf("%s\n", outbuf);
+ } else {
+ int i;
+ for (i = 0; i < ST_COUNT; i++) {
+ statprint(&statbuf, outbuf, *args, i, flags);
+ if (arrnam)
+ *arrptr++= ztrdup(outbuf);
+ else
+ printf("%s\n", outbuf);
+ }
+ }
+ if (ops['f'])
+ break;
+
+ if (!arrnam && args[1] && !(flags & STF_PICK))
+ putchar('\n');
+ }
+
+ if (arrnam)
+ if (ret) {
+ for (aptr = array; *aptr; aptr++)
+ zsfree(*aptr);
+ zfree(array, arrsize+1);
+ } else {
+ setaparam(arrnam, array);
+ if (errflag)
+ return 1;
+ }
+
+ return ret;
+}
+
+static struct builtin bintab[] = {
+ BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_stat(Module m)
+{
+ return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_stat(Module m)
+{
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+
+#endif
diff --git a/Src/Modules/stat.mdd b/Src/Modules/stat.mdd
new file mode 100644
index 000000000..b775fda09
--- /dev/null
+++ b/Src/Modules/stat.mdd
@@ -0,0 +1,3 @@
+autobins="stat"
+
+objects="stat.o"
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
new file mode 100644
index 000000000..ca0843419
--- /dev/null
+++ b/Src/Modules/zftp.c
@@ -0,0 +1,2596 @@
+/*
+ * zftp.c - builtin FTP client
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1998 Peter Stephenson
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Peter Stephenson or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Peter Stephenson and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Peter Stephenson and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Peter Stephenson and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/*
+ * TODO:
+ * can signal handling be improved?
+ * error messages may need tidying up.
+ * maybe we should block CTRL-c on some more operations,
+ * otherwise you can get the connection closed prematurely.
+ * some way of turning off progress reports when backgrounded
+ * would be nice, but the shell doesn't make it easy to find that out.
+ * the progress reports 100% a bit prematurely: the data may still
+ * be in transit, and we are stuck waiting for a message from the
+ * server. but there's really nothing else to do. it's worst
+ * with small files.
+ * proxy/gateway connections if i knew what to do
+ * options to specify e.g. a non-standard port
+ * optimizing things out is hard in general when you don't know what
+ * the shell's going to want, but they may be places to second guess
+ * the user. Some of the variables could be made special and so
+ * only retrieve things like the current directory when necessary.
+ * But it's much neater to have ordinary variables, which the shell
+ * can manage without our interference, and getting the directory
+ * just every time it changes isn't so bad. The user can always
+ * toggle the `Dumb' preference if it's feeling clever.
+ */
+#include "zftp.mdh"
+#include "zftp.pro"
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+/* it's a TELNET based protocol, but don't think I like doing this */
+#include <arpa/telnet.h>
+
+/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */
+#ifndef INADDR_NONE
+#define INADDR_NONE (in_addr_t)-1
+#endif
+
+/*
+ * For FTP block mode
+ *
+ * The server on our AIX machine here happily accepts block mode, takes the
+ * first connection, then at the second complains that it's got nowhere
+ * to send data. The same problem happens with ncftp, it's not just
+ * me. And a lot of servers don't even support block mode. So I'm not sure
+ * how widespread the supposed ability to leave open the data fd between
+ * transfers. Therefore, I've closed all connections after the transfer.
+ * But then what's the point in block mode? I only implemented it because
+ * it says in RFC959 that you need it to be able to restart transfers
+ * later in the file. However, it turns out that's not true for
+ * most servers --- but our AIX machine happily accepts the REST
+ * command and then dumps the whole file onto you. Sigh.
+ *
+ * Note on block sizes:
+ * Not quite sure how to optimize this: in principle
+ * we should handle blocks up to 65535 bytes, which
+ * is pretty big, and should presumably send blocks
+ * which are smaller to be on the safe side.
+ * Currently we send 32768 and use that also as
+ * the maximum to receive. No-one's complained yet. Of course,
+ * no-one's *used* it yet apart from me, but even so.
+ */
+
+struct zfheader {
+ char flags;
+ unsigned char bytes[2];
+};
+
+enum {
+ ZFHD_MARK = 16, /* restart marker */
+ ZFHD_ERRS = 32, /* suspected errors in block */
+ ZFHD_EOFB = 64, /* block is end of record */
+ ZFHD_EORB = 128 /* block is end of file */
+};
+
+typedef int (*readwrite_t)(int, char *, size_t, int);
+
+struct zftpcmd {
+ const char *nam;
+ int (*fun) _((char *, char **, int));
+ int min, max, flags;
+};
+
+enum {
+ ZFTP_CONN = 0x0001, /* must be connected */
+ ZFTP_LOGI = 0x0002, /* must be logged in */
+ ZFTP_TBIN = 0x0004, /* set transfer type image */
+ ZFTP_TASC = 0x0008, /* set transfer type ASCII */
+ ZFTP_NLST = 0x0010, /* use NLST rather than LIST */
+ ZFTP_DELE = 0x0020, /* a delete rather than a make */
+ ZFTP_SITE = 0x0040, /* a site rather than a quote */
+ ZFTP_APPE = 0x0080, /* append rather than overwrite */
+ ZFTP_HERE = 0x0100, /* here rather than over there */
+ ZFTP_CDUP = 0x0200, /* CDUP rather than CWD */
+ ZFTP_REST = 0x0400, /* restart: set point in remote file */
+ ZFTP_RECV = 0x0800 /* receive rather than send */
+};
+
+typedef struct zftpcmd *Zftpcmd;
+
+static struct zftpcmd zftpcmdtab[] = {
+ { "open", zftp_open, 0, 4, 0 },
+ { "params", zftp_params, 0, 4, 0 },
+ { "login", zftp_login, 0, 3, ZFTP_CONN },
+ { "user", zftp_login, 0, 3, ZFTP_CONN },
+ { "cd", zftp_cd, 1, 1, ZFTP_CONN|ZFTP_LOGI },
+ { "cdup", zftp_cd, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_CDUP },
+ { "dir", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI },
+ { "ls", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_NLST },
+ { "type", zftp_type, 0, 1, ZFTP_CONN|ZFTP_LOGI },
+ { "ascii", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TASC },
+ { "binary", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TBIN },
+ { "mode", zftp_mode, 0, 1, ZFTP_CONN|ZFTP_LOGI },
+ { "local", zftp_local, 0, -1, ZFTP_HERE },
+ { "remote", zftp_local, 1, -1, ZFTP_CONN|ZFTP_LOGI },
+ { "get", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV },
+ { "getat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV|ZFTP_REST },
+ { "put", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI },
+ { "putat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_REST },
+ { "append", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE },
+ { "appendat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE|ZFTP_REST },
+ { "delete", zftp_delete, 1, -1, ZFTP_CONN|ZFTP_LOGI },
+ { "mkdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI },
+ { "rmdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI|ZFTP_DELE },
+ { "rename", zftp_rename, 2, 2, ZFTP_CONN|ZFTP_LOGI },
+ { "quote", zftp_quote, 1, -1, ZFTP_CONN },
+ { "site", zftp_quote, 1, -1, ZFTP_CONN|ZFTP_SITE },
+ { "close", zftp_close, 0, 0, ZFTP_CONN },
+ { "quit", zftp_close, 0, 0, ZFTP_CONN },
+ { 0, 0, 0, 0}
+};
+
+static struct builtin bintab[] = {
+ BUILTIN("zftp", 0, bin_zftp, 1, -1, 0, NULL, NULL),
+};
+
+/*
+ * these are the non-special params to unset when a connection
+ * closes. any special params are handled, well, specially.
+ * currently there aren't any, which is the way I like it.
+ */
+static char *zfparams[] = {
+ "ZFTP_HOST", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER",
+ "ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", NULL
+};
+
+/* flags for zfsetparam */
+
+enum {
+ ZFPM_READONLY = 0x01, /* make parameter readonly */
+ ZFPM_IFUNSET = 0x02, /* only set if not already set */
+ ZFPM_INTEGER = 0x04 /* passed pointer to long */
+};
+
+/*
+ * Basic I/O variables for control connection:
+ * zcfd != -1 is a flag that there is a connection open.
+ */
+static int zcfd = -1;
+static FILE *zcin;
+static struct sockaddr_in zsock;
+
+/*
+ * zcfinish = 0 keep going
+ * 1 line finished, alles klar
+ * 2 EOF
+ */
+static int zcfinish;
+/* zfclosing is set if zftp_close() is active */
+static int zfclosing;
+
+/*
+ * Now stuff for data connection
+ */
+static int zdfd = -1;
+static struct sockaddr_in zdsock;
+
+/*
+ * Stuff about last message: last line of message and status code.
+ * The reply is also stored in $ZFTP_REPLY; we keep these separate
+ * for convenience.
+ */
+static char *lastmsg, lastcodestr[4];
+static int lastcode;
+
+/* flag for remote system is UNIX --- useful to know as things are simpler */
+static int zfis_unix, zfpassive_conn;
+
+/* remote system has size, mdtm commands */
+enum {
+ ZFCP_UNKN = 0, /* dunno if it works on this server */
+ ZFCP_YUPP = 1, /* it does */
+ ZFCP_NOPE = 2 /* it doesn't */
+};
+
+static int zfhas_size, zfhas_mdtm;
+
+/*
+ * We keep an fd open for communication between the main shell
+ * and forked off bits and pieces. This allows us to know
+ * if something happend in a subshell: mode changed, type changed,
+ * connection was closed. If something too substantial happened
+ * in a subshell --- connection opened, ZFTP_USER and ZFTP_PWD changed
+ * --- we don't try to track it because it's too complicated.
+ */
+enum {
+ ZFST_ASCI = 0x00, /* type for next transfer is ASCII */
+ ZFST_IMAG = 0x01, /* type for next transfer is image */
+
+ ZFST_TMSK = 0x01, /* mask for type flags */
+ ZFST_TBIT = 0x01, /* number of bits in type flags */
+
+ ZFST_CASC = 0x00, /* current type is ASCII - default */
+ ZFST_CIMA = 0x02, /* current type is image */
+
+ ZFST_STRE = 0x00, /* stream mode - default */
+ ZFST_BLOC = 0x04, /* block mode */
+
+ ZFST_MMSK = 0x04, /* mask for mode flags */
+
+ ZFST_LOGI = 0x08, /* user logged in */
+ ZFST_NOPS = 0x10, /* server doesn't understand PASV */
+ ZFST_NOSZ = 0x20, /* server doesn't send `(XXXX bytes)' reply */
+ ZFST_TRSZ = 0x40, /* tried getting 'size' from reply */
+ ZFST_CLOS = 0x80 /* connection closed */
+};
+#define ZFST_TYPE(x) (x & ZFST_TMSK)
+/*
+ * shift current type flags to match type flags: should be by
+ * the number of bits in the type flags
+ */
+#define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK)
+#define ZFST_MODE(x) (x & ZFST_MMSK)
+
+static int zfstatfd = -1, zfstatus;
+
+/* Preferences, read in from the `zftp_prefs' array variable */
+enum {
+ ZFPF_SNDP = 0x01, /* Use send port mode */
+ ZFPF_PASV = 0x02, /* Try using passive mode */
+ ZFPF_DUMB = 0x04 /* Don't do clever things with variables */
+};
+
+/* The flags as stored internally. */
+int zfprefs;
+
+
+/* zfuserparams is the storage area for zftp_params() */
+char **zfuserparams;
+
+/*
+ * Bits and pieces for dealing with SIGALRM (and SIGPIPE, but that's
+ * easier). The complication is that SIGALRM may already be handled
+ * by the user setting TMOUT and possibly setting their own trap --- in
+ * fact, it's always handled by the shell when it's interactive. It's
+ * too difficult to use zsh's own signal handler --- either it would
+ * need rewriting to use a C function as a trap, or we would need a
+ * hack to make it callback via a hidden builtin from a function --- so
+ * just install our own, and use settrap() to restore the behaviour
+ * afterwards if necessary. However, the more that could be done by
+ * the main shell code, the better I would like it.
+ *
+ * Since we don't want to go through the palaver of changing between
+ * the main zsh signal handler and ours every time we start or stop the
+ * alarm, we keep the flag zfalarmed set to 1 while zftp is rigged to
+ * handle alarms. This is tested at the end of bin_zftp(), which is
+ * the entry point for all functions, and that restores the original
+ * handler for SIGALRM. To turn off the alarm temporarily in the zftp
+ * code we then just call alarm(0).
+ *
+ * If we could rely on having select() or some replacement, we would
+ * only need the alarm during zftp_open().
+ */
+
+/* flags for alarm set, alarm gone off */
+int zfalarmed, zfdrrrring;
+/* remember old alarm status */
+time_t oaltime;
+unsigned int oalremain;
+
+/*
+ * Where to jump to when the alarm goes off. This is much
+ * easier than fiddling with error flags at every turn.
+ * Since we don't expect too many alarm's, the simple setjmp()
+ * mechanism should be good enough.
+ *
+ * gcc -O gives apparently spurious `may be clobbered by longjmp' warnings.
+ */
+jmp_buf zfalrmbuf;
+
+/* The signal handler itself */
+
+/**/
+static RETSIGTYPE
+zfhandler(int sig)
+{
+ if (sig == SIGALRM) {
+ zfdrrrring = 1;
+#ifdef ETIMEDOUT /* just in case */
+ errno = ETIMEDOUT;
+#else
+ errno = EIO;
+#endif
+ longjmp(zfalrmbuf, 1);
+ }
+ DPUTS(1, "zfhandler caught incorrect signal");
+}
+
+/* Set up for an alarm call */
+
+/**/
+static void
+zfalarm(int tmout)
+{
+ zfdrrrring = 0;
+ /*
+ * We need to do this even if tmout is zero, since there may
+ * be a non-zero timeout set in the main shell which we don't
+ * want to go off. This could be argued the other way, since
+ * if we don't get that it's probably harmless. But this looks safer.
+ */
+ if (zfalarmed) {
+ alarm(tmout);
+ return;
+ }
+ signal(SIGALRM, zfhandler);
+ oalremain = alarm(tmout);
+ if (oalremain)
+ oaltime = time(NULL);
+ /*
+ * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the
+ * shell's handler doesn't get the signal, they don't matter.
+ */
+ zfalarmed = 1;
+}
+
+/* Set up for a broken pipe */
+
+/**/
+static void
+zfpipe()
+{
+ /* Just ignore SIGPIPE and rely on getting EPIPE from the write. */
+ signal(SIGPIPE, SIG_IGN);
+}
+
+/* Unset the alarm, see above */
+
+/**/
+static void
+zfunalarm()
+{
+ if (oalremain) {
+ /*
+ * The alarm was previously set, so set it back, adjusting
+ * for the time used. Mostly the alarm was set to zero
+ * beforehand, so it would probably be best to reinstall
+ * the proper signal handler before resetting the alarm.
+ *
+ * I love the way alarm() uses unsigned int while time_t
+ * is probably something completely different.
+ */
+ time_t tdiff = time(NULL) - oaltime;
+ alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
+ } else
+ alarm(0);
+ if (sigtrapped[SIGALRM] || interact) {
+ if (sigfuncs[SIGALRM] || !sigtrapped[SIGALRM])
+ install_handler(SIGALRM);
+ else
+ signal_ignore(SIGALRM);
+ } else
+ signal_default(SIGALRM);
+ zfalarmed = 0;
+}
+
+/* Restore SIGPIPE handling to its usual status */
+
+/**/
+static void
+zfunpipe()
+{
+ if (sigtrapped[SIGPIPE]) {
+ if (sigfuncs[SIGPIPE])
+ install_handler(SIGPIPE);
+ else
+ signal_ignore(SIGPIPE);
+ } else
+ signal_default(SIGPIPE);
+}
+
+/*
+ * Same as movefd(), but don't mark the fd in the zsh tables,
+ * because we only want it closed by zftp. However, we still
+ * need to shift the fd's out of the way of the user-visible 0-9.
+ */
+
+/**/
+static int
+zfmovefd(int fd)
+{
+ if (fd != -1 && fd < 10) {
+#ifdef F_DUPFD
+ int fe = fcntl(fd, F_DUPFD, 10);
+#else
+ int fe = zfmovefd(dup(fd));
+#endif
+ close(fd);
+ fd = fe;
+ }
+ return fd;
+}
+
+/*
+ * set a non-special parameter.
+ * if ZFPM_IFUNSET, don't set if it already exists.
+ * if ZFPM_READONLY, make it readonly, but only when creating it.
+ * if ZFPM_INTEGER, val pointer is to long (NB not int), don't free.
+ */
+/**/
+static void
+zfsetparam(char *name, void *val, int flags)
+{
+ Param pm = NULL;
+ int type = (flags & ZFPM_INTEGER) ? PM_INTEGER : PM_SCALAR;
+
+ if (!(pm = (Param) paramtab->getnode(paramtab, name))
+ || (pm->flags & PM_UNSET)) {
+ /*
+ * just make it readonly when creating, in case user
+ * *really* knows what they're doing
+ */
+ if ((pm = createparam(name, type)) && (flags & ZFPM_READONLY))
+ pm->flags |= PM_READONLY;
+ } else if (flags & ZFPM_IFUNSET) {
+ pm = NULL;
+ }
+ if (!pm || PM_TYPE(pm->flags) != type) {
+ /* parameters are funny, you just never know */
+ if (type == PM_SCALAR)
+ zsfree((char *)val);
+ return;
+ }
+ if (type == PM_INTEGER)
+ pm->sets.ifn(pm, *(long *)val);
+ else
+ pm->sets.cfn(pm, (char *)val);
+}
+
+/*
+ * Unset a ZFTP parameter when the connection is closed.
+ * We only do this with connection-specific parameters.
+ */
+
+/**/
+static void
+zfunsetparam(char *name)
+{
+ Param pm;
+
+ if ((pm = (Param) paramtab->getnode(paramtab, name))) {
+ pm->flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 1);
+ }
+}
+
+/*
+ * Join command and arguments to make a proper TELNET command line.
+ * New line is in permanent storage.
+ */
+
+/**/
+static char *
+zfargstring(char *cmd, char **args)
+{
+ int clen = strlen(cmd) + 3;
+ char *line, **aptr;
+
+ for (aptr = args; *aptr; aptr++)
+ clen += strlen(*aptr) + 1;
+ line = zalloc(clen);
+ strcpy(line, cmd);
+ for (aptr = args; *aptr; aptr++) {
+ strcat(line, " ");
+ strcat(line, *aptr);
+ }
+ strcat(line, "\r\n");
+
+ return line;
+}
+
+/*
+ * get a line on the control connection according to TELNET rules
+ * Return status is first digit of FTP reply code
+ */
+
+/**/
+static int
+zfgetline(char *ln, int lnsize, int tmout)
+{
+ int ch, added = 0;
+ /* current line point */
+ char *pcur = ln, cmdbuf[3];
+
+ zcfinish = 0;
+ /* leave room for null byte */
+ lnsize--;
+ /* in case we return unexpectedly before getting anything */
+ ln[0] = '\0';
+
+ if (setjmp(zfalrmbuf)) {
+ alarm(0);
+ zwarnnam("zftp", "timeout getting response", NULL, 0);
+ return 5;
+ }
+ zfalarm(tmout);
+
+ /*
+ * We need to be more careful about errors here; we
+ * should do the stuff with errflag and so forth.
+ * We should probably holdintr() here, since if we don't
+ * get the message, the connection is going to be messed up.
+ * But then we get `frustrated user syndrome'.
+ */
+ for (;;) {
+ ch = fgetc(zcin);
+
+ switch(ch) {
+ case EOF:
+ if (ferror(zcin) && errno == EINTR) {
+ clearerr(zcin);
+ continue;
+ }
+ zcfinish = 2;
+ break;
+
+ case '\r':
+ /* always precedes something else */
+ ch = fgetc(zcin);
+ if (ch == EOF) {
+ zcfinish = 2;
+ break;
+ }
+ if (ch == '\n') {
+ zcfinish = 1;
+ break;
+ }
+ if (ch == '\0') {
+ ch = '\r';
+ break;
+ }
+ /* not supposed to get here */
+ ch = '\r';
+ break;
+
+ case '\n':
+ /* not supposed to get here */
+ zcfinish = 1;
+ break;
+
+ case IAC:
+ /*
+ * oh great, now it's sending TELNET commands. try
+ * to persuade it not to.
+ */
+ ch = fgetc(zcin);
+ switch (ch) {
+ case WILL:
+ case WONT:
+ ch = fgetc(zcin);
+ /* whatever it wants to do, stop it. */
+ cmdbuf[0] = (char)IAC;
+ cmdbuf[1] = (char)DONT;
+ cmdbuf[2] = ch;
+ write(zcfd, cmdbuf, 3);
+ continue;
+
+ case DO:
+ case DONT:
+ ch = fgetc(zcin);
+ /* well, tough, we're not going to. */
+ cmdbuf[0] = (char)IAC;
+ cmdbuf[1] = (char)WONT;
+ cmdbuf[2] = ch;
+ write(zcfd, cmdbuf, 3);
+ continue;
+
+ case EOF:
+ /* strange machine. */
+ zcfinish = 2;
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ if (zcfinish)
+ break;
+ if (added < lnsize) {
+ *pcur++ = ch;
+ added++;
+ }
+ /* junk it if we don't have room, but go on reading */
+ }
+
+ alarm(0);
+
+ *pcur = '\0';
+ /* if zcfinish == 2, at EOF, return that, else 0 */
+ return (zcfinish & 2);
+}
+
+/*
+ * Get a whole message from the server. A dash after
+ * the first line code means keep reading until we get
+ * a line with the same code followed by a space.
+ *
+ * Note that this returns an FTP status code, the first
+ * digit of the reply. There is also a pseudocode, 6, which
+ * means `there's no point trying anything, just yet'.
+ * We return it either if the connection is closed, or if
+ * we got a 530 (user not logged in), in which case whatever
+ * you're trying to do isn't going to work.
+ */
+
+/**/
+static int
+zfgetmsg()
+{
+ char line[256], *ptr, *verbose;
+ int stopit, printing = 0, tmout;
+
+ if (zcfd == -1)
+ return 5;
+ if (!(verbose = getsparam("ZFTP_VERBOSE")))
+ verbose = "";
+ zsfree(lastmsg);
+ lastmsg = NULL;
+
+ tmout = getiparam("ZFTP_TMOUT");
+
+ zfgetline(line, 256, tmout);
+ ptr = line;
+ if (zfdrrrring || !isdigit((int)*ptr) || !isdigit((int)ptr[1]) ||
+ !isdigit((int)ptr[2])) {
+ /* timeout, or not talking FTP. not really interested. */
+ zcfinish = 2;
+ if (!zfclosing)
+ zfclose();
+ lastmsg = ztrdup("");
+ strcpy(lastcodestr, "000");
+ zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY);
+ return 6;
+ }
+ strncpy(lastcodestr, ptr, 3);
+ ptr += 3;
+ lastcodestr[3] = '\0';
+ lastcode = atoi(lastcodestr);
+ zfsetparam("ZFTP_CODE", ztrdup(lastcodestr), ZFPM_READONLY);
+ stopit = (*ptr++ != '-');
+
+ if (strchr(verbose, lastcodestr[0])) {
+ /* print the whole thing verbatim */
+ printing = 1;
+ fputs(line, stderr);
+ } else if (strchr(verbose, '0') && !stopit) {
+ /* print multiline parts with the code stripped */
+ printing = 2;
+ fputs(ptr, stderr);
+ }
+ if (printing)
+ fputc('\n', stderr);
+
+ while (zcfinish != 2 && !stopit) {
+ zfgetline(line, 256, tmout);
+ ptr = line;
+ if (zfdrrrring) {
+ line[0] = '\0';
+ break;
+ }
+
+ if (!strncmp(lastcodestr, line, 3)) {
+ if (line[3] == ' ') {
+ stopit = 1;
+ ptr += 4;
+ } else if (line[3] == '-')
+ ptr += 4;
+ } else if (!strncmp(" ", line, 4))
+ ptr += 4;
+
+ if (printing == 2) {
+ if (!stopit) {
+ fputs(ptr, stderr);
+ fputc('\n', stderr);
+ }
+ } else if (printing) {
+ fputs(line, stderr);
+ fputc('\n', stderr);
+ }
+ }
+
+ if (printing)
+ fflush(stderr);
+
+ /* The internal message is just the text. */
+ lastmsg = ztrdup(ptr);
+ /*
+ * The parameter is the whole thing, including the code.
+ */
+ zfsetparam("ZFTP_REPLY", ztrdup(line), ZFPM_READONLY);
+ /*
+ * close the connection here if zcfinish == 2, i.e. EOF,
+ * or if we get a 421 (service not available, closing connection),
+ * but don't do it if it's expected (zfclosing set).
+ */
+ if ((zcfinish == 2 || lastcode == 421) && !zfclosing) {
+ zcfinish = 2; /* don't need to tell server */
+ zfclose();
+ /* unexpected, so tell user */
+ zwarnnam("zftp", "remote server has closed connection", NULL, 0);
+ return 6; /* pretend it failed, because it did */
+ }
+ if (lastcode == 530) {
+ /* user not logged in */
+ return 6;
+ }
+ /*
+ * May as well handle this here, though it's pretty uncommon.
+ * A 120 is something like "service ready in nnn minutes".
+ * It means we just hang around waiting for another reply.
+ */
+ if (lastcode == 120) {
+ zwarnnam("zftp", "delay expected, waiting: %s", lastmsg, 0);
+ return zfgetmsg();
+ }
+
+ /* first digit of code determines success, failure, not in the mood... */
+ return lastcodestr[0] - '0';
+}
+
+
+/*
+ * Send a command and get the reply.
+ * The command is expected to have the \r\n already tacked on.
+ * Returns the status code for the reply.
+ */
+
+/**/
+static int
+zfsendcmd(char *cmd)
+{
+ /*
+ * We use the fd directly; there's no point even using
+ * stdio with line buffering, since we always send the
+ * complete line in one string anyway.
+ */
+ int ret, tmout;
+
+ if (zcfd == -1)
+ return 5;
+ tmout = getiparam("ZFTP_TMOUT");
+ if (setjmp(zfalrmbuf)) {
+ alarm(0);
+ zwarnnam("zftp", "timeout sending message", NULL, 0);
+ return 5;
+ }
+ zfalarm(tmout);
+ ret = write(zcfd, cmd, strlen(cmd));
+ alarm(0);
+
+ if (ret <= 0) {
+ zwarnnam("zftp send", "failed sending control message", NULL, 0);
+ return 5; /* FTP status code */
+ }
+
+ return zfgetmsg();
+}
+
+
+/* Set up a data connection, return 1 for failure, 0 for success */
+
+/**/
+static int
+zfopendata(char *name)
+{
+ if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) {
+ zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0);
+ return 1;
+ }
+ zdfd = zfmovefd(socket(AF_INET, SOCK_STREAM, 0));
+ if (zdfd < 0) {
+ zwarnnam(name, "can't get data socket: %e", NULL, errno);
+ return 1;
+ }
+
+ zdsock = zsock;
+ zdsock.sin_family = AF_INET;
+
+ if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) {
+ char *ptr;
+ int i, nums[6], err;
+ unsigned char iaddr[4], iport[2];
+
+ if (zfsendcmd("PASV\r\n") == 6)
+ return 1;
+ else if (lastcode >= 500 && lastcode <= 504) {
+ /*
+ * Fall back to send port mode. That will
+ * test the preferences for whether that's OK.
+ */
+ zfstatus |= ZFST_NOPS;
+ zfclosedata();
+ return zfopendata(name);
+ }
+ /*
+ * OK, now we need to know what port we're looking at,
+ * which is cunningly concealed in the reply.
+ * lastmsg already has the reply code expunged.
+ */
+ for (ptr = lastmsg; *ptr; ptr++)
+ if (isdigit(*ptr))
+ break;
+ if (sscanf(ptr, "%d,%d,%d,%d,%d,%d",
+ nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) {
+ zwarnnam(name, "bad response to PASV: %s", lastmsg, 0);
+ zfclosedata();
+ return 1;
+ }
+ for (i = 0; i < 4; i++)
+ iaddr[i] = STOUC(nums[i]);
+ iport[0] = STOUC(nums[4]);
+ iport[1] = STOUC(nums[5]);
+
+ memcpy(&zdsock.sin_addr, iaddr, sizeof(iaddr));
+ memcpy(&zdsock.sin_port, iport, sizeof(iport));
+
+ /* we should timeout this connect */
+ do {
+ err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock));
+ } while (err && errno == EINTR && !errflag);
+
+ if (err) {
+ zwarnnam(name, "connect failed: %e", NULL, errno);
+ zfclosedata();
+ return 1;
+ }
+
+ zfpassive_conn = 1;
+ } else {
+ char portcmd[40];
+ unsigned char *addr, *port;
+ int ret, len;
+
+ if (!(zfprefs & ZFPF_SNDP)) {
+ zwarnnam(name, "only sendport mode available for data", NULL, 0);
+ return 1;
+ }
+
+ zdsock.sin_port = 0; /* to be set by bind() */
+ len = sizeof(zdsock);
+ /* need to do timeout stuff and probably handle EINTR here */
+ if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0)
+ ret = 1;
+ else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0)
+ ret = 2;
+ else if (listen(zdfd, 1) < 0)
+ ret = 3;
+ else
+ ret = 0;
+
+ if (ret) {
+ zwarnnam(name, "failure on data socket: %s: %e",
+ ret == 3 ? "listen" : ret == 2 ? "getsockname" : "bind",
+ errno);
+ zfclosedata();
+ return 1;
+ }
+
+ addr = (unsigned char *) &zdsock.sin_addr;
+ port = (unsigned char *) &zdsock.sin_port;
+ sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n",
+ addr[0],addr[1],addr[2],addr[3],port[0],port[1]);
+ if (zfsendcmd(portcmd) >= 5) {
+ zwarnnam(name, "port command failed", NULL, 0);
+ zfclosedata();
+ return 1;
+ }
+ zfpassive_conn = 0;
+ }
+
+ return 0;
+}
+
+/* Close the data connection. */
+
+/**/
+static void
+zfclosedata(void)
+{
+ if (zdfd == -1)
+ return;
+ close(zdfd);
+ zdfd = -1;
+}
+
+/*
+ * Set up a data connection and use cmd to initiate a transfer.
+ * The actual data fd will be zdfd; the calling routine
+ * must handle the data itself.
+ * rest is a REST command to specify starting somewhere other
+ * then the start of the remote file.
+ * getsize is non-zero if we want to try to find the number
+ * of bytes in the reply to a RETR command.
+ *
+ * Return 0 on success, 1 on failure.
+ */
+
+/**/
+static int
+zfgetdata(char *name, char *rest, char *cmd, int getsize)
+{
+ int len, newfd;
+
+ if (zfopendata(name))
+ return 1;
+
+ /*
+ * Set position in remote file for get/put.
+ * According to RFC959, the restart command needs something
+ * called a marker which has previously been put into the data.
+ * Luckily for the real world, UNIX machines just interpret this
+ * as an offset into the byte stream.
+ *
+ * This has to be sent immediately before the data transfer, i.e.
+ * after all mucking around with types and sizes and so on.
+ */
+ if (rest && zfsendcmd(rest) > 3) {
+ zfclosedata();
+ return 1;
+ }
+
+ if (zfsendcmd(cmd) > 2) {
+ zfclosedata();
+ return 1;
+ }
+ if (getsize || (!(zfstatus & ZFST_TRSZ) && !strncmp(cmd, "RETR", 4))) {
+ /*
+ * See if we got something like:
+ * Opening data connection for nortypix.gif (1234567 bytes).
+ * On the first RETR, always see if this works, Then we
+ * can avoid sending a special SIZE command beforehand.
+ */
+ char *ptr = strstr(lastmsg, "bytes");
+ zfstatus |= ZFST_NOSZ|ZFST_TRSZ;
+ if (ptr) {
+ while (ptr > lastmsg && !isdigit(*ptr))
+ ptr--;
+ while (ptr > lastmsg && isdigit(ptr[-1]))
+ ptr--;
+ if (isdigit(*ptr)) {
+ zfstatus &= ~ZFST_NOSZ;
+ if (getsize) {
+ long sz = zstrtol(ptr, NULL, 10);
+ zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER);
+ }
+ }
+ }
+ }
+
+ if (!zfpassive_conn) {
+ /*
+ * the current zdfd is the socket we opened, but we need
+ * to let the server set up a different fd for reading/writing.
+ * then we can close the fd we were listening for a connection on.
+ * don't expect me to understand this, i'm only the programmer.
+ */
+
+ /* accept the connection */
+ len = sizeof(zdsock);
+ newfd = zfmovefd(accept(zdfd, (struct sockaddr *)&zdsock, &len));
+ zfclosedata();
+ if (newfd < 0) {
+ zwarnnam(name, "unable to accept data.", NULL, 0);
+ return 1;
+ }
+ zdfd = newfd; /* this is now the actual data fd */
+ }
+
+
+ /* more options, just to look professional */
+#ifdef SO_LINGER
+ /*
+ * Since data can take arbitrary amounts of time to arrive,
+ * the socket can be made to hang around until it doesn't think
+ * anything is arriving.
+ *
+ * In BSD 4.3, you could only linger for infinity. Don't
+ * know if this has changed.
+ */
+ {
+ struct linger li;
+
+ li.l_onoff = 1;
+ li.l_linger = 120;
+ setsockopt(zdfd, SOL_SOCKET, SO_LINGER, (char *)&li, sizeof(li));
+ }
+#endif
+#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
+ /* try to get high throughput, snigger */
+ {
+ int arg = IPTOS_THROUGHPUT;
+ setsockopt(zdfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg));
+ }
+#endif
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+ /* If the shell execs a program, we don't want this fd left open. */
+ len = FD_CLOEXEC;
+ fcntl(zdfd, F_SETFD, &len);
+#endif
+
+ return 0;
+}
+
+/*
+ * Find out about a local or remote file and pass back the information.
+ *
+ * We could jigger this to use ls like ncftp does as a backup.
+ * But if the server is non-standard enough not to have SIZE and MDTM,
+ * there's a very good chance ls -l isn't going to do great things.
+ *
+ * if fd is >= 0, it is used for an fstat when remote is zero:
+ * this is because on a put we are taking input from fd 0.
+ */
+
+/**/
+static int
+zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd)
+{
+ long sz = -1;
+ char *mt = NULL;
+ int ret;
+
+ if (retsize)
+ *retsize = -1;
+ if (retmdtm)
+ *retmdtm = NULL;
+ if (remote) {
+ char *cmd;
+ if ((zfhas_size == ZFCP_NOPE && retsize) ||
+ (zfhas_mdtm == ZFCP_NOPE && retmdtm))
+ return 2;
+
+ /*
+ * File is coming from over there.
+ * Make sure we get the type right.
+ */
+ zfsettype(ZFST_TYPE(zfstatus));
+ if (retsize) {
+ cmd = tricat("SIZE ", fnam, "\r\n");
+ ret = zfsendcmd(cmd);
+ zsfree(cmd);
+ if (ret == 6)
+ return 1;
+ else if (lastcode < 300) {
+ sz = zstrtol(lastmsg, 0, 10);
+ zfhas_size = ZFCP_YUPP;
+ } else if (lastcode >= 500 && lastcode <= 504) {
+ zfhas_size = ZFCP_NOPE;
+ return 2;
+ } else if (lastcode == 550)
+ return 1;
+ /* if we got a 550 from SIZE, the file doesn't exist */
+ }
+
+ if (retmdtm) {
+ cmd = tricat("MDTM ", fnam, "\r\n");
+ ret = zfsendcmd(cmd);
+ zsfree(cmd);
+ if (ret == 6)
+ return 1;
+ else if (lastcode < 300) {
+ mt = ztrdup(lastmsg);
+ zfhas_mdtm = ZFCP_YUPP;
+ } else if (lastcode >= 500 && lastcode <= 504) {
+ zfhas_mdtm = ZFCP_NOPE;
+ return 2;
+ } else if (lastcode == 550)
+ return 1;
+ }
+ } else {
+ /* File is over here */
+ struct stat statbuf;
+ struct tm *tm;
+ char tmbuf[20];
+
+ if ((fd == -1 ? stat(fnam, &statbuf) : fstat(fd, &statbuf)) < 0)
+ return 1;
+ /* make sure it's long, since this has to be a pointer */
+ sz = statbuf.st_size;
+
+ if (retmdtm) {
+ /* use gmtime() rather than localtime() for consistency */
+ tm = gmtime(&statbuf.st_mtime);
+ /*
+ * FTP format for data is YYYYMMDDHHMMSS
+ * Using tm directly is easier than worrying about
+ * incompatible strftime()'s.
+ */
+ sprintf(tmbuf, "%04d%02d%02d%02d%02d%02d",
+ tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ mt = ztrdup(tmbuf);
+ }
+ }
+ if (retsize)
+ *retsize = sz;
+ if (retmdtm)
+ *retmdtm = mt;
+ return 0;
+}
+
+/* Set parameters to say what's coming */
+
+/**/
+static void
+zfstarttrans(char *nam, int recv, long sz)
+{
+ long cnt = 0;
+ /*
+ * sz = -1 signifies error getting size. don't set ZFTP_SIZE if sz is
+ * zero, either: it probably came from an fstat() on a pipe, so it
+ * means we don't know and shouldn't tell the user porkies.
+ */
+ if (sz > 0)
+ zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER);
+ zfsetparam("ZFTP_FILE", ztrdup(nam), ZFPM_READONLY);
+ zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "G" : "P"), ZFPM_READONLY);
+ zfsetparam("ZFTP_COUNT", &cnt, ZFPM_READONLY|ZFPM_INTEGER);
+}
+
+/* Tidy up afterwards */
+
+/**/
+static void
+zfendtrans()
+{
+ zfunsetparam("ZFTP_SIZE");
+ zfunsetparam("ZFTP_FILE");
+ zfunsetparam("ZFTP_TRANSFER");
+ zfunsetparam("ZFTP_COUNT");
+}
+
+/* Read with timeout if recv is set. */
+
+/**/
+static int
+zfread(int fd, char *bf, size_t sz, int tmout)
+{
+ int ret;
+
+ if (!tmout)
+ return read(fd, bf, sz);
+
+ if (setjmp(zfalrmbuf)) {
+ alarm(0);
+ zwarnnam("zftp", "timeout on network read", NULL, 0);
+ return -1;
+ }
+ zfalarm(tmout);
+
+ ret = read(fd, bf, sz);
+
+ /* we don't bother turning off the whole alarm mechanism here */
+ alarm(0);
+ return ret;
+}
+
+/* Write with timeout if recv is not set. */
+
+/**/
+static int
+zfwrite(int fd, char *bf, size_t sz, int tmout)
+{
+ int ret;
+
+ if (!tmout)
+ return write(fd, bf, sz);
+
+ if (setjmp(zfalrmbuf)) {
+ alarm(0);
+ zwarnnam("zftp", "timeout on network write", NULL, 0);
+ return -1;
+ }
+ zfalarm(tmout);
+
+ ret = write(fd, bf, sz);
+
+ /* we don't bother turning off the whole alarm mechanism here */
+ alarm(0);
+ return ret;
+}
+
+static int zfread_eof;
+
+/* Version of zfread when we need to read in block mode. */
+
+/**/
+static int
+zfread_block(int fd, char *bf, size_t sz, int tmout)
+{
+ int n;
+ struct zfheader hdr;
+ size_t blksz, cnt;
+ char *bfptr;
+ do {
+ /* we need the header */
+ do {
+ n = zfread(fd, (char *)&hdr, sizeof(hdr), tmout);
+ } while (n < 0 && errno == EINTR);
+ if (n != 3 && !zfdrrrring) {
+ zwarnnam("zftp", "failed to read FTP block header", NULL, 0);
+ return n;
+ }
+ /* size is stored in network byte order */
+ if (hdr.flags & ZFHD_EOFB)
+ zfread_eof = 1;
+ blksz = (hdr.bytes[0] << 8) | hdr.bytes[1];
+ if (blksz > sz) {
+ /*
+ * See comments in file headers
+ */
+ zwarnnam("zftp", "block too large to handle", NULL, 0);
+ errno = EIO;
+ return -1;
+ }
+ bfptr = bf;
+ cnt = blksz;
+ while (cnt) {
+ n = zfread(fd, bfptr, cnt, tmout);
+ if (n > 0) {
+ bfptr += n;
+ cnt -= n;
+ } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR))
+ return n;
+ else
+ break;
+ }
+ if (cnt) {
+ zwarnnam("zftp", "short data block", NULL, 0);
+ errno = EIO;
+ return -1;
+ }
+ } while ((hdr.flags & ZFHD_MARK) && !zfread_eof);
+ return (hdr.flags & ZFHD_MARK) ? 0 : blksz;
+}
+
+/* Version of zfwrite when we need to write in block mode. */
+
+/**/
+static int
+zfwrite_block(int fd, char *bf, size_t sz, int tmout)
+{
+ int n;
+ struct zfheader hdr;
+ size_t cnt;
+ char *bfptr;
+ /* we need the header */
+ do {
+ hdr.bytes[0] = (sz & 0xff00) >> 8;
+ hdr.bytes[1] = sz & 0xff;
+ hdr.flags = sz ? 0 : ZFHD_EOFB;
+ n = zfwrite(fd, (char *)&hdr, sizeof(hdr), tmout);
+ } while (n < 0 && errno == EINTR);
+ if (n != 3 && !zfdrrrring) {
+ zwarnnam("zftp", "failed to write FTP block header", NULL, 0);
+ return n;
+ }
+ bfptr = bf;
+ cnt = sz;
+ while (cnt) {
+ n = zfwrite(fd, bfptr, cnt, tmout);
+ if (n > 0) {
+ bfptr += n;
+ cnt -= n;
+ } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR))
+ return n;
+ }
+
+ return sz;
+}
+
+/*
+ * Move stuff from fdin to fdout, tidying up the data connection
+ * when finished. The data connection could be either input or output:
+ * recv is 1 for receiving a file, 0 for sending.
+ *
+ * progress is 1 to use a progress meter.
+ * startat says how far in we're starting with a REST command.
+ *
+ * Since we're doing some buffering here anyway, we don't bother
+ * with a stdio layer.
+ */
+
+/**/
+static int
+zfsenddata(char *name, int recv, int progress, long startat)
+{
+#define ZF_BUFSIZE 32768
+#define ZF_ASCSIZE (ZF_BUFSIZE/2)
+ /* ret = 2 signals the local read/write failed, so send abort */
+ int n, ret = 0, gotack = 0, fdin, fdout, fromasc = 0, toasc = 0;
+ int rtmout = 0, wtmout = 0;
+ char lsbuf[ZF_BUFSIZE], *ascbuf = NULL, *optr;
+ long sofar = 0, last_sofar = 0;
+ readwrite_t read_ptr = zfread, write_ptr = zfwrite;
+ List l;
+
+ if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) {
+ /*
+ * progress to set up: ZFTP_COUNT is zero.
+ * We do this here in case we needed to wait for a RETR
+ * command to tell us how many bytes are coming.
+ */
+ doshfunc("zftp_progress", l, NULL, 0, 1);
+ /* Now add in the bit of the file we've got/sent already */
+ sofar = last_sofar = startat;
+ }
+ if (recv) {
+ fdin = zdfd;
+ fdout = 1;
+ rtmout = getiparam("ZFTP_TMOUT");
+ if (ZFST_CTYP(zfstatus) == ZFST_ASCI)
+ fromasc = 1;
+ if (ZFST_MODE(zfstatus) == ZFST_BLOC)
+ read_ptr = zfread_block;
+ } else {
+ fdin = 0;
+ fdout = zdfd;
+ wtmout = getiparam("ZFTP_TMOUT");
+ if (ZFST_CTYP(zfstatus) == ZFST_ASCI)
+ toasc = 1;
+ if (ZFST_MODE(zfstatus) == ZFST_BLOC)
+ write_ptr = zfwrite_block;
+ }
+
+ if (toasc)
+ ascbuf = zalloc(ZF_ASCSIZE);
+ zfpipe();
+ zfread_eof = 0;
+ while (!ret && !zfread_eof) {
+ n = (toasc) ? read_ptr(fdin, ascbuf, ZF_ASCSIZE, rtmout)
+ : read_ptr(fdin, lsbuf, ZF_BUFSIZE, rtmout);
+ if (n > 0) {
+ char *iptr;
+ if (toasc) {
+ /* \n -> \r\n it shouldn't happen to a dog. */
+ char *iptr = ascbuf, *optr = lsbuf;
+ int cnt = n;
+ while (cnt--) {
+ if (*iptr == '\n') {
+ *optr++ = '\r';
+ n++;
+ }
+ *optr++ = *iptr++;
+ }
+ }
+ if (fromasc && (iptr = memchr(lsbuf, '\r', n))) {
+ /* \r\n -> \n */
+ char *optr = iptr;
+ int cnt = n - (iptr - lsbuf);
+ while (cnt--) {
+ if (*iptr != '\r' || iptr[1] != '\n') {
+ *optr++ = *iptr;
+ } else
+ n--;
+ iptr++;
+ }
+ }
+ optr = lsbuf;
+
+ sofar += n;
+
+ for (;;) {
+ /*
+ * in principle, write can be interrupted after
+ * safely writing some bytes, and will return the
+ * number already written, which may not be the
+ * complete buffer. so make this robust. they call me
+ * `robustness stephenson'. in my dreams.
+ */
+ int newn = write_ptr(fdout, optr, n, wtmout);
+ if (newn == n)
+ break;
+ if (newn < 0) {
+ /*
+ * The somewhat contorted test here (for write)
+ * and below (for read) means:
+ * real error if
+ * - errno is set and it's not just an interrupt, or
+ * - errflag is set, probably due to CTRL-c, or
+ * - zfdrrrring is set, due to the alarm going off.
+ * print an error message if
+ * - not a timeout, since that was reported, and
+ * either
+ * - a non-interactive shell, where we don't
+ * know what happened otherwise
+ * - or both of
+ * - not errflag, i.e. CTRL-c or what have you,
+ * since the user probably knows about that, and
+ * - not a SIGPIPE, since usually people are
+ * silent about those when going to pagers
+ * (if you quit less or more in the middle
+ * and see an error message you think `I
+ * shouldn't have done that').
+ *
+ * If we didn't print an error message here,
+ * and were going to do an abort (ret == 2)
+ * because the error happened on the local end
+ * of the connection, set ret to 3 and don't print
+ * the 'aborting...' either.
+ *
+ * There must be a better way of doing this.
+ */
+ if (errno != EINTR || errflag || zfdrrrring) {
+ if (!zfdrrrring &&
+ (!interact || (!errflag && errno != EPIPE))) {
+ ret = recv ? 2 : 1;
+ zwarnnam(name, "write failed: %e", NULL, errno);
+ } else
+ ret = recv ? 3 : 1;
+ break;
+ }
+ continue;
+ }
+ optr += newn;
+ n -= newn;
+ }
+ } else if (n < 0) {
+ if (errno != EINTR || errflag || zfdrrrring) {
+ if (!zfdrrrring &&
+ (!interact || (!errflag && errno != EPIPE))) {
+ ret = recv ? 1 : 2;
+ zwarnnam(name, "read failed: %e", NULL, errno);
+ } else
+ ret = recv ? 1 : 3;
+ break;
+ }
+ } else
+ break;
+ if (!ret && sofar != last_sofar && progress &&
+ (l = getshfunc("zftp_progress")) != &dummy_list) {
+ zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER);
+ doshfunc("zftp_progress", l, NULL, 0, 1);
+ last_sofar = sofar;
+ }
+ }
+ zfunpipe();
+ /*
+ * At this point any timeout was on the data connection,
+ * so we don't need to force the control connection to close.
+ */
+ zfdrrrring = 0;
+ if (!errflag && !ret && !recv && ZFST_MODE(zfstatus) == ZFST_BLOC) {
+ /* send an end-of-file marker block */
+ ret = (zfwrite_block(fdout, lsbuf, 0, wtmout) < 0);
+ }
+ if (errflag || ret > 1) {
+ /*
+ * some error occurred, maybe a keyboard interrupt, or
+ * a local file/pipe handling problem.
+ * send an abort.
+ *
+ * safest to block all signals here? can get frustrating if
+ * we're waiting for an abort. don't I know. let's start
+ * off just by blocking SIGINT's.
+ *
+ * maybe the timeout for the abort should be shorter than
+ * for normal commands. and what about aborting after
+ * we had a timeout on the data connection, is that
+ * really a good idea?
+ */
+ /* RFC 959 says this is what to send */
+ unsigned char msg[4] = { IAC, IP, IAC, SYNCH };
+
+ if (ret == 2)
+ zwarnnam(name, "aborting data transfer...", NULL, 0);
+
+ holdintr();
+
+ /* the following is black magic, as far as I'm concerned. */
+ /* what are we going to do if it fails? not a lot, actually. */
+ send(zcfd, (char *)msg, 3, 0);
+ send(zcfd, (char *)msg+3, 1, MSG_OOB);
+
+ zfsendcmd("ABOR\r\n");
+ if (lastcode == 226) {
+ /*
+ * 226 is supposed to mean the transfer got sent OK after
+ * all, and the abort got ignored, at least that's what
+ * rfc959 seems to be saying. but in fact what can happen
+ * is the transfer finishes (at least as far as the
+ * server's concerned) and it's response is waiting, then
+ * the abort gets sent, and we need to mop up a response to
+ * that. so actually in most cases we get two replies
+ * anyway. we could test if we had select() on all hosts.
+ */
+ /* gotack = 1; */
+ /*
+ * we'd better leave errflag, since we don't know
+ * where it came from. maybe the user wants to abort
+ * a whole script or function.
+ */
+ } else
+ ret = 1;
+
+ noholdintr();
+ }
+
+ if (toasc)
+ zfree(ascbuf, ZF_ASCSIZE);
+ zfclosedata();
+ if (!gotack && zfgetmsg() > 2)
+ ret = 1;
+ return ret != 0;
+}
+
+/* Open a new control connection, i.e. start a new FTP session */
+
+/**/
+static int
+zftp_open(char *name, char **args, int flags)
+{
+ struct in_addr ipaddr;
+ struct protoent *zprotop;
+ struct servent *zservp;
+ struct hostent *zhostp = NULL;
+ char **addrp, tbuf[2] = "X", *fname;
+ int err, len, tmout;
+
+ if (!*args) {
+ if (zfuserparams)
+ args = zfuserparams;
+ else {
+ zwarnnam(name, "no host specified", NULL, 0);
+ return 1;
+ }
+ }
+
+ /*
+ * Close the existing connection if any.
+ * Probably this is the safest thing to do. It's possible
+ * a `QUIT' will hang, though.
+ */
+ if (zcfd != -1)
+ zfclose();
+
+ /* this is going to give 0. why bother? */
+ zprotop = getprotobyname("tcp");
+ zservp = getservbyname("ftp", "tcp");
+
+ if (!zprotop || !zservp) {
+ zwarnnam(name, "Somebody stole FTP!", NULL, 0);
+ return 1;
+ }
+
+ /* don't try talking to server yet */
+ zcfinish = 2;
+
+ /*
+ * This sets an alarm for the whole process, getting the host name
+ * as well as connecting. Arguably you could time them out separately.
+ */
+ tmout = getiparam("ZFTP_TMOUT");
+ if (setjmp(zfalrmbuf)) {
+ char *hname;
+ alarm(0);
+ if ((hname = getsparam("ZFTP_HOST")) && *hname)
+ zwarnnam(name, "timeout connecting to %s", hname, 0);
+ else
+ zwarnnam(name, "timeout on host name lookup", NULL, 0);
+ zfclose();
+ return 1;
+ }
+ zfalarm(tmout);
+
+ /*
+ * Now this is what I like. A library which provides decent higher
+ * level functions to do things like converting address types. It saves
+ * so much trouble. Pity about the rest of the network interface, though.
+ */
+ ipaddr.s_addr = inet_addr(args[0]);
+ if (ipaddr.s_addr != INADDR_NONE) {
+ /*
+ * hmmm, we don't necessarily want to do this... maybe the
+ * user is actively trying to avoid a bad nameserver.
+ * perhaps better just to set ZFTP_HOST to the dot address, too.
+ * that way shell functions know how it was opened.
+ *
+ * zhostp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET);
+ *
+ * or, we could have a `no_lookup' flag.
+ */
+ zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY);
+ zsock.sin_family = AF_INET;
+ } else {
+ zhostp = gethostbyname(args[0]);
+ if (!zhostp || errflag) {
+ /* should use herror() here if available, but maybe
+ * needs configure test. on AIX it's present but not
+ * in headers.
+ */
+ zwarnnam(name, "host not found: %s", args[0], 0);
+ alarm(0);
+ return 1;
+ }
+ zsock.sin_family = zhostp->h_addrtype;
+ zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY);
+ }
+
+ zsock.sin_port = ntohs(zservp->s_port);
+ zcfd = zfmovefd(socket(zsock.sin_family, SOCK_STREAM, 0));
+ if (zcfd < 0) {
+ zwarnnam(name, "socket failed: %e", NULL, errno);
+ zfunsetparam("ZFTP_HOST");
+ alarm(0);
+ return 1;
+ }
+
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+ /* If the shell execs a program, we don't want this fd left open. */
+ len = FD_CLOEXEC;
+ fcntl(zcfd, F_SETFD, &len);
+#endif
+
+ /*
+ * now connect the socket. manual pages all say things like `this is all
+ * explained oh-so-wonderfully in some other manual page'. not.
+ */
+
+ err = 1;
+
+ if (ipaddr.s_addr != INADDR_NONE) {
+ /* dot address */
+ memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr));
+ do {
+ err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock));
+ } while (err && errno == EINTR && !errflag);
+ } else {
+ /* host name: try all possible IP's */
+ for (addrp = zhostp->h_addr_list; *addrp; addrp++) {
+ memcpy(&zsock.sin_addr, *addrp, zhostp->h_length);
+ do {
+ err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock));
+ } while (err && errno == EINTR && !errflag);
+ /* you can check whether it's worth retrying here */
+ }
+ }
+
+ alarm(0);
+
+ if (err < 0) {
+ zwarnnam(name, "connect failed: %e", NULL, errno);
+ zfclose();
+ return 1;
+ }
+ zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY);
+ /* now we can talk to the control connection */
+ zcfinish = 0;
+
+ len = sizeof(zsock);
+ if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) {
+ zwarnnam(name, "getsockname failed: %e", NULL, errno);
+ zfclose();
+ return 1;
+ }
+ /* nice to get some options right, ignore if they don't work */
+#ifdef SO_OOBINLINE
+ /*
+ * this says we want messages in line. maybe sophisticated people
+ * do clever things with SIGURG.
+ */
+ len = 1;
+ setsockopt(zcfd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len));
+#endif
+#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
+ /* for control connection we want low delay. please don't laugh. */
+ len = IPTOS_LOWDELAY;
+ setsockopt(zcfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len));
+#endif
+
+ /*
+ * We use stdio with line buffering for convenience on input.
+ * On output, we can just dump a complete message to the fd via write().
+ */
+ zcin = fdopen(zcfd, "r");
+
+ if (!zcin) {
+ zwarnnam(name, "file handling error", NULL, 0);
+ zfclose();
+ return 1;
+ }
+
+#ifdef _IONBF
+ setvbuf(zcin, NULL, _IONBF, 0);
+#else
+ setlinebuf(zcin);
+#endif
+
+ /*
+ * now see what the remote server has to say about that.
+ */
+ if (zfgetmsg() >= 4) {
+ zfclose();
+ return 1;
+ }
+
+ zfis_unix = 0;
+ zfhas_size = zfhas_mdtm = ZFCP_UNKN;
+ zdfd = -1;
+ /* initial status: open, ASCII data, stream mode 'n' stuff */
+ zfstatus = 0;
+
+ /* open file for saving the current status */
+ fname = gettempname();
+ zfstatfd = open(fname, O_RDWR|O_CREAT, 0600);
+ DPUTS(zfstatfd == -1, "zfstatfd not created");
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+ /* If the shell execs a program, we don't want this fd left open. */
+ len = FD_CLOEXEC;
+ fcntl(zfstatfd, F_SETFD, &len);
+#endif
+ unlink(fname);
+
+ /* now find out what system we're connected to */
+ if (!(zfprefs & ZFPF_DUMB) && zfsendcmd("SYST\r\n") == 2) {
+ char *ptr = lastmsg, *eptr, *systype;
+ for (eptr = ptr; *eptr; eptr++)
+ ;
+ systype = ztrduppfx(ptr, eptr-ptr);
+ if (!strncmp(systype, "UNIX Type: L8", 13)) {
+ /*
+ * Use binary for transfers. This simple test saves much
+ * hassle for all concerned, particularly me.
+ */
+ zfstatus |= ZFST_IMAG;
+ zfis_unix = 1;
+ }
+ /*
+ * we could set zfis_unix based just on the UNIX part,
+ * but I don't really know the consequences of that.
+ */
+ zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY);
+ } else if (zcfd == -1) {
+ /* final paranoid check */
+ return 1;
+ }
+
+ tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I';
+ zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY);
+ zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY);
+ /* if remaining arguments, use them to log in. */
+ if (zcfd > -1 && *++args)
+ return zftp_login(name, args, flags);
+ /* if something wayward happened, connection was already closed */
+ return zcfd == -1;
+}
+
+/*
+ * Read a parameter string, with a prompt if reading from stdin.
+ * The returned string is on the heap.
+ * If noecho, turn off ECHO mode while reading.
+ */
+
+/**/
+static char *
+zfgetinfo(char *prompt, int noecho)
+{
+ int resettty = 0;
+ /* 256 characters should be enough, hardly worth allocating
+ * a password string byte by byte
+ */
+ char instr[256], *strret;
+ int len;
+
+ /*
+ * Only print the prompt if getting info from a tty. Of
+ * course, we don't know if stderr has been redirected, but
+ * that seems a minor point.
+ */
+ if (isatty(0)) {
+ if (noecho) {
+ /* hmmm... all this great big shell and we have to read
+ * something with no echo by ourselves.
+ * bin_read() is far to complicated for our needs.
+ * we could use zread(), but that relies on static
+ * variables, so someone doesn't want that to happen.
+ *
+ * this is modified from setcbreak() in utils.c,
+ * except I don't see any point in using cbreak mode
+ */
+ struct ttyinfo ti;
+
+ ti = shttyinfo;
+#ifdef HAS_TIO
+ ti.tio.c_lflag &= ~ECHO;
+#else
+ ti.sgttyb.sg_flags &= ~ECHO;
+#endif
+ settyinfo(&ti);
+ resettty = 1;
+ }
+ fflush(stdin);
+ fputs(prompt, stderr);
+ fflush(stderr);
+ }
+
+ fgets(instr, 256, stdin);
+ if (instr[len = strlen(instr)-1] == '\n')
+ instr[len] = '\0';
+
+ strret = dupstring(instr);
+
+ if (resettty) {
+ /* '\n' didn't get echoed */
+ fputc('\n', stdout);
+ fflush(stdout);
+ settyinfo(&shttyinfo);
+ }
+
+ return strret;
+}
+
+/*
+ * set params for an open with no arguments.
+ * this allows easy re-opens.
+ */
+
+/**/
+static int
+zftp_params(char *name, char **args, int flags)
+{
+ char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " };
+ char **aptr, **newarr;
+ int i, j, len;
+
+ if (!*args) {
+ if (zfuserparams) {
+ for (aptr = zfuserparams, i = 0; *aptr; aptr++, i++) {
+ if (i == 2) {
+ len = strlen(*aptr);
+ for (j = 0; j < len; j++)
+ fputc('*', stdout);
+ fputc('\n', stdout);
+ } else
+ fprintf(stdout, "%s\n", *aptr);
+ }
+ }
+ return 0;
+ }
+ if (!strcmp(*args, "-")) {
+ if (zfuserparams)
+ freearray(zfuserparams);
+ zfuserparams = 0;
+ return 0;
+ }
+ len = arrlen(args);
+ newarr = (char **)zcalloc((len+1)*sizeof(char *));
+ for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) {
+ char *str;
+ if (!strcmp(*aptr, "?"))
+ str = zfgetinfo(prompts[i], i == 2);
+ else
+ str = *aptr;
+ newarr[i] = ztrdup(str);
+ }
+ if (errflag) {
+ /* maybe user CTRL-c'd in the middle somewhere */
+ for (aptr = newarr; *aptr; aptr++)
+ zsfree(*aptr);
+ zfree(newarr, len+1);
+ return 1;
+ }
+ if (zfuserparams)
+ freearray(zfuserparams);
+ zfuserparams = newarr;
+ return 0;
+}
+
+/* login a user: often called as part of the open sequence */
+
+/**/
+static int
+zftp_login(char *name, char **args, int flags)
+{
+ char *ucmd, *passwd = NULL, *acct = NULL;
+ char *user;
+ int stopit;
+
+ if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4)
+ return 1;
+
+ zfstatus &= ~ZFST_LOGI;
+ if (*args) {
+ user = *args++;
+ } else {
+ user = zfgetinfo("User: ", 0);
+ }
+
+ ucmd = tricat("USER ", user, "\r\n");
+ stopit = 0;
+
+ if (zfsendcmd(ucmd) == 6)
+ stopit = 2;
+
+ while (!stopit && !errflag) {
+ switch (lastcode) {
+ case 230: /* user logged in */
+ case 202: /* command not implemented, don't care */
+ stopit = 1;
+ break;
+
+ case 331: /* need password */
+ if (*args)
+ passwd = *args++;
+ else
+ passwd = zfgetinfo("Password: ", 1);
+ zsfree(ucmd);
+ ucmd = tricat("PASS ", passwd, "\r\n");
+ if (zfsendcmd(ucmd) == 6)
+ stopit = 2;
+ break;
+
+ case 332: /* need account */
+ case 532:
+ if (*args)
+ acct = *args++;
+ else
+ acct = zfgetinfo("Account: ", 0);
+ zsfree(ucmd);
+ ucmd = tricat("ACCT ", passwd, "\r\n");
+ if (zfsendcmd(ucmd) == 6)
+ stopit = 2;
+ break;
+
+ case 421: /* service not available, so closed anyway */
+ case 501: /* syntax error */
+ case 503: /* bad commands */
+ case 530: /* not logged in */
+ case 550: /* random can't-do-that */
+ default: /* whatever, should flag this as bad karma */
+ /* need more diagnostics here */
+ stopit = 2;
+ break;
+ }
+ }
+
+ zsfree(ucmd);
+ if (zcfd == -1)
+ return 1;
+ if (stopit == 2 || (lastcode != 230 && lastcode != 202)) {
+ zwarnnam(name, "login failed", NULL, 0);
+ return 1;
+ }
+
+ if (*args) {
+ int cnt;
+ for (cnt = 0; *args; args++)
+ cnt++;
+ zwarnnam(name, "warning: %d comand arguments not used\n", NULL, cnt);
+ }
+ zfstatus |= ZFST_LOGI;
+ zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY);
+ if (acct)
+ zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), ZFPM_READONLY);
+
+ /*
+ * Get the directory. This is possibly an unnecessary overhead, of
+ * course, but when you're being driven by shell functions there's
+ * just no way of telling.
+ */
+ return zfgetcwd();
+}
+
+/* do ls or dir on the remote directory */
+
+/**/
+static int
+zftp_dir(char *name, char **args, int flags)
+{
+ /* maybe should be cleverer about handling arguments */
+ char *cmd;
+ int ret;
+
+ /*
+ * RFC959 says this must be ASCII or EBCDIC, not image format.
+ * I rather suspect on a UNIX server we get away handsomely
+ * with doing everything, including this, as image.
+ */
+ zfsettype(ZFST_ASCI);
+
+ cmd = zfargstring((flags & ZFTP_NLST) ? "NLST" : "LIST", args);
+ ret = zfgetdata(name, NULL, cmd, 0);
+ zsfree(cmd);
+ if (ret)
+ return 1;
+
+ fflush(stdout); /* since we're now using fd 1 */
+ return zfsenddata(name, 1, 0, 0);
+}
+
+/* change the remote directory */
+
+/**/
+static int
+zftp_cd(char *name, char **args, int flags)
+{
+ /* change directory --- enhance to allow 'zftp cdup' */
+ int ret;
+
+ if ((flags & ZFTP_CDUP) || !strcmp(*args, "..") ||
+ !strcmp(*args, "../")) {
+ ret = zfsendcmd("CDUP\r\n");
+ } else {
+ char *cmd = tricat("CWD ", *args, "\r\n");
+ ret = zfsendcmd(cmd);
+ zsfree(cmd);
+ }
+ if (ret > 2)
+ return 1;
+ /* sometimes the directory may be in the response. usually not. */
+ if (zfgetcwd())
+ return 1;
+
+ return 0;
+}
+
+/* get the remote directory */
+
+/**/
+static int
+zfgetcwd(void)
+{
+ char *ptr, *eptr;
+ int endc;
+ List l;
+
+ if (zfprefs & ZFPF_DUMB)
+ return 1;
+ if (zfsendcmd("PWD\r\n") > 2) {
+ zfunsetparam("ZFTP_PWD");
+ return 1;
+ }
+ ptr = lastmsg;
+ while (*ptr == ' ')
+ ptr++;
+ if (!*ptr) /* ultra safe */
+ return 1;
+ if (*ptr == '"') {
+ ptr++;
+ endc = '"';
+ } else
+ endc = ' ';
+ for (eptr = ptr; *eptr && *eptr != endc; eptr++)
+ ;
+ zfsetparam("ZFTP_PWD", ztrduppfx(ptr, eptr-ptr), ZFPM_READONLY);
+
+ /*
+ * This isn't so necessary if we're going to have a shell function
+ * front end. By putting it here, and in close when ZFTP_PWD is unset,
+ * we at least cover the bases.
+ */
+ if ((l = getshfunc("zftp_chpwd")) != &dummy_list)
+ doshfunc("zftp_chpwd", l, NULL, 0, 1);
+
+ return 0;
+}
+
+/*
+ * Set the type for the next transfer, usually image (binary) or ASCII.
+ */
+
+/**/
+static int
+zfsettype(int type)
+{
+ char buf[] = "TYPE X\r\n";
+ if (ZFST_TYPE(type) == ZFST_CTYP(zfstatus))
+ return 0;
+ buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I';
+ if (zfsendcmd(buf) > 2)
+ return 1;
+ zfstatus &= ~(ZFST_TMSK << ZFST_TBIT);
+ /* shift the type left to set the current type bits */;
+ zfstatus |= type << ZFST_TBIT;
+ return 0;
+}
+
+/*
+ * Print or get a new type for the transfer.
+ * We don't actually set the type at this point.
+ */
+
+/**/
+static int
+zftp_type(char *name, char **args, int flags)
+{
+ char *str, nt, tbuf[2] = "A";
+ if (flags & (ZFTP_TBIN|ZFTP_TASC)) {
+ nt = (flags & ZFTP_TBIN) ? 'I' : 'A';
+ } else if (!(str = *args)) {
+ /*
+ * Since this is supposed to be a low-level basis for
+ * an FTP system, just print the single code letter.
+ */
+ printf("%c\n", (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I');
+ fflush(stdout);
+ return 0;
+ } else {
+ nt = toupper(*str);
+ /*
+ * RFC959 specifies other types, but these are the only
+ * ones we know what to do with.
+ */
+ if (str[1] || (nt != 'A' && nt != 'B' && nt != 'I')) {
+ zwarnnam(name, "transfer type %s not recognised", str, 0);
+ return 1;
+ }
+
+ if (nt == 'B') /* binary = image */
+ nt = 'I';
+ }
+
+ zfstatus &= ~ZFST_TMSK;
+ zfstatus |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI;
+ tbuf[0] = nt;
+ zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY);
+ return 0;
+}
+
+/**/
+static int
+zftp_mode(char *name, char **args, int flags)
+{
+ char *str, cmd[] = "MODE X\r\n";
+ int nt;
+
+ if (!(str = *args)) {
+ printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B');
+ fflush(stdout);
+ return 0;
+ }
+ nt = str[0] = toupper(*str);
+ if (str[1] || (nt != 'S' && nt != 'B')) {
+ zwarnnam(name, "transfer mode %s not recognised", str, 0);
+ return 1;
+ }
+ cmd[5] = (char) nt;
+ if (zfsendcmd(cmd) > 2)
+ return 1;
+ zfstatus &= ZFST_MMSK;
+ zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC;
+ zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY);
+ return 0;
+}
+
+/**/
+static int
+zftp_local(char *name, char **args, int flags)
+{
+ int more = !!args[1], ret = 0, dofd = !*args;
+ while (*args || dofd) {
+ long sz;
+ char *mt;
+ int newret = zfstats(*args, !(flags & ZFTP_HERE), &sz, &mt,
+ dofd ? 0 : -1);
+ if (newret == 2) /* at least one is not implemented */
+ return 2;
+ else if (newret) {
+ ret = 1;
+ if (mt)
+ zsfree(mt);
+ args++;
+ continue;
+ }
+ if (more) {
+ fputs(*args, stdout);
+ fputc(' ', stdout);
+ }
+ printf("%ld %s\n", sz, mt);
+ zsfree(mt);
+ if (dofd)
+ break;
+ args++;
+ }
+ fflush(stdout);
+
+ return ret;
+}
+
+/*
+ * Generic transfer for get, put and append.
+ *
+ * Get sends all files to stdout, i.e. this is basically cat. It's up to a
+ * shell function driver to turn this into standard FTP-like commands.
+ *
+ * Put/append sends everything from stdin down the drai^H^H^Hata connection.
+ * Slightly weird with multiple files in that it tries to read
+ * a separate complete file from stdin each time, which is
+ * only even potentially useful interactively. But the only
+ * real alternative is just to allow one file at a time.
+ */
+
+/**/
+static int
+zftp_getput(char *name, char **args, int flags)
+{
+ int ret = 0, recv = (flags & ZFTP_RECV), getsize = 0, progress = 1;
+ char *cmd = recv ? "RETR " : (flags & ZFTP_APPE) ? "APPE " : "STOR ";
+ List l;
+
+ /*
+ * At this point I'd like to set progress to 0 if we're
+ * backgrounded, since it's hard for the user to find out.
+ * It turns out it's hard enough for us to find out.
+ * The problem is that zsh clears it's job table, so we
+ * just don't know if we're some forked shell in a pipeline
+ * somewhere or in the background. This seems to me a problem.
+ */
+
+ zfsettype(ZFST_TYPE(zfstatus));
+
+ if (recv)
+ fflush(stdout); /* since we may be using fd 1 */
+ for (; *args; args++) {
+ char *ln, *rest = NULL;
+ long startat = 0;
+ if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) {
+ long sz;
+ /*
+ * This calls the SIZE command to get the size for remote
+ * files. Some servers send the size with the reply to
+ * the transfer command (i.e. RETR), in which
+ * case we note the fact and don't call this
+ * next time. For that reason, the first call
+ * of zftp_progress is delayed until zfsenddata().
+ */
+ if ((!(zfprefs & ZFPF_DUMB) &&
+ (zfstatus & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ)
+ || !recv) {
+ /* the final 0 is a local fd to fstat if recv is zero */
+ zfstats(*args, recv, &sz, NULL, 0);
+ /* even if it doesn't support SIZE, it may tell us */
+ if (recv && sz == -1)
+ getsize = 1;
+ } else
+ getsize = 1;
+ zfstarttrans(*args, recv, sz);
+ }
+
+ if (flags & ZFTP_REST) {
+ startat = zstrtol(args[1], NULL, 10);
+ rest = tricat("REST ", args[1], "\r\n");
+ }
+
+ ln = tricat(cmd, *args, "\r\n");
+ /* note zdfd doesn't exist till zfgetdata() creates it */
+ if (zfgetdata(name, rest, ln, getsize) ||
+ zfsenddata(name, recv, progress, startat))
+ ret = 1;
+ zsfree(ln);
+ if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) {
+ /* progress to finish: ZFTP_TRANSFER set to GF or PF */
+ zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"),
+ ZFPM_READONLY);
+ doshfunc("zftp_progress", l, NULL, 0, 1);
+ }
+ if (rest) {
+ zsfree(rest);
+ args++;
+ }
+ if (errflag)
+ break;
+ }
+ zfendtrans();
+ return ret;
+}
+
+/*
+ * Delete a list of files on the server. We allow a list by analogy with
+ * `rm'.
+ */
+
+/**/
+static int
+zftp_delete(char *name, char **args, int flags)
+{
+ int ret = 0;
+ char *cmd, **aptr;
+ for (aptr = args; *aptr; aptr++) {
+ cmd = tricat("DELE ", *aptr, "\r\n");
+ if (zfsendcmd(cmd) > 2)
+ ret = 1;
+ zsfree(cmd);
+ }
+ return ret;
+}
+
+/* Create or remove a directory on the server */
+
+/**/
+static int
+zftp_mkdir(char *name, char **args, int flags)
+{
+ int ret;
+ char *cmd = tricat((flags & ZFTP_DELE) ? "RMD " : "MKD ",
+ *args, "\r\n");
+ ret = (zfsendcmd(cmd) > 2);
+ zsfree(cmd);
+ return ret;
+}
+
+/* Rename a file on the server */
+
+/**/
+static int
+zftp_rename(char *name, char **args, int flags)
+{
+ int ret;
+ char *cmd;
+
+ cmd = tricat("RNFR ", args[0], "\r\n");
+ ret = 1;
+ if (zfsendcmd(cmd) == 3) {
+ zsfree(cmd);
+ cmd = tricat("RNTO ", args[1], "\r\n");
+ if (zfsendcmd(cmd) == 2)
+ ret = 0;
+ }
+ zsfree(cmd);
+ return ret;
+}
+
+/*
+ * Send random commands, either with SITE or literal.
+ * In the second case, the user better know what they're doing.
+ */
+
+/**/
+static int
+zftp_quote(char *name, char **args, int flags)
+{
+ int ret = 0;
+ char *cmd;
+
+ cmd = (flags & ZFTP_SITE) ? zfargstring("SITE", args)
+ : zfargstring(args[0], args+1);
+ ret = (zfsendcmd(cmd) > 2);
+ zsfree(cmd);
+
+ return ret;
+}
+
+/* Close the connection, ending the session */
+
+/**/
+static int
+zftp_close(char *name, char **args, int flags)
+{
+ char **aptr;
+ List l;
+ zfclosing = 1;
+ if (zcfinish != 2) {
+ /*
+ * haven't had EOF from server, so send a QUIT and get the response.
+ * maybe we should set a shorter timeout for this to avoid
+ * CTRL-c rage.
+ */
+ zfsendcmd("QUIT\r\n");
+ }
+ if (zcin)
+ fclose(zcin);
+ zcin = NULL;
+ close(zcfd);
+ zcfd = -1;
+
+ /* Write the final status in case this is a subshell */
+ zfstatus |= ZFST_CLOS;
+ lseek(zfstatfd, 0, 0);
+ write(zfstatfd, &zfstatus, sizeof(zfstatus));
+ close(zfstatfd);
+ zfstatfd = -1;
+
+ /* Unset the non-special parameters */
+ for (aptr = zfparams; *aptr; aptr++)
+ zfunsetparam(*aptr);
+
+ /* Now ZFTP_PWD is unset. It's up to zftp_chpwd to notice. */
+ if ((l = getshfunc("zftp_chpwd")) != &dummy_list)
+ doshfunc("zftp_chpwd", l, NULL, 0, 1);
+
+ /* tidy up status variables, because mess is bad */
+ zfclosing = zfdrrrring = 0;
+
+ return 0;
+}
+
+/* Safe front end to zftp_close() from within the package */
+
+/**/
+static void
+zfclose(void)
+{
+ if (zcfd != -1)
+ zftp_close("zftp close", NULL, 0);
+}
+
+/* The builtin command frontend to the rest of the package */
+
+/**/
+static int
+bin_zftp(char *name, char **args, char *ops, int func)
+{
+ char fullname[11] = "zftp ";
+ char *cnam = *args++, *prefs, *ptr;
+ Zftpcmd zptr;
+ int n, ret;
+
+ for (zptr = zftpcmdtab; zptr->nam; zptr++)
+ if (!strcmp(zptr->nam, cnam))
+ break;
+
+ if (!zptr->nam) {
+ zwarnnam(name, "no such subcommand: %s", cnam, 0);
+ return 1;
+ }
+
+ /* check number of arguments */
+ for (n = 0; args[n]; n++)
+ ;
+ if (n < zptr->min || (zptr->max != -1 && n > zptr->max)) {
+ zwarnnam(name, "wrong no. of arguments for %s", cnam, 0);
+ return 1;
+ }
+
+ strcat(fullname, cnam);
+ if (zfstatfd != -1) {
+ /* Get the status in case it was set by a forked process */
+ int oldstatus = zfstatus;
+ lseek(zfstatfd, 0, 0);
+ read(zfstatfd, &zfstatus, sizeof(zfstatus));
+ if (zcfd != -1 && (zfstatus & ZFST_CLOS)) {
+ /* got closed in subshell without us knowing */
+ zcfinish = 2;
+ zfclose();
+ } else {
+ /*
+ * fix up status types: unfortunately they may already
+ * have been looked at between being changed in the subshell
+ * and now, but we can't help that.
+ */
+ if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatus))
+ zfsetparam("ZFTP_TYPE",
+ ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ?
+ "A" : "I"), ZFPM_READONLY);
+ if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus))
+ zfsetparam("ZFTP_MODE",
+ ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ?
+ "B" : "S"), ZFPM_READONLY);
+ }
+ }
+ if ((zptr->flags & ZFTP_CONN) && zcfd == -1) {
+ zwarnnam(fullname, "not connected.", NULL, 0);
+ return 1;
+ }
+
+ if ((prefs = getsparam("ZFTP_PREFS"))) {
+ zfprefs = 0;
+ for (ptr = prefs; *ptr; ptr++) {
+ switch (toupper(*ptr)) {
+ case 'S':
+ /* sendport */
+ zfprefs |= ZFPF_SNDP;
+ break;
+
+ case 'P':
+ /*
+ * passive
+ * If we have already been told to use sendport mode,
+ * we're never going to use passive mode.
+ */
+ if (!(zfprefs & ZFPF_SNDP))
+ zfprefs |= ZFPF_PASV;
+ break;
+
+ case 'D':
+ /* dumb */
+ zfprefs |= ZFPF_DUMB;
+ break;
+
+ default:
+ zwarnnam(name, "preference %c not recognized", NULL, *ptr);
+ break;
+ }
+ }
+ }
+
+ ret = (*zptr->fun)(fullname, args, zptr->flags);
+
+ if (zfalarmed)
+ zfunalarm();
+ if (zfdrrrring) {
+ /* had a timeout, close the connection */
+ zcfinish = 2; /* don't try sending QUIT */
+ zfclose();
+ }
+ if (zfstatfd != -1) {
+ /* Set the status in case another process needs to know */
+ lseek(zfstatfd, 0, 0);
+ write(zfstatfd, &zfstatus, sizeof(zfstatus));
+ }
+ return ret;
+}
+
+/* The load/unload routines required by the zsh library interface */
+
+/**/
+int
+boot_zftp(Module m)
+{
+ int ret;
+ if ((ret = addbuiltins(m->nam, bintab,
+ sizeof(bintab)/sizeof(*bintab))) == 1) {
+ /* if successful, set some default parameters */
+ long tmout_def = 60;
+ zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET);
+ zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER);
+ zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET);
+ /* default preferences if user deletes variable */
+ zfprefs = ZFPF_SNDP|ZFPF_PASV;
+ }
+ return !ret;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_zftp(Module m)
+{
+ /*
+ * There are various parameters hanging around, but they're
+ * all non-special so are entirely non-life-threatening.
+ */
+ zfclosedata();
+ zfclose();
+ zsfree(lastmsg);
+ if (zfuserparams)
+ freearray(zfuserparams);
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+
+#endif
diff --git a/Src/Modules/zftp.mdd b/Src/Modules/zftp.mdd
new file mode 100644
index 000000000..83051ae54
--- /dev/null
+++ b/Src/Modules/zftp.mdd
@@ -0,0 +1,3 @@
+autobins="zftp"
+
+objects="zftp.o"
diff --git a/Src/Zle/.cvsignore b/Src/Zle/.cvsignore
new file mode 100644
index 000000000..b10adcc3b
--- /dev/null
+++ b/Src/Zle/.cvsignore
@@ -0,0 +1,14 @@
+Makefile
+Makefile.in
+*.pro
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+thingies.list
+widgets.list
+zle_things.h
+zle_widget.h
diff --git a/Src/Zle/.distfiles b/Src/Zle/.distfiles
new file mode 100644
index 000000000..42c62efe9
--- /dev/null
+++ b/Src/Zle/.distfiles
@@ -0,0 +1,10 @@
+DISTFILES_SRC='
+ .cvsignore .distfiles .exrc
+ comp1.mdd comp.h comp1.c
+ compctl.mdd compctl.c
+ deltochar.mdd deltochar.c
+ zle.mdd iwidgets.list zle.h zle_bindings.c zle_hist.c
+ zle_keymap.c zle_main.c zle_misc.c zle_move.c zle_params.c
+ zle_refresh.c zle_things.sed zle_thingy.c zle_tricky.c
+ zle_utils.c zle_vi.c zle_widget.sed zle_word.c
+'
diff --git a/Src/Zle/.exrc b/Src/Zle/.exrc
new file mode 100644
index 000000000..91d0b39ef
--- /dev/null
+++ b/Src/Zle/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/Src/Zle/.lastloc b/Src/Zle/.lastloc
new file mode 100644
index 000000000..a060de394
--- /dev/null
+++ b/Src/Zle/.lastloc
@@ -0,0 +1,10 @@
+(("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle.h" . 2619)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle.export" . 35)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_main.c" . 13806)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/iwidgets.list" . 7831)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp.h" . 8512)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_tricky.c" . 72920)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp1.c" . 9640)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_vi.c" . 16035)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_utils.c" . 8406)
+ ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp1.export" . 81))
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
new file mode 100644
index 000000000..5d7ef74e2
--- /dev/null
+++ b/Src/Zle/comp.h
@@ -0,0 +1,137 @@
+/*
+ * comp.h - header file for completion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#undef compctlread
+
+typedef struct compctlp *Compctlp;
+typedef struct compctl *Compctl;
+typedef struct compcond *Compcond;
+
+/* node for compctl hash table (compctltab) */
+
+struct compctlp {
+ HashNode next; /* next in hash chain */
+ char *nam; /* command name */
+ int flags; /* CURRENTLY UNUSED */
+ Compctl cc; /* pointer to the compctl desc. */
+};
+
+/* compctl -x condition */
+
+struct compcond {
+ Compcond and, or; /* the next or'ed/and'ed conditions */
+ int type; /* the type (CCT_*) */
+ int n; /* the array length */
+ union { /* these structs hold the data used to */
+ struct { /* test this condition */
+ int *a, *b; /* CCT_POS, CCT_NUMWORDS */
+ }
+ r;
+ struct { /* CCT_CURSTR, CCT_CURPAT,... */
+ int *p;
+ char **s;
+ }
+ s;
+ struct { /* CCT_RANGESTR,... */
+ char **a, **b;
+ }
+ l;
+ }
+ u;
+};
+
+#define CCT_UNUSED 0
+#define CCT_POS 1
+#define CCT_CURSTR 2
+#define CCT_CURPAT 3
+#define CCT_WORDSTR 4
+#define CCT_WORDPAT 5
+#define CCT_CURSUF 6
+#define CCT_CURPRE 7
+#define CCT_CURSUB 8
+#define CCT_CURSUBC 9
+#define CCT_NUMWORDS 10
+#define CCT_RANGESTR 11
+#define CCT_RANGEPAT 12
+
+/* Contains the real description for compctls */
+
+struct compctl {
+ int refc; /* reference count */
+ Compctl next; /* next compctl for -x */
+ unsigned long mask; /* mask of things to complete (CC_*) */
+ char *keyvar; /* for -k (variable) */
+ char *glob; /* for -g (globbing) */
+ char *str; /* for -s (expansion) */
+ char *func; /* for -K (function) */
+ char *explain; /* for -X (explanation) */
+ char *ylist; /* for -y (user-defined desc. for listing) */
+ char *prefix, *suffix; /* for -P and -S (prefix, suffix) */
+ char *subcmd; /* for -l (command name to use) */
+ char *withd; /* for -w (with directory */
+ char *hpat; /* for -H (history pattern) */
+ int hnum; /* for -H (number of events to search) */
+ Compctl ext; /* for -x (first of the compctls after -x) */
+ Compcond cond; /* for -x (condition for this compctl) */
+ Compctl xor; /* for + (next of the xor'ed compctls) */
+};
+
+/* objects to complete */
+#define CC_FILES (1<<0)
+#define CC_COMMPATH (1<<1)
+#define CC_REMOVE (1<<2)
+#define CC_OPTIONS (1<<3)
+#define CC_VARS (1<<4)
+#define CC_BINDINGS (1<<5)
+#define CC_ARRAYS (1<<6)
+#define CC_INTVARS (1<<7)
+#define CC_SHFUNCS (1<<8)
+#define CC_PARAMS (1<<9)
+#define CC_ENVVARS (1<<10)
+#define CC_JOBS (1<<11)
+#define CC_RUNNING (1<<12)
+#define CC_STOPPED (1<<13)
+#define CC_BUILTINS (1<<14)
+#define CC_ALREG (1<<15)
+#define CC_ALGLOB (1<<16)
+#define CC_USERS (1<<17)
+#define CC_DISCMDS (1<<18)
+#define CC_EXCMDS (1<<19)
+#define CC_SCALARS (1<<20)
+#define CC_READONLYS (1<<21)
+#define CC_SPECIALS (1<<22)
+#define CC_DELETE (1<<23)
+#define CC_NAMED (1<<24)
+#define CC_QUOTEFLAG (1<<25)
+#define CC_EXTCMDS (1<<26)
+#define CC_RESWDS (1<<27)
+#define CC_DIRS (1<<28)
+
+#define CC_EXPANDEXPL (1<<30)
+#define CC_RESERVED (1<<31)
diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c
new file mode 100644
index 000000000..acd1288a6
--- /dev/null
+++ b/Src/Zle/comp1.c
@@ -0,0 +1,291 @@
+/*
+ * comp1.c - base of the completion system
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "comp1.mdh"
+
+#include "comp1.pro"
+
+/* default completion infos */
+
+/**/
+struct compctl cc_compos, cc_default, cc_first, cc_dummy;
+
+/* hash table for completion info for commands */
+
+/**/
+HashTable compctltab;
+
+/* Words on the command line, for use in completion */
+
+/**/
+int clwsize, clwnum, clwpos;
+/**/
+char **clwords;
+
+/* != 0 if in a shell function called from completion, such that read -[cl] *
+ * will work (i.e., the line is metafied, and the above word arrays are OK). */
+
+/**/
+int incompctlfunc;
+
+/**/
+static void
+createcompctltable(void)
+{
+ compctltab = newhashtable(23, "compctltab", NULL);
+
+ compctltab->hash = hasher;
+ compctltab->emptytable = emptyhashtable;
+ compctltab->filltable = NULL;
+ compctltab->addnode = addhashnode;
+ compctltab->getnode = gethashnode2;
+ compctltab->getnode2 = gethashnode2;
+ compctltab->removenode = removehashnode;
+ compctltab->disablenode = NULL;
+ compctltab->enablenode = NULL;
+ compctltab->freenode = freecompctlp;
+ compctltab->printnode = NULL;
+}
+
+/**/
+static void
+freecompctlp(HashNode hn)
+{
+ Compctlp ccp = (Compctlp) hn;
+
+ zsfree(ccp->nam);
+ freecompctl(ccp->cc);
+ zfree(ccp, sizeof(struct compctlp));
+}
+
+/**/
+void
+freecompctl(Compctl cc)
+{
+ if (cc == &cc_default ||
+ cc == &cc_first ||
+ cc == &cc_compos ||
+ --cc->refc > 0)
+ return;
+
+ zsfree(cc->keyvar);
+ zsfree(cc->glob);
+ zsfree(cc->str);
+ zsfree(cc->func);
+ zsfree(cc->explain);
+ zsfree(cc->ylist);
+ zsfree(cc->prefix);
+ zsfree(cc->suffix);
+ zsfree(cc->hpat);
+ zsfree(cc->subcmd);
+ if (cc->cond)
+ freecompcond(cc->cond);
+ if (cc->ext) {
+ Compctl n, m;
+
+ n = cc->ext;
+ do {
+ m = (Compctl) (n->next);
+ freecompctl(n);
+ n = m;
+ }
+ while (n);
+ }
+ if (cc->xor && cc->xor != &cc_default)
+ freecompctl(cc->xor);
+ zfree(cc, sizeof(struct compctl));
+}
+
+/**/
+void
+freecompcond(void *a)
+{
+ Compcond cc = (Compcond) a;
+ Compcond and, or, c;
+ int n;
+
+ for (c = cc; c; c = or) {
+ or = c->or;
+ for (; c; c = and) {
+ and = c->and;
+ if (c->type == CCT_POS ||
+ c->type == CCT_NUMWORDS) {
+ free(c->u.r.a);
+ free(c->u.r.b);
+ } else if (c->type == CCT_CURSUF ||
+ c->type == CCT_CURPRE) {
+ for (n = 0; n < c->n; n++)
+ if (c->u.s.s[n])
+ zsfree(c->u.s.s[n]);
+ free(c->u.s.s);
+ } else if (c->type == CCT_RANGESTR ||
+ c->type == CCT_RANGEPAT) {
+ for (n = 0; n < c->n; n++)
+ if (c->u.l.a[n])
+ zsfree(c->u.l.a[n]);
+ free(c->u.l.a);
+ for (n = 0; n < c->n; n++)
+ if (c->u.l.b[n])
+ zsfree(c->u.l.b[n]);
+ free(c->u.l.b);
+ } else {
+ for (n = 0; n < c->n; n++)
+ if (c->u.s.s[n])
+ zsfree(c->u.s.s[n]);
+ free(c->u.s.p);
+ free(c->u.s.s);
+ }
+ zfree(c, sizeof(struct compcond));
+ }
+ }
+}
+
+/**/
+int
+compctlread(char *name, char **args, char *ops, char *reply)
+{
+ char *buf, *bptr;
+
+ /* only allowed to be called for completion */
+ if (!incompctlfunc) {
+ zwarnnam(name, "option valid only in functions called for completion",
+ NULL, 0);
+ return 1;
+ }
+
+ if (ops['l']) {
+ /* -ln gives the index of the word the cursor is currently on, which is
+ available in cs (but remember that Zsh counts from one, not zero!) */
+ if (ops['n']) {
+ char nbuf[14];
+
+ if (ops['e'] || ops['E'])
+ printf("%d\n", cs + 1);
+ if (!ops['e']) {
+ sprintf(nbuf, "%d", cs + 1);
+ setsparam(reply, ztrdup(nbuf));
+ }
+ return 0;
+ }
+ /* without -n, the current line is assigned to the given parameter as a
+ scalar */
+ if (ops['e'] || ops['E']) {
+ zputs((char *) line, stdout);
+ putchar('\n');
+ }
+ if (!ops['e'])
+ setsparam(reply, ztrdup((char *) line));
+ } else {
+ int i;
+
+ /* -cn gives the current cursor position within the current word, which
+ is available in clwpos (but remember that Zsh counts from one, not
+ zero!) */
+ if (ops['n']) {
+ char nbuf[14];
+
+ if (ops['e'] || ops['E'])
+ printf("%d\n", clwpos + 1);
+ if (!ops['e']) {
+ sprintf(nbuf, "%d", clwpos + 1);
+ setsparam(reply, ztrdup(nbuf));
+ }
+ return 0;
+ }
+ /* without -n, the words of the current line are assigned to the given
+ parameters separately */
+ if (ops['A'] && !ops['e']) {
+ /* the -A option means that one array is specified, instead of
+ many parameters */
+ char **p, **b = (char **)zcalloc((clwnum + 1) * sizeof(char *));
+
+ for (i = 0, p = b; i < clwnum; p++, i++)
+ *p = ztrdup(clwords[i]);
+
+ setaparam(reply, b);
+ return 0;
+ }
+ if (ops['e'] || ops['E']) {
+ for (i = 0; i < clwnum; i++) {
+ zputs(clwords[i], stdout);
+ putchar('\n');
+ }
+
+ if (ops['e'])
+ return 0;
+ }
+
+ for (i = 0; i < clwnum && *args; reply = *args++, i++)
+ setsparam(reply, ztrdup(clwords[i]));
+
+ if (i < clwnum) {
+ int j, len;
+
+ for (j = i, len = 0; j < clwnum; len += strlen(clwords[j++]));
+ bptr = buf = zalloc(len + j - i);
+ while (i < clwnum) {
+ strucpy(&bptr, clwords[i++]);
+ *bptr++ = ' ';
+ }
+ bptr[-1] = '\0';
+ } else
+ buf = ztrdup("");
+ setsparam(reply, buf);
+ }
+ return 0;
+}
+
+/**/
+int
+boot_comp1(Module m)
+{
+ compctlreadptr = compctlread;
+ clwords = (char **) zcalloc((clwsize = 16) * sizeof(char *));
+ createcompctltable();
+ cc_compos.mask = CC_COMMPATH;
+ cc_default.refc = 10000;
+ cc_default.mask = CC_FILES;
+ cc_first.refc = 10000;
+ cc_first.mask = 0;
+ return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_comp1(Module m)
+{
+ deletehashtable(compctltab);
+ zfree(clwords, clwsize * sizeof(char *));
+ compctlreadptr = fallback_compctlread;
+ return 0;
+}
+
+#endif /* MODULE */
diff --git a/Src/Zle/comp1.export b/Src/Zle/comp1.export
new file mode 100644
index 000000000..1ac9195df
--- /dev/null
+++ b/Src/Zle/comp1.export
@@ -0,0 +1,22 @@
+#!
+cc_compos
+cc_default
+cc_dummy
+cc_first
+clwnum
+clwords
+clwpos
+clwsize
+cmatcher
+compctl_widgetptr
+compctltab
+freecmatcher
+freecmlist
+freecompcond
+freecompctl
+incompctlfunc
+instring
+patcomps
+printcompctlptr
+quotename
+rembslash
diff --git a/Src/Zle/comp1.mdd b/Src/Zle/comp1.mdd
new file mode 100644
index 000000000..9037e568a
--- /dev/null
+++ b/Src/Zle/comp1.mdd
@@ -0,0 +1,3 @@
+objects="comp1.o"
+
+headers="comp.h"
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
new file mode 100644
index 000000000..658cf4161
--- /dev/null
+++ b/Src/Zle/compctl.c
@@ -0,0 +1,1085 @@
+/*
+ * compctl.c - the compctl builtin
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "compctl.mdh"
+#include "compctl.pro"
+
+#define COMP_LIST (1<<0) /* -L */
+#define COMP_COMMAND (1<<1) /* -C */
+#define COMP_DEFAULT (1<<2) /* -D */
+#define COMP_FIRST (1<<3) /* -T */
+#define COMP_REMOVE (1<<4)
+
+#define COMP_SPECIAL (COMP_COMMAND|COMP_DEFAULT|COMP_FIRST)
+
+/* Flag for listing, command, default, or first completion */
+static int cclist;
+
+/* Mask for determining what to print */
+static unsigned long showmask = 0;
+
+/* Parse the basic flags for `compctl' */
+
+/**/
+static int
+get_compctl(char *name, char ***av, Compctl cc, int first, int isdef)
+{
+ /* Parse the basic flags for completion:
+ * first is a flag that we are not in extended completion,
+ * while hx indicates or (+) completion (need to know for
+ * default and command completion as the initial compctl is special).
+ * cct is a temporary just to hold flags; it never needs freeing.
+ */
+ struct compctl cct;
+ char **argv = *av;
+ int ready = 0, hx = 0;
+
+ /* Handle `compctl + foo ...' specially: turn it into
+ * a default compctl by removing it from the hash table.
+ */
+ if (first && argv[0][0] == '+' && !argv[0][1] &&
+ !(argv[1] && argv[1][0] == '-' && argv[1][1])) {
+ argv++;
+ if(argv[0] && argv[0][0] == '-')
+ argv++;
+ *av = argv;
+ freecompctl(cc);
+ cclist = COMP_REMOVE;
+ return 0;
+ }
+
+ memset((void *)&cct, 0, sizeof(cct));
+
+ /* Loop through the flags until we have no more: *
+ * those with arguments are not properly allocated yet, *
+ * we just hang on to the argument that was passed. */
+ for (; !ready && argv[0] && argv[0][0] == '-' && (argv[0][1] || !first);) {
+ if (!argv[0][1])
+ *argv = "-+";
+ while (!ready && *++(*argv)) {
+ if(**argv == Meta)
+ *++*argv ^= 32;
+ switch (**argv) {
+ case 'f':
+ cct.mask |= CC_FILES;
+ break;
+ case 'c':
+ cct.mask |= CC_COMMPATH;
+ break;
+ case 'm':
+ cct.mask |= CC_EXTCMDS;
+ break;
+ case 'w':
+ cct.mask |= CC_RESWDS;
+ break;
+ case 'o':
+ cct.mask |= CC_OPTIONS;
+ break;
+ case 'v':
+ cct.mask |= CC_VARS;
+ break;
+ case 'b':
+ cct.mask |= CC_BINDINGS;
+ break;
+ case 'A':
+ cct.mask |= CC_ARRAYS;
+ break;
+ case 'I':
+ cct.mask |= CC_INTVARS;
+ break;
+ case 'F':
+ cct.mask |= CC_SHFUNCS;
+ break;
+ case 'p':
+ cct.mask |= CC_PARAMS;
+ break;
+ case 'E':
+ cct.mask |= CC_ENVVARS;
+ break;
+ case 'j':
+ cct.mask |= CC_JOBS;
+ break;
+ case 'r':
+ cct.mask |= CC_RUNNING;
+ break;
+ case 'z':
+ cct.mask |= CC_STOPPED;
+ break;
+ case 'B':
+ cct.mask |= CC_BUILTINS;
+ break;
+ case 'a':
+ cct.mask |= CC_ALREG | CC_ALGLOB;
+ break;
+ case 'R':
+ cct.mask |= CC_ALREG;
+ break;
+ case 'G':
+ cct.mask |= CC_ALGLOB;
+ break;
+ case 'u':
+ cct.mask |= CC_USERS;
+ break;
+ case 'd':
+ cct.mask |= CC_DISCMDS;
+ break;
+ case 'e':
+ cct.mask |= CC_EXCMDS;
+ break;
+ case 'N':
+ cct.mask |= CC_SCALARS;
+ break;
+ case 'O':
+ cct.mask |= CC_READONLYS;
+ break;
+ case 'Z':
+ cct.mask |= CC_SPECIALS;
+ break;
+ case 'q':
+ cct.mask |= CC_REMOVE;
+ break;
+ case 'U':
+ cct.mask |= CC_DELETE;
+ break;
+ case 'n':
+ cct.mask |= CC_NAMED;
+ break;
+ case 'Q':
+ cct.mask |= CC_QUOTEFLAG;
+ break;
+ case '/':
+ cct.mask |= CC_DIRS;
+ break;
+ case 'k':
+ if ((*argv)[1]) {
+ cct.keyvar = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "variable name expected after -%c", NULL,
+ **argv);
+ return 1;
+ } else {
+ cct.keyvar = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'K':
+ if ((*argv)[1]) {
+ cct.func = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "function name expected after -%c", NULL,
+ **argv);
+ return 1;
+ } else {
+ cct.func = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'Y':
+ cct.mask |= CC_EXPANDEXPL;
+ goto expl;
+ case 'X':
+ cct.mask &= ~CC_EXPANDEXPL;
+ expl:
+ if ((*argv)[1]) {
+ cct.explain = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "string expected after -%c", NULL, **argv);
+ return 1;
+ } else {
+ cct.explain = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'y':
+ if ((*argv)[1]) {
+ cct.ylist = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "function/variable expected after -%c",
+ NULL, **argv);
+ } else {
+ cct.ylist = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'P':
+ if ((*argv)[1]) {
+ cct.prefix = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "string expected after -%c", NULL, **argv);
+ return 1;
+ } else {
+ cct.prefix = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'S':
+ if ((*argv)[1]) {
+ cct.suffix = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "string expected after -%c", NULL, **argv);
+ return 1;
+ } else {
+ cct.suffix = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'g':
+ if ((*argv)[1]) {
+ cct.glob = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "glob pattern expected after -%c", NULL,
+ **argv);
+ return 1;
+ } else {
+ cct.glob = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 's':
+ if ((*argv)[1]) {
+ cct.str = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "command string expected after -%c", NULL,
+ **argv);
+ return 1;
+ } else {
+ cct.str = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'l':
+ if ((*argv)[1]) {
+ cct.subcmd = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "command name expected after -%c", NULL,
+ **argv);
+ return 1;
+ } else {
+ cct.subcmd = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'W':
+ if ((*argv)[1]) {
+ cct.withd = (*argv) + 1;
+ *argv = "" - 1;
+ } else if (!argv[1]) {
+ zwarnnam(name, "path expected after -%c", NULL,
+ **argv);
+ return 1;
+ } else {
+ cct.withd = *++argv;
+ *argv = "" - 1;
+ }
+ break;
+ case 'H':
+ if ((*argv)[1])
+ cct.hnum = atoi((*argv) + 1);
+ else if (argv[1])
+ cct.hnum = atoi(*++argv);
+ else {
+ zwarnnam(name, "number expected after -%c", NULL,
+ **argv);
+ return 1;
+ }
+ if (!argv[1]) {
+ zwarnnam(name, "missing pattern after -%c", NULL,
+ **argv);
+ return 1;
+ }
+ cct.hpat = *++argv;
+ if (cct.hnum < 1)
+ cct.hnum = 0;
+ if (*cct.hpat == '*' && !cct.hpat[1])
+ cct.hpat = "";
+ *argv = "" - 1;
+ break;
+ case 'C':
+ if (first && !hx) {
+ cclist |= COMP_COMMAND;
+ } else {
+ zwarnnam(name, "misplaced command completion (-C) flag",
+ NULL, 0);
+ return 1;
+ }
+ break;
+ case 'D':
+ if (first && !hx) {
+ isdef = 1;
+ cclist |= COMP_DEFAULT;
+ } else {
+ zwarnnam(name, "misplaced default completion (-D) flag",
+ NULL, 0);
+ return 1;
+ }
+ break;
+ case 'T':
+ if (first && !hx) {
+ cclist |= COMP_FIRST;
+ } else {
+ zwarnnam(name, "misplaced first completion (-T) flag",
+ NULL, 0);
+ return 1;
+ }
+ break;
+ case 'L':
+ if (!first || hx) {
+ zwarnnam(name, "illegal use of -L flag", NULL, 0);
+ return 1;
+ }
+ cclist |= COMP_LIST;
+ break;
+ case 'x':
+ if (!argv[1]) {
+ zwarnnam(name, "condition expected after -%c", NULL,
+ **argv);
+ return 1;
+ }
+ if (first) {
+ argv++;
+ if (get_xcompctl(name, &argv, &cct, isdef)) {
+ if (cct.ext)
+ freecompctl(cct.ext);
+ return 1;
+ }
+ ready = 2;
+ } else {
+ zwarnnam(name, "recursive extended completion not allowed",
+ NULL, 0);
+ return 1;
+ }
+ break;
+ default:
+ if (!first && (**argv == '-' || **argv == '+'))
+ (*argv)--, argv--, ready = 1;
+ else {
+ zwarnnam(name, "bad option: -%c", NULL, **argv);
+ return 1;
+ }
+ }
+ }
+
+ if (*++argv && (!ready || ready == 2) &&
+ **argv == '+' && !argv[0][1]) {
+ /* There's an alternative (+) completion: assign
+ * what we have so far before moving on to that.
+ */
+ if (cc_assign(name, &cc, &cct, first && !hx))
+ return 1;
+
+ hx = 1;
+ ready = 0;
+
+ if (!*++argv || **argv != '-' ||
+ (**argv == '-' && (!argv[0][1] ||
+ (argv[0][1] == '-' && !argv[0][2])))) {
+ /* No argument to +, which means do default completion */
+ if (isdef)
+ zwarnnam(name,
+ "recursive xor'd default completions not allowed",
+ NULL, 0);
+ else
+ cc->xor = &cc_default;
+ } else {
+ /* more flags follow: prepare to loop again */
+ cc->xor = (Compctl) zcalloc(sizeof(*cc));
+ cc = cc->xor;
+ memset((void *)&cct, 0, sizeof(cct));
+ }
+ }
+ }
+ if (!ready && *argv && **argv == '-')
+ argv++;
+
+ if (! (cct.mask & (CC_EXCMDS | CC_DISCMDS)))
+ cct.mask |= CC_EXCMDS;
+
+ /* assign the last set of flags we parsed */
+ if (cc_assign(name, &cc, &cct, first && !hx))
+ return 1;
+
+ *av = argv;
+
+ return 0;
+}
+
+/* Handle the -x ... -- part of compctl. */
+
+/**/
+static int
+get_xcompctl(char *name, char ***av, Compctl cc, int isdef)
+{
+ char **argv = *av, *t, *tt, sav;
+ int n, l = 0, ready = 0;
+ Compcond m, c, o;
+ Compctl *next = &(cc->ext);
+
+ while (!ready) {
+ /* o keeps track of or's, m remembers the starting condition,
+ * c is the current condition being parsed
+ */
+ o = m = c = (Compcond) zcalloc(sizeof(*c));
+ /* Loop over each condition: something like 's[...][...], p[...]' */
+ for (t = *argv; *t;) {
+ while (*t == ' ')
+ t++;
+ /* First get the condition code */
+ switch (*t) {
+ case 's':
+ c->type = CCT_CURSUF;
+ break;
+ case 'S':
+ c->type = CCT_CURPRE;
+ break;
+ case 'p':
+ c->type = CCT_POS;
+ break;
+ case 'c':
+ c->type = CCT_CURSTR;
+ break;
+ case 'C':
+ c->type = CCT_CURPAT;
+ break;
+ case 'w':
+ c->type = CCT_WORDSTR;
+ break;
+ case 'W':
+ c->type = CCT_WORDPAT;
+ break;
+ case 'n':
+ c->type = CCT_CURSUB;
+ break;
+ case 'N':
+ c->type = CCT_CURSUBC;
+ break;
+ case 'm':
+ c->type = CCT_NUMWORDS;
+ break;
+ case 'r':
+ c->type = CCT_RANGESTR;
+ break;
+ case 'R':
+ c->type = CCT_RANGEPAT;
+ break;
+ default:
+ t[1] = '\0';
+ zwarnnam(name, "unknown condition code: %s", t, 0);
+ zfree(m, sizeof(struct compcond));
+
+ return 1;
+ }
+ /* Now get the arguments in square brackets */
+ if (t[1] != '[') {
+ t[1] = '\0';
+ zwarnnam(name, "expected condition after condition code: %s", t, 0);
+ zfree(m, sizeof(struct compcond));
+
+ return 1;
+ }
+ t++;
+ /* First count how many or'd arguments there are,
+ * marking the active ]'s and ,'s with unprintable characters.
+ */
+ for (n = 0, tt = t; *tt == '['; n++) {
+ for (l = 1, tt++; *tt && l; tt++)
+ if (*tt == '\\' && tt[1])
+ tt++;
+ else if (*tt == '[')
+ l++;
+ else if (*tt == ']')
+ l--;
+ else if (l == 1 && *tt == ',')
+ *tt = '\201';
+ if (tt[-1] == ']')
+ tt[-1] = '\200';
+ }
+
+ if (l) {
+ t[1] = '\0';
+ zwarnnam(name, "error after condition code: %s", t, 0);
+ zfree(m, sizeof(struct compcond));
+
+ return 1;
+ }
+ c->n = n;
+
+ /* Allocate space for all the arguments of the conditions */
+ if (c->type == CCT_POS ||
+ c->type == CCT_NUMWORDS) {
+ c->u.r.a = (int *)zcalloc(n * sizeof(int));
+ c->u.r.b = (int *)zcalloc(n * sizeof(int));
+ } else if (c->type == CCT_CURSUF ||
+ c->type == CCT_CURPRE)
+ c->u.s.s = (char **)zcalloc(n * sizeof(char *));
+
+ else if (c->type == CCT_RANGESTR ||
+ c->type == CCT_RANGEPAT) {
+ c->u.l.a = (char **)zcalloc(n * sizeof(char *));
+ c->u.l.b = (char **)zcalloc(n * sizeof(char *));
+ } else {
+ c->u.s.p = (int *)zcalloc(n * sizeof(int));
+ c->u.s.s = (char **)zcalloc(n * sizeof(char *));
+ }
+ /* Now loop over the actual arguments */
+ for (l = 0; *t == '['; l++, t++) {
+ for (t++; *t && *t == ' '; t++);
+ tt = t;
+ if (c->type == CCT_POS ||
+ c->type == CCT_NUMWORDS) {
+ /* p[...] or m[...]: one or two numbers expected */
+ for (; *t && *t != '\201' && *t != '\200'; t++);
+ if (!(sav = *t)) {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.r.a[l] = atoi(tt);
+ /* Second argument is optional: see if it's there */
+ if (sav == '\200')
+ /* no: copy first argument */
+ c->u.r.b[l] = c->u.r.a[l];
+ else {
+ tt = ++t;
+ for (; *t && *t != '\200'; t++);
+ if (!*t) {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.r.b[l] = atoi(tt);
+ }
+ } else if (c->type == CCT_CURSUF ||
+ c->type == CCT_CURPRE) {
+ /* -s[..] or -S[..]: single string expected */
+ for (; *t && *t != '\200'; t++)
+ if (*t == '\201')
+ *t = ',';
+ if (!*t) {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.s.s[l] = ztrdup(tt);
+ } else if (c->type == CCT_RANGESTR ||
+ c->type == CCT_RANGEPAT) {
+ /* -r[..,..] or -R[..,..]: two strings expected */
+ for (; *t && *t != '\201'; t++);
+ if (!*t) {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.l.a[l] = ztrdup(tt);
+ tt = ++t;
+ /* any more commas are text, not active */
+ for (; *t && *t != '\200'; t++)
+ if (*t == '\201')
+ *t = ',';
+ if (!*t) {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.l.b[l] = ztrdup(tt);
+ } else {
+ /* remaining patterns are number followed by string */
+ for (; *t && *t != '\200' && *t != '\201'; t++);
+ if (!*t || *t == '\200') {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.s.p[l] = atoi(tt);
+ tt = ++t;
+ for (; *t && *t != '\200'; t++)
+ if (*t == '\201')
+ *t = ',';
+ if (!*t) {
+ zwarnnam(name, "error in condition", NULL, 0);
+ freecompcond(m);
+ return 1;
+ }
+ *t = '\0';
+ c->u.s.s[l] = ztrdup(tt);
+ }
+ }
+ while (*t == ' ')
+ t++;
+ if (*t == ',') {
+ /* Another condition to `or' */
+ o->or = c = (Compcond) zcalloc(sizeof(*c));
+ o = c;
+ t++;
+ } else if (*t) {
+ /* Another condition to `and' */
+ c->and = (Compcond) zcalloc(sizeof(*c));
+ c = c->and;
+ }
+ }
+ /* Assign condition to current compctl */
+ *next = (Compctl) zcalloc(sizeof(*cc));
+ (*next)->cond = m;
+ argv++;
+ /* End of the condition; get the flags that go with it. */
+ if (get_compctl(name, &argv, *next, 0, isdef))
+ return 1;
+ if ((!argv || !*argv) && (cclist & COMP_SPECIAL))
+ /* default, first, or command completion finished */
+ ready = 1;
+ else {
+ /* see if we are looking for more conditions or are
+ * ready to return (ready = 1)
+ */
+ if (!argv || !*argv || **argv != '-' ||
+ ((!argv[0][1] || argv[0][1] == '+') && !argv[1])) {
+ zwarnnam(name, "missing command names", NULL, 0);
+ return 1;
+ }
+ if (!strcmp(*argv, "--"))
+ ready = 1;
+ else if (!strcmp(*argv, "-+") && argv[1] &&
+ !strcmp(argv[1], "--")) {
+ ready = 1;
+ argv++;
+ }
+ argv++;
+ /* prepare to put the next lot of conditions on the end */
+ next = &((*next)->next);
+ }
+ }
+ /* save position at end of parsing */
+ *av = argv - 1;
+ return 0;
+}
+
+/**/
+static int
+cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass)
+{
+ /* Copy over the details from the values in cct to those in *ccptr */
+ Compctl cc;
+
+ if (cct->subcmd && (cct->keyvar || cct->glob || cct->str ||
+ cct->func || cct->explain || cct->ylist ||
+ cct->prefix)) {
+ zwarnnam(name, "illegal combination of options", NULL, 0);
+ return 1;
+ }
+
+ /* Handle assignment of new default or command completion */
+ if (reass && !(cclist & COMP_LIST)) {
+ /* if not listing */
+ if (cclist == (COMP_COMMAND|COMP_DEFAULT)
+ || cclist == (COMP_COMMAND|COMP_FIRST)
+ || cclist == (COMP_DEFAULT|COMP_FIRST)
+ || cclist == COMP_SPECIAL) {
+ zwarnnam(name, "can't set -D, -T, and -C simultaneously", NULL, 0);
+ /* ... because the following code wouldn't work. */
+ return 1;
+ }
+ if (cclist & COMP_COMMAND) {
+ /* command */
+ *ccptr = &cc_compos;
+ cc_reassign(*ccptr);
+ } else if (cclist & COMP_DEFAULT) {
+ /* default */
+ *ccptr = &cc_default;
+ cc_reassign(*ccptr);
+ } else if (cclist & COMP_FIRST) {
+ /* first */
+ *ccptr = &cc_first;
+ cc_reassign(*ccptr);
+ }
+ }
+
+ /* Free the old compctl */
+ cc = *ccptr;
+ zsfree(cc->keyvar);
+ zsfree(cc->glob);
+ zsfree(cc->str);
+ zsfree(cc->func);
+ zsfree(cc->explain);
+ zsfree(cc->ylist);
+ zsfree(cc->prefix);
+ zsfree(cc->suffix);
+ zsfree(cc->subcmd);
+ zsfree(cc->withd);
+ zsfree(cc->hpat);
+
+ /* and copy over the new stuff, (permanently) allocating
+ * space for strings.
+ */
+ cc->mask = cct->mask;
+ cc->keyvar = ztrdup(cct->keyvar);
+ cc->glob = ztrdup(cct->glob);
+ cc->str = ztrdup(cct->str);
+ cc->func = ztrdup(cct->func);
+ cc->explain = ztrdup(cct->explain);
+ cc->ylist = ztrdup(cct->ylist);
+ cc->prefix = ztrdup(cct->prefix);
+ cc->suffix = ztrdup(cct->suffix);
+ cc->subcmd = ztrdup(cct->subcmd);
+ cc->withd = ztrdup(cct->withd);
+ cc->hpat = ztrdup(cct->hpat);
+ cc->hnum = cct->hnum;
+
+ /* careful with extended completion: it's already allocated */
+ cc->ext = cct->ext;
+
+ return 0;
+}
+
+/**/
+static void
+cc_reassign(Compctl cc)
+{
+ /* Free up a new default or command completion:
+ * this is a hack to free up the parts which should be deleted,
+ * without removing the basic variable which is statically allocated
+ */
+ Compctl c2;
+
+ c2 = (Compctl) zcalloc(sizeof *cc);
+ c2->xor = cc->xor;
+ c2->ext = cc->ext;
+ c2->refc = 1;
+
+ freecompctl(c2);
+
+ cc->ext = cc->xor = NULL;
+}
+
+/**/
+static void
+compctl_process_cc(char **s, Compctl cc)
+{
+ Compctlp ccp;
+
+ if (cclist & COMP_REMOVE) {
+ /* Delete entries for the commands listed */
+ for (; *s; s++) {
+ if ((ccp = (Compctlp) compctltab->removenode(compctltab, *s)))
+ compctltab->freenode((HashNode) ccp);
+ }
+ } else {
+ /* Add the compctl just read to the hash table */
+
+ cc->refc = 0;
+ for (; *s; s++) {
+ cc->refc++;
+ ccp = (Compctlp) zalloc(sizeof *ccp);
+ ccp->cc = cc;
+ compctltab->addnode(compctltab, ztrdup(*s), ccp);
+ }
+ }
+}
+
+/* Print a `compctl' */
+
+/**/
+static void
+printcompctl(char *s, Compctl cc, int printflags)
+{
+ Compctl cc2;
+ char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/";
+ char *mss = " pcCwWsSnNmrR";
+ unsigned long t = 0x7fffffff;
+ unsigned long flags = cc->mask;
+ unsigned long oldshowmask;
+
+ if ((flags & CC_EXCMDS) && !(flags & CC_DISCMDS))
+ flags &= ~CC_EXCMDS;
+
+ /* If showmask is non-zero, then print only those *
+ * commands with that flag set. */
+ if (showmask && !(flags & showmask))
+ return;
+
+ /* Temporarily clear showmask in case we make *
+ * recursive calls to printcompctl. */
+ oldshowmask = showmask;
+ showmask = 0;
+
+ /* print either command name or start of compctl command itself */
+ if (s) {
+ if (cclist & COMP_LIST) {
+ printf("compctl");
+ if (cc == &cc_compos)
+ printf(" -C");
+ if (cc == &cc_default)
+ printf(" -D");
+ if (cc == &cc_first)
+ printf(" -T");
+ } else
+ quotedzputs(s, stdout);
+ }
+
+ /* loop through flags w/o args that are set, printing them if so */
+ if (flags & t) {
+ printf(" -");
+ if ((flags & (CC_ALREG | CC_ALGLOB)) == (CC_ALREG | CC_ALGLOB))
+ putchar('a'), flags &= ~(CC_ALREG | CC_ALGLOB);
+ while (*css) {
+ if (flags & t & 1)
+ putchar(*css);
+ css++;
+ flags >>= 1;
+ t >>= 1;
+ }
+ }
+
+ /* now flags with arguments */
+ flags = cc->mask;
+ printif(cc->keyvar, 'k');
+ printif(cc->func, 'K');
+ printif(cc->explain, (cc->mask & CC_EXPANDEXPL) ? 'Y' : 'X');
+ printif(cc->ylist, 'y');
+ printif(cc->prefix, 'P');
+ printif(cc->suffix, 'S');
+ printif(cc->glob, 'g');
+ printif(cc->str, 's');
+ printif(cc->subcmd, 'l');
+ printif(cc->withd, 'W');
+ if (cc->hpat) {
+ printf(" -H %d ", cc->hnum);
+ quotedzputs(cc->hpat, stdout);
+ }
+
+ /* now the -x ... -- extended completion part */
+ if (cc->ext) {
+ Compcond c, o;
+ int i;
+
+ cc2 = cc->ext;
+ printf(" -x");
+
+ while (cc2) {
+ /* loop over conditions */
+ c = cc2->cond;
+
+ printf(" '");
+ for (c = cc2->cond; c;) {
+ /* loop over or's */
+ o = c->or;
+ while (c) {
+ /* loop over and's */
+ putchar(mss[c->type]);
+
+ for (i = 0; i < c->n; i++) {
+ /* for all [...]'s of a given condition */
+ putchar('[');
+ switch (c->type) {
+ case CCT_POS:
+ case CCT_NUMWORDS:
+ printf("%d,%d", c->u.r.a[i], c->u.r.b[i]);
+ break;
+ case CCT_CURSUF:
+ case CCT_CURPRE:
+ printqt(c->u.s.s[i]);
+ break;
+ case CCT_RANGESTR:
+ case CCT_RANGEPAT:
+ printqt(c->u.l.a[i]);
+ putchar(',');
+ printqt(c->u.l.b[i]);
+ break;
+ default:
+ printf("%d,", c->u.s.p[i]);
+ printqt(c->u.s.s[i]);
+ }
+ putchar(']');
+ }
+ if ((c = c->and))
+ putchar(' ');
+ }
+ if ((c = o))
+ printf(" , ");
+ }
+ putchar('\'');
+ c = cc2->cond;
+ cc2->cond = NULL;
+ /* now print the flags for the current condition */
+ printcompctl(NULL, cc2, 0);
+ cc2->cond = c;
+ if ((cc2 = (Compctl) (cc2->next)))
+ printf(" -");
+ }
+ if (cclist & COMP_LIST)
+ printf(" --");
+ }
+ if (cc && cc->xor) {
+ /* print xor'd (+) completions */
+ printf(" +");
+ if (cc->xor != &cc_default)
+ printcompctl(NULL, cc->xor, 0);
+ }
+ if (s) {
+ if ((cclist & COMP_LIST) && (cc != &cc_compos)
+ && (cc != &cc_default) && (cc != &cc_first)) {
+ if(s[0] == '-' || s[0] == '+')
+ printf(" -");
+ putchar(' ');
+ quotedzputs(s, stdout);
+ }
+ putchar('\n');
+ }
+
+ showmask = oldshowmask;
+}
+
+/**/
+static void
+printcompctlp(HashNode hn, int printflags)
+{
+ Compctlp ccp = (Compctlp) hn;
+
+ /* Function needed for use by scanhashtable() */
+ printcompctl(ccp->nam, ccp->cc, printflags);
+}
+
+/* Main entry point for the `compctl' builtin */
+
+/**/
+static int
+bin_compctl(char *name, char **argv, char *ops, int func)
+{
+ Compctl cc = NULL;
+ int ret = 0;
+
+ /* clear static flags */
+ cclist = 0;
+ showmask = 0;
+
+ /* Parse all the arguments */
+ if (*argv) {
+ cc = (Compctl) zcalloc(sizeof(*cc));
+ if (get_compctl(name, &argv, cc, 1, 0)) {
+ freecompctl(cc);
+ return 1;
+ }
+
+ /* remember flags for printing */
+ showmask = cc->mask;
+ if ((showmask & CC_EXCMDS) && !(showmask & CC_DISCMDS))
+ showmask &= ~CC_EXCMDS;
+
+ /* if no command arguments or just listing, we don't want cc */
+ if (!*argv || (cclist & COMP_LIST))
+ freecompctl(cc);
+ }
+
+ /* If no commands and no -C, -T, or -D, print all the compctl's *
+ * If some flags (other than -C, -T, or -D) were given, then *
+ * only print compctl containing those flags. */
+ if (!*argv && !(cclist & COMP_SPECIAL)) {
+ scanhashtable(compctltab, 1, 0, 0, compctltab->printnode, 0);
+ printcompctl((cclist & COMP_LIST) ? "" : "COMMAND", &cc_compos, 0);
+ printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0);
+ printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0);
+ return ret;
+ }
+
+ /* If we're listing and we've made it to here, then there are arguments *
+ * or a COMP_SPECIAL flag (-D, -C, -T), so print only those. */
+ if (cclist & COMP_LIST) {
+ HashNode hn;
+ char **ptr;
+
+ showmask = 0;
+ for (ptr = argv; *ptr; ptr++) {
+ if ((hn = compctltab->getnode(compctltab, *ptr))) {
+ compctltab->printnode(hn, 0);
+ } else {
+ zwarnnam(name, "no compctl defined for %s", *ptr, 0);
+ ret = 1;
+ }
+ }
+ if (cclist & COMP_COMMAND)
+ printcompctl("", &cc_compos, 0);
+ if (cclist & COMP_DEFAULT)
+ printcompctl("", &cc_default, 0);
+ if (cclist & COMP_FIRST)
+ printcompctl("", &cc_first, 0);
+ return ret;
+ }
+
+ /* Assign the compctl to the commands given */
+ if (*argv) {
+ if(cclist & COMP_SPECIAL)
+ /* Ideally we'd handle this properly, setting both the *
+ * special and normal completions. For the moment, *
+ * this is better than silently failing. */
+ zwarnnam(name, "extraneous commands ignored", NULL, 0);
+ else
+ compctl_process_cc(argv, cc);
+ }
+
+ return ret;
+}
+
+static struct builtin bintab[] = {
+ BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL),
+};
+
+/**/
+int
+boot_compctl(Module m)
+{
+ if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
+ return 1;
+ compctltab->printnode = printcompctlp;
+ return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_compctl(Module m)
+{
+ compctltab->printnode = NULL;
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+#endif
diff --git a/Src/Zle/compctl.mdd b/Src/Zle/compctl.mdd
new file mode 100644
index 000000000..c83ecda29
--- /dev/null
+++ b/Src/Zle/compctl.mdd
@@ -0,0 +1,5 @@
+moddeps="comp1"
+
+autobins="compctl"
+
+objects="compctl.o"
diff --git a/Src/Zle/deltochar.c b/Src/Zle/deltochar.c
new file mode 100644
index 000000000..87f8593b8
--- /dev/null
+++ b/Src/Zle/deltochar.c
@@ -0,0 +1,91 @@
+/*
+ * deltochar.c - ZLE module implementing Emacs' zap-to-char
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Peter Stephenson
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Peter Stephenson or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Peter Stephenson and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Peter Stephenson and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Peter Stephenson and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "deltochar.mdh"
+#include "deltochar.pro"
+
+static Widget w_deletetochar;
+
+/**/
+static void
+deltochar(void)
+{
+ int c = getkey(0), dest = cs, ok = 0, n = zmult;
+
+ if (n > 0) {
+ while (n-- && dest != ll) {
+ while (dest != ll && line[dest] != c)
+ dest++;
+ if (dest != ll) {
+ dest++;
+ if (!n) {
+ foredel(dest - cs);
+ ok++;
+ }
+ }
+ }
+ } else {
+ /* ignore character cursor is on when scanning backwards */
+ if (dest)
+ dest--;
+ while (n++ && dest != 0) {
+ while (dest != 0 && line[dest] != c)
+ dest--;
+ if (line[dest] == c && !n) {
+ backdel(cs - dest);
+ ok++;
+ }
+ }
+ }
+ if (!ok)
+ feep();
+}
+
+/**/
+int
+boot_deltochar(Module m)
+{
+ w_deletetochar = addzlefunction("delete-to-char", deltochar, ZLE_KEEPSUFFIX);
+ if (w_deletetochar)
+ return 0;
+ zwarnnam(m->nam, "name clash when adding ZLE function `delete-to-char'",
+ NULL, 0);
+ return -1;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_deltochar(Module m)
+{
+ deletezlefunction(w_deletetochar);
+ return 0;
+}
+#endif
diff --git a/Src/Zle/deltochar.mdd b/Src/Zle/deltochar.mdd
new file mode 100644
index 000000000..4d1f89d1c
--- /dev/null
+++ b/Src/Zle/deltochar.mdd
@@ -0,0 +1,3 @@
+moddeps="zle"
+
+objects="deltochar.o"
diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list
new file mode 100644
index 000000000..01862160e
--- /dev/null
+++ b/Src/Zle/iwidgets.list
@@ -0,0 +1,172 @@
+#
+# intwidgets.list - list of internally implemented ZLE widgets
+#
+# Each line has the form:
+#
+# "canonical-name" , functionname , ZLE_FLAGS
+#
+# `#' starts a comment. Blank lines are ignored.
+#
+
+"accept-and-hold", acceptandhold, 0
+"accept-and-infer-next-history", acceptandinfernexthistory, 0
+"accept-and-menu-complete", acceptandmenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"accept-line", acceptline, 0
+"accept-line-and-down-history", acceptlineanddownhistory, 0
+"backward-char", backwardchar, 0
+"backward-delete-char", backwarddeletechar, ZLE_KEEPSUFFIX
+"backward-delete-word", backwarddeleteword, ZLE_KEEPSUFFIX
+"backward-kill-line", backwardkillline, ZLE_KILL | ZLE_KEEPSUFFIX
+"backward-kill-word", backwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX
+"backward-word", backwardword, 0
+"beginning-of-buffer-or-history", beginningofbufferorhistory, 0
+"beginning-of-history", beginningofhistory, 0
+"beginning-of-line", beginningofline, 0
+"beginning-of-line-hist", beginningoflinehist, 0
+"capitalize-word", capitalizeword, 0
+"clear-screen", clearscreen, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"complete-word", completeword, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"copy-prev-word", copyprevword, 0
+"copy-region-as-kill", copyregionaskill, ZLE_KEEPSUFFIX
+"delete-char", deletechar, ZLE_KEEPSUFFIX
+"delete-char-or-list", deletecharorlist, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"delete-word", deleteword, ZLE_KEEPSUFFIX
+"describe-key-briefly", describekeybriefly, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"down-case-word", downcaseword, 0
+"down-history", downhistory, 0
+"down-line-or-history", downlineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL
+"down-line-or-search", downlineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL
+"emacs-backward-word", emacsbackwardword, 0
+"emacs-forward-word", emacsforwardword, 0
+"end-of-buffer-or-history", endofbufferorhistory, 0
+"end-of-history", endofhistory, 0
+"end-of-line", endofline, 0
+"end-of-line-hist", endoflinehist, 0
+"exchange-point-and-mark", exchangepointandmark, 0
+"execute-last-named-cmd", NULL, 0
+"execute-named-cmd", NULL, 0
+"expand-cmd-path", expandcmdpath, 0
+"expand-history", expandhistory, 0
+"expand-or-complete", expandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"expand-or-complete-prefix", expandorcompleteprefix, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"expand-word", expandword, 0
+"forward-char", forwardchar, 0
+"forward-word", forwardword, 0
+"get-line", getline, 0
+"gosmacs-transpose-chars", gosmacstransposechars, 0
+"history-beginning-search-backward", historybeginningsearchbackward, 0
+"history-beginning-search-forward", historybeginningsearchforward, 0
+"history-incremental-search-backward", historyincrementalsearchbackward, 0
+"history-incremental-search-forward", historyincrementalsearchforward, 0
+"history-search-backward", historysearchbackward, 0
+"history-search-forward", historysearchforward, 0
+"infer-next-history", infernexthistory, 0
+"insert-last-word", insertlastword, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"kill-buffer", killbuffer, ZLE_KILL | ZLE_KEEPSUFFIX
+"kill-line", killline, ZLE_KILL | ZLE_KEEPSUFFIX
+"kill-region", killregion, ZLE_KILL | ZLE_KEEPSUFFIX
+"kill-whole-line", killwholeline, ZLE_KILL | ZLE_KEEPSUFFIX
+"kill-word", killword, ZLE_KILL | ZLE_KEEPSUFFIX
+"list-choices", listchoices, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"list-expand", listexpand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"magic-space", magicspace, 0
+"menu-complete", menucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"menu-expand-or-complete", menuexpandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"neg-argument", negargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"overwrite-mode", overwritemode, 0
+"pound-insert", poundinsert, 0
+"push-input", pushinput, 0
+"push-line", pushline, 0
+"push-line-or-edit", pushlineoredit, 0
+"quoted-insert", quotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"quote-line", quoteline, 0
+"quote-region", quoteregion, 0
+"redisplay", redisplay, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"redo", redo, 0
+"reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"run-help", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"self-insert", selfinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"self-insert-unmeta", selfinsertunmeta, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"send-break", sendbreak, 0
+"set-mark-command", setmarkcommand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"spell-word", spellword, 0
+"transpose-chars", transposechars, 0
+"transpose-words", transposewords, 0
+"undefined-key", undefinedkey, 0
+"undo", undo, 0
+"universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"up-case-word", upcaseword, 0
+"up-history", uphistory, 0
+"up-line-or-history", uplineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL
+"up-line-or-search", uplineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL
+"vi-add-eol", viaddeol, 0
+"vi-add-next", viaddnext, 0
+"vi-backward-blank-word", vibackwardblankword, 0
+"vi-backward-char", vibackwardchar, 0
+"vi-backward-delete-char", vibackwarddeletechar, ZLE_KEEPSUFFIX
+"vi-backward-kill-word", vibackwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-backward-word", vibackwardword, 0
+"vi-beginning-of-line", vibeginningofline, 0
+"vi-caps-lock-panic", vicapslockpanic, 0
+"vi-change", vichange, 0
+"vi-change-eol", vichangeeol, 0
+"vi-change-whole-line", vichangewholeline, 0
+"vi-cmd-mode", vicmdmode, 0
+"vi-delete", videlete, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-delete-char", videletechar, ZLE_KEEPSUFFIX
+"vi-digit-or-beginning-of-line", vidigitorbeginningofline, 0
+"vi-down-line-or-history", vidownlineorhistory, ZLE_LINEMOVE
+"vi-end-of-line", viendofline, ZLE_LASTCOL
+"vi-fetch-history", vifetchhistory, 0
+"vi-find-next-char", vifindnextchar, 0
+"vi-find-next-char-skip", vifindnextcharskip, 0
+"vi-find-prev-char", vifindprevchar, 0
+"vi-find-prev-char-skip", vifindprevcharskip, 0
+"vi-first-non-blank", vifirstnonblank, 0
+"vi-forward-blank-word", viforwardblankword, 0
+"vi-forward-blank-word-end", viforwardblankwordend, 0
+"vi-forward-char", viforwardchar, 0
+"vi-forward-word", viforwardword, 0
+"vi-forward-word-end", viforwardwordend, 0
+"vi-goto-column", vigotocolumn, 0
+"vi-goto-mark", vigotomark, 0
+"vi-goto-mark-line", vigotomarkline, 0
+"vi-history-search-backward", vihistorysearchbackward, 0
+"vi-history-search-forward", vihistorysearchforward, 0
+"vi-indent", viindent, 0
+"vi-insert", viinsert, 0
+"vi-insert-bol", viinsertbol, 0
+"vi-join", vijoin, 0
+"vi-kill-eol", vikilleol, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-kill-line", vikillline, ZLE_KILL | ZLE_KEEPSUFFIX
+"vi-match-bracket", vimatchbracket, 0
+"vi-open-line-above", viopenlineabove, 0
+"vi-open-line-below", viopenlinebelow, 0
+"vi-oper-swap-case", vioperswapcase, 0
+"vi-pound-insert", vipoundinsert, 0
+"vi-put-after", viputafter, ZLE_YANK
+"vi-put-before", viputbefore, ZLE_YANK
+"vi-quoted-insert", viquotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
+"vi-repeat-change", virepeatchange, 0
+"vi-repeat-find", virepeatfind, 0
+"vi-repeat-search", virepeatsearch, 0
+"vi-replace", vireplace, 0
+"vi-replace-chars", vireplacechars, 0
+"vi-rev-repeat-find", virevrepeatfind, 0
+"vi-rev-repeat-search", virevrepeatsearch, 0
+"vi-set-buffer", visetbuffer, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"vi-set-mark", visetmark, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"vi-substitute", visubstitute, 0
+"vi-swap-case", viswapcase, 0
+"vi-undo-change", viundochange, 0
+"vi-unindent", viunindent, 0
+"vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE
+"vi-yank", viyank, 0
+"vi-yank-eol", viyankeol, 0
+"vi-yank-whole-line", viyankwholeline, 0
+"what-cursor-position", whatcursorposition, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"where-is", whereis, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"which-command", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"yank", yank, ZLE_YANK
+"yank-pop", yankpop, ZLE_YANK
diff --git a/Src/Zle/zle.export b/Src/Zle/zle.export
new file mode 100644
index 000000000..ccd5df98a
--- /dev/null
+++ b/Src/Zle/zle.export
@@ -0,0 +1,10 @@
+#!
+addzlefunction
+backdel
+backkill
+deletezlefunction
+feep
+foredel
+forekill
+getkey
+zmod
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
new file mode 100644
index 000000000..faf6cf878
--- /dev/null
+++ b/Src/Zle/zle.h
@@ -0,0 +1,137 @@
+/*
+ * zle.h - header file for line editor
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#undef trashzle
+#undef zleread
+#undef spaceinline
+#undef gotword
+#undef refresh
+
+typedef struct widget *Widget;
+typedef struct thingy *Thingy;
+
+/* widgets (ZLE functions) */
+
+typedef void (*ZleIntFunc) _((void));
+
+struct widget {
+ int flags; /* flags (see below) */
+ Thingy first; /* `first' thingy that names this widget */
+ union {
+ ZleIntFunc fn; /* pointer to internally implemented widget */
+ char *fnnam; /* name of the shell function for user-defined widget */
+ } u;
+};
+
+#define WIDGET_INT (1<<0) /* widget is internally implemented */
+#define ZLE_MENUCMP (1<<1) /* DON'T invalidate completion list */
+#define ZLE_YANK (1<<3)
+#define ZLE_LINEMOVE (1<<4) /* command is a line-oriented movement */
+#define ZLE_LASTCOL (1<<5) /* command maintains lastcol correctly */
+#define ZLE_KILL (1<<6)
+#define ZLE_KEEPSUFFIX (1<<9) /* DON'T remove added suffix */
+
+/* thingies */
+
+struct thingy {
+ HashNode next; /* next node in the hash chain */
+ char *nam; /* name of the thingy */
+ int flags; /* TH_* flags (see below) */
+ int rc; /* reference count */
+ Widget widget; /* widget named by this thingy */
+ Thingy samew; /* `next' thingy (circularly) naming the same widget */
+};
+
+/* DISABLED is (1<<0) */
+#define TH_IMMORTAL (1<<1) /* can't refer to a different widget */
+
+/* command modifier prefixes */
+
+struct modifier {
+ int flags; /* MOD_* flags (see below) */
+ int mult; /* repeat count */
+ int tmult; /* repeat count actually being edited */
+ int vibuf; /* vi cut buffer */
+};
+
+#define MOD_MULT (1<<0) /* a repeat count has been selected */
+#define MOD_TMULT (1<<1) /* a repeat count is being entered */
+#define MOD_VIBUF (1<<2) /* a vi cut buffer has been selected */
+#define MOD_VIAPP (1<<3) /* appending to the vi cut buffer */
+#define MOD_NEG (1<<4) /* last command was negate argument */
+
+/* current modifier status */
+
+#define zmult (zmod.mult)
+
+/* undo system */
+
+struct change {
+ struct change *prev, *next; /* adjacent changes */
+ int flags; /* see below */
+ int hist; /* history line being changed */
+ int off; /* offset of the text changes */
+ char *del; /* characters to delete (metafied) */
+ char *ins; /* characters to insert (metafied) */
+};
+
+#define CH_NEXT (1<<0) /* next structure is also part of this change */
+#define CH_PREV (1<<1) /* previous structure is also part of this change */
+
+/* known thingies */
+
+#define Th(X) (&thingies[X])
+
+/* opaque keymap type */
+
+typedef struct keymap *Keymap;
+
+typedef void (*KeyScanFunc) _((char *, Thingy, char *, void *));
+
+#define invicmdmode() (!strcmp(curkeymapname, "vicmd"))
+
+/* Standard type of suffix removal. */
+
+#define removesuffix() iremovesuffix(256)
+
+/* Cut/kill buffer type. The buffer itself is purely binary data, *
+ * not NUL-terminated. len is a length count. flags uses the *
+ * CUTBUFFER_* constants defined below. */
+
+struct cutbuffer {
+ char *buf;
+ size_t len;
+ char flags;
+};
+
+typedef struct cutbuffer *Cutbuffer;
+
+#define CUTBUFFER_LINE 1 /* for vi: buffer contains whole lines of data */
+
+#define KRINGCT 8 /* number of buffers in the kill ring */
diff --git a/Src/Zle/zle.mdd b/Src/Zle/zle.mdd
new file mode 100644
index 000000000..29f39d363
--- /dev/null
+++ b/Src/Zle/zle.mdd
@@ -0,0 +1,70 @@
+moddeps="comp1"
+
+autobins="bindkey vared zle"
+
+objects="zle_bindings.o zle_hist.o zle_keymap.o zle_main.o \
+zle_misc.o zle_move.o zle_params.o zle_refresh.o \
+zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o"
+
+headers="zle.h zle_things.h"
+
+:<<\Make
+zle_things.h: thingies.list zle_things.sed
+ ( \
+ echo '/** zle_things.h **/'; \
+ echo '/** indices of and pointers to known thingies **/'; \
+ echo; \
+ echo 'enum {'; \
+ sed -n -f $(sdir)/zle_things.sed < thingies.list; \
+ echo ' ZLE_BUILTIN_THINGY_COUNT'; \
+ echo '};'; \
+ ) > $@
+
+zle_widget.h: widgets.list zle_widget.sed
+ ( \
+ echo '/** zle_widget.h **/'; \
+ echo '/** indices of and pointers to internal widgets **/'; \
+ echo; \
+ echo 'enum {'; \
+ sed -n -f $(sdir)/zle_widget.sed < widgets.list; \
+ echo ' ZLE_BUILTIN_WIDGET_COUNT'; \
+ echo '};'; \
+ ) > $@
+
+thingies.list: iwidgets.list
+ ( \
+ echo '/** thingies.list **/'; \
+ echo '/** thingy structures for the known thingies **/'; \
+ echo; \
+ echo '/* format: T("name", TH_FLAGS, w_widget, t_nextthingy) */'; \
+ echo; \
+ sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \
+ -e 's/^"/T("/; s/$$/, 0,/; h' \
+ -e 's/-//g; s/^.*"\(.*\)".*/w_\1, t_D\1)/' \
+ -e 'H; g; s/\n/ /' \
+ < $(sdir)/iwidgets.list; \
+ sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \
+ -e 's/^"/T("./; s/$$/, TH_IMMORTAL,/; h' \
+ -e 's/-//g; s/^.*"\.\(.*\)".*/w_\1, t_\1)/' \
+ -e 'H; g; s/\n/ /' \
+ < $(sdir)/iwidgets.list; \
+ ) > $@
+
+widgets.list: iwidgets.list
+ ( \
+ echo '/** widgets.list **/'; \
+ echo '/** widget structures for the internal widgets **/'; \
+ echo; \
+ echo '/* format: W(ZLE_FLAGS, t_firstname, functionname) */'; \
+ echo; \
+ sed -e 's/#.*//; /^$$/d; s/-//g' \
+ -e 's/^"\(.*\)" *, *\([^ ]*\) *, *\(.*\)/W(\3, t_\1, \2)/' \
+ < $(sdir)/iwidgets.list; \
+ ) > $@
+
+zle_bindings.o zle_bindings..o: zle_widget.h widgets.list thingies.list
+
+clean-here: clean.zle
+clean.zle:
+ rm -f zle_things.h zle_widget.h widgets.list thingies.list
+Make
diff --git a/Src/Zle/zle_bindings.c b/Src/Zle/zle_bindings.c
new file mode 100644
index 000000000..40e555ad1
--- /dev/null
+++ b/Src/Zle/zle_bindings.c
@@ -0,0 +1,421 @@
+/*
+ * zle_bindings.c - commands and keymaps
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_widget.h"
+
+#include "zle_bindings.pro"
+
+/*
+ * widgets is the table of internally implemented widgets. This
+ * table is not used directly, but each widget in it is referenced
+ * by address from within the table of thingies (below). The only
+ * complication here is that not all systems support union
+ * initialisation.
+ */
+
+static
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+struct widget
+#else /* !HAVE_UNION_INIT */
+# define BR(X) X
+struct intwidget {
+ int flags;
+ Thingy first;
+ ZleIntFunc fn;
+}
+#endif /* !HAVE_UNION_INIT */
+widgets[] = {
+#define W(zle_flags, t_firstname, functionname) \
+ { WIDGET_INT | zle_flags, t_firstname, BR(functionname) },
+#include "widgets.list"
+#undef W
+};
+
+/*
+ * thingies is the table of `known thingies', that exist on startup.
+ * Some bits of ZLE rely on some of these thingies always being the
+ * ones in this table, rather than doing a name lookup and accepting
+ * any semantically identical thingy. The initial reference count of
+ * these thingies is 2: 1 for the widget they name, and 1 extra to
+ * make sure they never get deleted.
+ */
+
+/**/
+struct thingy thingies[] = {
+#define T(name, th_flags, w_idget, t_next) \
+ { NULL, name, th_flags, 2, w_idget, t_next },
+#include "thingies.list"
+#undef T
+ { NULL, NULL, 0, 0, NULL, NULL }
+};
+
+/*
+ * Default key binding tables:
+ *
+ * In these tables, each element is bound to a single thingy, the index
+ * of which in the above table is stored here.
+ */
+
+/**/
+int emacsbind[32] = {
+ /* ^@ */ z_setmarkcommand,
+ /* ^A */ z_beginningofline,
+ /* ^B */ z_backwardchar,
+ /* ^C */ z_undefinedkey,
+ /* ^D */ z_deletecharorlist,
+ /* ^E */ z_endofline,
+ /* ^F */ z_forwardchar,
+ /* ^G */ z_sendbreak,
+ /* ^H */ z_backwarddeletechar,
+ /* ^I */ z_expandorcomplete,
+ /* ^J */ z_acceptline,
+ /* ^K */ z_killline,
+ /* ^L */ z_clearscreen,
+ /* ^M */ z_acceptline,
+ /* ^N */ z_downlineorhistory,
+ /* ^O */ z_acceptlineanddownhistory,
+ /* ^P */ z_uplineorhistory,
+ /* ^Q */ z_pushline,
+ /* ^R */ z_historyincrementalsearchbackward,
+ /* ^S */ z_historyincrementalsearchforward,
+ /* ^T */ z_transposechars,
+ /* ^U */ z_killwholeline,
+ /* ^V */ z_quotedinsert,
+ /* ^W */ z_backwardkillword,
+ /* ^X */ z_undefinedkey,
+ /* ^Y */ z_yank,
+ /* ^Z */ z_undefinedkey,
+ /* ^[ */ z_undefinedkey,
+ /* ^\ */ z_undefinedkey,
+ /* ^] */ z_undefinedkey,
+ /* ^^ */ z_undefinedkey,
+ /* ^_ */ z_undo,
+};
+
+/**/
+int metabind[128] = {
+ /* M-^@ */ z_undefinedkey,
+ /* M-^A */ z_undefinedkey,
+ /* M-^B */ z_undefinedkey,
+ /* M-^C */ z_undefinedkey,
+ /* M-^D */ z_listchoices,
+ /* M-^E */ z_undefinedkey,
+ /* M-^F */ z_undefinedkey,
+ /* M-^G */ z_sendbreak,
+ /* M-^H */ z_backwardkillword,
+ /* M-^I */ z_selfinsertunmeta,
+ /* M-^J */ z_selfinsertunmeta,
+ /* M-^K */ z_undefinedkey,
+ /* M-^L */ z_clearscreen,
+ /* M-^M */ z_selfinsertunmeta,
+ /* M-^N */ z_undefinedkey,
+ /* M-^O */ z_undefinedkey,
+ /* M-^P */ z_undefinedkey,
+ /* M-^Q */ z_undefinedkey,
+ /* M-^R */ z_undefinedkey,
+ /* M-^S */ z_undefinedkey,
+ /* M-^T */ z_undefinedkey,
+ /* M-^U */ z_undefinedkey,
+ /* M-^V */ z_undefinedkey,
+ /* M-^W */ z_undefinedkey,
+ /* M-^X */ z_undefinedkey,
+ /* M-^Y */ z_undefinedkey,
+ /* M-^Z */ z_undefinedkey,
+ /* M-^[ */ z_undefinedkey,
+ /* M-^\ */ z_undefinedkey,
+ /* M-^] */ z_undefinedkey,
+ /* M-^^ */ z_undefinedkey,
+ /* M-^_ */ z_copyprevword,
+ /* M- */ z_expandhistory,
+ /* M-! */ z_expandhistory,
+ /* M-" */ z_quoteregion,
+ /* M-# */ z_undefinedkey,
+ /* M-$ */ z_spellword,
+ /* M-% */ z_undefinedkey,
+ /* M-& */ z_undefinedkey,
+ /* M-' */ z_quoteline,
+ /* M-( */ z_undefinedkey,
+ /* M-) */ z_undefinedkey,
+ /* M-* */ z_undefinedkey,
+ /* M-+ */ z_undefinedkey,
+ /* M-, */ z_undefinedkey,
+ /* M-- */ z_negargument,
+ /* M-. */ z_insertlastword,
+ /* M-/ */ z_undefinedkey,
+ /* M-0 */ z_digitargument,
+ /* M-1 */ z_digitargument,
+ /* M-2 */ z_digitargument,
+ /* M-3 */ z_digitargument,
+ /* M-4 */ z_digitargument,
+ /* M-5 */ z_digitargument,
+ /* M-6 */ z_digitargument,
+ /* M-7 */ z_digitargument,
+ /* M-8 */ z_digitargument,
+ /* M-9 */ z_digitargument,
+ /* M-: */ z_undefinedkey,
+ /* M-; */ z_undefinedkey,
+ /* M-< */ z_beginningofbufferorhistory,
+ /* M-= */ z_undefinedkey,
+ /* M-> */ z_endofbufferorhistory,
+ /* M-? */ z_whichcommand,
+ /* M-@ */ z_undefinedkey,
+ /* M-A */ z_acceptandhold,
+ /* M-B */ z_backwardword,
+ /* M-C */ z_capitalizeword,
+ /* M-D */ z_killword,
+ /* M-E */ z_undefinedkey,
+ /* M-F */ z_forwardword,
+ /* M-G */ z_getline,
+ /* M-H */ z_runhelp,
+ /* M-I */ z_undefinedkey,
+ /* M-J */ z_undefinedkey,
+ /* M-K */ z_undefinedkey,
+ /* M-L */ z_downcaseword,
+ /* M-M */ z_undefinedkey,
+ /* M-N */ z_historybeginningsearchforward,
+ /* M-O */ z_undefinedkey,
+ /* M-P */ z_historybeginningsearchbackward,
+ /* M-Q */ z_pushline,
+ /* M-R */ z_undefinedkey,
+ /* M-S */ z_spellword,
+ /* M-T */ z_transposewords,
+ /* M-U */ z_upcaseword,
+ /* M-V */ z_undefinedkey,
+ /* M-W */ z_copyregionaskill,
+ /* M-X */ z_undefinedkey,
+ /* M-Y */ z_undefinedkey,
+ /* M-Z */ z_undefinedkey,
+ /* M-[ */ z_undefinedkey,
+ /* M-\ */ z_undefinedkey,
+ /* M-] */ z_undefinedkey,
+ /* M-^ */ z_undefinedkey,
+ /* M-_ */ z_insertlastword,
+ /* M-` */ z_undefinedkey,
+ /* M-a */ z_acceptandhold,
+ /* M-b */ z_backwardword,
+ /* M-c */ z_capitalizeword,
+ /* M-d */ z_killword,
+ /* M-e */ z_undefinedkey,
+ /* M-f */ z_forwardword,
+ /* M-g */ z_getline,
+ /* M-h */ z_runhelp,
+ /* M-i */ z_undefinedkey,
+ /* M-j */ z_undefinedkey,
+ /* M-k */ z_undefinedkey,
+ /* M-l */ z_downcaseword,
+ /* M-m */ z_undefinedkey,
+ /* M-n */ z_historybeginningsearchforward,
+ /* M-o */ z_undefinedkey,
+ /* M-p */ z_historybeginningsearchbackward,
+ /* M-q */ z_pushline,
+ /* M-r */ z_undefinedkey,
+ /* M-s */ z_spellword,
+ /* M-t */ z_transposewords,
+ /* M-u */ z_upcaseword,
+ /* M-v */ z_undefinedkey,
+ /* M-w */ z_copyregionaskill,
+ /* M-x */ z_executenamedcmd,
+ /* M-y */ z_yankpop,
+ /* M-z */ z_executelastnamedcmd,
+ /* M-{ */ z_undefinedkey,
+ /* M-| */ z_vigotocolumn,
+ /* M-} */ z_undefinedkey,
+ /* M-~ */ z_undefinedkey,
+ /* M-^? */ z_backwardkillword,
+};
+
+/**/
+int viinsbind[32] = {
+ /* ^@ */ z_undefinedkey,
+ /* ^A */ z_selfinsert,
+ /* ^B */ z_selfinsert,
+ /* ^C */ z_selfinsert,
+ /* ^D */ z_listchoices,
+ /* ^E */ z_selfinsert,
+ /* ^F */ z_selfinsert,
+ /* ^G */ z_listexpand,
+ /* ^H */ z_vibackwarddeletechar,
+ /* ^I */ z_expandorcomplete,
+ /* ^J */ z_acceptline,
+ /* ^K */ z_selfinsert,
+ /* ^L */ z_clearscreen,
+ /* ^M */ z_acceptline,
+ /* ^N */ z_selfinsert,
+ /* ^O */ z_selfinsert,
+ /* ^P */ z_selfinsert,
+ /* ^Q */ z_viquotedinsert,
+ /* ^R */ z_redisplay,
+ /* ^S */ z_selfinsert,
+ /* ^T */ z_selfinsert,
+ /* ^U */ z_vikillline,
+ /* ^V */ z_viquotedinsert,
+ /* ^W */ z_vibackwardkillword,
+ /* ^X */ z_selfinsert,
+ /* ^Y */ z_selfinsert,
+ /* ^Z */ z_selfinsert,
+ /* ^[ */ z_vicmdmode,
+ /* ^\ */ z_selfinsert,
+ /* ^] */ z_selfinsert,
+ /* ^^ */ z_selfinsert,
+ /* ^_ */ z_selfinsert,
+};
+
+/**/
+int vicmdbind[128] = {
+ /* ^@ */ z_undefinedkey,
+ /* ^A */ z_undefinedkey,
+ /* ^B */ z_undefinedkey,
+ /* ^C */ z_undefinedkey,
+ /* ^D */ z_listchoices,
+ /* ^E */ z_undefinedkey,
+ /* ^F */ z_undefinedkey,
+ /* ^G */ z_listexpand,
+ /* ^H */ z_vibackwardchar,
+ /* ^I */ z_undefinedkey,
+ /* ^J */ z_acceptline,
+ /* ^K */ z_undefinedkey,
+ /* ^L */ z_clearscreen,
+ /* ^M */ z_acceptline,
+ /* ^N */ z_downhistory,
+ /* ^O */ z_undefinedkey,
+ /* ^P */ z_uphistory,
+ /* ^Q */ z_undefinedkey,
+ /* ^R */ z_redisplay,
+ /* ^S */ z_undefinedkey,
+ /* ^T */ z_undefinedkey,
+ /* ^U */ z_undefinedkey,
+ /* ^V */ z_undefinedkey,
+ /* ^W */ z_undefinedkey,
+ /* ^X */ z_undefinedkey,
+ /* ^Y */ z_undefinedkey,
+ /* ^Z */ z_undefinedkey,
+ /* ^[ */ z_undefinedkey,
+ /* ^\ */ z_undefinedkey,
+ /* ^] */ z_undefinedkey,
+ /* ^^ */ z_undefinedkey,
+ /* ^_ */ z_undefinedkey,
+ /* */ z_viforwardchar,
+ /* ! */ z_undefinedkey,
+ /* " */ z_visetbuffer,
+ /* # */ z_poundinsert,
+ /* $ */ z_viendofline,
+ /* % */ z_vimatchbracket,
+ /* & */ z_undefinedkey,
+ /* ' */ z_vigotomarkline,
+ /* ( */ z_undefinedkey,
+ /* ) */ z_undefinedkey,
+ /* * */ z_undefinedkey,
+ /* + */ z_vidownlineorhistory,
+ /* , */ z_virevrepeatfind,
+ /* - */ z_viuplineorhistory,
+ /* . */ z_virepeatchange,
+ /* / */ z_vihistorysearchbackward,
+ /* 0 */ z_vidigitorbeginningofline,
+ /* 1 */ z_digitargument,
+ /* 2 */ z_digitargument,
+ /* 3 */ z_digitargument,
+ /* 4 */ z_digitargument,
+ /* 5 */ z_digitargument,
+ /* 6 */ z_digitargument,
+ /* 7 */ z_digitargument,
+ /* 8 */ z_digitargument,
+ /* 9 */ z_digitargument,
+ /* : */ z_undefinedkey,
+ /* ; */ z_virepeatfind,
+ /* < */ z_viunindent,
+ /* = */ z_listchoices,
+ /* > */ z_viindent,
+ /* ? */ z_vihistorysearchforward,
+ /* @ */ z_undefinedkey,
+ /* A */ z_viaddeol,
+ /* B */ z_vibackwardblankword,
+ /* C */ z_vichangeeol,
+ /* D */ z_vikilleol,
+ /* E */ z_viforwardblankwordend,
+ /* F */ z_vifindprevchar,
+ /* G */ z_vifetchhistory,
+ /* H */ z_undefinedkey,
+ /* I */ z_viinsertbol,
+ /* J */ z_vijoin,
+ /* K */ z_undefinedkey,
+ /* L */ z_undefinedkey,
+ /* M */ z_undefinedkey,
+ /* N */ z_virevrepeatsearch,
+ /* O */ z_viopenlineabove,
+ /* P */ z_viputbefore,
+ /* Q */ z_undefinedkey,
+ /* R */ z_vireplace,
+ /* S */ z_vichangewholeline,
+ /* T */ z_vifindprevcharskip,
+ /* U */ z_undefinedkey,
+ /* V */ z_undefinedkey,
+ /* W */ z_viforwardblankword,
+ /* X */ z_vibackwarddeletechar,
+ /* Y */ z_viyankwholeline,
+ /* Z */ z_undefinedkey,
+ /* [ */ z_undefinedkey,
+ /* \ */ z_undefinedkey,
+ /* ] */ z_undefinedkey,
+ /* ^ */ z_vifirstnonblank,
+ /* _ */ z_undefinedkey,
+ /* ` */ z_vigotomark,
+ /* a */ z_viaddnext,
+ /* b */ z_vibackwardword,
+ /* c */ z_vichange,
+ /* d */ z_videlete,
+ /* e */ z_viforwardwordend,
+ /* f */ z_vifindnextchar,
+ /* g */ z_undefinedkey,
+ /* h */ z_vibackwardchar,
+ /* i */ z_viinsert,
+ /* j */ z_downlineorhistory,
+ /* k */ z_uplineorhistory,
+ /* l */ z_viforwardchar,
+ /* m */ z_visetmark,
+ /* n */ z_virepeatsearch,
+ /* o */ z_viopenlinebelow,
+ /* p */ z_viputafter,
+ /* q */ z_undefinedkey,
+ /* r */ z_vireplacechars,
+ /* s */ z_visubstitute,
+ /* t */ z_vifindnextcharskip,
+ /* u */ z_viundochange,
+ /* v */ z_undefinedkey,
+ /* w */ z_viforwardword,
+ /* x */ z_videletechar,
+ /* y */ z_viyank,
+ /* z */ z_undefinedkey,
+ /* { */ z_undefinedkey,
+ /* | */ z_vigotocolumn,
+ /* } */ z_undefinedkey,
+ /* ~ */ z_viswapcase,
+ /* ^? */ z_vibackwardchar,
+};
diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c
new file mode 100644
index 000000000..76e421c1c
--- /dev/null
+++ b/Src/Zle/zle_hist.c
@@ -0,0 +1,1139 @@
+/*
+ * zle_hist.c - history editing
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_hist.pro"
+
+/* Are references to earlier history lines permitted? == 0 if *
+ * editing or reading a standalone line, such as in vared or select. */
+
+/**/
+int histallowed;
+
+/* Column position of vi ideal cursor. -1 if it is unknown -- most *
+ * movements and changes do this. */
+
+/**/
+int lastcol;
+
+/* current history line number */
+
+/**/
+int histline;
+
+/* the last line in the history (the current one), metafied */
+
+/**/
+char *curhistline;
+
+/**/
+void
+remember_edits(void)
+{
+ if (histline == curhist) {
+ zsfree(curhistline);
+ curhistline = metafy((char *) line, ll, META_DUP);
+ }
+ else {
+ Histent ent = gethistent(histline);
+
+ if (metadiffer(ent->zle_text ? ent->zle_text : ent->text,
+ (char *) line, ll)) {
+ zsfree(ent->zle_text);
+ ent->zle_text = metafy((char *) line, ll, META_DUP);
+ }
+ }
+}
+
+/**/
+void
+forget_edits(void)
+{
+ int i;
+
+ for (i = 0; i < histentct; i++) {
+ zsfree(histentarr[i].zle_text);
+ histentarr[i].zle_text = NULL;
+ }
+}
+
+/**/
+void
+uphistory(void)
+{
+ if (zmult < 0) {
+ zmult = -zmult;
+ downhistory();
+ zmult = -zmult;
+ } else if(!zle_goto_hist(histline - zmult) && isset(HISTBEEP))
+ feep();
+}
+
+/**/
+int
+upline(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -zmult;
+ n = downline();
+ zmult = -zmult;
+ return n;
+ }
+ if (lastcol == -1)
+ lastcol = cs - findbol();
+ cs = findbol();
+ while (n) {
+ if (!cs)
+ break;
+ cs--;
+ cs = findbol();
+ n--;
+ }
+ if (!n) {
+ int x = findeol();
+
+ if ((cs += lastcol) >= x) {
+ cs = x;
+ if (cs > findbol() && invicmdmode())
+ cs--;
+ }
+ }
+ return n;
+}
+
+/**/
+void
+uplineorhistory(void)
+{
+ int ocs = cs;
+ int n = upline();
+ if (n) {
+ int m = zmult;
+
+ cs = ocs;
+ if (virangeflag || !histallowed) {
+ feep();
+ return;
+ }
+ zmult = n;
+ uphistory();
+ zmult = m;
+ }
+}
+
+/**/
+void
+viuplineorhistory(void)
+{
+ int col = lastcol;
+ uplineorhistory();
+ lastcol = col;
+ vifirstnonblank();
+}
+
+
+/**/
+void
+uplineorsearch(void)
+{
+ int ocs = cs;
+ int n = upline();
+ if (n) {
+ int m = zmult;
+
+ cs = ocs;
+ if (virangeflag || !histallowed) {
+ feep();
+ return;
+ }
+ zmult = n;
+ historysearchbackward();
+ zmult = m;
+ }
+}
+
+/**/
+int
+downline(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -zmult;
+ n = upline();
+ zmult = -zmult;
+ return n;
+ }
+ if (lastcol == -1)
+ lastcol = cs - findbol();
+ while (n) {
+ int x = findeol();
+
+ if (x == ll)
+ break;
+ cs = x + 1;
+ n--;
+ }
+ if (!n) {
+ int x = findeol();
+
+ if ((cs += lastcol) >= x) {
+ cs = x;
+ if (cs > findbol() && invicmdmode())
+ cs--;
+ }
+ }
+ return n;
+}
+
+/**/
+void
+downlineorhistory(void)
+{
+ int ocs = cs;
+ int n = downline();
+ if (n) {
+ int m = zmult;
+
+ cs = ocs;
+ if (virangeflag || !histallowed) {
+ feep();
+ return;
+ }
+ zmult = n;
+ downhistory();
+ zmult = m;
+ }
+}
+
+/**/
+void
+vidownlineorhistory(void)
+{
+ int col = lastcol;
+ downlineorhistory();
+ lastcol = col;
+ vifirstnonblank();
+}
+
+/**/
+void
+downlineorsearch(void)
+{
+ int ocs = cs;
+ int n = downline();
+ if (n) {
+ int m = zmult;
+
+ cs = ocs;
+ if (virangeflag || !histallowed) {
+ feep();
+ return;
+ }
+ zmult = n;
+ historysearchforward();
+ zmult = m;
+ }
+}
+
+/**/
+void
+acceptlineanddownhistory(void)
+{
+ char *s;
+
+ if (!(s = zle_get_event(histline + 1))) {
+ feep();
+ return;
+ }
+ pushnode(bufstack, ztrdup(s));
+ done = 1;
+ stackhist = histline + 1;
+}
+
+/**/
+void
+downhistory(void)
+{
+ if (zmult < 0) {
+ zmult = -zmult;
+ uphistory();
+ zmult = -zmult;
+ } else if(!zle_goto_hist(histline + zmult) && isset(HISTBEEP))
+ feep();
+}
+
+/**/
+void
+historysearchbackward(void)
+{
+ int histpos, histmpos, hl = histline;
+ int n = zmult;
+ char *s;
+
+ if (!n)
+ return;
+ if (n < 0) {
+ zmult = -n;
+ historysearchforward();
+ zmult = n;
+ return;
+ }
+ for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]);
+ histpos++, histmpos++)
+ if(imeta(line[histpos]))
+ histmpos++;
+ for (;;) {
+ hl--;
+ if (!(s = zle_get_event(hl))) {
+ feep();
+ return;
+ }
+ if (metadiffer(s, (char *) line, histpos) < 0 &&
+ iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos]) &&
+ metadiffer(s, (char *) line, ll) && !--n)
+ break;
+ }
+ zle_goto_hist(hl);
+}
+
+/**/
+void
+historysearchforward(void)
+{
+ int histpos, histmpos, hl = histline;
+ int n = zmult;
+ char *s;
+
+ if (!n)
+ return;
+ if (n < 0) {
+ zmult = -n;
+ historysearchbackward();
+ zmult = n;
+ return;
+ }
+ for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]);
+ histpos++, histmpos++)
+ if(imeta(line[histpos]))
+ histmpos++;
+ for (;;) {
+ hl++;
+ if (!(s = zle_get_event(hl))) {
+ feep();
+ return;
+ }
+ if (metadiffer(s, (char *) line, histpos) < (histline == curhist) &&
+ (!s[histmpos] ||
+ iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos])) &&
+ metadiffer(s, (char *) line, ll) && !--n)
+ break;
+ }
+ zle_goto_hist(hl);
+}
+
+/**/
+void
+beginningofbufferorhistory(void)
+{
+ if (findbol())
+ cs = 0;
+ else
+ beginningofhistory();
+}
+
+/**/
+void
+beginningofhistory(void)
+{
+ if (!zle_goto_hist(firsthist()) && isset(HISTBEEP))
+ feep();
+}
+
+/**/
+void
+endofbufferorhistory(void)
+{
+ if (findeol() != ll)
+ cs = ll;
+ else
+ endofhistory();
+}
+
+/**/
+void
+endofhistory(void)
+{
+ zle_goto_hist(curhist);
+}
+
+/**/
+void
+insertlastword(void)
+{
+ int n;
+ char *s, *t;
+ Histent he;
+
+/* multiple calls will now search back through the history, pem */
+ static char *lastinsert;
+ static int lasthist, lastpos;
+ int evhist = curhist - 1, save;
+
+ if (lastinsert) {
+ int lastlen = ztrlen(lastinsert);
+ int pos = cs;
+
+ if (lastpos <= pos &&
+ lastlen == pos - lastpos &&
+ memcmp(lastinsert, (char *)&line[lastpos], lastlen) == 0) {
+ evhist = --lasthist;
+ cs = lastpos;
+ foredel(pos - cs);
+ }
+ zsfree(lastinsert);
+ lastinsert = NULL;
+ }
+ if (!(he = quietgethist(evhist)) || !he->nwords) {
+ feep();
+ return;
+ }
+ if (zmult > 0) {
+ n = he->nwords - (zmult - 1);
+ } else {
+ n = 1 - zmult;
+ }
+ if (n < 1 || n > he->nwords) {
+ feep();
+ return;
+ }
+ s = he->text + he->words[2*n-2];
+ t = he->text + he->words[2*n-1];
+ save = *t;
+ *t = '\0'; /* ignore trailing whitespace */
+
+ lasthist = evhist;
+ lastpos = cs;
+ lastinsert = ztrdup(s);
+ n = zmult;
+ zmult = 1;
+ doinsert(s);
+ zmult = n;
+ *t = save;
+}
+
+/**/
+char *
+qgetevent(int ev)
+{
+ return ((ev == curhist) ? curhistline : quietgetevent(ev));
+}
+
+/**/
+char *
+zle_get_event(int ev)
+{
+ Histent ent;
+
+ if (ev == curhist)
+ return curhistline;
+ if (! (ent = quietgethist(ev)))
+ return NULL;
+ if (ent->zle_text)
+ return ent->zle_text;
+ return ent->text;
+}
+
+/**/
+static int
+zle_goto_hist(int ev)
+{
+ char *t;
+
+ remember_edits();
+ if(!(t = zle_get_event(ev)))
+ return 0;
+ mkundoent();
+ histline = ev;
+ setline(t);
+ setlastline();
+ return 1;
+}
+
+/**/
+void
+pushline(void)
+{
+ int n = zmult;
+
+ if (n < 0)
+ return;
+ pushnode(bufstack, metafy((char *) line, ll, META_DUP));
+ while (--n)
+ pushnode(bufstack, ztrdup(""));
+ stackcs = cs;
+ *line = '\0';
+ ll = cs = 0;
+}
+
+/**/
+void
+pushlineoredit(void)
+{
+ int ics;
+ unsigned char *s;
+ char *hline = hgetline();
+
+ if (zmult < 0)
+ return;
+ if (hline && *hline) {
+ ics = ztrlen(hline);
+ sizeline(ics + ll + 1);
+ for (s = line + ll; --s >= line; *(s + ics) = *s);
+ for (s = line; *hline; hline++)
+ *s++ = *hline == Meta ? *++hline ^ 32 : *hline;
+ ll += ics;
+ cs += ics;
+ }
+ pushline();
+ if (!isfirstln) {
+ errflag = done = 1;
+ }
+}
+
+/**/
+void
+pushinput(void)
+{
+ int i;
+
+ if (zmult < 0)
+ return;
+ zmult += i = !isfirstln;
+ pushlineoredit();
+ zmult -= i;
+}
+
+/**/
+void
+getline(void)
+{
+ char *s = (char *)getlinknode(bufstack);
+
+ if (!s)
+ feep();
+ else {
+ int cc;
+
+ unmetafy(s, &cc);
+ spaceinline(cc);
+ memcpy((char *)line + cs, s, cc);
+ cs += cc;
+ free(s);
+ }
+}
+
+/**/
+void
+historyincrementalsearchbackward(void)
+{
+ doisearch(-1);
+}
+
+/**/
+void
+historyincrementalsearchforward(void)
+{
+ doisearch(1);
+}
+
+static struct isrch_spot {
+ int hl; /* This spot's histline */
+ unsigned short pos; /* The search position in our metafied str */
+ unsigned short cs; /* The visible search position to the user */
+ unsigned short len; /* The search string's length */
+ unsigned short flags; /* This spot's flags */
+#define ISS_FAILING 1
+#define ISS_FORWARD 2
+} *isrch_spots;
+
+static int max_spot = 0;
+
+#ifdef MODULE
+
+/**/
+void
+free_isrch_spots(void)
+{
+ zfree(isrch_spots, max_spot * sizeof(*isrch_spots));
+}
+
+#endif /* MODULE */
+
+/**/
+static void
+set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch)
+{
+ if (num >= max_spot) {
+ if (!isrch_spots) {
+ isrch_spots = (struct isrch_spot*)
+ zalloc((max_spot = 64) * sizeof *isrch_spots);
+ } else {
+ isrch_spots = (struct isrch_spot*)realloc((char*)isrch_spots,
+ (max_spot += 64) * sizeof *isrch_spots);
+ }
+ }
+
+ isrch_spots[num].hl = hl;
+ isrch_spots[num].pos = (unsigned short)pos;
+ isrch_spots[num].cs = (unsigned short)cs;
+ isrch_spots[num].len = (unsigned short)len;
+ isrch_spots[num].flags = (dir > 0? ISS_FORWARD : 0)
+ + (nomatch? ISS_FAILING : 0);
+}
+
+/**/
+static void
+get_isrch_spot(int num, int *hlp, int *posp, int *csp, int *lenp, int *dirp, int *nomatch)
+{
+ *hlp = isrch_spots[num].hl;
+ *posp = (int)isrch_spots[num].pos;
+ *csp = (int)isrch_spots[num].cs;
+ *lenp = (int)isrch_spots[num].len;
+ *dirp = (isrch_spots[num].flags & ISS_FORWARD)? 1 : -1;
+ *nomatch = (isrch_spots[num].flags & ISS_FAILING);
+}
+
+#define ISEARCH_PROMPT "failing XXX-i-search: "
+#define NORM_PROMPT_POS 8
+#define FIRST_SEARCH_CHAR (NORM_PROMPT_POS + 14)
+
+/**/
+static void
+doisearch(int dir)
+{
+ char *s, *ibuf = halloc(80), *sbuf = ibuf + FIRST_SEARCH_CHAR;
+ int sbptr = 0, top_spot = 0, pos, sibuf = 80;
+ int nomatch = 0, skip_line = 0, skip_pos = 0;
+ int odir = dir, sens = zmult == 1 ? 3 : 1;
+ int hl = histline;
+ Thingy cmd;
+ char *okeymap = curkeymapname;
+ static char *previous_search = NULL;
+ static int previous_search_len = 0;
+
+ strcpy(ibuf, ISEARCH_PROMPT);
+ memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
+ remember_edits();
+ s = zle_get_event(hl);
+ selectkeymap("main", 1);
+ pos = metalen(s, cs);
+ for (;;) {
+ /* Remember the current values in case search fails (doesn't push). */
+ set_isrch_spot(top_spot, hl, pos, cs, sbptr, dir, nomatch);
+ if (sbptr == 1 && sbuf[0] == '^') {
+ cs = 0;
+ nomatch = 0;
+ statusline = ibuf + NORM_PROMPT_POS;
+ } else if (sbptr > 0) {
+ char *last_line = s;
+
+ for (;;) {
+ char *t;
+
+ if (skip_pos) {
+ if (dir < 0) {
+ if (pos == 0)
+ skip_line = 1;
+ else
+ pos -= 1 + (pos != 1 && s[pos-2] == Meta);
+ } else if (sbuf[0] != '^') {
+ if (pos >= strlen(s+1))
+ skip_line = 1;
+ else
+ pos += 1 + (s[pos] == Meta);
+ } else
+ skip_line = 1;
+ skip_pos = 0;
+ }
+ if (!skip_line && ((sbuf[0] == '^') ?
+ (t = metadiffer(s, sbuf + 1, sbptr - 1) < sens ? s : NULL) :
+ (t = hstrnstr(s, pos, sbuf, sbptr, dir, sens)))) {
+ zle_goto_hist(hl);
+ pos = t - s;
+ cs = ztrsub(t, s) + (dir == 1? sbptr - (sbuf[0]=='^') : 0);
+ nomatch = 0;
+ statusline = ibuf + NORM_PROMPT_POS;
+ break;
+ }
+ hl += dir;
+ if (!(s = zle_get_event(hl))) {
+ if (sbptr == (int)isrch_spots[top_spot-1].len
+ && (isrch_spots[top_spot-1].flags & ISS_FAILING))
+ top_spot--;
+ get_isrch_spot(top_spot, &hl, &pos, &cs, &sbptr,
+ &dir, &nomatch);
+ if (!nomatch) {
+ feep();
+ nomatch = 1;
+ }
+ s = last_line;
+ skip_line = 0;
+ statusline = ibuf;
+ break;
+ }
+ pos = dir == 1? 0 : strlen(s);
+ skip_line = !strcmp(last_line, s);
+ }
+ } else {
+ top_spot = 0;
+ nomatch = 0;
+ statusline = ibuf + NORM_PROMPT_POS;
+ }
+ sbuf[sbptr] = '_';
+ statusll = sbuf - statusline + sbptr + 1;
+ ref:
+ refresh();
+ if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+ int i;
+ get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch);
+ s = zle_get_event(hl);
+ zle_goto_hist(hl);
+ cs = i;
+ break;
+ }
+ if(cmd == Th(z_clearscreen)) {
+ clearscreen();
+ goto ref;
+ } else if(cmd == Th(z_redisplay)) {
+ redisplay();
+ goto ref;
+ } else if(cmd == Th(z_vicmdmode)) {
+ if(selectkeymap(invicmdmode() ? "main" : "vicmd", 0))
+ feep();
+ goto ref;
+ } else if(cmd == Th(z_vibackwarddeletechar) ||
+ cmd == Th(z_backwarddeletechar)) {
+ if (top_spot)
+ get_isrch_spot(--top_spot, &hl, &pos, &cs, &sbptr,
+ &dir, &nomatch);
+ else
+ feep();
+ if (nomatch) {
+ statusline = ibuf;
+ skip_pos = 1;
+ }
+ s = zle_get_event(hl);
+ if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) {
+ int i = cs;
+ zle_goto_hist(hl);
+ cs = i;
+ }
+ memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
+ continue;
+ } else if(cmd == Th(z_acceptandhold)) {
+ acceptandhold();
+ break;
+ } else if(cmd == Th(z_acceptandinfernexthistory)) {
+ acceptandinfernexthistory();
+ break;
+ } else if(cmd == Th(z_acceptlineanddownhistory)) {
+ acceptlineanddownhistory();
+ break;
+ } else if(cmd == Th(z_acceptline)) {
+ acceptline();
+ break;
+ } else if(cmd == Th(z_historyincrementalsearchbackward)) {
+ set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+ if (dir != -1)
+ dir = -1;
+ else
+ skip_pos = 1;
+ goto rpt;
+ } else if(cmd == Th(z_historyincrementalsearchforward)) {
+ set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+ if (dir != 1)
+ dir = 1;
+ else
+ skip_pos = 1;
+ goto rpt;
+ } else if(cmd == Th(z_virevrepeatsearch)) {
+ set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+ dir = -odir;
+ skip_pos = 1;
+ goto rpt;
+ } else if(cmd == Th(z_virepeatsearch)) {
+ set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+ dir = odir;
+ skip_pos = 1;
+ rpt:
+ if (!sbptr && previous_search_len) {
+ if (previous_search_len > sibuf - FIRST_SEARCH_CHAR - 2) {
+ ibuf = hrealloc(ibuf, sibuf, sibuf + previous_search_len);
+ sbuf = ibuf + FIRST_SEARCH_CHAR;
+ sibuf += previous_search_len;
+ }
+ memcpy(sbuf, previous_search, sbptr = previous_search_len);
+ }
+ memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3);
+ continue;
+ } else if(cmd == Th(z_viquotedinsert) ||
+ cmd == Th(z_quotedinsert)) {
+ if(cmd == Th(z_viquotedinsert)) {
+ sbuf[sbptr] = '^';
+ refresh();
+ }
+ if ((c = getkey(0)) == EOF)
+ feep();
+ else
+ goto ins;
+ } else {
+ if(cmd == Th(z_selfinsertunmeta)) {
+ c &= 0x7f;
+ if(c == '\r')
+ c = '\n';
+ } else if (cmd == Th(z_magicspace))
+ c = ' ';
+ else if (cmd != Th(z_selfinsert)) {
+ ungetkeycmd();
+ if (cmd == Th(z_sendbreak))
+ sbptr = 0;
+ break;
+ }
+ ins:
+ if (sbptr == PATH_MAX) {
+ feep();
+ continue;
+ }
+ set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch);
+ if (sbptr == sibuf - FIRST_SEARCH_CHAR - 2) {
+ ibuf = hrealloc(ibuf, sibuf, sibuf * 2);
+ sbuf = ibuf + FIRST_SEARCH_CHAR;
+ sibuf *= 2;
+ }
+ sbuf[sbptr++] = c;
+ }
+ handlefeep();
+ }
+ if (sbptr) {
+ zfree(previous_search, previous_search_len);
+ previous_search = zalloc(sbptr);
+ memcpy(previous_search, sbuf, previous_search_len = sbptr);
+ }
+ statusline = NULL;
+ selectkeymap(okeymap, 1);
+}
+
+/**/
+void
+acceptandinfernexthistory(void)
+{
+ int t0;
+ char *s;
+
+ done = 1;
+ for (t0 = histline - 2;; t0--) {
+ if (!(s = qgetevent(t0)))
+ return;
+ if (!metadiffer(s, (char *) line, ll))
+ break;
+ }
+ if (!(s = qgetevent(t0 + 1)))
+ return;
+ pushnode(bufstack, ztrdup(s));
+ stackhist = t0 + 1;
+}
+
+/**/
+void
+infernexthistory(void)
+{
+ int t0;
+ char *s;
+
+ for (t0 = histline - 2;; t0--) {
+ if (!(s = qgetevent(t0))) {
+ feep();
+ return;
+ }
+ if (! metadiffer(s, (char *) line, ll))
+ break;
+ }
+ if (!(s = qgetevent(t0 + 1))) {
+ feep();
+ return;
+ }
+ zle_goto_hist(t0 + 1);
+}
+
+/**/
+void
+vifetchhistory(void)
+{
+ if (zmult < 0)
+ return;
+ if (histline == curhist) {
+ if (!(zmod.flags & MOD_MULT)) {
+ cs = ll;
+ cs = findbol();
+ return;
+ }
+ }
+ if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist) &&
+ isset(HISTBEEP))
+ feep();
+}
+
+/* the last vi search */
+
+static char *visrchstr;
+static int visrchsense;
+
+/**/
+static int
+getvisrchstr(void)
+{
+ char *sbuf = halloc(80);
+ int sptr = 1, ret = 0, ssbuf = 80;
+ Thingy cmd;
+ char *okeymap = curkeymapname;
+
+ if (visrchstr) {
+ zsfree(visrchstr);
+ visrchstr = NULL;
+ }
+ statusline = sbuf;
+ sbuf[0] = (visrchsense == -1) ? '?' : '/';
+ selectkeymap("main", 1);
+ while (sptr) {
+ sbuf[sptr] = '_';
+ statusll = sptr + 1;
+ refresh();
+ if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+ ret = 0;
+ break;
+ }
+ if(cmd == Th(z_magicspace)) {
+ c = ' ';
+ cmd = Th(z_selfinsert);
+ }
+ if(cmd == Th(z_redisplay)) {
+ redisplay();
+ } else if(cmd == Th(z_clearscreen)) {
+ clearscreen();
+ } else if(cmd == Th(z_acceptline) ||
+ cmd == Th(z_vicmdmode)) {
+ sbuf[sptr] = 0;
+ visrchstr = metafy(sbuf + 1, sptr - 1, META_DUP);
+ ret = 1;
+ sptr = 0;
+ } else if(cmd == Th(z_backwarddeletechar) ||
+ cmd == Th(z_vibackwarddeletechar)) {
+ sptr--;
+ } else if(cmd == Th(z_backwardkillword) ||
+ cmd == Th(z_vibackwardkillword)) {
+ while(sptr != 1 && iblank(sbuf[sptr - 1]))
+ sptr--;
+ if(iident(sbuf[sptr - 1]))
+ while(sptr != 1 && iident(sbuf[sptr - 1]))
+ sptr--;
+ else
+ while(sptr != 1 && !iident(sbuf[sptr - 1]) && !iblank(sbuf[sptr - 1]))
+ sptr--;
+ } else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) {
+ if(cmd == Th(z_viquotedinsert)) {
+ sbuf[sptr] = '^';
+ refresh();
+ }
+ if ((c = getkey(0)) == EOF)
+ feep();
+ else
+ goto ins;
+ } else if(cmd == Th(z_selfinsertunmeta) || cmd == Th(z_selfinsert)) {
+ if(cmd == Th(z_selfinsertunmeta)) {
+ c &= 0x7f;
+ if(c == '\r')
+ c = '\n';
+ }
+ ins:
+ if(sptr == ssbuf - 1) {
+ char *newbuf = halloc(ssbuf *= 2);
+ strcpy(newbuf, sbuf);
+ statusline = sbuf = newbuf;
+ }
+ sbuf[sptr++] = c;
+ } else {
+ feep();
+ }
+ handlefeep();
+ }
+ statusline = NULL;
+ selectkeymap(okeymap, 1);
+ return ret;
+}
+
+/**/
+void
+vihistorysearchforward(void)
+{
+ visrchsense = 1;
+ if (getvisrchstr())
+ virepeatsearch();
+}
+
+/**/
+void
+vihistorysearchbackward(void)
+{
+ visrchsense = -1;
+ if (getvisrchstr())
+ virepeatsearch();
+}
+
+/**/
+void
+virepeatsearch(void)
+{
+ int hl = histline, t0;
+ int n = zmult;
+ char *s;
+
+ if (!visrchstr) {
+ feep();
+ return;
+ }
+ if (!n)
+ return;
+ if (n < 0) {
+ n = -n;
+ visrchsense = -visrchsense;
+ }
+ t0 = strlen(visrchstr);
+ for (;;) {
+ hl += visrchsense;
+ if (!(s = zle_get_event(hl))) {
+ feep();
+ return;
+ }
+ if (!metadiffer(s, (char *) line, ll))
+ continue;
+ if (*visrchstr == '^') {
+ if (strncmp(s, visrchstr + 1, t0 - 1) != 0)
+ continue;
+ } else if (!hstrnstr(s, 0, visrchstr, t0, 1, 1))
+ continue;
+ if (--n <= 0)
+ break;
+ }
+ zle_goto_hist(hl);
+}
+
+/**/
+void
+virevrepeatsearch(void)
+{
+ visrchsense = -visrchsense;
+ virepeatsearch();
+ visrchsense = -visrchsense;
+}
+
+/* Extra function added by A.R. Iano-Fletcher. */
+/*The extern variable "cs" is the position of the cursor. */
+/* history-beginning-search-backward */
+
+/**/
+void
+historybeginningsearchbackward(void)
+{
+ int cpos = cs; /* save cursor position */
+ int hl = histline;
+ int n = zmult;
+ char *s;
+
+ if (!n)
+ return;
+ if (n < 0) {
+ zmult = -n;
+ historybeginningsearchforward();
+ zmult = n;
+ return;
+ }
+ for (;;) {
+ hl--;
+ if (!(s = zle_get_event(hl))) {
+ feep();
+ return;
+ }
+ if (metadiffer(s, (char *)line, cs) < 0 &&
+ metadiffer(s, (char *)line, ll))
+ if (--n <= 0)
+ break;
+ }
+
+ zle_goto_hist(hl);
+ cs = cpos;
+}
+
+/* Extra function added by A.R. Iano-Fletcher. */
+
+/* history-beginning-search-forward */
+/**/
+void
+historybeginningsearchforward(void)
+{
+ int cpos = cs; /* save cursor position */
+ int hl = histline;
+ int n = zmult;
+ char *s;
+
+ if (!n)
+ return;
+ if (n < 0) {
+ zmult = -n;
+ historybeginningsearchbackward();
+ zmult = n;
+ return;
+ }
+ for (;;) {
+ hl++;
+ if (!(s = zle_get_event(hl))) {
+ feep();
+ return;
+ }
+ if (metadiffer(s, (char *)line, cs) < (hl == curhist) &&
+ metadiffer(s, (char *)line, ll))
+ if (--n <= 0)
+ break;
+ }
+
+ zle_goto_hist(hl);
+ cs = cpos;
+}
diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c
new file mode 100644
index 000000000..7de96bd03
--- /dev/null
+++ b/Src/Zle/zle_keymap.c
@@ -0,0 +1,1238 @@
+/*
+ * zle_keymap.c - keymaps and key bindings
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+
+/*
+ * Keymap structures:
+ *
+ * There is a hash table of keymap names. Each name just points to a keymap.
+ * More than one name may point to the same keymap.
+ *
+ * Each keymap consists of a table of bindings for each character, and a
+ * hash table of multi-character key bindings. The keymap has no individual
+ * name, but maintains a reference count.
+ *
+ * In a keymap's table of initial bindings, each character is either bound to
+ * a thingy, or is a prefix (in which case NULL is stored). Those prefix
+ * entries are matched by more complex entries in the multi-character
+ * binding hash table. Each entry in this hash table (which is indexed by
+ * metafied key sequence) either has a normal thingy binding or a string to
+ * send (in which case the NULL thingy is used). Each entry also has a count
+ * of other entries for which it is a prefix.
+ */
+
+typedef struct keymapname *KeymapName;
+typedef struct key *Key;
+
+struct keymapname {
+ HashNode next; /* next in the hash chain */
+ char *nam; /* name of the keymap */
+ int flags; /* various flags (see below) */
+ Keymap keymap; /* the keymap itsef */
+};
+
+#define KMN_IMMORTAL (1<<1)
+
+struct keymap {
+ Thingy first[256]; /* base binding of each character */
+ HashTable multi; /* multi-character bindings */
+ int flags; /* various flags (see below) */
+ int rc; /* reference count */
+};
+
+#define KM_IMMUTABLE (1<<1)
+
+struct key {
+ HashNode next; /* next in hash chain */
+ char *nam; /* key sequence (metafied) */
+ Thingy bind; /* binding of this key sequence */
+ char *str; /* string for send-string (metafied) */
+ int prefixct; /* number of sequences for which this is a prefix */
+};
+
+/* This structure is used when listing keymaps. */
+
+struct bindstate {
+ int flags;
+ char *kmname;
+ char *firstseq;
+ char *lastseq;
+ Thingy bind;
+ char *str;
+};
+
+#define BS_LIST (1<<0)
+#define BS_ALL (1<<1)
+
+/* local functions */
+
+#include "zle_keymap.pro"
+
+/* currently selected keymap, and its name */
+
+/**/
+Keymap curkeymap;
+/**/
+char *curkeymapname;
+
+/* the hash table of keymap names */
+
+static HashTable keymapnamtab;
+
+/* key sequence reading data */
+
+static char *keybuf;
+static int keybuflen, keybufsz = 20;
+
+/* last command executed with execute-named-command */
+
+static Thingy lastnamed;
+
+/**********************************/
+/* hashtable management functions */
+/**********************************/
+
+/**/
+static void
+createkeymapnamtab(void)
+{
+ keymapnamtab = newhashtable(7, "keymapnamtab", NULL);
+
+ keymapnamtab->hash = hasher;
+ keymapnamtab->emptytable = emptyhashtable;
+ keymapnamtab->filltable = NULL;
+ keymapnamtab->addnode = addhashnode;
+ keymapnamtab->getnode = gethashnode2;
+ keymapnamtab->getnode2 = gethashnode2;
+ keymapnamtab->removenode = removehashnode;
+ keymapnamtab->disablenode = NULL;
+ keymapnamtab->enablenode = NULL;
+ keymapnamtab->freenode = freekeymapnamnode;
+ keymapnamtab->printnode = NULL;
+}
+
+/**/
+static KeymapName
+makekeymapnamnode(Keymap keymap)
+{
+ KeymapName kmn = (KeymapName) zcalloc(sizeof(*kmn));
+
+ kmn->keymap = keymap;
+ return kmn;
+}
+
+/**/
+static void
+freekeymapnamnode(HashNode hn)
+{
+ KeymapName kmn = (KeymapName) hn;
+
+ zsfree(kmn->nam);
+ if(!--kmn->keymap->rc)
+ deletekeymap(kmn->keymap);
+ zfree(kmn, sizeof(*kmn));
+}
+
+/**/
+static HashTable
+newkeytab(char *kmname)
+{
+ HashTable ht = newhashtable(19,
+ kmname ? dyncat("keytab:", kmname) : "keytab:", NULL);
+
+ ht->hash = hasher;
+ ht->emptytable = emptyhashtable;
+ ht->filltable = NULL;
+ ht->addnode = addhashnode;
+ ht->getnode = gethashnode2;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = NULL;
+ ht->enablenode = NULL;
+ ht->freenode = freekeynode;
+ ht->printnode = NULL;
+
+ return ht;
+}
+
+/**/
+static Key
+makekeynode(Thingy t, char *str)
+{
+ Key k = (Key) zcalloc(sizeof(*k));
+
+ k->bind = t;
+ k->str = str;
+ return k;
+}
+
+/**/
+static void
+freekeynode(HashNode hn)
+{
+ Key k = (Key) hn;
+
+ zsfree(k->nam);
+ unrefthingy(k->bind);
+ zsfree(k->str);
+ zfree(k, sizeof(*k));
+}
+
+/**************************/
+/* main keymap operations */
+/**************************/
+
+static HashTable copyto;
+
+/**/
+static Keymap
+newkeymap(Keymap tocopy, char *kmname)
+{
+ Keymap km = zcalloc(sizeof(*km));
+ int i;
+
+ km->rc = 0;
+ km->multi = newkeytab(kmname);
+ if(tocopy) {
+ for(i = 256; i--; )
+ km->first[i] = refthingy(tocopy->first[i]);
+ copyto = km->multi;
+ scanhashtable(tocopy->multi, 0, 0, 0, scancopykeys, 0);
+ } else {
+ for(i = 256; i--; )
+ km->first[i] = refthingy(t_undefinedkey);
+ }
+ return km;
+}
+
+/**/
+static void
+scancopykeys(HashNode hn, int flags)
+{
+ Key k = (Key) hn;
+ Key kn = zalloc(sizeof(*k));
+
+ memcpy(kn, k, sizeof(*k));
+ refthingy(kn->bind);
+ kn->str = ztrdup(k->str);
+ copyto->addnode(copyto, ztrdup(k->nam), kn);
+}
+
+/**/
+static void
+deletekeymap(Keymap km)
+{
+ int i;
+
+ deletehashtable(km->multi);
+ for(i = 256; i--; )
+ unrefthingy(km->first[i]);
+ zfree(km, sizeof(*km));
+}
+
+static Keymap skm_km;
+static int skm_last;
+static KeyScanFunc skm_func;
+static void *skm_magic;
+
+/**/
+void
+scankeymap(Keymap km, int sort, KeyScanFunc func, void *magic)
+{
+ char m[3];
+
+ skm_km = km;
+ skm_last = sort ? -1 : 255;
+ skm_func = func;
+ skm_magic = magic;
+ scanhashtable(km->multi, sort, 0, 0, scankeys, 0);
+ if(!sort)
+ skm_last = -1;
+ while(skm_last < 255) {
+ skm_last++;
+ if(km->first[skm_last] && km->first[skm_last] != t_undefinedkey) {
+ m[0] = skm_last;
+ metafy(m, 1, META_NOALLOC);
+ func(m, km->first[skm_last], NULL, magic);
+ }
+ }
+}
+
+/**/
+static void
+scankeys(HashNode hn, int flags)
+{
+ Key k = (Key) hn;
+ int f = k->nam[0] == Meta ? STOUC(k->nam[1])^32 : STOUC(k->nam[0]);
+ char m[3];
+
+ while(skm_last < f) {
+ skm_last++;
+ if(skm_km->first[skm_last] &&
+ skm_km->first[skm_last] != t_undefinedkey) {
+ m[0] = skm_last;
+ metafy(m, 1, META_NOALLOC);
+ skm_func(m, skm_km->first[skm_last], NULL, skm_magic);
+ }
+ }
+ skm_func(k->nam, k->bind, k->str, skm_magic);
+}
+
+/**************************/
+/* keymap name operations */
+/**************************/
+
+/**/
+Keymap
+openkeymap(char *name)
+{
+ KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
+ return n ? n->keymap : NULL;
+}
+
+/**/
+static int
+unlinkkeymap(char *name)
+{
+ KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
+ if(!n)
+ return 2;
+ if(n->flags & KMN_IMMORTAL)
+ return 1;
+ keymapnamtab->freenode(keymapnamtab->removenode(keymapnamtab, name));
+ return 0;
+}
+
+/**/
+static int
+linkkeymap(Keymap km, char *name)
+{
+ KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
+ if(n) {
+ if(n->flags & KMN_IMMORTAL)
+ return 1;
+ if(n->keymap == km)
+ return 0;
+ if(!--n->keymap->rc)
+ deletekeymap(n->keymap);
+ n->keymap = km;
+ } else
+ keymapnamtab->addnode(keymapnamtab, ztrdup(name),
+ makekeymapnamnode(km));
+ km->rc++;
+ return 0;
+}
+
+/* Select a keymap as the current ZLE keymap. Can optionally fall back *
+ * on the guaranteed safe keymap if it fails. */
+
+/**/
+int
+selectkeymap(char *name, int fb)
+{
+ Keymap km = openkeymap(name);
+
+ if(!km) {
+ char *nm = niceztrdup(name);
+ char *msg = tricat("No such keymap `", nm, "'");
+
+ zsfree(nm);
+ showmsg(msg);
+ zsfree(msg);
+ if(!fb)
+ return 1;
+ km = openkeymap(name = ".safe");
+ }
+ curkeymapname = name;
+ curkeymap = km;
+ return 0;
+}
+
+/* Reopen the currently selected keymap, in case it got deleted. This *
+ * should be called after doing anything that might have run an *
+ * arbitrary user-specified command. */
+
+/**/
+void
+reselectkeymap(void)
+{
+ selectkeymap(curkeymapname, 1);
+}
+
+/******************************/
+/* operations on key bindings */
+/******************************/
+
+/* Add/delete/change a keybinding in some keymap. km is the keymap to be *
+ * altered. seq is the metafied key sequence whose binding is to change. *
+ * bind is the thingy to which the key sequence is to be bound. For *
+ * send-string, bind is NULL and str is the metafied key sequence to push *
+ * back onto the input. */
+
+/**/
+int
+bindkey(Keymap km, char *seq, Thingy bind, char *str)
+{
+ Key k;
+ int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]);
+ char *buf, *ptr;
+
+ if(km->flags & KM_IMMUTABLE)
+ return 1;
+ if(!*seq)
+ return 2;
+ if(!bind || ztrlen(seq) > 1) {
+ /* key needs to become a prefix if isn't one already */
+ if(km->first[f]) {
+ char fs[3];
+ fs[0] = f;
+ metafy(fs, 1, META_NOALLOC);
+ km->multi->addnode(km->multi, ztrdup(fs),
+ makekeynode(km->first[f], NULL));
+ km->first[f] = NULL;
+ }
+ k = (Key) km->multi->getnode(km->multi, seq);
+ } else {
+ /* If the sequence is a prefix entry only due to being *
+ * a send-string binding, we can remove that entry. */
+ if(!km->first[f]) {
+ k = (Key) km->multi->getnode(km->multi, seq);
+ if(!k->prefixct)
+ km->multi->freenode(km->multi->removenode(km->multi, seq));
+ else
+ goto domulti;
+ } else
+ unrefthingy(km->first[f]);
+ /* Just replace the single-character binding. */
+ km->first[f] = bind;
+ return 0;
+ }
+ domulti:
+ buf = ztrdup(seq);
+ ptr = strchr(buf, 0);
+ if(bind == t_undefinedkey) {
+ if(k) {
+ zsfree(k->str);
+ unrefthingy(k->bind);
+ k->bind = t_undefinedkey;
+ k->str = NULL;
+ while(!k->prefixct && k->bind == t_undefinedkey) {
+ km->multi->freenode(km->multi->removenode(km->multi, buf));
+ *--ptr = 0;
+ if(ptr[-1] == Meta)
+ *--ptr = 0;
+ k = (Key) km->multi->getnode(km->multi, buf);
+ k->prefixct--;
+ if(!k->prefixct && k->bind &&
+ (!buf[1] || (buf[0] == Meta && !buf[2]))) {
+ km->first[f] = refthingy(k->bind);
+ km->multi->freenode(km->multi->removenode(km->multi, buf));
+ break;
+ }
+ }
+ }
+ } else {
+ if(!k) {
+ int added;
+
+ km->multi->addnode(km->multi, ztrdup(buf), makekeynode(bind, ztrdup(str)));
+ do {
+ *--ptr = 0;
+ if(ptr > buf && ptr[-1] == Meta)
+ *--ptr = 0;
+ k = (Key) km->multi->getnode(km->multi, buf);
+ if((added = !k))
+ km->multi->addnode(km->multi, ztrdup(buf),
+ k = makekeynode(refthingy(t_undefinedkey), NULL));
+ k->prefixct++;
+ } while(added);
+ } else {
+ unrefthingy(k->bind);
+ zsfree(k->str);
+ k->bind = bind;
+ k->str = bind ? NULL : ztrdup(str);
+ }
+ }
+ free(buf);
+ return 0;
+}
+
+/* Look up a key binding. The binding is returned. In the case of a *
+ * send-string, NULL is returned and *strp is modified to point to the *
+ * metafied string of characters to be pushed back. */
+
+/**/
+Thingy
+keybind(Keymap km, char *seq, char **strp)
+{
+ Key k;
+
+ if(ztrlen(seq) == 1) {
+ int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]);
+ Thingy bind = km->first[f];
+
+ if(bind)
+ return bind;
+ }
+ k = (Key) km->multi->getnode(km->multi, seq);
+ if(!k)
+ return t_undefinedkey;
+ *strp = k->str;
+ return k->bind;
+}
+
+/* Check whether a key sequence is a prefix of a longer bound sequence. *
+ * One oddity: if *nothing* in the keymap is bound, this returns true *
+ * for the empty sequence, even though this is not strictly accurate. */
+
+/**/
+static int
+keyisprefix(Keymap km, char *seq)
+{
+ Key k;
+
+ if(!*seq)
+ return 1;
+ if(ztrlen(seq) == 1) {
+ int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]);
+
+ if(km->first[f])
+ return 0;
+ }
+ k = (Key) km->multi->getnode(km->multi, seq);
+ return k && k->prefixct;
+}
+
+/*******************/
+/* bindkey builtin */
+/*******************/
+
+/*
+ * THE BINDKEY BUILTIN
+ *
+ * Keymaps can be specified to bindkey in the following ways:
+ *
+ * -e select "emacs", also link it to "main"
+ * -v select "viins", also link it to "main"
+ * -a select "vicmd"
+ * -M first argument gives map name
+ * defaults to "main"
+ *
+ * These operations cannot have a keymap selected in the normal way:
+ *
+ * -l list all the keymap names
+ * -d delete all keymaps and reset to the default state (no arguments)
+ * -D delete named keymaps
+ * -A link the two named keymaps (2 arguments)
+ * -N create new empty keymap (1 argument)
+ * -N create new keymap, copying the second named keymap (2 arguments)
+ *
+ * Other operations:
+ *
+ * -m add the meta bindings to the selected keymap (no arguments)
+ * -r unbind each named string in the selected keymap
+ * -s bind send-strings in the selected keymap (2+ arguments)
+ * bind commands in the selected keymap (2+ arguments)
+ * display one binding in the selected keymap (1 argument)
+ * display the entire selected keymap (no arguments)
+ *
+ * There is an exception that the entire keymap display will not be performed
+ * if the -e or -v options were used.
+ *
+ * Other options:
+ *
+ * -L do listings in the form of bindkey commands
+ * -R for the binding operations, accept ranges instead of sequences
+ */
+
+/**/
+int
+bin_bindkey(char *name, char **argv, char *ops, int func)
+{
+ static struct opn {
+ char o;
+ char selp;
+ int (*func) _((char *, char *, Keymap, char **, char *, char));
+ int min, max;
+ } const opns[] = {
+ { 'l', 0, bin_bindkey_lsmaps, 0, 0 },
+ { 'd', 0, bin_bindkey_delall, 0, 0 },
+ { 'D', 0, bin_bindkey_del, 1, -1 },
+ { 'A', 0, bin_bindkey_link, 2, 2 },
+ { 'N', 0, bin_bindkey_new, 1, 2 },
+ { 'm', 1, bin_bindkey_meta, 0, 0 },
+ { 'r', 1, bin_bindkey_bind, 1, -1 },
+ { 's', 1, bin_bindkey_bind, 2, -1 },
+ { 0, 1, bin_bindkey_bind, 0, -1 },
+ };
+ struct opn const *op, *opp;
+ char *kmname;
+ Keymap km;
+ int n;
+
+ /* select operation and ensure no clashing arguments */
+ for(op = opns; op->o && !ops[op->o]; op++) ;
+ if(op->o)
+ for(opp = op; (++opp)->o; )
+ if(ops[opp->o]) {
+ zwarnnam(name, "incompatible operation selection options",
+ NULL, 0);
+ return 1;
+ }
+ n = ops['e'] + ops['v'] + ops['a'] + ops['M'];
+ if(!op->selp && n) {
+ zwarnnam(name, "keymap cannot be selected with -%c", NULL, op->o);
+ return 1;
+ }
+ if(n > 1) {
+ zwarnnam(name, "incompatible keymap selection options", NULL, 0);
+ return 1;
+ }
+
+ /* keymap selection */
+ if(op->selp) {
+ if(ops['e'])
+ kmname = "emacs";
+ else if(ops['v'])
+ kmname = "viins";
+ else if(ops['a'])
+ kmname = "vicmd";
+ else if(ops['M']) {
+ kmname = *argv++;
+ if(!kmname) {
+ zwarnnam(name, "-M option requires a keymap argument", NULL, 0);
+ return 1;
+ }
+ } else
+ kmname = "main";
+ km = openkeymap(kmname);
+ if(!km) {
+ zwarnnam(name, "no such keymap `%s'", kmname, 0);
+ return 1;
+ }
+ if(ops['e'] || ops['v'])
+ linkkeymap(km, "main");
+ } else {
+ kmname = NULL;
+ km = NULL;
+ }
+
+ /* listing is a special case */
+ if(!op->o && (!argv[0] || !argv[1])) {
+ if(ops['e'] || ops['v'])
+ return 0;
+ return bin_bindkey_list(name, kmname, km, argv, ops, op->o);
+ }
+
+ /* check number of arguments */
+ for(n = 0; argv[n]; n++) ;
+ if(n < op->min) {
+ zwarnnam(name, "not enough arguments for -%c", NULL, op->o);
+ return 1;
+ } else if(op->max != -1 && n > op->max) {
+ zwarnnam(name, "too many arguments for -%c", NULL, op->o);
+ return 1;
+ }
+
+ /* pass on the work to the operation function */
+ return op->func(name, kmname, km, argv, ops, op->o);
+}
+
+/* list the available keymaps */
+
+/**/
+static int
+bin_bindkey_lsmaps(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ scanhashtable(keymapnamtab, 1, 0, 0, scanlistmaps, ops['L']);
+ return 0;
+}
+
+/**/
+static void
+scanlistmaps(HashNode hn, int list)
+{
+ KeymapName n = (KeymapName) hn;
+
+ if(list) {
+ fputs("bindkey -N ", stdout);
+ if(n->nam[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(n->nam, stdout);
+ } else
+ nicezputs(n->nam, stdout);
+ putchar('\n');
+}
+
+/* reset all keymaps to the default state */
+
+/**/
+static int
+bin_bindkey_delall(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ keymapnamtab->emptytable(keymapnamtab);
+ default_bindings();
+ return 0;
+}
+
+/* delete named keymaps */
+
+/**/
+static int
+bin_bindkey_del(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ int ret = 0;
+
+ do {
+ int r = unlinkkeymap(*argv);
+ if(r == 1)
+ zwarnnam(name, "keymap name `%s' is protected", *argv, 0);
+ else if(r == 2)
+ zwarnnam(name, "no such keymap `%s'", *argv, 0);
+ ret |= !!r;
+ } while(*++argv);
+ return ret;
+}
+
+/* link named keymaps */
+
+/**/
+static int
+bin_bindkey_link(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ km = openkeymap(argv[0]);
+ if(!km) {
+ zwarnnam(name, "no such keymap `%s'", argv[0], 0);
+ return 1;
+ } else if(linkkeymap(km, argv[1])) {
+ zwarnnam(name, "keymap name `%s' is protected", argv[1], 0);
+ return 1;
+ }
+ return 0;
+}
+
+/* create a new keymap */
+
+/**/
+static int
+bin_bindkey_new(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ KeymapName kmn = (KeymapName) keymapnamtab->getnode(keymapnamtab, argv[0]);
+
+ if(kmn && (kmn -> flags & KMN_IMMORTAL)) {
+ zwarnnam(name, "keymap name `%s' is protected", argv[0], 0);
+ return 1;
+ }
+ if(argv[1]) {
+ km = openkeymap(argv[1]);
+ if(!km) {
+ zwarnnam(name, "no such keymap `%s'", argv[0], 0);
+ return 1;
+ }
+ } else
+ km = NULL;
+ linkkeymap(newkeymap(km, argv[0]), argv[0]);
+ return 0;
+}
+
+/* Add standard meta bindings to a keymap. Only sequences currently either *
+ * unbound or bound to self-insert are affected. Note that the use of *
+ * bindkey() is quite necessary: if this function were to go through the *
+ * km->first table itself, it would miss any prefix sequences that should *
+ * be rebound. */
+
+/**/
+static int
+bin_bindkey_meta(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ char m[3], *str;
+ int i;
+ Thingy fn;
+
+ if(km->flags & KM_IMMUTABLE) {
+ zwarnnam(name, "keymap `%s' is protected", kmname, 0);
+ return 1;
+ }
+ for(i = 128; i < 256; i++)
+ if(metabind[i - 128] != z_undefinedkey) {
+ m[0] = i;
+ metafy(m, 1, META_NOALLOC);
+ fn = keybind(km, m, &str);
+ if(fn == t_selfinsert || fn == t_undefinedkey)
+ bindkey(km, m, refthingy(Th(metabind[i - 128])), NULL);
+ }
+ return 0;
+}
+
+/* Change key bindings. func can be: *
+ * 'r' bind sequences to undefined-key *
+ * 's' bind sequneces to specified send-strings *
+ * 0 bind sequences to specified functions *
+ * If the -R option is used, bind to key ranges *
+ * instead of single key sequences. */
+
+/**/
+static int
+bin_bindkey_bind(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ int ret = 0;
+
+ if(!func || func == 's') {
+ char **a;
+
+ for(a = argv+2; *a; a++)
+ if(!*++a) {
+ zwarnnam(name, "even number of arguments required", NULL, 0);
+ return 1;
+ }
+ }
+ if(km->flags & KM_IMMUTABLE) {
+ zwarnnam(name, "keymap `%s' is protected", kmname, 0);
+ return 1;
+ }
+ do {
+ char *useq = *argv, *bseq, *seq, *str;
+ int len;
+ Thingy fn;
+
+ if(func == 'r') {
+ fn = refthingy(t_undefinedkey);
+ str = NULL;
+ } else if(func == 's') {
+ str = getkeystring(*++argv, &len, 2, NULL);
+ fn = NULL;
+ str = metafy(str, len, META_HREALLOC);
+ } else {
+ fn = rthingy(*++argv);
+ str = NULL;
+ }
+ bseq = getkeystring(useq, &len, 2, NULL);
+ seq = metafy(bseq, len, META_USEHEAP);
+ if(ops['R']) {
+ int first, last;
+ char m[3];
+
+ if(len < 2 || len > 2 + (bseq[1] == '-') ||
+ (first = STOUC(bseq[0])) > (last = STOUC(bseq[len - 1]))) {
+ zwarnnam(name, "malformed key range `%s'", useq, 0);
+ ret = 1;
+ } else {
+ for(; first <= last; first++) {
+ m[0] = first;
+ metafy(m, 1, META_NOALLOC);
+ bindkey(km, m, refthingy(fn), str);
+ }
+ unrefthingy(fn);
+ }
+ } else {
+ if(bindkey(km, seq, fn, str)) {
+ zwarnnam(name, "cannot bind to an empty key sequence", NULL, 0);
+ ret = 1;
+ }
+ }
+ } while(*++argv);
+ return ret;
+}
+
+/* List key bindings. If an argument is given, list just that one *
+ * binding, otherwise list the entire keymap. If the -L option is *
+ * given, list in the form of bindkey commands. */
+
+/**/
+static int
+bin_bindkey_list(char *name, char *kmname, Keymap km, char **argv, char *ops, char func)
+{
+ struct bindstate bs;
+
+ bs.flags = ops['L'] ? BS_LIST : 0;
+ bs.kmname = kmname;
+ if(argv[0]) {
+ int len;
+ char *seq;
+
+ seq = getkeystring(argv[0], &len, 2, NULL);
+ seq = metafy(seq, len, META_HREALLOC);
+ bs.flags |= BS_ALL;
+ bs.firstseq = bs.lastseq = seq;
+ bs.bind = keybind(km, seq, &bs.str);
+ bindlistout(&bs);
+ } else {
+ bs.firstseq = ztrdup("");
+ bs.lastseq = ztrdup("");
+ bs.bind = t_undefinedkey;
+ bs.str = NULL;
+ scankeymap(km, 1, scanbindlist, &bs);
+ bindlistout(&bs);
+ zsfree(bs.firstseq);
+ zsfree(bs.lastseq);
+ }
+ return 0;
+}
+
+/**/
+static void
+scanbindlist(char *seq, Thingy bind, char *str, void *magic)
+{
+ struct bindstate *bs = magic;
+
+ if(bind == bs->bind && (bind || !strcmp(str, bs->str)) &&
+ ztrlen(seq) == 1 && ztrlen(bs->lastseq) == 1) {
+ int l = bs->lastseq[1] ?
+ STOUC(bs->lastseq[1]) ^ 32 : STOUC(bs->lastseq[0]);
+ int t = seq[1] ? STOUC(seq[1]) ^ 32 : STOUC(seq[0]);
+
+ if(t == l + 1) {
+ zsfree(bs->lastseq);
+ bs->lastseq = ztrdup(seq);
+ return;
+ }
+ }
+ bindlistout(bs);
+ zsfree(bs->firstseq);
+ bs->firstseq = ztrdup(seq);
+ zsfree(bs->lastseq);
+ bs->lastseq = ztrdup(seq);
+ bs->bind = bind;
+ bs->str = str;
+}
+
+/**/
+static void
+bindlistout(struct bindstate *bs)
+{
+ int range;
+
+ if(bs->bind == t_undefinedkey && !(bs->flags & BS_ALL))
+ return;
+ range = strcmp(bs->firstseq, bs->lastseq);
+ if(bs->flags & BS_LIST) {
+ int nodash = 1;
+
+ fputs("bindkey ", stdout);
+ if(range)
+ fputs("-R ", stdout);
+ if(!bs->bind)
+ fputs("-s ", stdout);
+ if(!strcmp(bs->kmname, "main"))
+ ;
+ else if(!strcmp(bs->kmname, "vicmd"))
+ fputs("-a ", stdout);
+ else {
+ fputs("-M ", stdout);
+ quotedzputs(bs->kmname, stdout);
+ putchar(' ');
+ nodash = 0;
+ }
+ if(nodash && bs->firstseq[0] == '-')
+ fputs("-- ", stdout);
+ }
+ printbind(bs->firstseq, stdout);
+ if(range) {
+ putchar('-');
+ printbind(bs->lastseq, stdout);
+ }
+ putchar(' ');
+ if(bs->bind) {
+ ((bs->flags & BS_LIST) ? quotedzputs : nicezputs)
+ (bs->bind->nam, stdout);
+ } else
+ printbind(bs->str, stdout);
+ putchar('\n');
+}
+
+/****************************/
+/* initialisation functions */
+/****************************/
+
+/* main initialisation entry point */
+
+/**/
+void
+init_keymaps(void)
+{
+ createkeymapnamtab();
+ default_bindings();
+ keybuf = (char *)zalloc(keybufsz);
+ lastnamed = refthingy(t_undefinedkey);
+}
+
+#ifdef MODULE
+
+/* cleanup entry point (for unloading the zle module) */
+
+/**/
+void
+cleanup_keymaps(void)
+{
+ unrefthingy(lastnamed);
+ deletehashtable(keymapnamtab);
+ zfree(keybuf, keybufsz);
+}
+
+#endif /* MODULE */
+
+/* Create the default keymaps. For efficiency reasons, this function *
+ * assigns directly to the km->first array. It knows that there are no *
+ * prefix bindings in the way, and that it is using a simple keymap. */
+
+/**/
+static void
+default_bindings(void)
+{
+ Keymap vmap = newkeymap(NULL, "viins");
+ Keymap emap = newkeymap(NULL, "emacs");
+ Keymap amap = newkeymap(NULL, "vicmd");
+ Keymap smap = newkeymap(NULL, ".safe");
+ char buf[3], *ed;
+ int i;
+
+ /* vi insert mode and emacs mode: *
+ * 0-31 taken from the tables *
+ * 32-126 self-insert *
+ * 127 same as entry[8] *
+ * 128-255 self-insert */
+ for (i = 0; i < 32; i++) {
+ vmap->first[i] = refthingy(Th(viinsbind[i]));
+ emap->first[i] = refthingy(Th(emacsbind[i]));
+ }
+ for (i = 32; i < 256; i++) {
+ vmap->first[i] = refthingy(t_selfinsert);
+ emap->first[i] = refthingy(t_selfinsert);
+ }
+ unrefthingy(t_selfinsert);
+ unrefthingy(t_selfinsert);
+ vmap->first[127] = refthingy(vmap->first[8]);
+ emap->first[127] = refthingy(emap->first[8]);
+
+ /* vi command mode: *
+ * 0-127 taken from the table *
+ * 128-255 undefined-key */
+ for (i = 0; i < 128; i++)
+ amap->first[i] = refthingy(Th(vicmdbind[i]));
+ for (i = 128; i < 256; i++)
+ amap->first[i] = refthingy(t_undefinedkey);
+
+ /* safe fallback keymap:
+ * 0-255 self-insert, except: *
+ * '\n' accept-line *
+ * '\r' accept-line */
+ for (i = 0; i < 256; i++)
+ smap->first[i] = refthingy(t_selfinsert);
+ unrefthingy(t_selfinsert);
+ unrefthingy(t_selfinsert);
+ smap->first['\n'] = refthingy(t_acceptline);
+ smap->first['\r'] = refthingy(t_acceptline);
+
+ /* vt100 arrow keys are bound by default, for historical reasons. *
+ * Both standard and keypad modes are supported. */
+
+ /* vi command mode: arrow keys */
+ bindkey(amap, "\33[A", refthingy(t_uplineorhistory), NULL);
+ bindkey(amap, "\33[B", refthingy(t_downlineorhistory), NULL);
+ bindkey(amap, "\33[C", refthingy(t_viforwardchar), NULL);
+ bindkey(amap, "\33[D", refthingy(t_vibackwardchar), NULL);
+ bindkey(amap, "\33OA", refthingy(t_uplineorhistory), NULL);
+ bindkey(amap, "\33OB", refthingy(t_downlineorhistory), NULL);
+ bindkey(amap, "\33OC", refthingy(t_viforwardchar), NULL);
+ bindkey(amap, "\33OD", refthingy(t_vibackwardchar), NULL);
+
+ /* emacs mode: arrow keys */
+ bindkey(emap, "\33[A", refthingy(t_uplineorhistory), NULL);
+ bindkey(emap, "\33[B", refthingy(t_downlineorhistory), NULL);
+ bindkey(emap, "\33[C", refthingy(t_forwardchar), NULL);
+ bindkey(emap, "\33[D", refthingy(t_backwardchar), NULL);
+ bindkey(emap, "\33OA", refthingy(t_uplineorhistory), NULL);
+ bindkey(emap, "\33OB", refthingy(t_downlineorhistory), NULL);
+ bindkey(emap, "\33OC", refthingy(t_forwardchar), NULL);
+ bindkey(emap, "\33OD", refthingy(t_backwardchar), NULL);
+
+ /* emacs mode: ^X sequences */
+ bindkey(emap, "\30*", refthingy(t_expandword), NULL);
+ bindkey(emap, "\30g", refthingy(t_listexpand), NULL);
+ bindkey(emap, "\30G", refthingy(t_listexpand), NULL);
+ bindkey(emap, "\30\16", refthingy(t_infernexthistory), NULL);
+ bindkey(emap, "\30\13", refthingy(t_killbuffer), NULL);
+ bindkey(emap, "\30\6", refthingy(t_vifindnextchar), NULL);
+ bindkey(emap, "\30\17", refthingy(t_overwritemode), NULL);
+ bindkey(emap, "\30\25", refthingy(t_undo), NULL);
+ bindkey(emap, "\30\26", refthingy(t_vicmdmode), NULL);
+ bindkey(emap, "\30\12", refthingy(t_vijoin), NULL);
+ bindkey(emap, "\30\2", refthingy(t_vimatchbracket), NULL);
+ bindkey(emap, "\30s", refthingy(t_historyincrementalsearchforward), NULL);
+ bindkey(emap, "\30r", refthingy(t_historyincrementalsearchbackward), NULL);
+ bindkey(emap, "\30u", refthingy(t_undo), NULL);
+ bindkey(emap, "\30\30", refthingy(t_exchangepointandmark), NULL);
+ bindkey(emap, "\30=", refthingy(t_whatcursorposition), NULL);
+
+ /* emacs mode: ESC sequences, all taken from the meta binding table */
+ buf[0] = '\33';
+ buf[2] = 0;
+ for (i = 0; i < 128; i++)
+ if (metabind[i] != z_undefinedkey) {
+ buf[1] = i;
+ bindkey(emap, buf, refthingy(Th(metabind[i])), NULL);
+ }
+
+ /* Put the keymaps in the right namespace. The "main" keymap *
+ * will be linked to the "emacs" keymap, except that if VISUAL *
+ * or EDITOR contain the string "vi" then it will be linked to *
+ * the "viins" keymap. */
+ linkkeymap(vmap, "viins");
+ linkkeymap(emap, "emacs");
+ linkkeymap(amap, "vicmd");
+ linkkeymap(smap, ".safe");
+ if (((ed = zgetenv("VISUAL")) && strstr(ed, "vi")) ||
+ ((ed = zgetenv("EDITOR")) && strstr(ed, "vi")))
+ linkkeymap(vmap, "main");
+ else
+ linkkeymap(emap, "main");
+
+ /* the .safe map cannot be modified or deleted */
+ smap->flags |= KM_IMMUTABLE;
+ ((KeymapName) keymapnamtab->getnode(keymapnamtab, ".safe"))->flags
+ |= KMN_IMMORTAL;
+}
+
+/*************************/
+/* reading key sequences */
+/*************************/
+
+/* read a sequence of keys that is bound to some command in a keymap */
+
+/**/
+char *
+getkeymapcmd(Keymap km, Thingy *funcp, char **strp)
+{
+ Thingy func = t_undefinedkey;
+ char *str = NULL;
+ int lastlen = 0, lastc = c;
+
+ keybuflen = 0;
+ keybuf[0] = 0;
+ while((c = getkeybuf(!!lastlen)) != EOF) {
+ char *s;
+ Thingy f = keybind(km, keybuf, &s);
+
+ if(f != t_undefinedkey) {
+ lastlen = keybuflen;
+ func = f;
+ str = s;
+ lastc = c;
+ }
+ if(!keyisprefix(km, keybuf))
+ break;
+ }
+ if(!lastlen && keybuflen)
+ lastlen = keybuflen;
+ else
+ c = lastc;
+ if(lastlen != keybuflen) {
+ unmetafy(keybuf + lastlen, &keybuflen);
+ ungetkeys(keybuf+lastlen, keybuflen);
+ if(vichgflag)
+ vichgbufptr -= keybuflen;
+ keybuf[lastlen] = 0;
+ }
+ *funcp = func;
+ *strp = str;
+ return keybuf;
+}
+
+/**/
+static int
+getkeybuf(int w)
+{
+ int c = getkey(w);
+
+ if(c < 0)
+ return EOF;
+ if(keybuflen + 3 > keybufsz)
+ keybuf = realloc(keybuf, keybufsz *= 2);
+ if(imeta(c)) {
+ keybuf[keybuflen++] = Meta;
+ keybuf[keybuflen++] = c ^ 32;
+ } else
+ keybuf[keybuflen++] = c;
+ keybuf[keybuflen] = 0;
+ return c;
+}
+
+/* Push back the last command sequence read by getkeymapcmd(). *
+ * Must be executed at most once after each getkeymapcmd(). */
+
+/**/
+void
+ungetkeycmd(void)
+{
+ ungetkeys(keybuf, keybuflen);
+}
+
+/* read a command from the current keymap, with widgets */
+
+/**/
+Thingy
+getkeycmd(void)
+{
+ Thingy func;
+ int hops = 0;
+ char *seq, *str;
+
+ sentstring:
+ seq = getkeymapcmd(curkeymap, &func, &str);
+ if(!*seq)
+ return NULL;
+ if(!func) {
+ int len;
+ char *pb;
+
+ if (++hops == 20) {
+ zerr("string inserting another one too many times", NULL, 0);
+ hops = 0;
+ return NULL;
+ }
+ pb = unmetafy(ztrdup(str), &len);
+ ungetkeys(pb, len);
+ zfree(pb, strlen(str) + 1);
+ goto sentstring;
+ }
+ if (func == Th(z_executenamedcmd) && !statusline) {
+ while(func == Th(z_executenamedcmd))
+ func = executenamedcommand("execute: ");
+ if(!func)
+ func = t_undefinedkey;
+ else if(func != Th(z_executelastnamedcmd)) {
+ unrefthingy(lastnamed);
+ lastnamed = refthingy(func);
+ }
+ }
+ if (func == Th(z_executelastnamedcmd))
+ func = lastnamed;
+ return func;
+}
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
new file mode 100644
index 000000000..338cdef41
--- /dev/null
+++ b/Src/Zle/zle_main.c
@@ -0,0 +1,907 @@
+/*
+ * zle_main.c - main routines for line editor
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_main.pro"
+
+/* != 0 if we're done editing */
+
+/**/
+int done;
+
+/* location of mark */
+
+/**/
+int mark;
+
+/* last character pressed */
+
+/**/
+int c;
+
+/* the binding for this key */
+
+/**/
+Thingy bindk;
+
+/* insert mode/overwrite mode flag */
+
+/**/
+int insmode;
+
+static int eofchar, eofsent;
+static long keytimeout;
+
+#ifdef HAVE_SELECT
+/* Terminal baud rate */
+
+static int baud;
+#endif
+
+/* flags associated with last command */
+
+/**/
+int lastcmd;
+
+/* the status line, and its length */
+
+/**/
+char *statusline;
+/**/
+int statusll;
+
+/* The current history line and cursor position for the top line *
+ * on the buffer stack. */
+
+/**/
+int stackhist, stackcs;
+
+/* != 0 if we are making undo records */
+
+/**/
+int undoing;
+
+/* current modifier status */
+
+/**/
+struct modifier zmod;
+
+/* Current command prefix status. This is normally 0. Prefixes set *
+ * this to 1. Each time round the main loop, this is checked: if it *
+ * is 0, the modifier status is reset; if it is 1, the modifier *
+ * status is left unchanged, and this flag is reset to 0. The *
+ * effect is that several prefix commands can be executed, and have *
+ * cumulative effect, but any other command execution will clear the *
+ * modifiers. */
+
+/**/
+int prefixflag;
+
+/* != 0 if there is a pending beep (usually indicating an error) */
+
+/**/
+int feepflag;
+
+/* set up terminal */
+
+/**/
+void
+setterm(void)
+{
+ struct ttyinfo ti;
+
+#if defined(CLOBBERS_TYPEAHEAD) && defined(FIONREAD)
+ int val;
+
+ ioctl(SHTTY, FIONREAD, (char *)&val);
+ if (val)
+ return;
+#endif
+
+/* sanitize the tty */
+#ifdef HAS_TIO
+ shttyinfo.tio.c_lflag |= ICANON | ECHO;
+# ifdef FLUSHO
+ shttyinfo.tio.c_lflag &= ~FLUSHO;
+# endif
+#else /* not HAS_TIO */
+ shttyinfo.sgttyb.sg_flags = (shttyinfo.sgttyb.sg_flags & ~CBREAK) | ECHO;
+ shttyinfo.lmodes &= ~LFLUSHO;
+#endif
+
+ attachtty(mypgrp);
+ ti = shttyinfo;
+#ifdef HAS_TIO
+ if (unset(FLOWCONTROL))
+ ti.tio.c_iflag &= ~IXON;
+ ti.tio.c_lflag &= ~(ICANON | ECHO
+# ifdef FLUSHO
+ | FLUSHO
+# endif
+ );
+# ifdef TAB3
+ ti.tio.c_oflag &= ~TAB3;
+# else
+# ifdef OXTABS
+ ti.tio.c_oflag &= ~OXTABS;
+# else
+ ti.tio.c_oflag &= ~XTABS;
+# endif
+# endif
+ ti.tio.c_oflag |= ONLCR;
+ ti.tio.c_cc[VQUIT] =
+# ifdef VDISCARD
+ ti.tio.c_cc[VDISCARD] =
+# endif
+# ifdef VSUSP
+ ti.tio.c_cc[VSUSP] =
+# endif
+# ifdef VDSUSP
+ ti.tio.c_cc[VDSUSP] =
+# endif
+# ifdef VSWTCH
+ ti.tio.c_cc[VSWTCH] =
+# endif
+# ifdef VLNEXT
+ ti.tio.c_cc[VLNEXT] =
+# endif
+ VDISABLEVAL;
+# if defined(VSTART) && defined(VSTOP)
+ if (unset(FLOWCONTROL))
+ ti.tio.c_cc[VSTART] = ti.tio.c_cc[VSTOP] = VDISABLEVAL;
+# endif
+ eofchar = ti.tio.c_cc[VEOF];
+ ti.tio.c_cc[VMIN] = 1;
+ ti.tio.c_cc[VTIME] = 0;
+ ti.tio.c_iflag |= (INLCR | ICRNL);
+ /* this line exchanges \n and \r; it's changed back in getkey
+ so that the net effect is no change at all inside the shell.
+ This double swap is to allow typeahead in common cases, eg.
+
+ % bindkey -s '^J' 'echo foo^M'
+ % sleep 10
+ echo foo<return> <--- typed before sleep returns
+
+ The shell sees \n instead of \r, since it was changed by the kernel
+ while zsh wasn't looking. Then in getkey() \n is changed back to \r,
+ and it sees "echo foo<accept line>", as expected. Without the double
+ swap the shell would see "echo foo\n", which is translated to
+ "echo fooecho foo<accept line>" because of the binding.
+ Note that if you type <line-feed> during the sleep the shell just sees
+ \n, which is translated to \r in getkey(), and you just get another
+ prompt. For type-ahead to work in ALL cases you have to use
+ stty inlcr.
+
+ Unfortunately it's IMPOSSIBLE to have a general solution if both
+ <return> and <line-feed> are mapped to the same character. The shell
+ could check if there is input and read it before setting it's own
+ terminal modes but if we get a \n we don't know whether to keep it or
+ change to \r :-(
+ */
+
+#else /* not HAS_TIO */
+ ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO & ~XTABS;
+ ti.lmodes &= ~LFLUSHO;
+ eofchar = ti.tchars.t_eofc;
+ ti.tchars.t_quitc =
+ ti.ltchars.t_suspc =
+ ti.ltchars.t_flushc =
+ ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1;
+#endif
+
+#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT)
+ if (baud) { /**/
+ int n = 0;
+
+ while ((ioctl(SHTTY, TIOCOUTQ, (char *)&n) >= 0) && n) {
+ struct timeval tv;
+
+ tv.tv_sec = n / baud;
+ tv.tv_usec = ((n % baud) * 1000000) / baud;
+ select(0, NULL, NULL, NULL, &tv);
+ }
+ }
+#endif
+
+ settyinfo(&ti);
+}
+
+static char *kungetbuf;
+static int kungetct, kungetsz;
+
+/**/
+void
+ungetkey(int ch)
+{
+ if (kungetct == kungetsz)
+ kungetbuf = realloc(kungetbuf, kungetsz *= 2);
+ kungetbuf[kungetct++] = ch;
+}
+
+/**/
+void
+ungetkeys(char *s, int len)
+{
+ s += len;
+ while (len--)
+ ungetkey(*--s);
+}
+
+#if defined(pyr) && defined(HAVE_SELECT)
+static int
+breakread(int fd, char *buf, int n)
+{
+ fd_set f;
+
+ FD_ZERO(&f);
+ FD_SET(fd, &f);
+ return (select(fd + 1, (SELECT_ARG_2_T) & f, NULL, NULL, NULL) == -1 ?
+ EOF : read(fd, buf, n));
+}
+
+# define read breakread
+#endif
+
+/**/
+int
+getkey(int keytmout)
+{
+ char cc;
+ unsigned int ret;
+ long exp100ths;
+ int die = 0, r, icnt = 0;
+ int old_errno = errno;
+
+#ifdef HAVE_SELECT
+ fd_set foofd;
+
+#else
+# ifdef HAS_TIO
+ struct ttyinfo ti;
+
+# endif
+#endif
+
+ if (kungetct)
+ ret = STOUC(kungetbuf[--kungetct]);
+ else {
+ if (keytmout) {
+ if (keytimeout > 500)
+ exp100ths = 500;
+ else if (keytimeout > 0)
+ exp100ths = keytimeout;
+ else
+ exp100ths = 0;
+#ifdef HAVE_SELECT
+ if (exp100ths) {
+ struct timeval expire_tv;
+
+ expire_tv.tv_sec = exp100ths / 100;
+ expire_tv.tv_usec = (exp100ths % 100) * 10000L;
+ FD_ZERO(&foofd);
+ FD_SET(SHTTY, &foofd);
+ if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
+ NULL, NULL, &expire_tv) <= 0)
+ return EOF;
+ }
+#else
+# ifdef HAS_TIO
+ ti = shttyinfo;
+ ti.tio.c_lflag &= ~ICANON;
+ ti.tio.c_cc[VMIN] = 0;
+ ti.tio.c_cc[VTIME] = exp100ths / 10;
+# ifdef HAVE_TERMIOS_H
+ tcsetattr(SHTTY, TCSANOW, &ti.tio);
+# else
+ ioctl(SHTTY, TCSETA, &ti.tio);
+# endif
+ r = read(SHTTY, &cc, 1);
+# ifdef HAVE_TERMIOS_H
+ tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
+# else
+ ioctl(SHTTY, TCSETA, &shttyinfo.tio);
+# endif
+ return (r <= 0) ? EOF : cc;
+# endif
+#endif
+ }
+ while ((r = read(SHTTY, &cc, 1)) != 1) {
+ if (r == 0) {
+ /* The test for IGNOREEOF was added to make zsh ignore ^Ds
+ that were typed while commands are running. Unfortuantely
+ this caused trouble under at least one system (SunOS 4.1).
+ Here shells that lost their xterm (e.g. if it was killed
+ with -9) didn't fail to read from the terminal but instead
+ happily continued to read EOFs, so that the above read
+ returned with 0, and, with IGNOREEOF set, this caused
+ an infinite loop. The simple way around this was to add
+ the counter (icnt) so that this happens 20 times and than
+ the shell gives up (yes, this is a bit dirty...). */
+ if (isset(IGNOREEOF) && icnt++ < 20)
+ continue;
+ stopmsg = 1;
+ zexit(1, 0);
+ }
+ icnt = 0;
+ if (errno == EINTR) {
+ die = 0;
+ if (!errflag && !retflag && !breaks)
+ continue;
+ errflag = 0;
+ errno = old_errno;
+ return EOF;
+ } else if (errno == EWOULDBLOCK) {
+ fcntl(0, F_SETFL, 0);
+ } else if (errno == EIO && !die) {
+ ret = opts[MONITOR];
+ opts[MONITOR] = 1;
+ attachtty(mypgrp);
+ refresh(); /* kludge! */
+ opts[MONITOR] = ret;
+ die = 1;
+ } else if (errno != 0) {
+ zerr("error on TTY read: %e", NULL, errno);
+ stopmsg = 1;
+ zexit(1, 0);
+ }
+ }
+ if (cc == '\r') /* undo the exchange of \n and \r determined by */
+ cc = '\n'; /* setterm() */
+ else if (cc == '\n')
+ cc = '\r';
+
+ ret = STOUC(cc);
+ }
+ if (vichgflag) {
+ if (vichgbufptr == vichgbufsz)
+ vichgbuf = realloc(vichgbuf, vichgbufsz *= 2);
+ vichgbuf[vichgbufptr++] = ret;
+ }
+ errno = old_errno;
+ return ret;
+}
+
+/* Read a line. It is returned metafied. */
+
+/**/
+unsigned char *
+zleread(char *lp, char *rp, int ha)
+{
+ unsigned char *s;
+ int old_errno = errno;
+ int tmout = getiparam("TMOUT");
+
+#ifdef HAVE_SELECT
+ long costmult;
+ struct timeval tv;
+ fd_set foofd;
+
+ baud = getiparam("BAUD");
+ costmult = (baud) ? 3840000L / baud : 0;
+ tv.tv_sec = 0;
+#endif
+
+ /* ZLE doesn't currently work recursively. This is needed in case a *
+ * select loop is used in a function called from ZLE. vared handles *
+ * this differently itself. */
+ if(zleactive) {
+ char *pptbuf;
+ int pptlen;
+
+ pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen);
+ write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+ free(pptbuf);
+ return (unsigned char *)shingetline();
+ }
+
+ keytimeout = getiparam("KEYTIMEOUT");
+ if (!shout) {
+ if (SHTTY != -1)
+ init_shout();
+
+ if (!shout)
+ return NULL;
+ /* We could be smarter and default to a system read. */
+
+ /* If we just got a new shout, make sure the terminal is set up. */
+ if (termflags & TERM_UNKNOWN)
+ init_term();
+ }
+
+ fflush(shout);
+ fflush(stderr);
+ intr();
+ insmode = unset(OVERSTRIKE);
+ eofsent = 0;
+ resetneeded = 0;
+ lpptbuf = promptexpand(lp, 1, NULL, NULL);
+ pmpt_attr = txtchange;
+ rpptbuf = promptexpand(rp, 1, NULL, NULL);
+ rpmpt_attr = txtchange;
+ histallowed = ha;
+ PERMALLOC {
+ histline = curhist;
+#ifdef HAVE_SELECT
+ FD_ZERO(&foofd);
+#endif
+ undoing = 1;
+ line = (unsigned char *)zalloc((linesz = 256) + 2);
+ virangeflag = lastcmd = done = cs = ll = mark = 0;
+ curhistline = NULL;
+ vichgflag = 0;
+ viinsbegin = 0;
+ statusline = NULL;
+ selectkeymap("main", 1);
+ fixsuffix();
+ if ((s = (unsigned char *)getlinknode(bufstack))) {
+ setline((char *)s);
+ zsfree((char *)s);
+ if (stackcs != -1) {
+ cs = stackcs;
+ stackcs = -1;
+ if (cs > ll)
+ cs = ll;
+ }
+ if (stackhist != -1) {
+ histline = stackhist;
+ stackhist = -1;
+ }
+ }
+ initundo();
+ if (isset(PROMPTCR))
+ putc('\r', shout);
+ if (tmout)
+ alarm(tmout);
+ zleactive = 1;
+ resetneeded = 1;
+ errflag = retflag = 0;
+ lastcol = -1;
+ initmodifier(&zmod);
+ prefixflag = 0;
+ feepflag = 0;
+ refresh();
+ while (!done && !errflag) {
+
+ statusline = NULL;
+ vilinerange = 0;
+ reselectkeymap();
+ bindk = getkeycmd();
+ if (!ll && isfirstln && c == eofchar) {
+ eofsent = 1;
+ break;
+ }
+ if (bindk) {
+ execzlefunc(bindk);
+ handleprefixes();
+ /* for vi mode, make sure the cursor isn't somewhere illegal */
+ if (invicmdmode() && cs > findbol() &&
+ (cs == ll || line[cs] == '\n'))
+ cs--;
+ if (undoing)
+ handleundo();
+ } else {
+ errflag = 1;
+ break;
+ }
+#ifdef HAVE_SELECT
+ if (baud && !(lastcmd & ZLE_MENUCMP)) {
+ FD_SET(SHTTY, &foofd);
+ if ((tv.tv_usec = cost * costmult) > 500000)
+ tv.tv_usec = 500000;
+ if (!kungetct && select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
+ NULL, NULL, &tv) <= 0)
+ refresh();
+ } else
+#endif
+ if (!kungetct)
+ refresh();
+ handlefeep();
+ }
+ statusline = NULL;
+ invalidatelist();
+ trashzle();
+ free(lpptbuf);
+ free(rpptbuf);
+ zleactive = 0;
+ alarm(0);
+ } LASTALLOC;
+ zsfree(curhistline);
+ freeundo();
+ if (eofsent) {
+ free(line);
+ line = NULL;
+ } else {
+ line[ll++] = '\n';
+ line = (unsigned char *) metafy((char *) line, ll, META_REALLOC);
+ }
+ forget_edits();
+ errno = old_errno;
+ return line;
+}
+
+/* execute a widget */
+
+/**/
+void
+execzlefunc(Thingy func)
+{
+ Widget w;
+
+ if(func->flags & DISABLED) {
+ /* this thingy is not the name of a widget */
+ char *nm = niceztrdup(func->nam);
+ char *msg = tricat("No such widget `", nm, "'");
+
+ zsfree(nm);
+ showmsg(msg);
+ zsfree(msg);
+ feep();
+ } else if((w = func->widget)->flags & WIDGET_INT) {
+ int wflags = w->flags;
+
+ if(!(wflags & ZLE_KEEPSUFFIX))
+ removesuffix();
+ if(!(wflags & ZLE_MENUCMP)) {
+ fixsuffix();
+ invalidatelist();
+ }
+ if (wflags & ZLE_LINEMOVE)
+ vilinerange = 1;
+ if(!(wflags & ZLE_LASTCOL))
+ lastcol = -1;
+ w->u.fn();
+ lastcmd = wflags;
+ } else {
+ List l = getshfunc(w->u.fnnam);
+
+ if(l == &dummy_list) {
+ /* the shell function doesn't exist */
+ char *nm = niceztrdup(w->u.fnnam);
+ char *msg = tricat("No such shell function `", nm, "'");
+
+ zsfree(nm);
+ showmsg(msg);
+ zsfree(msg);
+ feep();
+ } else {
+ startparamscope();
+ makezleparams();
+ doshfunc(l, NULL, 0, 1);
+ endparamscope();
+ lastcmd = 0;
+ }
+ }
+}
+
+/* initialise command modifiers */
+
+/**/
+static void
+initmodifier(struct modifier *mp)
+{
+ mp->flags = 0;
+ mp->mult = 1;
+ mp->tmult = 1;
+ mp->vibuf = 0;
+}
+
+/* Reset command modifiers, unless the command just executed was a prefix. *
+ * Also set zmult, if the multiplier has been amended. */
+
+/**/
+static void
+handleprefixes(void)
+{
+ if (prefixflag) {
+ prefixflag = 0;
+ if(zmod.flags & MOD_TMULT) {
+ zmod.flags |= MOD_MULT;
+ zmod.mult = zmod.tmult;
+ }
+ } else
+ initmodifier(&zmod);
+}
+
+/* vared: edit (literally) a parameter value */
+
+/**/
+static int
+bin_vared(char *name, char **args, char *ops, int func)
+{
+ char *s;
+ char *t;
+ Param pm;
+ int create = 0;
+ char *p1 = NULL, *p2 = NULL;
+
+ /* all options are handled as arguments */
+ while (*args && **args == '-') {
+ while (*++(*args))
+ switch (**args) {
+ case 'c':
+ /* -c option -- allow creation of the parameter if it doesn't
+ yet exist */
+ create = 1;
+ break;
+ case 'p':
+ /* -p option -- set main prompt string */
+ if ((*args)[1])
+ p1 = *args + 1, *args = "" - 1;
+ else if (args[1])
+ p1 = *(++args), *args = "" - 1;
+ else {
+ zwarnnam(name, "prompt string expected after -%c", NULL,
+ **args);
+ return 1;
+ }
+ break;
+ case 'r':
+ /* -r option -- set right prompt string */
+ if ((*args)[1])
+ p2 = *args + 1, *args = "" - 1;
+ else if (args[1])
+ p2 = *(++args), *args = "" - 1;
+ else {
+ zwarnnam(name, "prompt string expected after -%c", NULL,
+ **args);
+ return 1;
+ }
+ break;
+ case 'h':
+ /* -h option -- enable history */
+ ops['h'] = 1;
+ break;
+ default:
+ /* unrecognised option character */
+ zwarnnam(name, "unknown option: %s", *args, 0);
+ return 1;
+ }
+ args++;
+ }
+
+ /* check we have a parameter name */
+ if (!*args) {
+ zwarnnam(name, "missing variable", NULL, 0);
+ return 1;
+ }
+ /* handle non-existent parameter */
+ if (!(s = getsparam(args[0]))) {
+ if (create)
+ createparam(args[0], PM_SCALAR);
+ else {
+ zwarnnam(name, "no such variable: %s", args[0], 0);
+ return 1;
+ }
+ }
+
+ if(zleactive) {
+ zwarnnam(name, "ZLE cannot be used recursively (yet)", NULL, 0);
+ return 1;
+ }
+
+ /* edit the parameter value */
+ PERMALLOC {
+ pushnode(bufstack, ztrdup(s));
+ } LASTALLOC;
+ t = (char *) zleread(p1, p2, ops['h']);
+ if (!t || errflag) {
+ /* error in editing */
+ errflag = 0;
+ return 1;
+ }
+ /* strip off trailing newline, if any */
+ if (t[strlen(t) - 1] == '\n')
+ t[strlen(t) - 1] = '\0';
+ /* final assignment of parameter value */
+ pm = (Param) paramtab->getnode(paramtab, args[0]);
+ if (pm && PM_TYPE(pm->flags) == PM_ARRAY) {
+ char **a;
+
+ PERMALLOC {
+ a = spacesplit(t, 1);
+ } LASTALLOC;
+ setaparam(args[0], a);
+ } else
+ setsparam(args[0], t);
+ return 0;
+}
+
+/**/
+void
+describekeybriefly(void)
+{
+ char *seq, *str, *msg, *is;
+ Thingy func;
+
+ if (statusline)
+ return;
+ statusline = "Describe key briefly: _";
+ statusll = strlen(statusline);
+ refresh();
+ seq = getkeymapcmd(curkeymap, &func, &str);
+ statusline = NULL;
+ if(!*seq)
+ return;
+ msg = bindztrdup(seq);
+ msg = appstr(msg, " is ");
+ if (!func)
+ is = bindztrdup(str);
+ else
+ is = niceztrdup(func->nam);
+ msg = appstr(msg, is);
+ zsfree(is);
+ showmsg(msg);
+ zsfree(msg);
+}
+
+#define MAXFOUND 4
+
+struct findfunc {
+ Thingy func;
+ int found;
+ char *msg;
+};
+
+/**/
+static void
+scanfindfunc(char *seq, Thingy func, char *str, void *magic)
+{
+ struct findfunc *ff = magic;
+
+ if(func != ff->func)
+ return;
+ if (!ff->found++)
+ ff->msg = appstr(ff->msg, " is on");
+ if(ff->found <= MAXFOUND) {
+ char *b = bindztrdup(seq);
+
+ ff->msg = appstr(ff->msg, " ");
+ ff->msg = appstr(ff->msg, b);
+ zsfree(b);
+ }
+}
+
+/**/
+void
+whereis(void)
+{
+ struct findfunc ff;
+
+ if (!(ff.func = executenamedcommand("Where is: ")))
+ return;
+ ff.found = 0;
+ ff.msg = niceztrdup(ff.func->nam);
+ scankeymap(curkeymap, 1, scanfindfunc, &ff);
+ if (!ff.found)
+ ff.msg = appstr(ff.msg, " is not bound to any key");
+ else if(ff.found > MAXFOUND)
+ ff.msg = appstr(ff.msg, " et al");
+ showmsg(ff.msg);
+ zsfree(ff.msg);
+}
+
+/**/
+void
+trashzle(void)
+{
+ if (zleactive) {
+ /* This refresh() is just to get the main editor display right and *
+ * get the cursor in the right place. For that reason, we disable *
+ * list display (which would otherwise result in infinite *
+ * recursion [at least, it would if refresh() didn't have its *
+ * extra `inlist' check]). */
+ int sl = showinglist;
+ showinglist = 0;
+ refresh();
+ showinglist = sl;
+ moveto(nlnct, 0);
+ if (clearflag && tccan(TCCLEAREOD)) {
+ tcout(TCCLEAREOD);
+ clearflag = 0;
+ }
+ if (postedit)
+ fprintf(shout, "%s", postedit);
+ fflush(shout);
+ resetneeded = 1;
+ settyinfo(&shttyinfo);
+ }
+ if (errflag)
+ kungetct = 0;
+}
+
+static struct builtin bintab[] = {
+ BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL),
+ BUILTIN("vared", 0, bin_vared, 1, 7, 0, NULL, NULL),
+ BUILTIN("zle", 0, bin_zle, 0, -1, 0, "lDANL", NULL),
+};
+
+/**/
+int
+boot_zle(Module m)
+{
+ /* Set up editor entry points */
+ trashzleptr = trashzle;
+ gotwordptr = gotword;
+ refreshptr = refresh;
+ spaceinlineptr = spaceinline;
+ zlereadptr = zleread;
+
+ /* initialise the thingies */
+ init_thingies();
+
+ /* miscellaneous initialisations */
+ stackhist = stackcs = -1;
+ kungetbuf = (char *) zalloc(kungetsz = 32);
+
+ /* initialise the keymap system */
+ init_keymaps();
+
+ addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_zle(Module m)
+{
+ int i;
+
+ if(zleactive) {
+ zerrnam(m->nam, "can't unload the zle module while zle is active",
+ NULL, 0);
+ return 1;
+ }
+
+ deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+ cleanup_keymaps();
+ deletehashtable(thingytab);
+
+ zfree(vichgbuf, vichgbufsz);
+ zfree(kungetbuf, kungetsz);
+ free_isrch_spots();
+
+ zfree(cutbuf.buf, cutbuf.len);
+ for(i = KRINGCT; i--; )
+ zfree(kring[i].buf, kring[i].len);
+ for(i = 35; i--; )
+ zfree(vibuf[i].buf, vibuf[i].len);
+
+ /* editor entry points */
+ trashzleptr = noop_function;
+ gotwordptr = noop_function;
+ refreshptr = noop_function;
+ spaceinlineptr = noop_function_int;
+ zlereadptr = fallback_zleread;
+
+ return 0;
+}
+
+#endif /* MODULE */
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
new file mode 100644
index 000000000..42953852f
--- /dev/null
+++ b/Src/Zle/zle_misc.c
@@ -0,0 +1,816 @@
+/*
+ * zle_misc.c - miscellaneous editor routines
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_misc.pro"
+
+/* insert a metafied string, with repetition and suffix removal */
+
+/**/
+void
+doinsert(char *str)
+{
+ char *s;
+ int len = ztrlen(str);
+ int c1 = *str == Meta ? STOUC(str[1])^32 : STOUC(*str);/* first character */
+ int neg = zmult < 0; /* insert *after* the cursor? */
+ int m = neg ? -zmult : zmult; /* number of copies to insert */
+
+ iremovesuffix(c1);
+ invalidatelist();
+
+ if(insmode)
+ spaceinline(m * len);
+ else if(cs + m * len > ll)
+ spaceinline(cs + m * len - ll);
+ while(m--)
+ for(s = str; *s; s++)
+ line[cs++] = *s == Meta ? *++s ^ 32 : *s;
+ if(neg)
+ cs += zmult * len;
+}
+
+/**/
+void
+selfinsert(void)
+{
+ char s[3], *p = s;
+
+ if(imeta(c)) {
+ *p++ = Meta;
+ c ^= 32;
+ }
+ *p++ = c;
+ *p = 0;
+ doinsert(s);
+}
+
+/**/
+void
+selfinsertunmeta(void)
+{
+ c &= 0x7f;
+ if (c == '\r')
+ c = '\n';
+ selfinsert();
+}
+
+/**/
+void
+deletechar(void)
+{
+ if (zmult < 0) {
+ zmult = -zmult;
+ backwarddeletechar();
+ zmult = -zmult;
+ return;
+ }
+ if (cs + zmult <= ll) {
+ cs += zmult;
+ backdel(zmult);
+ } else
+ feep();
+}
+
+/**/
+void
+backwarddeletechar(void)
+{
+ if (zmult < 0) {
+ zmult = -zmult;
+ deletechar();
+ zmult = -zmult;
+ return;
+ }
+ backdel(zmult > cs ? cs : zmult);
+}
+
+/**/
+void
+killwholeline(void)
+{
+ int i, fg, n = zmult;
+
+ if (n < 0)
+ return;
+ while (n--) {
+ if ((fg = (cs && cs == ll)))
+ cs--;
+ while (cs && line[cs - 1] != '\n')
+ cs--;
+ for (i = cs; i != ll && line[i] != '\n'; i++);
+ forekill(i - cs + (i != ll), fg);
+ }
+}
+
+/**/
+void
+killbuffer(void)
+{
+ cs = 0;
+ forekill(ll, 0);
+}
+
+/**/
+void
+backwardkillline(void)
+{
+ int i = 0, n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ killline();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ if (cs && line[cs - 1] == '\n')
+ cs--, i++;
+ else
+ while (cs && line[cs - 1] != '\n')
+ cs--, i++;
+ }
+ forekill(i, 1);
+}
+
+/**/
+void
+gosmacstransposechars(void)
+{
+ int cc;
+
+ if (cs < 2 || line[cs - 1] == '\n' || line[cs - 2] == '\n') {
+ if (cs == ll || line[cs] == '\n' ||
+ ((cs + 1 == ll || line[cs + 1] == '\n') &&
+ (!cs || line[cs - 1] == '\n'))) {
+ feep();
+ return;
+ }
+ cs += (cs == 0 || line[cs - 1] == '\n') ? 2 : 1;
+ }
+ cc = line[cs - 2];
+ line[cs - 2] = line[cs - 1];
+ line[cs - 1] = cc;
+}
+
+/**/
+void
+transposechars(void)
+{
+ int cc, ct;
+ int n = zmult;
+ int neg = n < 0;
+
+ if (neg)
+ n = -n;
+ while (n--) {
+ if (!(ct = cs) || line[cs - 1] == '\n') {
+ if (ll == cs || line[cs] == '\n') {
+ feep();
+ return;
+ }
+ if (!neg)
+ cs++;
+ ct++;
+ }
+ if (neg) {
+ if (cs && line[cs - 1] != '\n') {
+ cs--;
+ if (ct > 1 && line[ct - 2] != '\n')
+ ct--;
+ }
+ } else {
+ if (cs != ll && line[cs] != '\n')
+ cs++;
+ }
+ if (ct == ll || line[ct] == '\n')
+ ct--;
+ if (ct < 1 || line[ct - 1] == '\n') {
+ feep();
+ return;
+ }
+ cc = line[ct - 1];
+ line[ct - 1] = line[ct];
+ line[ct] = cc;
+ }
+}
+
+/**/
+void
+poundinsert(void)
+{
+ cs = 0;
+ vifirstnonblank();
+ if (line[cs] != '#') {
+ spaceinline(1);
+ line[cs] = '#';
+ cs = findeol();
+ while(cs != ll) {
+ cs++;
+ vifirstnonblank();
+ spaceinline(1);
+ line[cs] = '#';
+ cs = findeol();
+ }
+ } else {
+ foredel(1);
+ cs = findeol();
+ while(cs != ll) {
+ cs++;
+ vifirstnonblank();
+ if(line[cs] == '#')
+ foredel(1);
+ cs = findeol();
+ }
+ }
+ done = 1;
+}
+
+/**/
+void
+acceptline(void)
+{
+ done = 1;
+}
+
+/**/
+void
+acceptandhold(void)
+{
+ pushnode(bufstack, metafy((char *)line, ll, META_DUP));
+ stackcs = cs;
+ done = 1;
+}
+
+/**/
+void
+killline(void)
+{
+ int i = 0, n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwardkillline();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ if (line[cs] == '\n')
+ cs++, i++;
+ else
+ while (cs != ll && line[cs] != '\n')
+ cs++, i++;
+ }
+ backkill(i, 0);
+}
+
+/**/
+void
+killregion(void)
+{
+ if (mark > ll)
+ mark = ll;
+ if (mark > cs)
+ forekill(mark - cs, 0);
+ else
+ backkill(cs - mark, 1);
+}
+
+/**/
+void
+copyregionaskill(void)
+{
+ if (mark > ll)
+ mark = ll;
+ if (mark > cs)
+ cut(cs, mark - cs, 0);
+ else
+ cut(mark, cs - mark, 1);
+}
+
+static int kct, yankb, yanke;
+
+/**/
+void
+yank(void)
+{
+ Cutbuffer buf = &cutbuf;
+ int n = zmult;
+
+ if (n < 0)
+ return;
+ if (zmod.flags & MOD_VIBUF)
+ buf = &vibuf[zmod.vibuf];
+ if (!buf->buf) {
+ feep();
+ return;
+ }
+ mark = cs;
+ yankb = cs;
+ while (n--) {
+ kct = kringnum;
+ spaceinline(buf->len);
+ memcpy((char *)line + cs, buf->buf, buf->len);
+ cs += buf->len;
+ yanke = cs;
+ }
+}
+
+/**/
+void
+yankpop(void)
+{
+ int cc;
+
+ if (!(lastcmd & ZLE_YANK) || !kring[kct].buf) {
+ feep();
+ return;
+ }
+ cs = yankb;
+ foredel(yanke - yankb);
+ cc = kring[kct].len;
+ spaceinline(cc);
+ memcpy((char *)line + cs, kring[kct].buf, cc);
+ cs += cc;
+ yanke = cs;
+ kct = (kct + KRINGCT - 1) % KRINGCT;
+}
+
+/**/
+void
+overwritemode(void)
+{
+ insmode ^= 1;
+}
+/**/
+void
+whatcursorposition(void)
+{
+ char msg[100];
+ char *s = msg;
+ int bol = findbol();
+ int c = STOUC(line[cs]);
+
+ if (cs == ll)
+ strucpy(&s, "EOF");
+ else {
+ strucpy(&s, "Char: ");
+ switch (c) {
+ case ' ':
+ strucpy(&s, "SPC");
+ break;
+ case '\t':
+ strucpy(&s, "TAB");
+ break;
+ case '\n':
+ strucpy(&s, "LFD");
+ break;
+ default:
+ if (imeta(c)) {
+ *s++ = Meta;
+ *s++ = c ^ 32;
+ } else
+ *s++ = c;
+ }
+ sprintf(s, " (0%o, %d, 0x%x)", c, c, c);
+ s += strlen(s);
+ }
+ sprintf(s, " point %d of %d(%d%%) column %d", cs+1, ll+1,
+ ll ? 100 * cs / ll : 0, cs - bol);
+ showmsg(msg);
+}
+
+/**/
+void
+undefinedkey(void)
+{
+ feep();
+}
+
+/**/
+void
+quotedinsert(void)
+{
+#ifndef HAS_TIO
+ struct sgttyb sob;
+
+ sob = shttyinfo.sgttyb;
+ sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
+ ioctl(SHTTY, TIOCSETN, &sob);
+#endif
+ c = getkey(0);
+#ifndef HAS_TIO
+ setterm();
+#endif
+ if (c < 0)
+ feep();
+ else
+ selfinsert();
+}
+
+/**/
+void
+digitargument(void)
+{
+ int sign = (zmult < 0) ? -1 : 1;
+
+ if (!(zmod.flags & MOD_TMULT))
+ zmod.tmult = 0;
+ if (zmod.flags & MOD_NEG) {
+ /* If we just had a negative argument, this is the digit, *
+ * rather than the -1 assumed by negargument() */
+ zmod.tmult = sign * (c & 0xf);
+ zmod.flags &= ~MOD_NEG;
+ } else
+ zmod.tmult = zmod.tmult * 10 + sign * (c & 0xf);
+ zmod.flags |= MOD_TMULT;
+ prefixflag = 1;
+}
+
+/**/
+void
+negargument(void)
+{
+ if(zmod.flags & MOD_TMULT) {
+ feep();
+ return;
+ }
+ zmod.tmult = -1;
+ zmod.flags |= MOD_TMULT|MOD_NEG;
+ prefixflag = 1;
+}
+
+/**/
+void
+universalargument(void)
+{
+ int digcnt = 0, pref = 0, minus = 1, gotk;
+ while ((gotk = getkey(0)) != EOF) {
+ if (gotk == '-' && !digcnt) {
+ minus = -1;
+ digcnt++;
+ } else if (gotk >= '0' && gotk <= '9') {
+ pref = pref * 10 + (gotk & 0xf);
+ digcnt++;
+ } else {
+ ungetkey(gotk);
+ break;
+ }
+ }
+ if (digcnt)
+ zmod.tmult = minus * (pref ? pref : 1);
+ else
+ zmod.tmult *= 4;
+ zmod.flags |= MOD_TMULT;
+ prefixflag = 1;
+}
+
+/**/
+void
+copyprevword(void)
+{
+ int len, t0;
+
+ for (t0 = cs - 1; t0 >= 0; t0--)
+ if (iword(line[t0]))
+ break;
+ for (; t0 >= 0; t0--)
+ if (!iword(line[t0]))
+ break;
+ if (t0)
+ t0++;
+ len = cs - t0;
+ spaceinline(len);
+ memcpy((char *)&line[cs], (char *)&line[t0], len);
+ cs += len;
+}
+
+/**/
+void
+sendbreak(void)
+{
+ errflag = 1;
+}
+
+/**/
+void
+quoteregion(void)
+{
+ char *str;
+ size_t len;
+
+ if (mark > ll)
+ mark = ll;
+ if (mark < cs) {
+ int tmp = mark;
+ mark = cs;
+ cs = tmp;
+ }
+ str = (char *)hcalloc(len = mark - cs);
+ memcpy(str, (char *)&line[cs], len);
+ foredel(len);
+ str = makequote(str, &len);
+ spaceinline(len);
+ memcpy((char *)&line[cs], str, len);
+ mark = cs;
+ cs += len;
+}
+
+/**/
+void
+quoteline(void)
+{
+ char *str;
+ size_t len = ll;
+
+ str = makequote((char *)line, &len);
+ sizeline(len);
+ memcpy(line, str, len);
+ cs = ll = len;
+}
+
+/**/
+static char *
+makequote(char *str, size_t *len)
+{
+ int qtct = 0;
+ char *l, *ol;
+ char *end = str + *len;
+
+ for (l = str; l < end; l++)
+ if (*l == '\'')
+ qtct++;
+ *len += 2 + qtct*3;
+ l = ol = (char *)halloc(*len);
+ *l++ = '\'';
+ for (; str < end; str++)
+ if (*str == '\'') {
+ *l++ = '\'';
+ *l++ = '\\';
+ *l++ = '\'';
+ *l++ = '\'';
+ } else
+ *l++ = *str;
+ *l++ = '\'';
+ return ol;
+}
+
+static char *cmdbuf;
+static LinkList cmdll;
+static int cmdambig;
+
+/**/
+static void
+scancompcmd(HashNode hn, int flags)
+{
+ int l;
+ Thingy t = (Thingy) hn;
+
+ if(strpfx(cmdbuf, t->nam)) {
+ addlinknode(cmdll, t->nam);
+ l = pfxlen(peekfirst(cmdll), t->nam);
+ if (l < cmdambig)
+ cmdambig = l;
+ }
+
+}
+
+#define NAMLEN 60
+
+/**/
+Thingy
+executenamedcommand(char *prmt)
+{
+ Thingy cmd;
+ int len, l = strlen(prmt);
+ char *ptr;
+ char *okeymap = curkeymapname;
+
+ cmdbuf = halloc(l + NAMLEN + 2);
+ strcpy(cmdbuf, prmt);
+ statusline = cmdbuf;
+ selectkeymap("main", 1);
+ ptr = cmdbuf += l;
+ len = 0;
+ for (;;) {
+ *ptr = '_';
+ statusll = l + len + 1;
+ refresh();
+ if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
+ statusline = NULL;
+ selectkeymap(okeymap, 1);
+ return NULL;
+ }
+ if(cmd == Th(z_clearscreen)) {
+ clearscreen();
+ } else if(cmd == Th(z_redisplay)) {
+ redisplay();
+ } else if(cmd == Th(z_viquotedinsert)) {
+ *ptr = '^';
+ refresh();
+ c = getkey(0);
+ if(c == EOF || !c || len == NAMLEN)
+ feep();
+ else
+ *ptr++ = c, len++;
+ } else if(cmd == Th(z_quotedinsert)) {
+ if((c = getkey(0)) == EOF || !c || len == NAMLEN)
+ feep();
+ else
+ *ptr++ = c, len++;
+ } else if(cmd == Th(z_backwarddeletechar) ||
+ cmd == Th(z_vibackwarddeletechar)) {
+ if (len)
+ len--, ptr--;
+ } else if(cmd == Th(z_killregion) || cmd == Th(z_backwardkillword) ||
+ cmd == Th(z_vibackwardkillword)) {
+ while (len && (len--, *--ptr != '-'));
+ } else if(cmd == Th(z_killwholeline) || cmd == Th(z_vikillline) ||
+ cmd == Th(z_backwardkillline)) {
+ len = 0;
+ ptr = cmdbuf;
+ } else {
+ if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) {
+ Thingy r;
+ unambiguous:
+ *ptr = 0;
+ r = rthingy(cmdbuf);
+ if (!(r->flags & DISABLED)) {
+ unrefthingy(r);
+ statusline = NULL;
+ selectkeymap(okeymap, 1);
+ return r;
+ }
+ unrefthingy(r);
+ }
+ if(cmd == Th(z_selfinsertunmeta)) {
+ c &= 0x7f;
+ if(c == '\r')
+ c = '\n';
+ cmd = Th(z_selfinsert);
+ }
+ if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist) ||
+ cmd == Th(z_expandorcomplete) || cmd == Th(z_completeword) ||
+ cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_vicmdmode) ||
+ cmd == Th(z_acceptline) || c == ' ' || c == '\t') {
+ cmdambig = 100;
+
+ HEAPALLOC {
+ cmdll = newlinklist();
+ *ptr = 0;
+
+ scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0);
+ } LASTALLOC;
+ if (empty(cmdll))
+ feep();
+ else if (cmd == Th(z_listchoices) ||
+ cmd == Th(z_deletecharorlist)) {
+ int zmultsav = zmult;
+ *ptr = '_';
+ statusll = l + len + 1;
+ zmult = 1;
+ listlist(cmdll);
+ zmult = zmultsav;
+ } else if (!nextnode(firstnode(cmdll))) {
+ strcpy(ptr = cmdbuf, peekfirst(cmdll));
+ ptr += (len = strlen(ptr));
+ if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode))
+ goto unambiguous;
+ } else {
+ strcpy(cmdbuf, peekfirst(cmdll));
+ ptr = cmdbuf + cmdambig;
+ *ptr = '_';
+ if (isset(AUTOLIST) &&
+ !(isset(LISTAMBIGUOUS) && cmdambig > len)) {
+ int zmultsav = zmult;
+ if (isset(LISTBEEP))
+ feep();
+ statusll = l + cmdambig + 1;
+ zmult = 1;
+ listlist(cmdll);
+ zmult = zmultsav;
+ }
+ len = cmdambig;
+ }
+ } else {
+ if (len == NAMLEN || icntrl(c) || cmd != Th(z_selfinsert))
+ feep();
+ else
+ *ptr++ = c, len++;
+ }
+ }
+ handlefeep();
+ }
+}
+
+/*****************/
+/* Suffix system */
+/*****************/
+
+/*
+ * The completion system sometimes tentatively adds a suffix to a word,
+ * which can be removed depending on what is inserted next. These
+ * functions provide the capability to handle a removable suffix.
+ *
+ * Any removable suffix consists of characters immediately before the
+ * cursor. Whether it is removed depends on the next editing action.
+ * There can be more than one suffix simultaneously present, with
+ * different actions deleting different numbers of characters.
+ *
+ * If the next editing action changes the buffer other than by inserting
+ * characters, normally the suffix should be removed so as to leave a
+ * meaningful complete word. The behaviour should be the same if the
+ * next character inserted is a word separator. If the next character
+ * reasonably belongs where it is typed, or if the next editing action
+ * is a deletion, the suffix should not be removed. Other reasons for
+ * suffix removal may have other behaviour.
+ *
+ * In order to maintain a consistent state, after a suffix has been added
+ * the table *must* be zeroed, one way or another, before the buffer is
+ * changed. If the suffix is not being removed, call fixsuffix() to
+ * indicate that it is being permanently fixed.
+ */
+
+/* Length of suffix to remove when inserting each possible character value. *
+ * suffixlen[256] is the length to remove for non-insertion editing actions. */
+
+/**/
+int suffixlen[257];
+
+/* Set up suffix: the last n characters are a suffix that should be *
+ * removed in the usual word end conditions. */
+
+/**/
+void
+makesuffix(int n)
+{
+ suffixlen[256] = suffixlen[' '] = suffixlen['\t'] = suffixlen['\n'] = n;
+}
+
+/* Set up suffix for parameter names: the last n characters are a suffix *
+ * that should be removed if the next character is one of the ones that *
+ * needs to go immediately after the parameter name. br indicates that *
+ * the name is in braces (${PATH} instead of $PATH), so the extra *
+ * characters that can only be used in braces are included. */
+
+/**/
+void
+makeparamsuffix(int br, int n)
+{
+ if(br || unset(KSHARRAYS))
+ suffixlen[':'] = suffixlen['['] = n;
+ if(br) {
+ suffixlen['#'] = suffixlen['%'] = suffixlen['?'] = n;
+ suffixlen['-'] = suffixlen['+'] = suffixlen['='] = n;
+ /*{*/ suffixlen['}'] = n;
+ }
+}
+
+/* Remove suffix, if there is one, when inserting character c. */
+
+/**/
+void
+iremovesuffix(int c)
+{
+ int sl = suffixlen[c];
+ if(sl) {
+ backdel(sl);
+ invalidatelist();
+ }
+ fixsuffix();
+}
+
+/* Fix the suffix in place, if there is one, making it non-removable. */
+
+/**/
+void
+fixsuffix(void)
+{
+ memset(suffixlen, 0, sizeof(suffixlen));
+}
diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c
new file mode 100644
index 000000000..8ed4c657a
--- /dev/null
+++ b/Src/Zle/zle_move.c
@@ -0,0 +1,502 @@
+/*
+ * zle_move.c - editor movement
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_move.pro"
+
+static vimarkcs[27], vimarkline[27];
+
+/**/
+void
+beginningofline(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ endofline();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ if (cs == 0)
+ return;
+ if (line[cs - 1] == '\n')
+ if (!--cs)
+ return;
+ while (cs && line[cs - 1] != '\n')
+ cs--;
+ }
+}
+
+/**/
+void
+endofline(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ beginningofline();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ if (cs >= ll) {
+ cs = ll;
+ return;
+ }
+ if (line[cs] == '\n')
+ if (++cs == ll)
+ return;
+ while (cs != ll && line[cs] != '\n')
+ cs++;
+ }
+}
+
+/**/
+void
+beginningoflinehist(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ endoflinehist();
+ zmult = n;
+ return;
+ }
+ while (n) {
+ if (cs == 0)
+ break;
+ if (line[cs - 1] == '\n')
+ if (!--cs)
+ break;
+ while (cs && line[cs - 1] != '\n')
+ cs--;
+ n--;
+ }
+ if (n) {
+ int m = zmult;
+
+ zmult = n;
+ uphistory();
+ zmult = m;
+ cs = 0;
+ }
+}
+
+/**/
+void
+endoflinehist(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ beginningoflinehist();
+ zmult = n;
+ return;
+ }
+ while (n) {
+ if (cs >= ll) {
+ cs = ll;
+ break;
+ }
+ if (line[cs] == '\n')
+ if (++cs == ll)
+ break;
+ while (cs != ll && line[cs] != '\n')
+ cs++;
+ n--;
+ }
+ if (n) {
+ int m = zmult;
+
+ zmult = n;
+ downhistory();
+ zmult = m;
+ }
+}
+
+/**/
+void
+forwardchar(void)
+{
+ cs += zmult;
+ if (cs > ll)
+ cs = ll;
+ if (cs < 0)
+ cs = 0;
+}
+
+/**/
+void
+backwardchar(void)
+{
+ cs -= zmult;
+ if (cs > ll)
+ cs = ll;
+ if (cs < 0)
+ cs = 0;
+}
+
+/**/
+void
+setmarkcommand(void)
+{
+ mark = cs;
+}
+
+/**/
+void
+exchangepointandmark(void)
+{
+ int x;
+
+ x = mark;
+ mark = cs;
+ cs = x;
+ if (cs > ll)
+ cs = ll;
+}
+
+/**/
+void
+vigotocolumn(void)
+{
+ int x, y;
+
+ findline(&x, &y);
+ if (zmult >= 0)
+ cs = x + zmult - (zmult > 0);
+ else
+ cs = y + zmult;
+ if (cs > y)
+ cs = y;
+ if (cs < x)
+ cs = x;
+}
+
+/**/
+void
+vimatchbracket(void)
+{
+ int ocs = cs, dir, ct;
+ unsigned char oth, me;
+
+ otog:
+ if (cs == ll || line[cs] == '\n') {
+ feep();
+ cs = ocs;
+ return;
+ }
+ switch (me = line[cs]) {
+ case '{':
+ dir = 1;
+ oth = '}';
+ break;
+ case /*{*/ '}':
+ virangeflag = -virangeflag;
+ dir = -1;
+ oth = '{'; /*}*/
+ break;
+ case '(':
+ dir = 1;
+ oth = ')';
+ break;
+ case ')':
+ virangeflag = -virangeflag;
+ dir = -1;
+ oth = '(';
+ break;
+ case '[':
+ dir = 1;
+ oth = ']';
+ break;
+ case ']':
+ virangeflag = -virangeflag;
+ dir = -1;
+ oth = '[';
+ break;
+ default:
+ cs++;
+ goto otog;
+ }
+ ct = 1;
+ while (cs >= 0 && cs < ll && ct) {
+ cs += dir;
+ if (line[cs] == oth)
+ ct--;
+ else if (line[cs] == me)
+ ct++;
+ }
+ if (cs < 0 || cs >= ll) {
+ feep();
+ cs = ocs;
+ } else if(dir > 0 && virangeflag)
+ cs++;
+}
+
+/**/
+void
+viforwardchar(void)
+{
+ int lim = findeol() - invicmdmode();
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ vibackwardchar();
+ zmult = n;
+ return;
+ }
+ if (cs >= lim) {
+ feep();
+ return;
+ }
+ while (n-- && cs < lim)
+ cs++;
+}
+
+/**/
+void
+vibackwardchar(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ viforwardchar();
+ zmult = n;
+ return;
+ }
+ if (cs == findbol()) {
+ feep();
+ return;
+ }
+ while (n--) {
+ cs--;
+ if (cs < 0 || line[cs] == '\n') {
+ cs++;
+ break;
+ }
+ }
+}
+
+/**/
+void
+viendofline(void)
+{
+ int oldcs = cs, n = zmult;
+
+ if (n < 1) {
+ feep();
+ return;
+ }
+ while(n--) {
+ if (cs > ll) {
+ cs = oldcs;
+ feep();
+ return;
+ }
+ cs = findeol() + 1;
+ }
+ cs--;
+ lastcol = 1<<30;
+}
+
+/**/
+void
+vibeginningofline(void)
+{
+ cs = findbol();
+}
+
+static int vfindchar, vfinddir, tailadd;
+
+/**/
+void
+vifindnextchar(void)
+{
+ if ((vfindchar = vigetkey()) != -1) {
+ vfinddir = 1;
+ tailadd = 0;
+ virepeatfind();
+ }
+}
+
+/**/
+void
+vifindprevchar(void)
+{
+ if ((vfindchar = vigetkey()) != -1) {
+ vfinddir = -1;
+ tailadd = 0;
+ virepeatfind();
+ }
+}
+
+/**/
+void
+vifindnextcharskip(void)
+{
+ if ((vfindchar = vigetkey()) != -1) {
+ vfinddir = 1;
+ tailadd = -1;
+ virepeatfind();
+ }
+}
+
+/**/
+void
+vifindprevcharskip(void)
+{
+ if ((vfindchar = vigetkey()) != -1) {
+ vfinddir = -1;
+ tailadd = 1;
+ virepeatfind();
+ }
+}
+
+/**/
+void
+virepeatfind(void)
+{
+ int ocs = cs, n = zmult;
+
+ if (!vfinddir) {
+ feep();
+ return;
+ }
+ if (n < 0) {
+ zmult = -n;
+ virevrepeatfind();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ do
+ cs += vfinddir;
+ while (cs >= 0 && cs < ll && line[cs] != vfindchar && line[cs] != '\n');
+ if (cs < 0 || cs >= ll || line[cs] == '\n') {
+ feep();
+ cs = ocs;
+ return;
+ }
+ }
+ cs += tailadd;
+ if (vfinddir == 1 && virangeflag)
+ cs++;
+}
+
+/**/
+void
+virevrepeatfind(void)
+{
+ if (zmult < 0) {
+ zmult = -zmult;
+ virepeatfind();
+ zmult = -zmult;
+ return;
+ }
+ vfinddir = -vfinddir;
+ virepeatfind();
+ vfinddir = -vfinddir;
+}
+
+/**/
+void
+vifirstnonblank(void)
+{
+ cs = findbol();
+ while (cs != ll && iblank(line[cs]))
+ cs++;
+}
+
+/**/
+void
+visetmark(void)
+{
+ int ch;
+
+ ch = getkey(0);
+ if (ch < 'a' || ch > 'z') {
+ feep();
+ return;
+ }
+ ch -= 'a';
+ vimarkcs[ch] = cs;
+ vimarkline[ch] = histline;
+}
+
+/**/
+void
+vigotomark(void)
+{
+ int ch;
+
+ ch = getkey(0);
+ if (ch == c)
+ ch = 26;
+ else {
+ if (ch < 'a' || ch > 'z') {
+ feep();
+ return;
+ }
+ ch -= 'a';
+ }
+ if (!vimarkline[ch]) {
+ feep();
+ return;
+ }
+ if (curhist != vimarkline[ch]) {
+ char *s;
+
+ remember_edits();
+ if (!(s = qgetevent(vimarkline[ch]))) {
+ vimarkline[ch] = 0;
+ feep();
+ return;
+ }
+ histline = vimarkline[ch];
+ setline(s);
+ }
+ cs = vimarkcs[ch];
+ if (cs > ll)
+ cs = ll;
+}
+
+/**/
+void
+vigotomarkline(void)
+{
+ vigotomark();
+ vifirstnonblank();
+}
diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c
new file mode 100644
index 000000000..ed1420829
--- /dev/null
+++ b/Src/Zle/zle_params.c
@@ -0,0 +1,196 @@
+/*
+ * zle_params.c - ZLE special parameters
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+
+#include "zle_params.pro"
+
+/*
+ * ZLE SPECIAL PARAMETERS:
+ *
+ * These special parameters are created, with a local scope, when
+ * running user-defined widget functions. Reading and writing them
+ * reads and writes bits of ZLE state. The parameters are:
+ *
+ * BUFFER (scalar) entire buffer contents
+ * CURSOR (integer) cursor position; 0 <= $CURSOR <= $#BUFFER
+ * LBUFFER (scalar) portion of buffer to the left of the cursor
+ * RBUFFER (scalar) portion of buffer to the right of the cursor
+ */
+
+#define FN(X) ( (void (*) _((void))) (X) )
+static struct zleparam {
+ char *name;
+ int type;
+ void (*setfn) _((void));
+ void (*getfn) _((void));
+ void (*unsetfn) _((Param, int));
+ void *data;
+} zleparams[] = {
+ { "BUFFER", PM_SCALAR, FN(set_buffer), FN(get_buffer),
+ zleunsetfn, NULL },
+ { "CURSOR", PM_INTEGER, FN(set_cursor), FN(get_cursor),
+ zleunsetfn, NULL },
+ { "LBUFFER", PM_SCALAR, FN(set_lbuffer), FN(get_lbuffer),
+ zleunsetfn, NULL },
+ { "RBUFFER", PM_SCALAR, FN(set_rbuffer), FN(get_rbuffer),
+ zleunsetfn, NULL },
+ { NULL, 0, NULL, NULL, NULL, NULL }
+};
+
+/**/
+void
+makezleparams(void)
+{
+ struct zleparam *zp;
+
+ for(zp = zleparams; zp->name; zp++) {
+ Param pm = createparam(zp->name, zp->type | PM_SPECIAL);
+
+ pm->level = locallevel;
+ pm->u.data = zp->data;
+ switch(PM_TYPE(zp->type)) {
+ case PM_SCALAR:
+ pm->sets.cfn = (void (*) _((Param, char *))) zp->setfn;
+ pm->gets.cfn = (char *(*) _((Param))) zp->getfn;
+ break;
+ case PM_ARRAY:
+ pm->sets.afn = (void (*) _((Param, char **))) zp->setfn;
+ pm->gets.afn = (char **(*) _((Param))) zp->getfn;
+ break;
+ case PM_INTEGER:
+ pm->sets.ifn = (void (*) _((Param, long))) zp->setfn;
+ pm->gets.ifn = (long (*) _((Param))) zp->getfn;
+ break;
+ }
+ pm->unsetfn = zp->unsetfn;
+ }
+}
+
+/* Special unset function for ZLE special parameters: act like the standard *
+ * unset function if this is a user-initiated unset, but nothing is done if *
+ * the parameter is merely going out of scope (which it will do). */
+
+/**/
+static void
+zleunsetfn(Param pm, int exp)
+{
+ if(exp)
+ stdunsetfn(pm, exp);
+}
+
+/**/
+static void
+set_buffer(Param pm, char *x)
+{
+ if(x) {
+ unmetafy(x, &ll);
+ sizeline(ll);
+ strcpy((char *)line, x);
+ zsfree(x);
+ if(cs > ll)
+ cs = ll;
+ } else
+ cs = ll = 0;
+}
+
+/**/
+static char *
+get_buffer(Param pm)
+{
+ return metafy((char *)line, ll, META_HEAPDUP);
+}
+
+/**/
+static void
+set_cursor(Param pm, long x)
+{
+ if(x < 0)
+ cs = 0;
+ else if(x > ll)
+ cs = ll;
+ else
+ cs = x;
+}
+
+/**/
+static long
+get_cursor(Param pm)
+{
+ return cs;
+}
+
+/**/
+static void
+set_lbuffer(Param pm, char *x)
+{
+ char *y;
+ int len;
+
+ if(x)
+ unmetafy(y = x, &len);
+ else
+ y = "", len = 0;
+ sizeline(ll - cs + len);
+ memmove(line + len, line + cs, ll - cs);
+ memcpy(line, y, len);
+ ll = ll - cs + len;
+ cs = len;
+ zsfree(x);
+}
+
+/**/
+static char *
+get_lbuffer(Param pm)
+{
+ return metafy((char *)line, cs, META_HEAPDUP);
+}
+
+/**/
+static void
+set_rbuffer(Param pm, char *x)
+{
+ char *y;
+ int len;
+
+ if(x)
+ unmetafy(y = x, &len);
+ else
+ y = "", len = 0;
+ sizeline(ll = cs + len);
+ memcpy(line + cs, y, len);
+ zsfree(x);
+}
+
+/**/
+static char *
+get_rbuffer(Param pm)
+{
+ return metafy((char *)line + cs, ll - cs, META_HEAPDUP);
+}
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
new file mode 100644
index 000000000..4621b5124
--- /dev/null
+++ b/Src/Zle/zle_refresh.c
@@ -0,0 +1,1116 @@
+/*
+ * zle_refresh.c - screen update
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_refresh.pro"
+
+/* Expanded prompts */
+
+/**/
+char *lpptbuf, *rpptbuf;
+
+/* Text attributes after displaying prompts */
+
+/**/
+unsigned pmpt_attr, rpmpt_attr;
+
+/* number of lines displayed */
+
+/**/
+int nlnct;
+
+/* Most lines of the buffer we've shown at once with the current list *
+ * showing. == 0 if there is no list. == -1 if a new list has just *
+ * been put on the screen. == -2 if refresh() needs to put up a new *
+ * list. */
+
+/**/
+int showinglist;
+
+/* Non-zero if ALWAYS_LAST_PROMPT has been used, meaning that the *
+ * screen below the buffer display should not be cleared by *
+ * refresh(), but should be by trashzle(). */
+
+/**/
+int clearflag;
+
+#ifdef HAVE_SELECT
+/* cost of last update */
+/**/
+int cost;
+
+# define SELECT_ADD_COST(X) cost += X
+# define zputc(a, b) putc(a, b), cost++
+# define zwrite(a, b, c, d) fwrite(a, b, c, d), cost += (b * c)
+#else
+# define SELECT_ADD_COST(X)
+# define zputc(a, b) putc(a, b)
+# define zwrite(a, b, c, d) fwrite(a, b, c, d)
+#endif
+
+/* Oct/Nov 94: <mason> some code savagely redesigned to fix several bugs -
+ refreshline() & tc_rightcurs() majorly rewritten; refresh() fixed -
+ I've put my fingers into just about every routine in here -
+ any queries about updates to mason@werple.net.au */
+
+static char **nbuf = NULL, /* new video buffer line-by-line char array */
+ **obuf = NULL; /* old video buffer line-by-line char array */
+static int more_start, /* more text before start of screen? */
+ more_end, /* more stuff after end of screen? */
+ lppth, /* lines taken up by the prompt */
+ olnct, /* previous number of lines */
+ ovln, /* previous video cursor position line */
+ pptw, rpw, /* prompt widths on screen */
+ rppth, /* right prompt height */
+ vcs, vln, /* video cursor position column & line */
+ vmaxln, /* video maximum number of lines */
+ winw, winh, rwinh, /* window width & height */
+ winpos; /* singlelinezle: line's position in window */
+
+/**/
+void
+resetvideo(void)
+{
+ int ln;
+ static int lwinw = -1, lwinh = -1; /* last window width & height */
+
+ genprompts();
+ winw = columns; /* terminal width */
+ if (termflags & TERM_SHORT)
+ winh = 1;
+ else
+ winh = (lines < 2) ? 24 : lines;
+ rwinh = lines; /* keep the real number of lines */
+ winpos = vln = vmaxln = 0;
+ if (lwinw != winw || lwinh != winh) {
+ if (nbuf) {
+ for (ln = 0; ln != lwinh; ln++) {
+ zfree(nbuf[ln], lwinw + 2);
+ zfree(obuf[ln], lwinw + 2);
+ }
+ free(nbuf);
+ free(obuf);
+ }
+ nbuf = (char **)zcalloc((winh + 1) * sizeof(char *));
+ obuf = (char **)zcalloc((winh + 1) * sizeof(char *));
+ nbuf[0] = (char *)zalloc(winw + 2);
+ obuf[0] = (char *)zalloc(winw + 2);
+
+ lwinw = winw;
+ lwinh = winh;
+ }
+ for (ln = 0; ln != winh + 1; ln++) {
+ if (nbuf[ln])
+ *nbuf[ln] = '\0';
+ if (obuf[ln])
+ *obuf[ln] = '\0';
+ }
+
+ if (pptw) {
+ memset(nbuf[0], ' ', pptw);
+ memset(obuf[0], ' ', pptw);
+ nbuf[0][pptw] = obuf[0][pptw] = '\0';
+ }
+
+ vcs = pptw;
+ olnct = nlnct = 0;
+ if (showinglist > 0)
+ showinglist = -2;
+}
+
+/*
+ * Nov 96: <mason> changed to single line scroll
+ */
+
+/**/
+static void
+scrollwindow(int tline)
+{
+ int t0;
+ char *s;
+
+ s = nbuf[tline];
+ for (t0 = tline; t0 < winh - 1; t0++)
+ nbuf[t0] = nbuf[t0 + 1];
+ nbuf[winh - 1] = s;
+ if (!tline)
+ more_start = 1;
+ return;
+}
+
+/* this is the messy part. */
+/* this define belongs where it's used!!! */
+
+#define nextline \
+{ \
+ *s = '\0'; \
+ if (ln != winh - 1) \
+ ln++; \
+ else { \
+ if (!canscroll) { \
+ if (nvln != -1 && nvln != winh - 1 \
+ && (numscrolls != onumscrolls - 1 \
+ || nvln <= winh / 2)) \
+ break; \
+ numscrolls++; \
+ canscroll = winh / 2; \
+ } \
+ canscroll--; \
+ scrollwindow(0); \
+ if (nvln != -1) \
+ nvln--; \
+ } \
+ if (!nbuf[ln]) \
+ nbuf[ln] = (char *)zalloc(winw + 2); \
+ s = (unsigned char *)nbuf[ln]; \
+ sen = s + winw; \
+}
+
+#define snextline \
+{ \
+ *s = '\0'; \
+ if (ln != winh - 1) \
+ ln++; \
+ else \
+ if (tosln < 3) { \
+ more_status = 1; \
+ scrollwindow(tosln + 1); \
+ } else if (tosln - 1 <= nvln) { \
+ scrollwindow(0); \
+ if (nvln) \
+ nvln--, tosln--; \
+ } else { \
+ tosln--; \
+ scrollwindow(tosln); \
+ } \
+ if (!nbuf[ln]) \
+ nbuf[ln] = (char *)zalloc(winw + 2); \
+ s = (unsigned char *)nbuf[ln]; \
+ sen = s + winw; \
+}
+
+static int cleareol, /* clear to end-of-line (if can't cleareod) */
+ clearf, /* alwayslastprompt used immediately before */
+ put_rpmpt, /* whether we should display right-prompt */
+ oput_rpmpt, /* whether displayed right-prompt last time */
+ oxtabs, /* oxtabs - tabs expand to spaces if set */
+ numscrolls, onumscrolls;
+
+/**/
+void
+refresh(void)
+{
+ static int inlist; /* avoiding recursion */
+ int canscroll = 0, /* number of lines we are allowed to scroll */
+ ln = 0, /* current line we're working on */
+ more_status = 0, /* more stuff in status line */
+ nvcs = 0, nvln = -1, /* video cursor column and line */
+ t0 = -1, /* tmp */
+ tosln = 0; /* tmp in statusline stuff */
+ unsigned char *s, /* pointer into the video buffer */
+ *t, /* pointer into the real buffer */
+ *sen, /* pointer to end of the video buffer (eol) */
+ *scs; /* pointer to cursor position in real buffer */
+ char **qbuf; /* tmp */
+
+ /* If this is called from listmatches() (indirectly via trashzle()), and *
+ * that was called from the end of refresh(), then we don't need to do *
+ * anything. All this `inlist' code is actually unnecessary, but it *
+ * improves speed a little in a common case. */
+ if (inlist)
+ return;
+
+#ifdef HAVE_SELECT
+ cost = 0; /* reset */
+#endif
+
+/* Nov 96: <mason> I haven't checked how complete this is. sgtty stuff may
+ or may not work */
+ oxtabs = ((SGTTYFLAG & SGTABTYPE) == SGTABTYPE);
+
+ cleareol = 0; /* unset */
+ more_start = more_end = 0; /* unset */
+ if (isset(SINGLELINEZLE) || lines < 3
+ || (termflags & (TERM_NOUP | TERM_BAD | TERM_UNKNOWN)))
+ termflags |= TERM_SHORT;
+ else
+ termflags &= ~TERM_SHORT;
+ if (resetneeded) {
+ onumscrolls = 0;
+ setterm();
+#ifdef TIOCGWINSZ
+ if (winchanged) {
+ moveto(0, 0);
+ t0 = olnct; /* this is to clear extra lines even when */
+ winchanged = 0; /* the terminal cannot TCCLEAREOD */
+ }
+#endif
+ resetvideo();
+ resetneeded = 0; /* unset */
+ oput_rpmpt = 0; /* no right-prompt currently on screen */
+
+ /* we probably should only have explicitly set attributes */
+ tsetcap(TCALLATTRSOFF, 0);
+ tsetcap(TCSTANDOUTEND, 0);
+ tsetcap(TCUNDERLINEEND, 0);
+
+ if (!clearflag)
+ if (tccan(TCCLEAREOD))
+ tcout(TCCLEAREOD);
+ else
+ cleareol = 1; /* request: clear to end of line */
+ if (t0 > -1)
+ olnct = t0;
+ if (termflags & TERM_SHORT)
+ vcs = 0;
+ else if (!clearflag && lpptbuf[0])
+ zputs(lpptbuf, shout);
+ if (clearflag) {
+ zputc('\r', shout);
+ vcs = 0;
+ moveto(0, pptw);
+ }
+ fflush(shout);
+ clearf = clearflag;
+ } else if (winw != columns || rwinh != lines)
+ resetvideo();
+
+/* now winw equals columns and winh equals lines
+ width comparisons can be made with winw, height comparisons with winh */
+
+ if (termflags & TERM_SHORT) {
+ singlerefresh();
+ return;
+ }
+
+ if (cs < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "BUG: negative cursor position\n");
+ fflush(stderr);
+#endif
+ cs = 0;
+ }
+ scs = line + cs;
+ numscrolls = 0;
+
+/* first, we generate the video line buffers so we know what to put on
+ the screen - also determine final cursor position (nvln, nvcs) */
+
+ /* Deemed necessary by PWS 1995/05/15 due to kill-line problems */
+ if (!*nbuf)
+ *nbuf = (char *)zalloc(winw + 2);
+
+ s = (unsigned char *)(nbuf[ln = 0] + pptw);
+ t = line;
+ sen = (unsigned char *)(*nbuf + winw);
+ for (; t < line+ll; t++) {
+ if (t == scs) /* if cursor is here, remember it */
+ nvcs = s - (unsigned char *)(nbuf[nvln = ln]);
+
+ if (*t == '\n') { /* newline */
+ nbuf[ln][winw + 1] = '\0'; /* text not wrapped */
+ nextline
+ } else if (*t == '\t') { /* tab */
+ t0 = (char *)s - nbuf[ln];
+ if ((t0 | 7) + 1 >= winw) {
+ nbuf[ln][winw + 1] = '\n'; /* text wrapped */
+ nextline
+ } else
+ do
+ *s++ = ' ';
+ while ((++t0) & 7);
+ } else if (icntrl(*t)) { /* other control character */
+ *s++ = '^';
+ if (s == sen) {
+ nbuf[ln][winw + 1] = '\n'; /* text wrapped */
+ nextline
+ }
+ *s++ = (*t == 127) ? '?' : (*t | '@');
+ } else /* normal character */
+ *s++ = *t;
+ if (s == sen) {
+ nbuf[ln][winw + 1] = '\n'; /* text wrapped */
+ nextline
+ }
+ }
+
+/* if we're really on the next line, don't fake it; do everything properly */
+ if (t == scs && (nvcs = s - (unsigned char *)(nbuf[nvln = ln])) == winw) {
+ nbuf[ln][winw + 1] = '\n'; /* text wrapped */
+ switch ('\0') { /* a sad hack to make the break */
+ case '\0': /* in nextline work */
+ nextline
+ }
+ *s = '\0';
+ nvcs = 0;
+ nvln++;
+ }
+
+ if (t != line + ll)
+ more_end = 1;
+
+ if (statusline) {
+ tosln = ln + 1;
+ if (ln == winh - 1) {
+ if (nvln > 0) {
+ scrollwindow(0);
+ nvln--;
+ }
+ tosln--;
+ }
+ nbuf[ln][winw + 1] = '\0'; /* text not wrapped */
+ snextline
+ t = (unsigned char *)statusline;
+ for (; t < (unsigned char *)statusline + statusll; t++) {
+ if (icntrl(*t)) { /* simplified processing in the status line */
+ *s++ = '^';
+ if (s == sen) {
+ nbuf[ln][winw + 1] = '\n'; /* text wrapped */
+ snextline
+ }
+ *s++ = (*t == 127) ? '?' : (*t | '@');
+ } else
+ *s++ = *t;
+ if (s == sen) {
+ nbuf[ln][winw + 1] = '\n'; /* text wrapped */
+ snextline
+ }
+ }
+ }
+
+/* insert <.... at end of last line if there is more text past end of screen */
+ if (more_end) {
+ if (!statusline)
+ tosln = winh;
+ strncpy(nbuf[tosln - 1] + winw - 7, " <.... ", 7);
+ nbuf[tosln - 1][winw] = nbuf[tosln - 1][winw + 1] = '\0';
+ }
+
+/* insert <....> at end of first status line if status is too big */
+ if (more_status) {
+ strncpy(nbuf[tosln] + winw - 8, " <....> ", 8);
+ nbuf[tosln][winw] = nbuf[tosln][winw + 1] = '\0';
+ }
+
+ *s = '\0';
+ nlnct = ln + 1;
+ for (ln = nlnct; ln < winh; ln++)
+ zfree(nbuf[ln], winw + 2), nbuf[ln] = NULL;
+
+/* determine whether the right-prompt exists and can fit on the screen */
+ if (!more_start)
+ put_rpmpt = rppth == 1 && rpptbuf[0] && !strchr(rpptbuf, '\t') &&
+ (int)strlen(nbuf[0]) + rpw < winw - 1;
+ else {
+/* insert >.... on first line if there is more text before start of screen */
+ memset(nbuf[0], ' ', pptw);
+ t0 = winw - pptw;
+ t0 = t0 > 5 ? 5 : t0;
+ strncpy(nbuf[0] + pptw, ">....", t0);
+ memset(nbuf[0] + pptw + t0, ' ', winw - t0 - pptw);
+ nbuf[0][winw] = nbuf[0][winw + 1] = '\0';
+ }
+
+ for (ln = 0; !clearf && (ln < nlnct); ln++) {
+ /* if we have more lines than last time, clear the newly-used lines */
+ if (ln >= olnct)
+ cleareol = 1;
+
+ /* if old line and new line are different,
+ see if we can insert/delete a line to speed up update */
+
+ if (ln < olnct - 1 && !(hasam && vcs == winw) &&
+ nbuf[ln] && obuf[ln] &&
+ strncmp(nbuf[ln], obuf[ln], 16)) {
+ if (tccan(TCDELLINE) && obuf[ln + 1] && obuf[ln + 1][0] &&
+ nbuf[ln] && !strncmp(nbuf[ln], obuf[ln + 1], 16)) {
+ moveto(ln, 0);
+ tcout(TCDELLINE);
+ zfree(obuf[ln], winw + 2);
+ for (t0 = ln; t0 != olnct; t0++)
+ obuf[t0] = obuf[t0 + 1];
+ obuf[--olnct] = NULL;
+ }
+ /* don't try to insert a line if olnct = vmaxln (vmaxln is the number
+ of lines that have been displayed by this routine) so that we don't
+ go off the end of the screen. */
+
+ else if (tccan(TCINSLINE) && olnct < vmaxln && nbuf[ln + 1] &&
+ obuf[ln] && !strncmp(nbuf[ln + 1], obuf[ln], 16)) {
+ moveto(ln, 0);
+ tcout(TCINSLINE);
+ for (t0 = olnct; t0 != ln; t0--)
+ obuf[t0] = obuf[t0 - 1];
+ obuf[ln] = NULL;
+ olnct++;
+ }
+ }
+
+ /* update the single line */
+ refreshline(ln);
+
+ /* output the right-prompt if appropriate */
+ if (put_rpmpt && !ln && !oput_rpmpt) {
+ moveto(0, winw - 1 - rpw);
+ zputs(rpptbuf, shout);
+ vcs = winw - 1;
+ /* reset character attributes to that set by the main prompt */
+ txtchange = pmpt_attr;
+ if (txtchangeisset(TXTNOBOLDFACE) && (rpmpt_attr & TXTBOLDFACE))
+ tsetcap(TCALLATTRSOFF, 0);
+ if (txtchangeisset(TXTNOSTANDOUT) && (rpmpt_attr & TXTSTANDOUT))
+ tsetcap(TCSTANDOUTEND, 0);
+ if (txtchangeisset(TXTNOUNDERLINE) && (rpmpt_attr & TXTUNDERLINE))
+ tsetcap(TCUNDERLINEEND, 0);
+ if (txtchangeisset(TXTBOLDFACE) && (rpmpt_attr & TXTNOBOLDFACE))
+ tsetcap(TCBOLDFACEBEG, 0);
+ if (txtchangeisset(TXTSTANDOUT) && (rpmpt_attr & TXTNOSTANDOUT))
+ tsetcap(TCSTANDOUTBEG, 0);
+ if (txtchangeisset(TXTUNDERLINE) && (rpmpt_attr & TXTNOUNDERLINE))
+ tsetcap(TCUNDERLINEBEG, 0);
+ }
+ }
+
+/* if old buffer had extra lines, set them to be cleared and refresh them
+individually */
+
+ if (olnct > nlnct) {
+ cleareol = 1;
+ for (ln = nlnct; ln < olnct; ln++)
+ refreshline(ln);
+ }
+
+/* reset character attributes */
+ if (clearf && postedit) {
+ if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr)) {
+ if (txtchangeisset(TXTNOBOLDFACE))
+ tsetcap(TCALLATTRSOFF, 0);
+ if (txtchangeisset(TXTNOSTANDOUT))
+ tsetcap(TCSTANDOUTEND, 0);
+ if (txtchangeisset(TXTNOUNDERLINE))
+ tsetcap(TCUNDERLINEEND, 0);
+ if (txtchangeisset(TXTBOLDFACE))
+ tsetcap(TCBOLDFACEBEG, 0);
+ if (txtchangeisset(TXTSTANDOUT))
+ tsetcap(TCSTANDOUTBEG, 0);
+ if (txtchangeisset(TXTUNDERLINE))
+ tsetcap(TCUNDERLINEBEG, 0);
+ }
+ }
+ clearf = 0;
+
+/* move to the new cursor position */
+ moveto(nvln, nvcs);
+
+/* swap old and new buffers - better than freeing/allocating every time */
+ qbuf = nbuf;
+ nbuf = obuf;
+ obuf = qbuf;
+/* store current values so we can use them next time */
+ ovln = nvln;
+ olnct = nlnct;
+ oput_rpmpt = put_rpmpt;
+ onumscrolls = numscrolls;
+ if (nlnct > vmaxln)
+ vmaxln = nlnct;
+ fflush(shout); /* make sure everything is written out */
+
+ /* if we have a new list showing, note it; if part of the list has been
+ overwritten, redisplay it. */
+ if (showinglist == -2 || (showinglist > 0 && showinglist < nlnct)) {
+ inlist = 1;
+ listmatches();
+ inlist = 0;
+ refresh();
+ }
+ if (showinglist == -1)
+ showinglist = nlnct;
+}
+
+#define tcinscost(X) (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS])
+#define tcdelcost(X) (tccan(TCMULTDEL) ? tclen[TCMULTDEL] : (X)*tclen[TCDEL])
+#define tc_delchars(X) (void) tcmultout(TCDEL, TCMULTDEL, (X))
+#define tc_inschars(X) (void) tcmultout(TCINS, TCMULTINS, (X))
+#define tc_upcurs(X) (void) tcmultout(TCUP, TCMULTUP, (X))
+#define tc_leftcurs(X) (void) tcmultout(TCLEFT, TCMULTLEFT, (X))
+
+/* refresh one line, using whatever speed-up tricks are provided by the tty */
+
+/**/
+static void
+refreshline(int ln)
+{
+ char *nl, *ol, *p1; /* line buffer pointers */
+ int ccs = 0, /* temporary count for cursor position */
+ char_ins = 0, /* number of characters inserted/deleted */
+ col_cleareol, /* clear to end-of-line from this column */
+ i, j, /* tmp */
+ ins_last, /* insert pushed last character off line */
+ nllen, ollen, /* new and old line buffer lengths */
+ rnllen; /* real new line buffer length */
+
+/* 0: setup */
+ nl = nbuf[ln];
+ rnllen = nllen = nl ? strlen(nl) : 0;
+ ol = obuf[ln] ? obuf[ln] : "";
+ ollen = strlen(ol);
+
+/* optimisation: can easily happen for clearing old lines. If the terminal has
+ the capability, then this is the easiest way to skip unnecessary stuff */
+ if (cleareol && !nllen && !(hasam && ln < nlnct - 1)
+ && tccan(TCCLEAREOL)) {
+ moveto(ln, 0);
+ tcout(TCCLEAREOL);
+ return;
+ }
+
+/* 1: pad out the new buffer with spaces to contain _all_ of the characters
+ which need to be written. do this now to allow some pre-processing */
+
+ if (cleareol /* request to clear to end of line */
+ || !nllen /* no line buffer given */
+ || (ln == 0 && (put_rpmpt != oput_rpmpt))) { /* prompt changed */
+ p1 = halloc(winw + 2);
+ if (nllen)
+ strncpy(p1, nl, nllen);
+ memset(p1 + nllen, ' ', winw - nllen);
+ p1[winw] = '\0';
+ p1[winw + 1] = (nllen < winw) ? '\0' : nl[winw + 1];
+ if (ln && nbuf[ln])
+ memcpy(nl, p1, winw + 2); /* next time obuf will be up-to-date */
+ else
+ nl = p1; /* don't keep the padding for prompt line */
+ nllen = winw;
+ } else if (ollen > nllen) { /* make new line at least as long as old */
+ p1 = halloc(ollen + 1);
+ strncpy(p1, nl, nllen);
+ memset(p1 + nllen, ' ', ollen - nllen);
+ p1[ollen] = '\0';
+ nl = p1;
+ nllen = ollen;
+ }
+
+/* 2: see if we can clear to end-of-line, and if it's faster, work out where
+ to do it from - we can normally only do so if there's no right-prompt.
+ With automatic margins, we shouldn't do it if there is another line, in
+ case it messes up cut and paste. */
+
+ if (hasam && ln < nlnct - 1 && rnllen == winw)
+ col_cleareol = -2; /* clearing eol would be evil so don't */
+ else {
+ col_cleareol = -1;
+ if (tccan(TCCLEAREOL) && (nllen == winw || put_rpmpt != oput_rpmpt)) {
+ for (i = nllen; i && nl[i - 1] == ' '; i--);
+ for (j = ollen; j && ol[j - 1] == ' '; j--);
+ if ((j > i + tclen[TCCLEAREOL]) /* new buf has enough spaces */
+ || (nllen == winw && nl[winw - 1] == ' '))
+ col_cleareol = i;
+ }
+ }
+
+/* 2b: first a new trick for automargin niceness - good for cut and paste */
+
+ if (hasam && vcs == winw) {
+ if (nbuf[vln] && nbuf[vln][vcs + 1] == '\n') {
+ vln++, vcs = 1;
+ if (nbuf[vln] && *nbuf[vln])
+ zputc(*nbuf[vln], shout);
+ else
+ zputc(' ', shout); /* I don't think this should happen */
+ if (ln == vln) { /* better safe than sorry */
+ nl++;
+ if (*ol)
+ ol++;
+ ccs = 1;
+ } /* else hmmm... I wonder what happened */
+ } else {
+ vln++, vcs = 0;
+ zputc('\n', shout);
+ }
+ }
+ ins_last = 0;
+
+/* 2c: if we're on the first line, start checking at the end of the prompt;
+ we shouldn't be doing anything within the prompt */
+
+ if (ln == 0 && pptw) {
+ i = pptw - ccs;
+ j = strlen(ol);
+ nl += i;
+ ol += (i > j ? j : i); /* if ol is too short, point it to '\0' */
+ ccs = pptw;
+ }
+
+/* 3: main display loop - write out the buffer using whatever tricks we can */
+
+ for (;;) {
+ if (*nl && *ol && nl[1] == ol[1]) /* skip only if second chars match */
+ /* skip past all matching characters */
+ for (; *nl && (*nl == *ol); nl++, ol++, ccs++) ;
+
+ if (!*nl) {
+ if (ccs == winw && hasam && char_ins > 0 && ins_last
+ && vcs != winw) {
+ nl--; /* we can assume we can go back here */
+ moveto(ln, winw - 1);
+ zputc(*nl, shout);
+ vcs++;
+ return; /* write last character in line */
+ }
+ if ((char_ins <= 0) || (ccs >= winw)) /* written everything */
+ return;
+ if (tccan(TCCLEAREOL) && (char_ins >= tclen[TCCLEAREOL])
+ && col_cleareol != -2)
+ /* we've got junk on the right yet to clear */
+ col_cleareol = 0; /* force a clear to end of line */
+ }
+
+ moveto(ln, ccs); /* move to where we do all output from */
+
+ /* if we can finish quickly, do so */
+ if ((col_cleareol >= 0) && (ccs >= col_cleareol)) {
+ tcout(TCCLEAREOL);
+ return;
+ }
+
+ /* we've written out the new but yet to clear rubbish due to inserts */
+ if (!*nl) {
+ i = (winw - ccs < char_ins) ? (winw - ccs) : char_ins;
+ if (tccan(TCDEL) && (tcdelcost(i) <= i + 1))
+ tc_delchars(i);
+ else {
+ vcs += i;
+ while (i-- > 0)
+ zputc(' ', shout);
+ }
+ return;
+ }
+
+ /* if we've reached the end of the old buffer, then there are few tricks
+ we can do, so we just dump out what we must and clear if we can */
+ if (!*ol) {
+ i = (col_cleareol >= 0) ? col_cleareol : nllen;
+ i -= vcs;
+ zwrite(nl, i, 1, shout);
+ vcs += i;
+ if (col_cleareol >= 0)
+ tcout(TCCLEAREOL);
+ return;
+ }
+
+ /* inserting & deleting chars: we can if there's no right-prompt */
+ if ((ln || !put_rpmpt || !oput_rpmpt)
+ && (nl[1] && ol[1] && nl[1] != ol[1])) {
+
+ /* deleting characters - see if we can find a match series that
+ makes it cheaper to delete intermediate characters
+ eg. oldline: hifoobar \ hopefully cheaper here to delete two
+ newline: foobar / characters, then we have six matches */
+ if (tccan(TCDEL)) {
+ for (i = 1; *(ol + i); i++)
+ if (tcdelcost(i) < pfxlen(ol + i, nl)) {
+ tc_delchars(i);
+ ol += i;
+ char_ins -= i;
+ i = 0;
+ break;
+ }
+ if (!i)
+ continue;
+ }
+ /* inserting characters - characters pushed off the right should be
+ annihilated, but we don't do this if we're on the last line lest
+ undesired scrolling occurs due to `illegal' characters on screen */
+
+ if (tccan(TCINS) && (vln != lines - 1)) { /* not on last line */
+ for (i = 1; *(nl + i); i++)
+ if (tcinscost(i) < pfxlen(nl + i, ol)) {
+ tc_inschars(i);
+ zwrite(nl, i, 1, shout);
+ nl += i;
+ char_ins += i;
+ ccs = (vcs += i);
+ /* if we've pushed off the right, truncate oldline */
+ for (i = 0; *(ol + i) && i < winw - ccs; i++);
+ if (i == winw - ccs) {
+ *(ol + i) = '\0';
+ ins_last = 1;
+ }
+ i = 0;
+ break;
+ }
+ if (!i)
+ continue;
+ }
+ }
+ /* we can't do any fancy tricks, so just dump the single character
+ and keep on trying */
+ zputc(*nl, shout);
+ nl++, ol++;
+ ccs++, vcs++;
+ }
+}
+
+/* move the cursor to line ln (relative to the prompt line),
+ absolute column cl; update vln, vcs - video line and column */
+
+/**/
+void
+moveto(int ln, int cl)
+{
+ int c;
+
+ if (vcs == winw) {
+ vln++, vcs = 0;
+ if (!hasam) {
+ zputc('\r', shout);
+ zputc('\n', shout);
+ } else {
+ if ((vln < nlnct) && nbuf[vln] && *nbuf[vln])
+ c = *nbuf[vln];
+ else
+ c = ' ';
+ zputc(c, shout);
+ zputc('\r', shout);
+ if ((vln < olnct) && obuf[vln] && *obuf[vln])
+ *obuf[vln] = c;
+ }
+ }
+
+ if (ln == vln && cl == vcs)
+ return;
+
+/* move up */
+ if (ln < vln) {
+ tc_upcurs(vln - ln);
+ vln = ln;
+ }
+/* move down; if we might go off the end of the screen, use newlines
+ instead of TCDOWN */
+
+ while (ln > vln) {
+ if (vln < vmaxln - 1)
+ if (ln > vmaxln - 1) {
+ if (tc_downcurs(vmaxln - 1 - vln))
+ vcs = 0;
+ vln = vmaxln - 1;
+ } else {
+ if (tc_downcurs(ln - vln))
+ vcs = 0;
+ vln = ln;
+ continue;
+ }
+ zputc('\r', shout), vcs = 0; /* safety precaution */
+ while (ln > vln) {
+ zputc('\n', shout);
+ vln++;
+ }
+ }
+
+ if (cl == vcs)
+ return;
+
+/* choose cheapest movements for ttys without multiple movement capabilities -
+ do this now because it's easier (to code) */
+ if (cl <= vcs / 2) {
+ zputc('\r', shout);
+ vcs = 0;
+ }
+ if (vcs < cl)
+ tc_rightcurs(cl);
+ else if (vcs > cl)
+ tc_leftcurs(vcs - cl);
+ vcs = cl;
+}
+
+/**/
+int
+tcmultout(int cap, int multcap, int ct)
+{
+ if (tccan(multcap) && (!tccan(cap) || tclen[multcap] <= tclen[cap] * ct)) {
+ tcoutarg(multcap, ct);
+ return 1;
+ } else if (tccan(cap)) {
+ while (ct--)
+ tcout(cap);
+ return 1;
+ }
+ return 0;
+}
+
+/**/
+static void
+tc_rightcurs(int cl)
+{
+ int ct, /* number of characters to move across */
+ i = vcs, /* cursor position after initial movements */
+ j;
+ char *t;
+
+ ct = cl - vcs;
+
+/* do a multright if we can - it's the most reliable */
+ if (tccan(TCMULTRIGHT)) {
+ tcoutarg(TCMULTRIGHT, ct);
+ return;
+ }
+
+/* try tabs if tabs are non destructive and multright is not possible */
+ if (!oxtabs && tccan(TCNEXTTAB) && ((vcs | 7) < cl)) {
+ i = (vcs | 7) + 1;
+ tcout(TCNEXTTAB);
+ for ( ; i + 8 <= cl; i += 8)
+ tcout(TCNEXTTAB);
+ if ((ct = cl - i) == 0) /* number of chars still to move across */
+ return;
+ }
+
+/* otherwise _carefully_ write the contents of the video buffer.
+ if we're anywhere in the prompt, goto the left column and write the whole
+ prompt out unless ztrlen(lpptbuf) == pptw : we can cheat then */
+ if (vln == 0 && i < pptw) {
+ if (strlen(lpptbuf) == pptw)
+ fputs(lpptbuf + i, shout);
+ else if (tccan(TCRIGHT) && (tclen[TCRIGHT] * ct <= ztrlen(lpptbuf)))
+ /* it is cheaper to send TCRIGHT than reprint the whole prompt */
+ for (ct = pptw - i; ct--; )
+ tcout(TCRIGHT);
+ else {
+ if (i != 0)
+ zputc('\r', shout);
+ tc_upcurs(lppth - 1);
+ zputs(lpptbuf, shout);
+ }
+ i = pptw;
+ ct = cl - i;
+ }
+
+ if (nbuf[vln]) {
+ for (j = 0, t = nbuf[vln]; *t && (j < i); j++, t++);
+ if (j == i)
+ for ( ; *t && ct; ct--, t++)
+ zputc(*t, shout);
+ }
+ while (ct--)
+ zputc(' ', shout); /* not my fault your terminal can't go right */
+}
+
+/**/
+static int
+tc_downcurs(int ct)
+{
+ int ret = 0;
+
+ if (ct && !tcmultout(TCDOWN, TCMULTDOWN, ct)) {
+ while (ct--)
+ zputc('\n', shout);
+ zputc('\r', shout), ret = -1;
+ }
+ return ret;
+}
+
+/**/
+void
+tcout(int cap)
+{
+ tputs(tcstr[cap], 1, putshout);
+ SELECT_ADD_COST(tclen[cap]);
+}
+
+/**/
+static void
+tcoutarg(int cap, int arg)
+{
+ char *result;
+
+ result = tgoto(tcstr[cap], arg, arg);
+ tputs(result, 1, putshout);
+ SELECT_ADD_COST(strlen(result));
+}
+
+/**/
+void
+clearscreen(void)
+{
+ tcout(TCCLEARSCREEN);
+ resetneeded = 1;
+ clearflag = 0;
+}
+
+/**/
+void
+redisplay(void)
+{
+ moveto(0, 0);
+ zputc('\r', shout); /* extra care */
+ tc_upcurs(lppth - 1);
+ resetneeded = 1;
+ clearflag = 0;
+}
+
+/**/
+static void
+singlerefresh(void)
+{
+ char *vbuf, *vp, /* video buffer and pointer */
+ **qbuf, /* tmp */
+ *refreshop = *obuf; /* pointer to old video buffer */
+ int t0, /* tmp */
+ vsiz, /* size of new video buffer */
+ nvcs = 0; /* new video cursor column */
+
+ nlnct = 1;
+/* generate the new line buffer completely */
+ for (vsiz = 1 + pptw, t0 = 0; t0 != ll; t0++, vsiz++)
+ if (line[t0] == '\t')
+ vsiz = (vsiz | 7) + 1;
+ else if (icntrl(line[t0]))
+ vsiz++;
+ vbuf = (char *)zalloc(vsiz);
+
+ if (cs < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "BUG: negative cursor position\n");
+ fflush(stderr);
+#endif
+ cs = 0;
+ }
+
+ memcpy(vbuf, strchr(lpptbuf, 0) - pptw, pptw); /* only use last part of prompt */
+ vbuf[pptw] = '\0';
+ vp = vbuf + pptw;
+
+ for (t0 = 0; t0 != ll; t0++) {
+ if (line[t0] == '\t')
+ for (*vp++ = ' '; (vp - vbuf) & 7; )
+ *vp++ = ' ';
+ else if (line[t0] == '\n') {
+ *vp++ = '\\';
+ *vp++ = 'n';
+ } else if (line[t0] == 0x7f) {
+ *vp++ = '^';
+ *vp++ = '?';
+ } else if (icntrl(line[t0])) {
+ *vp++ = '^';
+ *vp++ = line[t0] | '@';
+ } else
+ *vp++ = line[t0];
+ if (t0 == cs)
+ nvcs = vp - vbuf - 1;
+ }
+ if (t0 == cs)
+ nvcs = vp - vbuf;
+ *vp = '\0';
+
+/* determine which part of the new line buffer we want for the display */
+ if ((winpos && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)) {
+ if ((winpos = nvcs - ((winw - hasam) / 2)) < 0)
+ winpos = 0;
+ }
+ if (winpos)
+ vbuf[winpos] = '<'; /* line continues to the left */
+ if ((int)strlen(vbuf + winpos) > (winw - hasam)) {
+ vbuf[winpos + winw - hasam - 1] = '>'; /* line continues to right */
+ vbuf[winpos + winw - hasam] = '\0';
+ }
+ strcpy(nbuf[0], vbuf + winpos);
+ zfree(vbuf, vsiz);
+ nvcs -= winpos;
+
+/* display the `visable' portion of the line buffer */
+ for (t0 = 0, vp = *nbuf;;) {
+ /* skip past all matching characters */
+ for (; *vp && *vp == *refreshop; t0++, vp++, refreshop++) ;
+
+ if (!*vp && !*refreshop)
+ break;
+
+ singmoveto(t0); /* move to where we do all output from */
+
+ if (!*refreshop) {
+ if ((t0 = strlen(vp)))
+ zwrite(vp, t0, 1, shout);
+ vcs += t0;
+ break;
+ }
+ if (!*vp) {
+ if (tccan(TCCLEAREOL))
+ tcout(TCCLEAREOL);
+ else
+ for (; *refreshop++; vcs++)
+ zputc(' ', shout);
+ break;
+ }
+ zputc(*vp, shout);
+ vcs++, t0++;
+ vp++, refreshop++;
+ }
+/* move to the new cursor position */
+ singmoveto(nvcs);
+
+ qbuf = nbuf;
+ nbuf = obuf;
+ obuf = qbuf;
+ fflush(shout); /* make sure everything is written out */
+}
+
+/**/
+static void
+singmoveto(int pos)
+{
+ if (pos == vcs)
+ return;
+ if (pos <= vcs / 2) {
+ zputc('\r', shout);
+ vcs = 0;
+ }
+ if (pos < vcs) {
+ tc_leftcurs(vcs - pos);
+ vcs = pos;
+ }
+ if (pos > vcs) {
+ if (tcmultout(TCRIGHT, TCMULTRIGHT, pos - vcs))
+ vcs = pos;
+ else
+ while (pos > vcs) {
+ zputc(nbuf[0][vcs], shout);
+ vcs++;
+ }
+ }
+}
+
+/* recheck size of prompts */
+
+/**/
+static void
+genprompts(void)
+{
+ countprompt(lpptbuf, &pptw, &lppth);
+ countprompt(rpptbuf, &rpw, &rppth);
+}
diff --git a/Src/Zle/zle_things.sed b/Src/Zle/zle_things.sed
new file mode 100644
index 000000000..781d23704
--- /dev/null
+++ b/Src/Zle/zle_things.sed
@@ -0,0 +1,9 @@
+/^ *T("/{
+ s/^[^"]*"/ z_/
+ s/".*$/,/
+ s/-//g
+ s/\./D/g
+ P
+ s/ z_\(.*\),/#define t_\1 (\&thingies[z_\1])/
+ P
+}
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
new file mode 100644
index 000000000..c4f2e25e1
--- /dev/null
+++ b/Src/Zle/zle_thingy.c
@@ -0,0 +1,491 @@
+/*
+ * zle_thingy.c - thingies
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_thingy.pro"
+
+/*
+ * Thingies:
+ *
+ * From the user's point of view, a thingy is just a string. Internally,
+ * the thingy is a struct thingy; these structures are in a hash table
+ * indexed by the string the user sees. This hash table contains all
+ * thingies currently referenced anywhere; each has a reference count,
+ * and is deleted when it becomes unused. Being the name of a function
+ * counts as a reference.
+ *
+ * The DISABLED flag on a thingy indicates that it is not the name of a
+ * widget. This makes it easy to generate completion lists;
+ * looking only at the `enabled' nodes makes the thingy table look like
+ * a table of widgets.
+ */
+
+/* Hashtable of thingies. Enabled nodes are those that refer to widgets. */
+
+/**/
+HashTable thingytab;
+
+/**********************************/
+/* hashtable management functions */
+/**********************************/
+
+/**/
+static void
+createthingytab(void)
+{
+ thingytab = newhashtable(199, "thingytab", NULL);
+
+ thingytab->hash = hasher;
+ thingytab->emptytable = emptythingytab;
+ thingytab->filltable = NULL;
+ thingytab->addnode = addhashnode;
+ thingytab->getnode = gethashnode;
+ thingytab->getnode2 = gethashnode2;
+ thingytab->removenode = removehashnode;
+ thingytab->disablenode = NULL;
+ thingytab->enablenode = NULL;
+ thingytab->freenode = freethingynode;
+ thingytab->printnode = NULL;
+}
+
+/**/
+static void
+emptythingytab(HashTable ht)
+{
+ /* This will only be called when deleting the thingy table, which *
+ * is only done to unload the zle module. A normal emptytable() *
+ * function would free all the thingies, but we don't want to do *
+ * that because some of them are the known thingies in the fixed *
+ * `thingies' table. As the module cleanup code deletes all the *
+ * keymaps and so on before deleting the thingy table, we can *
+ * just remove the user-defined widgets and then be sure that *
+ * *all* the thingies left are the fixed ones. This has the side *
+ * effect of freeing all resources used by user-defined widgets. */
+ scanhashtable(thingytab, 0, 0, DISABLED, scanemptythingies, 0);
+}
+
+/**/
+static void
+scanemptythingies(HashNode hn, int flags)
+{
+ Thingy t = (Thingy) hn;
+
+ /* Mustn't unbind internal widgets -- we wouldn't want to free the *
+ * memory they use. */
+ if(!(t->widget->flags & WIDGET_INT))
+ unbindwidget(t, 1);
+}
+
+/**/
+static Thingy
+makethingynode(void)
+{
+ Thingy t = (Thingy) zcalloc(sizeof(*t));
+
+ t->flags = DISABLED;
+ return t;
+}
+
+/**/
+static void
+freethingynode(HashNode hn)
+{
+ Thingy th = (Thingy) hn;
+
+ zsfree(th->nam);
+ zfree(th, sizeof(*th));
+}
+
+/************************/
+/* referencing thingies */
+/************************/
+
+/* It is important to maintain the reference counts on thingies. When *
+ * copying a reference to a thingy, wrap the copy in refthingy(), to *
+ * increase its reference count. When removing a reference, *
+ * unrefthingy() it. Both of these functions handle NULL arguments *
+ * correctly. */
+
+/**/
+Thingy
+refthingy(Thingy th)
+{
+ if(th)
+ th->rc++;
+ return th;
+}
+
+/**/
+void
+unrefthingy(Thingy th)
+{
+ if(th && !--th->rc)
+ thingytab->freenode(thingytab->removenode(thingytab, th->nam));
+}
+
+/* Use rthingy() to turn a string into a thingy. It increases the reference *
+ * count, after creating the thingy structure if necessary. */
+
+/**/
+Thingy
+rthingy(char *nam)
+{
+ Thingy t = (Thingy) thingytab->getnode2(thingytab, nam);
+
+ if(!t)
+ thingytab->addnode(thingytab, ztrdup(nam), t = makethingynode());
+ return refthingy(t);
+}
+
+/***********/
+/* widgets */
+/***********/
+
+/*
+ * Each widget is attached to one or more thingies. Each thingy
+ * names either zero or one widgets. Thingies that name a widget
+ * are treated as being referenced. The widget type, flags and pointer
+ * are stored in a separate structure pointed to by the thingies. Each
+ * thingy also has a pointer to the `next' thingy (in a circular list)
+ * that references the same widget. The DISABLED flag is unset in these
+ * thingies.
+ */
+
+/* Bind a widget to a thingy. The thingy's reference count must already *
+ * have been incremented. The widget may already be bound to other *
+ * thingies; if it is not, then its `first' member must be NULL. Return *
+ * is 0 on success, or -1 if the thingy has the TH_IMMORTAL flag set. */
+
+/**/
+static int
+bindwidget(Widget w, Thingy t)
+{
+ if(t->flags & TH_IMMORTAL) {
+ unrefthingy(t);
+ return -1;
+ }
+ if(!(t->flags & DISABLED)) {
+ if(t->widget == w)
+ return 0;
+ unbindwidget(t, 1);
+ }
+ if(w->first) {
+ t->samew = w->first->samew;
+ w->first->samew = t;
+ } else {
+ w->first = t;
+ t->samew = t;
+ }
+ t->widget = w;
+ t->flags &= ~DISABLED;
+ return 0;
+}
+
+/* Unbind a widget from a thingy. This decrements the thingy's reference *
+ * count. The widget will be destroyed if this is its last name. *
+ * TH_IMMORTAL thingies won't be touched, unless override is non-zero. *
+ * Returns 0 on success, or -1 if the thingy is protected. If the thingy *
+ * doesn't actually reference a widget, this is considered successful. */
+
+/**/
+static int
+unbindwidget(Thingy t, int override)
+{
+ Widget w;
+
+ if(t->flags & DISABLED)
+ return 0;
+ if(!override && (t->flags & TH_IMMORTAL))
+ return -1;
+ w = t->widget;
+ if(t->samew == t)
+ freewidget(w);
+ else {
+ Thingy p;
+ for(p = w->first; p->samew != t; p = p->samew) ;
+ w->first = p; /* optimised for deletezlefunction() */
+ p->samew = t->samew;
+ }
+ t->flags &= ~TH_IMMORTAL;
+ t->flags |= DISABLED;
+ unrefthingy(t);
+ return 0;
+}
+
+/* Free a widget. */
+
+/**/
+static void
+freewidget(Widget w)
+{
+ if(!(w->flags & WIDGET_INT))
+ zsfree(w->u.fnnam);
+ zfree(w, sizeof(*w));
+}
+
+/* Add am internal widget provided by a module. The name given is the *
+ * canonical one, which must not begin with a dot. The widget is first *
+ * bound to the dotted canonical name; if that name is already taken by *
+ * an internal widget, failure is indicated. The same widget is then *
+ * bound to the canonical name, and a pointer to the widget structure *
+ * returned. */
+
+/**/
+Widget
+addzlefunction(char *name, ZleIntFunc ifunc, int flags)
+{
+ VARARR(char, dotn, strlen(name) + 2);
+ Widget w;
+ Thingy t;
+
+ if(name[0] == '.')
+ return NULL;
+ dotn[0] = '.';
+ strcpy(dotn + 1, name);
+ t = (Thingy) thingytab->getnode(thingytab, dotn);
+ if(t && (t->flags & TH_IMMORTAL))
+ return NULL;
+ w = zalloc(sizeof(*w));
+ w->flags = WIDGET_INT | flags;
+ w->first = NULL;
+ w->u.fn = ifunc;
+ t = rthingy(dotn);
+ bindwidget(w, t);
+ t->flags |= TH_IMMORTAL;
+ bindwidget(w, rthingy(name));
+ return w;
+}
+
+#ifdef DYNAMIC
+
+/* Delete an internal widget provided by a module. Don't try to delete *
+ * a widget from the fixed table -- it would be bad. (Thanks, Egon.) */
+
+/**/
+void
+deletezlefunction(Widget w)
+{
+ Thingy p, n;
+
+ p = w->first;
+ while(1) {
+ n = p->samew;
+ if(n == p) {
+ unbindwidget(p, 1);
+ return;
+ }
+ unbindwidget(p, 1);
+ p = n;
+ }
+}
+
+#endif /* DYNAMIC */
+
+/***************/
+/* zle builtin */
+/***************/
+
+/*
+ * The available operations are:
+ *
+ * -l list user-defined widgets (no arguments)
+ * -D delete widget names
+ * -A link the two named widgets (2 arguments)
+ * -N create new user-defined widget (1 or 2 arguments)
+ * invoke a widget (1 argument)
+ */
+
+/**/
+int
+bin_zle(char *name, char **args, char *ops, int func)
+{
+ static struct opn {
+ char o;
+ int (*func) _((char *, char **, char *, char));
+ int min, max;
+ } const opns[] = {
+ { 'l', bin_zle_list, 0, 0 },
+ { 'D', bin_zle_del, 1, -1 },
+ { 'A', bin_zle_link, 2, 2 },
+ { 'N', bin_zle_new, 1, 2 },
+ { 0, bin_zle_call, 0, -1 },
+ };
+ struct opn const *op, *opp;
+ int n;
+
+ /* select operation and ensure no clashing arguments */
+ for(op = opns; op->o && !ops[op->o]; op++) ;
+ if(op->o)
+ for(opp = op; (++opp)->o; )
+ if(ops[opp->o]) {
+ zerrnam(name, "incompatible operation selection options",
+ NULL, 0);
+ return 1;
+ }
+
+ /* check number of arguments */
+ for(n = 0; args[n]; n++) ;
+ if(!op->o && n != 1) {
+ zerrnam(name, "wrong number of arguments", NULL, 0);
+ return 1;
+ }
+ if(n < op->min) {
+ zerrnam(name, "not enough arguments for -%c", NULL, op->o);
+ return 1;
+ } else if(op->max != -1 && n > op->max) {
+ zerrnam(name, "too many arguments for -%c", NULL, op->o);
+ return 1;
+ }
+
+ /* pass on the work to the operation function */
+ return op->func(name, args, ops, op->o);
+}
+
+/**/
+static int
+bin_zle_list(char *name, char **args, char *ops, char func)
+{
+ scanhashtable(thingytab, 1, 0, DISABLED, scanlistwidgets, ops['L']);
+ return 0;
+}
+
+/**/
+static void
+scanlistwidgets(HashNode hn, int list)
+{
+ Thingy t = (Thingy) hn;
+ Widget w = t->widget;
+
+ if(w->flags & WIDGET_INT)
+ return;
+ if(list) {
+ fputs("zle -N ", stdout);
+ if(t->nam[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(t->nam, stdout);
+ if(strcmp(t->nam, w->u.fnnam)) {
+ fputc(' ', stdout);
+ quotedzputs(w->u.fnnam, stdout);
+ }
+ } else {
+ nicezputs(t->nam, stdout);
+ if(strcmp(t->nam, w->u.fnnam)) {
+ fputs(" (", stdout);
+ nicezputs(w->u.fnnam, stdout);
+ fputc(')', stdout);
+ }
+ }
+ putchar('\n');
+}
+
+/**/
+static int
+bin_zle_del(char *name, char **args, char *ops, char func)
+{
+ int ret = 0;
+
+ do {
+ Thingy t = (Thingy) thingytab->getnode(thingytab, *args);
+ if(!t) {
+ zwarnnam(name, "no such widget `%s'", *args, 0);
+ ret = 1;
+ } else if(unbindwidget(t, 0)) {
+ zwarnnam(name, "widget name `%s' is protected", *args, 0);
+ ret = 1;
+ }
+ } while(*++args);
+ return ret;
+}
+
+/**/
+static int
+bin_zle_link(char *name, char **args, char *ops, char func)
+{
+ Thingy t = (Thingy) thingytab->getnode(thingytab, args[0]);
+
+ if(!t) {
+ zerrnam(name, "no such widget `%s'", args[0], 0);
+ return 1;
+ } else if(bindwidget(t->widget, rthingy(args[1]))) {
+ zerrnam(name, "widget name `%s' is protected", args[1], 0);
+ return 1;
+ }
+ return 0;
+
+}
+
+/**/
+static int
+bin_zle_new(char *name, char **args, char *ops, char func)
+{
+ Widget w = zalloc(sizeof(*w));
+
+ w->flags = 0;
+ w->first = NULL;
+ w->u.fnnam = ztrdup(args[1] ? args[1] : args[0]);
+ if(!bindwidget(w, rthingy(args[0])))
+ return 0;
+ freewidget(w);
+ zerrnam(name, "widget name `%s' is protected", args[0], 0);
+ return 1;
+}
+
+/**/
+static int
+bin_zle_call(char *name, char **args, char *ops, char func)
+{
+ Thingy t;
+
+ if(!zleactive || incompctlfunc) {
+ zerrnam(name, "widgets can only be called when ZLE is active",
+ NULL, 0);
+ return 1;
+ }
+ t = rthingy(args[0]);
+ PERMALLOC {
+ execzlefunc(t);
+ } LASTALLOC;
+ unrefthingy(t);
+ return 0;
+}
+
+/*******************/
+/* initialiasation */
+/*******************/
+
+/**/
+void
+init_thingies(void)
+{
+ Thingy t;
+
+ createthingytab();
+ for(t = thingies; t->nam; t++)
+ thingytab->addnode(thingytab, t->nam, t);
+}
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
new file mode 100644
index 000000000..1aa1a008c
--- /dev/null
+++ b/Src/Zle/zle_tricky.c
@@ -0,0 +1,4015 @@
+/*
+ * zle_tricky.c - expansion and completion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_tricky.pro"
+
+/* The main part of ZLE maintains the line being edited as binary data, *
+ * but here, where we interface with the lexer and other bits of zsh, *
+ * we need the line metafied. The technique used is quite simple: on *
+ * entry to the expansion/completion system, we metafy the line in *
+ * place, adjusting ll and cs to match. All completion and expansion *
+ * is done on the metafied line. Immediately before returning, the *
+ * line is unmetafied again, changing ll and cs back. (ll and cs might *
+ * have changed during completion, so they can't be merely saved and *
+ * restored.) The various indexes into the line that are used in this *
+ * file only are not translated: they remain indexes into the metafied *
+ * line. */
+
+#ifdef HAVE_NIS_PLUS
+# include <rpcsvc/nis.h>
+#else
+# ifdef HAVE_NIS
+# include <rpc/types.h>
+# include <rpc/rpc.h>
+# include <rpcsvc/ypclnt.h>
+# include <rpcsvc/yp_prot.h>
+
+/* This is used when getting usernames from the NIS. */
+typedef struct {
+ int len;
+ char *s;
+}
+dopestring;
+# endif
+#endif
+
+#define inststr(X) inststrlen((X),1,-1)
+
+/* wb and we hold the beginning/end position of the word we are completing. */
+
+static int wb, we;
+
+/* offs is the cursor position within the tokenized *
+ * current word after removing nulargs. */
+
+static int offs;
+
+/* These control the type of completion that will be done. They are *
+ * affected by the choice of ZLE command and by relevant shell options. */
+
+static int usemenu, useglob;
+
+/* != 0 if we are in the middle of a menu completion */
+
+static int menucmp;
+
+/* A pointer to the current position in the menu-completion array (the one *
+ * that was put in the command line last). */
+
+static char **menucur;
+
+/* menupos is the point (in the command line) where the menu-completion *
+ * strings are inserted. menulen is the length of the string that was *
+ * inserted last. menuend is the end position of this string in the *
+ * command line. menuwe is non-zero if the cursor was at the end of the *
+ * word (meaning that suffixes should go before the cursor). menuinsc is *
+ * the length of any suffix that has been temporarily added. */
+
+static int menupos, menulen, menuend, menuwe, menuinsc;
+
+/* This is used as a flag from get_comp_string() that we are doing *
+ * completion inside a brace expansion. */
+
+static int complinbrace;
+
+/* The list of matches. fmatches contains the matches we first ignore *
+ * because of fignore. */
+
+static LinkList matches, fmatches;
+
+/* The list of matches turned into an array. This is used to sort this *
+ * list and when menu-completion is used (directly or via automenu). */
+
+static char **amatches;
+
+/* The number of matches. */
+
+static int nmatches;
+
+/* A list of user-defined explanations for the completions to be shown *
+ * instead of amatches when listing completions. */
+
+static char **aylist;
+
+/* !=0 if we have a valid completion list. */
+
+static int validlist;
+
+/* This flag is non-zero if we are completing a pattern (with globcomplete) */
+
+static int ispattern;
+
+/* Two patterns used when doing glob-completion. The first one is built *
+ * from the whole word we are completing and the second one from that *
+ * part of the word that was identified as a possible filename. */
+
+static Comp patcomp, filecomp;
+
+/* We store the following prefixes/suffixes: *
+ * lpre/lsuf -- what's on the line *
+ * rpre/rsuf -- same as lpre/lsuf, but expanded *
+ * *
+ * ... and if we are completing files, too: *
+ * ppre/psuf -- the path prefix/suffix *
+ * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in *
+ * prpre -- ppre in expanded form usable for opendir *
+ * *
+ * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf, *
+ * fpre, and fsuf. noreal is non-zero if we have rpre/rsuf. */
+
+static char *lpre, *lsuf;
+static char *rpre, *rsuf;
+static char *ppre, *psuf, *prpre;
+static char *fpre, *fsuf;
+static int lpl, lsl, rpl, rsl, fpl, fsl;
+static int noreal;
+
+/* This is used when completing after `$' and holds the whole prefix, *
+ * used in do_single() to check whether the word expands to a directory *
+ * name (in that case and if autoparamslash is set, we add a `/'). *
+ * qparampre is the same but quoted. The length of it is in qparprelen. *
+ * parambr is != 0 if the parameter name is in braces. */
+
+static char *parampre = NULL, *qparampre = NULL;
+static int qparprelen, parambr;
+
+/* This is either zero or equal to the special character the word we are *
+ * trying to complete starts with (e.g. Tilde or Equals). */
+
+static char ic;
+
+/* These hold the minimum common prefix/suffix lengths (normal and for *
+ * fignore ignored). */
+
+static int ab, ae, fab, fae;
+
+/* This variable says what we are currently adding to the list of matches. */
+
+static int addwhat;
+
+/* firstm hold the first match we found, shortest contains the shortest *
+ * one (normal and for fignore ignored). */
+
+static char *firstm, *shortest, *ffirstm, *fshortest;
+
+/* This holds the word we are completing in quoted from. */
+
+static char *qword;
+
+/* This is the length of the shortest match we found (normal and for *
+ * fignore ignored). */
+
+static int shortl, fshortl;
+
+/* This is non-zero if we are doing a menu-completion and this is not the *
+ * first call (e.g. when automenu is set and menu-completion was entered *
+ * due to this). */
+
+static int amenu;
+
+/* Find out if we have to insert a tab (instead of trying to complete). */
+
+/**/
+static int
+usetab(void)
+{
+ unsigned char *s = line + cs - 1;
+
+ for (; s >= line && *s != '\n'; s--)
+ if (*s != '\t' && *s != ' ')
+ return 0;
+ return 1;
+}
+
+#define COMP_COMPLETE 0
+#define COMP_LIST_COMPLETE 1
+#define COMP_SPELL 2
+#define COMP_EXPAND 3
+#define COMP_EXPAND_COMPLETE 4
+#define COMP_LIST_EXPAND 5
+#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND)
+
+/**/
+void
+completeword(void)
+{
+ usemenu = isset(MENUCOMPLETE);
+ useglob = isset(GLOBCOMPLETE);
+ if (c == '\t' && usetab())
+ selfinsert();
+ else
+ docomplete(COMP_COMPLETE);
+}
+
+/**/
+void
+menucomplete(void)
+{
+ usemenu = 1;
+ useglob = isset(GLOBCOMPLETE);
+ if (c == '\t' && usetab())
+ selfinsert();
+ else
+ docomplete(COMP_COMPLETE);
+}
+
+/**/
+void
+listchoices(void)
+{
+ usemenu = isset(MENUCOMPLETE);
+ useglob = isset(GLOBCOMPLETE);
+ docomplete(COMP_LIST_COMPLETE);
+}
+
+/**/
+void
+spellword(void)
+{
+ usemenu = useglob = 0;
+ docomplete(COMP_SPELL);
+}
+
+/**/
+void
+deletecharorlist(void)
+{
+ char **mc = menucur;
+
+ usemenu = isset(MENUCOMPLETE);
+ useglob = isset(GLOBCOMPLETE);
+ if (cs != ll)
+ deletechar();
+ else
+ docomplete(COMP_LIST_COMPLETE);
+
+ menucur = mc;
+}
+
+/**/
+void
+expandword(void)
+{
+ usemenu = useglob = 0;
+ if (c == '\t' && usetab())
+ selfinsert();
+ else
+ docomplete(COMP_EXPAND);
+}
+
+/**/
+void
+expandorcomplete(void)
+{
+ usemenu = isset(MENUCOMPLETE);
+ useglob = isset(GLOBCOMPLETE);
+ if (c == '\t' && usetab())
+ selfinsert();
+ else
+ docomplete(COMP_EXPAND_COMPLETE);
+}
+
+/**/
+void
+menuexpandorcomplete(void)
+{
+ usemenu = 1;
+ useglob = isset(GLOBCOMPLETE);
+ if (c == '\t' && usetab())
+ selfinsert();
+ else
+ docomplete(COMP_EXPAND_COMPLETE);
+}
+
+/**/
+void
+listexpand(void)
+{
+ usemenu = isset(MENUCOMPLETE);
+ useglob = isset(GLOBCOMPLETE);
+ docomplete(COMP_LIST_EXPAND);
+}
+
+/**/
+void
+reversemenucomplete(void)
+{
+ if (!menucmp) {
+ menucomplete();
+ return;
+ }
+ HEAPALLOC {
+ if (menucur == amatches)
+ menucur = amatches + nmatches - 1;
+ else
+ menucur--;
+ metafy_line();
+ do_single(*menucur);
+ unmetafy_line();
+ } LASTALLOC;
+}
+
+/* Accepts the current completion and starts a new arg, *
+ * with the next completions. This gives you a way to *
+ * accept several selections from the list of matches. */
+
+/**/
+void
+acceptandmenucomplete(void)
+{
+ if (!menucmp) {
+ feep();
+ return;
+ }
+ cs = menuend + menuinsc;
+ inststrlen(" ", 1, 1);
+ if (qparampre)
+ inststrlen(qparampre, 1, qparprelen);
+ if (lpre && !ispattern)
+ inststrlen(lpre, 1, -1);
+ if (lsuf && !ispattern)
+ inststrlen(lsuf, 0, -1);
+ menupos = cs;
+ menuend = cs + (lsuf ? strlen(lsuf) : 0);
+ menulen = 0;
+ menuinsc = 0;
+ menuwe = 1;
+ menucomplete();
+}
+
+/* These are flags saying if we are completing in the command *
+ * position or in a redirection. */
+
+static int lincmd, linredir;
+
+/* Non-zero if the last completion done was ambiguous (used to find *
+ * out if AUTOMENU should start). More precisely, it's nonzero after *
+ * successfully doing any completion, unless the completion was *
+ * unambiguous and did not cause the display of a completion list. *
+ * From the other point of view, it's nonzero iff AUTOMENU (if set) *
+ * should kick in on another completion. */
+
+static int lastambig;
+
+/* This describes some important things collected during the last *
+ * completion. Its value is zero or the inclusive OR of some of *
+ * the HAS_* things below. */
+
+static int haswhat;
+
+/* We have a suffix to add (given with compctl -S). */
+
+#define HAS_SUFFIX 1
+
+/* We have filenames in the completion list. */
+
+#define HAS_FILES 2
+
+/* We have other things than files in the completion list. If this is *
+ * not set but HAS_FILES is, we probably put the file type characters *
+ * in the completion list (if listtypes is set) and we attempt to add *
+ * a slash to completed directories. */
+
+#define HAS_MISC 4
+
+/* This is set if we have filenames in the completion list that were *
+ * generated by a globcompletion pattern. */
+
+#define HAS_PATHPAT 8
+
+
+/* This holds the naem of the current command (used to find the right *
+ * compctl). */
+
+static char *cmdstr;
+
+
+/* Check if the given string is the name of a parameter and if this *
+ * parameter is one worth expanding. */
+
+/**/
+static int
+checkparams(char *p)
+{
+ int t0, n, l = strlen(p), e = 0;
+ struct hashnode *hn;
+
+ for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--)
+ for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next)
+ if (pfxlen(p, hn->nam) == l) {
+ n++;
+ if (strlen(hn->nam) == l)
+ e = 1;
+ }
+ return (n == 1) ? (getsparam(p) != NULL) :
+ (!menucmp && e && isset(RECEXACT));
+}
+
+/* Check if the given string has wildcards. The difficulty is that we *
+ * have to treat things like job specifications (%...) and parameter *
+ * expressions correctly. */
+
+/**/
+static int
+cmphaswilds(char *str)
+{
+ if ((*str == Inbrack || *str == Outbrack) && !str[1])
+ return 0;
+
+ /* If a leading % is immediately followed by ?, then don't *
+ * treat that ? as a wildcard. This is so you don't have *
+ * to escape job references such as %?foo. */
+ if (str[0] == '%' && str[1] ==Quest)
+ str += 2;
+
+ for (; *str;) {
+ if (*str == String || *str == Qstring) {
+ /* A parameter expression. */
+
+ if (*++str == Inbrace)
+ skipparens(Inbrace, Outbrace, &str);
+ else if (*str == String || *str == Qstring)
+ str++;
+ else {
+ /* Skip all the things a parameter expression might start *
+ * with (before we come to the parameter name). */
+ for (; *str; str++)
+ if (*str != '^' && *str != Hat &&
+ *str != '=' && *str != Equals &&
+ *str != '~' && *str != Tilde)
+ break;
+ if (*str == '#' || *str == Pound)
+ str++;
+ /* Star and Quest are parameter names here, not wildcards */
+ if (*str == Star || *str == Quest)
+ str++;
+ }
+ } else {
+ /* Not a parameter expression so we check for wildcards */
+ if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) ||
+ *str == Star || *str == Bar || *str == Quest ||
+ !skipparens(Inbrack, Outbrack, &str) ||
+ !skipparens(Inang, Outang, &str) ||
+ (unset(IGNOREBRACES) &&
+ !skipparens(Inbrace, Outbrace, &str)) ||
+ (*str == Inpar && str[1] == ':' &&
+ !skipparens(Inpar, Outpar, &str)))
+ return 1;
+ if (*str)
+ str++;
+ }
+ }
+ return 0;
+}
+
+/* The main entry point for completion. */
+
+/**/
+static void
+docomplete(int lst)
+{
+ char *s, *ol;
+ int olst = lst, chl = 0, ne = noerrs, ocs;
+
+ /* If we are doing a menu-completion... */
+
+ if (menucmp && lst != COMP_LIST_EXPAND) {
+ do_menucmp(lst);
+ return;
+ }
+
+ /* Check if we have to start a menu-completion (via automenu). */
+
+ if ((amenu = (isset(AUTOMENU) && lastambig)))
+ usemenu = 1;
+
+ /* Expand history references before starting completion. If anything *
+ * changed, do no more. */
+
+ if (doexpandhist())
+ return;
+
+ metafy_line();
+
+ ocs = cs;
+ if (!isfirstln && chline != NULL) {
+ /* If we are completing in a multi-line buffer (which was not *
+ * taken from the history), we have to prepend the stuff saved *
+ * in chline to the contents of line. */
+
+ ol = dupstring((char *)line);
+ /* Make sure that chline is zero-terminated. */
+ *hptr = '\0';
+ cs = 0;
+ inststr(chline);
+ chl = cs;
+ cs += ocs;
+ } else
+ ol = NULL;
+ inwhat = IN_NOTHING;
+ qword = NULL;
+ /* Get the word to complete. */
+ noerrs = 1;
+ s = get_comp_string();
+ DPUTS(wb < 0 || cs < wb || cs > we,
+ "BUG: 0 <= wb <= cs <= we is not true!");
+ noerrs = ne;
+ /* For vi mode, reset the start-of-insertion pointer to the beginning *
+ * of the word being completed, if it is currently later. Vi itself *
+ * would never change the pointer in the middle of an insertion, but *
+ * then vi doesn't have completion. More to the point, this is only *
+ * an emulation. */
+ if (viinsbegin > ztrsub((char *) line + wb, (char *) line))
+ viinsbegin = ztrsub((char *) line + wb, (char *) line);
+ /* If we added chline to the line buffer, reset the original contents. */
+ if (ol) {
+ cs -= chl;
+ wb -= chl;
+ we -= chl;
+ if (wb < 0) {
+ strcpy((char *) line, ol);
+ ll = strlen((char *) line);
+ cs = ocs;
+ unmetafy_line();
+ feep();
+ return;
+ }
+ ocs = cs;
+ cs = 0;
+ foredel(chl);
+ cs = ocs;
+ }
+ freeheap();
+ /* Save the lexer state, in case the completion code uses the lexer *
+ * somewhere (e.g. when processing a compctl -s flag). */
+ lexsave();
+ if (inwhat == IN_ENV)
+ lincmd = 0;
+ if (s) {
+ if (lst == COMP_EXPAND_COMPLETE) {
+ /* Check if we have to do expansion or completion. */
+ char *q = s;
+
+ if (*q == Equals) {
+ /* The word starts with `=', see if we can expand it. */
+ q = s + 1;
+ if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked))
+ if (isset(RECEXACT))
+ lst = COMP_EXPAND;
+ else {
+ int t0, n = 0;
+ char *fc;
+ struct hashnode *hn;
+
+ for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
+ for (hn = cmdnamtab->nodes[t0]; hn;
+ hn = hn->next) {
+ if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) {
+ zsfree(fc);
+ n++;
+ }
+ if (n == 2)
+ break;
+ }
+
+ if (n == 1)
+ lst = COMP_EXPAND;
+ }
+ }
+ if (lst == COMP_EXPAND_COMPLETE)
+ do {
+ /* check if there is a parameter expresiion. */
+ for (; *q && *q != String; q++);
+ if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
+ if (*++q == Inbrace) {
+ if (! skipparens(Inbrace, Outbrace, &q) &&
+ q == s + cs - wb)
+ lst = COMP_EXPAND;
+ } else {
+ char *t, sav, sav2;
+
+ /* Skip the things parameter expressions might *
+ * start with (the things before the parameter *
+ * name). */
+ for (; *q; q++)
+ if (*q != '^' && *q != Hat &&
+ *q != '=' && *q != Equals &&
+ *q != '~' && *q != Tilde)
+ break;
+ if ((*q == '#' || *q == Pound || *q == '+') &&
+ q[1] != String)
+ q++;
+
+ sav2 = *(t = q);
+ if (*q == Quest || *q == Star || *q == String ||
+ *q == Qstring)
+ *q = ztokens[*q - Pound], ++q;
+ else if (*q == '?' || *q == '*' || *q == '$' ||
+ *q == '-' || *q == '!' || *q == '@')
+ q++;
+ else if (idigit(*q))
+ do q++; while (idigit(*q));
+ else
+ while (iident(*q))
+ q++;
+ sav = *q;
+ *q = '\0';
+ if (cs - wb == q - s &&
+ (idigit(sav2) || checkparams(t)))
+ lst = COMP_EXPAND;
+ *q = sav;
+ *t = sav2;
+ }
+ if (lst != COMP_EXPAND)
+ lst = COMP_COMPLETE;
+ } else
+ break;
+ } while (q < s + cs - wb);
+ if (lst == COMP_EXPAND_COMPLETE) {
+ /* If it is still not clear if we should use expansion or *
+ * completion and there is a `$' or a backtick in the word, *
+ * than do expansion. */
+ for (q = s; *q; q++)
+ if (*q == Tick || *q == Qtick ||
+ *q == String || *q == Qstring)
+ break;
+ lst = *q ? COMP_EXPAND : COMP_COMPLETE;
+ }
+ /* And do expansion if there are wildcards and globcomplete is *
+ * not used. */
+ if (unset(GLOBCOMPLETE) && cmphaswilds(s))
+ lst = COMP_EXPAND;
+ }
+ if (lincmd && (inwhat == IN_NOTHING))
+ inwhat = IN_CMD;
+
+ if (lst == COMP_SPELL) {
+ char *x, *q;
+
+ for (q = s; *q; q++)
+ if (INULL(*q))
+ *q = Nularg;
+ cs = wb;
+ foredel(we - wb);
+ HEAPALLOC {
+ untokenize(x = dupstring(s));
+ if (*s == Tilde || *s == Equals || *s == String)
+ *x = *s;
+ spckword(&x, 0, lincmd, 0);
+ } LASTALLOC;
+ untokenize(x);
+ inststr(x);
+ } else if (COMP_ISEXPAND(lst)) {
+ /* Do expansion. */
+ char *ol = (olst == COMP_EXPAND_COMPLETE) ?
+ dupstring((char *)line) : (char *)line;
+ int ocs = cs, ne = noerrs;
+
+ noerrs = 1;
+ doexpansion(s, lst, olst, lincmd);
+ lastambig = 0;
+ noerrs = ne;
+
+ /* If expandorcomplete was invoked and the expansion didn't *
+ * change the command line, do completion. */
+ if (olst == COMP_EXPAND_COMPLETE &&
+ !strcmp(ol, (char *)line)) {
+ char *p;
+
+ cs = ocs;
+ errflag = 0;
+
+ p = s;
+ if (*p == Tilde || *p == Equals)
+ p++;
+ for (; *p; p++)
+ if (itok(*p))
+ if (*p != String && *p != Qstring)
+ *p = ztokens[*p - Pound];
+ else if (p[1] == Inbrace)
+ p++, skipparens(Inbrace, Outbrace, &p);
+ docompletion(s, lst, lincmd, 1);
+ }
+ } else
+ /* Just do completion. */
+ docompletion(s, lst, lincmd, 0);
+ zsfree(s);
+ }
+ /* Reset the lexer state, pop the heap. */
+ lexrestore();
+ popheap();
+ zsfree(qword);
+ unmetafy_line();
+}
+
+/* Do completion, given that we are in the middle of a menu completion. We *
+ * don't need to generate a list of matches, because that's already been *
+ * done by previous commands. We will either list the completions, or *
+ * insert the next completion. */
+
+/**/
+static void
+do_menucmp(int lst)
+{
+ /* Just list the matches if the list was requested. */
+ if (lst == COMP_LIST_COMPLETE) {
+ showinglist = -2;
+ return;
+ }
+ /* Otherwise go to the next match in the array... */
+ HEAPALLOC {
+ if (!*++menucur)
+ menucur = amatches;
+ /* ... and insert it into the command line. */
+ metafy_line();
+ do_single(*menucur);
+ unmetafy_line();
+ } LASTALLOC;
+}
+
+/* 1 if we are completing in a string */
+static int instring;
+
+/* 1 if we are completing the prefix */
+static int comppref;
+
+/* This function inserts an `x' in the command line at the cursor position. *
+ * *
+ * Oh, you want to know why? Well, if completion is tried somewhere on an *
+ * empty part of the command line, the lexer code would normally not be *
+ * able to give us the `word' we want to complete, since there is no word. *
+ * But we need to call the lexer to find out where we are (and for which *
+ * command we are completing and such things). So we temporarily add a `x' *
+ * (any character without special meaning would do the job) at the cursor *
+ * position, than the lexer gives us the word `x' and its beginning and end *
+ * positions and we can remove the `x'. *
+ * *
+ * If we are just completing the prefix (comppref set), we also insert a *
+ * space after the x to end the word. We never need to remove the space: *
+ * anywhere we are able to retrieve a word for completion it will be *
+ * discarded as whitespace. It has the effect of making any suffix *
+ * referrable to as the next word on the command line when indexing *
+ * from a completion function. */
+
+/**/
+static void
+addx(char **ptmp)
+{
+ int addspace = 0;
+
+ if (!line[cs] || line[cs] == '\n' ||
+ (iblank(line[cs]) && (!cs || line[cs-1] != '\\')) ||
+ line[cs] == ')' || line[cs] == '`' ||
+ (instring && (line[cs] == '"' || line[cs] == '\'')) ||
+ (addspace = (comppref && !iblank(line[cs])))) {
+ *ptmp = (char *)line;
+ line = (unsigned char *)halloc(strlen((char *)line) + 3 + addspace);
+ memcpy(line, *ptmp, cs);
+ line[cs] = 'x';
+ if (addspace)
+ line[cs+1] = ' ';
+ strcpy((char *)line + cs + 1 + addspace, (*ptmp) + cs);
+ addedx = 1 + addspace;
+ } else {
+ addedx = 0;
+ *ptmp = NULL;
+ }
+}
+
+/* Like dupstring, but add an extra space at the end of the string. */
+
+/**/
+static char *
+dupstrspace(const char *str)
+{
+ int len = strlen((char *)str);
+ char *t = (char *)ncalloc(len + 2);
+ strcpy(t, str);
+ strcpy(t+len, " ");
+ return t;
+}
+
+/* These functions metafy and unmetafy the ZLE buffer, as described at the *
+ * top of this file. Note that ll and cs are translated. They *must* be *
+ * called in matching pairs, around all the expansion/completion code. *
+ * Currently, there are four pairs: in history expansion, in the main *
+ * completion function, and one in each of the middle-of-menu-completion *
+ * functions (there's one for each direction). */
+
+/**/
+static void
+metafy_line(void)
+{
+ int len = ll;
+ char *s;
+
+ for (s = (char *) line; s < (char *) line + ll;)
+ if (imeta(*s++))
+ len++;
+ sizeline(len);
+ (void) metafy((char *) line, ll, META_NOALLOC);
+ ll = len;
+ cs = metalen((char *) line, cs);
+}
+
+/**/
+static void
+unmetafy_line(void)
+{
+ cs = ztrsub((char *) line + cs, (char *) line);
+ (void) unmetafy((char *) line, &ll);
+}
+
+/* Lasciate ogni speranza. *
+ * This function is a nightmare. It works, but I'm sure that nobody really *
+ * understands why. The problem is: to make it cleaner we would need *
+ * changes in the lexer code (and then in the parser, and then...). */
+
+/**/
+static char *
+get_comp_string(void)
+{
+ int t0, tt0, i, j, k, cp, rd, sl, ocs;
+ char *s = NULL, *linptr, *tmp, *p, *tt = NULL;
+
+ complinbrace = 0;
+ /* This global flag is used to signal the lexer code if it should *
+ * expand aliases or not. */
+ noaliases = isset(COMPLETEALIASES);
+
+ /* Find out if we are somewhere in a `string', i.e. inside '...', *
+ * "...", `...`, or ((...)). */
+
+ for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++)
+ if (*p == '`' && !(k & 1))
+ i++;
+ else if (*p == '\"' && !(k & 1) && !(i & 1))
+ j++;
+ else if (*p == '\'' && !(j & 1))
+ k++;
+ else if (*p == '\\' && p[1] && !(k & 1))
+ p++;
+ instring = (j & 1) ? 2 : (k & 1);
+ addx(&tmp);
+ if (instring) {
+ /* Yes, we are in a string. */
+ if (!tmp) {
+ tmp = (char *)line;
+ line = (unsigned char *) dupstring((char *) line);
+ }
+ /* Now remove the quotes. *
+ * What?? Why that?? Well, we want to be able to complete *
+ * inside strings. The lexer code gives us no help here, *
+ * so we have to cheat. We remove the quotes, the lexer *
+ * will than treat the words in the strings normally and we *
+ * can complete them. *
+ * This is completely the wrong thing to do, but it's *
+ * occasionally useful, and we can't handle quotes properly *
+ * yet anyway. */
+ for (p = (char *)line; *p; p++)
+ if (*p == '"' || *p == '\'')
+ *p = ' ';
+ }
+ linptr = (char *)line;
+ pushheap();
+ HEAPALLOC {
+ start:
+ inwhat = IN_NOTHING;
+ /* Now set up the lexer and start it. */
+ parbegin = parend = -1;
+ lincmd = incmdpos;
+ linredir = inredir;
+ zsfree(cmdstr);
+ cmdstr = NULL;
+ zleparse = 1;
+ clwpos = -1;
+ lexsave();
+ inpush(dupstrspace((char *) linptr), 0, NULL);
+ strinbeg();
+ stophist = 2;
+ i = tt0 = cp = rd = 0;
+
+ /* This loop is possibly the wrong way to do this. It goes through *
+ * the previously massaged command line using the lexer. It stores *
+ * each token in each command (commands being regarded, roughly, as *
+ * being separated by tokens | & &! |& || &&). The loop stops when *
+ * the end of the command containing the cursor is reached. It's a *
+ * simple way to do things, but suffers from an inability to *
+ * distinguish actual command arguments from, for example, *
+ * filenames in redirections. (But note that code elsewhere checks *
+ * if we are completing *in* a redirection.) The only way to fix *
+ * this would be to pass the command line through the parser too, *
+ * and get the arguments that way. Maybe in 3.1... */
+ do {
+ lincmd = incmdpos;
+ linredir = inredir;
+ /* Get the next token. */
+ ctxtlex();
+ if (tok == DINPAR)
+ tokstr = NULL;
+
+ /* We reached the end. */
+ if (tok == ENDINPUT)
+ break;
+ if (tok == BAR || tok == AMPER ||
+ tok == BARAMP || tok == AMPERBANG ||
+ ((tok == DBAR || tok == DAMPER) && !incond)) {
+ /* This is one of the things that separate commands. If we *
+ * already have the things we need (e.g. the token strings), *
+ * leave the loop. */
+ if (tt)
+ break;
+ /* Otherwise reset the variables we are collecting data in. */
+ i = tt0 = cp = rd = 0;
+ }
+ if (lincmd && tok == STRING) {
+ /* The lexer says, this token is in command position, so *
+ * store the token string (to find the right compctl). */
+ zsfree(cmdstr);
+ cmdstr = ztrdup(tokstr);
+ i = 0;
+ }
+ if (!zleparse && !tt0) {
+ /* This is done when the lexer reached the word the cursor is on. */
+ tt = tokstr ? dupstring(tokstr) : NULL;
+ /* If we added a `x', remove it. */
+ if (addedx && tt)
+ chuck(tt + cs - wb);
+ tt0 = tok;
+ /* Store the number of this word. */
+ clwpos = i;
+ cp = lincmd;
+ rd = linredir;
+ if (inwhat == IN_NOTHING && incond)
+ inwhat = IN_COND;
+ }
+ if (!tokstr)
+ continue;
+ /* We need to store the token strings of all words (for some of *
+ * the more complicated compctl -x things). They are stored in *
+ * the clwords array. Make this array big enough. */
+ if (i + 1 == clwsize) {
+ int n;
+ clwords = (char **)realloc(clwords,
+ (clwsize *= 2) * sizeof(char *));
+ for(n = clwsize; --n > i; )
+ clwords[n] = NULL;
+ }
+ zsfree(clwords[i]);
+ /* And store the current token string. */
+ clwords[i] = ztrdup(tokstr);
+ sl = strlen(tokstr);
+ /* Sometimes the lexer gives us token strings ending with *
+ * spaces we delete the spaces. */
+ while (sl && clwords[i][sl - 1] == ' ' &&
+ (sl < 2 || (clwords[i][sl - 2] != Bnull &&
+ clwords[i][sl - 2] != Meta)))
+ clwords[i][--sl] = '\0';
+ /* If this is the word the cursor is in and we added a `x', *
+ * remove it. */
+ if (clwpos == i++ && addedx)
+ chuck(&clwords[i - 1][((cs - wb) >= sl) ?
+ (sl - 1) : (cs - wb)]);
+ } while (tok != LEXERR && tok != ENDINPUT &&
+ (tok != SEPER || (zleparse && !tt0)));
+ /* Calculate the number of words stored in the clwords array. */
+ clwnum = (tt || !i) ? i : i - 1;
+ zsfree(clwords[clwnum]);
+ clwords[clwnum] = NULL;
+ t0 = tt0;
+ lincmd = cp;
+ linredir = rd;
+ strinend();
+ inpop();
+ errflag = zleparse = 0;
+ if (parbegin != -1) {
+ /* We are in command or process substitution */
+ if (parend >= 0 && !tmp)
+ line = (unsigned char *) dupstring(tmp = (char *)line);
+ linptr = (char *) line + ll + addedx - parbegin + 1;
+ if (parend >= 0) {
+ ll -= parend;
+ line[ll + addedx] = '\0';
+ }
+ lexrestore();
+ goto start;
+ }
+
+ if (inwhat == IN_MATH)
+ s = NULL;
+ else if (!t0 || t0 == ENDINPUT) {
+ /* There was no word (empty line). */
+ s = ztrdup("");
+ we = wb = cs;
+ clwpos = clwnum;
+ t0 = STRING;
+ } else if (t0 == STRING) {
+ /* We found a simple string. */
+ s = ztrdup(clwords[clwpos]);
+ } else if (t0 == ENVSTRING) {
+ /* The cursor was inside a parameter assignment. */
+ for (s = tt; iident(*s); s++);
+ if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb)
+ s = NULL, inwhat = IN_MATH;
+ else if (*s == '=') {
+ s++;
+ wb += s - tt;
+ t0 = STRING;
+ s = ztrdup(s);
+ inwhat = IN_ENV;
+ }
+ lincmd = 1;
+ }
+ if (we > ll)
+ we = ll;
+ tt = (char *)line;
+ if (tmp) {
+ line = (unsigned char *)tmp;
+ ll = strlen((char *)line);
+ }
+ if (t0 != STRING && inwhat != IN_MATH) {
+ if (tmp) {
+ tmp = NULL;
+ linptr = (char *)line;
+ lexrestore();
+ goto start;
+ }
+ feep();
+ noaliases = 0;
+ lexrestore();
+ LASTALLOC_RETURN NULL;
+ }
+
+ noaliases = 0;
+
+ /* Check if we are in an array subscript. We simply assume that *
+ * we are in a subscript if we are in brackets. Correct solution *
+ * is very difficult. This is quite close, but gets things like *
+ * foo[_ wrong (note no $). If we are in a subscript, treat it *
+ * as being in math. */
+ if (inwhat != IN_MATH) {
+ int i = 0;
+ for (tt = s; ++tt < s + cs - wb;)
+ if (*tt == Inbrack)
+ i++;
+ else if (i && *tt == Outbrack)
+ i--;
+ if (i)
+ inwhat = IN_MATH;
+ }
+ if (inwhat == IN_MATH) {
+ /* In mathematical expression, we complete parameter names (even *
+ * if they don't have a `$' in front of them). So we have to *
+ * find that name. */
+ for (we = cs; iident(line[we]); we++);
+ for (wb = cs; --wb >= 0 && iident(line[wb]););
+ wb++;
+ zsfree(s);
+ s = zalloc(we - wb + 1);
+ strncpy(s, (char *) line + wb, we - wb);
+ s[we - wb] = '\0';
+ }
+ /* This variable will hold the current word in quoted form. */
+ qword = ztrdup(s);
+ /* While building the quoted form, we also clean up the command line. */
+ offs = cs - wb;
+ for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
+ if (INULL(*p)) {
+ if (i < cs)
+ offs--;
+ if (p[1] || *p != Bnull) {
+ if (*p == Bnull) {
+ *tt = '\\';
+ if (cs == i + 1)
+ cs++, offs++;
+ } else {
+ ocs = cs;
+ cs = i;
+ foredel(1);
+ chuck(tt--);
+ if ((cs = ocs) > i--)
+ cs--;
+ we--;
+ }
+ } else {
+ ocs = cs;
+ *tt = '\0';
+ cs = we;
+ backdel(1);
+ if (ocs == we)
+ cs = we - 1;
+ else
+ cs = ocs;
+ we--;
+ }
+ chuck(p--);
+ }
+
+ if (!isset(IGNOREBRACES)) {
+ /* Try and deal with foo{xxx etc.; only simple cases
+ * (only one inbrace, completion after inbrace and before outbrace
+ * if present).
+ */
+ int myoffs = isset(COMPLETEINWORD) ? offs : strlen(s);
+ tt = NULL;
+ /* First check the conditions mentioned above
+ * and locate opening brace
+ */
+ for (i = 0, p = s; *p; p++, i++) {
+ /* careful, ${... is not a brace expansion...
+ * in fact, if it's got a substitution in it's too
+ * hard for us anyway. sorry.
+ */
+ if (*p == String || *p == Qstring) {
+ tt = NULL;
+ break;
+ } else if (*p == Inbrace) {
+ if (tt) {
+ /* too many inbraces */
+ tt = NULL;
+ break;
+ }
+ tt = p;
+ } else if (*p == Outbrace && i < myoffs) {
+ /* outbrace is before cursor pos, so nothing to complete */
+ tt = NULL;
+ break;
+ }
+ }
+
+ if (tt && tt < s + myoffs) {
+ /* Braces are go: delete opening brace */
+ char *com = NULL;
+ chuck(tt);
+ offs--;
+ myoffs--;
+
+ /* Look for text up to comma before cursor and delete it */
+ for (i = tt - s, p = tt; *p && i < myoffs; p++, i++)
+ if (*p == Comma)
+ com = p;
+ if (com) {
+ i = com - tt + 1;
+ while (i--)
+ chuck(tt), offs--, myoffs--;
+ }
+
+ /* Look for text between subsequent comma
+ * and closing brace or end of string and delete it
+ */
+ for (p = s + myoffs; *p && *p != Outbrace; p++)
+ if (*p == Comma) {
+ while (*p && *p != Outbrace)
+ chuck(p);
+ break;
+ }
+ if (*p == Outbrace)
+ chuck(p);
+ else {
+ /* we are still waiting for an outbrace and maybe commas */
+ complinbrace = 1;
+ }
+ }
+ }
+
+ } LASTALLOC;
+ lexrestore();
+
+ return (char *)s;
+}
+
+/* Expand the current word. */
+
+/**/
+static void
+doexpansion(char *s, int lst, int olst, int explincmd)
+{
+ LinkList vl;
+ char *ss;
+
+ DPUTS(useheap, "BUG: useheap in doexpansion()");
+ HEAPALLOC {
+ pushheap();
+ vl = newlinklist();
+ ss = dupstring(s);
+ addlinknode(vl, ss);
+ prefork(vl, 0);
+ if (errflag)
+ goto end;
+ if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) {
+ int ng = opts[NULLGLOB];
+
+ opts[NULLGLOB] = 1;
+ globlist(vl);
+ opts[NULLGLOB] = ng;
+ }
+ if (errflag)
+ goto end;
+ if (empty(vl) || !*(char *)peekfirst(vl)) {
+ if (!noerrs)
+ feep();
+ goto end;
+ }
+ if (peekfirst(vl) == (void *) ss ||
+ (olst == COMP_EXPAND_COMPLETE &&
+ !nextnode(firstnode(vl)) && *s == Tilde &&
+ (ss = dupstring(s), filesubstr(&ss, 0)) &&
+ !strcmp(ss, (char *)peekfirst(vl)))) {
+ /* If expansion didn't change the word, try completion if *
+ * expandorcomplete was called, otherwise, just beep. */
+ if (lst == COMP_EXPAND_COMPLETE)
+ docompletion(s, COMP_COMPLETE, explincmd, 0);
+ else
+ feep();
+ goto end;
+ }
+ if (lst == COMP_LIST_EXPAND) {
+ /* Only the list of expansions was requested. */
+ listlist(vl);
+ goto end;
+ }
+ /* Remove the current word and put the expansions there. */
+ cs = wb;
+ foredel(we - wb);
+ while ((ss = (char *)ugetnode(vl))) {
+ untokenize(ss);
+ ss = quotename(ss, NULL, NULL, NULL);
+ inststr(ss);
+#if 0
+ if (nonempty(vl)) {
+ spaceinline(1);
+ line[cs++] = ' ';
+ }
+#endif
+ if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
+ (cs && line[cs-1] != '/')) {
+ spaceinline(1);
+ line[cs++] = ' ';
+ }
+ }
+ end:
+ popheap();
+ } LASTALLOC;
+}
+
+/* This is called from the lexer to give us word positions. */
+
+/**/
+void
+gotword(void)
+{
+ we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0);
+ if (cs <= we) {
+ wb = ll - wordbeg + addedx;
+ zleparse = 0;
+ }
+}
+
+/* Insert the given string into the command line. If move is non-zero, *
+ * the cursor position is changed and len is the length of the string *
+ * to insert (if it is -1, the length is calculated here). */
+
+/**/
+static void
+inststrlen(char *str, int move, int len)
+{
+ if (!len)
+ return;
+ if (len == -1)
+ len = strlen(str);
+ spaceinline(len);
+ strncpy((char *)(line + cs), str, len);
+ if (move)
+ cs += len;
+}
+
+/* Quote the string s and return the result. If e is non-zero, it the *
+ * pointer it points to may point to aposition in s and in e the position *
+ * of the corresponding character in the quoted string is returned. Like *
+ * e, te may point to a position in the string and pl is used to return *
+ * the position of the character pointed to by te in the quoted string. *
+ * The string is metafied and may contain tokens. */
+
+/**/
+static char *
+quotename(const char *s, char **e, char *te, int *pl)
+{
+ const char *u, *tt;
+ char *v, buf[PATH_MAX * 2];
+ int sf = 0;
+
+ tt = v = buf;
+ u = s;
+ for (; *u; u++) {
+ if (e && *e == u)
+ *e = v, sf |= 1;
+ if (te == u)
+ *pl = v - tt, sf |= 2;
+ if (ispecial(*u) &&
+ (!instring || (isset(BANGHIST) &&
+ *u == (char)bangchar) ||
+ (instring == 2 &&
+ (*u == '$' || *u == '`' || *u == '\"')) ||
+ (instring == 1 && *u == '\'')))
+ if (*u == '\n' || (instring == 1 && *u == '\'')) {
+ if (unset(RCQUOTES)) {
+ *v++ = '\'';
+ if (*u == '\'')
+ *v++ = '\\';
+ *v++ = *u;
+ *v++ = '\'';
+ } else if (*u == '\n')
+ *v++ = '"', *v++ = '\n', *v++ = '"';
+ else
+ *v++ = '\'', *v++ = '\'';
+ continue;
+ } else
+ *v++ = '\\';
+ if(*u == Meta)
+ *v++ = *u++;
+ *v++ = *u;
+ }
+ *v = '\0';
+ if (strcmp(buf, s))
+ tt = dupstring(buf);
+ else
+ tt = s;
+ v += tt - buf;
+ if (e && (sf & 1))
+ *e += tt - buf;
+
+ if (e && *e == u)
+ *e = v;
+ if (te == u)
+ *pl = v - tt;
+
+ return (char *) tt;
+}
+
+/* This adds a match to the list of matches. The string to add is given *
+ * in s, the type of match is given in the global variable addwhat and *
+ * the parameter t (if not NULL) is a pointer to a hash node node which *
+ * may be used to give other information to this function. *
+ * *
+ * addwhat contains either one of the special values (negative, see below) *
+ * or the inclusive OR of some of the CC_* flags used for compctls. */
+
+/**/
+static void
+addmatch(char *s, char *t)
+{
+ int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp;
+ char *e = NULL, *tt, *te, *fc, **fm;
+ Comp cp = patcomp;
+ HashNode hn;
+ Param pm;
+ LinkList l = matches;
+
+/*
+ * addwhat: -5 is for files,
+ * -6 is for glob expansions,
+ * -8 is for executable files (e.g. command paths),
+ * -9 is for parameters
+ * -7 is for command names (from cmdnamtab)
+ * -4 is for a cdable parameter
+ * -3 is for executable command names.
+ * -2 is for anything unquoted
+ * -1 is for other file specifications
+ * (things with `~' of `=' at the beginning, ...).
+ */
+
+ /* Just to make the code cleaner */
+ hn = (HashNode) t;
+ pm = (Param) t;
+
+ if (!addwhat) {
+ test = 1;
+ } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 ||
+ addwhat == CC_FILES || addwhat == -7 || addwhat == -8) {
+ if (sl < fpl + fsl)
+ return;
+
+ if ((addwhat == CC_FILES ||
+ addwhat == -5) && !*psuf && !*fsuf) {
+ /* If this is a filename, do the fignore check. */
+ char **pt = fignore;
+ int filell;
+
+ for (test = 1; test && *pt; pt++)
+ if ((filell = strlen(*pt)) < sl
+ && !strcmp(*pt, s + sl - filell))
+ test = 0;
+
+ if (!test)
+ l = fmatches;
+ }
+ pl = fpl;
+ if (addwhat == -5 || addwhat == -8) {
+ test = 1;
+ cp = filecomp;
+ cc = cp || ispattern;
+ e = s + sl - fsl;
+ } else {
+ if ((cp = filecomp)) {
+ if ((test = domatch(s, filecomp, 0)))
+ cc = 1;
+ } else {
+ e = s + sl - fsl;
+ if ((test = !strncmp(s, fpre, fpl)))
+ test = !strcmp(e, fsuf);
+ if (ispattern)
+ cc = 1;
+ }
+ }
+ if (test) {
+ fc = NULL;
+ if (addwhat == -7 && !(fc = findcmd(s)))
+ return;
+ if (fc)
+ zsfree(fc);
+ haswhat |= HAS_FILES;
+
+ if (addwhat == CC_FILES || addwhat == -6 ||
+ addwhat == -5 || addwhat == -8) {
+ te = s + pl;
+ s = quotename(s, &e, te, &pl);
+ sl = strlen(s);
+ } else if (!cc) {
+ s = dupstring(t = s);
+ e += s - t;
+ }
+ if (cc) {
+ tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1);
+ strcpy(tt, ppre);
+ strcat(tt, s);
+ strcat(tt, psuf);
+ untokenize(s = tt);
+ }
+ }
+ } else if (addwhat == CC_QUOTEFLAG || addwhat == -2 ||
+ (addwhat == -3 && !(hn->flags & DISABLED)) ||
+ (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) &&
+ (tt = pm->gets.cfn(pm)) && *tt == '/') ||
+ (addwhat == -9 && !(hn->flags & PM_UNSET)) ||
+ (addwhat > 0 &&
+ ((!(hn->flags & PM_UNSET) &&
+ (((addwhat & CC_ARRAYS) && (hn->flags & PM_ARRAY)) ||
+ ((addwhat & CC_INTVARS) && (hn->flags & PM_INTEGER)) ||
+ ((addwhat & CC_ENVVARS) && (hn->flags & PM_EXPORTED)) ||
+ ((addwhat & CC_SCALARS) && (hn->flags & PM_SCALAR)) ||
+ ((addwhat & CC_READONLYS) && (hn->flags & PM_READONLY)) ||
+ ((addwhat & CC_SPECIALS) && (hn->flags & PM_SPECIAL)) ||
+ ((addwhat & CC_PARAMS) && !(hn->flags & PM_EXPORTED)))) ||
+ ((( addwhat & CC_SHFUNCS) ||
+ ( addwhat & CC_BUILTINS) ||
+ ( addwhat & CC_EXTCMDS) ||
+ ( addwhat & CC_RESWDS) ||
+ ((addwhat & CC_ALREG) && !(hn->flags & ALIAS_GLOBAL)) ||
+ ((addwhat & CC_ALGLOB) && (hn->flags & ALIAS_GLOBAL))) &&
+ (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) ||
+ ((addwhat & CC_EXCMDS) && !(hn->flags & DISABLED)))) ||
+ ((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) {
+ if (sl >= rpl + rsl) {
+ if (cp)
+ test = domatch(s, patcomp, 0);
+ else {
+ e = s + sl - rsl;
+ if ((test = !strncmp(s, rpre, rpl)))
+ test = !strcmp(e, rsuf);
+ }
+ }
+ if (!test && sl < lpl + lsl)
+ return;
+ if (!test && lpre && lsuf && sl >= lpl + lsl) {
+ e = s + sl - lsl;
+ if ((test = !strncmp(s, lpre, lpl)))
+ test = !strcmp(e, lsuf);
+ pl = lpl;
+ }
+ if (addwhat == CC_QUOTEFLAG) {
+ te = s + pl;
+ s = quotename(s, &e, te, &pl);
+ sl = strlen(s);
+ }
+ if (test)
+ haswhat |= HAS_MISC;
+ }
+ if (!test)
+ return;
+
+ if (ispattern) {
+ t = s;
+ } else {
+ t = s += pl;
+ if (*e)
+ t = s = dupstrpfx(t, e - t);
+ }
+
+ if (l == fmatches) {
+ bp = &fab;
+ ep = &fae;
+ sp = &fshortl;
+ fm = &ffirstm;
+ } else {
+ bp = &ab;
+ ep = &ae;
+ sp = &shortl;
+ fm = &firstm;
+ }
+
+ if (!ispattern && *fm) {
+ if ((test = pfxlen(*fm, s)) < *bp)
+ *bp = test;
+ if ((test = sfxlen(*fm, s)) < *ep)
+ *ep = test;
+ if (*ep > *sp - *bp)
+ *ep = *sp - *bp;
+ }
+
+ /* If we are doing a glob completion we store the whole string in *
+ * the list. Otherwise only the part that fits between the prefix *
+ * and the suffix is stored. */
+ addlinknode(l, t);
+ if (!*fm) {
+ *bp = *ep = 10000;
+ *fm = t;
+ *sp = 100000;
+ }
+ if (!ispattern && (sl = strlen(t)) < *sp) {
+ *sp = sl;
+ if (l == fmatches)
+ fshortest = t;
+ else
+ shortest = t;
+ }
+}
+
+#ifdef HAVE_NIS_PLUS
+static int
+match_username(nis_name table, nis_object *object, void *userdata)
+{
+ if (errflag)
+ return 1;
+ else {
+ static char buf[40];
+ register entry_col *ec =
+ object->zo_data.objdata_u.en_data.en_cols.en_cols_val;
+ register int l = minimum(ec->ec_value.ec_value_len, 39);
+
+ memcpy(buf, ec->ec_value.ec_value_val, l);
+ buf[l] = '\0';
+
+ addmatch(dupstring(buf), NULL);
+ }
+ return 0;
+}
+#else
+# ifdef HAVE_NIS
+static int
+match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data)
+{
+ if (errflag || status != YP_TRUE)
+ return 1;
+
+ if (vallen > keylen && val[keylen] == ':') {
+ val[keylen] = '\0';
+ addmatch(dupstring(val), NULL);
+ }
+ return 0;
+}
+# endif /* HAVE_NIS */
+#endif /* HAVE_NIS_PLUS */
+
+/**/
+static void
+maketildelist(void)
+{
+#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS)
+ FILE *pwf;
+ char buf[BUFSIZ], *p;
+ int skipping;
+
+# ifndef HAVE_NIS_PLUS
+ char domain[YPMAXDOMAIN];
+ struct ypall_callback cb;
+ dopestring data;
+
+ data.s = fpre;
+ data.len = fpl;
+ /* Get potential matches from NIS and cull those without local accounts */
+ if (getdomainname(domain, YPMAXDOMAIN) == 0) {
+ cb.foreach = (int (*)()) match_username;
+ cb.data = (char *)&data;
+ yp_all(domain, PASSWD_MAP, &cb);
+ }
+# else /* HAVE_NIS_PLUS */
+ /* Maybe we should turn this string into a #define'd constant...? */
+
+ nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH,
+ match_username, 0);
+# endif
+ /* Don't forget the non-NIS matches from the flat passwd file */
+ if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) {
+ skipping = 0;
+ while (fgets(buf, BUFSIZ, pwf) != NULL) {
+ if (strchr(buf, '\n') != NULL) {
+ if (!skipping) {
+ if ((p = strchr(buf, ':')) != NULL) {
+ *p = '\0';
+ addmatch(dupstring(buf), NULL);
+ }
+ } else
+ skipping = 0;
+ } else
+ skipping = 1;
+ }
+ fclose(pwf);
+ }
+#else /* no NIS or NIS_PLUS */
+ /* add all the usernames to the named directory table */
+ nameddirtab->filltable(nameddirtab);
+#endif
+
+ scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0,
+ addhnmatch, 0);
+}
+
+/* Copy the given string and remove backslashes from the copy and return it. */
+
+/**/
+static char *
+rembslash(char *s)
+{
+ char *t = s = dupstring(s);
+
+ while (*s)
+ if (*s == '\\') {
+ chuck(s);
+ if (*s)
+ s++;
+ } else
+ s++;
+
+ return t;
+}
+
+/* This does the check for compctl -x `n' and `N' patterns. */
+
+/**/
+static int
+getcpat(char *wrd, int cpatindex, char *cpat, int class)
+{
+ char *str, *s, *t, *p;
+ int d = 0;
+
+ if (!wrd || !*wrd)
+ return -1;
+
+ cpat = rembslash(cpat);
+
+ str = ztrdup(wrd);
+ untokenize(str);
+ if (!cpatindex)
+ cpatindex++, d = 0;
+ else if ((d = (cpatindex < 0)))
+ cpatindex = -cpatindex;
+
+ for (s = d ? str + strlen(str) - 1 : str;
+ d ? (s >= str) : *s;
+ d ? s-- : s++) {
+ for (t = s, p = cpat; *t && *p; p++) {
+ if (class) {
+ if (*p == *s && !--cpatindex) {
+ zsfree(str);
+ return (int)(s - str + 1);
+ }
+ } else if (*t++ != *p)
+ break;
+ }
+ if (!class && !*p && !--cpatindex) {
+ zsfree(str);
+ t += wrd - str;
+ for (d = 0; --t >= wrd;)
+ if (! INULL(*t))
+ d++;
+ return d;
+ }
+ }
+ zsfree(str);
+ return -1;
+}
+
+/* This holds a pointer to the compctl we are using. */
+
+static Compctl ccmain;
+
+
+/* Find the compctl to use and return it. The first argument gives a *
+ * compctl to start searching with (if it is zero, the hash table is *
+ * searched). compadd is used to return a number of characters that *
+ * should be ignored at the beginning of the word and incmd is *
+ * non-zero if we are in command position. */
+
+/**/
+static Compctl
+get_ccompctl(Compctl occ, int *compadd, int incmd)
+{
+ Compctl compc, ret;
+ Compctlp ccp;
+ int t, i, a, b, tt, ra, rb, j, isf = 1;
+ Compcond or, cc;
+ char *s, *ss, *sc, *cmd = dupstring(cmdstr);
+ Comp comp;
+
+ first_rec:
+ *compadd = 0;
+ ra = 0;
+ rb = clwnum - 1;
+ sc = NULL;
+
+ if (!(ret = compc = occ)) {
+ if (isf) {
+ isf = 0;
+ ret = &cc_first;
+ }
+ else if (inwhat == IN_ENV)
+ /* Default completion for parameter values. */
+ ret = &cc_default;
+ else if (inwhat == IN_MATH) {
+ /* Parameter names inside mathematical expression. */
+ cc_dummy.mask = CC_PARAMS;
+ ret = &cc_dummy;
+ cc_dummy.refc = 10000;
+ } else if (inwhat == IN_COND) {
+ /* We try to be clever here: in conditions we complete option *
+ * names after a `-o', file names after `-nt', `-ot', and `-ef' *
+ * and file names and parameter names elsewhere. */
+ s = clwpos ? clwords[clwpos - 1] : "";
+ cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS :
+ ((*s == '-' && s[1] && !s[2]) ||
+ !strcmp("-nt", s) ||
+ !strcmp("-ot", s) ||
+ !strcmp("-ef", s)) ? CC_FILES :
+ (CC_FILES | CC_PARAMS);
+ ret = &cc_dummy;
+ cc_dummy.refc = 10000;
+ } else if (incmd)
+ ret = &cc_compos;
+ /* And in redirections or if there is no command name (and we are *
+ * not in command position) or if no special compctl was given *
+ * for the command: use default completion. Note that we first *
+ * search the complete command name and than the trailing *
+ * pathname component. */
+ else if (linredir ||
+ !(cmd &&
+ (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) &&
+ (compc = ret = ccp->cc)) ||
+ ((s = dupstring(cmd)) && remlpaths(&s) &&
+ (ccp = (Compctlp) compctltab->getnode(compctltab, s)) &&
+ (compc = ret = ccp->cc)))))
+ ret = &cc_default;
+
+ ccmain = compc = ret;
+ ccmain->refc++;
+ }
+ /* The compctl we found has extended completion patterns, check them. */
+ if (compc && compc->ext) {
+ compc = compc->ext;
+ /* This loops over the patterns separated by `--'. */
+ for (t = 0; compc && !t; compc = compc->next) {
+ /* This loops over OR'ed patterns. */
+ for (cc = compc->cond; cc && !t; cc = or) {
+ or = cc->or;
+ /* This loops over AND'ed patterns. */
+ for (t = 1; cc && t; cc = cc->and) {
+ /* And this loops of [...] pairs. */
+ for (t = i = 0; i < cc->n && !t; i++) {
+ s = NULL;
+ ra = 0;
+ rb = clwnum - 1;
+ switch (cc->type) {
+ case CCT_POS:
+ tt = clwpos;
+ goto cct_num;
+ case CCT_NUMWORDS:
+ tt = clwnum;
+ cct_num:
+ if ((a = cc->u.r.a[i]) < 0)
+ a += clwnum;
+ if ((b = cc->u.r.b[i]) < 0)
+ b += clwnum;
+ if (cc->type == CCT_POS)
+ ra = a, rb = b;
+ t = (tt >= a && tt <= b);
+ break;
+ case CCT_CURSUF:
+ case CCT_CURPRE:
+ s = ztrdup(clwpos < clwnum ? clwords[clwpos] : "");
+ untokenize(s);
+ sc = rembslash(cc->u.s.s[i]);
+ a = strlen(sc);
+ if (!strncmp(s, sc, a)) {
+ *compadd = (cc->type == CCT_CURSUF ? a : 0);
+ t = 1;
+ }
+ break;
+ case CCT_CURSUB:
+ case CCT_CURSUBC:
+ if (clwpos < 0 || clwpos > clwnum)
+ t = 0;
+ else {
+ a = getcpat(clwords[clwpos],
+ cc->u.s.p[i],
+ cc->u.s.s[i],
+ cc->type == CCT_CURSUBC);
+ if (a != -1)
+ *compadd = a, t = 1;
+ }
+ break;
+
+ case CCT_CURPAT:
+ case CCT_CURSTR:
+ tt = clwpos;
+ goto cct_str;
+ case CCT_WORDPAT:
+ case CCT_WORDSTR:
+ tt = 0;
+ cct_str:
+ if ((a = tt + cc->u.s.p[i]) < 0)
+ a += clwnum;
+ s = ztrdup((a < 0 || a >= clwnum) ? "" :
+ clwords[a]);
+ untokenize(s);
+
+ if (cc->type == CCT_CURPAT ||
+ cc->type == CCT_WORDPAT) {
+ tokenize(ss = dupstring(cc->u.s.s[i]));
+ t = ((comp = parsereg(ss)) &&
+ domatch(s, comp, 0));
+ } else
+ t = (!strcmp(s, rembslash(cc->u.s.s[i])));
+ break;
+ case CCT_RANGESTR:
+ case CCT_RANGEPAT:
+ if (cc->type == CCT_RANGEPAT)
+ tokenize(sc = dupstring(cc->u.l.a[i]));
+ for (j = clwpos; j; j--) {
+ untokenize(s = ztrdup(clwords[j]));
+ if (cc->type == CCT_RANGESTR)
+ sc = rembslash(cc->u.l.a[i]);
+ if (cc->type == CCT_RANGESTR ?
+ !strncmp(s, sc, strlen(sc)) :
+ ((comp = parsereg(sc)) &&
+ domatch(s, comp, 0))) {
+ zsfree(s);
+ ra = j + 1;
+ t = 1;
+ break;
+ }
+ zsfree(s);
+ }
+ if (t) {
+ if (cc->type == CCT_RANGEPAT)
+ tokenize(sc = dupstring(cc->u.l.b[i]));
+ for (j++; j < clwnum; j++) {
+ untokenize(s = ztrdup(clwords[j]));
+ if (cc->type == CCT_RANGESTR)
+ sc = rembslash(cc->u.l.b[i]);
+ if (cc->type == CCT_RANGESTR ?
+ !strncmp(s, sc, strlen(sc)) :
+ ((comp = parsereg(sc)) &&
+ domatch(s, comp, 0))) {
+ zsfree(s);
+ rb = j - 1;
+ t = clwpos <= rb;
+ break;
+ }
+ zsfree(s);
+ }
+ }
+ s = NULL;
+ }
+ zsfree(s);
+ }
+ }
+ }
+ if (t)
+ break;
+ }
+ if (compc)
+ /* We found a matching pattern, we may return it. */
+ ret = compc;
+ }
+ if (ret->subcmd) {
+ /* The thing we want to return has a subcmd flag (-l). */
+ char **ow = clwords, *os = cmdstr, *ops = NULL;
+ int oldn = clwnum, oldp = clwpos;
+
+ /* So we restrict the words-array. */
+ if (ra >= clwnum)
+ ra = clwnum - 1;
+ if (ra < 1)
+ ra = 1;
+ if (rb >= clwnum)
+ rb = clwnum - 1;
+ if (rb < 1)
+ rb = 1;
+ clwnum = rb - ra + 1;
+ clwpos = clwpos - ra;
+
+ if (ret->subcmd[0]) {
+ /* And probably put the command name given to the flag *
+ * in the array. */
+ clwpos++;
+ clwnum++;
+ incmd = 0;
+ ops = clwords[ra - 1];
+ clwords[ra - 1] = cmdstr = ret->subcmd;
+ clwords += ra - 1;
+ } else {
+ cmdstr = clwords[ra];
+ incmd = !clwpos;
+ clwords += ra;
+ }
+ *compadd = 0;
+ if (ccmain != &cc_dummy)
+ freecompctl(ccmain);
+ /* Then we call this function recursively. */
+
+ ret = get_ccompctl(NULL, compadd, incmd);
+ /* And restore the things we changed. */
+ clwords = ow;
+ cmdstr = os;
+ clwnum = oldn;
+ clwpos = oldp;
+ if (ops)
+ clwords[ra - 1] = ops;
+ }
+ if (ret == &cc_first)
+ goto first_rec;
+ return ret;
+}
+
+/* Dump a hash table (without sorting). For each element the addmatch *
+ * function is called and at the beginning the addwhat variable is set. *
+ * This could be done using scanhashtable(), but this is easy and much *
+ * more efficient. */
+
+/**/
+static void
+dumphashtable(HashTable ht, int what)
+{
+ HashNode hn;
+ int i;
+
+ addwhat = what;
+
+ for (i = 0; i < ht->hsize; i++)
+ for (hn = ht->nodes[i]; hn; hn = hn->next)
+ addmatch(hn->nam, (char *) hn);
+
+}
+
+/* ScanFunc used by maketildelist() et al. */
+
+/**/
+static void
+addhnmatch(HashNode hn, int flags)
+{
+ addmatch(hn->nam, NULL);
+}
+
+/* Perform expansion on the given string and return the result. *
+ * During this errors are not reported. */
+
+/**/
+static char *
+getreal(char *str)
+{
+ LinkList l = newlinklist();
+ int ne = noerrs;
+
+ noerrs = 1;
+ addlinknode(l, dupstring(str));
+ prefork(l, 0);
+ noerrs = ne;
+ if (!errflag && nonempty(l))
+ return ztrdup(peekfirst(l));
+ errflag = 0;
+
+ return ztrdup(str);
+}
+
+/* This reads a directory and adds the files to the list of *
+ * matches. The parameters say which files should be added. */
+
+/**/
+static void
+gen_matches_files(int dirs, int execs, int all)
+{
+ DIR *d;
+ struct stat buf;
+ char *n, p[PATH_MAX], *q = NULL, *e;
+ LinkList l = NULL;
+ int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat;
+
+ addwhat = execs ? -8 : -5;
+ opts[NULLGLOB] = 1;
+
+ if (*psuf) {
+ /* If there is a path suffix, check if it doesn't have a `*' or *
+ * `)' at the end (this is used to determine if we should use *
+ * globbing). */
+ q = psuf + strlen(psuf) - 1;
+ ns = !(*q == Star || *q == Outpar);
+ l = newlinklist();
+ /* And generate only directory names. */
+ dirs = 1;
+ all = execs = 0;
+ }
+ /* Open directory. */
+ if ((d = opendir((prpre && *prpre) ? prpre : "."))) {
+ /* If we search only special files, prepare a path buffer for stat. */
+ if (!all && prpre) {
+ strcpy(p, prpre);
+ q = p + strlen(prpre);
+ }
+ /* Fine, now read the directory. */
+ while ((n = zreaddir(d, 1)) && !errflag) {
+ /* Ignore files beginning with `.' unless the thing we found on *
+ * the command line also starts with a dot or GLOBDOTS is set. */
+ if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) {
+ if (filecomp)
+ /* If we have a pattern for the filename check, use it. */
+ test = domatch(n, filecomp, 0);
+ else {
+ /* Otherwise use the prefix and suffix strings directly. */
+ e = n + strlen(n) - fsl;
+ if ((test = !strncmp(n, fpre, fpl)))
+ test = !strcmp(e, fsuf);
+ }
+ /* Filename didn't match? */
+ if (!test)
+ continue;
+ if (!all) {
+ /* We still have to check the file type, so prepare *
+ * the path buffer by appending the filename. */
+ strcpy(q, n);
+ /* And do the stat. */
+ if (stat(p, &buf) < 0)
+ continue;
+ }
+ if (all ||
+ (dirs && S_ISDIR(buf.st_mode)) ||
+ (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) {
+ /* If we want all files or the file has the right type... */
+ if (*psuf) {
+ /* We have to test for a path suffix. */
+ int o = strlen(p), tt;
+
+ /* Append it to the path buffer. */
+ strcpy(p + o, psuf);
+
+ /* Do we have to use globbing? */
+ if (ispattern || (ns && isset(GLOBCOMPLETE))) {
+ /* Yes, so append a `*' if needed. */
+ if (ns) {
+ int tl = strlen(p);
+
+ p[tl] = Star;
+ p[tl + 1] = '\0';
+ }
+ /* Do the globbing... */
+ remnulargs(p);
+ addlinknode(l, p);
+ globlist(l);
+ /* And see if that produced a filename. */
+ tt = nonempty(l);
+ while (ugetnode(l));
+ } else
+ /* Otherwise just check, if we have access *
+ * to the file. */
+ tt = !access(p, F_OK);
+
+ p[o] = '\0';
+ if (tt)
+ /* Ok, we can add the filename to the *
+ * list of matches. */
+ addmatch(dupstring(n), NULL);
+ } else
+ /* We want all files, so just add the name *
+ * to the matches. */
+ addmatch(dupstring(n), NULL);
+ }
+ }
+ }
+ closedir(d);
+ }
+ opts[NULLGLOB] = ng;
+ addwhat = aw;
+}
+
+/* This holds the explanation string we have to print. */
+
+static char *expl;
+
+/* This holds the suffix to add (given with compctl -S). */
+
+static char *ccsuffix;
+
+/* This s non-zero if the compctl -q flag was given (the suffix should *
+ * be removed when a space or something like that is typed next). */
+
+static int remsuffix;
+
+/**/
+static void
+quotepresuf(char **ps)
+{
+ if (*ps) {
+ char *p = quotename(*ps, NULL, NULL, NULL);
+
+ if (p != *ps) {
+ zsfree(*ps);
+ *ps = ztrdup(p);
+ }
+ }
+}
+
+/**/
+static void
+docompletion(char *s, int lst, int incmd, int untokenized)
+{
+ static int delit, compadd;
+
+ fixsuffix();
+ HEAPALLOC {
+ pushheap();
+
+ /* Make sure we have the completion list and compctl. */
+ if(makecomplist(s, incmd, &delit, &compadd, untokenized)) {
+ /* Error condition: feeeeeeeeeeeeep(). */
+ feep();
+ goto compend;
+ }
+
+ if (lst == COMP_LIST_COMPLETE)
+ /* All this and the guy only wants to see the list, sigh. */
+ showinglist = -2;
+ else {
+ /* We have matches. */
+ if (delit) {
+ /* If we have to delete the word from the command line, *
+ * do it now. */
+ wb -= compadd;
+ strcpy((char *)line + wb, (char *)line + we);
+ we = cs = wb;
+ }
+ if (nmatches > 1)
+ /* There are more than one match. */
+ do_ambiguous();
+ else if (nmatches == 1) {
+ /* Only one match. */
+ do_single(amatches[0]);
+ invalidatelist();
+ }
+ }
+
+ /* Print the explanation string if needed. */
+ if (!showinglist && expl && nmatches != 1) {
+ int up;
+
+ if (!nmatches)
+ feep();
+ trashzle();
+
+ clearflag = (isset(USEZLE) && !termflags &&
+ (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
+ (unset(ALWAYSLASTPROMPT) && zmult != 1);
+
+ up = printfmt(expl, nmatches, 1);
+
+ if (clearflag)
+ tcmultout(TCUP, TCMULTUP, up + nlnct);
+ else
+ putc('\n', shout);
+ fflush(shout);
+ }
+ compend:
+ ll = strlen((char *)line);
+ if (cs > ll)
+ cs = ll;
+ popheap();
+ } LASTALLOC;
+}
+
+/* Create the completion list. This is called whenever some bit of *
+ * completion code needs the list. If the list is already available *
+ * (validlist!=0), this function doesn't do anything. Along with *
+ * the list is maintained the prefixes/suffixes etc. When any of *
+ * this becomes invalid -- e.g. if some text is changed on the *
+ * command line -- invalidatelist() should be called, to set *
+ * validlist to zero and free up the memory used. This function *
+ * returns non-zero on error. delit and compadd return information *
+ * about bits of the command line that need to be deleted. */
+
+/**/
+static int
+makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized)
+{
+ Compctl cc = NULL;
+ int oloffs = offs, owe = we, owb = wb, ocs = cs, oll = ll, isf = 1;
+ int t, sf1, sf2, ooffs;
+ char *p, *sd = NULL, *tt, *s1, *s2, *os = NULL;
+ unsigned char *ol = NULL;
+
+ /* If we already have a list from a previous execution of this *
+ * function, skip the list building code. */
+ if (validlist)
+ return !nmatches;
+
+ os = dupstring(s);
+ ol = (unsigned char *)dupstring((char *)line);
+
+ xorrec:
+
+ DPUTS(ll != strlen((char *) line), "BUG: xorrec: ll != strlen(line)");
+
+ /* Go to the end of the word if complete_in_word is not set. */
+ if (unset(COMPLETEINWORD) && cs != we)
+ cs = we, offs = strlen(s);
+
+ ispattern = haswhat = lastambig = 0;
+ patcomp = filecomp = NULL;
+ menucur = NULL;
+ shortest = NULL;
+ fshortest = NULL;
+ rpre = rsuf = lpre = lsuf = ppre = psuf = prpre =
+ fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL;
+
+ /* Blank out the lists. */
+ matches = newlinklist();
+ fmatches = newlinklist();
+
+ /* If we don't have a compctl definition yet or we have a compctl *
+ * with extended completion, get it (or the next one, resp.). */
+ if (!cc || cc->ext)
+ cc = get_ccompctl(cc, compadd, incmd);
+
+ /* *compadd is the number of characters we have to ignore at the *
+ * beginning of the word. */
+ wb += *compadd;
+ s += *compadd;
+ if ((offs -= *compadd) < 0)
+ /* It's bigger than our word prefix, so we can't help here... */
+ return 1;
+
+ /* Insert the prefix (compctl -P), if any. */
+ if (cc->prefix) {
+ int pl = 0, sl = strlen(cc->prefix);
+
+ if (*s) {
+ /* First find out how much of the prefix is already on the line. */
+ sd = dupstring(s);
+ untokenize(sd);
+ pl = pfxlen(cc->prefix, sd);
+ s += pl;
+ }
+ if (pl < sl) {
+ int savecs = cs;
+
+ /* Then insert the prefix. */
+ cs = wb + pl;
+ inststrlen(cc->prefix + pl, 0, sl - pl);
+ cs = savecs + sl - pl;
+ }
+ /* And adjust the word beginning/end variables. */
+ wb += sl;
+ we += sl - pl;
+ offs -= pl;
+ }
+ /* Does this compctl have a suffix (compctl -S)? */
+ if ((ccsuffix = cc->suffix) && *ccsuffix) {
+ char *sdup = dupstring(ccsuffix);
+ int sl = strlen(sdup), suffixll;
+
+ /* Ignore trailing spaces. */
+ for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--);
+ p[1] = '\0';
+
+ if (!sd) {
+ sd = dupstring(s);
+ untokenize(sd);
+ }
+ /* If the suffix is already there, ignore it (and don't add *
+ * it again). */
+ if (*sd && (suffixll = strlen(sd)) >= sl &&
+ offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) {
+ ccsuffix = NULL;
+ haswhat |= HAS_SUFFIX;
+ s[suffixll - sl] = '\0';
+ }
+ }
+ /* Do we have one of the special characters `~' and `=' at the beginning? */
+ if ((ic = *s) != Tilde && ic != Equals)
+ ic = 0;
+
+ /* Check if we have to complete a parameter name... */
+
+ /* Try to find a `$'. */
+ for (p = s + offs; p > s && *p != String; p--);
+ if (*p == String) {
+ /* Handle $$'s */
+ while (p > s && p[-1] == String)
+ p--;
+ while (p[1] == String && p[2] == String)
+ p += 2;
+ }
+ if (*p == String && p[1] != Inpar && p[1] != Inbrack) {
+ /* This is really a parameter expression (not $(...) or $[...]). */
+ char *b = p + 1, *e = b;
+ int n = 0, br = 1;
+
+ if (*b == Inbrace) {
+ /* If this is a ${...}, ignore the possible (...) flags. */
+ b++, br++;
+ n = skipparens(Inpar, Outpar, &b);
+ }
+
+ /* Ignore the stuff before the parameter name. */
+ for (; *b; b++)
+ if (*b != '^' && *b != Hat &&
+ *b != '=' && *b != Equals &&
+ *b != '~' && *b != Tilde)
+ break;
+ if (*b == '#' || *b == Pound || *b == '+')
+ b++;
+
+ e = b;
+ /* Find the end of the name. */
+ if (*e == Quest || *e == Star || *e == String || *e == Qstring ||
+ *e == '?' || *e == '*' || *e == '$' ||
+ *e == '-' || *e == '!' || *e == '@')
+ e++;
+ else if (idigit(*e))
+ while (idigit(*e))
+ e++;
+ else if (iident(*e))
+ while (iident(*e) ||
+ (useglob && (*e == Star || *e == Quest)))
+ e++;
+
+ /* Now make sure that the cursor is inside the name. */
+ if (offs <= e - s && offs >= b - s && n <= 0) {
+ /* It is. */
+ parambr = br - 1;
+ /* Get the prefix (anything up to the character before the name). */
+ *e = '\0';
+ parampre = ztrduppfx(s, b - s);
+ qparampre = ztrdup(quotename(parampre, NULL, NULL, NULL));
+ untokenize(qparampre);
+ qparprelen = strlen(qparampre);
+ /* And adjust wb, we, and offs again. */
+ offs -= b - s;
+ wb = cs - offs;
+ we = wb + e - b;
+ s = b;
+ /* And now make sure that we complete parameter names. */
+ cc = ccmain = &cc_dummy;
+ cc_dummy.refc = 10000;
+ cc_dummy.mask = CC_PARAMS | CC_ENVVARS;
+ }
+ }
+ ooffs = offs;
+ /* If we have to ignore the word, do that. */
+ if (cc->mask & CC_DELETE) {
+ *delit = 1;
+ *s = '\0';
+ offs = 0;
+ } else
+ *delit = 0;
+
+ /* Compute line prefix/suffix. */
+
+ lpl = offs;
+ lpre = zalloc(lpl + 1);
+ memcpy(lpre, s, lpl);
+ lpre[lpl] = '\0';
+ p = quotename(lpre, NULL, NULL, NULL);
+ if (strcmp(p, lpre) && !strpfx(p, qword)) {
+ int l1, l2;
+
+ backdel(l1 = cs - wb);
+ untokenize(p);
+ inststrlen(p, 1, l2 = strlen(p));
+ we += l2 - l1;
+ }
+ lsuf = ztrdup(s + offs);
+ lsl = strlen(lsuf);
+ if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) &&
+ (strcmp(p, lsuf) && !strsfx(p, qword))) {
+ int l1, l2;
+
+ foredel(l1 = strlen(s + offs));
+ untokenize(p);
+ inststrlen(p, 0, l2 = strlen(p));
+ we += l2 - l1;
+ }
+
+ /* First check for ~.../... */
+ if (ic == Tilde) {
+ for (p = lpre + lpl; p > lpre; p--)
+ if (*p == '/')
+ break;
+
+ if (*p == '/')
+ ic = 0;
+ }
+ /* Compute real prefix/suffix. */
+
+ noreal = !*delit;
+ for (p = lpre; *p && *p != String && *p != Tick; p++);
+ tt = ic && !parampre ? lpre + 1 : lpre;
+ rpre = (*p || *lpre == Tilde || *lpre == Equals) ?
+ (noreal = 0, getreal(tt)) :
+ ztrdup(tt);
+
+ for (p = lsuf; *p && *p != String && *p != Tick; p++);
+ rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf);
+
+ /* Check if word is a pattern. */
+
+ for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1;
+ p >= rpre && (ispattern != 3 || !sf1);
+ p--)
+ if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde)))
+ ispattern |= sf1 ? 1 : 2;
+ else if (*p == '/') {
+ sf1++;
+ if (!s1)
+ s1 = p;
+ }
+ for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++)
+ if (itok(*p))
+ t |= sf2 ? 4 : 2;
+ else if (*p == '/') {
+ sf2++;
+ if (!s2)
+ s2 = p;
+ }
+ ispattern = ispattern | t;
+
+ /* But if we were asked not to do glob completion, we never treat the *
+ * thing as a pattern. */
+ if (!useglob)
+ ispattern = 0;
+
+ if (ispattern) {
+ /* The word should be treated as a pattern, so compute the matcher. */
+ p = (char *)ncalloc(rpl + rsl + 2);
+ strcpy(p, rpre);
+ if (rpl && p[rpl - 1] != Star) {
+ p[rpl] = Star;
+ strcpy(p + rpl + 1, rsuf);
+ } else
+ strcpy(p + rpl, rsuf);
+ patcomp = parsereg(p);
+ }
+ if (!patcomp) {
+ untokenize(rpre);
+ untokenize(rsuf);
+
+ rpl = strlen(rpre);
+ rsl = strlen(rsuf);
+ }
+ untokenize(lpre);
+ untokenize(lsuf);
+
+ /* Handle completion of files specially (of course). */
+
+ if ((cc->mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) || cc->glob) {
+ /* s1 and s2 point to the last/first slash in the prefix/suffix. */
+ if (!s1)
+ s1 = rpre;
+ if (!s2)
+ s2 = rsuf + rsl;
+
+ /* Compute the path prefix/suffix. */
+ if (*s1 != '/')
+ ppre = ztrdup("");
+ else
+ ppre = ztrduppfx(rpre, s1 - rpre + 1);
+ psuf = ztrdup(s2);
+
+ /* And get the file prefix. */
+ fpre = ztrdup(((s1 == s || s1 == rpre || ic) &&
+ (*s != '/' || cs == wb)) ? s1 : s1 + 1);
+ /* And the suffix. */
+ fsuf = ztrduppfx(rsuf, s2 - rsuf);
+
+ if (useglob && (ispattern & 2)) {
+ int t2;
+
+ /* We have to use globbing, so compute the pattern from *
+ * the file prefix and suffix with a `*' between them. */
+ p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2);
+ strcpy(p, fpre);
+ if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star)
+ p[t2++] = Star;
+ strcpy(p + t2, fsuf);
+ filecomp = parsereg(p);
+ }
+ if (!filecomp) {
+ untokenize(fpre);
+ untokenize(fsuf);
+
+ fpl = strlen(fpre);
+ fsl = strlen(fsuf);
+ }
+ addwhat = -1;
+
+ /* Completion after `~', maketildelist adds the usernames *
+ * and named directories. */
+ if (ic == Tilde)
+ maketildelist();
+ else if (ic == Equals) {
+ /* Completion after `=', get the command names from *
+ * the cmdnamtab and aliases from aliastab. */
+ if (isset(HASHLISTALL))
+ cmdnamtab->filltable(cmdnamtab);
+ dumphashtable(cmdnamtab, -7);
+ dumphashtable(aliastab, -2);
+ } else {
+ /* Normal file completion... */
+ if (ispattern & 1) {
+ /* But with pattern matching. */
+ LinkList l = newlinklist();
+ LinkNode n;
+ int ng = opts[NULLGLOB];
+
+ opts[NULLGLOB] = 1;
+
+ addwhat = 0;
+ p = (char *)ncalloc(lpl + lsl + 3);
+ strcpy(p, lpre);
+ if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*')
+ strcat(p, "*");
+ strcat(p, lsuf);
+ if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')')
+ strcat(p, "*");
+
+ /* Do the globbing. */
+ tokenize(p);
+ remnulargs(p);
+ addlinknode(l, p);
+ globlist(l);
+
+ if (nonempty(l)) {
+ /* And add the resulting words. */
+ haswhat |= HAS_PATHPAT;
+ for (n = firstnode(l); n; incnode(n))
+ addmatch(getdata(n), NULL);
+ }
+ opts[NULLGLOB] = ng;
+ } else {
+ /* No pattern matching. */
+ addwhat = CC_FILES;
+ if (cc->withd) {
+ prpre = tricat(cc->withd, "/", ppre);
+ } else
+ prpre = ztrdup(ppre);
+
+ if (sf2)
+ /* We are in the path, so add only directories. */
+ gen_matches_files(1, 0, 0);
+ else {
+ if (cc->mask & CC_FILES)
+ /* Add all files. */
+ gen_matches_files(0, 0, 1);
+ else if (cc->mask & CC_COMMPATH) {
+ /* Completion of command paths. */
+ if (sf1 || cc->withd)
+ /* There is a path prefix, so add *
+ * directories and executables. */
+ gen_matches_files(1, 1, 0);
+ else {
+ /* No path prefix, so add the things *
+ * reachable via the PATH variable. */
+ char **pc = path, *pp = prpre;
+
+ for (; *pc; pc++)
+ if (!**pc || (pc[0][0] == '.' && !pc[0][1]))
+ break;
+ if (*pc) {
+ prpre = "./";
+ gen_matches_files(1, 1, 0);
+ prpre = pp;
+ }
+ }
+ } else if (cc->mask & CC_DIRS)
+ gen_matches_files(1, 0, 0);
+ /* The compctl has a glob pattern (compctl -g). */
+ if (cc->glob) {
+ int ns, pl = strlen(prpre), o;
+ char *g = dupstring(cc->glob), pa[PATH_MAX];
+ char *p2, *p3;
+ int ne = noerrs, md = opts[MARKDIRS];
+
+ /* These are used in the globbing code to make *
+ * things a bit faster. */
+ glob_pre = fpre;
+ glob_suf = fsuf;
+
+ noerrs = 1;
+ addwhat = -6;
+ strcpy(pa, prpre);
+ o = strlen(pa);
+ opts[MARKDIRS] = 0;
+
+ /* The compctl -g string may contain more than *
+ * one pattern, so we need a loop. */
+ while (*g) {
+ LinkList l = newlinklist();
+ int ng;
+
+ /* Find the blank terminating the pattern. */
+ while (*g && inblank(*g))
+ g++;
+ /* Oops, we already reached the end of the
+ string. */
+ if (!*g)
+ break;
+ for (p = g + 1; *p && !inblank(*p); p++)
+ if (*p == '\\' && p[1])
+ p++;
+ /* Get the pattern string. */
+ tokenize(g = dupstrpfx(g, p - g));
+ if (*g == '=')
+ *g = Equals;
+ if (*g == '~')
+ *g = Tilde;
+ remnulargs(g);
+ if ((*g == Equals || *g == Tilde) && !cc->withd) {
+ /* The pattern has a `~' or `=' at the *
+ * beginning, so we expand this and use *
+ * the result. */
+ filesub(&g, 0);
+ addlinknode(l, dupstring(g));
+ } else if (*g == '/' && !cc->withd)
+ /* The pattern is a full path (starting *
+ * with '/'), so add it unchanged. */
+ addlinknode(l, dupstring(g));
+ else {
+ /* It's a simple pattern, so append it to *
+ * the path we have on the command line. */
+ strcpy(pa + o, g);
+ addlinknode(l, dupstring(pa));
+ }
+ /* Do the globbing. */
+ ng = opts[NULLGLOB];
+ opts[NULLGLOB] = 1;
+ globlist(l);
+ opts[NULLGLOB] = ng;
+ /* Get the results. */
+ if (nonempty(l) && peekfirst(l)) {
+ for (p2 = (char *)peekfirst(l); *p2; p2++)
+ if (itok(*p2))
+ break;
+ if (!*p2) {
+ if ((*g == Equals || *g == Tilde ||
+ *g == '/') || cc->withd) {
+ /* IF the pattern started with `~', *
+ * `=', or `/', add the result only, *
+ * if it really matches what we have *
+ * on the line. *
+ * Do this if an initial directory *
+ * was specified, too. */
+ while ((p2 = (char *)ugetnode(l)))
+ if (strpfx(prpre, p2))
+ addmatch(p2 + pl, NULL);
+ } else {
+ /* Otherwise ignore the path we *
+ * prepended to the pattern. */
+ while ((p2 = p3 =
+ (char *)ugetnode(l))) {
+ for (ns = sf1; *p3 && ns; p3++)
+ if (*p3 == '/')
+ ns--;
+
+ addmatch(p3, NULL);
+ }
+ }
+ }
+ }
+ pa[o] = '\0';
+ g = p;
+ }
+ glob_pre = glob_suf = NULL;
+ noerrs = ne;
+ opts[MARKDIRS] = md;
+ }
+ }
+ }
+ }
+ }
+ /* Use tricat() instead of dyncat() to get zalloc()'d memory. */
+ if (ic) {
+ /* Now change the `~' and `=' tokens to the real characters so *
+ * that things starting with these characters will be added. */
+ char *orpre = rpre;
+
+ rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre);
+ rpl++;
+ zsfree(orpre);
+ }
+ if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) {
+ /* If we have to complete commands, add alias names, *
+ * shell functions and builtins too. */
+ dumphashtable(aliastab, -3);
+ dumphashtable(reswdtab, -3);
+ dumphashtable(shfunctab, -3);
+ dumphashtable(builtintab, -3);
+ if (isset(HASHLISTALL))
+ cmdnamtab->filltable(cmdnamtab);
+ dumphashtable(cmdnamtab, -3);
+ /* And parameter names if autocd and cdablevars are set. */
+ if (isset(AUTOCD) && isset(CDABLEVARS))
+ dumphashtable(paramtab, -4);
+ }
+ addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG;
+
+ if (cc->mask & CC_NAMED)
+ /* Add named directories. */
+ dumphashtable(nameddirtab, addwhat);
+ if (cc->mask & CC_OPTIONS)
+ /* Add option names. */
+ dumphashtable(optiontab, addwhat);
+ if (cc->mask & CC_VARS)
+ /* And parameter names. */
+ dumphashtable(paramtab, -9);
+ if (cc->mask & CC_BINDINGS)
+ /* And zle function names... */
+ dumphashtable(thingytab, CC_BINDINGS);
+ if (cc->keyvar) {
+ /* This adds things given to the compctl -k flag *
+ * (from a parameter or a list of words). */
+ char **usr = get_user_var(cc->keyvar);
+
+ if (usr)
+ while (*usr)
+ addmatch(*usr++, NULL);
+ }
+ if (cc->mask & CC_USERS)
+ /* Add user names. */
+ maketildelist();
+ if (cc->func) {
+ /* This handles the compctl -K flag. */
+ List list;
+ char **r;
+ int lv = lastval;
+
+ /* Get the function. */
+ if ((list = getshfunc(cc->func)) != &dummy_list) {
+ /* We have it, so build a argument list. */
+ LinkList args = newlinklist();
+
+ addlinknode(args, cc->func);
+
+ if (*delit) {
+ p = dupstrpfx(os, ooffs);
+ untokenize(p);
+ addlinknode(args, p);
+ p = dupstring(os + ooffs);
+ untokenize(p);
+ addlinknode(args, p);
+ } else {
+ addlinknode(args, lpre);
+ addlinknode(args, lsuf);
+ }
+
+ /* This flag allows us to use read -l and -c. */
+ incompctlfunc = 1;
+ /* Call the function. */
+ doshfunc(list, args, 0, 1);
+ incompctlfunc = 0;
+ /* And get the result from the reply parameter. */
+ if ((r = get_user_var("reply")))
+ while (*r)
+ addmatch(*r++, NULL);
+ }
+ lastval = lv;
+ }
+ if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) {
+ /* Get job names. */
+ int i;
+ char *j, *jj;
+
+ for (i = 0; i < MAXJOB; i++)
+ if (jobtab[i].stat & STAT_INUSE) {
+ int stopped = jobtab[i].stat & STAT_STOPPED;
+
+ j = jj = dupstring(jobtab[i].procs->text);
+ /* Find the first word. */
+ for (; *jj; jj++)
+ if (*jj == ' ') {
+ *jj = '\0';
+ break;
+ }
+ if ((cc->mask & CC_JOBS) ||
+ (stopped && (cc->mask & CC_STOPPED)) ||
+ (!stopped && (cc->mask & CC_RUNNING)))
+ addmatch(j, NULL);
+ }
+ }
+ if (cc->str) {
+ /* Get the stuff from a compctl -s. */
+ LinkList foo = newlinklist();
+ LinkNode n;
+ int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb;
+ char *tmpbuf;
+
+ opts[NULLGLOB] = 1;
+
+ /* Put the strin in the lexer buffer and call the lexer to *
+ * get the words we have to expand. */
+ zleparse = 1;
+ lexsave();
+ tmpbuf = (char *)halloc(strlen(cc->str) + 5);
+ sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */
+ inpush(tmpbuf, 0, NULL);
+ strinbeg();
+ noaliases = 1;
+ do {
+ ctxtlex();
+ if (tok == ENDINPUT || tok == LEXERR)
+ break;
+ if (!first && tokstr && *tokstr)
+ addlinknode(foo, ztrdup(tokstr));
+ first = 0;
+ } while (tok != ENDINPUT && tok != LEXERR);
+ noaliases = 0;
+ strinend();
+ inpop();
+ errflag = zleparse = 0;
+ lexrestore();
+ /* Fine, now do full expansion. */
+ prefork(foo, 0);
+ if (!errflag) {
+ globlist(foo);
+ if (!errflag)
+ /* And add the resulting words as matches. */
+ for (n = firstnode(foo); n; incnode(n))
+ addmatch((char *)n->dat, NULL);
+ }
+ opts[NULLGLOB] = ng;
+ we = oowe;
+ wb = oowb;
+ }
+ if (cc->hpat) {
+ /* We have a pattern to take things from the history. */
+ Comp compc = NULL;
+ char *e, *h, hpatsav;
+ Histent he;
+ int i = curhist - 1, n = cc->hnum;
+
+ /* Parse the pattern, if it isn't the null string. */
+ if (*(cc->hpat)) {
+ char *thpat = dupstring(cc->hpat);
+
+ tokenize(thpat);
+ compc = parsereg(thpat);
+ }
+ /* n holds the number of history line we have to search. */
+ if (!n)
+ n = -1;
+
+ /* Now search the history. */
+ while (n-- && (he = quietgethist(i--))) {
+ int iwords;
+ for (iwords = 0; iwords < he->nwords; iwords++) {
+ h = he->text + he->words[iwords*2];
+ e = he->text + he->words[iwords*2+1];
+ hpatsav = *e;
+ *e = '\0';
+ /* We now have a word from the history, ignore it *
+ * if it begins with a quote or `$'. */
+ if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' &&
+ (!compc || domatch(h, compc, 0)))
+ /* Otherwise add it if it was matched. */
+ addmatch(dupstring(h), NULL);
+ if (hpatsav)
+ *e = hpatsav;
+ }
+ }
+ }
+ if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS |
+ CC_READONLYS | CC_SPECIALS | CC_PARAMS)))
+ /* Add various flavours of parameters. */
+ dumphashtable(paramtab, t);
+ if ((t = cc->mask & CC_SHFUNCS))
+ /* Add shell functions. */
+ dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+ if ((t = cc->mask & CC_BUILTINS))
+ /* Add builtins. */
+ dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+ if ((t = cc->mask & CC_EXTCMDS))
+ /* Add external commands */
+ dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+ if ((t = cc->mask & CC_RESWDS))
+ /* Add reserved words */
+ dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+ if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
+ /* Add the two types of aliases. */
+ dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
+
+ /* If we have no matches, ignore fignore. */
+ if (empty(matches)) {
+ matches = fmatches;
+ firstm = ffirstm;
+ shortest = fshortest;
+ ab = fab;
+ ae = fae;
+ shortl = fshortl;
+ }
+
+ /* Make an array from the list of matches. */
+ makearray(matches);
+ PERMALLOC {
+ amatches = arrdup(amatches);
+ if (firstm)
+ firstm = ztrdup(firstm);
+ /* And quote the prefixes/suffixes. */
+ if (hasspecial(s)) {
+ zfree(lpre, lpl);
+ zfree(lsuf, lsl);
+ lpre = zalloc(lpl + 1);
+ memcpy(lpre, s, lpl);
+ lpre[lpl] = '\0';
+ lsuf = ztrdup(s + offs);
+ quotepresuf(&lpre);
+ quotepresuf(&lsuf);
+ untokenize(lpre);
+ untokenize(lsuf);
+ }
+ quotepresuf(&fpre);
+ quotepresuf(&fsuf);
+ quotepresuf(&ppre);
+ quotepresuf(&psuf);
+ } LASTALLOC;
+
+ if (!errflag && cc->ylist) {
+ /* generate the user-defined display list: if anything fails, *
+ * we silently allow the normal completion list to be used. */
+ char **yaptr, *uv = NULL;
+ List list;
+
+ if (cc->ylist[0] == '$' || cc->ylist[0] == '(') {
+ /* from variable */
+ uv = cc->ylist + (cc->ylist[0] == '$');
+ } else if ((list = getshfunc(cc->ylist)) != &dummy_list) {
+ /* from function: pass completions as arg list */
+ LinkList args = newlinklist();
+ int addlen = strlen(rpre) + strlen(rsuf) + 1;
+
+ addlinknode(args, cc->ylist);
+ for (yaptr = amatches; *yaptr; yaptr++) {
+ /* can't use tricat(). rats. */
+ char *ptr = (char *)halloc(addlen + strlen(*yaptr));
+ sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf);
+ addlinknode(args, ptr);
+ }
+
+ /* No harm in allowing read -l and -c here, too */
+ incompctlfunc = 1;
+ doshfunc(list, args, 0, 1);
+ incompctlfunc = 0;
+ uv = "reply";
+ }
+ if (uv && (yaptr = get_user_var(uv))) {
+ PERMALLOC {
+ aylist = arrdup(yaptr);
+ } LASTALLOC;
+ }
+ }
+
+ /* Get the explanation string we will have to print: *
+ * do this here in case a -y function alters the messge */
+ if ((expl = cc->explain)) {
+ if (cc->mask & CC_EXPANDEXPL && !parsestr(expl = dupstring(expl))) {
+ singsub(&expl);
+ untokenize(expl);
+ }
+ expl = ztrdup(expl);
+ }
+
+ remsuffix = (cc->mask & CC_REMOVE);
+ ccsuffix = cc->suffix;
+
+ validlist = 1;
+ if (nmatches && !errflag)
+ return 0;
+
+ if ((isf || cc->xor) && !parampre) {
+ /* We found no matches, but there is a xor'ed completion: *
+ * fine, so go back and continue with that compctl. */
+ errflag = 0;
+ cc = cc->xor;
+ isf = 0;
+ wb = owb;
+ we = owe;
+ cs = ocs;
+ ll = oll;
+ strcpy((char *)line, (char *)ol);
+ offs = oloffs;
+ s = dupstring(os);
+ free(amatches);
+ zsfree(rpre);
+ zsfree(rsuf);
+ zsfree(lpre);
+ zsfree(lsuf);
+ zsfree(ppre);
+ zsfree(psuf);
+ zsfree(fpre);
+ zsfree(fsuf);
+ zsfree(prpre);
+ zsfree(parampre);
+ zsfree(qparampre);
+ zsfree(firstm);
+ if (expl)
+ zsfree(expl);
+ expl = NULL;
+ if (aylist)
+ freearray(aylist);
+ aylist = NULL;
+ goto xorrec;
+ }
+
+ /* No matches and xor'ed completion: restore the command line if *
+ * it was alredy quoted, which is the case when s is untokenized. */
+ if (untokenized)
+ strcpy((char *)line, (char *)ol);
+ return 1;
+}
+
+/* Invalidate the completion list. */
+
+/**/
+void
+invalidatelist(void)
+{
+ if(showinglist == -2)
+ listmatches();
+ if(validlist) {
+ freearray(amatches);
+ if (aylist)
+ freearray(aylist);
+ aylist = NULL;
+ if (expl)
+ zsfree(expl);
+ expl = 0;
+ zsfree(rpre);
+ zsfree(rsuf);
+ zsfree(lpre);
+ zsfree(lsuf);
+ zsfree(ppre);
+ zsfree(psuf);
+ zsfree(fpre);
+ zsfree(fsuf);
+ zsfree(prpre);
+ zsfree(parampre);
+ zsfree(qparampre);
+ zsfree(firstm);
+ if (ccmain != &cc_dummy)
+ freecompctl(ccmain);
+ }
+ lastambig = menucmp = showinglist = validlist = 0;
+ menucur = NULL;
+}
+
+/* Get the words from a variable or a compctl -k list. */
+
+/**/
+static char **
+get_user_var(char *nam)
+{
+ if (!nam)
+ return NULL;
+ else if (*nam == '(') {
+ /* It's a (...) list, not a parameter name. */
+ char *ptr, *s, **uarr, **aptr;
+ int count = 0, notempty = 0, brk = 0;
+ LinkList arrlist = newlinklist();
+
+ ptr = dupstring(nam);
+ s = ptr + 1;
+ while (*++ptr) {
+ if (*ptr == '\\' && ptr[1])
+ chuck(ptr), notempty = 1;
+ else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
+ if (*ptr == ')')
+ brk++;
+ if (notempty) {
+ *ptr = '\0';
+ count++;
+ if (*s == '\n')
+ s++;
+ addlinknode(arrlist, s);
+ }
+ s = ptr + 1;
+ notempty = 0;
+ } else {
+ notempty = 1;
+ if(*ptr == Meta)
+ ptr++;
+ }
+ if (brk)
+ break;
+ }
+ if (!brk || !count)
+ return NULL;
+ *ptr = '\0';
+ aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1));
+
+ while ((*aptr++ = (char *)ugetnode(arrlist)));
+ uarr[count] = NULL;
+ return uarr;
+ } else {
+ /* Otherwise it should be a parameter name. */
+ char **arr = NULL, *val;
+ if (!(arr = getaparam(nam)) && (val = getsparam(nam))) {
+ arr = (char **)ncalloc(2*sizeof(char *));
+ arr[0] = val;
+ arr[1] = NULL;
+ }
+ return arr;
+ }
+
+}
+
+/* This is strcmp with ignoring backslashes. */
+
+/**/
+static int
+strbpcmp(const void *a, const void *b)
+{
+ char *aa = *((char **)a), *bb = *((char **)b);
+
+ while (*aa && *bb) {
+ if (*aa == '\\')
+ aa++;
+ if (*bb == '\\')
+ bb++;
+ if (*aa != *bb)
+ return (int)(*aa - *bb);
+ if (*aa)
+ aa++;
+ if (*bb)
+ bb++;
+ }
+ return (int)(*aa - *bb);
+}
+
+/* Make an array from a linked list */
+
+/**/
+static void
+makearray(LinkList l)
+{
+ char **ap, **bp, **cp;
+ LinkNode nod;
+
+ /* Build an array for the matches. */
+ ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) *
+ sizeof(char *));
+
+ /* And copy them into it. */
+ for (nod = firstnode(l); nod; incnode(nod))
+ *ap++ = (char *)getdata(nod);
+ *ap = NULL;
+
+ /* Now sort the array. */
+ qsort((void *) amatches, nmatches, sizeof(char *),
+ (int (*) _((const void *, const void *)))strbpcmp);
+
+ /* And delete the ones that occur more than once. */
+ for (ap = cp = amatches; *ap; ap++) {
+ *cp++ = *ap;
+ for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++);
+ ap = bp;
+ }
+ *cp = NULL;
+ nmatches = arrlen(amatches);
+}
+
+/* Handle the case were we found more than one match. */
+
+/**/
+static void
+do_ambiguous(void)
+{
+ int p = (usemenu || ispattern), atend = (cs == we);
+ int inv = 0;
+
+ menucmp = 0;
+
+ /* If we have to insert the first match, call do_single(). This is *
+ * how REC_EXACT takes effect. We effectively turn the ambiguous *
+ * completion into an unambiguous one. */
+ if (shortest && shortl == 0 && isset(RECEXACT) &&
+ (usemenu == 0 || unset(AUTOMENU))) {
+ do_single(shortest);
+ invalidatelist();
+ return;
+ }
+ /* Setting lastambig here means that the completion is ambiguous and *
+ * AUTO_MENU might want to start a menu completion next time round, *
+ * but this might be overridden below if we can complete an *
+ * unambiguous prefix. */
+ lastambig = 1;
+ if(p) {
+ /* p is set if we are in a position to start using menu completion *
+ * due to one of the menu completion options, or due to the *
+ * menu-complete-word command, or due to using GLOB_COMPLETE which *
+ * does menu-style completion regardless of the setting of the *
+ * normal menu completion options. */
+ do_ambig_menu();
+ } else {
+ /* Sort-of general case: we have an ambiguous completion, and aren't *
+ * starting menu completion or doing anything really weird. We need *
+ * to insert any unambiguous prefix and suffix, if possible. */
+ if(ab)
+ inststrlen(firstm, 1, ab);
+ if(ae && !atend)
+ inststrlen(firstm + strlen(firstm) - ae, 0, ae);
+ if(ab || (ae && !atend))
+ inv = 1;
+ /* If the LIST_AMBIGUOUS option (meaning roughly `show a list only *
+ * if the completion is completely ambiguous') is set, and some *
+ * prefix was inserted, return now, bypassing the list-displaying *
+ * code. On the way, invalidate the list and note that we don't *
+ * want to enter an AUTO_MENU imediately. */
+ if(isset(LISTAMBIGUOUS) && inv) {
+ invalidatelist();
+ lastambig = 0;
+ return;
+ }
+ }
+ /* At this point, we might want a completion listing. Show the listing *
+ * if it is needed. */
+ if (isset(LISTBEEP))
+ feep();
+ if (isset(AUTOLIST) && !amenu && !showinglist)
+ showinglist = -2;
+ if(inv)
+ invalidatelist();
+}
+
+/* This is a stat that ignores backslashes in the filename. The `ls' *
+ * parameter says if we have to do lstat() or stat(). I think this *
+ * should instead be done by use of a general function to expand a *
+ * filename (stripping backslashes), combined with the actual *
+ * (l)stat(). */
+
+/**/
+static int
+ztat(char *nam, struct stat *buf, int ls)
+{
+ char b[PATH_MAX], *p;
+
+ for (p = b; p < b + sizeof(b) - 1 && *nam; nam++)
+ if (*nam == '\\' && nam[1])
+ *p++ = *++nam;
+ else
+ *p++ = *nam;
+ *p = '\0';
+
+ return ls ? lstat(b, buf) : stat(b, buf);
+}
+
+/* Insert a single match in the command line. */
+
+/**/
+static void
+do_single(char *str)
+{
+ int l;
+ int havesuff = 0;
+
+ fixsuffix();
+
+ if (!menucur) {
+ /* We are currently not in a menu-completion, *
+ * so set the position variables. */
+ if (ispattern) {
+ cs = we;
+ menupos = wb;
+ } else
+ menupos = cs;
+ menuwe = (cs == we) || isset(ALWAYSTOEND);
+ menuend = we;
+ }
+ /* If we are already in a menu-completion or if we have done a *
+ * glob completion, we have to delete some of the stuff on the *
+ * command line. */
+ if (menucur) {
+ if (menuinsc) {
+ cs = menuend + lsl;
+ foredel(menuinsc);
+ }
+ l = menulen;
+ } else if (ispattern)
+ l = we - wb;
+ else
+ l = 0;
+
+ menuinsc = 0;
+ cs = menupos;
+ foredel(l);
+
+ /* And than we insert the new string. */
+ inststrlen(str, 1, menulen = strlen(str));
+ menuend = cs;
+
+ cs += lsl;
+
+ if (ccsuffix) {
+ /* There is a compctl -S suffix. Add it. */
+ if (!(haswhat & HAS_SUFFIX) && *ccsuffix) {
+ havesuff = 1;
+ inststr(ccsuffix);
+ menuinsc = ztrlen(ccsuffix);
+ if (remsuffix && menuwe)
+ makesuffix(menuinsc);
+ }
+ havesuff = 1;
+ } else {
+ /* There is no user-specified suffix, *
+ * so generate one automagically. */
+ if(parampre && parambr) {
+ /*{{*/
+ /* Completing a parameter in braces. Add a removable `}' suffix. */
+ inststrlen("}", 1, 1);
+ menuinsc++;
+ }
+ if(!(haswhat & HAS_MISC) ||
+ (parampre && isset(AUTOPARAMSLASH))) {
+ /* If we have only filenames or we completed a parameter name *
+ * and AUTO_PARAM_SLASH is set, lets see if it is a directory. *
+ * If it is, we append a slash. */
+ char *p;
+ struct stat buf;
+
+ /* Build the path name. */
+ if (ispattern || ic || parampre) {
+ int ne = noerrs;
+
+ noerrs = 1;
+
+ if (parampre) {
+ int pl = strlen(parampre);
+ p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) +
+ strlen(lsuf) + 1);
+ sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf);
+ if (pl && p[pl-1] == Inbrace)
+ strcpy(p+pl-1, p+pl);
+ }
+ else if (ic) {
+ p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) +
+ strlen(fsuf) + strlen(psuf) + 2);
+ sprintf(p, "%c%s%s%s%s%s", ic,
+ ppre, fpre, str, fsuf, psuf);
+ }
+ else
+ p = dupstring(str);
+ parsestr(p);
+ if (ic)
+ *p = ic;
+ singsub(&p);
+
+ noerrs = ne;
+ } else {
+ p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) +
+ strlen(str) + strlen(fsuf) + strlen(psuf) + 3);
+ sprintf(p, "%s%s%s%s%s",
+ (prpre && *prpre) ? prpre : "./", fpre, str,
+ fsuf, psuf);
+ }
+ /* And do the stat. */
+ if (!ztat(p, &buf, 0) && S_ISDIR(buf.st_mode)) {
+ /* It is a directory, so add the slash. */
+ havesuff = 1;
+ inststrlen("/", 1, 1);
+ menuinsc++;
+ if(menuwe && isset(AUTOREMOVESLASH)) {
+ makesuffix(1);
+ suffixlen['/'] = 1;
+ }
+ }
+ }
+ }
+ /* If completing in a brace expansion... */
+ if(complinbrace) {
+ if(havesuff) {
+ /*{{*/
+ /* If a suffix was added, and is removable, let *
+ * `,' and `}' remove it. */
+ if(isset(AUTOPARAMKEYS))
+ suffixlen[','] = suffixlen['}'] = suffixlen[256];
+ } else {
+ /*{{*/
+ /* Otherwise, add a `,' suffix, and let `}' remove it. */
+ havesuff = 1;
+ inststrlen(",", 1, 1);
+ menuinsc++;
+ if(menuwe && isset(AUTOPARAMKEYS))
+ suffixlen[','] = suffixlen['}'] = 1;
+ }
+ } else if(!menucmp && !havesuff) {
+ /* If we didn't add a suffix, add a space, unless we are *
+ * doing menu completion. */
+ inststrlen(" ", 1, 1);
+ menuinsc++;
+ if(menuwe)
+ makesuffix(1);
+ }
+ if(menuwe && parampre && isset(AUTOPARAMKEYS))
+ makeparamsuffix(parambr, menuinsc);
+
+ if (!menuwe)
+ cs = menuend;
+}
+
+/* This handles the beginning of menu-completion. */
+
+/**/
+static void
+do_ambig_menu(void)
+{
+ menucmp = 1;
+ menucur = NULL;
+ do_single(amatches[0]);
+ menucur = amatches;
+}
+
+/* Return the length of the common prefix of s and t. */
+
+/**/
+int
+pfxlen(char *s, char *t)
+{
+ int i = 0;
+
+ while (*s && *s == *t)
+ s++, t++, i++;
+ return i;
+}
+
+/* Return the length of the common suffix of s and t. */
+
+/**/
+static int
+sfxlen(char *s, char *t)
+{
+ if (*s && *t) {
+ int i = 0;
+ char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1;
+
+ while (s2 >= s && t2 >= t && *s2 == *t2)
+ s2--, t2--, i++;
+
+ return i;
+ } else
+ return 0;
+}
+
+/* This is used to print the explanation string. *
+ * It returns the number of lines printed. */
+
+/**/
+static int
+printfmt(char *fmt, int n, int dopr)
+{
+ char *p = fmt, nc[DIGBUFSIZE];
+ int l = 0, cc = 0;
+
+ for (; *p; p++) {
+ /* Handle the `%' stuff (%% == %, %n == <number of matches>). */
+ if (*p == '%') {
+ if (*++p) {
+ switch (*p) {
+ case '%':
+ if (dopr)
+ putc('%', shout);
+ cc++;
+ break;
+ case 'n':
+ sprintf(nc, "%d", n);
+ if (dopr)
+ fprintf(shout, nc);
+ cc += strlen(nc);
+ break;
+ }
+ } else
+ break;
+ } else {
+ cc++;
+ if (*p == '\n') {
+ l += 1 + (cc / columns);
+ cc = 0;
+ }
+ if (dopr)
+ putc(*p, shout);
+ }
+ }
+
+ return l + (cc / columns);
+}
+
+/* List the matches. Note that the list entries are metafied. */
+
+/**/
+void
+listmatches(void)
+{
+ int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
+ int off = 0, boff = 0, nboff = 0;
+ int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC));
+ char **arr, **ap, sav;
+ int nfpl, nfsl, nlpl, nlsl;
+ int listmax = getiparam("LISTMAX"), litnl = 0;
+ size_t (*strlenfn) _((char const *));
+
+#ifdef DEBUG
+ /* Sanity check */
+ if(!validlist) {
+ showmsg("BUG: listmatches called with bogus list");
+ return;
+ }
+#endif
+
+ /* Calculate lengths of prefixes/suffixes to be added */
+ nfpl = fpre ? niceztrlen(fpre) : 0;
+ nfsl = fsuf ? niceztrlen(fsuf) : 0;
+ nlpl = lpre ? niceztrlen(lpre) : 0;
+ nlsl = lsuf ? niceztrlen(lsuf) : 0;
+
+ /* Calculate the lengths of the prefixes/suffixes we have to ignore
+ during printing. */
+ if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) {
+ if (ppre && *ppre)
+ off = strlen(ppre);
+ if (psuf && *psuf) {
+ boff = strlen(psuf);
+ nboff = niceztrlen(psuf);
+ }
+ }
+
+ /* Set the cursor below the prompt. */
+ trashzle();
+ showinglist = 0;
+
+ clearflag = (isset(USEZLE) && !termflags &&
+ (isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
+ (unset(ALWAYSLASTPROMPT) && zmult != 1);
+
+ /* just to keep gcc happy */
+ fw = colsz = up = 0;
+ if (aylist) {
+ arr = aylist;
+ /* If no literal newlines, the remaining code should use strlen() */
+ strlenfn = (size_t (*) _((char const *)))strlen;
+
+ /* The hard bit here is that we are handling newlines literally. *
+ * In fact, we are in principle handling all characters literally, *
+ * but it's quite enough work with just newlines. *
+ * If there are such, we give up trying to print the list as *
+ * columns and print as rows, counting the extra newlines. */
+ ct = 0;
+ for (ap = arr; *ap; ap++) {
+ ct++;
+ if (strchr(*ap, '\n'))
+ litnl++;
+ }
+ if (litnl) {
+ colsz = ct;
+ up = colsz + nlnct - clearflag;
+ /* Count real newlines, as well as overflowing lines. */
+ for (ap = arr; *ap; ap++) {
+ char *nlptr, *sptr = *ap;
+ while (sptr && *sptr) {
+ up += (nlptr = strchr(sptr, '\n'))
+ ? 1 + (nlptr-sptr)/columns
+ : strlen(sptr)/columns;
+ sptr = nlptr ? nlptr+1 : NULL;
+ }
+ }
+ }
+ } else {
+ arr = amatches;
+ ct = nmatches;
+ strlenfn = niceztrlen;
+ }
+
+
+ if (!litnl) {
+ /* Calculate the column width, the number of columns and the
+ number of lines. */
+ for (ap = arr; *ap; ap++)
+ if ((cl = strlenfn(*ap + off) - nboff +
+ ((ispattern || aylist) ? 0 :
+ (!(haswhat & HAS_MISC) ?
+ nfpl + nfsl : nlpl + nlsl))) > longest)
+ longest = cl;
+ if (of)
+ longest++;
+
+ fw = longest + 2;
+ fct = (columns + 1) / fw;
+ if (fct == 0) {
+ fct = 1;
+ colsz = ct;
+ up = colsz + nlnct - clearflag;
+ for (ap = arr; *ap; ap++)
+ up += (strlenfn(*ap + off) - nboff + of +
+ ((ispattern || aylist) ? 0 :
+ (!(haswhat & HAS_MISC) ?
+ nfpl + nfsl : nlpl + nlsl))) / columns;
+ } else {
+ colsz = (ct + fct - 1) / fct;
+ up = colsz + nlnct - clearflag + (ct == 0);
+ }
+ }
+
+ /* Print the explanation string, if any. */
+ if (expl) {
+ xup = printfmt(expl, ct, 1) + 1;
+ putc('\n', shout);
+ up += xup;
+ }
+
+ /* Maybe we have to ask if the user wants to see the list. */
+ if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
+ int qup;
+ setterm();
+ qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1);
+ fflush(shout);
+ if (getzlequery() != 'y') {
+ if (clearflag) {
+ putc('\r', shout);
+ tcmultout(TCUP, TCMULTUP, qup);
+ if (tccan(TCCLEAREOD))
+ tcout(TCCLEAREOD);
+ tcmultout(TCUP, TCMULTUP, nlnct + xup);
+ } else
+ putc('\n', shout);
+ return;
+ }
+ if (clearflag) {
+ putc('\r', shout);
+ tcmultout(TCUP, TCMULTUP, qup);
+ if (tccan(TCCLEAREOD))
+ tcout(TCCLEAREOD);
+ } else
+ putc('\n', shout);
+ settyinfo(&shttyinfo);
+ }
+
+ /* Now print the matches. */
+ for (t1 = 0; t1 != colsz; t1++) {
+ ap = arr + t1;
+ if (of) {
+ /* We have to print the file types. */
+ while (*ap) {
+ int t2;
+ char *pb;
+ struct stat buf;
+
+ /* Build the path name for the stat. */
+ if (ispattern) {
+ int cut = strlen(*ap) - boff;
+
+ sav = ap[0][cut];
+ ap[0][cut] = '\0';
+ nicezputs(*ap + off, shout);
+ t2 = niceztrlen(*ap + off);
+ ap[0][cut] = sav;
+ pb = *ap;
+ } else {
+ nicezputs(fpre, shout);
+ nicezputs(*ap, shout);
+ nicezputs(fsuf, shout);
+ t2 = nfpl + niceztrlen(*ap) + nfsl;
+ pb = (char *) halloc((prpre ? strlen(prpre) : 0) + 3 +
+ strlen(fpre) + strlen(*ap) + strlen(fsuf));
+ sprintf(pb, "%s%s%s%s",
+ (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf);
+ }
+ if (ztat(pb, &buf, 1))
+ putc(' ', shout);
+ else
+ /* Print the file type character. */
+ putc(file_type(buf.st_mode), shout);
+ for (t0 = colsz; t0 && *ap; t0--, ap++);
+ if (*ap)
+ /* And add spaces to make the columns aligned. */
+ for (++t2; t2 < fw; t2++)
+ putc(' ', shout);
+ }
+ } else
+ while (*ap) {
+ int t2;
+
+ if (aylist) {
+ zputs(*ap, shout);
+ t2 = strlen(*ap);
+ } else if (ispattern) {
+ int cut = strlen(*ap) - boff;
+
+ sav = ap[0][cut];
+ ap[0][cut] = '\0';
+ nicezputs(*ap + off, shout);
+ t2 = niceztrlen(*ap + off);
+ ap[0][cut] = sav;
+ } else if (!(haswhat & HAS_MISC)) {
+ nicezputs(fpre, shout);
+ nicezputs(*ap, shout);
+ nicezputs(fsuf, shout);
+ t2 = nfpl + niceztrlen(*ap) + nfsl;
+ } else {
+ nicezputs(lpre, shout);
+ nicezputs(*ap, shout);
+ nicezputs(lsuf, shout);
+ t2 = nlpl + niceztrlen(*ap) + nlsl;
+ }
+ for (t0 = colsz; t0 && *ap; t0--, ap++);
+ if (*ap)
+ for (; t2 < fw; t2++)
+ putc(' ', shout);
+ }
+ if (t1 != colsz - 1 || !clearflag)
+ putc('\n', shout);
+ }
+ if (clearflag)
+ /* Move the cursor up to the prompt, if always_last_prompt *
+ * is set and all that... */
+ if (up < lines) {
+ tcmultout(TCUP, TCMULTUP, up);
+ showinglist = -1;
+ } else
+ clearflag = 0, putc('\n', shout);
+}
+
+/* This is used to print expansions. */
+
+/**/
+void
+listlist(LinkList l)
+{
+ int hw = haswhat, ip = ispattern;
+ char *lp = lpre, *ls = lsuf;
+ int nm = nmatches, vl = validlist;
+ char **am = amatches, **ay = aylist;
+ char *ex = expl;
+
+ haswhat = HAS_MISC;
+ ispattern = 0;
+ validlist = 1;
+ lpre = lsuf = "";
+ aylist = NULL;
+ expl = NULL;
+
+ makearray(l);
+ listmatches();
+ showinglist = 0;
+
+ expl = ex;
+ amatches = am;
+ aylist = ay;
+ nmatches = nm;
+ validlist = vl;
+ lpre = lp;
+ lsuf = ls;
+ ispattern = ip;
+ haswhat = hw;
+}
+
+/* Expand the history references. */
+
+/**/
+int
+doexpandhist(void)
+{
+ unsigned char *ol;
+ int oll, ocs, ne = noerrs, err;
+
+ DPUTS(useheap, "BUG: useheap in doexpandhist()");
+ HEAPALLOC {
+ pushheap();
+ metafy_line();
+ oll = ll;
+ ocs = cs;
+ ol = (unsigned char *)dupstring((char *)line);
+ expanding = 1;
+ excs = cs;
+ ll = cs = 0;
+ lexsave();
+ /* We push ol as it will remain unchanged */
+ inpush((char *) ol, 0, NULL);
+ strinbeg();
+ noaliases = 1;
+ noerrs = 1;
+ exlast = inbufct;
+ do {
+ ctxtlex();
+ } while (tok != ENDINPUT && tok != LEXERR);
+ stophist = 2;
+ while (!lexstop)
+ hgetc();
+ /* We have to save errflags because it's reset in lexrestore. Since *
+ * noerrs was set to 1 errflag is true if there was a habort() which *
+ * means that the expanded string is unusable. */
+ err = errflag;
+ noerrs = ne;
+ noaliases = 0;
+ strinend();
+ inpop();
+ zleparse = 0;
+ lexrestore();
+ expanding = 0;
+
+ if (!err) {
+ cs = excs;
+ if (strcmp((char *)line, (char *)ol)) {
+ unmetafy_line();
+ /* For vi mode -- reset the beginning-of-insertion pointer *
+ * to the beginning of the line. This seems a little silly, *
+ * if we are, for example, expanding "exec !!". */
+ if (viinsbegin > findbol())
+ viinsbegin = findbol();
+ popheap();
+ LASTALLOC_RETURN 1;
+ }
+ }
+
+ strcpy((char *)line, (char *)ol);
+ ll = oll;
+ cs = ocs;
+ unmetafy_line();
+
+ popheap();
+ } LASTALLOC;
+ return 0;
+}
+
+/**/
+void
+magicspace(void)
+{
+ c = ' ';
+ selfinsert();
+ doexpandhist();
+}
+
+/**/
+void
+expandhistory(void)
+{
+ if (!doexpandhist())
+ feep();
+}
+
+static int cmdwb, cmdwe;
+
+/**/
+static char *
+getcurcmd(void)
+{
+ int curlincmd;
+ char *s = NULL;
+
+ DPUTS(useheap, "BUG: useheap in getcurcmd()");
+ HEAPALLOC {
+ zleparse = 2;
+ lexsave();
+ metafy_line();
+ inpush(dupstrspace((char *) line), 0, NULL);
+ unmetafy_line();
+ strinbeg();
+ pushheap();
+ do {
+ curlincmd = incmdpos;
+ ctxtlex();
+ if (tok == ENDINPUT || tok == LEXERR)
+ break;
+ if (tok == STRING && curlincmd) {
+ zsfree(s);
+ s = ztrdup(tokstr);
+ cmdwb = ll - wordbeg;
+ cmdwe = ll + 1 - inbufct;
+ }
+ }
+ while (tok != ENDINPUT && tok != LEXERR && zleparse);
+ popheap();
+ strinend();
+ inpop();
+ errflag = zleparse = 0;
+ lexrestore();
+ } LASTALLOC;
+ return s;
+}
+
+/**/
+void
+processcmd(void)
+{
+ char *s;
+ int m = zmult;
+
+ s = getcurcmd();
+ if (!s) {
+ feep();
+ return;
+ }
+ zmult = 1;
+ pushline();
+ zmult = m;
+ inststr(bindk->nam);
+ inststr(" ");
+ untokenize(s);
+ HEAPALLOC {
+ inststr(quotename(s, NULL, NULL, NULL));
+ } LASTALLOC;
+ zsfree(s);
+ done = 1;
+}
+
+/**/
+void
+expandcmdpath(void)
+{
+ int oldcs = cs, na = noaliases;
+ char *s, *str;
+
+ noaliases = 1;
+ s = getcurcmd();
+ noaliases = na;
+ if (!s || cmdwb < 0 || cmdwe < cmdwb) {
+ feep();
+ return;
+ }
+ str = findcmd(s);
+ zsfree(s);
+ if (!str) {
+ feep();
+ return;
+ }
+ cs = cmdwb;
+ foredel(cmdwe - cmdwb);
+ spaceinline(strlen(str));
+ strncpy((char *)line + cs, str, strlen(str));
+ cs = oldcs;
+ if (cs >= cmdwe - 1)
+ cs += cmdwe - cmdwb + strlen(str);
+ if (cs > ll)
+ cs = ll;
+ zsfree(str);
+}
+
+/* Extra function added by AR Iano-Fletcher. */
+/* This is a expand/complete in the vein of wash. */
+
+/**/
+void
+expandorcompleteprefix(void)
+{
+ comppref = 1;
+ expandorcomplete();
+ comppref = 0;
+}
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
new file mode 100644
index 000000000..8fe3e7f0b
--- /dev/null
+++ b/Src/Zle/zle_utils.c
@@ -0,0 +1,650 @@
+/*
+ * zle_utils.c - miscellaneous line editor utilities
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_utils.pro"
+
+/* Primary cut buffer */
+
+/**/
+struct cutbuffer cutbuf;
+
+/* Emacs-style kill buffer ring */
+
+/**/
+struct cutbuffer kring[KRINGCT];
+/**/
+int kringnum;
+
+/* Vi named cut buffers. 0-25 are the named buffers "a to "z, and *
+ * 26-34 are the numbered buffer stack "1 to "9. */
+
+/**/
+struct cutbuffer vibuf[35];
+
+/* the line before last mod (for undo purposes) */
+
+/**/
+char *lastline;
+/**/
+int lastlinesz, lastll;
+
+/* size of line buffer */
+
+/**/
+int linesz;
+
+/* make sure that the line buffer has at least sz chars */
+
+/**/
+void
+sizeline(int sz)
+{
+ while (sz > linesz)
+ line = (unsigned char *)realloc(line, (linesz *= 4) + 2);
+}
+
+/* insert space for ct chars at cursor position */
+
+/**/
+void
+spaceinline(int ct)
+{
+ int i;
+
+ sizeline(ct + ll);
+ for (i = ll; --i >= cs;)
+ line[i + ct] = line[i];
+ ll += ct;
+ line[ll] = '\0';
+
+ if (mark > cs)
+ mark += ct;
+}
+
+/**/
+static void
+shiftchars(int to, int cnt)
+{
+ if (mark >= to + cnt)
+ mark -= cnt;
+ else if (mark > to)
+ mark = to;
+
+ while (to + cnt < ll) {
+ line[to] = line[to + cnt];
+ to++;
+ }
+ line[ll = to] = '\0';
+}
+
+/**/
+void
+backkill(int ct, int dir)
+{
+ int i = (cs -= ct);
+
+ cut(i, ct, dir);
+ shiftchars(i, ct);
+}
+
+/**/
+void
+forekill(int ct, int dir)
+{
+ int i = cs;
+
+ cut(i, ct, dir);
+ shiftchars(i, ct);
+}
+
+/**/
+void
+cut(int i, int ct, int dir)
+{
+ if (zmod.flags & MOD_VIBUF) {
+ struct cutbuffer *b = &vibuf[zmod.vibuf];
+
+ if (!(zmod.flags & MOD_VIAPP) || !b->buf) {
+ zfree(b->buf, b->len);
+ b->buf = (char *)zalloc(ct);
+ memcpy(b->buf, (char *) line + i, ct);
+ b->len = ct;
+ b->flags = vilinerange ? CUTBUFFER_LINE : 0;
+ } else {
+ int len = b->len;
+
+ if(vilinerange)
+ b->flags |= CUTBUFFER_LINE;
+ b->buf = realloc(b->buf, ct + len + !!(b->flags & CUTBUFFER_LINE));
+ if (b->flags & CUTBUFFER_LINE)
+ b->buf[len++] = '\n';
+ memcpy(b->buf + len, (char *) line + i, ct);
+ b->len = len + ct;
+ }
+ return;
+ } else {
+ /* Save in "1, shifting "1-"8 along to "2-"9 */
+ int n;
+ zfree(vibuf[34].buf, vibuf[34].len);
+ for(n=34; n>26; n--)
+ vibuf[n] = vibuf[n-1];
+ vibuf[26].buf = (char *)zalloc(ct);
+ memcpy(vibuf[26].buf, (char *) line + i, ct);
+ vibuf[26].len = ct;
+ vibuf[26].flags = vilinerange ? CUTBUFFER_LINE : 0;
+ }
+ if (!cutbuf.buf) {
+ cutbuf.buf = ztrdup("");
+ cutbuf.len = cutbuf.flags = 0;
+ } else if (!(lastcmd & ZLE_KILL)) {
+ kringnum = (kringnum + 1) % KRINGCT;
+ if (kring[kringnum].buf)
+ free(kring[kringnum].buf);
+ kring[kringnum] = cutbuf;
+ cutbuf.buf = ztrdup("");
+ cutbuf.len = cutbuf.flags = 0;
+ }
+ if (dir) {
+ char *s = (char *)zalloc(cutbuf.len + ct);
+
+ memcpy(s, (char *) line + i, ct);
+ memcpy(s + ct, cutbuf.buf, cutbuf.len);
+ free(cutbuf.buf);
+ cutbuf.buf = s;
+ cutbuf.len += ct;
+ } else {
+ cutbuf.buf = realloc(cutbuf.buf, cutbuf.len + ct);
+ memcpy(cutbuf.buf + cutbuf.len, (char *) line + i, ct);
+ cutbuf.len += ct;
+ }
+ if(vilinerange)
+ cutbuf.flags |= CUTBUFFER_LINE;
+ else
+ cutbuf.flags &= ~CUTBUFFER_LINE;
+}
+
+/**/
+void
+backdel(int ct)
+{
+ shiftchars(cs -= ct, ct);
+}
+
+/**/
+void
+foredel(int ct)
+{
+ shiftchars(cs, ct);
+}
+
+/**/
+void
+setline(char const *s)
+{
+ sizeline(strlen(s));
+ strcpy((char *) line, s);
+ unmetafy((char *) line, &ll);
+ if ((cs = ll) && invicmdmode())
+ cs--;
+}
+
+/**/
+int
+findbol(void)
+{
+ int x = cs;
+
+ while (x > 0 && line[x - 1] != '\n')
+ x--;
+ return x;
+}
+
+/**/
+int
+findeol(void)
+{
+ int x = cs;
+
+ while (x != ll && line[x] != '\n')
+ x++;
+ return x;
+}
+
+/**/
+void
+findline(int *a, int *b)
+{
+ *a = findbol();
+ *b = findeol();
+}
+
+/* Search for needle in haystack. Haystack is a metafied string while *
+ * needle is unmetafied and len-long. Start the search at position *
+ * pos. Search forward if dir > 0 otherwise search backward. */
+
+/**/
+char *
+hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens)
+{
+ char *s = haystack + pos;
+
+ if (dir > 0) {
+ while (*s) {
+ if (metadiffer(s, needle, len) < sens)
+ return s;
+ s += 1 + (*s == Meta);
+ }
+ } else {
+ for (;;) {
+ if (metadiffer(s, needle, len) < sens)
+ return s;
+ if (s == haystack)
+ break;
+ s -= 1 + (s != haystack+1 && s[-2] == Meta);
+ }
+ }
+ return NULL;
+}
+
+/* Query the user, and return a single character response. The *
+ * question is assumed to have been printed already, and the *
+ * cursor is left immediately after the response echoed. *
+ * (Might cause a problem if this takes it onto the next line.) *
+ * <Tab> is interpreted as 'y'; any other control character is *
+ * interpreted as 'n'. If there are any characters in the *
+ * buffer, this is taken as a negative response, and no *
+ * characters are read. Case is folded. */
+
+/**/
+int
+getzlequery(void)
+{
+ int c;
+#ifdef FIONREAD
+ int val;
+
+ /* check for typeahead, which is treated as a negative response */
+ ioctl(SHTTY, FIONREAD, (char *)&val);
+ if (val) {
+ putc('n', shout);
+ return 'n';
+ }
+#endif
+
+ /* get a character from the tty and interpret it */
+ c = getkey(0);
+ if (c == '\t')
+ c = 'y';
+ else if (icntrl(c) || c == EOF)
+ c = 'n';
+ else
+ c = tulower(c);
+
+ /* echo response and return */
+ putc(c, shout);
+ return c;
+}
+
+/* Format a string, keybinding style. */
+
+/**/
+char *
+bindztrdup(char *str)
+{
+ int c, len = 1;
+ char *buf, *ptr, *ret;
+
+ for(ptr = str; *ptr; ptr++) {
+ c = *ptr == Meta ? STOUC(*++ptr) ^ 32 : STOUC(*ptr);
+ if(c & 0x80) {
+ len += 3;
+ c &= 0x7f;
+ }
+ if(c < 32 || c == 0x7f) {
+ len++;
+ c ^= 64;
+ }
+ len += c == '\\' || c == '^';
+ len++;
+ }
+ ptr = buf = zalloc(len);
+ for(; *str; str++) {
+ c = *str == Meta ? STOUC(*++str) ^ 32 : STOUC(*str);
+ if(c & 0x80) {
+ *ptr++ = '\\';
+ *ptr++ = 'M';
+ *ptr++ = '-';
+ c &= 0x7f;
+ }
+ if(c < 32 || c == 0x7f) {
+ *ptr++ = '^';
+ c ^= 64;
+ }
+ if(c == '\\' || c == '^')
+ *ptr++ = '\\';
+ *ptr++ = c;
+ }
+ *ptr = 0;
+ ret = dquotedztrdup(buf);
+ zsfree(buf);
+ return ret;
+}
+
+/* Display a metafied string, keybinding-style. */
+
+/**/
+int
+printbind(char *str, FILE *stream)
+{
+ char *b = bindztrdup(str);
+ int ret = zputs(b, stream);
+
+ zsfree(b);
+ return ret;
+}
+
+/* Display a message where the completion list normally goes. *
+ * The message must be metafied. */
+
+/**/
+void
+showmsg(char const *msg)
+{
+ char const *p;
+ int up = 0, cc = 0, c;
+
+ trashzle();
+ clearflag = isset(USEZLE) && !termflags && isset(ALWAYSLASTPROMPT);
+
+ for(p = msg; (c = *p); p++) {
+ if(c == Meta)
+ c = *++p ^ 32;
+ if(c == '\n') {
+ putc('\n', shout);
+ up += 1 + cc / columns;
+ cc = 0;
+ } else {
+ char const *n = nicechar(c);
+ fputs(n, shout);
+ cc += strlen(n);
+ }
+ }
+ up += cc / columns;
+
+ if (clearflag) {
+ putc('\r', shout);
+ tcmultout(TCUP, TCMULTUP, up + nlnct);
+ } else
+ putc('\n', shout);
+ showinglist = 0;
+}
+
+/* handle the error flag */
+
+/**/
+void
+feep(void)
+{
+ feepflag = 1;
+}
+
+/**/
+void
+handlefeep(void)
+{
+ if(feepflag)
+ beep();
+ feepflag = 0;
+}
+
+/***************/
+/* undo system */
+/***************/
+
+/* head of the undo list, and the current position */
+
+static struct change *changes, *curchange;
+
+/* list of pending changes, not yet in the undo system */
+
+static struct change *nextchanges, *endnextchanges;
+
+/**/
+void
+initundo(void)
+{
+ nextchanges = NULL;
+ changes = curchange = zalloc(sizeof(*curchange));
+ curchange->prev = curchange->next = NULL;
+ curchange->del = curchange->ins = NULL;
+ lastline = zalloc(lastlinesz = linesz);
+ memcpy(lastline, line, lastll = ll);
+}
+
+/**/
+void
+freeundo(void)
+{
+ freechanges(changes);
+ freechanges(nextchanges);
+ zfree(lastline, lastlinesz);
+}
+
+/**/
+static void
+freechanges(struct change *p)
+{
+ struct change *n;
+
+ for(; p; p = n) {
+ n = p->next;
+ zsfree(p->del);
+ zsfree(p->ins);
+ zfree(p, sizeof(*p));
+ }
+}
+
+/* register pending changes in the undo system */
+
+/**/
+void
+handleundo(void)
+{
+ mkundoent();
+ if(!nextchanges)
+ return;
+ setlastline();
+ if(curchange->next) {
+ freechanges(curchange->next);
+ curchange->next = NULL;
+ zsfree(curchange->del);
+ zsfree(curchange->ins);
+ curchange->del = curchange->ins = NULL;
+ }
+ nextchanges->prev = curchange->prev;
+ if(curchange->prev)
+ curchange->prev->next = nextchanges;
+ else
+ changes = nextchanges;
+ curchange->prev = endnextchanges;
+ endnextchanges->next = curchange;
+ nextchanges = endnextchanges = NULL;
+}
+
+/* add an entry to the undo system, if anything has changed */
+
+/**/
+void
+mkundoent(void)
+{
+ int pre, suf;
+ int sh = ll < lastll ? ll : lastll;
+ struct change *ch;
+
+ if(lastll == ll && !memcmp(lastline, line, ll))
+ return;
+ for(pre = 0; pre < sh && line[pre] == lastline[pre]; )
+ pre++;
+ for(suf = 0; suf < sh - pre &&
+ line[ll - 1 - suf] == lastline[lastll - 1 - suf]; )
+ suf++;
+ ch = zalloc(sizeof(*ch));
+ ch->next = NULL;
+ ch->hist = histline;
+ ch->off = pre;
+ if(suf + pre == lastll)
+ ch->del = NULL;
+ else
+ ch->del = metafy(lastline + pre, lastll - pre - suf, META_DUP);
+ if(suf + pre == ll)
+ ch->ins = NULL;
+ else
+ ch->ins = metafy((char *)line + pre, ll - pre - suf, META_DUP);
+ if(nextchanges) {
+ ch->flags = CH_PREV;
+ ch->prev = endnextchanges;
+ endnextchanges->flags |= CH_NEXT;
+ endnextchanges->next = ch;
+ } else {
+ nextchanges = ch;
+ ch->flags = 0;
+ ch->prev = NULL;
+ }
+ endnextchanges = ch;
+}
+
+/* set lastline to match line */
+
+/**/
+void
+setlastline(void)
+{
+ if(lastlinesz != linesz)
+ lastline = realloc(lastline, lastlinesz = linesz);
+ memcpy(lastline, line, lastll = ll);
+}
+
+/* move backwards through the change list */
+
+/**/
+void
+undo(void)
+{
+ handleundo();
+ do {
+ if(!curchange->prev) {
+ feep();
+ return;
+ }
+ unapplychange(curchange = curchange->prev);
+ } while(curchange->flags & CH_PREV);
+ setlastline();
+}
+
+/**/
+static void
+unapplychange(struct change *ch)
+{
+ if(ch->hist != histline) {
+ remember_edits();
+ setline(zle_get_event(histline = ch->hist));
+ }
+ cs = ch->off;
+ if(ch->ins)
+ foredel(ztrlen(ch->ins));
+ if(ch->del) {
+ char *c = ch->del;
+
+ spaceinline(ztrlen(c));
+ for(; *c; c++)
+ if(*c == Meta)
+ line[cs++] = STOUC(*++c) ^ 32;
+ else
+ line[cs++] = STOUC(*c);
+ }
+}
+
+/* move forwards through the change list */
+
+/**/
+void
+redo(void)
+{
+ handleundo();
+ do {
+ if(!curchange->next) {
+ feep();
+ return;
+ }
+ applychange(curchange);
+ curchange = curchange->next;
+ } while(curchange->prev->flags & CH_NEXT);
+ setlastline();
+}
+
+/**/
+static void
+applychange(struct change *ch)
+{
+ if(ch->hist != histline) {
+ remember_edits();
+ setline(zle_get_event(histline = ch->hist));
+ }
+ cs = ch->off;
+ if(ch->del)
+ foredel(ztrlen(ch->del));
+ if(ch->ins) {
+ char *c = ch->ins;
+
+ spaceinline(ztrlen(c));
+ for(; *c; c++)
+ if(*c == Meta)
+ line[cs++] = STOUC(*++c) ^ 32;
+ else
+ line[cs++] = STOUC(*c);
+ }
+}
+
+/* vi undo: toggle between the end of the undo list and the preceding point */
+
+/**/
+void
+viundochange(void)
+{
+ handleundo();
+ if(curchange->next) {
+ do {
+ applychange(curchange);
+ curchange = curchange->next;
+ } while(curchange->next);
+ setlastline();
+ } else
+ undo();
+}
diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c
new file mode 100644
index 000000000..a599d8091
--- /dev/null
+++ b/Src/Zle/zle_vi.c
@@ -0,0 +1,925 @@
+/*
+ * zle_vi.c - vi-specific functions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_vi.pro"
+
+/* != 0 if we're getting a vi range */
+
+/**/
+int virangeflag;
+
+/* kludge to get cw and dw to work right */
+
+/**/
+int wordflag;
+
+/* != 0 if we're killing lines into a buffer, vi-style */
+
+/**/
+int vilinerange;
+
+/* last vi change buffer, for vi change repetition */
+
+/**/
+int vichgbufsz, vichgbufptr, vichgflag;
+
+/**/
+char *vichgbuf;
+
+/* point where vi insert mode was last entered */
+
+/**/
+int viinsbegin;
+
+static struct modifier lastmod;
+static int inrepeat, vichgrepeat;
+
+/**/
+static void
+startvichange(int im)
+{
+ if (im != -1) {
+ insmode = im;
+ vichgflag = 1;
+ }
+ if (inrepeat) {
+ zmod = lastmod;
+ inrepeat = vichgflag = 0;
+ vichgrepeat = 1;
+ } else {
+ lastmod = zmod;
+ if (vichgbuf)
+ free(vichgbuf);
+ vichgbuf = (char *)zalloc(vichgbufsz = 16);
+ vichgbuf[0] = c;
+ vichgbufptr = 1;
+ vichgrepeat = 0;
+ }
+}
+
+/**/
+static void
+startvitext(int im)
+{
+ startvichange(im);
+ selectkeymap("main", 1);
+ undoing = 0;
+ viinsbegin = cs;
+}
+
+/**/
+int
+vigetkey(void)
+{
+ Keymap mn = openkeymap("main");
+ char m[3], *str;
+ Thingy cmd;
+
+ if((c = getkey(0)) == EOF) {
+ feep();
+ return -1;
+ }
+
+ m[0] = c;
+ metafy(m, 1, META_NOALLOC);
+ if(mn)
+ cmd = keybind(mn, m, &str);
+ else
+ cmd = t_undefinedkey;
+
+ if (!cmd || cmd == Th(z_sendbreak)) {
+ feep();
+ return -1;
+ } else if (cmd == Th(z_quotedinsert)) {
+ if ((c = getkey(0)) == EOF) {
+ feep();
+ return -1;
+ }
+ } else if(cmd == Th(z_viquotedinsert)) {
+ char sav = line[cs];
+
+ line[cs] = '^';
+ refresh();
+ c = getkey(0);
+ line[cs] = sav;
+ if(c == EOF) {
+ feep();
+ return -1;
+ }
+ } else if (cmd == Th(z_vicmdmode))
+ return -1;
+ return c;
+}
+
+/**/
+static int
+getvirange(int wf)
+{
+ int pos = cs;
+ int mult1 = zmult, hist1 = histline;
+ Thingy k2;
+
+ virangeflag = 1;
+ wordflag = wf;
+ /* Now we need to execute the movement command, to see where it *
+ * actually goes. virangeflag here indicates to the movement *
+ * function that it should place the cursor at the end of the *
+ * range, rather than where the cursor would actually go if it *
+ * were executed normally. This makes a difference to some *
+ * commands, but not all. For example, if searching forward *
+ * for a character, under normal circumstances the cursor lands *
+ * on the character. For a range, the range must include the *
+ * character, so the cursor gets placed after the character if *
+ * virangeflag is set. vi-match-bracket needs to change the *
+ * value of virangeflag under some circumstances, meaning that *
+ * we need to change the *starting* position. */
+ zmod.flags &= ~MOD_TMULT;
+ do {
+ vilinerange = 0;
+ prefixflag = 0;
+ if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) ||
+ k2 == Th(z_sendbreak)) {
+ wordflag = 0;
+ virangeflag = 0;
+ feep();
+ return -1;
+ }
+ if(k2 == bindk)
+ /* The command key is repeated: a number of lines is used. */
+ dovilinerange();
+ else
+ execzlefunc(k2);
+ if(vichgrepeat)
+ zmult = mult1;
+ else
+ zmult = mult1 * zmod.tmult;
+ } while(prefixflag);
+ wordflag = 0;
+ virangeflag = 0;
+
+ /* It is an error to use a non-movement command to delimit the *
+ * range. We here reject the case where the command modified *
+ * the line, or selected a different history line. */
+ if(histline != hist1 || ll != lastll || memcmp(line, lastline, ll)) {
+ histline = hist1;
+ memcpy(line, lastline, ll = lastll);
+ cs = pos;
+ feep();
+ return -1;
+ }
+
+ /* Can't handle an empty file. Also, if the movement command *
+ * failed, or didn't move, it is an error. */
+ if (!ll || (cs == pos && virangeflag != 2)) {
+ feep();
+ return -1;
+ }
+
+ /* vi-match-bracket changes the value of virangeflag when *
+ * moving to the opening bracket, meaning that we need to *
+ * change the *starting* position. */
+ if(virangeflag == -1)
+ pos++;
+
+ /* Get the range the right way round. cs is placed at the *
+ * start of the range, and pos (the return value of this *
+ * function) is the end. */
+ if (cs > pos) {
+ int tmp = cs;
+ cs = pos;
+ pos = tmp;
+ }
+
+ /* Was it a line-oriented move? If so, the command will have set *
+ * the vilinerange flag. In this case, entire lines are taken, *
+ * rather than just the sequence of characters delimited by pos *
+ * and cs. The terminating newline is left out of the range, *
+ * which the real command must deal with appropriately. At this *
+ * point we just need to make the range encompass entire lines. */
+ if(vilinerange) {
+ int newcs = findbol();
+ cs = pos;
+ pos = findeol();
+ cs = newcs;
+ }
+ return pos;
+}
+
+/**/
+static void
+dovilinerange(void)
+{
+ int pos = cs, n = zmult;
+
+ /* A number of lines is taken as the range. The current line *
+ * is included. If the repeat count is positive the lines go *
+ * downward, otherwise upward. The repeat count gives the *
+ * number of lines. */
+ vilinerange = 1;
+ if (!n) {
+ feep();
+ return;
+ }
+ if (n > 0) {
+ while(n-- && cs <= ll)
+ cs = findeol() + 1;
+ if (n != -1) {
+ cs = pos;
+ feep();
+ return;
+ }
+ cs--;
+ } else {
+ while(n++ && cs >= 0)
+ cs = findbol() - 1;
+ if (n != 1) {
+ cs = pos;
+ feep();
+ return;
+ }
+ cs++;
+ }
+ virangeflag = 2;
+}
+
+/**/
+void
+viaddnext(void)
+{
+ if (cs != findeol())
+ cs++;
+ startvitext(1);
+}
+
+/**/
+void
+viaddeol(void)
+{
+ cs = findeol();
+ startvitext(1);
+}
+
+/**/
+void
+viinsert(void)
+{
+ startvitext(1);
+}
+
+/**/
+void
+viinsertbol(void)
+{
+ vifirstnonblank();
+ startvitext(1);
+}
+
+/**/
+void
+videlete(void)
+{
+ int c2;
+
+ startvichange(1);
+ if ((c2 = getvirange(0)) != -1) {
+ forekill(c2 - cs, 0);
+ if (vilinerange && ll) {
+ if (cs == ll)
+ cs--;
+ foredel(1);
+ vifirstnonblank();
+ }
+ }
+ vichgflag = 0;
+}
+
+/**/
+void
+videletechar(void)
+{
+ int n = zmult;
+
+ startvichange(-1);
+ /* handle negative argument */
+ if (n < 0) {
+ zmult = -n;
+ vibackwarddeletechar();
+ zmult = n;
+ return;
+ }
+ /* it is an error to be on the end of line */
+ if (cs == ll || line[cs] == '\n') {
+ feep();
+ return;
+ }
+ /* Put argument into the acceptable range -- it is not an error to *
+ * specify a greater count than the number of available characters. */
+ if (n > findeol() - cs)
+ n = findeol() - cs;
+ /* do the deletion */
+ forekill(n, 0);
+}
+
+/**/
+void
+vichange(void)
+{
+ int c2;
+
+ startvichange(1);
+ if ((c2 = getvirange(1)) != -1) {
+ forekill(c2 - cs, 0);
+ selectkeymap("main", 1);
+ viinsbegin = cs;
+ undoing = 0;
+ }
+}
+
+/**/
+void
+visubstitute(void)
+{
+ int n = zmult;
+
+ startvichange(1);
+ if (n < 0) {
+ feep();
+ return;
+ }
+ /* it is an error to be on the end of line */
+ if (cs == ll || line[cs] == '\n') {
+ feep();
+ return;
+ }
+ /* Put argument into the acceptable range -- it is not an error to *
+ * specify a greater count than the number of available characters. */
+ if (n > findeol() - cs)
+ n = findeol() - cs;
+ /* do the substitution */
+ forekill(n, 0);
+ startvitext(1);
+}
+
+/**/
+void
+vichangeeol(void)
+{
+ forekill(findeol() - cs, 0);
+ startvitext(1);
+}
+
+/**/
+void
+vichangewholeline(void)
+{
+ vifirstnonblank();
+ vichangeeol();
+}
+
+/**/
+void
+viyank(void)
+{
+ int oldcs = cs, c2;
+
+ startvichange(1);
+ if ((c2 = getvirange(0)) != -1)
+ cut(cs, c2 - cs, 0);
+ vichgflag = 0;
+ cs = oldcs;
+}
+
+/**/
+void
+viyankeol(void)
+{
+ int x = findeol();
+
+ startvichange(-1);
+ if (x == cs) {
+ feep();
+ return;
+ }
+ cut(cs, x - cs, 0);
+}
+
+/**/
+void
+viyankwholeline(void)
+{
+ int bol = findbol(), oldcs = cs;
+ int n = zmult;
+
+ startvichange(-1);
+ if (n < 1)
+ return;
+ while(n--) {
+ if (cs > ll) {
+ feep();
+ cs = oldcs;
+ return;
+ }
+ cs = findeol() + 1;
+ }
+ vilinerange = 1;
+ cut(bol, cs - bol - 1, 0);
+ cs = oldcs;
+}
+
+/**/
+void
+vireplace(void)
+{
+ startvitext(0);
+}
+
+/* vi-replace-chars has some oddities relating to vi-repeat-change. In *
+ * the real vi, if one does 3r at the end of a line, it feeps without *
+ * reading the argument, and won't repeat the action. A successful rx *
+ * followed by 3. at the end of a line (or 3rx followed by . at the end *
+ * of a line) will obviously feep after the ., even though it has the *
+ * argument available. Here repeating is tied very closely to argument *
+ * reading, so some trickery is needed to emulate this. When repeating *
+ * a change, we always read the argument normally, even if the count *
+ * was bad. When recording a change for repeating, and a bad count is *
+ * given, we squash the repeat buffer to avoid repeating the partial *
+ * command; we've lost the previous change, but that can't be avoided *
+ * without a rewrite of the repeat code. */
+
+/**/
+void
+vireplacechars(void)
+{
+ int ch, n = zmult;
+
+ startvichange(1);
+ /* check argument range */
+ if (n < 1 || n + cs > findeol()) {
+ if(vichgrepeat) {
+ int ofeep = feepflag;
+ vigetkey();
+ feepflag = ofeep;
+ }
+ if(vichgflag) {
+ free(vichgbuf);
+ vichgbuf = NULL;
+ vichgflag = 0;
+ }
+ feep();
+ return;
+ }
+ /* get key */
+ if((ch = vigetkey()) == -1) {
+ vichgflag = 0;
+ feep();
+ return;
+ }
+ /* do change */
+ if (ch == '\r' || ch == '\n') {
+ /* <return> handled specially */
+ cs += n - 1;
+ backkill(n - 1, 0);
+ line[cs++] = '\n';
+ } else {
+ while (n--)
+ line[cs++] = ch;
+ cs--;
+ }
+ vichgflag = 0;
+}
+
+/**/
+void
+vicmdmode(void)
+{
+ if (invicmdmode() || selectkeymap("vicmd", 0))
+ feep();
+ undoing = 1;
+ vichgflag = 0;
+ if (cs != findbol())
+ cs--;
+}
+
+/**/
+void
+viopenlinebelow(void)
+{
+ cs = findeol();
+ spaceinline(1);
+ line[cs++] = '\n';
+ startvitext(1);
+}
+
+/**/
+void
+viopenlineabove(void)
+{
+ cs = findbol();
+ spaceinline(1);
+ line[cs] = '\n';
+ startvitext(1);
+}
+
+/**/
+void
+vioperswapcase(void)
+{
+ int oldcs, c2;
+
+ /* get the range */
+ startvichange(1);
+ if ((c2 = getvirange(0)) != -1) {
+ oldcs = cs;
+ /* swap the case of all letters within range */
+ while (cs < c2) {
+ if (islower(line[cs]))
+ line[cs] = tuupper(line[cs]);
+ else if (isupper(line[cs]))
+ line[cs] = tulower(line[cs]);
+ cs++;
+ }
+ /* go back to the first line of the range */
+ cs = oldcs;
+ vifirstnonblank();
+ }
+ vichgflag = 0;
+}
+
+/**/
+void
+virepeatchange(void)
+{
+ /* make sure we have a change to repeat */
+ if (!vichgbuf || vichgflag) {
+ feep();
+ return;
+ }
+ /* restore or update the saved count and buffer */
+ if (zmod.flags & MOD_MULT) {
+ lastmod.mult = zmod.mult;
+ lastmod.flags |= MOD_MULT;
+ }
+ if (zmod.flags & MOD_VIBUF) {
+ lastmod.vibuf = zmod.vibuf;
+ lastmod.flags = (lastmod.flags & ~MOD_VIAPP) |
+ MOD_VIBUF | (zmod.flags & MOD_VIAPP);
+ }
+ /* repeat the command */
+ inrepeat = 1;
+ ungetkeys(vichgbuf, vichgbufptr);
+}
+
+/**/
+void
+viindent(void)
+{
+ int oldcs = cs, c2;
+
+ /* get the range */
+ startvichange(1);
+ if ((c2 = getvirange(0)) == -1) {
+ vichgflag = 0;
+ return;
+ }
+ vichgflag = 0;
+ /* must be a line range */
+ if (!vilinerange) {
+ feep();
+ cs = oldcs;
+ return;
+ }
+ oldcs = cs;
+ /* add a tab to the beginning of each line within range */
+ while (cs < c2) {
+ spaceinline(1);
+ line[cs] = '\t';
+ cs = findeol() + 1;
+ }
+ /* go back to the first line of the range */
+ cs = oldcs;
+ vifirstnonblank();
+}
+
+/**/
+void
+viunindent(void)
+{
+ int oldcs = cs, c2;
+
+ /* get the range */
+ startvichange(1);
+ if ((c2 = getvirange(0)) == -1) {
+ vichgflag = 0;
+ return;
+ }
+ vichgflag = 0;
+ /* must be a line range */
+ if (!vilinerange) {
+ feep();
+ cs = oldcs;
+ return;
+ }
+ oldcs = cs;
+ /* remove a tab from the beginning of each line within range */
+ while (cs < c2) {
+ if (line[cs] == '\t')
+ foredel(1);
+ cs = findeol() + 1;
+ }
+ /* go back to the first line of the range */
+ cs = oldcs;
+ vifirstnonblank();
+}
+
+/**/
+void
+vibackwarddeletechar(void)
+{
+ int n = zmult;
+
+ if (invicmdmode())
+ startvichange(-1);
+ /* handle negative argument */
+ if (n < 0) {
+ zmult = -n;
+ videletechar();
+ zmult = n;
+ return;
+ }
+ /* It is an error to be at the beginning of the line, or (in *
+ * insert mode) to delete past the beginning of insertion. */
+ if ((!invicmdmode() && cs - n < viinsbegin) || cs == findbol()) {
+ feep();
+ return;
+ }
+ /* Put argument into the acceptable range -- it is not an error to *
+ * specify a greater count than the number of available characters. */
+ if (n > cs - findbol())
+ n = cs - findbol();
+ /* do the deletion */
+ backkill(n, 1);
+}
+
+/**/
+void
+vikillline(void)
+{
+ if (viinsbegin > cs) {
+ feep();
+ return;
+ }
+ backdel(cs - viinsbegin);
+}
+
+/**/
+void
+viputbefore(void)
+{
+ Cutbuffer buf = &cutbuf;
+ int n = zmult;
+
+ startvichange(-1);
+ if (n < 0)
+ return;
+ if (zmod.flags & MOD_VIBUF)
+ buf = &vibuf[zmod.vibuf];
+ if (!buf->buf) {
+ feep();
+ return;
+ }
+ if(buf->flags & CUTBUFFER_LINE) {
+ cs = findbol();
+ spaceinline(buf->len + 1);
+ memcpy((char *)line + cs, buf->buf, buf->len);
+ line[cs + buf->len] = '\n';
+ vifirstnonblank();
+ } else {
+ while (n--) {
+ spaceinline(buf->len);
+ memcpy((char *)line + cs, buf->buf, buf->len);
+ cs += buf->len;
+ }
+ if (cs)
+ cs--;
+ }
+}
+
+/**/
+void
+viputafter(void)
+{
+ Cutbuffer buf = &cutbuf;
+ int n = zmult;
+
+ startvichange(-1);
+ if (n < 0)
+ return;
+ if (zmod.flags & MOD_VIBUF)
+ buf = &vibuf[zmod.vibuf];
+ if (!buf->buf) {
+ feep();
+ return;
+ }
+ if(buf->flags & CUTBUFFER_LINE) {
+ cs = findeol();
+ spaceinline(buf->len + 1);
+ line[cs++] = '\n';
+ memcpy((char *)line + cs, buf->buf, buf->len);
+ vifirstnonblank();
+ } else {
+ if (cs != findeol())
+ cs++;
+ while (n--) {
+ spaceinline(buf->len);
+ memcpy((char *)line + cs, buf->buf, buf->len);
+ cs += buf->len;
+ }
+ if (cs)
+ cs--;
+ }
+
+}
+
+/**/
+void
+vijoin(void)
+{
+ int x;
+
+ startvichange(-1);
+ if ((x = findeol()) == ll) {
+ feep();
+ return;
+ }
+ cs = x + 1;
+ for (x = 1; cs != ll && iblank(line[cs]); cs++, x++);
+ backdel(x);
+ if (cs && iblank(line[cs-1]))
+ cs--;
+ else {
+ spaceinline(1);
+ line[cs] = ' ';
+ }
+}
+
+/**/
+void
+viswapcase(void)
+{
+ int eol, n = zmult;
+
+ startvichange(-1);
+ if (n < 1)
+ return;
+ eol = findeol();
+ while (cs < eol && n--) {
+ if (islower(line[cs]))
+ line[cs] = tuupper(line[cs]);
+ else if (isupper(line[cs]))
+ line[cs] = tulower(line[cs]);
+ cs++;
+ }
+ if (cs && cs == eol)
+ cs--;
+}
+
+/**/
+void
+vicapslockpanic(void)
+{
+ beep();
+ statusline = "press a lowercase key to continue";
+ statusll = strlen(statusline);
+ refresh();
+ while (!islower(getkey(0)));
+ statusline = NULL;
+}
+
+/**/
+void
+visetbuffer(void)
+{
+ int ch;
+
+ if ((zmod.flags & MOD_VIBUF) ||
+ (((ch = getkey(0)) < '1' || ch > '9') &&
+ (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))) {
+ feep();
+ return;
+ }
+ if (ch >= 'A' && ch <= 'Z') /* needed in cut() */
+ zmod.flags |= MOD_VIAPP;
+ else
+ zmod.flags &= ~MOD_VIAPP;
+ zmod.vibuf = tulower(ch) + (idigit(ch) ? -'1' + 26 : -'a');
+ zmod.flags |= MOD_VIBUF;
+ prefixflag = 1;
+}
+
+/**/
+void
+vikilleol(void)
+{
+ int n = findeol() - cs;
+
+ startvichange(-1);
+ if (!n) {
+ /* error -- line already empty */
+ feep();
+ return;
+ }
+ /* delete to end of line */
+ forekill(findeol() - cs, 0);
+}
+
+/**/
+void
+vipoundinsert(void)
+{
+ int oldcs = cs;
+
+ startvichange(-1);
+ vifirstnonblank();
+ if(line[cs] != '#') {
+ spaceinline(1);
+ line[cs] = '#';
+ if(cs <= viinsbegin)
+ viinsbegin++;
+ cs = oldcs + (cs <= oldcs);
+ } else {
+ foredel(1);
+ if (cs < viinsbegin)
+ viinsbegin--;
+ cs = oldcs - (cs < oldcs);
+ }
+}
+
+/**/
+void
+viquotedinsert(void)
+{
+#ifndef HAS_TIO
+ struct sgttyb sob;
+#endif
+
+ spaceinline(1);
+ line[cs] = '^';
+ refresh();
+#ifndef HAS_TIO
+ sob = shttyinfo.sgttyb;
+ sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
+ ioctl(SHTTY, TIOCSETN, &sob);
+#endif
+ c = getkey(0);
+#ifndef HAS_TIO
+ setterm();
+#endif
+ foredel(1);
+ if(c < 0)
+ feep();
+ else
+ selfinsert();
+}
+
+/* the 0 key in vi: continue a repeat count in the manner of *
+ * digit-argument if possible, otherwise do vi-beginning-of-line. */
+
+/**/
+void
+vidigitorbeginningofline(void)
+{
+ if(zmod.flags & MOD_TMULT)
+ digitargument();
+ else {
+ removesuffix();
+ invalidatelist();
+ vibeginningofline();
+ }
+}
diff --git a/Src/Zle/zle_widget.sed b/Src/Zle/zle_widget.sed
new file mode 100644
index 000000000..635322b42
--- /dev/null
+++ b/Src/Zle/zle_widget.sed
@@ -0,0 +1,7 @@
+/^ *W(/{
+ s/[^,]*, *t_/ wi_/
+ s/ *,.*/,/
+ P
+ s/ wi_\(.*\),/#define w_\1 (\&widgets[wi_\1])/
+ P
+}
diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c
new file mode 100644
index 000000000..923216ef8
--- /dev/null
+++ b/Src/Zle/zle_word.c
@@ -0,0 +1,477 @@
+/*
+ * zle_word.c - word-related editor functions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "zle_word.pro"
+
+/**/
+void
+forwardword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs != ll && iword(line[cs]))
+ cs++;
+ if (wordflag && !n)
+ return;
+ while (cs != ll && !iword(line[cs]))
+ cs++;
+ }
+}
+
+/**/
+void
+viforwardword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ if (iident(line[cs]))
+ while (cs != ll && iident(line[cs]))
+ cs++;
+ else
+ while (cs != ll && !iident(line[cs]) && !iblank(line[cs]))
+ cs++;
+ if (wordflag && !n)
+ return;
+ while (cs != ll && iblank(line[cs]))
+ cs++;
+ }
+}
+
+/**/
+void
+viforwardblankword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ vibackwardblankword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs != ll && !iblank(line[cs]))
+ cs++;
+ if (wordflag && !n)
+ return;
+ while (cs != ll && iblank(line[cs]))
+ cs++;
+ }
+}
+
+/**/
+void
+emacsforwardword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ emacsbackwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs != ll && !iword(line[cs]))
+ cs++;
+ if (wordflag && !n)
+ return;
+ while (cs != ll && iword(line[cs]))
+ cs++;
+ }
+}
+
+/**/
+void
+viforwardblankwordend(void)
+{
+ int n = zmult;
+
+ if (n < 0)
+ return;
+ while (n--) {
+ while (cs != ll && iblank(line[cs + 1]))
+ cs++;
+ while (cs != ll && !iblank(line[cs + 1]))
+ cs++;
+ }
+ if (cs != ll && virangeflag)
+ cs++;
+}
+
+/**/
+void
+viforwardwordend(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ if (iblank(line[cs + 1]))
+ while (cs != ll && iblank(line[cs + 1]))
+ cs++;
+ if (iident(line[cs + 1]))
+ while (cs != ll && iident(line[cs + 1]))
+ cs++;
+ else
+ while (cs != ll && !iident(line[cs + 1]) && !iblank(line[cs + 1]))
+ cs++;
+ }
+ if (cs != ll && virangeflag)
+ cs++;
+}
+
+/**/
+void
+backwardword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ forwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs && !iword(line[cs - 1]))
+ cs--;
+ while (cs && iword(line[cs - 1]))
+ cs--;
+ }
+}
+
+/**/
+void
+vibackwardword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs && iblank(line[cs - 1]))
+ cs--;
+ if (iident(line[cs - 1]))
+ while (cs && iident(line[cs - 1]))
+ cs--;
+ else
+ while (cs && !iident(line[cs - 1]) && !iblank(line[cs - 1]))
+ cs--;
+ }
+}
+
+/**/
+void
+vibackwardblankword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ viforwardblankword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs && iblank(line[cs - 1]))
+ cs--;
+ while (cs && !iblank(line[cs - 1]))
+ cs--;
+ }
+}
+
+/**/
+void
+emacsbackwardword(void)
+{
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ emacsforwardword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (cs && !iword(line[cs - 1]))
+ cs--;
+ while (cs && iword(line[cs - 1]))
+ cs--;
+ }
+}
+
+/**/
+void
+backwarddeleteword(void)
+{
+ int x = cs, n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ deleteword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (x && !iword(line[x - 1]))
+ x--;
+ while (x && iword(line[x - 1]))
+ x--;
+ }
+ backdel(cs - x);
+}
+
+/**/
+void
+vibackwardkillword(void)
+{
+ int x = cs, lim = (viinsbegin > findbol()) ? viinsbegin : findbol();
+ int n = zmult;
+
+ if (n < 0) {
+ feep();
+ return;
+ }
+/* this taken from "vibackwardword" */
+ while (n--) {
+ while ((x > lim) && iblank(line[x - 1]))
+ x--;
+ if (iident(line[x - 1]))
+ while ((x > lim) && iident(line[x - 1]))
+ x--;
+ else
+ while ((x > lim) && !iident(line[x - 1]) && !iblank(line[x - 1]))
+ x--;
+ }
+ backkill(cs - x, 1);
+}
+
+/**/
+void
+backwardkillword(void)
+{
+ int x = cs;
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ killword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (x && !iword(line[x - 1]))
+ x--;
+ while (x && iword(line[x - 1]))
+ x--;
+ }
+ backkill(cs - x, 1);
+}
+
+/**/
+void
+upcaseword(void)
+{
+ int n = zmult;
+ int neg = n < 0, ocs = cs;
+
+ if (neg)
+ n = -n;
+ while (n--) {
+ while (cs != ll && !iword(line[cs]))
+ cs++;
+ while (cs != ll && iword(line[cs])) {
+ line[cs] = tuupper(line[cs]);
+ cs++;
+ }
+ }
+ if (neg)
+ cs = ocs;
+}
+
+/**/
+void
+downcaseword(void)
+{
+ int n = zmult;
+ int neg = n < 0, ocs = cs;
+
+ if (neg)
+ n = -n;
+ while (n--) {
+ while (cs != ll && !iword(line[cs]))
+ cs++;
+ while (cs != ll && iword(line[cs])) {
+ line[cs] = tulower(line[cs]);
+ cs++;
+ }
+ }
+ if (neg)
+ cs = ocs;
+}
+
+/**/
+void
+capitalizeword(void)
+{
+ int first, n = zmult;
+ int neg = n < 0, ocs = cs;
+
+ if (neg)
+ n = -n;
+ while (n--) {
+ first = 1;
+ while (cs != ll && !iword(line[cs]))
+ cs++;
+ while (cs != ll && iword(line[cs]) && !isalpha(line[cs]))
+ cs++;
+ while (cs != ll && iword(line[cs])) {
+ line[cs] = (first) ? tuupper(line[cs]) : tulower(line[cs]);
+ first = 0;
+ cs++;
+ }
+ }
+ if (neg)
+ cs = ocs;
+}
+
+/**/
+void
+deleteword(void)
+{
+ int x = cs;
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwarddeleteword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (x != ll && !iword(line[x]))
+ x++;
+ while (x != ll && iword(line[x]))
+ x++;
+ }
+ foredel(x - cs);
+}
+
+/**/
+void
+killword(void)
+{
+ int x = cs;
+ int n = zmult;
+
+ if (n < 0) {
+ zmult = -n;
+ backwardkillword();
+ zmult = n;
+ return;
+ }
+ while (n--) {
+ while (x != ll && !iword(line[x]))
+ x++;
+ while (x != ll && iword(line[x]))
+ x++;
+ }
+ forekill(x - cs, 0);
+}
+
+/**/
+void
+transposewords(void)
+{
+ int p1, p2, p3, p4, x = cs;
+ char *temp, *pp;
+ int n = zmult;
+ int neg = n < 0, ocs = cs;
+
+ if (neg)
+ n = -n;
+ while (n--) {
+ while (x != ll && line[x] != '\n' && !iword(line[x]))
+ x++;
+ if (x == ll || line[x] == '\n') {
+ x = cs;
+ while (x && line[x - 1] != '\n' && !iword(line[x]))
+ x--;
+ if (!x || line[x - 1] == '\n') {
+ feep();
+ return;
+ }
+ }
+ for (p4 = x; p4 != ll && iword(line[p4]); p4++);
+ for (p3 = p4; p3 && iword(line[p3 - 1]); p3--);
+ if (!p3) {
+ feep();
+ return;
+ }
+ for (p2 = p3; p2 && !iword(line[p2 - 1]); p2--);
+ if (!p2) {
+ feep();
+ return;
+ }
+ for (p1 = p2; p1 && iword(line[p1 - 1]); p1--);
+ pp = temp = (char *)halloc(p4 - p1 + 1);
+ struncpy(&pp, (char *) line + p3, p4 - p3);
+ struncpy(&pp, (char *) line + p2, p3 - p2);
+ struncpy(&pp, (char *) line + p1, p2 - p1);
+ strncpy((char *)line + p1, temp, p4 - p1);
+ cs = p4;
+ }
+ if (neg)
+ cs = ocs;
+}
diff --git a/Src/ansi2knr.c b/Src/ansi2knr.c
new file mode 100644
index 000000000..e5502a7e6
--- /dev/null
+++ b/Src/ansi2knr.c
@@ -0,0 +1,413 @@
+/* Copyright (C) 1989, 1991, 1993, 1994 Aladdin Enterprises. All rights reserved. */
+
+/* ansi2knr.c */
+/* Convert ANSI function declarations to K&R syntax */
+
+/*
+ansi2knr is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY. No author or distributor accepts responsibility
+to anyone for the consequences of using it or for whether it serves any
+particular purpose or works at all, unless he says so in writing. Refer
+to the GNU General Public License for full details.
+
+Everyone is granted permission to copy, modify and redistribute
+ansi2knr, but only under the conditions described in the GNU
+General Public License. A copy of this license is supposed to have been
+given to you along with ansi2knr so you can know your rights and
+responsibilities. It should be in a file named COPYING. Among other
+things, the copyright notice and this notice must be preserved on all
+copies.
+*/
+
+/*
+ * Usage:
+ ansi2knr input_file [output_file]
+ * If no output_file is supplied, output goes to stdout.
+ * There are no error messages.
+ *
+ * ansi2knr recognizes function definitions by seeing a non-keyword
+ * identifier at the left margin, followed by a left parenthesis,
+ * with a right parenthesis as the last character on the line.
+ * It will recognize a multi-line header provided that the last character
+ * of the last line of the header is a right parenthesis,
+ * and no intervening line ends with a left brace or a semicolon.
+ * These algorithms ignore whitespace and comments, except that
+ * the function name must be the first thing on the line.
+ * The following constructs will confuse it:
+ * - Any other construct that starts at the left margin and
+ * follows the above syntax (such as a macro or function call).
+ * - Macros that tinker with the syntax of the function header.
+ */
+
+/*
+ * Change history:
+ lpd 89-xx-xx original version
+ lpd 94-07-16 added some conditionals to help GNU `configure',
+ suggested by Francois Pinard <pinard@iro.umontreal.ca>;
+ properly erase prototype args in function parameters,
+ contributed by Jim Avera <jima@netcom.com>;
+ correct error in writeblanks (it shouldn't erase EOLs)
+ */
+
+/* Most of the conditionals here are to make ansi2knr work with */
+/* the GNU configure machinery. */
+
+#ifdef HAVE_CONFIG_H
+# ifdef CONFIG_BROKETS
+/*
+ We use <config.h> instead of "config.h" so that a compilation
+ using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
+ (which it would do because it found this file in $srcdir).
+ */
+# include <config.h>
+# else
+# include "config.h"
+# endif
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+
+/*
+ For properly autoconfiguring ansi2knr, use AC_CONFIG_HEADER(config.h).
+ This will define HAVE_CONFIG_H and so, activate the following lines.
+ */
+
+# if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+# else
+# include <strings.h>
+# endif
+
+#else /* not HAVE_CONFIG_H */
+
+/*
+ Without AC_CONFIG_HEADER, merely use <string.h> as in the original
+ Ghostscript distribution. This loses on older BSD systems.
+ */
+
+# include <string.h>
+
+#endif /* not HAVE_CONFIG_H */
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+#else
+/*
+ malloc and free should be declared in stdlib.h,
+ but if you've got a K&R compiler, they probably aren't.
+ */
+char *malloc();
+void free();
+#endif
+
+/* Scanning macros */
+#define isidchar(ch) (isalnum(ch) || (ch) == '_')
+#define isidfirstchar(ch) (isalpha(ch) || (ch) == '_')
+
+/* Forward references */
+char *skipspace();
+void writeblanks();
+int test1();
+int convert1();
+
+/* The main program */
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{ FILE *in, *out;
+#define bufsize 5000 /* arbitrary size */
+ char *buf;
+ char *line;
+ switch ( argc )
+ {
+ default:
+ printf("Usage: ansi2knr input_file [output_file]\n");
+ exit(0);
+ case 2:
+ out = stdout; break;
+ case 3:
+ out = fopen(argv[2], "w");
+ if ( out == NULL )
+ { fprintf(stderr, "Cannot open %s\n", argv[2]);
+ exit(1);
+ }
+ }
+ in = fopen(argv[1], "r");
+ if ( in == NULL )
+ { fprintf(stderr, "Cannot open %s\n", argv[1]);
+ exit(1);
+ }
+ fprintf(out, "#line 1 \"%s\"\n", argv[1]);
+ buf = malloc(bufsize);
+ line = buf;
+ while ( fgets(line, (unsigned)(buf + bufsize - line), in) != NULL )
+ { switch ( test1(buf) )
+ {
+ case 2: /* a function header */
+ convert1(buf, out, 1);
+ break;
+ case 1: /* a function */
+ convert1(buf, out, 0);
+ break;
+ case -1: /* maybe the start of a function */
+ line = buf + strlen(buf);
+ if ( line != buf + (bufsize - 1) ) /* overflow check */
+ continue;
+ /* falls through */
+ default: /* not a function */
+ fputs(buf, out);
+ break;
+ }
+ line = buf;
+ }
+ if ( line != buf ) fputs(buf, out);
+ free(buf);
+ fclose(out);
+ fclose(in);
+ return 0;
+}
+
+/* Skip over space and comments, in either direction. */
+char *
+skipspace(p, dir)
+ register char *p;
+ register int dir; /* 1 for forward, -1 for backward */
+{ for ( ; ; )
+ { while ( isspace(*p) ) p += dir;
+ if ( !(*p == '/' && p[dir] == '*') ) break;
+ p += dir; p += dir;
+ while ( !(*p == '*' && p[dir] == '/') )
+ { if ( *p == 0 ) return p; /* multi-line comment?? */
+ p += dir;
+ }
+ p += dir; p += dir;
+ }
+ return p;
+}
+
+/*
+ * Write blanks over part of a string.
+ * Don't overwrite end-of-line characters.
+ */
+void
+writeblanks(start, end)
+ char *start;
+ char *end;
+{ char *p;
+ for ( p = start; p < end; p++ )
+ if ( *p != '\r' && *p != '\n' ) *p = ' ';
+}
+
+/*
+ * Test whether the string in buf is a function definition.
+ * The string may contain and/or end with a newline.
+ * Return as follows:
+ * 0 - definitely not a function definition;
+ * 1 - definitely a function definition;
+ * 2 - definitely a function prototype (NOT USED);
+ * -1 - may be the beginning of a function definition,
+ * append another line and look again.
+ * The reason we don't attempt to convert function prototypes is that
+ * Ghostscript's declaration-generating macros look too much like
+ * prototypes, and confuse the algorithms.
+ */
+int
+test1(buf)
+ char *buf;
+{ register char *p = buf;
+ char *bend;
+ char *endfn;
+ int contin;
+ if ( !isidfirstchar(*p) )
+ return 0; /* no name at left margin */
+ bend = skipspace(buf + strlen(buf) - 1, -1);
+ switch ( *bend )
+ {
+ case ';': contin = 0 /*2*/; break;
+ case ')': contin = 1; break;
+ case '{': return 0; /* not a function */
+ default: contin = -1;
+ }
+ while ( isidchar(*p) ) p++;
+ endfn = p;
+ p = skipspace(p, 1);
+ if ( *p++ != '(' )
+ return 0; /* not a function */
+ p = skipspace(p, 1);
+ if ( *p == ')' )
+ return 0; /* no parameters */
+ /* Check that the apparent function name isn't a keyword. */
+ /* We only need to check for keywords that could be followed */
+ /* by a left parenthesis (which, unfortunately, is most of them). */
+ { static char *words[] =
+ { "asm", "auto", "case", "char", "const", "double",
+ "extern", "float", "for", "if", "int", "long",
+ "register", "return", "short", "signed", "sizeof",
+ "static", "switch", "typedef", "unsigned",
+ "void", "volatile", "while", 0
+ };
+ char **key = words;
+ char *kp;
+ int len = endfn - buf;
+ while ( (kp = *key) != 0 )
+ { if ( strlen(kp) == len && !strncmp(kp, buf, len) )
+ return 0; /* name is a keyword */
+ key++;
+ }
+ }
+ return contin;
+}
+
+/* Convert a recognized function definition or header to K&R syntax. */
+int
+convert1(buf, out, header)
+ char *buf;
+ FILE *out;
+ int header; /* Boolean */
+{ char *endfn;
+ register char *p;
+ char **breaks;
+ unsigned num_breaks = 2; /* for testing */
+ char **btop;
+ char **bp;
+ char **ap;
+ /* Pre-ANSI implementations don't agree on whether strchr */
+ /* is called strchr or index, so we open-code it here. */
+ for ( endfn = buf; *(endfn++) != '('; ) ;
+top: p = endfn;
+ breaks = (char **)malloc(sizeof(char *) * num_breaks * 2);
+ if ( breaks == 0 )
+ { /* Couldn't allocate break table, give up */
+ fprintf(stderr, "Unable to allocate break table!\n");
+ fputs(buf, out);
+ return -1;
+ }
+ btop = breaks + num_breaks * 2 - 2;
+ bp = breaks;
+ /* Parse the argument list */
+ do
+ { int level = 0;
+ char *lp = NULL;
+ char *rp;
+ char *end = NULL;
+ if ( bp >= btop )
+ { /* Filled up break table. */
+ /* Allocate a bigger one and start over. */
+ free((char *)breaks);
+ num_breaks <<= 1;
+ goto top;
+ }
+ *bp++ = p;
+ /* Find the end of the argument */
+ for ( ; end == NULL; p++ )
+ { switch(*p)
+ {
+ case ',':
+ if ( !level ) end = p;
+ break;
+ case '(':
+ if ( !level ) lp = p;
+ level++;
+ break;
+ case ')':
+ if ( --level < 0 ) end = p;
+ else rp = p;
+ break;
+ case '/':
+ p = skipspace(p, 1) - 1;
+ break;
+ default:
+ ;
+ }
+ }
+ /* Erase any embedded prototype parameters. */
+ if ( lp )
+ writeblanks(lp + 1, rp);
+ p--; /* back up over terminator */
+ /* Find the name being declared. */
+ /* This is complicated because of procedure and */
+ /* array modifiers. */
+ for ( ; ; )
+ { p = skipspace(p - 1, -1);
+ switch ( *p )
+ {
+ case ']': /* skip array dimension(s) */
+ case ')': /* skip procedure args OR name */
+ { int level = 1;
+ while ( level )
+ switch ( *--p )
+ {
+ case ']': case ')': level++; break;
+ case '[': case '(': level--; break;
+ case '/': p = skipspace(p, -1) + 1; break;
+ default: ;
+ }
+ }
+ if ( *p == '(' && *skipspace(p + 1, 1) == '*' )
+ { /* We found the name being declared */
+ while ( !isidfirstchar(*p) )
+ p = skipspace(p, 1) + 1;
+ goto found;
+ }
+ break;
+ default: goto found;
+ }
+ }
+found: if ( *p == '.' && p[-1] == '.' && p[-2] == '.' )
+ { p++;
+ if ( bp == breaks + 1 ) /* sole argument */
+ writeblanks(breaks[0], p);
+ else
+ writeblanks(bp[-1] - 1, p);
+ bp--;
+ }
+ else
+ { while ( isidchar(*p) ) p--;
+ *bp++ = p+1;
+ }
+ p = end;
+ }
+ while ( *p++ == ',' );
+ *bp = p;
+ /* Make a special check for 'void' arglist */
+ if ( bp == breaks+2 )
+ { p = skipspace(breaks[0], 1);
+ if ( !strncmp(p, "void", 4) )
+ { p = skipspace(p+4, 1);
+ if ( p == breaks[2] - 1 )
+ { bp = breaks; /* yup, pretend arglist is empty */
+ writeblanks(breaks[0], p + 1);
+ }
+ }
+ }
+ /* Put out the function name */
+ p = buf;
+ while ( p != endfn ) putc(*p, out), p++;
+ /* Put out the declaration */
+ if ( header )
+ { fputs(");", out);
+ for ( p = breaks[0]; *p; p++ )
+ if ( *p == '\n' )
+ putc('\n', out);
+ }
+ else
+ { for ( ap = breaks+1; ap < bp; ap += 2 )
+ { p = *ap;
+ while ( isidchar(*p) )
+ putc(*p, out), p++;
+ if ( ap < bp - 1 )
+ fputs(", ", out);
+ }
+ fputs(") ", out);
+ /* Put out the argument declarations */
+ for ( ap = breaks+2; ap <= bp; ap += 2 )
+ (*ap)[-1] = ';';
+ fputs(breaks[0], out);
+ }
+ free((char *)breaks);
+ return 0;
+}
diff --git a/Src/builtin.c b/Src/builtin.c
new file mode 100644
index 000000000..31f396d93
--- /dev/null
+++ b/Src/builtin.c
@@ -0,0 +1,3599 @@
+/*
+ * builtin.c - builtin commands
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "builtin.pro"
+
+/* Builtins in the main executable */
+
+static struct builtin builtins[] =
+{
+ BIN_PREFIX("-", BINF_DASH),
+ BIN_PREFIX("builtin", BINF_BUILTIN),
+ BIN_PREFIX("command", BINF_COMMAND),
+ BIN_PREFIX("exec", BINF_EXEC),
+ BIN_PREFIX("noglob", BINF_NOGLOB),
+ BUILTIN("[", 0, bin_test, 0, -1, BIN_BRACKET, NULL, NULL),
+ BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
+ BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
+ BUILTIN("alias", BINF_MAGICEQUALS, bin_alias, 0, -1, 0, "Lgmr", NULL),
+ BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "t", "u"),
+ BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
+ BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
+ BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
+ BUILTIN("cd", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL),
+ BUILTIN("chdir", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL),
+ BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
+ BUILTIN("declare", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtux", NULL),
+ BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "v", NULL),
+ BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmr", NULL),
+ BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
+ BUILTIN("echo", BINF_PRINTOPTS | BINF_ECHOPTS, bin_print, 0, -1, BIN_ECHO, "neE", "-"),
+ BUILTIN("echotc", 0, bin_echotc, 1, -1, 0, NULL, NULL),
+ BUILTIN("emulate", 0, bin_emulate, 1, 1, 0, "R", NULL),
+ BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmr", NULL),
+ BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
+ BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
+ BUILTIN("export", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "LRUZfilrtu", "x"),
+ BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
+ BUILTIN("fc", BINF_FCOPTS, bin_fc, 0, -1, BIN_FC, "nlreIRWAdDfEim", NULL),
+ BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
+ BUILTIN("functions", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "mtu", NULL),
+ BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
+ BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
+ BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "dfmrv", NULL),
+
+#ifdef ZSH_HASH_DEBUG
+ BUILTIN("hashinfo", 0, bin_hashinfo, 0, 0, 0, NULL, NULL),
+#endif
+
+ BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "nrdDfEim", "l"),
+ BUILTIN("integer", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "lrtux", "i"),
+ BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
+ BUILTIN("kill", 0, bin_kill, 0, -1, 0, NULL, NULL),
+ BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
+ BUILTIN("local", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZilrtu", NULL),
+ BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
+ BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
+
+#if defined(ZSH_MEM) & defined(ZSH_MEM_DEBUG)
+ BUILTIN("mem", 0, bin_mem, 0, 0, 0, "v", NULL),
+#endif
+
+ BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL),
+ BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPnrslzNu0123456789pioOcm-", NULL),
+ BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL),
+ BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
+ BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
+ BUILTIN("r", BINF_R, bin_fc, 0, -1, BIN_FC, "nrl", NULL),
+ BUILTIN("read", 0, bin_read, 0, -1, 0, "rzu0123456789pkqecnAlE", NULL),
+ BUILTIN("readonly", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfiltux", "r"),
+ BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "dfv", "r"),
+ BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
+ BUILTIN("set", BINF_PSPECIAL, bin_set, 0, -1, 0, NULL, NULL),
+ BUILTIN("setopt", 0, bin_setopt, 0, -1, BIN_SETOPT, NULL, NULL),
+ BUILTIN("shift", BINF_PSPECIAL, bin_shift, 0, -1, 0, NULL, NULL),
+ BUILTIN("source", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
+ BUILTIN("suspend", 0, bin_suspend, 0, 0, 0, "f", NULL),
+ BUILTIN("test", 0, bin_test, 0, -1, BIN_TEST, NULL, NULL),
+ BUILTIN("ttyctl", 0, bin_ttyctl, 0, 0, 0, "fu", NULL),
+ BUILTIN("times", BINF_PSPECIAL, bin_times, 0, 0, 0, NULL, NULL),
+ BUILTIN("trap", BINF_PSPECIAL, bin_trap, 0, -1, 0, NULL, NULL),
+ BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
+ BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsw", "v"),
+ BUILTIN("typeset", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtuxm", NULL),
+ BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
+ BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "m", "a"),
+ BUILTIN("unfunction", 0, bin_unhash, 1, -1, 0, "m", "f"),
+ BUILTIN("unhash", 0, bin_unhash, 1, -1, 0, "adfm", NULL),
+ BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fm", NULL),
+ BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL),
+ BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL),
+ BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsw", NULL),
+ BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsw", "ca"),
+ BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"),
+
+#ifdef DYNAMIC
+ BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "Laudi", NULL),
+#endif
+};
+
+/****************************************/
+/* Builtin Command Hash Table Functions */
+/****************************************/
+
+/* hash table containing builtin commands */
+
+/**/
+HashTable builtintab;
+
+/**/
+void
+createbuiltintable(void)
+{
+ builtintab = newhashtable(85, "builtintab", NULL);
+
+ builtintab->hash = hasher;
+ builtintab->emptytable = NULL;
+ builtintab->filltable = NULL;
+ builtintab->addnode = addhashnode;
+ builtintab->getnode = gethashnode;
+ builtintab->getnode2 = gethashnode2;
+ builtintab->removenode = removehashnode;
+ builtintab->disablenode = disablehashnode;
+ builtintab->enablenode = enablehashnode;
+ builtintab->freenode = freebuiltinnode;
+ builtintab->printnode = printbuiltinnode;
+
+ addbuiltins("zsh", builtins, sizeof(builtins)/sizeof(*builtins));
+}
+
+/* Print a builtin */
+
+/**/
+static void
+printbuiltinnode(HashNode hn, int printflags)
+{
+ Builtin bn = (Builtin) hn;
+
+ if (printflags & PRINT_WHENCE_WORD) {
+ printf("%s: builtin\n", bn->nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_CSH) {
+ printf("%s: shell built-in command\n", bn->nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ printf("%s is a shell builtin\n", bn->nam);
+ return;
+ }
+
+ /* default is name only */
+ printf("%s\n", bn->nam);
+}
+
+/**/
+static void
+freebuiltinnode(HashNode hn)
+{
+ Builtin bn = (Builtin) hn;
+
+ if(!(bn->flags & BINF_ADDED)) {
+ zsfree(bn->nam);
+ zsfree(bn->optstr);
+ zfree(bn, sizeof(struct builtin));
+ }
+}
+
+static char *auxdata;
+static int auxlen;
+
+/* execute a builtin handler function after parsing the arguments */
+
+#define MAX_OPS 128
+
+/**/
+int
+execbuiltin(LinkList args, Builtin bn)
+{
+ LinkNode n;
+ char ops[MAX_OPS], *arg, *pp, *name, **argv, **oargv, *optstr;
+ char *oxarg, *xarg = NULL;
+ int flags, sense, argc = 0, execop;
+
+ /* initialise some static variables */
+ auxdata = NULL;
+ auxlen = 0;
+
+ /* initialize some local variables */
+ memset(ops, 0, MAX_OPS);
+ name = (char *) ugetnode(args);
+
+ arg = (char *) ugetnode(args);
+
+#ifdef DYNAMIC
+ if (!bn->handlerfunc) {
+ zwarnnam(name, "autoload failed", NULL, 0);
+ deletebuiltin(bn->nam);
+ return 1;
+ }
+#endif
+
+ /* get some information about the command */
+ flags = bn->flags;
+ optstr = bn->optstr;
+
+ /* Sort out the options. */
+ if ((flags & BINF_ECHOPTS) && isset(BSDECHO))
+ ops['E'] = 1;
+ if (optstr)
+ /* while arguments look like options ... */
+ while (arg &&
+ ((sense = (*arg == '-')) ||
+ ((flags & BINF_PLUSOPTS) && *arg == '+')) &&
+ ((flags & BINF_PLUSOPTS) || !atoi(arg))) {
+ /* unrecognised options to echo etc. are not really options */
+ if (flags & BINF_ECHOPTS) {
+ char *p = arg;
+ while (*++p && strchr(optstr, (int) *p));
+ if (*p)
+ break;
+ }
+ /* save the options in xarg, for execution tracing */
+ if (xarg) {
+ oxarg = tricat(xarg, " ", arg);
+ zsfree(xarg);
+ xarg = oxarg;
+ } else
+ xarg = ztrdup(arg);
+ /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */
+ if (arg[1] == '-')
+ arg++;
+ if (!arg[1]) {
+ ops['-'] = 1;
+ if (!sense)
+ ops['+'] = 1;
+ }
+ /* save options in ops, as long as they are in bn->optstr */
+ execop = -1;
+ while (*++arg)
+ if (strchr(optstr, execop = (int)*arg))
+ ops[(int)*arg] = (sense) ? 1 : 2;
+ else
+ break;
+ /* "typeset" may take a numeric argument *
+ * at the tail of the options */
+ if (idigit(*arg) && (flags & BINF_TYPEOPT) &&
+ (arg[-1] == 'L' || arg[-1] == 'R' ||
+ arg[-1] == 'Z' || arg[-1] == 'i'))
+ auxlen = (int)zstrtol(arg, &arg, 10);
+ /* The above loop may have exited on an invalid option. (We *
+ * assume that any option requiring metafication is invalid.) */
+ if (*arg) {
+ if(*arg == Meta)
+ *++arg ^= 32;
+ zerr("bad option: -%c", NULL, *arg);
+ zsfree(xarg);
+ return 1;
+ }
+ arg = (char *) ugetnode(args);
+ /* for the "print" builtin, the options after -R are treated as
+ options to "echo" */
+ if ((flags & BINF_PRINTOPTS) && ops['R']) {
+ optstr = "ne";
+ flags |= BINF_ECHOPTS;
+ }
+ /* the option -- indicates the end of the options */
+ if (ops['-'])
+ break;
+ /* for "fc", -e takes an extra argument */
+ if ((flags & BINF_FCOPTS) && execop == 'e') {
+ auxdata = arg;
+ arg = (char *) ugetnode(args);
+ }
+ /* for "typeset", -L, -R, -Z and -i take a numeric extra argument */
+ if ((flags & BINF_TYPEOPT) && (execop == 'L' || execop == 'R' ||
+ execop == 'Z' || execop == 'i') && arg && idigit(*arg)) {
+ auxlen = atoi(arg);
+ arg = (char *) ugetnode(args);
+ }
+ }
+ if (flags & BINF_R)
+ auxdata = "-";
+ /* handle built-in options, for overloaded handler functions */
+ if ((pp = bn->defopts))
+ while (*pp)
+ ops[(int)*pp++] = 1;
+
+ /* Set up the argument list. */
+ if (arg) {
+ /* count the arguments */
+ argc = 1;
+ n = firstnode(args);
+ while (n)
+ argc++, incnode(n);
+ }
+ /* Get the actual arguments, into argv. Oargv saves the *
+ * beginning of the array for later reference. */
+ oargv = argv = (char **)ncalloc(sizeof(char **) * (argc + 1));
+ if ((*argv++ = arg))
+ while ((*argv++ = (char *)ugetnode(args)));
+ argv = oargv;
+ if (errflag) {
+ zsfree(xarg);
+ errflag = 0;
+ return 1;
+ }
+
+ /* check that the argument count lies within the specified bounds */
+ if (argc < bn->minargs || (argc > bn->maxargs && bn->maxargs != -1)) {
+ zwarnnam(name, (argc < bn->minargs)
+ ? "not enough arguments" : "too many arguments", NULL, 0);
+ zsfree(xarg);
+ return 1;
+ }
+
+ /* display execution trace information, if required */
+ if (isset(XTRACE)) {
+ fprintf(stderr, "%s%s", (prompt4) ? prompt4 : "", name);
+ if (xarg)
+ fprintf(stderr, " %s", xarg);
+ while (*oargv)
+ fprintf(stderr, " %s", *oargv++);
+ fputc('\n', stderr);
+ fflush(stderr);
+ }
+ zsfree(xarg);
+ /* call the handler function, and return its return value */
+ return (*(bn->handlerfunc)) (name, argv, ops, bn->funcid);
+}
+
+/* Enable/disable an element in one of the internal hash tables. *
+ * With no arguments, it lists all the currently enabled/disabled *
+ * elements in that particular hash table. */
+
+/**/
+int
+bin_enable(char *name, char **argv, char *ops, int func)
+{
+ HashTable ht;
+ HashNode hn;
+ ScanFunc scanfunc;
+ Comp com;
+ int flags1 = 0, flags2 = 0;
+ int match = 0, returnval = 0;
+
+ /* Find out which hash table we are working with. */
+ if (ops['f'])
+ ht = shfunctab;
+ else if (ops['r'])
+ ht = reswdtab;
+ else if (ops['a'])
+ ht = aliastab;
+ else
+ ht = builtintab;
+
+ /* Do we want to enable or disable? */
+ if (func == BIN_ENABLE) {
+ flags2 = DISABLED;
+ scanfunc = ht->enablenode;
+ } else {
+ flags1 = DISABLED;
+ scanfunc = ht->disablenode;
+ }
+
+ /* Given no arguments, print the names of the enabled/disabled elements *
+ * in this hash table. If func == BIN_ENABLE, then scanhashtable will *
+ * print nodes NOT containing the DISABLED flag, else scanhashtable will *
+ * print nodes containing the DISABLED flag. */
+ if (!*argv) {
+ scanhashtable(ht, 1, flags1, flags2, ht->printnode, 0);
+ return 0;
+ }
+
+ /* With -m option, treat arguments as glob patterns. */
+ if (ops['m']) {
+ for (; *argv; argv++) {
+ /* parse pattern */
+ tokenize(*argv);
+ if ((com = parsereg(*argv)))
+ match += scanmatchtable(ht, com, 0, 0, scanfunc, 0);
+ else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv, 0);
+ returnval = 1;
+ }
+ }
+ /* If we didn't match anything, we return 1. */
+ if (!match)
+ returnval = 1;
+ return returnval;
+ }
+
+ /* Take arguments literally -- do not glob */
+ for (; *argv; argv++) {
+ if ((hn = ht->getnode2(ht, *argv))) {
+ scanfunc(hn, 0);
+ } else {
+ zwarnnam(name, "no such hash table element: %s", *argv, 0);
+ returnval = 1;
+ }
+ }
+ return returnval;
+}
+
+/* set: either set the shell options, or set the shell arguments, *
+ * or declare an array, or show various things */
+
+/**/
+int
+bin_set(char *nam, char **args, char *ops, int func)
+{
+ int action, optno, array = 0, hadopt = 0,
+ hadplus = 0, hadend = 0, sort = 0;
+ char **x;
+
+ /* Obsolecent sh compatibility: set - is the same as set +xv *
+ * and set - args is the same as set +xv -- args */
+ if (*args && **args == '-' && !args[0][1]) {
+ dosetopt(VERBOSE, 0, 0);
+ dosetopt(XTRACE, 0, 0);
+ if (!args[1])
+ return 0;
+ }
+
+ /* loop through command line options (begins with "-" or "+") */
+ while (*args && (**args == '-' || **args == '+')) {
+ action = (**args == '-');
+ hadplus |= !action;
+ if(!args[0][1])
+ *args = "--";
+ while (*++*args) {
+ if(**args == Meta)
+ *++*args ^= 32;
+ if(**args != '-' || action)
+ hadopt = 1;
+ /* The pseudo-option `--' signifies the end of options. */
+ if (**args == '-') {
+ hadend = 1;
+ args++;
+ goto doneoptions;
+ } else if (**args == 'o') {
+ if (!*++*args)
+ args++;
+ if (!*args) {
+ zwarnnam(nam, "string expected after -o", NULL, 0);
+ inittyptab();
+ return 1;
+ }
+ if(!(optno = optlookup(*args)))
+ zwarnnam(nam, "no such option: %s", *args, 0);
+ else if(dosetopt(optno, action, 0))
+ zwarnnam(nam, "can't change option: %s", *args, 0);
+ break;
+ } else if(**args == 'A') {
+ if(!*++*args)
+ args++;
+ array = action ? 1 : -1;
+ goto doneoptions;
+ } else if (**args == 's')
+ sort = action ? 1 : -1;
+ else {
+ if (!(optno = optlookupc(**args)))
+ zwarnnam(nam, "bad option: -%c", NULL, **args);
+ else if(dosetopt(optno, action, 0))
+ zwarnnam(nam, "can't change option: -%c", NULL, **args);
+ }
+ }
+ args++;
+ }
+ doneoptions:
+ inittyptab();
+
+ /* Show the parameters, possibly with values */
+ if (!hadopt && !*args)
+ scanhashtable(paramtab, 1, 0, 0, paramtab->printnode,
+ hadplus ? PRINT_NAMEONLY : 0);
+
+ if (array && !*args) {
+ /* display arrays */
+ scanhashtable(paramtab, 1, PM_ARRAY, 0, paramtab->printnode,
+ hadplus ? PRINT_NAMEONLY : 0);
+ }
+ if (!*args && !hadend)
+ return 0;
+ if (array)
+ args++;
+ if (sort)
+ qsort(args, arrlen(args), sizeof(char *),
+ sort > 0 ? strpcmp : invstrpcmp);
+ if (array) {
+ /* create an array with the specified elements */
+ char **a = NULL, **y, *name = args[-1];
+ int len = arrlen(args);
+
+ if (array < 0 && (a = getaparam(name))) {
+ int al = arrlen(a);
+
+ if (al > len)
+ len = al;
+ }
+ for (x = y = zalloc((len + 1) * sizeof(char *)); len--; a++) {
+ if (!*args)
+ args = a;
+ *y++ = ztrdup(*args++);
+ }
+ *y++ = NULL;
+ setaparam(name, x);
+ } else {
+ /* set shell arguments */
+ freearray(pparams);
+ PERMALLOC {
+ pparams = arrdup(args);
+ } LASTALLOC;
+ }
+ return 0;
+}
+
+/**** directory-handling builtins ****/
+
+/**/
+int doprintdir = 0; /* set in exec.c (for autocd) */
+
+/* pwd: display the name of the current directory */
+
+/**/
+int
+bin_pwd(char *name, char **argv, char *ops, int func)
+{
+ if (ops['r'] || ops['P'] || (isset(CHASELINKS) && !ops['L']))
+ printf("%s\n", zgetcwd());
+ else {
+ zputs(pwd, stdout);
+ putchar('\n');
+ }
+ return 0;
+}
+
+/* the directory stack */
+
+/**/
+LinkList dirstack;
+
+/* dirs: list the directory stack, or replace it with a provided list */
+
+/**/
+int
+bin_dirs(char *name, char **argv, char *ops, int func)
+{
+ LinkList l;
+
+ /* with the -v option, provide a numbered list of directories, starting at
+ zero */
+ if (ops['v']) {
+ LinkNode node;
+ int pos = 1;
+
+ printf("0\t");
+ fprintdir(pwd, stdout);
+ for (node = firstnode(dirstack); node; incnode(node)) {
+ printf("\n%d\t", pos++);
+ fprintdir(getdata(node), stdout);
+ }
+ putchar('\n');
+ return 0;
+ }
+ /* given no arguments, list the stack normally */
+ if (!*argv) {
+ printdirstack();
+ return 0;
+ }
+ /* replace the stack with the specified directories */
+ PERMALLOC {
+ l = newlinklist();
+ if (*argv) {
+ while (*argv)
+ addlinknode(l, ztrdup(*argv++));
+ freelinklist(dirstack, freestr);
+ dirstack = l;
+ }
+ } LASTALLOC;
+ return 0;
+}
+
+/* cd, chdir, pushd, popd */
+
+/**/
+void
+set_pwd_env(void)
+{
+ Param pm;
+
+ pm = (Param) paramtab->getnode(paramtab, "PWD");
+ if (pm && PM_TYPE(pm->flags) != PM_SCALAR) {
+ pm->flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 1);
+ }
+
+ pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
+ if (pm && PM_TYPE(pm->flags) != PM_SCALAR) {
+ pm->flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 1);
+ }
+
+ setsparam("PWD", ztrdup(pwd));
+ setsparam("OLDPWD", ztrdup(oldpwd));
+
+ pm = (Param) paramtab->getnode(paramtab, "PWD");
+ if (!(pm->flags & PM_EXPORTED)) {
+ pm->flags |= PM_EXPORTED;
+ pm->env = addenv("PWD", pwd);
+ }
+ pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
+ if (!(pm->flags & PM_EXPORTED)) {
+ pm->flags |= PM_EXPORTED;
+ pm->env = addenv("PWD", pwd);
+ }
+}
+
+/* The main pwd changing function. The real work is done by other *
+ * functions. cd_get_dest() does the initial argument processing; *
+ * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() *
+ * does the ancilliary processing associated with actually changing *
+ * directory. */
+
+/**/
+int
+bin_cd(char *nam, char **argv, char *ops, int func)
+{
+ LinkNode dir;
+ struct stat st1, st2;
+ int chaselinks;
+
+ if (isset(RESTRICTED)) {
+ zwarnnam(nam, "restricted", NULL, 0);
+ return 1;
+ }
+ doprintdir = (doprintdir == -1);
+
+ for (; *argv && **argv == '-'; argv++) {
+ char *s = *argv + 1;
+
+ do {
+ switch (*s) {
+ case 's':
+ case 'P':
+ case 'L':
+ break;
+ default:
+ goto brk;
+ }
+ } while (*++s);
+ for (s = *argv; *++s; ops[*s] = 1);
+ }
+ brk:
+ chaselinks = ops['P'] || (isset(CHASELINKS) && !ops['L']);
+ PERMALLOC {
+ pushnode(dirstack, ztrdup(pwd));
+ if (!(dir = cd_get_dest(nam, argv, ops, func))) {
+ zsfree(getlinknode(dirstack));
+ LASTALLOC_RETURN 1;
+ }
+ } LASTALLOC;
+ cd_new_pwd(func, dir, chaselinks);
+
+ if (stat(unmeta(pwd), &st1) < 0) {
+ zsfree(pwd);
+ pwd = metafy(zgetcwd(), -1, META_DUP);
+ } else if (stat(".", &st2) < 0)
+ chdir(unmeta(pwd));
+ else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) {
+ if (chaselinks) {
+ zsfree(pwd);
+ pwd = metafy(zgetcwd(), -1, META_DUP);
+ } else {
+ chdir(unmeta(pwd));
+ }
+ }
+ set_pwd_env();
+ return 0;
+}
+
+/* Get directory to chdir to */
+
+/**/
+static LinkNode
+cd_get_dest(char *nam, char **argv, char *ops, int func)
+{
+ LinkNode dir = NULL;
+ LinkNode target;
+ char *dest;
+
+ if (!argv[0]) {
+ if (func == BIN_POPD && !nextnode(firstnode(dirstack))) {
+ zwarnnam(nam, "directory stack empty", NULL, 0);
+ return NULL;
+ }
+ if (func == BIN_PUSHD && unset(PUSHDTOHOME))
+ dir = nextnode(firstnode(dirstack));
+ if (dir)
+ insertlinknode(dirstack, dir, getlinknode(dirstack));
+ else if (func != BIN_POPD)
+ pushnode(dirstack, ztrdup(home));
+ } else if (!argv[1]) {
+ int dd;
+ char *end;
+
+ doprintdir++;
+ if (argv[0][1] && (argv[0][0] == '+' || argv[0][0] == '-')) {
+ dd = zstrtol(argv[0] + 1, &end, 10);
+ if (*end == '\0') {
+ if ((argv[0][0] == '+') ^ isset(PUSHDMINUS))
+ for (dir = firstnode(dirstack); dir && dd; dd--, incnode(dir));
+ else
+ for (dir = lastnode(dirstack); dir != (LinkNode) dirstack && dd;
+ dd--, dir = prevnode(dir));
+ if (!dir || dir == (LinkNode) dirstack) {
+ zwarnnam(nam, "no such entry in dir stack", NULL, 0);
+ return NULL;
+ }
+ }
+ }
+ if (!dir)
+ pushnode(dirstack, ztrdup(strcmp(argv[0], "-")
+ ? (doprintdir--, argv[0]) : oldpwd));
+ } else {
+ char *u, *d;
+ int len1, len2, len3;
+
+ if (!(u = strstr(pwd, argv[0]))) {
+ zwarnnam(nam, "string not in pwd: %s", argv[0], 0);
+ return NULL;
+ }
+ len1 = strlen(argv[0]);
+ len2 = strlen(argv[1]);
+ len3 = u - pwd;
+ d = (char *)zalloc(len3 + len2 + strlen(u + len1) + 1);
+ strncpy(d, pwd, len3);
+ strcpy(d + len3, argv[1]);
+ strcat(d, u + len1);
+ pushnode(dirstack, d);
+ doprintdir++;
+ }
+
+ target = dir;
+ if (func == BIN_POPD) {
+ if (!dir) {
+ target = dir = firstnode(dirstack);
+ } else if (dir != firstnode(dirstack)) {
+ return dir;
+ }
+ dir = nextnode(dir);
+ }
+ if (!dir) {
+ dir = firstnode(dirstack);
+ }
+ if (!(dest = cd_do_chdir(nam, getdata(dir), ops['s']))) {
+ if (!target)
+ zsfree(getlinknode(dirstack));
+ if (func == BIN_POPD)
+ zsfree(remnode(dirstack, dir));
+ return NULL;
+ }
+ if (dest != getdata(dir)) {
+ zsfree(getdata(dir));
+ setdata(dir, dest);
+ }
+ return target ? target : dir;
+}
+
+/* Change to given directory, if possible. This function works out *
+ * exactly how the directory should be interpreted, including cdpath *
+ * and CDABLEVARS. For each possible interpretation of the given *
+ * path, this calls cd_try_chdir(), which attempts to chdir to that *
+ * particular path. */
+
+/**/
+static char *
+cd_do_chdir(char *cnam, char *dest, int hard)
+{
+ char **pp, *ret;
+ int hasdot = 0, eno = ENOENT;
+ /* nocdpath indicates that cdpath should not be used. This is the case iff
+ dest is a relative path whose first segment is . or .., but if the path is
+ absolute then cdpath won't be used anyway. */
+ int nocdpath = dest[0] == '.' &&
+ (dest[1] == '/' || !dest[1] || (dest[1] == '.' &&
+ (dest[2] == '/' || !dest[2])));
+
+ /* if we have an absolute path, use it as-is only */
+ if (*dest == '/') {
+ if ((ret = cd_try_chdir(NULL, dest, hard)))
+ return ret;
+ zwarnnam(cnam, "%e: %s", dest, errno);
+ return NULL;
+ }
+
+ /* if cdpath is being used, check it for . */
+ if (!nocdpath)
+ for (pp = cdpath; *pp; pp++)
+ if (!(*pp)[0] || ((*pp)[0] == '.' && (*pp)[1] == '\0'))
+ hasdot = 1;
+ /* if there is no . in cdpath (or it is not being used), try the directory
+ as-is (i.e. from .) */
+ if (!hasdot) {
+ if ((ret = cd_try_chdir(NULL, dest, hard)))
+ return ret;
+ if (errno != ENOENT)
+ eno = errno;
+ }
+ /* if cdpath is being used, try given directory relative to each element in
+ cdpath in turn */
+ if (!nocdpath)
+ for (pp = cdpath; *pp; pp++) {
+ if ((ret = cd_try_chdir(*pp, dest, hard))) {
+ if (strcmp(*pp, ".")) {
+ doprintdir++;
+ }
+ return ret;
+ }
+ if (errno != ENOENT)
+ eno = errno;
+ }
+
+ /* handle the CDABLEVARS option */
+ if ((ret = cd_able_vars(dest))) {
+ if ((ret = cd_try_chdir(NULL, ret,hard))) {
+ doprintdir++;
+ return ret;
+ }
+ if (errno != ENOENT)
+ eno = errno;
+ }
+
+ /* If we got here, it means that we couldn't chdir to any of the
+ multitudinous possible paths allowed by zsh. We've run out of options!
+ Add more here! */
+ zwarnnam(cnam, "%e: %s", dest, eno);
+ return NULL;
+}
+
+/* If the CDABLEVARS option is set, return the new *
+ * interpretation of the given path. */
+
+/**/
+char *
+cd_able_vars(char *s)
+{
+ char *rest, save;
+
+ if (isset(CDABLEVARS)) {
+ for (rest = s; *rest && *rest != '/'; rest++);
+ save = *rest;
+ *rest = 0;
+ s = getnameddir(s);
+ *rest = save;
+
+ if (s && *rest)
+ s = dyncat(s, rest);
+
+ return s;
+ }
+ return NULL;
+}
+
+/* Attempt to change to a single given directory. The directory, *
+ * for the convenience of the calling function, may be provided in *
+ * two parts, which must be concatenated before attempting to chdir. *
+ * Returns NULL if the chdir fails. If the directory change is *
+ * possible, it is performed, and a pointer to the new full pathname *
+ * is returned. */
+
+/**/
+static char *
+cd_try_chdir(char *pfix, char *dest, int hard)
+{
+ char *buf;
+
+ /* handle directory prefix */
+ if (pfix && *pfix) {
+ if (*pfix == '/')
+ buf = tricat(pfix, "/", dest);
+ else {
+ int pwl = strlen(pwd);
+ int pfl = strlen(pfix);
+
+ buf = zalloc(pwl + pfl + strlen(dest) + 3);
+ strcpy(buf, pwd);
+ buf[pwl] = '/';
+ strcpy(buf + pwl + 1, pfix);
+ buf[pwl + 1 + pfl] = '/';
+ strcpy(buf + pwl + pfl + 2, dest);
+ }
+ } else if (*dest == '/')
+ buf = ztrdup(dest);
+ else {
+ int pwl = strlen(pwd);
+
+ buf = zalloc(pwl + strlen(dest) + 2);
+ strcpy(buf, pwd);
+ buf[pwl] = '/';
+ strcpy(buf + pwl + 1, dest);
+ }
+
+ /* Normalise path. See the definition of fixdir() for what this means. */
+ fixdir(buf);
+
+ if (lchdir(buf, NULL, hard)) {
+ zsfree(buf);
+ return NULL;
+ }
+ return metafy(buf, -1, META_NOALLOC);
+}
+
+/* do the extra processing associated with changing directory */
+
+/**/
+static void
+cd_new_pwd(int func, LinkNode dir, int chaselinks)
+{
+ Param pm;
+ List l;
+ char *new_pwd, *s;
+ int dirstacksize;
+
+ if (func == BIN_PUSHD)
+ rolllist(dirstack, dir);
+ new_pwd = remnode(dirstack, dir);
+
+ if (func == BIN_POPD && firstnode(dirstack)) {
+ zsfree(new_pwd);
+ new_pwd = getlinknode(dirstack);
+ } else if (func == BIN_CD && unset(AUTOPUSHD))
+ zsfree(getlinknode(dirstack));
+
+ if (chaselinks) {
+ s = new_pwd;
+ new_pwd = findpwd(s);
+ zsfree(s);
+ }
+ if (isset(PUSHDIGNOREDUPS)) {
+ LinkNode n;
+ for (n = firstnode(dirstack); n; incnode(n)) {
+ if (!strcmp(new_pwd, getdata(n))) {
+ zsfree(remnode(dirstack, n));
+ break;
+ }
+ }
+ }
+
+ /* shift around the pwd variables, to make oldpwd and pwd relate to the
+ current (i.e. new) pwd */
+ zsfree(oldpwd);
+ oldpwd = pwd;
+ pwd = new_pwd;
+ /* update the PWD and OLDPWD shell parameters */
+ if ((pm = (Param) paramtab->getnode(paramtab, "PWD")) &&
+ (pm->flags & PM_EXPORTED) && pm->env)
+ pm->env = replenv(pm->env, pwd);
+ if ((pm = (Param) paramtab->getnode(paramtab, "OLDPWD")) &&
+ (pm->flags & PM_EXPORTED) && pm->env)
+ pm->env = replenv(pm->env, oldpwd);
+ if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE))
+ printdirstack();
+ else if (doprintdir) {
+ fprintdir(pwd, stdout);
+ putchar('\n');
+ }
+
+ /* execute the chpwd function */
+ if ((l = getshfunc("chpwd")) != &dummy_list) {
+ fflush(stdout);
+ fflush(stderr);
+ doshfunc(l, NULL, 0, 1);
+ }
+
+ dirstacksize = getiparam("DIRSTACKSIZE");
+ /* handle directory stack sizes out of range */
+ if (dirstacksize > 0) {
+ int remove = countlinknodes(dirstack) -
+ (dirstacksize < 2 ? 2 : dirstacksize);
+ while (remove-- >= 0)
+ zsfree(remnode(dirstack, lastnode(dirstack)));
+ }
+}
+
+/* Print the directory stack */
+
+/**/
+static void
+printdirstack(void)
+{
+ LinkNode node;
+
+ fprintdir(pwd, stdout);
+ for (node = firstnode(dirstack); node; incnode(node)) {
+ putchar(' ');
+ fprintdir(getdata(node), stdout);
+ }
+ putchar('\n');
+}
+
+/* Normalise a path. Segments consisting of ., and foo/.. *
+ * combinations, are removed and the path is unmetafied. */
+
+/**/
+static void
+fixdir(char *src)
+{
+ char *dest = src;
+ char *d0 = dest;
+
+/*** if have RFS superroot directory ***/
+#ifdef HAVE_SUPERROOT
+ /* allow /.. segments to remain */
+ while (*src == '/' && src[1] == '.' && src[2] == '.' &&
+ (!src[3] || src[3] == '/')) {
+ *dest++ = '/';
+ *dest++ = '.';
+ *dest++ = '.';
+ src += 3;
+ }
+#endif
+
+ for (;;) {
+ /* compress multiple /es into single */
+ if (*src == '/') {
+ *dest++ = *src++;
+ while (*src == '/')
+ src++;
+ }
+ /* if we are at the end of the input path, remove a trailing / (if it
+ exists), and return ct */
+ if (!*src) {
+ while (dest > d0 + 1 && dest[-1] == '/')
+ dest--;
+ *dest = '\0';
+ return;
+ }
+ if (dest > d0 + 1 && src[0] == '.' && src[1] == '.' &&
+ (src[2] == '\0' || src[2] == '/')) {
+ /* remove a foo/.. combination */
+ for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
+ if (dest[-1] != '/')
+ dest--;
+ src++;
+ while (*++src == '/');
+ } else if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
+ /* skip a . section */
+ while (*++src == '/');
+ } else {
+ /* copy a normal segment into the output */
+ while (*src != '/' && *src != '\0')
+ if ((*dest++ = *src++) == Meta)
+ dest[-1] = *src++ ^ 32;
+ }
+ }
+}
+
+/**/
+void
+printqt(char *str)
+{
+ /* Print str, but turn any single quote into '\'' or ''. */
+ for (; *str; str++)
+ if (*str == '\'')
+ printf(isset(RCQUOTES) ? "''" : "'\\''");
+ else
+ putchar(*str);
+}
+
+/**/
+void
+printif(char *str, int c)
+{
+ /* If flag c has an argument, print that */
+ if (str) {
+ printf(" -%c ", c);
+ quotedzputs(str, stdout);
+ }
+}
+
+/**** history list functions ****/
+
+/* fc, history, r */
+
+/**/
+int
+bin_fc(char *nam, char **argv, char *ops, int func)
+{
+ int first = -1, last = -1, retval, minflag = 0;
+ char *s;
+ struct asgment *asgf = NULL, *asgl = NULL;
+ Comp com = NULL;
+
+ /* fc is only permitted in interactive shells */
+ if (!interact) {
+ zwarnnam(nam, "not interactive shell", NULL, 0);
+ return 1;
+ }
+ /* with the -m option, the first argument is taken *
+ * as a pattern that history lines have to match */
+ if (*argv && ops['m']) {
+ tokenize(*argv);
+ if (!(com = parsereg(*argv++))) {
+ zwarnnam(nam, "invalid match pattern", NULL, 0);
+ return 1;
+ }
+ }
+ if (ops['R']) {
+ /* read history from a file */
+ readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1);
+ return 0;
+ }
+ if (ops['W']) {
+ /* write history to a file */
+ savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
+ (ops['I'] ? 2 : 0));
+ return 0;
+ }
+ if (ops['A']) {
+ /* append history to a file */
+ savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
+ (ops['I'] ? 3 : 1));
+ return 0;
+ }
+ if (!(ops['l'] && unset(HISTNOSTORE)))
+ remhist();
+ /* put foo=bar type arguments into the substitution list */
+ while (*argv && equalsplit(*argv, &s)) {
+ Asgment a = (Asgment) alloc(sizeof *a);
+
+ if (!asgf)
+ asgf = asgl = a;
+ else {
+ asgl->next = a;
+ asgl = a;
+ }
+ a->name = *argv;
+ a->value = s;
+ argv++;
+ }
+ /* interpret and check first history line specifier */
+ if (*argv) {
+ minflag = **argv == '-';
+ first = fcgetcomm(*argv);
+ if (first == -1)
+ return 1;
+ argv++;
+ }
+ /* interpret and check second history line specifier */
+ if (*argv) {
+ last = fcgetcomm(*argv);
+ if (last == -1)
+ return 1;
+ argv++;
+ }
+ /* There is a maximum of two history specifiers. At least, there *
+ * will be as long as the history list is one-dimensional. */
+ if (*argv) {
+ zwarnnam("fc", "too many arguments", NULL, 0);
+ return 1;
+ }
+ /* default values of first and last, and range checking */
+ if (first == -1)
+ first = (ops['l']) ? curhist - 16 : curhist - 1;
+ if (last == -1)
+ last = (ops['l']) ? curhist - 1 : first;
+ if (first < firsthist())
+ first = firsthist();
+ if (last == -1)
+ last = (minflag) ? curhist : first;
+ else if (last < first)
+ last = first;
+ if (ops['l'])
+ /* list the required part of the history */
+ retval = fclist(stdout, !ops['n'], ops['r'], ops['D'],
+ ops['d'] + ops['f'] * 2 + ops['E'] * 4 + ops['i'] * 8,
+ first, last, asgf, com);
+ else {
+ /* edit history file, and (if successful) use the result as a new command */
+ int tempfd;
+ FILE *out;
+ char *fil;
+
+ retval = 1;
+ fil = gettempname();
+ if (((tempfd = open(fil, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600))
+ == -1) ||
+ ((out = fdopen(tempfd, "w")) == NULL)) {
+ zwarnnam("fc", "can't open temp file: %e", NULL, errno);
+ } else {
+ if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, com)) {
+ char *editor;
+
+ editor = auxdata ? auxdata : getsparam("FCEDIT");
+ if (!editor)
+ editor = DEFAULT_FCEDIT;
+
+ if (fcedit(editor, fil))
+ if (stuff(fil))
+ zwarnnam("fc", "%e: %s", s, errno);
+ else {
+ loop(0,1);
+ retval = lastval;
+ }
+ }
+ }
+ unlink(fil);
+ }
+ return retval;
+}
+
+/* History handling functions: these are called by ZLE, as well as *
+ * the actual builtins. fcgetcomm() gets a history line, specified *
+ * either by number or leading string. fcsubs() performs a given *
+ * set of simple old=new substitutions on a given command line. *
+ * fclist() outputs a given range of history lines to a text file. */
+
+/* get the history event associated with s */
+
+/**/
+static int
+fcgetcomm(char *s)
+{
+ int cmd;
+
+ /* First try to match a history number. Negative *
+ * numbers indicate reversed numbering. */
+ if ((cmd = atoi(s))) {
+ if (cmd < 0)
+ cmd = curhist + cmd;
+ if (cmd >= curhist) {
+ zwarnnam("fc", "bad history number: %d", 0, cmd);
+ return -1;
+ }
+ return cmd;
+ }
+ /* not a number, so search by string */
+ cmd = hcomsearch(s);
+ if (cmd == -1)
+ zwarnnam("fc", "event not found: %s", s, 0);
+ return cmd;
+}
+
+/* Perform old=new substituions. Uses the asgment structure from zsh.h, *
+ * which is essentially a linked list of string,replacement pairs. */
+
+/**/
+static int
+fcsubs(char **sp, struct asgment *sub)
+{
+ char *oldstr, *newstr, *oldpos, *newpos, *newmem, *s = *sp;
+ int subbed = 0;
+
+ /* loop through the linked list */
+ while (sub) {
+ oldstr = sub->name;
+ newstr = sub->value;
+ sub = sub->next;
+ oldpos = s;
+ /* loop over occurences of oldstr in s, replacing them with newstr */
+ while ((newpos = (char *)strstr(oldpos, oldstr))) {
+ newmem = (char *) alloc(1 + (newpos - s)
+ + strlen(newstr) + strlen(newpos + strlen(oldstr)));
+ ztrncpy(newmem, s, newpos - s);
+ strcat(newmem, newstr);
+ oldpos = newmem + strlen(newmem);
+ strcat(newmem, newpos + strlen(oldstr));
+ s = newmem;
+ subbed = 1;
+ }
+ }
+ *sp = s;
+ return subbed;
+}
+
+/* Print a series of history events to a file. The file pointer is *
+ * given by f, and the required range of events by first and last. *
+ * subs is an optional list of foo=bar substitutions to perform on the *
+ * history lines before output. com is an optional comp structure *
+ * that the history lines are required to match. n, r, D and d are *
+ * options: n indicates that each line should be numbered. r indicates *
+ * that the lines should be output in reverse order (newest first). *
+ * D indicates that the real time taken by each command should be *
+ * output. d indicates that the time of execution of each command *
+ * should be output; d>1 means that the date should be output too; d>3 *
+ * means that mm/dd/yyyy form should be used for the dates, as opposed *
+ * to dd.mm.yyyy form; d>7 means that yyyy-mm-dd form should be used. */
+
+/**/
+static int
+fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com)
+{
+ int fclistdone = 0;
+ char *s, *hs;
+ Histent ent;
+
+ /* reverse range if required */
+ if (r) {
+ r = last;
+ last = first;
+ first = r;
+ }
+ /* suppress "no substitution" warning if no substitution is requested */
+ if (!subs)
+ fclistdone = 1;
+
+ for (;;) {
+ hs = quietgetevent(first);
+ if (!hs) {
+ zwarnnam("fc", "no such event: %d", NULL, first);
+ return 1;
+ }
+ s = dupstring(hs);
+ /* this if does the pattern matching, if required */
+ if (!com || domatch(s, com, 0)) {
+ /* perform substitution */
+ fclistdone |= fcsubs(&s, subs);
+
+ /* do numbering */
+ if (n)
+ fprintf(f, "%5d ", first);
+ ent = NULL;
+ /* output actual time (and possibly date) of execution of the
+ command, if required */
+ if (d) {
+ struct tm *ltm;
+ if (!ent)
+ ent = gethistent(first);
+ ltm = localtime(&ent->stim);
+ if (d >= 2) {
+ if (d >= 8) {
+ fprintf(f, "%d-%02d-%02d ",
+ ltm->tm_year + 1900,
+ ltm->tm_mon + 1, ltm->tm_mday);
+ } else if (d >= 4) {
+ fprintf(f, "%d.%d.%d ",
+ ltm->tm_mday, ltm->tm_mon + 1,
+ ltm->tm_year + 1900);
+ } else {
+ fprintf(f, "%d/%d/%d ",
+ ltm->tm_mon + 1, ltm->tm_mday,
+ ltm->tm_year + 1900);
+ }
+ }
+ fprintf(f, "%02d:%02d ", ltm->tm_hour, ltm->tm_min);
+ }
+ /* display the time taken by the command, if required */
+ if (D) {
+ long diff;
+ if (!ent)
+ ent = gethistent(first);
+ diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
+ fprintf(f, "%ld:%02ld ", diff / 60, diff % 60);
+ }
+
+ /* output the command */
+ if (f == stdout) {
+ nicezputs(s, f);
+ putc('\n', f);
+ } else
+ fprintf(f, "%s\n", s);
+ }
+ /* move on to the next history line, or quit the loop */
+ if (first == last)
+ break;
+ else if (first > last)
+ first--;
+ else
+ first++;
+ }
+
+ /* final processing */
+ if (f != stdout)
+ fclose(f);
+ if (!fclistdone) {
+ zwarnnam("fc", "no substitutions performed", NULL, 0);
+ return 1;
+ }
+ return 0;
+}
+
+/* edit a history file */
+
+/**/
+static int
+fcedit(char *ename, char *fn)
+{
+ char *s;
+
+ if (!strcmp(ename, "-"))
+ return 1;
+
+ s = tricat(ename, " ", fn);
+ execstring(s, 1, 0);
+ zsfree(s);
+
+ return !lastval;
+}
+
+/**** parameter builtins ****/
+
+/* Separate an argument into name=value parts, returning them in an *
+ * asgment structure. Because the asgment structure used is global, *
+ * only one of these can be active at a time. The string s gets placed *
+ * in this global structure, so it needs to be in permanent memory. */
+
+/**/
+static Asgment
+getasg(char *s)
+{
+ static struct asgment asg;
+
+ /* sanity check for valid argument */
+ if (!s)
+ return NULL;
+
+ /* check if name is empty */
+ if (*s == '=') {
+ zerr("bad assignment", NULL, 0);
+ return NULL;
+ }
+ asg.name = s;
+
+ /* search for `=' */
+ for (; *s && *s != '='; s++);
+
+ /* found `=', so return with a value */
+ if (*s) {
+ *s = '\0';
+ asg.value = s + 1;
+ } else {
+ /* didn't find `=', so we only have a name */
+ asg.value = NULL;
+ }
+ return &asg;
+}
+
+/* declare, export, integer, local, readonly, typeset */
+
+/**/
+int
+bin_typeset(char *name, char **argv, char *ops, int func)
+{
+ Param pm;
+ Asgment asg;
+ Comp com;
+ char *optstr = "iLRZlurtxU";
+ int on = 0, off = 0, roff, bit = PM_INTEGER;
+ int initon, initoff, of, i;
+ int returnval = 0, printflags = 0;
+
+ /* hash -f is really the builtin `functions' */
+ if (ops['f'])
+ return bin_functions(name, argv, ops, func);
+
+ /* Translate the options into PM_* flags. *
+ * Unfortunately, this depends on the order *
+ * these flags are defined in zsh.h */
+ for (; *optstr; optstr++, bit <<= 1)
+ if (ops[*optstr] == 1)
+ on |= bit;
+ else if (ops[*optstr] == 2)
+ off |= bit;
+ roff = off;
+
+ /* Sanity checks on the options. Remove conficting options. */
+ if (on & PM_INTEGER)
+ off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY;
+ if (on & PM_LEFT)
+ off |= PM_RIGHT_B | PM_INTEGER;
+ if (on & PM_RIGHT_B)
+ off |= PM_LEFT | PM_INTEGER;
+ if (on & PM_RIGHT_Z)
+ off |= PM_INTEGER;
+ if (on & PM_UPPER)
+ off |= PM_LOWER;
+ if (on & PM_LOWER)
+ off |= PM_UPPER;
+ on &= ~off;
+
+ /* Given no arguments, list whatever the options specify. */
+ if (!*argv) {
+ if (!(on|roff))
+ printflags |= PRINT_TYPE;
+ if (roff || ops['+'])
+ printflags |= PRINT_NAMEONLY;
+ scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags);
+ return 0;
+ }
+
+ /* With the -m option, treat arguments as glob patterns */
+ if (ops['m']) {
+ while ((asg = getasg(*argv++))) {
+ tokenize(asg->name); /* expand argument */
+ if (!(com = parsereg(asg->name))) {
+ untokenize(asg->name);
+ zwarnnam(name, "bad pattern : %s", argv[-1], 0);
+ returnval = 1;
+ continue;
+ }
+ /* If no options or values are given, display all *
+ * parameters matching the glob pattern. */
+ if (!(on || roff || asg->value)) {
+ scanmatchtable(paramtab, com, 0, 0, paramtab->printnode, 0);
+ continue;
+ }
+ /* Since either options or values are given, we search *
+ * through the parameter table and change all parameters *
+ * matching the glob pattern to have these flags and/or *
+ * value. */
+ for (i = 0; i < paramtab->hsize; i++) {
+ for (pm = (Param) paramtab->nodes[i]; pm; pm = (Param) pm->next) {
+ if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED))
+ continue;
+ if (domatch(pm->nam, com, 0)) {
+ /* set up flags if we have any */
+ if (on || roff) {
+ if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) &&
+ !(pm->flags & PM_READONLY & ~off))
+ uniqarray((*pm->gets.afn) (pm));
+ pm->flags = (pm->flags | on) & ~off;
+ if (PM_TYPE(pm->flags) != PM_ARRAY) {
+ if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && auxlen)
+ pm->ct = auxlen;
+ /* did we just export this? */
+ if ((pm->flags & PM_EXPORTED) && !pm->env) {
+ pm->env = addenv(pm->nam, (asg->value) ? asg->value : getsparam(pm->nam));
+ } else if (!(pm->flags & PM_EXPORTED) && pm->env) {
+ /* did we just unexport this? */
+ delenv(pm->env);
+ zsfree(pm->env);
+ pm->env = NULL;
+ }
+ }
+ }
+ /* set up a new value if given */
+ if (asg->value) {
+ setsparam(pm->nam, ztrdup(asg->value));
+ }
+ }
+ }
+ }
+ }
+ return returnval;
+ }
+
+ /* Save the values of on, off, and func */
+ initon = on;
+ initoff = off;
+ of = func;
+
+ /* Take arguments literally. Don't glob */
+ while ((asg = getasg(*argv++))) {
+ /* restore the original values of on, off, and func */
+ on = initon;
+ off = initoff;
+ func = of;
+ on &= ~PM_ARRAY;
+
+ /* check if argument is a valid identifier */
+ if (!isident(asg->name)) {
+ zerr("not an identifier: %s", asg->name, 0);
+ returnval = 1;
+ continue;
+ }
+ bit = 0; /* flag for switching int<->not-int */
+ if ((pm = (Param)paramtab->getnode(paramtab, asg->name)) &&
+ (((pm->flags & PM_SPECIAL) && pm->level == locallevel) ||
+ (!(pm->flags & PM_UNSET) &&
+ ((locallevel == pm->level) || func == BIN_EXPORT) &&
+ !(bit = ((off & pm->flags) | (on & ~pm->flags)) & PM_INTEGER)))) {
+ /* if no flags or values are given, just print this parameter */
+ if (!on && !roff && !asg->value) {
+ paramtab->printnode((HashNode) pm, 0);
+ continue;
+ }
+ if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerrnam(name, "%s: restricted", pm->nam, 0);
+ returnval = 1;
+ continue;
+ }
+ if((pm->flags & PM_SPECIAL) &&
+ PM_TYPE((pm->flags | on) & ~off) != PM_TYPE(pm->flags)) {
+ zerrnam(name, "%s: cannot change type of a special parameter",
+ pm->nam, 0);
+ returnval = 1;
+ continue;
+ }
+ if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) &&
+ !(pm->flags & PM_READONLY & ~off))
+ uniqarray((*pm->gets.afn) (pm));
+ pm->flags = (pm->flags | on) & ~off;
+ if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) &&
+ auxlen)
+ pm->ct = auxlen;
+ if (PM_TYPE(pm->flags) != PM_ARRAY) {
+ if (pm->flags & PM_EXPORTED) {
+ if (!(pm->flags & PM_UNSET) && !pm->env && !asg->value)
+ pm->env = addenv(asg->name, getsparam(asg->name));
+ } else if (pm->env) {
+ delenv(pm->env);
+ zsfree(pm->env);
+ pm->env = NULL;
+ }
+ if (asg->value)
+ setsparam(asg->name, ztrdup(asg->value));
+ }
+ } else {
+ if (bit) {
+ if (pm->flags & PM_READONLY) {
+ on |= ~off & PM_READONLY;
+ pm->flags &= ~PM_READONLY;
+ }
+ if (!asg->value)
+ asg->value = dupstring(getsparam(asg->name));
+ unsetparam(asg->name);
+ }
+ /* create a new node for a parameter with the *
+ * flags in `on' minus the readonly flag */
+ pm = createparam(ztrdup(asg->name), on & ~PM_READONLY);
+ DPUTS(!pm, "BUG: parameter not created");
+ pm->ct = auxlen;
+ if (func != BIN_EXPORT)
+ pm->level = locallevel;
+ if (asg->value)
+ setsparam(asg->name, ztrdup(asg->value));
+ pm->flags |= (on & PM_READONLY);
+ }
+ }
+ return returnval;
+}
+
+/* Display or change the attributes of shell functions. *
+ * If called as autoload, it will define a new autoloaded *
+ * (undefined) shell function. */
+
+/**/
+int
+bin_functions(char *name, char **argv, char *ops, int func)
+{
+ Comp com;
+ Shfunc shf;
+ int i, returnval = 0;
+ int on = 0, off = 0;
+
+ /* Do we have any flags defined? */
+ if (ops['u'] || ops['t']) {
+ if (ops['u'] == 1)
+ on |= PM_UNDEFINED;
+ else if (ops['u'] == 2)
+ off |= PM_UNDEFINED;
+
+ if (ops['t'] == 1)
+ on |= PM_TAGGED;
+ else if (ops['t'] == 2)
+ off |= PM_TAGGED;
+ }
+
+ if (off & PM_UNDEFINED) {
+ zwarnnam(name, "invalid option(s)", NULL, 0);
+ return 1;
+ }
+
+ /* If no arguments given, we will print functions. If flags *
+ * are given, we will print only functions containing these *
+ * flags, else we'll print them all. */
+ if (!*argv) {
+ scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode, 0);
+ return 0;
+ }
+
+ /* With the -m option, treat arguments as glob patterns */
+ if (ops['m']) {
+ on &= ~PM_UNDEFINED;
+ for (; *argv; argv++) {
+ /* expand argument */
+ tokenize(*argv);
+ if ((com = parsereg(*argv))) {
+ /* with no options, just print all functions matching the glob pattern */
+ if (!(on|off)) {
+ scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, 0);
+ } else {
+ /* apply the options to all functions matching the glob pattern */
+ for (i = 0; i < shfunctab->hsize; i++) {
+ for (shf = (Shfunc) shfunctab->nodes[i]; shf; shf = (Shfunc) shf->next)
+ if (domatch(shf->nam, com, 0) && !(shf->flags & DISABLED))
+ shf->flags = (shf->flags | on) & (~off);
+ }
+ }
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv, 0);
+ returnval = 1;
+ }
+ }
+ return returnval;
+ }
+
+ /* Take the arguments literally -- do not glob */
+ for (; *argv; argv++) {
+ if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
+ /* if any flag was given */
+ if (on|off)
+ /* turn on/off the given flags */
+ shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off;
+ else
+ /* no flags, so just print */
+ shfunctab->printnode((HashNode) shf, 0);
+ } else if (on & PM_UNDEFINED) {
+ /* Add a new undefined (autoloaded) function to the *
+ * hash table with the corresponding flags set. */
+ shf = (Shfunc) zcalloc(sizeof *shf);
+ shf->flags = on;
+ shf->funcdef = mkautofn(shf);
+ shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
+ } else
+ returnval = 1;
+ }
+ return returnval;
+}
+
+/**/
+static List
+mkautofn(Shfunc shf)
+{
+ List l;
+ Sublist s;
+ Pline p;
+ Cmd c;
+ AutoFn a;
+ PERMALLOC {
+ a = (AutoFn)allocnode(N_AUTOFN);
+ a->shf = shf;
+ c = (Cmd)allocnode(N_CMD);
+ c->type = AUTOFN;
+ c->u.autofn = a;
+ p = (Pline)allocnode(N_PLINE);
+ p->left = c;
+ p->type = END;
+ s = (Sublist)allocnode(N_SUBLIST);
+ s->left = p;
+ l = (List)allocnode(N_LIST);
+ l->left = s;
+ l->type = Z_SYNC;
+ } LASTALLOC;
+ return l;
+}
+
+/* unset: unset parameters */
+
+/**/
+int
+bin_unset(char *name, char **argv, char *ops, int func)
+{
+ Param pm, next;
+ Comp com;
+ char *s;
+ int match = 0, returnval = 0;
+ int i;
+
+ /* unset -f is the same as unfunction */
+ if (ops['f'])
+ return bin_unhash(name, argv, ops, func);
+
+ /* with -m option, treat arguments as glob patterns */
+ if (ops['m']) {
+ while ((s = *argv++)) {
+ /* expand */
+ tokenize(s);
+ if ((com = parsereg(s))) {
+ /* Go through the parameter table, and unset any matches */
+ for (i = 0; i < paramtab->hsize; i++) {
+ for (pm = (Param) paramtab->nodes[i]; pm; pm = next) {
+ /* record pointer to next, since we may free this one */
+ next = (Param) pm->next;
+ if ((!(pm->flags & PM_RESTRICTED) ||
+ unset(RESTRICTED)) && domatch(pm->nam, com, 0)) {
+ unsetparam(pm->nam);
+ match++;
+ }
+ }
+ }
+ } else {
+ untokenize(s);
+ zwarnnam(name, "bad pattern : %s", s, 0);
+ returnval = 1;
+ }
+ }
+ /* If we didn't match anything, we return 1. */
+ if (!match)
+ returnval = 1;
+ return returnval;
+ }
+
+ /* do not glob -- unset the given parameter */
+ while ((s = *argv++)) {
+ pm = (Param) paramtab->getnode(paramtab, s);
+ if (!pm)
+ returnval = 1;
+ else if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerrnam(name, "%s: restricted", pm->nam, 0);
+ returnval = 1;
+ } else
+ unsetparam(s);
+ }
+ return returnval;
+}
+
+/* type, whence, which */
+
+/**/
+int
+bin_whence(char *nam, char **argv, char *ops, int func)
+{
+ HashNode hn;
+ Comp com;
+ int returnval = 0;
+ int printflags = 0;
+ int csh, all, v, wd;
+ int informed;
+ char *cnam;
+
+ /* Check some option information */
+ csh = ops['c'];
+ v = ops['v'];
+ all = ops['a'];
+ wd = ops['w'];
+
+ if (ops['w'])
+ printflags |= PRINT_WHENCE_WORD;
+ else if (ops['c'])
+ printflags |= PRINT_WHENCE_CSH;
+ else if (ops['v'])
+ printflags |= PRINT_WHENCE_VERBOSE;
+ else
+ printflags |= PRINT_WHENCE_SIMPLE;
+ if (ops['f'])
+ printflags |= PRINT_WHENCE_FUNCDEF;
+
+ /* With -m option -- treat arguments as a glob patterns */
+ if (ops['m']) {
+ for (; *argv; argv++) {
+ /* parse the pattern */
+ tokenize(*argv);
+ if (!(com = parsereg(*argv))) {
+ untokenize(*argv);
+ zwarnnam(nam, "bad pattern : %s", *argv, 0);
+ returnval = 1;
+ continue;
+ }
+ if (!ops['p']) {
+ /* -p option is for path search only. *
+ * We're not using it, so search for ... */
+
+ /* aliases ... */
+ scanmatchtable(aliastab, com, 0, DISABLED, aliastab->printnode, printflags);
+
+ /* and reserved words ... */
+ scanmatchtable(reswdtab, com, 0, DISABLED, reswdtab->printnode, printflags);
+
+ /* and shell functions... */
+ scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, printflags);
+
+ /* and builtins. */
+ scanmatchtable(builtintab, com, 0, DISABLED, builtintab->printnode, printflags);
+ }
+ /* Done search for `internal' commands, if the -p option *
+ * was not used. Now search the path. */
+ cmdnamtab->filltable(cmdnamtab);
+ scanmatchtable(cmdnamtab, com, 0, 0, cmdnamtab->printnode, printflags);
+
+ }
+ return returnval;
+ }
+
+ /* Take arguments literally -- do not glob */
+ for (; *argv; argv++) {
+ informed = 0;
+
+ if (!ops['p']) {
+ /* Look for alias */
+ if ((hn = aliastab->getnode(aliastab, *argv))) {
+ aliastab->printnode(hn, printflags);
+ if (!all)
+ continue;
+ informed = 1;
+ }
+ /* Look for reserved word */
+ if ((hn = reswdtab->getnode(reswdtab, *argv))) {
+ reswdtab->printnode(hn, printflags);
+ if (!all)
+ continue;
+ informed = 1;
+ }
+ /* Look for shell function */
+ if ((hn = shfunctab->getnode(shfunctab, *argv))) {
+ shfunctab->printnode(hn, printflags);
+ if (!all)
+ continue;
+ informed = 1;
+ }
+ /* Look for builtin command */
+ if ((hn = builtintab->getnode(builtintab, *argv))) {
+ builtintab->printnode(hn, printflags);
+ if (!all)
+ continue;
+ informed = 1;
+ }
+ /* Look for commands that have been added to the *
+ * cmdnamtab with the builtin `hash foo=bar'. */
+ if ((hn = cmdnamtab->getnode(cmdnamtab, *argv)) && (hn->flags & HASHED)) {
+ cmdnamtab->printnode(hn, printflags);
+ if (!all)
+ continue;
+ informed = 1;
+ }
+ }
+
+ /* Option -a is to search the entire path, *
+ * rather than just looking for one match. */
+ if (all) {
+ char **pp, buf[PATH_MAX], *z;
+
+ for (pp = path; *pp; pp++) {
+ z = buf;
+ if (**pp) {
+ strucpy(&z, *pp);
+ *z++ = '/';
+ }
+ if ((z - buf) + strlen(*argv) >= PATH_MAX)
+ continue;
+ strcpy(z, *argv);
+ if (iscom(buf)) {
+ if (wd) {
+ printf("%s: command\n", *argv);
+ } else {
+ if (v && !csh)
+ zputs(*argv, stdout), fputs(" is ", stdout);
+ zputs(buf, stdout);
+ if (ops['s'])
+ print_if_link(buf);
+ fputc('\n', stdout);
+ }
+ informed = 1;
+ }
+ }
+ if (!informed && (wd || v || csh)) {
+ zputs(*argv, stdout);
+ puts(wd ? ": none" : " not found");
+ returnval = 1;
+ }
+ } else if ((cnam = findcmd(*argv))) {
+ /* Found external command. */
+ if (wd) {
+ printf("%s: command\n", *argv);
+ } else {
+ if (v && !csh)
+ zputs(*argv, stdout), fputs(" is ", stdout);
+ zputs(cnam, stdout);
+ if (ops['s'])
+ print_if_link(cnam);
+ fputc('\n', stdout);
+ }
+ zsfree(cnam);
+ } else {
+ /* Not found at all. */
+ if (v || csh || wd)
+ zputs(*argv, stdout), puts(wd ? ": none" : " not found");
+ returnval = 1;
+ }
+ }
+ return returnval;
+}
+
+/**** command & named directory hash table builtins ****/
+
+/*****************************************************************
+ * hash -- explicitly hash a command. *
+ * 1) Given no arguments, list the hash table. *
+ * 2) The -m option prints out commands in the hash table that *
+ * match a given glob pattern. *
+ * 3) The -f option causes the entire path to be added to the *
+ * hash table (cannot be combined with any arguments). *
+ * 4) The -r option causes the entire hash table to be discarded *
+ * (cannot be combined with any arguments). *
+ * 5) Given argument of the form foo=bar, add element to command *
+ * hash table, so that when `foo' is entered, then `bar' is *
+ * executed. *
+ * 6) Given arguments not of the previous form, add it to the *
+ * command hash table as if it were being executed. *
+ * 7) The -d option causes analogous things to be done using *
+ * the named directory hash table. *
+ *****************************************************************/
+
+/**/
+int
+bin_hash(char *name, char **argv, char *ops, int func)
+{
+ HashTable ht;
+ Comp com;
+ Asgment asg;
+ int returnval = 0;
+
+ if (ops['d'])
+ ht = nameddirtab;
+ else
+ ht = cmdnamtab;
+
+ if (ops['r'] || ops['f']) {
+ /* -f and -r can't be used with any arguments */
+ if (*argv) {
+ zwarnnam("hash", "too many arguments", NULL, 0);
+ return 1;
+ }
+
+ /* empty the hash table */
+ if (ops['r'])
+ ht->emptytable(ht);
+
+ /* fill the hash table in a standard way */
+ if (ops['f'])
+ ht->filltable(ht);
+
+ return 0;
+ }
+
+ /* Given no arguments, display current hash table. */
+ if (!*argv) {
+ scanhashtable(ht, 1, 0, 0, ht->printnode, 0);
+ return 0;
+ }
+
+ while (*argv) {
+ void *hn;
+ if (ops['m']) {
+ /* with the -m option, treat the argument as a glob pattern */
+ tokenize(*argv); /* expand */
+ if ((com = parsereg(*argv))) {
+ /* display matching hash table elements */
+ scanmatchtable(ht, com, 0, 0, ht->printnode, 0);
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv, 0);
+ returnval = 1;
+ }
+ } else if((asg = getasg(*argv))->value) {
+ if(isset(RESTRICTED)) {
+ zwarnnam(name, "restricted: %s", asg->value, 0);
+ returnval = 1;
+ } else {
+ /* The argument is of the form foo=bar, *
+ * so define an entry for the table. */
+ if(ops['d']) {
+ Nameddir nd = hn = zcalloc(sizeof *nd);
+ nd->flags = 0;
+ nd->dir = ztrdup(asg->value);
+ } else {
+ Cmdnam cn = hn = zcalloc(sizeof *cn);
+ cn->flags = HASHED;
+ cn->u.cmd = ztrdup(asg->value);
+ }
+ ht->addnode(ht, ztrdup(asg->name), hn);
+ if(ops['v'])
+ ht->printnode(hn, 0);
+ }
+ } else if (!(hn = ht->getnode2(ht, asg->name))) {
+ /* With no `=value' part to the argument, *
+ * work out what it ought to be. */
+ if(ops['d']) {
+ if(!getnameddir(asg->name)) {
+ zwarnnam(name, "no such directory name: %s", asg->name, 0);
+ returnval = 1;
+ }
+ } else {
+ if (!hashcmd(asg->name, path)) {
+ zwarnnam(name, "no such command: %s", asg->name, 0);
+ returnval = 1;
+ }
+ }
+ if(ops['v'] && (hn = ht->getnode2(ht, asg->name)))
+ ht->printnode(hn, 0);
+ } else if(ops['v'])
+ ht->printnode(hn, 0);
+ argv++;
+ }
+ return returnval;
+}
+
+/* unhash: remove specified elements from a hash table */
+
+/**/
+int
+bin_unhash(char *name, char **argv, char *ops, int func)
+{
+ HashTable ht;
+ HashNode hn, nhn;
+ Comp com;
+ int match = 0, returnval = 0;
+ int i;
+
+ /* Check which hash table we are working with. */
+ if (ops['d'])
+ ht = nameddirtab; /* named directories */
+ else if (ops['f'])
+ ht = shfunctab; /* shell functions */
+ else if (ops['a'])
+ ht = aliastab; /* aliases */
+ else
+ ht = cmdnamtab; /* external commands */
+
+ /* With -m option, treat arguments as glob patterns. *
+ * "unhash -m '*'" is legal, but not recommended. */
+ if (ops['m']) {
+ for (; *argv; argv++) {
+ /* expand argument */
+ tokenize(*argv);
+ if ((com = parsereg(*argv))) {
+ /* remove all nodes matching glob pattern */
+ for (i = 0; i < ht->hsize; i++) {
+ for (hn = ht->nodes[i]; hn; hn = nhn) {
+ /* record pointer to next, since we may free this one */
+ nhn = hn->next;
+ if (domatch(hn->nam, com, 0)) {
+ ht->freenode(ht->removenode(ht, hn->nam));
+ match++;
+ }
+ }
+ }
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv, 0);
+ returnval = 1;
+ }
+ }
+ /* If we didn't match anything, we return 1. */
+ if (!match)
+ returnval = 1;
+ return returnval;
+ }
+
+ /* Take arguments literally -- do not glob */
+ for (; *argv; argv++) {
+ if ((hn = ht->removenode(ht, *argv))) {
+ ht->freenode(hn);
+ } else {
+ zwarnnam(name, "no such hash table element: %s", *argv, 0);
+ returnval = 1;
+ }
+ }
+ return returnval;
+}
+
+/**** alias builtins ****/
+
+/* alias: display or create aliases. */
+
+/**/
+int
+bin_alias(char *name, char **argv, char *ops, int func)
+{
+ Alias a;
+ Comp com;
+ Asgment asg;
+ int haveflags = 0, returnval = 0;
+ int flags1 = 0, flags2 = DISABLED;
+ int printflags = 0;
+
+ /* Did we specify the type of alias? */
+ if (ops['r'] || ops['g']) {
+ if (ops['r'] && ops['g']) {
+ zwarnnam(name, "illegal combination of options", NULL, 0);
+ return 1;
+ }
+ haveflags = 1;
+ if (ops['g'])
+ flags1 |= ALIAS_GLOBAL;
+ else
+ flags2 |= ALIAS_GLOBAL;
+ }
+
+ if (ops['L'])
+ printflags |= PRINT_LIST;
+
+ /* In the absence of arguments, list all aliases. If a command *
+ * line flag is specified, list only those of that type. */
+ if (!*argv) {
+ scanhashtable(aliastab, 1, flags1, flags2, aliastab->printnode, printflags);
+ return 0;
+ }
+
+ /* With the -m option, treat the arguments as *
+ * glob patterns of aliases to display. */
+ if (ops['m']) {
+ for (; *argv; argv++) {
+ tokenize(*argv); /* expand argument */
+ if ((com = parsereg(*argv))) {
+ /* display the matching aliases */
+ scanmatchtable(aliastab, com, flags1, flags2, aliastab->printnode, printflags);
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv, 0);
+ returnval = 1;
+ }
+ }
+ return returnval;
+ }
+
+ /* Take arguments literally. Don't glob */
+ while ((asg = getasg(*argv++))) {
+ if (asg->value && !ops['L']) {
+ /* The argument is of the form foo=bar and we are not *
+ * forcing a listing with -L, so define an alias */
+ aliastab->addnode(aliastab, ztrdup(asg->name),
+ createaliasnode(ztrdup(asg->value), flags1));
+ } else if ((a = (Alias) aliastab->getnode(aliastab, asg->name))) {
+ /* display alias if appropriate */
+ if (!haveflags ||
+ (ops['r'] && !(a->flags & ALIAS_GLOBAL)) ||
+ (ops['g'] && (a->flags & ALIAS_GLOBAL)))
+ aliastab->printnode((HashNode) a, printflags);
+ } else
+ returnval = 1;
+ }
+ return returnval;
+}
+
+
+/**** miscellaneous builtins ****/
+
+/* true, : (colon) */
+
+/**/
+int
+bin_true(char *name, char **argv, char *ops, int func)
+{
+ return 0;
+}
+
+/* false builtin */
+
+/**/
+int
+bin_false(char *name, char **argv, char *ops, int func)
+{
+ return 1;
+}
+
+/* the zle buffer stack */
+
+/**/
+LinkList bufstack;
+
+/* echo, print, pushln */
+
+/**/
+int
+bin_print(char *name, char **args, char *ops, int func)
+{
+ int nnl = 0, fd, argc, n;
+ int *len;
+ Histent ent;
+ FILE *fout = stdout;
+
+ /* -m option -- treat the first argument as a pattern and remove
+ * arguments not matching */
+ if (ops['m']) {
+ Comp com;
+ char **t, **p;
+
+ tokenize(*args);
+ if (!(com = parsereg(*args))) {
+ untokenize(*args);
+ zwarnnam(name, "bad pattern : %s", *args, 0);
+ return 1;
+ }
+ for (p = ++args; *p; p++)
+ if (!domatch(*p, com, 0))
+ for (t = p--; (*t = t[1]); t++);
+ }
+ /* compute lengths, and interpret according to -P, -D, -e, etc. */
+ argc = arrlen(args);
+ len = (int *)ncalloc(argc * sizeof(int));
+ for(n = 0; n < argc; n++) {
+ /* first \ sequences */
+ if (!ops['e'] && (ops['R'] || ops['r'] || ops['E']))
+ unmetafy(args[n], &len[n]);
+ else
+ args[n] = getkeystring(args[n], &len[n],
+ func != BIN_ECHO && !ops['e'], &nnl);
+ /* -P option -- interpret as a prompt sequence */
+ if(ops['P'])
+ args[n] = unmetafy(promptexpand(metafy(args[n], len[n],
+ META_NOALLOC), 0, NULL, NULL), &len[n]);
+ /* -D option -- interpret as a directory, and use ~ */
+ if(ops['D']) {
+ Nameddir d = finddir(args[n]);
+ if(d) {
+ char *arg = alloc(strlen(args[n]) + 1);
+ sprintf(arg, "~%s%s", d->nam,
+ args[n] + strlen(d->dir));
+ args[n] = arg;
+ len[n] = strlen(args[n]);
+ }
+ }
+ }
+
+ /* -z option -- push the arguments onto the editing buffer stack */
+ if (ops['z']) {
+ PERMALLOC {
+ pushnode(bufstack, sepjoin(args, NULL));
+ } LASTALLOC;
+ return 0;
+ }
+ /* -s option -- add the arguments to the history list */
+ if (ops['s']) {
+ int nwords = 0, nlen, iwords;
+ char **pargs = args;
+
+ PERMALLOC {
+ ent = gethistent(++curhist);
+ zsfree(ent->text);
+ if (ent->nwords)
+ zfree(ent->words, ent->nwords*2*sizeof(short));
+ while (*pargs++)
+ nwords++;
+ if ((ent->nwords = nwords)) {
+ ent->words = (short *)zalloc(nwords*2*sizeof(short));
+ nlen = iwords = 0;
+ for (pargs = args; *pargs; pargs++) {
+ ent->words[iwords++] = nlen;
+ nlen += strlen(*pargs);
+ ent->words[iwords++] = nlen;
+ nlen++;
+ }
+ } else
+ ent->words = (short *)NULL;
+ ent->text = zjoin(args, ' ');
+ ent->stim = ent->ftim = time(NULL);
+ ent->flags = 0;
+ } LASTALLOC;
+ return 0;
+ }
+ /* -u and -p -- output to other than standard output */
+ if (ops['u'] || ops['p']) {
+ if (ops['u']) {
+ for (fd = 0; fd < 10; fd++)
+ if (ops[fd + '0'])
+ break;
+ if (fd == 10)
+ fd = 0;
+ } else
+ fd = coprocout;
+ if ((fd = dup(fd)) < 0) {
+ zwarnnam(name, "bad file number", NULL, 0);
+ return 1;
+ }
+ if ((fout = fdopen(fd, "w")) == 0) {
+ zwarnnam(name, "bad mode on fd", NULL, 0);
+ return 1;
+ }
+ }
+
+ /* -o and -O -- sort the arguments */
+ if (ops['o']) {
+ if (ops['i'])
+ qsort(args, arrlen(args), sizeof(char *), cstrpcmp);
+
+ else
+ qsort(args, arrlen(args), sizeof(char *), strpcmp);
+ } else if (ops['O']) {
+ if (ops['i'])
+ qsort(args, arrlen(args), sizeof(char *), invcstrpcmp);
+
+ else
+ qsort(args, arrlen(args), sizeof(char *), invstrpcmp);
+ }
+ /* after sorting arguments, recalculate lengths */
+ if(ops['o'] || ops['O'])
+ for(n = 0; n < argc; n++)
+ len[n] = strlen(args[n]);
+
+ /* -c -- output in columns */
+ if (ops['c']) {
+ int l, nc, nr, sc, n, t, i;
+ char **ap;
+
+ for (n = l = 0, ap = args; *ap; ap++, n++)
+ if (l < (t = strlen(*ap)))
+ l = t;
+
+ sc = l + 2;
+ nc = (columns + 1) / sc;
+ if (!nc)
+ nc = 1;
+ nr = (n + nc - 1) / nc;
+
+ for (i = 0; i < nr; i++) {
+ ap = args + i;
+ do {
+ l = strlen(*ap);
+ fprintf(fout, "%s", *ap);
+ for (t = nr; t && *ap; t--, ap++);
+ if(*ap)
+ for (; l < sc; l++)
+ fputc(' ', fout);
+ } while (*ap);
+ fputc(ops['N'] ? '\0' : '\n', fout);
+ }
+ if (fout != stdout)
+ fclose(fout);
+ return 0;
+ }
+ /* normal output */
+ for (; *args; args++, len++) {
+ fwrite(*args, *len, 1, fout);
+ if (args[1])
+ fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+ }
+ if (!(ops['n'] || nnl))
+ fputc(ops['N'] ? '\0' : '\n', fout);
+ if (fout != stdout)
+ fclose(fout);
+ return 0;
+}
+
+/* echotc: output a termcap */
+
+/**/
+int
+bin_echotc(char *name, char **argv, char *ops, int func)
+{
+ char *s, buf[2048], *t, *u;
+ int num, argct;
+
+ s = *argv++;
+ if (termflags & TERM_BAD)
+ return 1;
+ if ((termflags & TERM_UNKNOWN) && (isset(INTERACTIVE) || !init_term()))
+ return 1;
+ /* if the specified termcap has a numeric value, display it */
+ if ((num = tgetnum(s)) != -1) {
+ printf("%d\n", num);
+ return 0;
+ }
+ /* if the specified termcap is boolean, and set, say so *
+ * ncurses can tell if an existing boolean capability is *
+ * off so in this case we print "no". */
+#if !defined(NCURSES_VERSION) || !defined(COLOR_PAIR)
+ if (tgetflag(s) > 0) {
+ puts("yes");
+ return (0);
+ }
+#else /* NCURSES_VERSION && COLOR_PAIR */
+ switch (tgetflag(s)) {
+ case -1:
+ break;
+ case 0:
+ puts("no");
+ return 0;
+ default:
+ puts("yes");
+ return 0;
+ }
+#endif /* NCURSES_VERSION && COLOR_PAIR */
+ /* get a string-type capability */
+ u = buf;
+ t = tgetstr(s, &u);
+ if (!t || !*t) {
+ /* capability doesn't exist, or (if boolean) is off */
+ zwarnnam(name, "no such capability: %s", s, 0);
+ return 1;
+ }
+ /* count the number of arguments required */
+ for (argct = 0, u = t; *u; u++)
+ if (*u == '%') {
+ if (u++, (*u == 'd' || *u == '2' || *u == '3' || *u == '.' ||
+ *u == '+'))
+ argct++;
+ }
+ /* check that the number of arguments provided is correct */
+ if (arrlen(argv) != argct) {
+ zwarnnam(name, (arrlen(argv) < argct) ? "not enough arguments" :
+ "too many arguments", NULL, 0);
+ return 1;
+ }
+ /* output string, through the proper termcap functions */
+ if (!argct)
+ tputs(t, 1, putraw);
+ else {
+ num = (argv[1]) ? atoi(argv[1]) : atoi(*argv);
+ tputs(tgoto(t, atoi(*argv), num), num, putraw);
+ }
+ return 0;
+}
+
+/* shift builtin */
+
+/**/
+int
+bin_shift(char *name, char **argv, char *ops, int func)
+{
+ int num = 1, l, ret = 0;
+ char **s;
+
+ /* optional argument can be either numeric or an array */
+ if (*argv && !getaparam(*argv))
+ num = matheval(*argv++);
+
+ if (num < 0) {
+ zwarnnam(name, "argument to shift must be non-negative", NULL, 0);
+ return 1;
+ }
+
+ if (*argv) {
+ for (; *argv; argv++)
+ if ((s = getaparam(*argv))) {
+ if (num > arrlen(s)) {
+ zwarnnam(name, "shift count must be <= $#", NULL, 0);
+ ret++;
+ continue;
+ }
+ PERMALLOC {
+ s = arrdup(s + num);
+ } LASTALLOC;
+ setaparam(*argv, s);
+ }
+ } else {
+ if (num > (l = arrlen(pparams))) {
+ zwarnnam(name, "shift count must be <= $#", NULL, 0);
+ ret = 1;
+ } else {
+ s = zalloc((l - num + 1) * sizeof(char *));
+ memcpy(s, pparams + num, (l - num + 1) * sizeof(char *));
+ while (num--)
+ zsfree(pparams[num]);
+ zfree(pparams, (l + 1) * sizeof(char *));
+ pparams = s;
+ }
+ }
+ return ret;
+}
+
+/* getopts: automagical option handling for shell scripts */
+
+/**/
+int
+bin_getopts(char *name, char **argv, char *ops, int func)
+{
+ int lenstr, lenoptstr, quiet, lenoptbuf;
+ char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++;
+ char **args = (*argv) ? argv : pparams;
+ static int optcind = 0;
+ char *str, optbuf[2] = " ", *p, opch;
+
+ /* zoptind keeps count of the current argument number. The *
+ * user can set it to zero to start a new option parse. */
+ if (zoptind < 1) {
+ /* first call */
+ zoptind = 1;
+ optcind = 0;
+ }
+ if(zoptind > arrlen(args))
+ /* no more options */
+ return 1;
+
+ /* leading ':' in optstr means don't print an error message */
+ quiet = *optstr == ':';
+ optstr += quiet;
+ lenoptstr -= quiet;
+
+ /* find place in relevant argument */
+ str = unmetafy(dupstring(args[zoptind - 1]), &lenstr);
+ if(optcind >= lenstr) {
+ optcind = 0;
+ if(!args[zoptind++])
+ return 1;
+ str = unmetafy(dupstring(args[zoptind - 1]), &lenstr);
+ }
+ if(!optcind) {
+ if(lenstr < 2 || (*str != '-' && *str != '+'))
+ return 1;
+ if(lenstr == 2 && str[0] == '-' && str[1] == '-') {
+ zoptind++;
+ return 1;
+ }
+ optcind++;
+ }
+ opch = str[optcind++];
+ if(str[0] == '+') {
+ optbuf[0] = '+';
+ lenoptbuf = 2;
+ } else
+ lenoptbuf = 1;
+ optbuf[lenoptbuf - 1] = opch;
+
+ /* check for legality */
+ if(opch == ':' || !(p = memchr(optstr, opch, lenoptstr))) {
+ p = "?";
+err:
+ zsfree(zoptarg);
+ if(quiet) {
+ setsparam(var, ztrdup(p));
+ zoptarg = metafy(optbuf, lenoptbuf, META_DUP);
+ } else {
+ zerr(*p == '?' ? "bad option: -%c" :
+ "argument expected after -%c option", NULL, opch);
+ zoptarg=ztrdup("");
+ errflag = 0;
+ }
+ return 0;
+ }
+
+ /* check for required argument */
+ if(p[1] == ':') {
+ if(optcind == lenstr) {
+ if(!args[zoptind]) {
+ p = ":";
+ goto err;
+ }
+ p = ztrdup(args[zoptind++]);
+ } else
+ p = metafy(str+optcind, lenstr-optcind, META_DUP);
+ optcind = ztrlen(args[zoptind - 1]);
+ zsfree(zoptarg);
+ zoptarg = p;
+ } else {
+ zsfree(zoptarg);
+ zoptarg = ztrdup("");
+ }
+
+ setsparam(var, metafy(optbuf, lenoptbuf, META_DUP));
+ return 0;
+}
+
+/* break, bye, continue, exit, logout, return -- most of these take *
+ * one numeric argument, and the other (logout) is related to return. *
+ * (return is treated as a logout when in a login shell.) */
+
+/**/
+int
+bin_break(char *name, char **argv, char *ops, int func)
+{
+ int num = lastval, nump = 0;
+
+ /* handle one optional numeric argument */
+ if (*argv) {
+ num = matheval(*argv++);
+ nump = 1;
+ }
+
+ switch (func) {
+ case BIN_CONTINUE:
+ if (!loops) { /* continue is only permitted in loops */
+ zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0);
+ return 1;
+ }
+ contflag = 1; /* ARE WE SUPPOSED TO FALL THROUGH HERE? */
+ case BIN_BREAK:
+ if (!loops) { /* break is only permitted in loops */
+ zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0);
+ return 1;
+ }
+ breaks = nump ? minimum(num,loops) : 1;
+ break;
+ case BIN_RETURN:
+ if (isset(INTERACTIVE) || locallevel || sourcelevel) {
+ retflag = 1;
+ breaks = loops;
+ lastval = num;
+ if (trapreturn == -2)
+ trapreturn = lastval;
+ return lastval;
+ }
+ zexit(num, 0); /* else treat return as logout/exit */
+ break;
+ case BIN_LOGOUT:
+ if (unset(LOGINSHELL)) {
+ zerrnam(name, "not login shell", NULL, 0);
+ return 1;
+ }
+ zexit(num, 0);
+ break;
+ case BIN_EXIT:
+ zexit(num, 0);
+ break;
+ }
+ return 0;
+}
+
+/* we have printed a 'you have stopped (running) jobs.' message */
+
+/**/
+int stopmsg;
+
+/* check to see if user has jobs running/stopped */
+
+/**/
+static void
+checkjobs(void)
+{
+ int i;
+
+ for (i = 1; i < MAXJOB; i++)
+ if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) &&
+ !(jobtab[i].stat & STAT_NOPRINT))
+ break;
+ if (i < MAXJOB) {
+ if (jobtab[i].stat & STAT_STOPPED) {
+
+#ifdef USE_SUSPENDED
+ zerr("you have suspended jobs.", NULL, 0);
+#else
+ zerr("you have stopped jobs.", NULL, 0);
+#endif
+
+ } else
+ zerr("you have running jobs.", NULL, 0);
+ stopmsg = 1;
+ }
+}
+
+/* exit the shell. val is the return value of the shell. *
+ * from_signal should be non-zero if zexit is being called *
+ * because of a signal. */
+
+/**/
+void
+zexit(int val, int from_signal)
+{
+ static int in_exit;
+
+ HEAPALLOC {
+ if (isset(MONITOR) && !stopmsg && !from_signal) {
+ scanjobs(); /* check if jobs need printing */
+ checkjobs(); /* check if any jobs are running/stopped */
+ if (stopmsg) {
+ stopmsg = 2;
+ LASTALLOC_RETURN;
+ }
+ }
+ if (in_exit++ && from_signal)
+ LASTALLOC_RETURN;
+ if (isset(MONITOR))
+ /* send SIGHUP to any jobs left running */
+ killrunjobs(from_signal);
+ if (isset(RCS) && interact) {
+ if (!nohistsave)
+ savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+ if (islogin && !subsh) {
+ sourcehome(".zlogout");
+#ifdef GLOBAL_ZLOGOUT
+ source(GLOBAL_ZLOGOUT);
+#endif
+ }
+ }
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ if (mypid != getpid())
+ _exit(val);
+ else
+ exit(val);
+ } LASTALLOC;
+}
+
+/* . (dot), source */
+
+/**/
+int
+bin_dot(char *name, char **argv, char *ops, int func)
+{
+ char **old, *old0 = NULL;
+ int ret, diddot = 0, dotdot = 0;
+ char buf[PATH_MAX];
+ char *s, **t, *enam, *arg0;
+ struct stat st;
+
+ if (!*argv || strlen(*argv) >= PATH_MAX)
+ return 0;
+ old = pparams;
+ /* get arguments for the script */
+ if (argv[1]) {
+ PERMALLOC {
+ pparams = arrdup(argv + 1);
+ } LASTALLOC;
+ }
+ enam = arg0 = ztrdup(*argv);
+ if (isset(FUNCTIONARGZERO)) {
+ old0 = argzero;
+ argzero = arg0;
+ }
+ s = unmeta(enam);
+ errno = ENOENT;
+ ret = 1;
+ /* for source only, check in current directory first */
+ if (*name != '.' && access(s, F_OK) == 0
+ && stat(s, &st) >= 0 && !S_ISDIR(st.st_mode)) {
+ diddot = 1;
+ ret = source(enam);
+ }
+ if (ret) {
+ /* use a path with / in it */
+ for (s = arg0; *s; s++)
+ if (*s == '/') {
+ if (*arg0 == '.') {
+ if (arg0 + 1 == s)
+ ++diddot;
+ else if (arg0[1] == '.' && arg0 + 2 == s)
+ ++dotdot;
+ }
+ ret = source(arg0);
+ break;
+ }
+ if (!*s || (ret && isset(PATHDIRS) && diddot < 2 && dotdot == 0)) {
+ /* search path for script */
+ for (t = path; *t; t++) {
+ if (!(*t)[0] || ((*t)[0] == '.' && !(*t)[1])) {
+ if (diddot)
+ continue;
+ diddot = 1;
+ strcpy(buf, arg0);
+ } else {
+ if (strlen(*t) + strlen(arg0) + 1 >= PATH_MAX)
+ continue;
+ sprintf(buf, "%s/%s", *t, arg0);
+ }
+ s = unmeta(buf);
+ if (access(s, F_OK) == 0 && stat(s, &st) >= 0
+ && !S_ISDIR(st.st_mode)) {
+ ret = source(enam = buf);
+ break;
+ }
+ }
+ }
+ }
+ /* clean up and return */
+ if (argv[1]) {
+ freearray(pparams);
+ pparams = old;
+ }
+ if (ret)
+ zwarnnam(name, "%e: %s", enam, errno);
+ zsfree(arg0);
+ if (old0)
+ argzero = old0;
+ return ret ? ret : lastval;
+}
+
+/**/
+int
+bin_emulate(char *nam, char **argv, char *ops, int func)
+{
+ emulate(*argv, ops['R']);
+ return 0;
+}
+
+/* eval: simple evaluation */
+
+/**/
+int
+bin_eval(char *nam, char **argv, char *ops, int func)
+{
+ List list;
+
+ inpush(zjoin(argv, ' '), 0, NULL);
+ strinbeg();
+ stophist = 2;
+ list = parse_list();
+ strinend();
+ inpop();
+ if (!list) {
+ errflag = 0;
+ return 1;
+ }
+ execlist(list, 1, 0);
+ if (errflag) {
+ lastval = errflag;
+ errflag = 0;
+ }
+ return lastval;
+}
+
+static char *zbuf;
+static int readfd;
+
+/* Read a character from readfd, or from the buffer zbuf. Return EOF on end of
+file/buffer. */
+
+/* read: get a line of input, or (for compctl functions) return some *
+ * useful data about the state of the editing line. The -E and -e *
+ * options mean that the result should be sent to stdout. -e means, *
+ * in addition, that the result should not actually be assigned to *
+ * the specified parameters. */
+
+/**/
+int
+bin_read(char *name, char **args, char *ops, int func)
+{
+ char *reply, *readpmpt;
+ int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash;
+ int haso = 0; /* true if /dev/tty has been opened specially */
+ int isem = !strcmp(term, "emacs");
+ char *buf, *bptr, *firstarg, *zbuforig;
+ LinkList readll = newlinklist();
+
+ if ((ops['k'] || ops['b']) && *args && idigit(**args)) {
+ if (!(nchars = atoi(*args)))
+ nchars = 1;
+ args++;
+ }
+
+ firstarg = *args;
+ if (*args && **args == '?')
+ args++;
+ /* default result parameter */
+ reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY";
+ if (ops['A'] && *args) {
+ zwarnnam(name, "only one array argument allowed", NULL, 0);
+ return 1;
+ }
+
+ /* handle compctl case */
+ if(ops['l'] || ops['c'])
+ return compctlread(name, args, ops, reply);
+
+ if ((ops['k'] && !ops['u'] && !ops['p']) || ops['q']) {
+ if (SHTTY == -1) {
+ /* need to open /dev/tty specially */
+ SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY);
+ haso = 1;
+ }
+ /* We should have a SHTTY opened by now. */
+ if (SHTTY == -1) {
+ /* Unfortunately, we didn't. */
+ fprintf(stderr, "not interactive and can't open terminal\n");
+ fflush(stderr);
+ return 1;
+ }
+ if (unset(INTERACTIVE))
+ gettyinfo(&shttyinfo);
+ /* attach to the tty */
+ attachtty(mypgrp);
+ if (!isem && ops['k'])
+ setcbreak();
+ readfd = SHTTY;
+ } else if (ops['u'] && !ops['p']) {
+ /* -u means take input from the specified file descriptor. *
+ * -up means take input from the coprocess. */
+ for (readfd = 9; readfd && !ops[readfd + '0']; --readfd);
+ } else if (ops['p'])
+ readfd = coprocin;
+ else
+ readfd = 0;
+
+ /* handle prompt */
+ if (firstarg) {
+ for (readpmpt = firstarg;
+ *readpmpt && *readpmpt != '?'; readpmpt++);
+ if (*readpmpt++) {
+ if (isatty(0)) {
+ zputs(readpmpt, stderr);
+ fflush(stderr);
+ }
+ readpmpt[-1] = '\0';
+ }
+ }
+
+ /* option -k means read only a given number of characters (default 1) */
+ if (ops['k']) {
+ int val;
+ char d;
+
+ /* allocate buffer space for result */
+ bptr = buf = (char *)zalloc(nchars+1);
+
+ do {
+ /* If read returns 0, is end of file */
+ if ((val = read(readfd, bptr, nchars)) <= 0)
+ break;
+
+ /* decrement number of characters read from number required */
+ nchars -= val;
+
+ /* increment pointer past read characters */
+ bptr += val;
+ } while (nchars > 0);
+
+ if (!ops['u'] && !ops['p']) {
+ /* dispose of result appropriately, etc. */
+ if (isem)
+ while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n');
+ else
+ settyinfo(&shttyinfo);
+ if (haso) {
+ close(SHTTY);
+ SHTTY = -1;
+ }
+ }
+
+ if (ops['e'] || ops['E'])
+ fwrite(buf, bptr - buf, 1, stdout);
+ if (!ops['e'])
+ setsparam(reply, metafy(buf, bptr - buf, META_REALLOC));
+ else
+ zfree(buf, bptr - buf + 1);
+ return val <= 0;
+ }
+
+ /* option -q means get one character, and interpret it as a Y or N */
+ if (ops['q']) {
+ char readbuf[2];
+
+ /* set up the buffer */
+ readbuf[1] = '\0';
+
+ /* get, and store, reply */
+ readbuf[0] = ((char)getquery(NULL, 0)) == 'y' ? 'y' : 'n';
+
+ /* dispose of result appropriately, etc. */
+ if (haso) {
+ close(SHTTY);
+ SHTTY = -1;
+ }
+
+ if (ops['e'] || ops['E'])
+ printf("%s\n", readbuf);
+ if (!ops['e'])
+ setsparam(reply, ztrdup(readbuf));
+
+ return readbuf[0] == 'n';
+ }
+
+ /* All possible special types of input have been exhausted. Take one line,
+ and assign words to the parameters until they run out. Leftover words go
+ onto the last parameter. If an array is specified, all the words become
+ separate elements of the array. */
+
+ zbuforig = zbuf = (!ops['z']) ? NULL :
+ (nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup("");
+ first = 1;
+ bslash = 0;
+ while (*args || (ops['A'] && !gotnl)) {
+ buf = bptr = (char *)zalloc(bsiz = 64);
+ /* get input, a character at a time */
+ while (!gotnl) {
+ c = zread();
+ /* \ at the end of a line indicates a continuation *
+ * line, except in raw mode (-r option) */
+ if (bslash && c == '\n') {
+ bslash = 0;
+ continue;
+ }
+ if (c == EOF || c == '\n')
+ break;
+ if (!bslash && isep(c)) {
+ if (bptr != buf || (!iwsep(c) && first)) {
+ first |= !iwsep(c);
+ break;
+ }
+ first |= !iwsep(c);
+ continue;
+ }
+ bslash = c == '\\' && !bslash && !ops['r'];
+ if (bslash)
+ continue;
+ first = 0;
+ if (imeta(c)) {
+ *bptr++ = Meta;
+ *bptr++ = c ^ 32;
+ } else
+ *bptr++ = c;
+ /* increase the buffer size, if necessary */
+ if (bptr >= buf + bsiz - 1) {
+ int blen = bptr - buf;
+
+ buf = realloc(buf, bsiz *= 2);
+ bptr = buf + blen;
+ }
+ }
+ if (c == '\n' || c == EOF)
+ gotnl = 1;
+ *bptr = '\0';
+ /* dispose of word appropriately */
+ if (ops['e'] || ops['E']) {
+ zputs(buf, stdout);
+ putchar('\n');
+ }
+ if (!ops['e']) {
+ if (ops['A']) {
+ addlinknode(readll, buf);
+ al++;
+ } else
+ setsparam(reply, buf);
+ } else
+ free(buf);
+ if (!ops['A'])
+ reply = *args++;
+ }
+ /* handle EOF */
+ if (c == EOF) {
+ if (readfd == coprocin) {
+ close(coprocin);
+ close(coprocout);
+ coprocin = coprocout = -1;
+ }
+ }
+ /* final assignment (and display) of array parameter */
+ if (ops['A']) {
+ char **pp, **p = NULL;
+ LinkNode n;
+
+ p = (ops['e'] ? (char **)NULL
+ : (char **)zalloc((al + 1) * sizeof(char *)));
+
+ for (pp = p, n = firstnode(readll); n; incnode(n)) {
+ if (ops['e'] || ops['E']) {
+ zputs((char *) getdata(n), stdout);
+ putchar('\n');
+ }
+ if (p)
+ *pp++ = (char *)getdata(n);
+ else
+ zsfree(getdata(n));
+ }
+ if (p) {
+ *pp++ = NULL;
+ setaparam(reply, p);
+ }
+ return c == EOF;
+ }
+ buf = bptr = (char *)zalloc(bsiz = 64);
+ /* any remaining part of the line goes into one parameter */
+ bslash = 0;
+ if (!gotnl)
+ for (;;) {
+ c = zread();
+ /* \ at the end of a line introduces a continuation line, except in
+ raw mode (-r option) */
+ if (bslash && c == '\n') {
+ bslash = 0;
+ continue;
+ }
+ if (c == EOF || (c == '\n' && !zbuf))
+ break;
+ if (!bslash && isep(c) && bptr == buf)
+ if (iwsep(c))
+ continue;
+ else if (!first) {
+ first = 1;
+ continue;
+ }
+ bslash = c == '\\' && !bslash && !ops['r'];
+ if (bslash)
+ continue;
+ if (imeta(c)) {
+ *bptr++ = Meta;
+ *bptr++ = c ^ 32;
+ } else
+ *bptr++ = c;
+ /* increase the buffer size, if necessary */
+ if (bptr >= buf + bsiz - 1) {
+ int blen = bptr - buf;
+
+ buf = realloc(buf, bsiz *= 2);
+ bptr = buf + blen;
+ }
+ }
+ while (bptr > buf && iwsep(bptr[-1]))
+ bptr--;
+ *bptr = '\0';
+ /* final assignment of reply, etc. */
+ if (ops['e'] || ops['E']) {
+ zputs(buf, stdout);
+ putchar('\n');
+ }
+ if (!ops['e'])
+ setsparam(reply, buf);
+ else
+ zsfree(buf);
+ if (zbuforig) {
+ char first = *zbuforig;
+
+ zsfree(zbuforig);
+ if (!first)
+ return 1;
+ } else if (c == EOF) {
+ if (readfd == coprocin) {
+ close(coprocin);
+ close(coprocout);
+ coprocin = coprocout = -1;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+/**/
+static int
+zread(void)
+{
+ char cc, retry = 0;
+
+ /* use zbuf if possible */
+ if (zbuf)
+ /* If zbuf points to anything, it points to the next character in the
+ buffer. This may be a null byte to indicate EOF. If reading from the
+ buffer, move on the buffer pointer. */
+ if (*zbuf == Meta)
+ return zbuf++, STOUC(*zbuf++ ^ 32);
+ else
+ return (*zbuf) ? STOUC(*zbuf++) : EOF;
+ for (;;) {
+ /* read a character from readfd */
+ switch (read(readfd, &cc, 1)) {
+ case 1:
+ /* return the character read */
+ return STOUC(cc);
+#if defined(EAGAIN) || defined(EWOULDBLOCK)
+ case -1:
+ if (!retry && readfd == 0 && (
+# ifdef EAGAIN
+ errno == EAGAIN
+# ifdef EWOULDBLOCK
+ ||
+# endif /* EWOULDBLOCK */
+# endif /* EAGAIN */
+# ifdef EWOULDBLOCK
+ errno == EWOULDBLOCK
+# endif /* EWOULDBLOCK */
+ ) && setblock_stdin()) {
+ retry = 1;
+ continue;
+ }
+ break;
+#endif /* EAGAIN || EWOULDBLOCK */
+ }
+ return EOF;
+ }
+}
+
+/* holds arguments for testlex() */
+/**/
+char **testargs;
+
+/* test, [: the old-style general purpose logical expression builtin */
+
+/**/
+void
+testlex(void)
+{
+ if (tok == LEXERR)
+ return;
+
+ tokstr = *testargs;
+ if (!*testargs) {
+ /* if tok is already zero, reading past the end: error */
+ tok = tok ? NULLTOK : LEXERR;
+ return;
+ } else if (!strcmp(*testargs, "-o"))
+ tok = DBAR;
+ else if (!strcmp(*testargs, "-a"))
+ tok = DAMPER;
+ else if (!strcmp(*testargs, "!"))
+ tok = BANG;
+ else if (!strcmp(*testargs, "("))
+ tok = INPAR;
+ else if (!strcmp(*testargs, ")"))
+ tok = OUTPAR;
+ else
+ tok = STRING;
+ testargs++;
+}
+
+/**/
+int
+bin_test(char *name, char **argv, char *ops, int func)
+{
+ char **s;
+ Cond c;
+
+ /* if "test" was invoked as "[", it needs a matching "]" *
+ * which is subsequently ignored */
+ if (func == BIN_BRACKET) {
+ for (s = argv; *s; s++);
+ if (s == argv || strcmp(s[-1], "]")) {
+ zwarnnam(name, "']' expected", NULL, 0);
+ return 1;
+ }
+ s[-1] = NULL;
+ }
+ /* an empty argument list evaluates to false (1) */
+ if (!*argv)
+ return 1;
+
+ testargs = argv;
+ tok = NULLTOK;
+ condlex = testlex;
+ testlex();
+ c = par_cond();
+ condlex = yylex;
+
+ if (errflag) {
+ errflag = 0;
+ return 1;
+ }
+
+ if (!c || tok == LEXERR) {
+ zwarnnam(name, tokstr ? "parse error" : "argument expected", NULL, 0);
+ return 1;
+ }
+
+ /* syntax is OK, so evaluate */
+ return !evalcond(c);
+}
+
+/* display a time, provided in units of 1/60s, as minutes and seconds */
+#define pttime(X) printf("%ldm%ld.%02lds",((long) (X))/3600,\
+ ((long) (X))/60%60,((long) (X))*100/60%100)
+
+/* times: display, in a two-line format, the times provided by times(3) */
+
+/**/
+int
+bin_times(char *name, char **argv, char *ops, int func)
+{
+ struct tms buf;
+
+ /* get time accounting information */
+ if (times(&buf) == -1)
+ return 1;
+ pttime(buf.tms_utime); /* user time */
+ putchar(' ');
+ pttime(buf.tms_stime); /* system time */
+ putchar('\n');
+ pttime(buf.tms_cutime); /* user time, children */
+ putchar(' ');
+ pttime(buf.tms_cstime); /* system time, children */
+ putchar('\n');
+ return 0;
+}
+
+/* trap: set/unset signal traps */
+
+/**/
+int
+bin_trap(char *name, char **argv, char *ops, int func)
+{
+ List l;
+ char *arg, *s;
+ int sig;
+
+ if (*argv && !strcmp(*argv, "--"))
+ argv++;
+
+ /* If given no arguments, list all currently-set traps */
+ if (!*argv) {
+ for (sig = 0; sig < VSIGCOUNT; sig++) {
+ if (sigtrapped[sig] & ZSIG_FUNC) {
+ char fname[20];
+ HashNode hn;
+
+ sprintf(fname, "TRAP%s", sigs[sig]);
+ if ((hn = shfunctab->getnode(shfunctab, fname)))
+ shfunctab->printnode(hn, 0);
+ DPUTS(!hn, "BUG: I did not find any trap functions!");
+ } else if (sigtrapped[sig]) {
+ if (!sigfuncs[sig])
+ printf("trap -- '' %s\n", sigs[sig]);
+ else {
+ s = getpermtext((void *) dupstruct((void *) sigfuncs[sig]));
+ printf("trap -- ");
+ quotedzputs(s, stdout);
+ printf(" %s\n", sigs[sig]);
+ zsfree(s);
+ }
+ }
+ }
+ return 0;
+ }
+
+ /* If we have a signal number, unset the specified *
+ * signals. With only -, remove all traps. */
+ if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) {
+ if (!*argv)
+ for (sig = 0; sig < VSIGCOUNT; sig++)
+ unsettrap(sig);
+ else
+ while (*argv)
+ unsettrap(getsignum(*argv++));
+ return 0;
+ }
+
+ /* Sort out the command to execute on trap */
+ arg = *argv++;
+ if (!*arg)
+ l = NULL;
+ else if (!(l = parse_string(arg))) {
+ zwarnnam(name, "couldn't parse trap command", NULL, 0);
+ return 1;
+ }
+
+ /* set traps */
+ for (; *argv; argv++) {
+ List t;
+
+ sig = getsignum(*argv);
+ if (sig == -1) {
+ zwarnnam(name, "undefined signal: %s", *argv, 0);
+ break;
+ }
+ PERMALLOC {
+ t = (List) dupstruct(l);
+ } LASTALLOC;
+ if (settrap(sig, t))
+ freestruct(t);
+ }
+ return *argv != NULL;
+}
+
+/**/
+int
+bin_ttyctl(char *name, char **argv, char *ops, int func)
+{
+ if (ops['f'])
+ ttyfrozen = 1;
+ else if (ops['u'])
+ ttyfrozen = 0;
+ else
+ printf("tty is %sfrozen\n", ttyfrozen ? "" : "not ");
+ return 0;
+}
+
+/* let -- mathematical evaluation */
+
+/**/
+int
+bin_let(char *name, char **argv, char *ops, int func)
+{
+ long val = 0;
+
+ while (*argv)
+ val = matheval(*argv++);
+ /* Errors in math evaluation in let are non-fatal. */
+ errflag = 0;
+ return !val;
+}
+
+/* umask command. umask may be specified as octal digits, or in the *
+ * symbolic form that chmod(1) uses. Well, a subset of it. Remember *
+ * that only the bottom nine bits of umask are used, so there's no *
+ * point allowing the set{u,g}id and sticky bits to be specified. */
+
+/**/
+int
+bin_umask(char *nam, char **args, char *ops, int func)
+{
+ mode_t um;
+ char *s = *args;
+
+ /* Get the current umask. */
+ um = umask(0);
+ umask(um);
+ /* No arguments means to display the current setting. */
+ if (!s) {
+ if (ops['S']) {
+ char *who = "ugo";
+
+ while (*who) {
+ char *what = "rwx";
+ printf("%c=", *who++);
+ while (*what) {
+ if (!(um & 0400))
+ putchar(*what);
+ um <<= 1;
+ what++;
+ }
+ putchar(*who ? ',' : '\n');
+ }
+ } else {
+ if (um & 0700)
+ putchar('0');
+ printf("%03o\n", (unsigned)um);
+ }
+ return 0;
+ }
+
+ if (idigit(*s)) {
+ /* Simple digital umask. */
+ um = zstrtol(s, &s, 8);
+ if (*s) {
+ zwarnnam(nam, "bad umask", NULL, 0);
+ return 1;
+ }
+ } else {
+ /* Symbolic notation -- slightly complicated. */
+ int whomask, umaskop, mask;
+
+ /* More than one symbolic argument may be used at once, each separated
+ by commas. */
+ for (;;) {
+ /* First part of the argument -- who does this apply to?
+ u=owner, g=group, o=other. */
+ whomask = 0;
+ while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a')
+ if (*s == 'u')
+ s++, whomask |= 0700;
+ else if (*s == 'g')
+ s++, whomask |= 0070;
+ else if (*s == 'o')
+ s++, whomask |= 0007;
+ else if (*s == 'a')
+ s++, whomask |= 0777;
+ /* Default whomask is everyone. */
+ if (!whomask)
+ whomask = 0777;
+ /* Operation may be +, - or =. */
+ umaskop = (int)*s;
+ if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) {
+ if (umaskop)
+ zwarnnam(nam, "bad symbolic mode operator: %c", NULL, umaskop);
+ else
+ zwarnnam(nam, "bad umask", NULL, 0);
+ return 1;
+ }
+ /* Permissions mask -- r=read, w=write, x=execute. */
+ mask = 0;
+ while (*++s && *s != ',')
+ if (*s == 'r')
+ mask |= 0444 & whomask;
+ else if (*s == 'w')
+ mask |= 0222 & whomask;
+ else if (*s == 'x')
+ mask |= 0111 & whomask;
+ else {
+ zwarnnam(nam, "bad symbolic mode permission: %c",
+ NULL, *s);
+ return 1;
+ }
+ /* Apply parsed argument to um. */
+ if (umaskop == '+')
+ um &= ~mask;
+ else if (umaskop == '-')
+ um |= mask;
+ else /* umaskop == '=' */
+ um = (um | (whomask)) & ~mask;
+ if (*s == ',')
+ s++;
+ else
+ break;
+ }
+ if (*s) {
+ zwarnnam(nam, "bad character in symbolic mode: %c", NULL, *s);
+ return 1;
+ }
+ }
+
+ /* Finally, set the new umask. */
+ umask(um);
+ return 0;
+}
+
+/* Generic builtin for facilities not available on this OS */
+
+/**/
+int
+bin_notavail(char *nam, char **argv, char *ops, int func)
+{
+ zwarnnam(nam, "not available on this system", NULL, 0);
+ return 1;
+}
diff --git a/Src/compat.c b/Src/compat.c
new file mode 100644
index 000000000..ca9c57aac
--- /dev/null
+++ b/Src/compat.c
@@ -0,0 +1,285 @@
+/*
+ * compat.c - compatibiltiy routines for the deprived
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "compat.pro"
+
+/* Return pointer to first occurence of string t *
+ * in string s. Return NULL if not present. */
+
+#ifndef HAVE_STRSTR
+char *
+strstr(const char *s, const char *t)
+{
+ char *p1, *p2;
+
+ for (; *s; s++) {
+ for (p1 = s, p2 = t; *p2; p1++, p2++)
+ if (*p1 != *p2)
+ break;
+ if (!*p2)
+ return (char *)s;
+ }
+ return NULL;
+}
+#endif
+
+
+#ifndef HAVE_GETHOSTNAME
+int
+gethostname(char *name, size_t namelen)
+{
+ struct utsname uts;
+
+ uname(&uts);
+ if(strlen(uts.nodename) >= namelen) {
+ errno = EINVAL;
+ return -1;
+ }
+ strcpy(name, uts.nodename);
+ return 0;
+}
+#endif
+
+
+#ifndef HAVE_GETTIMEOFDAY
+int
+gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+ tv->tv_usec = 0;
+ tv->tv_sec = (long)time((time_t) 0);
+ return 0;
+}
+#endif
+
+
+/* compute the difference between two calendar times */
+
+#ifndef HAVE_DIFFTIME
+double
+difftime(time_t t2, time_t t1)
+{
+ return ((double)t2 - (double)t1);
+}
+#endif
+
+
+#ifndef HAVE_STRERROR
+extern char *sys_errlist[];
+
+/* Get error message string associated with a particular *
+ * error number, and returns a pointer to that string. *
+ * This is not a particularly robust version of strerror. */
+
+char *
+strerror(int errnum)
+{
+ return (sys_errlist[errnum]);
+}
+#endif
+
+
+/**/
+char *
+zgetdir(struct dirsav *d)
+{
+ char nbuf[PATH_MAX+3];
+ char *buf;
+ int bufsiz, pos, len;
+ struct stat sbuf;
+ struct dirent *de;
+ DIR *dir;
+ ino_t ino, pino;
+ dev_t dev, pdev;
+
+ buf = halloc(bufsiz = PATH_MAX);
+ pos = bufsiz - 1;
+ buf[pos] = '\0';
+ strcpy(nbuf, "../");
+ if (stat(".", &sbuf) < 0) {
+ if (d)
+ return NULL;
+ buf[0] = '.';
+ buf[1] = '\0';
+ return buf;
+ }
+
+ pino = sbuf.st_ino;
+ pdev = sbuf.st_dev;
+ if (d)
+ d->ino = pino, d->dev = pdev;
+#ifdef HAVE_FCHDIR
+ else
+#endif
+ holdintr();
+
+ for (;;) {
+ if (stat("..", &sbuf) < 0)
+ break;
+
+ ino = pino;
+ dev = pdev;
+ pino = sbuf.st_ino;
+ pdev = sbuf.st_dev;
+
+ if (ino == pino && dev == pdev) {
+ if (!buf[pos])
+ buf[--pos] = '/';
+ if (d) {
+#ifndef HAVE_FCHDIR
+ zchdir(buf + pos);
+ noholdintr();
+#endif
+ return d->dirname = ztrdup(buf + pos);
+ }
+ zchdir(buf + pos);
+ noholdintr();
+ return buf + pos;
+ }
+
+ if (!(dir = opendir("..")))
+ break;
+
+ while ((de = readdir(dir))) {
+ char *fn = de->d_name;
+ /* Ignore `.' and `..'. */
+ if (fn[0] == '.' &&
+ (fn[1] == '\0' ||
+ (fn[1] == '.' && fn[2] == '\0')))
+ continue;
+#ifdef HAVE_STRUCT_DIRENT_D_STAT
+ if(de->d_stat.st_dev == dev && de->d_stat.st_ino == ino) {
+ strncpy(nbuf + 3, fn, PATH_MAX);
+ break;
+ }
+#else /* !HAVE_STRUCT_DIRENT_D_STAT */
+# ifdef HAVE_STRUCT_DIRENT_D_INO
+ if (dev != pdev || (ino_t) de->d_ino == ino)
+# endif /* HAVE_STRUCT_DIRENT_D_INO */
+ {
+ strncpy(nbuf + 3, fn, PATH_MAX);
+ lstat(nbuf, &sbuf);
+ if (sbuf.st_dev == dev && sbuf.st_ino == ino)
+ break;
+ }
+#endif /* !HAVE_STRUCT_DIRENT_D_STAT */
+ }
+ closedir(dir);
+ if (!de)
+ break;
+ len = strlen(nbuf + 2);
+ pos -= len;
+ while (pos <= 1) {
+ char *newbuf = halloc(2*bufsiz);
+ memcpy(newbuf + bufsiz, buf, bufsiz);
+ buf = newbuf;
+ pos += bufsiz;
+ bufsiz *= 2;
+ }
+ memcpy(buf + pos, nbuf + 2, len);
+#ifdef HAVE_FCHDIR
+ if (d)
+ return d->dirname = ztrdup(buf + pos + 1);
+#endif
+ if (chdir(".."))
+ break;
+ }
+ if (d) {
+#ifndef HAVE_FCHDIR
+ if (*buf)
+ zchdir(buf + pos + 1);
+ noholdintr();
+#endif
+ return NULL;
+ }
+ if (*buf)
+ zchdir(buf + pos + 1);
+ noholdintr();
+ buf[0] = '.';
+ buf[1] = '\0';
+ return buf;
+}
+
+/**/
+char *
+zgetcwd(void)
+{
+ return zgetdir(NULL);
+}
+
+/* chdir with arbitrary long pathname. Returns 0 on success, 0 on normal *
+ * faliliure and -2 when chdir failed and the current directory is lost. */
+
+/**/
+int
+zchdir(char *dir)
+{
+ char *s;
+ int currdir = -2;
+
+ for (;;) {
+ if (!*dir)
+ return 0;
+ if (!chdir(dir))
+ return 0;
+ if ((errno != ENAMETOOLONG && errno != ENOMEM) ||
+ strlen(dir) < PATH_MAX)
+ break;
+ for (s = dir + PATH_MAX - 1; s > dir && *s != '/'; s--);
+ if (s == dir)
+ break;
+#ifdef HAVE_FCHDIR
+ if (currdir == -2)
+ currdir = open(".", O_RDONLY|O_NOCTTY);
+#endif
+ *s = '\0';
+ if (chdir(dir)) {
+ *s = '/';
+ break;
+ }
+#ifndef HAVE_FCHDIR
+ currdir = -1;
+#endif
+ *s = '/';
+ while (*++s == '/');
+ dir = s;
+ }
+#ifdef HAVE_FCHDIR
+ if (currdir == -1 || (currdir >= 0 && fchdir(currdir))) {
+ if (currdir >= 0)
+ close(currdir);
+ return -2;
+ }
+ if (currdir >= 0)
+ close(currdir);
+ return -1;
+#else
+ return currdir == -2 ? -1 : -2;
+#endif
+}
diff --git a/Src/cond.c b/Src/cond.c
new file mode 100644
index 000000000..79886a720
--- /dev/null
+++ b/Src/cond.c
@@ -0,0 +1,226 @@
+/*
+ * cond.c - evaluate conditional expressions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "cond.pro"
+
+/**/
+int
+evalcond(Cond c)
+{
+ struct stat *st;
+
+ switch (c->type) {
+ case COND_NOT:
+ return !evalcond(c->left);
+ case COND_AND:
+ return evalcond(c->left) && evalcond(c->right);
+ case COND_OR:
+ return evalcond(c->left) || evalcond(c->right);
+ }
+ singsub((char **)&c->left);
+ untokenize(c->left);
+ if (c->right) {
+ singsub((char **)&c->right);
+ if (c->type != COND_STREQ && c->type != COND_STRNEQ)
+ untokenize(c->right);
+ }
+ switch (c->type) {
+ case COND_STREQ:
+ return matchpat(c->left, c->right);
+ case COND_STRNEQ:
+ return !matchpat(c->left, c->right);
+ case COND_STRLT:
+ return strcmp(c->left, c->right) < 0;
+ case COND_STRGTR:
+ return strcmp(c->left, c->right) > 0;
+ case 'e':
+ case 'a':
+ return (doaccess(c->left, F_OK));
+ case 'b':
+ return (S_ISBLK(dostat(c->left)));
+ case 'c':
+ return (S_ISCHR(dostat(c->left)));
+ case 'd':
+ return (S_ISDIR(dostat(c->left)));
+ case 'f':
+ return (S_ISREG(dostat(c->left)));
+ case 'g':
+ return (!!(dostat(c->left) & S_ISGID));
+ case 'k':
+ return (!!(dostat(c->left) & S_ISVTX));
+ case 'n':
+ return (!!strlen(c->left));
+ case 'o':
+ return (optison(c->left));
+ case 'p':
+ return (S_ISFIFO(dostat(c->left)));
+ case 'r':
+ return (doaccess(c->left, R_OK));
+ case 's':
+ return ((st = getstat(c->left)) && !!(st->st_size));
+ case 'S':
+ return (S_ISSOCK(dostat(c->left)));
+ case 'u':
+ return (!!(dostat(c->left) & S_ISUID));
+ case 'w':
+ return (doaccess(c->left, W_OK));
+ case 'x':
+ if (privasserted()) {
+ mode_t mode = dostat(c->left);
+ return (mode & S_IXUGO) || S_ISDIR(mode);
+ }
+ return doaccess(c->left, X_OK);
+ case 'z':
+ return (!strlen(c->left));
+ case 'h':
+ case 'L':
+ return (S_ISLNK(dolstat(c->left)));
+ case 'O':
+ return ((st = getstat(c->left)) && st->st_uid == geteuid());
+ case 'G':
+ return ((st = getstat(c->left)) && st->st_gid == getegid());
+ case 'N':
+ return ((st = getstat(c->left)) && st->st_atime <= st->st_mtime);
+ case 't':
+ return isatty(matheval(c->left));
+ case COND_EQ:
+ return matheval(c->left) == matheval(c->right);
+ case COND_NE:
+ return matheval(c->left) != matheval(c->right);
+ case COND_LT:
+ return matheval(c->left) < matheval(c->right);
+ case COND_GT:
+ return matheval(c->left) > matheval(c->right);
+ case COND_LE:
+ return matheval(c->left) <= matheval(c->right);
+ case COND_GE:
+ return matheval(c->left) >= matheval(c->right);
+ case COND_NT:
+ case COND_OT:
+ {
+ time_t a;
+
+ if (!(st = getstat(c->left)))
+ return 0;
+ a = st->st_mtime;
+ if (!(st = getstat(c->right)))
+ return 0;
+ return (c->type == COND_NT) ? a > st->st_mtime : a < st->st_mtime;
+ }
+ case COND_EF:
+ {
+ dev_t d;
+ ino_t i;
+
+ if (!(st = getstat(c->left)))
+ return 0;
+ d = st->st_dev;
+ i = st->st_ino;
+ if (!(st = getstat(c->right)))
+ return 0;
+ return d == st->st_dev && i == st->st_ino;
+ }
+ default:
+ zerr("bad cond structure", NULL, 0);
+ }
+ return 0;
+}
+
+
+/**/
+static int
+doaccess(char *s, int c)
+{
+ return !access(unmeta(s), c);
+}
+
+
+static struct stat st;
+
+/**/
+static struct stat *
+getstat(char *s)
+{
+/* /dev/fd/n refers to the open file descriptor n. We always use fstat *
+ * in this case since on Solaris /dev/fd/n is a device special file */
+ if (!strncmp(s, "/dev/fd/", 8)) {
+ if (fstat(atoi(s + 8), &st))
+ return NULL;
+ return &st;
+ }
+
+ if (stat(unmeta(s), &st))
+ return NULL;
+ return &st;
+}
+
+
+/**/
+static mode_t
+dostat(char *s)
+{
+ struct stat *statp;
+
+ if (!(statp = getstat(s)))
+ return 0;
+ return statp->st_mode;
+}
+
+
+/* pem@aaii.oz; needed since dostat now uses "stat" */
+
+/**/
+static mode_t
+dolstat(char *s)
+{
+ if (lstat(unmeta(s), &st) < 0)
+ return 0;
+ return st.st_mode;
+}
+
+
+/**/
+static int
+optison(char *s)
+{
+ int i;
+
+ if (strlen(s) == 1)
+ i = optlookupc(*s);
+ else
+ i = optlookup(s);
+ if (!i) {
+ zerr("no such option: %s", s, 0);
+ return 0;
+ } else if(i < 0)
+ return unset(-i);
+ else
+ return isset(i);
+}
diff --git a/Src/exec.c b/Src/exec.c
new file mode 100644
index 000000000..1b355d028
--- /dev/null
+++ b/Src/exec.c
@@ -0,0 +1,2965 @@
+/*
+ * exec.c - command execution
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "exec.pro"
+
+/* used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT. */
+
+/**/
+int noerrexit;
+
+/* suppress error messages */
+
+/**/
+int noerrs;
+
+/* do not save history on exec and exit */
+
+/**/
+int nohistsave;
+
+/* error/break flag */
+
+/**/
+int errflag;
+
+/* Status of return from a trap */
+
+/**/
+int trapreturn;
+
+/* != 0 if this is a subshell */
+
+/**/
+int subsh;
+
+/* != 0 if we have a return pending */
+
+/**/
+int retflag;
+
+/**/
+long lastval2;
+
+/* The table of file descriptors. A table element is zero if the *
+ * corresponding fd is not used by the shell. It is greater than *
+ * 1 if the fd is used by a <(...) or >(...) substitution and 1 if *
+ * it is an internal file descriptor which must be closed before *
+ * executing an external command. The first ten elements of the *
+ * table is not used. A table element is set by movefd and cleard *
+ * by zclose. */
+
+/**/
+char *fdtable;
+
+/* The allocated size of fdtable */
+
+/**/
+int fdtable_size;
+
+/* The highest fd that marked with nonzero in fdtable */
+
+/**/
+int max_zsh_fd;
+
+/* input fd from the coprocess */
+
+/**/
+int coprocin;
+
+/* output fd from the coprocess */
+
+/**/
+int coprocout;
+
+/* != 0 if the line editor is active */
+
+/**/
+int zleactive;
+
+/* pid of process undergoing 'process substitution' */
+
+/**/
+pid_t cmdoutpid;
+
+/* exit status of process undergoing 'process substitution' */
+
+/**/
+int cmdoutval;
+
+/* Stack to save some variables before executing a signal handler function */
+
+/**/
+struct execstack *exstack;
+
+#define execerr() if (!forked) { lastval = 1; return; } else _exit(1)
+
+static LinkList args;
+static int doneps4;
+
+/* parse string into a list */
+
+/**/
+List
+parse_string(char *s)
+{
+ List l;
+
+ lexsave();
+ inpush(s, 0, NULL);
+ strinbeg();
+ stophist = 2;
+ l = parse_list();
+ strinend();
+ inpop();
+ lexrestore();
+ return l;
+}
+
+#ifdef HAVE_GETRLIMIT
+
+/* the resource limits for the shell and its children */
+
+/**/
+struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
+
+/**/
+int
+zsetlimit(int limnum, char *nam)
+{
+ if (limits[limnum].rlim_max != current_limits[limnum].rlim_max ||
+ limits[limnum].rlim_cur != current_limits[limnum].rlim_cur) {
+ if (setrlimit(limnum, limits + limnum)) {
+ if (nam)
+ zwarnnam(nam, "setrlimit failed: %e", NULL, errno);
+ return -1;
+ }
+ current_limits[limnum] = limits[limnum];
+ }
+ return 0;
+}
+
+/**/
+int
+setlimits(char *nam)
+{
+ int limnum;
+ int ret = 0;
+
+ for (limnum = 0; limnum < RLIM_NLIMITS; limnum++)
+ if (zsetlimit(limnum, nam))
+ ret++;
+ return ret;
+}
+
+#endif /* HAVE_GETRLIMIT */
+
+/* fork and set limits */
+
+/**/
+static pid_t
+zfork(void)
+{
+ pid_t pid;
+
+ if (thisjob >= MAXJOB - 1) {
+ zerr("job table full", NULL, 0);
+ return -1;
+ }
+ pid = fork();
+ if (pid == -1) {
+ zerr("fork failed: %e", NULL, errno);
+ return -1;
+ }
+#ifdef HAVE_GETRLIMIT
+ if (!pid)
+ /* set resource limits for the child process */
+ setlimits(NULL);
+#endif
+ return pid;
+}
+
+
+/**/
+int list_pipe = 0, simple_pline = 0;
+
+static pid_t list_pipe_pid;
+static int nowait, pline_level = 0;
+static int list_pipe_child = 0, list_pipe_job;
+static char list_pipe_text[JOBTEXTSIZE];
+
+/* execute a current shell command */
+
+/**/
+static int
+execcursh(Cmd cmd)
+{
+ if (!list_pipe)
+ deletejob(jobtab + thisjob);
+ execlist(cmd->u.list, 1, cmd->flags & CFLAG_EXEC);
+ cmd->u.list = NULL;
+ return lastval;
+}
+
+/* execve after handling $_ and #! */
+
+#define POUNDBANGLIMIT 64
+
+/**/
+static int
+zexecve(char *pth, char **argv)
+{
+ int eno;
+ static char buf[PATH_MAX * 2];
+ char **eep;
+
+ unmetafy(pth, NULL);
+ for (eep = argv; *eep; eep++)
+ if (*eep != pth)
+ unmetafy(*eep, NULL);
+ for (eep = environ; *eep; eep++)
+ if (**eep == '_' && (*eep)[1] == '=')
+ break;
+ buf[0] = '_';
+ buf[1] = '=';
+ if (*pth == '/')
+ strcpy(buf + 2, pth);
+ else
+ sprintf(buf + 2, "%s/%s", pwd, pth);
+ if (!*eep)
+ eep[1] = NULL;
+ *eep = buf;
+ execve(pth, argv, environ);
+
+ /* If the execve returns (which in general shouldn't happen), *
+ * then check for an errno equal to ENOEXEC. This errno is set *
+ * if the process file has the appropriate access permission, *
+ * but has an invalid magic number in its header. */
+ if ((eno = errno) == ENOEXEC) {
+ char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
+ int fd, ct, t0;
+
+ if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) {
+ argv0 = *argv;
+ *argv = pth;
+ ct = read(fd, execvebuf, POUNDBANGLIMIT);
+ close(fd);
+ if (ct > 0) {
+ if (execvebuf[0] == '#') {
+ if (execvebuf[1] == '!') {
+ for (t0 = 0; t0 != ct; t0++)
+ if (execvebuf[t0] == '\n')
+ execvebuf[t0] = '\0';
+ execvebuf[POUNDBANGLIMIT] = '\0';
+ for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
+ for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
+ if (*ptr) {
+ *ptr = '\0';
+ argv[-2] = ptr2;
+ argv[-1] = ptr + 1;
+ execve(ptr2, argv - 2, environ);
+ } else {
+ argv[-1] = ptr2;
+ execve(ptr2, argv - 1, environ);
+ }
+ } else {
+ argv[-1] = "sh";
+ execve("/bin/sh", argv - 1, environ);
+ }
+ } else {
+ for (t0 = 0; t0 != ct; t0++)
+ if (!execvebuf[t0])
+ break;
+ if (t0 == ct) {
+ argv[-1] = "sh";
+ execve("/bin/sh", argv - 1, environ);
+ }
+ }
+ } else
+ eno = errno;
+ *argv = argv0;
+ } else
+ eno = errno;
+ }
+ /* restore the original arguments and path but do not bother with *
+ * null characters as these cannot be passed to external commands *
+ * anyway. So the result is truncated at the first null char. */
+ pth = metafy(pth, -1, META_NOALLOC);
+ for (eep = argv; *eep; eep++)
+ if (*eep != pth)
+ (void) metafy(*eep, -1, META_NOALLOC);
+ return eno;
+}
+
+#define MAXCMDLEN (PATH_MAX*4)
+
+/* test whether we really want to believe the error number */
+
+/**/
+static int
+isgooderr(int e, char *dir)
+{
+ /*
+ * Maybe the directory was unreadable, or maybe it wasn't
+ * even a directory.
+ */
+ return ((e != EACCES || !access(dir, X_OK)) &&
+ e != ENOENT && e != ENOTDIR);
+}
+
+/* execute an external command */
+
+/**/
+void
+execute(Cmdnam not_used_yet, int dash)
+{
+ Cmdnam cn;
+ static LinkList exargs;
+ char buf[MAXCMDLEN], buf2[MAXCMDLEN];
+ char *s, *z, *arg0;
+ char **argv, **pp;
+ int eno = 0, ee;
+
+ arg0 = (char *) peekfirst(args);
+ if (isset(RESTRICTED) && strchr(arg0, '/')) {
+ zerr("%s: restricted", arg0, 0);
+ _exit(1);
+ }
+
+ /* If the parameter STTY is set in the command's environment, *
+ * we first run the stty command with the value of this *
+ * parameter as it arguments. */
+ if (!exargs && (s = zgetenv("STTY")) && isatty(0)) {
+ char *t;
+
+ exargs = args; /* this prevents infinite recursion */
+ args = NULL;
+ t = tricat("stty", " ", s);
+ execstring(t, 1, 0);
+ zsfree(t);
+ args = exargs;
+ exargs = NULL;
+ }
+
+ cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+
+ /* If ARGV0 is in the commands environment, we use *
+ * that as argv[0] for this external command */
+ if (unset(RESTRICTED) && (z = zgetenv("ARGV0"))) {
+ setdata(firstnode(args), (void *) ztrdup(z));
+ delenv(z - 6);
+ } else if (dash) {
+ /* Else if the pre-command `-' was given, we add `-' *
+ * to the front of argv[0] for this command. */
+ sprintf(buf2, "-%s", arg0);
+ setdata(firstnode(args), (void *) ztrdup(buf2));
+ }
+
+ argv = makecline(args);
+ child_unblock();
+ if ((int) strlen(arg0) >= PATH_MAX) {
+ zerr("command too long: %s", arg0, 0);
+ _exit(1);
+ }
+ for (s = arg0; *s; s++)
+ if (*s == '/') {
+ errno = zexecve(arg0, argv);
+ if (arg0 == s || unset(PATHDIRS) ||
+ (arg0[0] == '.' && (arg0 + 1 == s ||
+ (arg0[1] == '.' && arg0 + 2 == s)))) {
+ zerr("%e: %s", arg0, errno);
+ _exit(1);
+ }
+ break;
+ }
+
+ if (cn) {
+ char nn[PATH_MAX], *dptr;
+
+ if (cn->flags & HASHED)
+ strcpy(nn, cn->u.cmd);
+ else {
+ for (pp = path; pp < cn->u.name; pp++)
+ if (!**pp || (**pp == '.' && (*pp)[1] == '\0')) {
+ ee = zexecve(arg0, argv);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ } else if (**pp != '/') {
+ z = buf;
+ strucpy(&z, *pp);
+ *z++ = '/';
+ strcpy(z, arg0);
+ ee = zexecve(buf, argv);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ }
+ strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+ strcat(nn, "/");
+ strcat(nn, cn->nam);
+ }
+ ee = zexecve(nn, argv);
+
+ if ((dptr = strrchr(nn, '/')))
+ *dptr = '\0';
+ if (isgooderr(ee, *nn ? nn : "/"))
+ eno = ee;
+ }
+ for (pp = path; *pp; pp++)
+ if (!(*pp)[0] || ((*pp)[0] == '.' && !(*pp)[1])) {
+ ee = zexecve(arg0, argv);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ } else {
+ z = buf;
+ strucpy(&z, *pp);
+ *z++ = '/';
+ strcpy(z, arg0);
+ ee = zexecve(buf, argv);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ }
+ if (eno)
+ zerr("%e: %s", arg0, eno);
+ else
+ zerr("command not found: %s", arg0, 0);
+ _exit(1);
+}
+
+#define try(X) { if (iscom(X)) return ztrdup(X); }
+
+/* get the full pathname of an external command */
+
+/**/
+char *
+findcmd(char *arg0)
+{
+ char **pp;
+ char *z, *s, buf[MAXCMDLEN];
+ Cmdnam cn;
+
+ cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+ if (!cn && isset(HASHCMDS))
+ cn = hashcmd(arg0, path);
+ if ((int) strlen(arg0) > PATH_MAX)
+ return NULL;
+ for (s = arg0; *s; s++)
+ if (*s == '/') {
+ try(arg0);
+ if (arg0 == s || unset(PATHDIRS)) {
+ return NULL;
+ }
+ break;
+ }
+ if (cn) {
+ char nn[PATH_MAX];
+
+ if (cn->flags & HASHED)
+ strcpy(nn, cn->u.cmd);
+ else {
+ for (pp = path; pp < cn->u.name; pp++)
+ if (**pp != '/') {
+ z = buf;
+ if (**pp) {
+ strucpy(&z, *pp);
+ *z++ = '/';
+ }
+ strcpy(z, arg0);
+ try(buf);
+ }
+ strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+ strcat(nn, "/");
+ strcat(nn, cn->nam);
+ }
+ try(nn);
+ }
+ for (pp = path; *pp; pp++) {
+ z = buf;
+ if (**pp) {
+ strucpy(&z, *pp);
+ *z++ = '/';
+ }
+ strcpy(z, arg0);
+ try(buf);
+ }
+ return NULL;
+}
+
+/**/
+int
+iscom(char *s)
+{
+ struct stat statbuf;
+ char *us = unmeta(s);
+
+ return (access(us, X_OK) == 0 && stat(us, &statbuf) >= 0 &&
+ S_ISREG(statbuf.st_mode));
+}
+
+/**/
+int
+isreallycom(Cmdnam cn)
+{
+ char fullnam[MAXCMDLEN];
+
+ strcpy(fullnam, cn->u.name ? *(cn->u.name) : "");
+ strcat(fullnam, "/");
+ strcat(fullnam, cn->nam);
+ return iscom(fullnam);
+}
+
+/**/
+int
+isrelative(char *s)
+{
+ if (*s != '/')
+ return 1;
+ for (; *s; s++)
+ if (*s == '.' && s[-1] == '/' &&
+ (s[1] == '/' || s[1] == '\0' ||
+ (s[1] == '.' && (s[2] == '/' || s[2] == '\0'))))
+ return 1;
+ return 0;
+}
+
+/**/
+Cmdnam
+hashcmd(char *arg0, char **pp)
+{
+ Cmdnam cn;
+ char *s, buf[PATH_MAX];
+ char **pq;
+
+ for (; *pp; pp++)
+ if (**pp == '/') {
+ s = buf;
+ strucpy(&s, *pp);
+ *s++ = '/';
+ if ((s - buf) + strlen(arg0) >= PATH_MAX)
+ continue;
+ strcpy(s, arg0);
+ if (iscom(buf))
+ break;
+ }
+
+ if (!*pp)
+ return NULL;
+
+ cn = (Cmdnam) zcalloc(sizeof *cn);
+ cn->flags = 0;
+ cn->u.name = pp;
+ cmdnamtab->addnode(cmdnamtab, ztrdup(arg0), cn);
+
+ if (isset(HASHDIRS)) {
+ for (pq = pathchecked; pq <= pp; pq++)
+ hashdir(pq);
+ pathchecked = pp + 1;
+ }
+
+ return cn;
+}
+
+/* execute a string */
+
+/**/
+void
+execstring(char *s, int dont_change_job, int exiting)
+{
+ List list;
+
+ pushheap();
+ if ((list = parse_string(s)))
+ execlist(list, dont_change_job, exiting);
+ popheap();
+}
+
+/* Main routine for executing a list. *
+ * exiting means that the (sub)shell we are in is a definite goner *
+ * after the current list is finished, so we may be able to exec the *
+ * last command directly instead of forking. If dont_change_job is *
+ * nonzero, then restore the current job number after executing the *
+ * list. */
+
+/**/
+void
+execlist(List list, int dont_change_job, int exiting)
+{
+ Sublist slist;
+ static int donetrap;
+ int ret, cj;
+ int old_pline_level, old_list_pipe;
+
+ cj = thisjob;
+ old_pline_level = pline_level;
+ old_list_pipe = list_pipe;
+
+ if (sourcelevel && unset(SHINSTDIN))
+ pline_level = list_pipe = 0;
+
+ /* Loop over all sets of comands separated by newline, *
+ * semi-colon or ampersand (`sublists'). */
+ while (list && list != &dummy_list && !breaks && !retflag) {
+ /* Reset donetrap: this ensures that a trap is only *
+ * called once for each sublist that fails. */
+ donetrap = 0;
+ simplifyright(list);
+ slist = list->left;
+
+ /* Loop through code followed by &&, ||, or end of sublist. */
+ while (slist) {
+ switch (slist->type) {
+ case END:
+ /* End of sublist; just execute, ignoring status. */
+ execpline(slist, list->type, !list->right && exiting);
+ goto sublist_done;
+ break;
+ case ANDNEXT:
+ /* If the return code is non-zero, we skip pipelines until *
+ * we find a sublist followed by ORNEXT. */
+ if ((ret = execpline(slist, Z_SYNC, 0))) {
+ while ((slist = slist->right))
+ if (slist->type == ORNEXT)
+ break;
+ if (!slist) {
+ /* We've skipped to the end of the list, not executing *
+ * the final pipeline, so don't perform error handling *
+ * for this sublist. */
+ donetrap = 1;
+ goto sublist_done;
+ }
+ }
+ break;
+ case ORNEXT:
+ /* If the return code is zero, we skip pipelines until *
+ * we find a sublist followed by ANDNEXT. */
+ if (!(ret = execpline(slist, Z_SYNC, 0))) {
+ while ((slist = slist->right))
+ if (slist->type == ANDNEXT)
+ break;
+ if (!slist) {
+ /* We've skipped to the end of the list, not executing *
+ * the final pipeline, so don't perform error handling *
+ * for this sublist. */
+ donetrap = 1;
+ goto sublist_done;
+ }
+ }
+ break;
+ }
+ slist = slist->right;
+ }
+sublist_done:
+
+ if (sigtrapped[SIGDEBUG])
+ dotrap(SIGDEBUG);
+
+ /* Check whether we are suppressing traps/errexit *
+ * (typically in init scripts) and if we haven't *
+ * already performed them for this sublist. */
+ if (!noerrexit && !donetrap) {
+ if (sigtrapped[SIGZERR] && lastval) {
+ dotrap(SIGZERR);
+ donetrap = 1;
+ }
+ if (lastval && isset(ERREXIT)) {
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ if (mypid != getpid())
+ _exit(lastval);
+ else
+ exit(lastval);
+ }
+ }
+
+ list = list->right;
+ }
+
+ pline_level = old_pline_level;
+ list_pipe = old_list_pipe;
+ if (dont_change_job)
+ thisjob = cj;
+}
+
+/* Execute a pipeline. *
+ * last1 is a flag that this command is the last command in a shell *
+ * that is about to exit, so we can exec instead of forking. It gets *
+ * passed all the way down to execcmd() which actually makes the *
+ * decision. A 0 is always passed if the command is not the last in *
+ * the pipeline. This function assumes that the sublist is not NULL. *
+ * If last1 is zero but the command is at the end of a pipeline, we *
+ * pass 2 down to execcmd(). *
+ */
+
+/**/
+static int
+execpline(Sublist l, int how, int last1)
+{
+ int ipipe[2], opipe[2];
+ int pj, newjob;
+ int old_simple_pline = simple_pline;
+ static int lastwj;
+
+ if (!l->left)
+ return lastval = (l->flags & PFLAG_NOT) != 0;
+
+ pj = thisjob;
+ ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
+ child_block();
+
+ /* get free entry in job table and initialize it */
+ if ((thisjob = newjob = initjob()) == -1)
+ return 1;
+ if (how & Z_TIMED)
+ jobtab[thisjob].stat |= STAT_TIMED;
+
+ if (l->flags & PFLAG_COPROC) {
+ how = Z_ASYNC;
+ if (coprocin >= 0) {
+ zclose(coprocin);
+ zclose(coprocout);
+ }
+ mpipe(ipipe);
+ mpipe(opipe);
+ coprocin = ipipe[0];
+ coprocout = opipe[1];
+ fdtable[coprocin] = fdtable[coprocout] = 0;
+ }
+ if (!pline_level++) {
+ list_pipe_job = newjob;
+ nowait = 0;
+ }
+ list_pipe_pid = lastwj = 0;
+ if (pline_level == 1)
+ simple_pline = (l->left->type == END);
+ execpline2(l->left, how, opipe[0], ipipe[1], last1);
+ pline_level--;
+ if (how & Z_ASYNC) {
+ lastwj = newjob;
+ jobtab[thisjob].stat |= STAT_NOSTTY;
+ if (l->flags & PFLAG_COPROC)
+ zclose(ipipe[1]);
+ if (how & Z_DISOWN) {
+ deletejob(jobtab + thisjob);
+ thisjob = -1;
+ }
+ else
+ spawnjob();
+ child_unblock();
+ return 0;
+ } else {
+ if (newjob != lastwj) {
+ Job jn = jobtab + newjob;
+
+ if (newjob == list_pipe_job && list_pipe_child)
+ _exit(0);
+
+ lastwj = thisjob = newjob;
+
+ if (list_pipe)
+ jn->stat |= STAT_NOPRINT;
+
+ if (nowait) {
+ if(!pline_level) {
+ struct process *pn, *qn;
+
+ curjob = newjob;
+ addproc(list_pipe_pid, list_pipe_text);
+
+ for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
+ if (WIFSTOPPED(pn->status))
+ break;
+
+ if (pn) {
+ for (qn = jn->procs; qn->next; qn = qn->next);
+ qn->status = pn->status;
+ }
+
+ jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
+ jn->stat |= STAT_STOPPED | STAT_CHANGED;
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ }
+ else
+ deletejob(jn);
+ }
+
+ for (; !nowait;) {
+ if (list_pipe_child) {
+ jn->stat |= STAT_NOPRINT;
+ makerunning(jn);
+ }
+ if (!(jn->stat & STAT_LOCKED))
+ waitjobs();
+
+ if (list_pipe_child &&
+ jn->stat & STAT_DONE &&
+ lastval2 & 0200)
+ killpg(mypgrp, lastval2 & ~0200);
+ if ((list_pipe || last1) && !list_pipe_child &&
+ jn->stat & STAT_STOPPED) {
+ pid_t pid;
+ int synch[2];
+
+ pipe(synch);
+
+ if ((pid = fork()) == -1) {
+ trashzle();
+ close(synch[0]);
+ close(synch[1]);
+ putc('\n', stderr);
+ fprintf(stderr, "zsh: job can't be suspended\n");
+ fflush(stderr);
+ makerunning(jn);
+ killjb(jn, SIGCONT);
+ thisjob = newjob;
+ }
+ else if (pid) {
+ char dummy;
+
+ list_pipe_pid = pid;
+ nowait = errflag = 1;
+ breaks = loops;
+ close(synch[1]);
+ read(synch[0], &dummy, 1);
+ close(synch[0]);
+ jobtab[list_pipe_job].other = newjob;
+ jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
+ jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
+ jn->other = pid;
+ killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
+ break;
+ }
+ else {
+ close(synch[0]);
+ entersubsh(Z_ASYNC, 0, 0);
+ setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader);
+ close(synch[1]);
+ kill(getpid(), SIGSTOP);
+ list_pipe = 0;
+ list_pipe_child = 1;
+ break;
+ }
+ }
+ else if (subsh && jn->stat & STAT_STOPPED)
+ thisjob = newjob;
+ else
+ break;
+ }
+ child_unblock();
+
+ if (list_pipe && (lastval & 0200) && pj >= 0 &&
+ (!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
+ jn = jobtab + pj;
+ jn->stat |= STAT_NOPRINT;
+ killjb(jobtab + pj, lastval & ~0200);
+ }
+ if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE)))
+ deletejob(jn);
+ thisjob = pj;
+
+ }
+ if (l->flags & PFLAG_NOT)
+ lastval = !lastval;
+ }
+ if (!pline_level)
+ simple_pline = old_simple_pline;
+ return lastval;
+}
+
+static int subsh_close = -1;
+
+/* execute pipeline. This function assumes the `pline' is not NULL. */
+
+/**/
+static void
+execpline2(Pline pline, int how, int input, int output, int last1)
+{
+ pid_t pid;
+ int pipes[2];
+ int oldlineno;
+
+ if (breaks || retflag)
+ return;
+
+ oldlineno = lineno;
+ lineno = pline->left->lineno;
+
+ if (pline_level == 1)
+ strcpy(list_pipe_text, getjobtext((void *) pline->left));
+ if (pline->type == END) {
+ execcmd(pline->left, input, output, how, last1 ? 1 : 2);
+ pline->left = NULL;
+ } else {
+ int old_list_pipe = list_pipe;
+
+ mpipe(pipes);
+
+ /* if we are doing "foo | bar" where foo is a current *
+ * shell command, do foo in a subshell and do the *
+ * rest of the pipeline in the current shell. */
+ if (pline->left->type >= CURSH && (how & Z_SYNC)) {
+ int synch[2];
+
+ pipe(synch);
+ if ((pid = fork()) == -1) {
+ close(synch[0]);
+ close(synch[1]);
+ zerr("fork failed: %e", NULL, errno);
+ } else if (pid) {
+ char dummy, *text;
+
+ text = getjobtext((void *) pline->left);
+ addproc(pid, text);
+ close(synch[1]);
+ read(synch[0], &dummy, 1);
+ close(synch[0]);
+ } else {
+ zclose(pipes[0]);
+ close(synch[0]);
+ entersubsh(how, 2, 0);
+ close(synch[1]);
+ execcmd(pline->left, input, pipes[1], how, 0);
+ _exit(lastval);
+ }
+ } else {
+ /* otherwise just do the pipeline normally. */
+ subsh_close = pipes[0];
+ execcmd(pline->left, input, pipes[1], how, 0);
+ }
+ pline->left = NULL;
+ zclose(pipes[1]);
+ if (pline->right) {
+ /* if another execpline() is invoked because the command is *
+ * a list it must know that we're already in a pipeline */
+ list_pipe = 1;
+ execpline2(pline->right, how, pipes[0], output, last1);
+ list_pipe = old_list_pipe;
+ zclose(pipes[0]);
+ subsh_close = -1;
+ }
+ }
+
+ lineno = oldlineno;
+}
+
+/* make the argv array */
+
+/**/
+static char **
+makecline(LinkList list)
+{
+ LinkNode node;
+ char **argv, **ptr;
+
+ /* A bigger argv is necessary for executing scripts */
+ ptr =
+ argv = 2 + (char **) ncalloc((countlinknodes(list) + 4) * sizeof(char *));
+ if (isset(XTRACE)) {
+ if (!doneps4)
+ fprintf(stderr, "%s", (prompt4) ? prompt4 : "");
+
+ for (node = firstnode(list); node; incnode(node)) {
+ *ptr++ = (char *)getdata(node);
+ zputs(getdata(node), stderr);
+ if (nextnode(node))
+ fputc(' ', stderr);
+ }
+ fputc('\n', stderr);
+ fflush(stderr);
+ } else {
+ for (node = firstnode(list); node; incnode(node))
+ *ptr++ = (char *)getdata(node);
+ }
+ *ptr = NULL;
+ return (argv);
+}
+
+/**/
+void
+untokenize(char *s)
+{
+ for (; *s; s++)
+ if (itok(*s))
+ if (*s == Nularg)
+ chuck(s--);
+ else
+ *s = ztokens[*s - Pound];
+}
+
+/* Open a file for writing redicection */
+
+/**/
+static int
+clobber_open(struct redir *f)
+{
+ struct stat buf;
+ int fd, oerrno;
+
+ /* If clobbering, just open. */
+ if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type))
+ return open(unmeta(f->name),
+ O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0666);
+
+ /* If not clobbering, attempt to create file exclusively. */
+ if ((fd = open(unmeta(f->name),
+ O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0666)) >= 0)
+ return fd;
+
+ /* If that fails, we are still allowed to open non-regular files. *
+ * Try opening, and if it's a regular file then close it again *
+ * because we weren't supposed to open it. */
+ oerrno = errno;
+ if ((fd = open(unmeta(f->name), O_WRONLY | O_NOCTTY)) != -1) {
+ if(!fstat(fd, &buf) && !S_ISREG(buf.st_mode))
+ return fd;
+ close(fd);
+ }
+ errno = oerrno;
+ return -1;
+}
+
+/* size of buffer for tee and cat processes */
+#define TCBUFSIZE 4092
+
+/* close an multio (success) */
+
+/**/
+static void
+closemn(struct multio **mfds, int fd)
+{
+ struct multio *mn = mfds[fd];
+ char buf[TCBUFSIZE];
+ int len, i;
+
+ if (fd < 0 || !mfds[fd] || mfds[fd]->ct < 2)
+ return;
+ if (zfork()) {
+ for (i = 0; i < mn->ct; i++)
+ zclose(mn->fds[i]);
+ zclose(mn->pipe);
+ mn->ct = 1;
+ mn->fds[0] = fd;
+ return;
+ }
+ /* pid == 0 */
+ closeallelse(mn);
+ if (mn->rflag) {
+ /* tee process */
+ while ((len = read(mn->pipe, buf, TCBUFSIZE)) > 0)
+ for (i = 0; i < mn->ct; i++)
+ write(mn->fds[i], buf, len);
+ } else {
+ /* cat process */
+ for (i = 0; i < mn->ct; i++)
+ while ((len = read(mn->fds[i], buf, TCBUFSIZE)) > 0)
+ write(mn->pipe, buf, len);
+ }
+ _exit(0);
+}
+
+/* close all the mnodes (failure) */
+
+/**/
+static void
+closemnodes(struct multio **mfds)
+{
+ int i, j;
+
+ for (i = 0; i < 10; i++)
+ if (mfds[i]) {
+ for (j = 0; j < mfds[i]->ct; j++)
+ zclose(mfds[i]->fds[j]);
+ mfds[i] = NULL;
+ }
+}
+
+/**/
+static void
+closeallelse(struct multio *mn)
+{
+ int i, j;
+
+ for (i = 0; i < OPEN_MAX; i++)
+ if (mn->pipe != i) {
+ for (j = 0; j < mn->ct; j++)
+ if (mn->fds[j] == i)
+ break;
+ if (j == mn->ct)
+ zclose(i);
+ }
+}
+
+/* A multio is a list of fds associated with a certain fd. *
+ * Thus if you do "foo >bar >ble", the multio for fd 1 will have *
+ * two fds, the result of open("bar",...), and the result of *
+ * open("ble",....). */
+
+/* Add a fd to an multio. fd1 must be < 10, and may be in any state. *
+ * fd2 must be open, and is `consumed' by this function. Note that *
+ * fd1 == fd2 is possible, and indicates that fd1 was really closed. *
+ * We effectively do `fd2 = movefd(fd2)' at the beginning of this *
+ * function, but in most cases we can avoid an extra dup by delaying *
+ * the movefd: we only >need< to move it if we're actually doing a *
+ * multiple redirection. */
+
+/**/
+static void
+addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
+{
+ int pipes[2];
+
+ if (!mfds[fd1] || unset(MULTIOS)) {
+ if(!mfds[fd1]) { /* starting a new multio */
+ mfds[fd1] = (struct multio *) alloc(sizeof(struct multio));
+ if (!forked && save[fd1] == -2)
+ save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1);
+ }
+ redup(fd2, fd1);
+ mfds[fd1]->ct = 1;
+ mfds[fd1]->fds[0] = fd1;
+ mfds[fd1]->rflag = rflag;
+ } else {
+ if (mfds[fd1]->rflag != rflag) {
+ zerr("file mode mismatch on fd %d", NULL, fd1);
+ return;
+ }
+ if (mfds[fd1]->ct == 1) { /* split the stream */
+ mfds[fd1]->fds[0] = movefd(fd1);
+ mfds[fd1]->fds[1] = movefd(fd2);
+ mpipe(pipes);
+ mfds[fd1]->pipe = pipes[1 - rflag];
+ redup(pipes[rflag], fd1);
+ mfds[fd1]->ct = 2;
+ } else { /* add another fd to an already split stream */
+ if(!(mfds[fd1]->ct % MULTIOUNIT)) {
+ int new = sizeof(struct multio) + sizeof(int) * mfds[fd1]->ct;
+ int old = new - sizeof(int) * MULTIOUNIT;
+ mfds[fd1] = hrealloc((char *)mfds[fd1], old, new);
+ }
+ mfds[fd1]->fds[mfds[fd1]->ct++] = movefd(fd2);
+ }
+ }
+ if (subsh_close >= 0 && !fdtable[subsh_close])
+ subsh_close = -1;
+}
+
+/**/
+static void
+addvars(LinkList l, int export)
+{
+ Varasg v;
+ LinkList vl;
+ int xtr;
+ char **arr, **ptr;
+
+ xtr = isset(XTRACE);
+ if (xtr && nonempty(l)) {
+ fprintf(stderr, "%s", prompt4 ? prompt4 : "");
+ doneps4 = 1;
+ }
+
+ while (nonempty(l)) {
+ v = (Varasg) ugetnode(l);
+ singsub(&v->name);
+ if (errflag)
+ return;
+ untokenize(v->name);
+ if (xtr)
+ fprintf(stderr, "%s=", v->name);
+ if (v->type == PM_SCALAR) {
+ vl = newlinklist();
+ addlinknode(vl, v->str);
+ } else
+ vl = v->arr;
+ prefork(vl, v->type == PM_SCALAR ? 7 : 3);
+ if (errflag)
+ return;
+ if (isset(GLOBASSIGN) || v->type != PM_SCALAR)
+ globlist(vl);
+ if (errflag)
+ return;
+ if (v->type == PM_SCALAR && (empty(vl) || !nextnode(firstnode(vl)))) {
+ Param pm;
+ char *val;
+ int allexp;
+
+ if (empty(vl))
+ val = ztrdup("");
+ else {
+ untokenize(peekfirst(vl));
+ val = ztrdup(ugetnode(vl));
+ }
+ if (xtr)
+ fprintf(stderr, "%s ", val);
+ if (export) {
+ if (export < 0) {
+ /* We are going to fork so do not bother freeing this */
+ pm = (Param) paramtab->removenode(paramtab, v->name);
+ if (isset(RESTRICTED) && (pm->flags & PM_RESTRICTED)) {
+ zerr("%s: restricted", pm->nam, 0);
+ zsfree(val);
+ return;
+ }
+ }
+ allexp = opts[ALLEXPORT];
+ opts[ALLEXPORT] = 1;
+ pm = setsparam(v->name, val);
+ opts[ALLEXPORT] = allexp;
+ } else
+ pm = setsparam(v->name, val);
+ if (errflag)
+ return;
+ continue;
+ }
+ ptr = arr = (char **) zalloc(sizeof(char **) * (countlinknodes(vl) + 1));
+
+ while (nonempty(vl))
+ *ptr++ = ztrdup((char *) ugetnode(vl));
+
+ *ptr = NULL;
+ if (xtr) {
+ fprintf(stderr, "( ");
+ for (ptr = arr; *ptr; ptr++)
+ fprintf(stderr, "%s ", *ptr);
+ fprintf(stderr, ") ");
+ }
+ setaparam(v->name, arr);
+ if (errflag)
+ return;
+ }
+}
+
+/**/
+static void
+execcmd(Cmd cmd, int input, int output, int how, int last1)
+{
+ HashNode hn = NULL;
+ LinkNode node;
+ Redir fn;
+ struct multio *mfds[10];
+ char *text;
+ int save[10];
+ int fil, dfil, is_cursh, type, i;
+ int nullexec = 0, assign = 0, forked = 0;
+ int is_shfunc = 0, is_builtin = 0, is_exec = 0;
+ /* Various flags to the command. */
+ int cflags = 0, checked = 0;
+
+ doneps4 = 0;
+ args = cmd->args;
+ type = cmd->type;
+
+ for (i = 0; i < 10; i++) {
+ save[i] = -2;
+ mfds[i] = NULL;
+ }
+
+ /* If the command begins with `%', then assume it is a *
+ * reference to a job in the job table. */
+ if (type == SIMPLE && nonempty(args) &&
+ *(char *)peekfirst(args) == '%') {
+ pushnode(args, dupstring((how & Z_DISOWN)
+ ? "disown" : (how & Z_ASYNC) ? "bg" : "fg"));
+ how = Z_SYNC;
+ }
+
+ /* If AUTORESUME is set, the command is SIMPLE, and doesn't have *
+ * any redirections, then check if it matches as a prefix of a *
+ * job currently in the job table. If it does, then we treat it *
+ * as a command to resume this job. */
+ if (isset(AUTORESUME) && type == SIMPLE && (how & Z_SYNC) &&
+ nonempty(args) && empty(cmd->redir) && !input &&
+ !nextnode(firstnode(args))) {
+ if (unset(NOTIFY))
+ scanjobs();
+ if (findjobnam(peekfirst(args)) != -1)
+ pushnode(args, dupstring("fg"));
+ }
+
+ /* Check if it's a builtin needing automatic MAGIC_EQUALS_SUBST *
+ * handling. Things like typeset need this. We can't detect the *
+ * command if it contains some tokens (e.g. x=ex; ${x}port), so this *
+ * only works in simple cases. has_token() is called to make sure *
+ * this really is a simple case. */
+ if (type == SIMPLE) {
+ while (nonempty(args)) {
+ char *cmdarg = (char *) peekfirst(args);
+ checked = !has_token(cmdarg);
+ if (!checked)
+ break;
+ if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
+ (hn = shfunctab->getnode(shfunctab, cmdarg))) {
+ is_shfunc = 1;
+ break;
+ }
+ if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
+ checked = !(cflags & BINF_BUILTIN);
+ break;
+ }
+ if (!(hn->flags & BINF_PREFIX)) {
+ is_builtin = 1;
+#ifdef DYNAMIC
+ /* autoload the builtin if necessary */
+ if (!((Builtin) hn)->handlerfunc) {
+ load_module(((Builtin) hn)->optstr);
+ hn = builtintab->getnode(builtintab, cmdarg);
+ }
+#endif
+ assign = (hn->flags & BINF_MAGICEQUALS);
+ break;
+ }
+ cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+ cflags |= hn->flags;
+ uremnode(args, firstnode(args));
+ hn = NULL;
+ checked = 0;
+ if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
+ break;
+ }
+ }
+
+ /* Do prefork substitutions */
+ prefork(args, assign ? 2 : isset(MAGICEQUALSUBST));
+
+ if (type == SIMPLE) {
+ int unglobbed = 0;
+
+ for (;;) {
+ char *cmdarg;
+
+ if (!(cflags & BINF_NOGLOB))
+ while (!checked && !errflag && nonempty(args) &&
+ has_token((char *) peekfirst(args)))
+ glob(args, firstnode(args));
+ else if (!unglobbed) {
+ for (node = firstnode(args); node; incnode(node))
+ untokenize((char *) getdata(node));
+ unglobbed = 1;
+ }
+
+ /* Current shell should not fork unless the *
+ * exec occurs at the end of a pipeline. */
+ if ((cflags & BINF_EXEC) && last1 == 2)
+ cmd->flags |= CFLAG_EXEC;
+
+ /* Empty command */
+ if (empty(args)) {
+ if (nonempty(cmd->redir)) {
+ if (cmd->flags & CFLAG_EXEC) {
+ /* Was this "exec < foobar"? */
+ nullexec = 1;
+ break;
+ } else if (!nullcmd || !*nullcmd ||
+ (cflags & BINF_PREFIX)) {
+ zerr("redirection with no command", NULL, 0);
+ errflag = lastval = 1;
+ return;
+ } else if (readnullcmd && *readnullcmd &&
+ ((Redir) peekfirst(cmd->redir))->type == READ &&
+ !nextnode(firstnode(cmd->redir))) {
+ addlinknode(args, dupstring(readnullcmd));
+ } else
+ addlinknode(args, dupstring(nullcmd));
+ } else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
+ lastval = 0;
+ return;
+ } else {
+ cmdoutval = 0;
+ addvars(cmd->vars, 0);
+ if (errflag)
+ lastval = errflag;
+ else
+ lastval = cmdoutval;
+ if (isset(XTRACE)) {
+ fputc('\n', stderr);
+ fflush(stderr);
+ }
+ return;
+ }
+ } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) &&
+ (cmd->flags & CFLAG_EXEC)) {
+ zerrnam("exec", "%s: restricted", (char *) getdata(firstnode(args)), 0);
+ lastval = 1;
+ return;
+ }
+
+ if (errflag || checked ||
+ (unset(POSIXBUILTINS) && (cflags & BINF_COMMAND)))
+ break;
+
+ cmdarg = (char *) peekfirst(args);
+ if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
+ (hn = shfunctab->getnode(shfunctab, cmdarg))) {
+ is_shfunc = 1;
+ break;
+ }
+ if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
+ if (cflags & BINF_BUILTIN) {
+ zerr("no such builtin: %s", cmdarg, 0);
+ errflag = lastval = 1;
+ return;
+ }
+ break;
+ }
+ if (!(hn->flags & BINF_PREFIX)) {
+ is_builtin = 1;
+#ifdef DYNAMIC
+ /* autoload the builtin if necessary */
+ if (!((Builtin) hn)->handlerfunc)
+ load_module(((Builtin) hn)->optstr);
+#endif
+ break;
+ }
+ cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+ cflags |= hn->flags;
+ uremnode(args, firstnode(args));
+ hn = NULL;
+ }
+ }
+
+ if (errflag) {
+ lastval = 1;
+ return;
+ }
+
+ /* Get the text associated with this command. */
+ if (jobbing || (how & Z_TIMED))
+ text = getjobtext((void *) cmd);
+ else
+ text = NULL;
+
+ /* Set up special parameter $_ */
+ zsfree(underscore);
+ if (nonempty(args)
+ && (underscore = ztrdup((char *) getdata(lastnode(args)))))
+ untokenize(underscore);
+ else
+ underscore = ztrdup("");
+
+ /* Warn about "rm *" */
+ if (type == SIMPLE && interact && unset(RMSTARSILENT)
+ && isset(SHINSTDIN) && nonempty(args) && nextnode(firstnode(args))
+ && !strcmp(peekfirst(args), "rm")) {
+ LinkNode node, next;
+
+ for (node = nextnode(firstnode(args)); node && !errflag; node = next) {
+ char *s = (char *) getdata(node);
+ int l = strlen(s);
+
+ next = nextnode(node);
+ if (s[0] == Star && !s[1]) {
+ if (!checkrmall(pwd))
+ uremnode(args, node);
+ } else if (l > 2 && s[l - 2] == '/' && s[l - 1] == Star) {
+ char t = s[l - 2];
+
+ s[l - 2] = 0;
+ if (!checkrmall(s))
+ uremnode(args, node);
+ s[l - 2] = t;
+ }
+ }
+ if (!nextnode(firstnode(args)))
+ errflag = 1;
+ }
+
+ if (errflag) {
+ lastval = 1;
+ return;
+ }
+
+ if (type == SIMPLE && !nullexec) {
+ char *s;
+ char trycd = (isset(AUTOCD) && isset(SHINSTDIN)
+ && empty(cmd->redir) && !empty(args)
+ && !nextnode(firstnode(args))
+ && *(char *)peekfirst(args));
+
+ DPUTS(empty(args), "BUG: empty(args) in exec.c");
+ if (!hn) {
+ /* Resolve external commands */
+ char *cmdarg = (char *) peekfirst(args);
+
+ hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
+ if (hn && trycd && !isreallycom((Cmdnam)hn)) {
+ cmdnamtab->removenode(cmdnamtab, cmdarg);
+ cmdnamtab->freenode(hn);
+ hn = NULL;
+ }
+ if (!hn && isset(HASHCMDS) && strcmp(cmdarg, "..")) {
+ for (s = cmdarg; *s && *s != '/'; s++);
+ if (!*s)
+ hn = (HashNode) hashcmd(cmdarg, pathchecked);
+ }
+ }
+
+ /* If no command found yet, see if it *
+ * is a directory we should AUTOCD to. */
+ if (!hn && trycd && (s = cancd(peekfirst(args)))) {
+ peekfirst(args) = (void *) s;
+ pushnode(args, dupstring("cd"));
+ if ((hn = builtintab->getnode(builtintab, "cd")))
+ is_builtin = 1;
+ }
+ }
+
+ /* This is nonzero if the command is a current shell procedure? */
+ is_cursh = (is_builtin || is_shfunc || (type >= CURSH) || nullexec);
+
+ /**************************************************************************
+ * Do we need to fork? We need to fork if: *
+ * 1) The command is supposed to run in the background. (or) *
+ * 2) There is no `exec' flag, and either: *
+ * a) This is a builtin or shell function with output piped somewhere. *
+ * b) This is an external command and we can't do a `fake exec'. *
+ * *
+ * A `fake exec' is possible if we have all the following conditions: *
+ * 1) last1 flag is 1. This indicates that the current shell will not *
+ * be needed after the current command. This is typically the case *
+ * when when the command is the last stage in a subshell, or is the *
+ * last command after the option `-c'. *
+ * 2) We are not trapping EXIT or ZERR. *
+ * 3) We don't have any files to delete. *
+ * *
+ * The condition above for a `fake exec' will also work for a current *
+ * shell command such as a builtin, but doesn't really buy us anything *
+ * (doesn't save us a process), since it is already running in the *
+ * current shell. *
+ **************************************************************************/
+
+ if ((how & Z_ASYNC) || (!(cmd->flags & CFLAG_EXEC) &&
+ (((is_builtin || is_shfunc) && output) ||
+ (!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] ||
+ sigtrapped[SIGEXIT] || havefiles()))))) {
+
+ pid_t pid;
+ int synch[2];
+ char dummy;
+
+ child_block();
+ pipe(synch);
+
+ if ((pid = zfork()) == -1) {
+ close(synch[0]);
+ close(synch[1]);
+ return;
+ } if (pid) {
+ close(synch[1]);
+ read(synch[0], &dummy, 1);
+ close(synch[0]);
+#ifdef PATH_DEV_FD
+ closem(2);
+#endif
+ if (how & Z_ASYNC) {
+ lastpid = (long) pid;
+ } else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) {
+ /* search for STTY=... */
+ while (nonempty(cmd->vars))
+ if (!strcmp(((Varasg) ugetnode(cmd->vars))->name, "STTY")) {
+ jobtab[thisjob].stty_in_env = 1;
+ break;
+ }
+ }
+ addproc(pid, text);
+ return;
+ }
+ /* pid == 0 */
+ close(synch[0]);
+ entersubsh(how, type != SUBSH && !(how & Z_ASYNC) ? 2 : 1, 0);
+ close(synch[1]);
+ forked = 1;
+ if (sigtrapped[SIGINT] & ZSIG_IGNORED)
+ holdintr();
+#ifdef HAVE_NICE
+ /* Check if we should run background jobs at a lower priority. */
+ if ((how & Z_ASYNC) && isset(BGNICE))
+ nice(5);
+#endif /* HAVE_NICE */
+
+ } else if (is_cursh) {
+ /* This is a current shell procedure that didn't need to fork. *
+ * This includes current shell procedures that are being exec'ed, *
+ * as well as null execs. */
+ jobtab[thisjob].stat |= STAT_CURSH;
+ } else {
+ /* This is an exec (real or fake) for an external command. *
+ * Note that any form of exec means that the subshell is fake *
+ * (but we may be in a subshell already). */
+ is_exec = 1;
+ }
+
+ if (!(cflags & BINF_NOGLOB))
+ globlist(args);
+ if (errflag) {
+ lastval = 1;
+ goto err;
+ }
+
+ /* Add pipeline input/output to mnodes */
+ if (input)
+ addfd(forked, save, mfds, 0, input, 0);
+ if (output)
+ addfd(forked, save, mfds, 1, output, 1);
+
+ /* Do process substitutions */
+ spawnpipes(cmd->redir);
+
+ /* Do io redirections */
+ while (nonempty(cmd->redir)) {
+ fn = (Redir) ugetnode(cmd->redir);
+ DPUTS(fn->type == HEREDOC || fn->type == HEREDOCDASH,
+ "BUG: unexpanded here document");
+ if (fn->type == INPIPE) {
+ if (fn->fd2 == -1) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 0);
+ } else if (fn->type == OUTPIPE) {
+ if (fn->fd2 == -1) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 1);
+ } else {
+ if (fn->type != HERESTR && xpandredir(fn, cmd->redir))
+ continue;
+ if (errflag) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) {
+ zerr("writing redirection not allowed in restricted mode", NULL, 0);
+ execerr();
+ }
+ if (unset(EXECOPT))
+ continue;
+ switch(fn->type) {
+ case HERESTR:
+ fil = getherestr(fn);
+ if (fil == -1) {
+ closemnodes(mfds);
+ fixfds(save);
+ if (errno != EINTR)
+ zerr("%e", NULL, errno);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 0);
+ break;
+ case READ:
+ case READWRITE:
+ if (fn->type == READ)
+ fil = open(unmeta(fn->name), O_RDONLY | O_NOCTTY);
+ else
+ fil = open(unmeta(fn->name),
+ O_RDWR | O_CREAT | O_NOCTTY, 0666);
+ if (fil == -1) {
+ closemnodes(mfds);
+ fixfds(save);
+ if (errno != EINTR)
+ zerr("%e: %s", fn->name, errno);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 0);
+ /* If this is 'exec < file', read from stdin, *
+ * not terminal, unless `file' is a terminal. */
+ if (nullexec && fn->fd1 == 0 && isset(SHINSTDIN) && interact)
+ init_io();
+ break;
+ case CLOSE:
+ if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2)
+ save[fn->fd1] = movefd(fn->fd1);
+ closemn(mfds, fn->fd1);
+ zclose(fn->fd1);
+ break;
+ case MERGEIN:
+ case MERGEOUT:
+ if(fn->fd2 < 10)
+ closemn(mfds, fn->fd2);
+ fil = dup(fn->fd2);
+ if (fil == -1) {
+ char fdstr[4];
+
+ closemnodes(mfds);
+ fixfds(save);
+ sprintf(fdstr, "%d", fn->fd2);
+ zerr("%s: %e", fdstr, errno);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, fn->type == MERGEOUT);
+ break;
+ default:
+ if (IS_APPEND_REDIR(fn->type))
+ fil = open(unmeta(fn->name),
+ (unset(CLOBBER) && !IS_CLOBBER_REDIR(fn->type)) ?
+ O_WRONLY | O_APPEND | O_NOCTTY :
+ O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0666);
+ else
+ fil = clobber_open(fn);
+ if(fil != -1 && IS_ERROR_REDIR(fn->type))
+ dfil = dup(fil);
+ else
+ dfil = 0;
+ if (fil == -1 || dfil == -1) {
+ if(fil != -1)
+ close(fil);
+ closemnodes(mfds);
+ fixfds(save);
+ if (errno != EINTR)
+ zerr("%e: %s", fn->name, errno);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 1);
+ if(IS_ERROR_REDIR(fn->type))
+ addfd(forked, save, mfds, 2, dfil, 1);
+ break;
+ }
+ }
+ }
+
+ /* We are done with redirection. close the mnodes, *
+ * spawning tee/cat processes as necessary. */
+ for (i = 0; i < 10; i++)
+ closemn(mfds, i);
+
+ if (nullexec) {
+ for (i = 0; i < 10; i++)
+ if (save[i] != -2)
+ zclose(save[i]);
+ /*
+ * Here we specifically *don't* restore the original fd's
+ * before returning.
+ */
+ return;
+ }
+
+ if (isset(EXECOPT) && !errflag) {
+ /*
+ * We delay the entersubsh() to here when we are exec'ing
+ * the current shell (including a fake exec to run a builtin then
+ * exit) in case there is an error return.
+ */
+ if (is_exec)
+ entersubsh(how, type != SUBSH ? 2 : 1, 1);
+ if (type >= CURSH) {
+ static int (*func[]) _((Cmd)) = {
+ execcursh, exectime, execfuncdef, execfor, execwhile,
+ execrepeat, execif, execcase, execselect, execcond,
+ execarith, execautofn
+ };
+
+ if (last1 == 1)
+ cmd->flags |= CFLAG_EXEC;
+ lastval = (func[type - CURSH]) (cmd);
+ } else if (is_builtin || is_shfunc) {
+ LinkList restorelist = 0, removelist = 0;
+ /* builtin or shell function */
+
+ if (!forked && ((cflags & BINF_COMMAND) ||
+ (unset(POSIXBUILTINS) && !assign) ||
+ (isset(POSIXBUILTINS) && !is_shfunc &&
+ !(hn->flags & BINF_PSPECIAL))))
+ save_params(cmd, &restorelist, &removelist);
+
+ if (cmd->vars) {
+ /* Export this if the command is a shell function,
+ * but not if it's a builtin.
+ */
+ addvars(cmd->vars, is_shfunc);
+ if (errflag) {
+ restore_params(restorelist, removelist);
+ lastval = 1;
+ fixfds(save);
+ return;
+ }
+ }
+
+ if (is_shfunc) {
+ /* It's a shell function */
+#ifdef PATH_DEV_FD
+ int i;
+
+ for (i = 10; i <= max_zsh_fd; i++)
+ if (fdtable[i] > 1)
+ fdtable[i]++;
+#endif
+ if (subsh_close >= 0)
+ zclose(subsh_close);
+ subsh_close = -1;
+ execshfunc(cmd, (Shfunc) hn);
+#ifdef PATH_DEV_FD
+ for (i = 10; i <= max_zsh_fd; i++)
+ if (fdtable[i] > 1)
+ if (--(fdtable[i]) <= 2)
+ zclose(i);
+#endif
+ } else {
+ /* It's a builtin */
+ if (forked)
+ closem(1);
+ lastval = execbuiltin(args, (Builtin) hn);
+#ifdef PATH_DEV_FD
+ closem(2);
+#endif
+ if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && lastval && !subsh) {
+ fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
+ }
+ fflush(stdout);
+ if (save[1] == -2) {
+ if (ferror(stdout)) {
+ zerr("write error: %e", NULL, errno);
+ clearerr(stdout);
+ errflag = 0;
+ }
+ } else
+ clearerr(stdout);
+ }
+
+ if (cmd->flags & CFLAG_EXEC) {
+ if (subsh)
+ _exit(lastval);
+
+ /* If we are exec'ing a command, and we are not in a subshell, *
+ * then check if we should save the history file. */
+ if (isset(RCS) && interact && !nohistsave)
+ savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+ exit(lastval);
+ }
+
+ restore_params(restorelist, removelist);
+
+ } else {
+ if (cmd->flags & CFLAG_EXEC) {
+ setiparam("SHLVL", --shlvl);
+ /* If we are exec'ing a command, and we are not *
+ * in a subshell, then save the history file. */
+ if (!subsh && isset(RCS) && interact && !nohistsave)
+ savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
+ }
+ if (type == SIMPLE) {
+ if (cmd->vars) {
+ addvars(cmd->vars, -1);
+ if (errflag)
+ _exit(1);
+ }
+ closem(1);
+ if (coprocin)
+ zclose(coprocin);
+ if (coprocout)
+ zclose(coprocout);
+#ifdef HAVE_GETRLIMIT
+ if (!forked)
+ setlimits(NULL);
+#endif
+ execute((Cmdnam) hn, cflags & BINF_DASH);
+ } else { /* ( ... ) */
+ DPUTS(cmd->vars && nonempty(cmd->vars),
+ "BUG: assigment before complex command");
+ list_pipe = 0;
+ if (subsh_close >= 0)
+ zclose(subsh_close);
+ subsh_close = -1;
+ /* If we're forked (and we should be), no need to return */
+ DPUTS(last1 != 1 && !forked, "BUG: not exiting?");
+ execlist(cmd->u.list, 0, 1);
+ }
+ }
+ }
+
+ err:
+ if (forked)
+ _exit(lastval);
+ fixfds(save);
+}
+
+/* Arrange to have variables restored. */
+
+/**/
+static void
+save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p)
+{
+ Param pm;
+ LinkNode node;
+ char *s;
+
+ MUSTUSEHEAP("save_params()");
+
+ *restore_p = newlinklist();
+ *remove_p = newlinklist();
+
+ for (node = firstnode(cmd->vars); node; incnode(node)) {
+ s = ((Varasg) getdata(node))->name;
+ if ((pm = (Param) paramtab->getnode(paramtab, s))) {
+ if (!(pm->flags & PM_SPECIAL)) {
+ paramtab->removenode(paramtab, s);
+ } else if (!(pm->flags & PM_READONLY) &&
+ (unset(RESTRICTED) || !(pm->flags & PM_RESTRICTED))) {
+ Param tpm = (Param) alloc(sizeof *tpm);
+
+ tpm->nam = s;
+ tpm->flags = pm->flags;
+ switch (PM_TYPE(pm->flags)) {
+ case PM_SCALAR:
+ tpm->u.str = ztrdup(pm->gets.cfn(pm));
+ break;
+ case PM_INTEGER:
+ tpm->u.val = pm->gets.ifn(pm);
+ break;
+ case PM_ARRAY:
+ PERMALLOC {
+ tpm->u.arr = arrdup(pm->gets.afn(pm));
+ } LASTALLOC;
+ break;
+ }
+ pm = tpm;
+ }
+ addlinknode(*remove_p, s);
+ addlinknode(*restore_p, pm);
+ } else {
+ addlinknode(*remove_p, s);
+ }
+ }
+}
+
+/* Restore saved parameters after executing a shfunc or builtin */
+
+/**/
+static void
+restore_params(LinkList restorelist, LinkList removelist)
+{
+ Param pm;
+ char *s;
+
+ if (removelist) {
+ /* remove temporary parameters */
+ while ((s = (char *) ugetnode(removelist))) {
+ if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
+ !(pm->flags & PM_SPECIAL)) {
+ pm->flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 0);
+ }
+ }
+ }
+
+ if (restorelist) {
+ /* restore saved parameters */
+ while ((pm = (Param) ugetnode(restorelist))) {
+ if (pm->flags & PM_SPECIAL) {
+ Param tpm = (Param) paramtab->getnode(paramtab, pm->nam);
+
+ DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
+ !(pm->flags & PM_SPECIAL),
+ "BUG: in restoring special parameters");
+ tpm->flags = pm->flags;
+ switch (PM_TYPE(pm->flags)) {
+ case PM_SCALAR:
+ tpm->sets.cfn(tpm, pm->u.str);
+ break;
+ case PM_INTEGER:
+ tpm->sets.ifn(tpm, pm->u.val);
+ break;
+ case PM_ARRAY:
+ tpm->sets.afn(tpm, pm->u.arr);
+ break;
+ }
+ } else
+ paramtab->addnode(paramtab, pm->nam, pm);
+ if (pm->flags & PM_EXPORTED)
+ pm->env = addenv(pm->nam, getsparam(pm->nam));
+ }
+ }
+}
+
+/* restore fds after redirecting a builtin */
+
+/**/
+static void
+fixfds(int *save)
+{
+ int old_errno = errno;
+ int i;
+
+ for (i = 0; i != 10; i++)
+ if (save[i] != -2)
+ redup(save[i], i);
+ errno = old_errno;
+}
+
+/**/
+static void
+entersubsh(int how, int cl, int fake)
+{
+ int sig;
+
+ if (cl != 2)
+ for (sig = 0; sig < VSIGCOUNT; sig++)
+ if (!(sigtrapped[sig] & ZSIG_FUNC))
+ unsettrap(sig);
+ if (unset(MONITOR)) {
+ if (how & Z_ASYNC) {
+ settrap(SIGINT, NULL);
+ settrap(SIGQUIT, NULL);
+ if (isatty(0)) {
+ close(0);
+ if (open("/dev/null", O_RDWR | O_NOCTTY)) {
+ zerr("can't open /dev/null: %e", NULL, errno);
+ _exit(1);
+ }
+ }
+ }
+ } else if (thisjob != -1 && cl) {
+ if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
+ if (kill(jobtab[list_pipe_job].gleader, 0) == -1 ||
+ setpgrp(0L, jobtab[list_pipe_job].gleader) == -1) {
+ jobtab[list_pipe_job].gleader =
+ jobtab[thisjob].gleader = mypgrp;
+ setpgrp(0L, mypgrp);
+
+ if (how & Z_SYNC)
+ attachtty(jobtab[thisjob].gleader);
+ }
+ }
+ else if (!jobtab[thisjob].gleader ||
+ (setpgrp(0L, jobtab[thisjob].gleader) == -1)) {
+ jobtab[thisjob].gleader = getpid();
+ if (list_pipe_job != thisjob &&
+ !jobtab[list_pipe_job].gleader)
+ jobtab[list_pipe_job].gleader = jobtab[thisjob].gleader;
+ setpgrp(0L, jobtab[thisjob].gleader);
+ if (how & Z_SYNC)
+ attachtty(jobtab[thisjob].gleader);
+ }
+ }
+ if (!fake)
+ subsh = 1;
+ if (SHTTY != -1) {
+ zclose(SHTTY);
+ SHTTY = -1;
+ }
+ if (isset(MONITOR)) {
+ signal_default(SIGTTOU);
+ signal_default(SIGTTIN);
+ signal_default(SIGTSTP);
+ }
+ if (interact) {
+ signal_default(SIGTERM);
+ if (!(sigtrapped[SIGINT] & ZSIG_IGNORED))
+ signal_default(SIGINT);
+ }
+ if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
+ signal_default(SIGQUIT);
+ opts[MONITOR] = opts[USEZLE] = 0;
+ zleactive = 0;
+ if (cl)
+ clearjobtab();
+ times(&shtms);
+}
+
+/* close internal shell fds */
+
+/**/
+void
+closem(int how)
+{
+ int i;
+
+ for (i = 10; i <= max_zsh_fd; i++)
+ if (fdtable[i] && (!how || fdtable[i] == how))
+ zclose(i);
+}
+
+/* convert here document into a here string */
+
+/**/
+char *
+gethere(char *str, int typ)
+{
+ char *buf;
+ int bsiz, qt = 0, strip = 0;
+ char *s, *t, *bptr, c;
+
+ for (s = str; *s; s++)
+ if (INULL(*s)) {
+ *s = Nularg;
+ qt = 1;
+ }
+ untokenize(str);
+ if (typ == HEREDOCDASH) {
+ strip = 1;
+ while (*str == '\t')
+ str++;
+ }
+ bptr = buf = zalloc(bsiz = 256);
+ for (;;) {
+ t = bptr;
+
+ while ((c = hgetc()) == '\t' && strip)
+ ;
+ for (;;) {
+ if (bptr == buf + bsiz) {
+ buf = realloc(buf, 2 * bsiz);
+ t = buf + bsiz - (bptr - t);
+ bptr = buf + bsiz;
+ bsiz *= 2;
+ }
+ if (lexstop || c == '\n')
+ break;
+ *bptr++ = c;
+ c = hgetc();
+ }
+ *bptr = '\0';
+ if (!strcmp(t, str))
+ break;
+ if (lexstop) {
+ t = bptr;
+ break;
+ }
+ *bptr++ = '\n';
+ }
+ if (t > buf && t[-1] == '\n')
+ t--;
+ *t = '\0';
+ if (!qt)
+ parsestr(buf);
+ s = dupstring(buf);
+ zfree(buf, bsiz);
+ return s;
+}
+
+/* open here string fd */
+
+/**/
+static int
+getherestr(struct redir *fn)
+{
+ char *s, *t;
+ int fd, len;
+
+ t = fn->name;
+ singsub(&t);
+ untokenize(t);
+ unmetafy(t, &len);
+ t[len++] = '\n';
+ s = gettempname();
+ if (!s || (fd = open(s, O_CREAT|O_WRONLY|O_EXCL|O_NOCTTY, 0600)) == -1)
+ return -1;
+ write(fd, t, len);
+ close(fd);
+ fd = open(s, O_RDONLY | O_NOCTTY);
+ unlink(s);
+ return fd;
+}
+
+/* $(...) */
+
+/**/
+LinkList
+getoutput(char *cmd, int qt)
+{
+ List list;
+ int pipes[2];
+ pid_t pid;
+ Cmd c;
+ Redir r;
+
+ if (!(list = parse_string(cmd)))
+ return NULL;
+ if (list != &dummy_list && !list->right && !list->left->flags &&
+ list->left->type == END && list->left->left->type == END &&
+ (c = list->left->left->left)->type == SIMPLE && empty(c->args) &&
+ empty(c->vars) && nonempty(c->redir) &&
+ !nextnode(firstnode(c->redir)) &&
+ (r = (Redir) getdata(firstnode(c->redir)))->fd1 == 0 &&
+ r->type == READ) {
+ /* $(< word) */
+ int stream;
+ char *s = r->name;
+
+ singsub(&s);
+ if (errflag)
+ return NULL;
+ untokenize(s);
+ if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) {
+ zerr("%e: %s", s, errno);
+ return NULL;
+ }
+ return readoutput(stream, qt);
+ }
+
+ mpipe(pipes);
+ child_block();
+ cmdoutval = 0;
+ if ((cmdoutpid = pid = zfork()) == -1) {
+ /* fork error */
+ zclose(pipes[0]);
+ zclose(pipes[1]);
+ errflag = 1;
+ cmdoutpid = 0;
+ child_unblock();
+ return NULL;
+ } else if (pid) {
+ LinkList retval;
+
+ zclose(pipes[1]);
+ retval = readoutput(pipes[0], qt);
+ fdtable[pipes[0]] = 0;
+ child_suspend(0); /* unblocks */
+ lastval = cmdoutval;
+ return retval;
+ }
+
+ /* pid == 0 */
+ child_unblock();
+ zclose(pipes[0]);
+ redup(pipes[1], 1);
+ opts[MONITOR] = 0;
+ entersubsh(Z_SYNC, 1, 0);
+ execlist(list, 0, 1);
+ close(1);
+ _exit(lastval);
+ zerr("exit returned in child!!", NULL, 0);
+ kill(getpid(), SIGKILL);
+ return NULL;
+}
+
+/* read output of command substitution */
+
+/**/
+static LinkList
+readoutput(int in, int qt)
+{
+ LinkList ret;
+ char *buf, *ptr;
+ int bsiz, c, cnt = 0;
+ FILE *fin;
+
+ fin = fdopen(in, "r");
+ ret = newlinklist();
+ ptr = buf = (char *) ncalloc(bsiz = 64);
+ while ((c = fgetc(fin)) != EOF || errno == EINTR) {
+ if (c == EOF) {
+ errno = 0;
+ clearerr(fin);
+ continue;
+ }
+ if (imeta(c)) {
+ *ptr++ = Meta;
+ c ^= 32;
+ cnt++;
+ }
+ if (++cnt >= bsiz) {
+ char *pp = (char *) ncalloc(bsiz *= 2);
+
+ memcpy(pp, buf, cnt - 1);
+ ptr = (buf = pp) + cnt - 1;
+ }
+ *ptr++ = c;
+ }
+ fclose(fin);
+ while (cnt && ptr[-1] == '\n')
+ ptr--, cnt--;
+ *ptr = '\0';
+ if (qt) {
+ if (!cnt) {
+ *ptr++ = Nularg;
+ *ptr = '\0';
+ }
+ addlinknode(ret, buf);
+ } else {
+ char **words = spacesplit(buf, 0);
+
+ while (*words) {
+ if (isset(GLOBSUBST))
+ tokenize(*words);
+ addlinknode(ret, *words++);
+ }
+ }
+ return ret;
+}
+
+/**/
+static List
+parsecmd(char *cmd)
+{
+ char *str;
+ List list;
+
+ for (str = cmd + 2; *str && *str != Outpar; str++);
+ if (!*str || cmd[1] != Inpar) {
+ zerr("oops.", NULL, 0);
+ return NULL;
+ }
+ *str = '\0';
+ if (str[1] || !(list = parse_string(cmd + 2))) {
+ zerr("parse error in process substitution", NULL, 0);
+ return NULL;
+ }
+ return list;
+}
+
+/* =(...) */
+
+/**/
+char *
+getoutputfile(char *cmd)
+{
+ pid_t pid;
+ char *nam;
+ List list;
+ int fd;
+
+ if (thisjob == -1)
+ return NULL;
+ if (!(list = parsecmd(cmd)))
+ return NULL;
+ if (!(nam = gettempname()))
+ return NULL;
+
+ nam = ztrdup(nam);
+ PERMALLOC {
+ if (!jobtab[thisjob].filelist)
+ jobtab[thisjob].filelist = newlinklist();
+ addlinknode(jobtab[thisjob].filelist, nam);
+ } LASTALLOC;
+ child_block();
+ fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600);
+
+ if (fd < 0 || (cmdoutpid = pid = zfork()) == -1) {
+ /* fork or open error */
+ child_unblock();
+ return nam;
+ } else if (pid) {
+ int os;
+
+ close(fd);
+ os = jobtab[thisjob].stat;
+ waitforpid(pid);
+ cmdoutval = 0;
+ jobtab[thisjob].stat = os;
+ return nam;
+ }
+
+ /* pid == 0 */
+ redup(fd, 1);
+ opts[MONITOR] = 0;
+ entersubsh(Z_SYNC, 1, 0);
+ execlist(list, 0, 1);
+ close(1);
+ _exit(lastval);
+ zerr("exit returned in child!!", NULL, 0);
+ kill(getpid(), SIGKILL);
+ return NULL;
+}
+
+#if !defined(PATH_DEV_FD) && defined(HAVE_FIFOS)
+/* get a temporary named pipe */
+
+static char *
+namedpipe(void)
+{
+ char *tnam = gettempname();
+
+# ifdef HAVE_MKFIFO
+ if (mkfifo(tnam, 0600) < 0)
+# else
+ if (mknod(tnam, 0010600, 0) < 0)
+# endif
+ return NULL;
+ return tnam;
+}
+#endif /* ! PATH_DEV_FD && HAVE_FIFOS */
+
+/* <(...) or >(...) */
+
+/**/
+char *
+getproc(char *cmd)
+{
+#if !defined(HAVE_FIFOS) && !defined(PATH_DEV_FD)
+ zerr("doesn't look like your system supports FIFOs.", NULL, 0);
+ return NULL;
+#else
+ List list;
+ int out = *cmd == Inang;
+ char *pnam;
+#ifndef PATH_DEV_FD
+ int fd;
+#else
+ int pipes[2];
+#endif
+
+ if (thisjob == -1)
+ return NULL;
+#ifndef PATH_DEV_FD
+ if (!(pnam = namedpipe()))
+ return NULL;
+#else
+ pnam = ncalloc(strlen(PATH_DEV_FD) + 6);
+#endif
+ if (!(list = parsecmd(cmd)))
+ return NULL;
+#ifndef PATH_DEV_FD
+ PERMALLOC {
+ if (!jobtab[thisjob].filelist)
+ jobtab[thisjob].filelist = newlinklist();
+ addlinknode(jobtab[thisjob].filelist, ztrdup(pnam));
+ } LASTALLOC;
+ if (zfork()) {
+#else
+ mpipe(pipes);
+ if (zfork()) {
+ sprintf(pnam, "%s/%d", PATH_DEV_FD, pipes[!out]);
+ zclose(pipes[out]);
+ fdtable[pipes[!out]] = 2;
+#endif
+ return pnam;
+ }
+#ifndef PATH_DEV_FD
+ closem(0);
+ fd = open(pnam, out ? O_WRONLY | O_NOCTTY : O_RDONLY | O_NOCTTY);
+ if (fd == -1) {
+ zerr("can't open %s: %e", pnam, errno);
+ _exit(1);
+ }
+ entersubsh(Z_ASYNC, 1, 0);
+ redup(fd, out);
+#else
+ entersubsh(Z_ASYNC, 1, 0);
+ redup(pipes[out], out);
+ closem(0); /* this closes pipes[!out] as well */
+#endif
+ execlist(list, 0, 1);
+ zclose(out);
+ _exit(lastval);
+ return NULL;
+#endif /* HAVE_FIFOS and PATH_DEV_FD not defined */
+}
+
+/* > >(...) or < <(...) (does not use named pipes) */
+
+/**/
+static int
+getpipe(char *cmd)
+{
+ List list;
+ int pipes[2], out = *cmd == Inang;
+
+ if (!(list = parsecmd(cmd)))
+ return -1;
+ mpipe(pipes);
+ if (zfork()) {
+ zclose(pipes[out]);
+ return pipes[!out];
+ }
+ entersubsh(Z_ASYNC, 1, 0);
+ redup(pipes[out], out);
+ closem(0); /* this closes pipes[!out] as well */
+ execlist(list, 0, 1);
+ _exit(lastval);
+ return 0;
+}
+
+/* open pipes with fds >= 10 */
+
+/**/
+static void
+mpipe(int *pp)
+{
+ pipe(pp);
+ pp[0] = movefd(pp[0]);
+ pp[1] = movefd(pp[1]);
+}
+
+/* Do process substitution with redirection */
+
+/**/
+static void
+spawnpipes(LinkList l)
+{
+ LinkNode n;
+ Redir f;
+ char *str;
+
+ n = firstnode(l);
+ for (; n; incnode(n)) {
+ f = (Redir) getdata(n);
+ if (f->type == OUTPIPE || f->type == INPIPE) {
+ str = f->name;
+ f->fd2 = getpipe(str);
+ }
+ }
+}
+
+/* evaluate a [[ ... ]] */
+
+/**/
+static int
+execcond(Cmd cmd)
+{
+ return !evalcond(cmd->u.cond);
+}
+
+/* evaluate a ((...)) arithmetic command */
+
+/**/
+static int
+execarith(Cmd cmd)
+{
+ char *e;
+ long val = 0;
+
+ while ((e = (char *) ugetnode(cmd->args)))
+ val = matheval(e);
+ errflag = 0;
+ return !val;
+}
+
+/* perform time ... command */
+
+/**/
+static int
+exectime(Cmd cmd)
+{
+ int jb;
+
+ jb = thisjob;
+ if (!cmd->u.pline) {
+ shelltime();
+ return 0;
+ }
+ execpline(cmd->u.pline, Z_TIMED|Z_SYNC, 0);
+ thisjob = jb;
+ return lastval;
+}
+
+/* Define a shell function */
+
+/**/
+static int
+execfuncdef(Cmd cmd)
+{
+ Shfunc shf;
+ char *s;
+ int signum;
+
+ PERMALLOC {
+ while ((s = (char *) ugetnode(cmd->args))) {
+ shf = (Shfunc) zalloc(sizeof *shf);
+ shf->funcdef = (List) dupstruct(cmd->u.list);
+ shf->flags = 0;
+
+ /* is this shell function a signal trap? */
+ if (!strncmp(s, "TRAP", 4) && (signum = getsignum(s + 4)) != -1) {
+ if (settrap(signum, shf->funcdef)) {
+ freestruct(shf->funcdef);
+ zfree(shf, sizeof *shf);
+ LASTALLOC_RETURN 1;
+ }
+ sigtrapped[signum] |= ZSIG_FUNC;
+ }
+ shfunctab->addnode(shfunctab, ztrdup(s), shf);
+ }
+ } LASTALLOC;
+ if(isset(HISTNOFUNCTIONS))
+ remhist();
+ return 0;
+}
+
+/* Main entry point to execute a shell function. */
+
+/**/
+static void
+execshfunc(Cmd cmd, Shfunc shf)
+{
+ LinkList last_file_list = NULL;
+
+ if (errflag)
+ return;
+
+ if (!list_pipe) {
+ /* Without this deletejob the process table *
+ * would be filled by a recursive function. */
+ last_file_list = jobtab[thisjob].filelist;
+ jobtab[thisjob].filelist = NULL;
+ deletejob(jobtab + thisjob);
+ }
+
+ doshfunc(shf->funcdef, cmd->args, shf->flags, 0);
+
+ if (!list_pipe)
+ deletefilelist(last_file_list);
+}
+
+/* Function to execute the special type of command that represents an *
+ * autoloaded shell function. The command structure tells us which *
+ * function it is. This function is actually called as part of the *
+ * execution of the autoloaded function itself, so when the function *
+ * has been autoloaded, its list is just run with no frills. */
+
+/**/
+static int
+execautofn(Cmd cmd)
+{
+ Shfunc shf = cmd->u.autofn->shf;
+ List l = getfpfunc(shf->nam);
+ if(l == &dummy_list) {
+ zerr("%s: function definition file not found", shf->nam, 0);
+ return 1;
+ }
+ if(isset(KSHAUTOLOAD)) {
+ VARARR(char, n, strlen(shf->nam) + 1);
+ strcpy(n, shf->nam);
+ execlist(l, 1, 0);
+ shf = (Shfunc) shfunctab->getnode(shfunctab, n);
+ if(!shf || (shf->flags & PM_UNDEFINED)) {
+ zerr("%s: function not defined by file", n, 0);
+ return 1;
+ }
+ } else {
+ freestruct(shf->funcdef);
+ PERMALLOC {
+ shf->funcdef = dupstruct(stripkshdef(l, shf->nam));
+ } LASTALLOC;
+ shf->flags &= ~PM_UNDEFINED;
+ }
+ HEAPALLOC {
+ execlist(dupstruct(shf->funcdef), 1, 0);
+ } LASTALLOC;
+ return lastval;
+}
+
+/* execute a shell function */
+
+/**/
+void
+doshfunc(List list, LinkList doshargs, int flags, int noreturnval)
+/* If noreturnval is nonzero, then reset the current return *
+ * value (lastval) to its value before the shell function *
+ * was executed. */
+{
+ char **tab, **x, *oargv0 = NULL;
+ int xexittr, newexittr, oldzoptind, oldlastval;
+ char *ou;
+ void *xexitfn, *newexitfn;
+ char saveopts[OPT_SIZE];
+ int obreaks = breaks;
+
+ HEAPALLOC {
+ pushheap();
+ if (trapreturn < 0)
+ trapreturn--;
+ oldlastval = lastval;
+ xexittr = sigtrapped[SIGEXIT];
+ if (xexittr & ZSIG_FUNC)
+ xexitfn = shfunctab->removenode(shfunctab, "TRAPEXIT");
+ else
+ xexitfn = sigfuncs[SIGEXIT];
+ sigtrapped[SIGEXIT] = 0;
+ sigfuncs[SIGEXIT] = NULL;
+ tab = pparams;
+ oldzoptind = zoptind;
+ zoptind = 1;
+
+ /* We need to save the current options even if LOCALOPTIONS is *
+ * not currently set. That's because if it gets set in the *
+ * function we need to restore the original options on exit. */
+ memcpy(saveopts, opts, sizeof(opts));
+
+ if (flags & PM_TAGGED)
+ opts[XTRACE] = 1;
+ opts[PRINTEXITVALUE] = 0;
+ if (doshargs) {
+ LinkNode node;
+
+ node = doshargs->first;
+ pparams = x = (char **) zcalloc(((sizeof *x) * (1 + countlinknodes(doshargs))));
+ if (isset(FUNCTIONARGZERO)) {
+ oargv0 = argzero;
+ argzero = ztrdup((char *) node->dat);
+ }
+ node = node->next;
+ for (; node; node = node->next, x++)
+ *x = ztrdup((char *) node->dat);
+ } else {
+ pparams = (char **) zcalloc(sizeof *pparams);
+ if (isset(FUNCTIONARGZERO)) {
+ oargv0 = argzero;
+ argzero = ztrdup(argzero);
+ }
+ }
+ startparamscope();
+ ou = underscore;
+ underscore = ztrdup(underscore);
+ execlist(dupstruct(list), 1, 0);
+ zsfree(underscore);
+ underscore = ou;
+ endparamscope();
+
+ if (retflag) {
+ retflag = 0;
+ breaks = obreaks;
+ }
+ freearray(pparams);
+ if (oargv0) {
+ zsfree(argzero);
+ argzero = oargv0;
+ }
+ zoptind = oldzoptind;
+ pparams = tab;
+
+ if (isset(LOCALOPTIONS)) {
+ /* restore all shell options except PRIVILEGED and RESTRICTED */
+ saveopts[PRIVILEGED] = opts[PRIVILEGED];
+ saveopts[RESTRICTED] = opts[RESTRICTED];
+ memcpy(opts, saveopts, sizeof(opts));
+ } else {
+ /* just restore a couple. */
+ opts[XTRACE] = saveopts[XTRACE];
+ opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
+ opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
+ }
+
+ /*
+ * The trap '...' EXIT runs in the environment of the caller,
+ * so remember it here but run it after resetting the
+ * traps for the parent.
+ */
+ newexittr = sigtrapped[SIGEXIT];
+ newexitfn = sigfuncs[SIGEXIT];
+ if (newexittr & ZSIG_FUNC)
+ shfunctab->removenode(shfunctab, "TRAPEXIT");
+
+ sigtrapped[SIGEXIT] = xexittr;
+ if (xexittr & ZSIG_FUNC) {
+ shfunctab->addnode(shfunctab, ztrdup("TRAPEXIT"), xexitfn);
+ sigfuncs[SIGEXIT] = ((Shfunc) xexitfn)->funcdef;
+ } else
+ sigfuncs[SIGEXIT] = (List) xexitfn;
+
+ if (newexitfn) {
+ dotrapargs(SIGEXIT, &newexittr, newexitfn);
+ freestruct(newexitfn);
+ }
+
+ if (trapreturn < -1)
+ trapreturn++;
+ if (noreturnval)
+ lastval = oldlastval;
+ popheap();
+ } LASTALLOC;
+}
+
+/* Search fpath for an undefined function. Finds the file, and returns the *
+ * list of its contents. */
+
+/**/
+static List
+getfpfunc(char *s)
+{
+ char **pp, buf[PATH_MAX];
+ off_t len;
+ char *d;
+ List r;
+ int fd;
+
+ pp = fpath;
+ for (; *pp; pp++) {
+ if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX)
+ continue;
+ if (**pp)
+ sprintf(buf, "%s/%s", *pp, s);
+ else
+ strcpy(buf, s);
+ unmetafy(buf, NULL);
+ if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
+ if ((len = lseek(fd, 0, 2)) != -1) {
+ lseek(fd, 0, 0);
+ d = (char *) zcalloc(len + 1);
+ if (read(fd, d, len) == len) {
+ close(fd);
+ d = metafy(d, len, META_REALLOC);
+ HEAPALLOC {
+ r = parse_string(d);
+ } LASTALLOC;
+ zfree(d, len + 1);
+ return r;
+ } else {
+ zfree(d, len + 1);
+ close(fd);
+ }
+ } else {
+ close(fd);
+ }
+ }
+ }
+ return &dummy_list;
+}
+
+/* Handle the most common type of ksh-style autoloading, when doing a *
+ * zsh-style autoload. Given the list read from an autoload file, and the *
+ * name of the function being defined, check to see if the file consists *
+ * entirely of a single definition for that function. If so, use the *
+ * contents of that definition. Otherwise, use the entire file. */
+
+/**/
+static List
+stripkshdef(List l, char *name)
+{
+ Sublist s;
+ Pline p;
+ Cmd c;
+ if(!l)
+ return NULL;
+ if(l->type != Z_SYNC || l->right)
+ return l;
+ s = l->left;
+ if(s->flags || s->right)
+ return l;
+ p = s->left;
+ if(p->right)
+ return l;
+ c = p->left;
+ if(c->type != FUNCDEF || c->flags ||
+ nonempty(c->redir) || nonempty(c->vars) ||
+ empty(c->args) || lastnode(c->args) != firstnode(c->args) ||
+ strcmp(name, peekfirst(c->args)))
+ return l;
+ return c->u.list;
+}
+
+/* check to see if AUTOCD applies here */
+
+/**/
+static char *
+cancd(char *s)
+{
+ int nocdpath = s[0] == '.' &&
+ (s[1] == '/' || !s[1] || (s[1] == '.' && (s[2] == '/' || !s[1])));
+ char *t;
+
+ if (*s != '/') {
+ char sbuf[PATH_MAX], **cp;
+
+ if (cancd2(s))
+ return s;
+ if (access(unmeta(s), X_OK) == 0)
+ return NULL;
+ if (!nocdpath)
+ for (cp = cdpath; *cp; cp++) {
+ if (strlen(*cp) + strlen(s) + 1 >= PATH_MAX)
+ continue;
+ if (**cp)
+ sprintf(sbuf, "%s/%s", *cp, s);
+ else
+ strcpy(sbuf, s);
+ if (cancd2(sbuf)) {
+ doprintdir = -1;
+ return dupstring(sbuf);
+ }
+ }
+ if ((t = cd_able_vars(s))) {
+ if (cancd2(t)) {
+ doprintdir = -1;
+ return t;
+ }
+ }
+ return NULL;
+ }
+ return cancd2(s) ? s : NULL;
+}
+
+/**/
+static int
+cancd2(char *s)
+{
+ struct stat buf;
+ char *us = unmeta(s);
+
+ return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+}
+
+/**/
+void
+execsave(void)
+{
+ struct execstack *es;
+
+ es = (struct execstack *) malloc(sizeof(struct execstack));
+ es->args = args;
+ es->list_pipe_pid = list_pipe_pid;
+ es->nowait = nowait;
+ es->pline_level = pline_level;
+ es->list_pipe_child = list_pipe_child;
+ es->list_pipe_job = list_pipe_job;
+ strcpy(es->list_pipe_text, list_pipe_text);
+ es->lastval = lastval;
+ es->noeval = noeval;
+ es->badcshglob = badcshglob;
+ es->cmdoutpid = cmdoutpid;
+ es->cmdoutval = cmdoutval;
+ es->trapreturn = trapreturn;
+ es->noerrs = noerrs;
+ es->subsh_close = subsh_close;
+ es->underscore = underscore;
+ underscore = ztrdup(underscore);
+ es->next = exstack;
+ exstack = es;
+ noerrs = cmdoutpid = 0;
+}
+
+/**/
+void
+execrestore(void)
+{
+ struct execstack *en;
+
+ DPUTS(!exstack, "BUG: execrestore() without execsave()");
+ args = exstack->args;
+ list_pipe_pid = exstack->list_pipe_pid;
+ nowait = exstack->nowait;
+ pline_level = exstack->pline_level;
+ list_pipe_child = exstack->list_pipe_child;
+ list_pipe_job = exstack->list_pipe_job;
+ strcpy(list_pipe_text, exstack->list_pipe_text);
+ lastval = exstack->lastval;
+ noeval = exstack->noeval;
+ badcshglob = exstack->badcshglob;
+ cmdoutpid = exstack->cmdoutpid;
+ cmdoutval = exstack->cmdoutval;
+ trapreturn = exstack->trapreturn;
+ noerrs = exstack->noerrs;
+ subsh_close = exstack->subsh_close;
+ zsfree(underscore);
+ underscore = exstack->underscore;
+ en = exstack->next;
+ free(exstack);
+ exstack = en;
+}
diff --git a/Src/glob.c b/Src/glob.c
new file mode 100644
index 000000000..be7a04515
--- /dev/null
+++ b/Src/glob.c
@@ -0,0 +1,2800 @@
+/*
+ * glob.c - filename generation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "glob.pro"
+
+/* flag for CSHNULLGLOB */
+
+/**/
+int badcshglob;
+
+static int mode; /* != 0 if we are parsing glob patterns */
+static int pathpos; /* position in pathbuf */
+static int matchsz; /* size of matchbuf */
+static int matchct; /* number of matches found */
+static char *pathbuf; /* pathname buffer */
+static int pathbufsz; /* size of pathbuf */
+static int pathbufcwd; /* where did we chdir()'ed */
+static char **matchbuf; /* array of matches */
+static char **matchptr; /* &matchbuf[matchct] */
+static char *colonmod; /* colon modifiers in qualifier list */
+
+typedef struct stat *Statptr; /* This makes the Ultrix compiler happy. Go figure. */
+
+/* modifier for unit conversions */
+
+#define TT_DAYS 0
+#define TT_HOURS 1
+#define TT_MINS 2
+#define TT_WEEKS 3
+#define TT_MONTHS 4
+
+#define TT_BYTES 0
+#define TT_POSIX_BLOCKS 1
+#define TT_KILOBYTES 2
+#define TT_MEGABYTES 3
+
+typedef int (*TestMatchFunc) _((struct stat *, long));
+
+struct qual {
+ struct qual *next; /* Next qualifier, must match */
+ struct qual *or; /* Alternative set of qualifiers to match */
+ TestMatchFunc func; /* Function to call to test match */
+ long data; /* Argument passed to function */
+ int sense; /* Whether asserting or negating */
+ int amc; /* Flag for which time to test (a, m, c) */
+ int range; /* Whether to test <, > or = (as per signum) */
+ int units; /* Multiplier for time or size, respectively */
+};
+
+/* Qualifiers pertaining to current pattern */
+static struct qual *quals;
+
+/* Other state values for current pattern */
+static int qualct, qualorct;
+static int range, amc, units;
+static int gf_nullglob, gf_markdirs, gf_noglobdots, gf_listtypes, gf_follow;
+
+/* Prefix, suffix for doing zle trickery */
+
+/**/
+char *glob_pre, *glob_suf;
+
+/* pathname component in filename patterns */
+
+struct complist {
+ Complist next;
+ Comp comp;
+ int closure; /* 1 if this is a (foo/)# */
+ int follow; /* 1 to go thru symlinks */
+};
+struct comp {
+ Comp left, right, next, exclude;
+ char *str;
+ int stat;
+};
+
+/* Type of Comp: a closure with one or two #'s, the end of a *
+ * pattern or path component, a piece of path to be added. */
+#define C_ONEHASH 1
+#define C_TWOHASH 2
+#define C_OPTIONAL 4
+#define C_STAR 8
+#define C_CLOSURE (C_ONEHASH|C_TWOHASH|C_OPTIONAL|C_STAR)
+#define C_LAST 16
+#define C_PATHADD 32
+
+/* Test macros for the above */
+#define CLOSUREP(c) (c->stat & C_CLOSURE)
+#define ONEHASHP(c) (c->stat & (C_ONEHASH|C_STAR))
+#define TWOHASHP(c) (c->stat & C_TWOHASH)
+#define OPTIONALP(c) (c->stat & C_OPTIONAL)
+#define STARP(c) (c->stat & C_STAR)
+#define LASTP(c) (c->stat & C_LAST)
+#define PATHADDP(c) (c->stat & C_PATHADD)
+
+/* Flags passed down to guts when compiling */
+#define GF_PATHADD 1 /* file glob, adding path components */
+#define GF_TOPLEV 2 /* outside (), so ~ ends main match */
+
+static char *pptr; /* current place in string being matched */
+static Comp tail;
+static int first; /* are leading dots special? */
+
+/* Add a component to pathbuf: This keeps track of how *
+ * far we are into a file name, since each path component *
+ * must be matched separately. */
+
+/**/
+static void
+addpath(char *s)
+{
+ DPUTS(!pathbuf, "BUG: pathbuf not initialised");
+ while (pathpos + (int) strlen(s) + 1 >= pathbufsz)
+ pathbuf = realloc(pathbuf, pathbufsz *= 2);
+ while ((pathbuf[pathpos++] = *s++));
+ pathbuf[pathpos - 1] = '/';
+ pathbuf[pathpos] = '\0';
+}
+
+/* stat the filename s appended to pathbuf. l should be true for lstat, *
+ * false for stat. If st is NULL, the file is only chechked for existance. *
+ * s == "" is treated as s == ".". This is necessary since on most systems *
+ * foo/ can be used to reference a non-directory foo. Returns nonzero if *
+ * the file does not exists. */
+
+/**/
+static int
+statfullpath(const char *s, struct stat *st, int l)
+{
+ char buf[PATH_MAX];
+
+ DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX,
+ "BUG: statfullpath(): pathname too long");
+ strcpy(buf, pathbuf + pathbufcwd);
+ strcpy(buf + pathpos - pathbufcwd, s);
+ if (!*s) {
+ buf[pathpos - pathbufcwd] = '.';
+ buf[pathpos - pathbufcwd + 1] = '\0';
+ l = 0;
+ }
+ unmetafy(buf, NULL);
+ if (!st)
+ return access(buf, F_OK) && (!l || readlink(buf, NULL, 0));
+ return l ? lstat(buf, st) : stat(buf, st);
+}
+
+/* add a match to the list */
+
+/**/
+static void
+insert(char *s, int checked)
+{
+ struct stat buf, buf2, *bp;
+ char *news = s;
+ int statted = 0;
+
+ if (gf_listtypes || gf_markdirs) {
+ /* Add the type marker to the end of the filename */
+ mode_t mode;
+ checked = statted = 1;
+ if (statfullpath(s, &buf, 1))
+ return;
+ mode = buf.st_mode;
+ if (gf_follow) {
+ if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0))
+ memcpy(&buf2, &buf, sizeof(buf));
+ statted = 2;
+ mode = buf2.st_mode;
+ }
+ if (gf_listtypes || S_ISDIR(mode)) {
+ int ll = strlen(s);
+
+ news = (char *)ncalloc(ll + 2);
+ strcpy(news, s);
+ news[ll] = file_type(mode);
+ news[ll + 1] = '\0';
+ }
+ }
+ if (qualct || qualorct) {
+ /* Go through the qualifiers, rejecting the file if appropriate */
+ struct qual *qo, *qn;
+
+ if (!statted && statfullpath(s, &buf, 1))
+ return;
+ qo = quals;
+ for (qn = qo; qn && qn->func;) {
+ range = qn->range;
+ amc = qn->amc;
+ units = qn->units;
+ if ((qn->sense & 2) && statted != 2) {
+ /* If (sense & 2), we're following links */
+ if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
+ memcpy(&buf2, &buf, sizeof(buf));
+ statted = 2;
+ }
+ bp = (qn->sense & 2) ? &buf2 : &buf;
+ /* Reject the file if the function returned zero *
+ * and the sense was positive (sense&1 == 0), or *
+ * vice versa. */
+ if ((!((qn->func) (bp, qn->data)) ^ qn->sense) & 1) {
+ /* Try next alternative, or return if there are no more */
+ if (!(qo = qo->or))
+ return;
+ qn = qo;
+ continue;
+ }
+ qn = qn->next;
+ }
+ } else if (!checked && statfullpath(s, NULL, 1))
+ return;
+
+ news = dyncat(pathbuf, news);
+ if (colonmod) {
+ /* Handle the remainder of the qualifer: e.g. (:r:s/foo/bar/). */
+ s = colonmod;
+ modify(&news, &s);
+ }
+ *matchptr++ = news;
+ if (++matchct == matchsz) {
+ matchbuf = (char **)realloc((char *)matchbuf,
+ sizeof(char **) * (matchsz *= 2));
+
+ matchptr = matchbuf + matchct;
+ }
+}
+
+/* Check to see if str is eligible for filename generation. */
+
+/**/
+int
+haswilds(char *str)
+{
+ /* `[' and `]' are legal even if bad patterns are usually not. */
+ if ((*str == Inbrack || *str == Outbrack) && !str[1])
+ return 0;
+
+ /* If % is immediately followed by ?, then that ? is *
+ * not treated as a wildcard. This is so you don't have *
+ * to escape job references such as %?foo. */
+ if (str[0] == '%' && str[1] == Quest)
+ str[1] = '?';
+
+ for (; *str; str++) {
+ switch (*str) {
+ case Inpar:
+ case Bar:
+ case Star:
+ case Inbrack:
+ case Inang:
+ case Quest:
+ return 1;
+ case Pound:
+ case Hat:
+ if (isset(EXTENDEDGLOB))
+ return 1;
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Do the globbing: scanner is called recursively *
+ * with successive bits of the path until we've *
+ * tried all of it. */
+
+/**/
+static void
+scanner(Complist q)
+{
+ Comp c;
+ int closure;
+ int pbcwdsav = pathbufcwd;
+ struct dirsav ds;
+
+ ds.ino = ds.dev = 0;
+ ds.dirname = NULL;
+ ds.dirfd = ds.level = -1;
+ if (!q)
+ return;
+
+ if ((closure = q->closure)) /* (foo/)# - match zero or more dirs */
+ if (q->closure == 2) /* (foo/)## - match one or more dirs */
+ q->closure = 1;
+ else
+ scanner(q->next);
+ c = q->comp;
+ /* Now the actual matching for the current path section. */
+ if (!(c->next || c->left) && !haswilds(c->str)) {
+ /* It's a straight string to the end of the path section. */
+ int l = strlen(c->str);
+
+ if (l + !l + pathpos - pathbufcwd >= PATH_MAX) {
+ int err;
+
+ if (l >= PATH_MAX)
+ return;
+ err = lchdir(pathbuf + pathbufcwd, &ds, 0);
+ if (err == -1)
+ return;
+ if (err) {
+ zerr("current directory lost during glob", NULL, 0);
+ return;
+ }
+ pathbufcwd = pathpos;
+ }
+ if (q->next) {
+ /* Not the last path section. Just add it to the path. */
+ int oppos = pathpos;
+
+ if (!errflag && !(q->closure && !strcmp(c->str, "."))) {
+ addpath(c->str);
+ if (!closure || statfullpath("", NULL, 1))
+ scanner((q->closure) ? q : q->next);
+ pathbuf[pathpos = oppos] = '\0';
+ }
+ } else
+ insert(c->str, 0);
+ } else {
+ /* Do pattern matching on current path section. */
+ char *fn;
+ int dirs = !!q->next;
+ DIR *lock;
+ char *subdirs = NULL;
+ int subdirlen = 0;
+
+ fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
+ if (dirs) {
+ struct stat st;
+ stat(fn, &st);
+ /* a directory with subdirectories has link count greater than 2 */
+ if (!S_ISDIR(st.st_mode) || st.st_nlink == 2)
+ return;
+ }
+ lock = opendir(fn);
+ if (lock == NULL)
+ return;
+ while ((fn = zreaddir(lock, 1)) && !errflag) {
+ /* prefix and suffix are zle trickery */
+ if (!dirs && !colonmod &&
+ ((glob_pre && !strpfx(glob_pre, fn))
+ || (glob_suf && !strsfx(glob_suf, fn))))
+ continue;
+ if (domatch(fn, c, gf_noglobdots)) {
+ /* if this name matchs the pattern... */
+ if (pbcwdsav == pathbufcwd &&
+ strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
+ int err;
+
+ DPUTS(pathpos == pathbufcwd,
+ "BUG: filename longer than PATH_MAX");
+ err = lchdir(pathbuf + pathbufcwd, &ds, 0);
+ if (err == -1)
+ break;
+ if (err) {
+ zerr("current directory lost during glob", NULL, 0);
+ break;
+ }
+ pathbufcwd = pathpos;
+ }
+ if (dirs) {
+ int l;
+
+ /* if not the last component in the path */
+ if (closure) {
+ /* if matching multiple directories */
+ struct stat buf;
+
+ if (statfullpath(fn, &buf, !q->follow)) {
+ if (errno != ENOENT && errno != EINTR &&
+ errno != ENOTDIR && !errflag) {
+ zerr("%e: %s", fn, errno);
+ errflag = 0;
+ }
+ continue;
+ }
+ if (!S_ISDIR(buf.st_mode))
+ continue;
+ }
+ l = strlen(fn) + 1;
+ subdirs = hrealloc(subdirs, subdirlen, subdirlen + l);
+ strcpy(subdirs + subdirlen, fn);
+ subdirlen += l;
+ } else
+ /* if the last filename component, just add it */
+ insert(fn, 1);
+ }
+ }
+ closedir(lock);
+ if (subdirs) {
+ int oppos = pathpos;
+
+ for (fn = subdirs; fn < subdirs+subdirlen; fn += strlen(fn) + 1) {
+ addpath(fn);
+ scanner((q->closure) ? q : q->next); /* scan next level */
+ pathbuf[pathpos = oppos] = '\0';
+ }
+ hrealloc(subdirs, subdirlen, 0);
+ }
+ }
+ if (pbcwdsav < pathbufcwd) {
+ if (restoredir(&ds))
+ zerr("current directory lost during glob", NULL, 0);
+ zsfree(ds.dirname);
+ if (ds.dirfd >= 0)
+ close(ds.dirfd);
+ pathbufcwd = pbcwdsav;
+ }
+}
+
+/* Parse a series of path components pointed to by pptr */
+
+/* enum used with ksh-like patterns, @(...) etc. */
+
+enum { KF_NONE, KF_AT, KF_QUEST, KF_STAR, KF_PLUS, KF_NOT };
+
+/* parse lowest level pattern */
+
+/**/
+static Comp
+parsecomp(int gflag)
+{
+ int kshfunc;
+ Comp c = (Comp) alloc(sizeof *c), c1, c2;
+ char *cstr, *ls = NULL;
+
+ /* In case of alternatives, code coming up is stored in tail. */
+ c->next = tail;
+ cstr = pptr;
+
+ while (*pptr && (mode || *pptr != '/') && *pptr != Bar &&
+ (unset(EXTENDEDGLOB) || *pptr != Tilde ||
+ !pptr[1] || pptr[1] == Outpar || pptr[1] == Bar) &&
+ *pptr != Outpar) {
+ /* Go through code until we find something separating alternatives,
+ * or path components if relevant.
+ */
+ if (*pptr == Hat && isset(EXTENDEDGLOB)) {
+ /* negate remaining pattern */
+ Comp stail = tail;
+ tail = NULL;
+ c->str = dupstrpfx(cstr, pptr - cstr);
+ pptr++;
+
+ c1 = (Comp) alloc(sizeof *c1);
+ c1->stat |= C_STAR;
+
+ c2 = (Comp) alloc(sizeof *c2);
+ if (!(c2->exclude = parsecomp(gflag)))
+ return NULL;
+ if (!*pptr || *pptr == '/')
+ c2->stat |= C_LAST;
+ c2->left = c1;
+ c2->next = stail;
+ c->next = c2;
+ tail = stail;
+ return c;
+ }
+
+ /* Ksh-type globs */
+ kshfunc = KF_NONE;
+ if (isset(KSHGLOB) && *pptr && pptr[1] == Inpar) {
+ switch (*pptr) {
+ case '@': /* just do paren as usual */
+ kshfunc = KF_AT;
+ break;
+
+ case Quest:
+ case '?': /* matched optionally, treat as (...|) */
+ kshfunc = KF_QUEST;
+ break;
+
+ case Star:
+ case '*': /* treat as (...)# */
+ kshfunc = KF_STAR;
+ break;
+
+ case '+': /* treat as (...)## */
+ kshfunc = KF_PLUS;
+ break;
+
+ case '!': /* treat as (*~...) */
+ kshfunc = KF_NOT;
+ break;
+ }
+ if (kshfunc != KF_NONE)
+ pptr++;
+ }
+
+ if (*pptr == Inpar) {
+ /* Found a group (...) */
+ char *startp = pptr, *endp;
+ Comp stail = tail;
+ int dpnd = 0;
+
+ /* Need matching close parenthesis */
+ if (skipparens(Inpar, Outpar, &pptr)) {
+ errflag = 1;
+ return NULL;
+ }
+ if (kshfunc == KF_STAR)
+ dpnd = 1;
+ else if (kshfunc == KF_PLUS)
+ dpnd = 2;
+ else if (kshfunc == KF_QUEST)
+ dpnd = 3;
+ if (*pptr == Pound && isset(EXTENDEDGLOB)) {
+ /* Zero (or one) or more repetitions of group */
+ pptr++;
+ if(*pptr == Pound) {
+ pptr++;
+ if(dpnd == 0)
+ dpnd = 2;
+ else if(dpnd == 3)
+ dpnd = 1;
+ } else
+ dpnd = 1;
+ }
+ /* Parse the remaining pattern following the group... */
+ if (!(c1 = parsecomp(gflag)))
+ return NULL;
+ /* ...remembering what comes after it... */
+ tail = (dpnd || kshfunc == KF_NOT) ? NULL : c1;
+ /* ...before going back and parsing inside the group. */
+ endp = pptr;
+ pptr = startp;
+ c->str = dupstrpfx(cstr, (pptr - cstr) - (kshfunc != KF_NONE));
+ pptr++;
+ c2 = (Comp) alloc(sizeof *c);
+ c->next = c2;
+ c2->next = (dpnd || kshfunc == KF_NOT) ?
+ c1 : (Comp) alloc(sizeof *c);
+ if (!(c2->left = parsecompsw(0)))
+ return NULL;
+ if (kshfunc == KF_NOT) {
+ /* we'd actually rather it didn't match. Instead, match *
+ * a star and put the parsed pattern into exclude. */
+ Comp c3 = (Comp) alloc(sizeof *c3);
+ c3->stat |= C_STAR;
+
+ c2->exclude = c2->left;
+ c2->left = c3;
+ }
+ /* Remember closures for group. */
+ if (dpnd)
+ c2->stat |= (dpnd == 3) ? C_OPTIONAL
+ : (dpnd == 2) ? C_TWOHASH : C_ONEHASH;
+ pptr = endp;
+ tail = stail;
+ return c;
+ }
+ if (*pptr == Star && pptr[1] &&
+ (unset(EXTENDEDGLOB) || !(gflag & GF_TOPLEV) ||
+ pptr[1] != Tilde || !pptr[2] || pptr[2] == Bar ||
+ pptr[2] == Outpar) && (mode || pptr[1] != '/')) {
+ /* Star followed by other patterns is now treated as a special
+ * type of closure in doesmatch().
+ */
+ c->str = dupstrpfx(cstr, pptr - cstr);
+ pptr++;
+ c1 = (Comp) alloc(sizeof *c1);
+ c1->stat |= C_STAR;
+ if (!(c2 = parsecomp(gflag)))
+ return NULL;
+ c1->next = c2;
+ c->next = c1;
+ return c;
+ }
+ if (*pptr == Pound && isset(EXTENDEDGLOB)) {
+ /* repeat whatever we've just had (ls) zero or more times */
+ if (!ls)
+ return NULL;
+ c2 = (Comp) alloc(sizeof *c);
+ c2->str = dupstrpfx(ls, pptr - ls);
+ pptr++;
+ if (*pptr == Pound) {
+ /* need one or more matches: cheat by copying previous char */
+ pptr++;
+ c->next = c1 = (Comp) alloc(sizeof *c);
+ c1->str = c2->str;
+ } else
+ c1 = c;
+ c1->next = c2;
+ c2->stat |= C_ONEHASH;
+ /* parse the rest of the pattern and return. */
+ c2->next = parsecomp(gflag);
+ if (!c2->next)
+ return NULL;
+ c->str = dupstrpfx(cstr, ls - cstr);
+ return c;
+ }
+ ls = pptr; /* whatever we just parsed */
+ if (*pptr == Inang) {
+ /* Numeric glob */
+ int dshct;
+
+ dshct = (pptr[1] == Outang);
+ while (*++pptr && *pptr != Outang)
+ if (*pptr == '-' && !dshct)
+ dshct = 1;
+ else if (!idigit(*pptr))
+ break;
+ if (*pptr != Outang)
+ return NULL;
+ } else if (*pptr == Inbrack) {
+ /* Character set: brackets had better match */
+ if (pptr[1] == Outbrack)
+ *++pptr = ']';
+ else if ((pptr[1] == Hat || pptr[1] == '^' || pptr[1] == '!') &&
+ pptr[2] == Outbrack)
+ *(pptr += 2) = ']';
+ while (*++pptr && *pptr != Outbrack) {
+ if (itok(*pptr)) {
+ /* POSIX classes: make sure it's a real one, *
+ * leave the Inbrack tokenised if so. */
+ char *nptr;
+ if (*pptr == Inbrack && pptr[1] == ':'
+ && (nptr = strchr(pptr+2, ':')) &&
+ *++nptr == Outbrack)
+ pptr = nptr;
+ *pptr = ztokens[*pptr - Pound];
+ }
+ }
+ if (*pptr != Outbrack)
+ return NULL;
+ } else if (itok(*pptr) && *pptr != Star && *pptr != Quest)
+ /* something that can be tokenised which isn't otherwise special */
+ *pptr = ztokens[*pptr - Pound];
+ pptr++;
+ }
+ /* mark if last pattern component in path component or pattern */
+ if (*pptr == '/' || !*pptr ||
+ (isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV)))
+ c->stat |= C_LAST;
+ c->str = dupstrpfx(cstr, pptr - cstr);
+ return c;
+}
+
+/* Parse pattern possibly with different alternatives (|) */
+
+/**/
+static Comp
+parsecompsw(int gflag)
+{
+ Comp c1, c2, c3, excl = NULL, stail = tail;
+ char *sptr;
+
+ /*
+ * Check for a tilde in the expression. We need to know this in
+ * advance so as to be able to treat the whole a~b expression by
+ * backtracking: see exclusion code in doesmatch().
+ */
+ if (isset(EXTENDEDGLOB)) {
+ int pct = 0;
+ for (sptr = pptr; *sptr; sptr++) {
+ if (*sptr == Inpar)
+ pct++;
+ else if (*sptr == Outpar && --pct < 0)
+ break;
+ else if (*sptr == Bar && !pct)
+ break;
+ else if (*sptr == Tilde && !pct) {
+ tail = NULL;
+ break;
+ }
+ }
+ }
+
+ c1 = parsecomp(gflag);
+ if (!c1)
+ return NULL;
+ if (isset(EXTENDEDGLOB) && *pptr == Tilde) {
+ /* Matching remainder of pattern excludes the pattern from matching */
+ int oldmode = mode;
+
+ mode = 1;
+ pptr++;
+ excl = parsecomp(gflag);
+ mode = oldmode;
+ if (!excl)
+ return NULL;
+ }
+ tail = stail;
+ if (*pptr == Bar || excl) {
+ /* found an alternative or something to exclude */
+ c2 = (Comp) alloc(sizeof *c2);
+ if (*pptr == Bar) {
+ /* get the next alternative after the | */
+ pptr++;
+ c3 = parsecompsw(gflag);
+ if (!c3)
+ return NULL;
+ } else
+ c3 = NULL;
+ /* mark if end of pattern or path component */
+ if (!*pptr || *pptr == '/')
+ c1->stat |= c2->stat = C_LAST;
+ c2->str = dupstring("");
+ c2->left = c1;
+ c2->right = c3;
+ if ((c2->exclude = excl))
+ c2->next = stail;
+ if (gflag & GF_PATHADD)
+ c2->stat |= C_PATHADD;
+ return c2;
+ }
+ return c1;
+}
+
+/* This function tokenizes a zsh glob pattern */
+
+/**/
+static Complist
+parsecomplist(void)
+{
+ Comp c1;
+ Complist p1;
+ char *str;
+
+ if (pptr[0] == Star && pptr[1] == Star &&
+ (pptr[2] == '/' || (pptr[2] == Star && pptr[3] == '/'))) {
+ /* Match any number of directories. */
+ int follow;
+
+ /* with three stars, follow symbolic links */
+ follow = (pptr[2] == Star);
+ pptr += (3 + follow);
+
+ /* Now get the next path component if there is one. */
+ p1 = (Complist) alloc(sizeof *p1);
+ if ((p1->next = parsecomplist()) == NULL) {
+ errflag = 1;
+ return NULL;
+ }
+ p1->comp = (Comp) alloc(sizeof *p1->comp);
+ p1->comp->stat |= C_LAST; /* end of path component */
+ p1->comp->str = dupstring("*");
+ *p1->comp->str = Star; /* match anything... */
+ p1->closure = 1; /* ...zero or more times. */
+ p1->follow = follow;
+ return p1;
+ }
+
+ /* Parse repeated directories such as (dir/)# and (dir/)## */
+ if (*(str = pptr) == Inpar && !skipparens(Inpar, Outpar, &str) &&
+ *str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
+ pptr++;
+ if (!(c1 = parsecompsw(0)))
+ return NULL;
+ if (pptr[0] == '/' && pptr[1] == Outpar && pptr[2] == Pound) {
+ int pdflag = 0;
+
+ pptr += 3;
+ if (*pptr == Pound) {
+ pdflag = 1;
+ pptr++;
+ }
+ p1 = (Complist) alloc(sizeof *p1);
+ p1->comp = c1;
+ p1->closure = 1 + pdflag;
+ p1->follow = 0;
+ p1->next = parsecomplist();
+ return (p1->comp) ? p1 : NULL;
+ }
+ } else {
+ /* parse single path component */
+ if (!(c1 = parsecompsw(GF_PATHADD|GF_TOPLEV)))
+ return NULL;
+ /* then do the remaining path compoents */
+ if (*pptr == '/' || !*pptr) {
+ int ef = *pptr == '/';
+
+ p1 = (Complist) alloc(sizeof *p1);
+ p1->comp = c1;
+ p1->closure = 0;
+ p1->next = ef ? (pptr++, parsecomplist()) : NULL;
+ return (ef && !p1->next) ? NULL : p1;
+ }
+ }
+ errflag = 1;
+ return NULL;
+}
+
+/* turn a string into a Complist struct: this has path components */
+
+/**/
+static Complist
+parsepat(char *str)
+{
+ mode = 0; /* path components present */
+ pptr = str;
+ tail = NULL;
+ return parsecomplist();
+}
+
+/* get number after qualifier */
+
+/**/
+static long
+qgetnum(char **s)
+{
+ long v = 0;
+
+ if (!idigit(**s)) {
+ zerr("number expected", NULL, 0);
+ return 0;
+ }
+ while (idigit(**s))
+ v = v * 10 + *(*s)++ - '0';
+ return v;
+}
+
+/* get octal number after qualifier */
+
+/**/
+static long
+qgetoctnum(char **s)
+{
+ long v = 0;
+
+ if (!idigit(**s)) {
+ zerr("octal number expected", NULL, 0);
+ return 0;
+ }
+ while (**s >= '0' && **s <= '7')
+ v = v * 010 + *(*s)++ - '0';
+ return v;
+}
+
+/* Main entry point to the globbing code for filename globbing. *
+ * np points to a node in the list list which will be expanded *
+ * into a series of nodes. */
+
+/**/
+void
+glob(LinkList list, LinkNode np)
+{
+ struct qual *qo, *qn, *ql;
+ LinkNode node = prevnode(np);
+ char *str; /* the pattern */
+ int sl; /* length of the pattern */
+ Complist q; /* pattern after parsing */
+ char *ostr = (char *)getdata(np); /* the pattern before the parser */
+ /* chops it up */
+
+ MUSTUSEHEAP("glob");
+ if (unset(GLOBOPT) || !haswilds(ostr)) {
+ untokenize(ostr);
+ return;
+ }
+ str = dupstring(ostr);
+ sl = strlen(str);
+ uremnode(list, np);
+
+ /* Initialise state variables for current file pattern */
+ qo = qn = quals = ql = NULL;
+ qualct = qualorct = 0;
+ colonmod = NULL;
+ gf_nullglob = isset(NULLGLOB);
+ gf_markdirs = isset(MARKDIRS);
+ gf_listtypes = gf_follow = 0;
+ gf_noglobdots = unset(GLOBDOTS);
+
+ /* Check for qualifiers */
+ if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) {
+ char *s;
+
+ /* Check these are really qualifiers, not a set of *
+ * alternatives or exclusions */
+ for (s = str + sl - 2; *s != Inpar; s--)
+ if (*s == Bar || *s == Outpar ||
+ (isset(EXTENDEDGLOB) && *s == Tilde))
+ break;
+ if (*s == Inpar) {
+ /* Real qualifiers found. */
+ int sense = 0; /* bit 0 for match (0)/don't match (1) */
+ /* bit 1 for follow links (2), don't (0) */
+ long data = 0; /* Any numerical argument required */
+ int (*func) _((Statptr, long));
+
+ str[sl-1] = 0;
+ *s++ = 0;
+ while (*s && !colonmod) {
+ func = (int (*) _((Statptr, long)))0;
+ if (idigit(*s)) {
+ /* Store numeric argument for qualifier */
+ func = qualflags;
+ data = 0;
+ while (idigit(*s))
+ data = data * 010 + (*s++ - '0');
+ } else if (*s == ',') {
+ /* A comma separates alternative sets of qualifiers */
+ s++;
+ sense = 0;
+ if (qualct) {
+ qn = (struct qual *)hcalloc(sizeof *qn);
+ qo->or = qn;
+ qo = qn;
+ qualorct++;
+ qualct = 0;
+ ql = NULL;
+ }
+ } else
+ switch (*s++) {
+ case ':':
+ /* Remaining arguments are history-type *
+ * colon substitutions, handled separately. */
+ colonmod = s - 1;
+ untokenize(colonmod);
+ break;
+ case Hat:
+ case '^':
+ /* Toggle sense: go from positive to *
+ * negative match and vice versa. */
+ sense ^= 1;
+ break;
+ case '-':
+ /* Toggle matching of symbolic links */
+ sense ^= 2;
+ break;
+ case '@':
+ /* Match symbolic links */
+ func = qualislnk;
+ break;
+ case Equals:
+ case '=':
+ /* Match sockets */
+ func = qualissock;
+ break;
+ case 'p':
+ /* Match named pipes */
+ func = qualisfifo;
+ break;
+ case '/':
+ /* Match directories */
+ func = qualisdir;
+ break;
+ case '.':
+ /* Match regular files */
+ func = qualisreg;
+ break;
+ case '%':
+ /* Match special files: block, *
+ * character or any device */
+ if (*s == 'b')
+ s++, func = qualisblk;
+ else if (*s == 'c')
+ s++, func = qualischr;
+ else
+ func = qualisdev;
+ break;
+ case Star:
+ /* Match executable plain files */
+ func = qualiscom;
+ break;
+ case 'R':
+ /* Match world-readable files */
+ func = qualflags;
+ data = 0004;
+ break;
+ case 'W':
+ /* Match world-writeable files */
+ func = qualflags;
+ data = 0002;
+ break;
+ case 'X':
+ /* Match world-executable files */
+ func = qualflags;
+ data = 0001;
+ break;
+ case 'A':
+ func = qualflags;
+ data = 0040;
+ break;
+ case 'I':
+ func = qualflags;
+ data = 0020;
+ break;
+ case 'E':
+ func = qualflags;
+ data = 0010;
+ break;
+ case 'r':
+ /* Match files readable by current process */
+ func = qualflags;
+ data = 0400;
+ break;
+ case 'w':
+ /* Match files writeable by current process */
+ func = qualflags;
+ data = 0200;
+ break;
+ case 'x':
+ /* Match files executable by current process */
+ func = qualflags;
+ data = 0100;
+ break;
+ case 's':
+ /* Match setuid files */
+ func = qualflags;
+ data = 04000;
+ break;
+ case 'S':
+ /* Match setgid files */
+ func = qualflags;
+ data = 02000;
+ break;
+ case 't':
+ func = qualflags;
+ data = 01000;
+ break;
+ case 'd':
+ /* Match device files by device number *
+ * (as given by stat's st_dev element). */
+ func = qualdev;
+ data = qgetnum(&s);
+ break;
+ case 'l':
+ /* Match files with the given no. of hard links */
+ func = qualnlink;
+ amc = -1;
+ goto getrange;
+ case 'U':
+ /* Match files owned by effective user ID */
+ func = qualuid;
+ data = geteuid();
+ break;
+ case 'G':
+ /* Match files owned by effective group ID */
+ func = qualgid;
+ data = getegid();
+ break;
+ case 'u':
+ /* Match files owned by given user id */
+ func = qualuid;
+ /* either the actual uid... */
+ if (idigit(*s))
+ data = qgetnum(&s);
+ else {
+ /* ... or a user name */
+ char sav, *tt;
+
+ /* Find matching delimiters */
+ tt = get_strarg(s);
+ if (!*tt) {
+ zerr("missing end of name",
+ NULL, 0);
+ data = 0;
+ } else {
+#ifdef HAVE_GETPWNAM
+ struct passwd *pw;
+ sav = *tt;
+ *tt = '\0';
+
+ if ((pw = getpwnam(s + 1)))
+ data = pw->pw_uid;
+ else {
+ zerr("unknown user", NULL, 0);
+ data = 0;
+ }
+ *tt = sav;
+#else /* !HAVE_GETPWNAM */
+ sav = *tt;
+ zerr("unknown user", NULL, 0);
+ data = 0;
+#endif /* !HAVE_GETPWNAM */
+ if (sav)
+ s = tt + 1;
+ else
+ s = tt;
+ }
+ }
+ break;
+ case 'g':
+ /* Given gid or group id... works like `u' */
+ func = qualgid;
+ /* either the actual gid... */
+ if (idigit(*s))
+ data = qgetnum(&s);
+ else {
+ /* ...or a delimited group name. */
+ char sav, *tt;
+
+ tt = get_strarg(s);
+ if (!*tt) {
+ zerr("missing end of name",
+ NULL, 0);
+ data = 0;
+ } else {
+#ifdef HAVE_GETGRNAM
+ struct group *gr;
+ sav = *tt;
+ *tt = '\0';
+
+ if ((gr = getgrnam(s + 1)))
+ data = gr->gr_gid;
+ else {
+ zerr("unknown group", NULL, 0);
+ data = 0;
+ }
+ *tt = sav;
+#else /* !HAVE_GETGRNAM */
+ sav = *tt;
+ zerr("unknown group", NULL, 0);
+ data = 0;
+#endif /* !HAVE_GETGRNAM */
+ if (sav)
+ s = tt + 1;
+ else
+ s = tt;
+ }
+ }
+ break;
+ case 'o':
+ /* Match octal mode of file exactly. *
+ * Currently undocumented. */
+ func = qualeqflags;
+ data = qgetoctnum(&s);
+ break;
+ case 'M':
+ /* Mark directories with a / */
+ if ((gf_markdirs = !(sense & 1)))
+ gf_follow = sense & 2;
+ break;
+ case 'T':
+ /* Mark types in a `ls -F' type fashion */
+ if ((gf_listtypes = !(sense & 1)))
+ gf_follow = sense & 2;
+ break;
+ case 'N':
+ /* Nullglob: remove unmatched patterns. */
+ gf_nullglob = !(sense & 1);
+ break;
+ case 'D':
+ /* Glob dots: match leading dots implicitly */
+ gf_noglobdots = sense & 1;
+ break;
+ case 'a':
+ /* Access time in given range */
+ amc = 0;
+ func = qualtime;
+ goto getrange;
+ case 'm':
+ /* Modification time in given range */
+ amc = 1;
+ func = qualtime;
+ goto getrange;
+ case 'c':
+ /* Inode creation time in given range */
+ amc = 2;
+ func = qualtime;
+ goto getrange;
+ case 'L':
+ /* File size (Length) in given range */
+ func = qualsize;
+ amc = -1;
+ /* Get size multiplier */
+ units = TT_BYTES;
+ if (*s == 'p' || *s == 'P')
+ units = TT_POSIX_BLOCKS, ++s;
+ else if (*s == 'k' || *s == 'K')
+ units = TT_KILOBYTES, ++s;
+ else if (*s == 'm' || *s == 'M')
+ units = TT_MEGABYTES, ++s;
+ getrange:
+ /* Get time multiplier */
+ if (amc >= 0) {
+ units = TT_DAYS;
+ if (*s == 'h')
+ units = TT_HOURS, ++s;
+ else if (*s == 'm')
+ units = TT_MINS, ++s;
+ else if (*s == 'w')
+ units = TT_WEEKS, ++s;
+ else if (*s == 'M')
+ units = TT_MONTHS, ++s;
+ }
+ /* See if it's greater than, equal to, or less than */
+ if ((range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
+ ++s;
+ data = qgetnum(&s);
+ break;
+
+ default:
+ zerr("unknown file attribute", NULL, 0);
+ return;
+ }
+ if (func) {
+ /* Requested test is performed by function func */
+ if (!qn)
+ qn = (struct qual *)hcalloc(sizeof *qn);
+ if (ql)
+ ql->next = qn;
+ ql = qn;
+ if (!quals)
+ quals = qo = qn;
+ qn->func = func;
+ qn->sense = sense;
+ qn->data = data;
+ qn->range = range;
+ qn->units = units;
+ qn->amc = amc;
+ qn = NULL;
+ qualct++;
+ }
+ if (errflag)
+ return;
+ }
+ }
+ }
+ if (!pathbuf)
+ pathbuf = zalloc(pathbufsz = PATH_MAX);
+ DPUTS(pathbufcwd, "BUG: glob changed directory");
+ if (*str == '/') { /* pattern has absolute path */
+ str++;
+ pathbuf[0] = '/';
+ pathbuf[pathpos = 1] = '\0';
+ } else /* pattern is relative to pwd */
+ pathbuf[pathpos = 0] = '\0';
+ q = parsepat(str);
+ if (!q || errflag) { /* if parsing failed */
+ if (unset(BADPATTERN)) {
+ untokenize(ostr);
+ insertlinknode(list, node, ostr);
+ return;
+ }
+ errflag = 0;
+ zerr("bad pattern: %s", ostr, 0);
+ return;
+ }
+
+ /* Initialise receptacle for matched files, *
+ * expanded by insert() where necessary. */
+ matchptr = matchbuf = (char **)zalloc((matchsz = 16) * sizeof(char *));
+ matchct = 0;
+
+ /* The actual processing takes place here: matches go into *
+ * matchbuf. This is the only top-level call to scanner(). */
+ scanner(q);
+
+ /* Deal with failures to match depending on options */
+ if (matchct)
+ badcshglob |= 2; /* at least one cmd. line expansion O.K. */
+ else if (!gf_nullglob)
+ if (isset(CSHNULLGLOB)) {
+ badcshglob |= 1; /* at least one cmd. line expansion failed */
+ } else if (isset(NOMATCH)) {
+ zerr("no matches found: %s", ostr, 0);
+ free(matchbuf);
+ return;
+ } else {
+ /* treat as an ordinary string */
+ untokenize(*matchptr++ = dupstring(ostr));
+ matchct = 1;
+ }
+ /* Sort arguments in to lexical (and possibly numeric) order. *
+ * This is reversed to facilitate insertion into the list. */
+ qsort((void *) & matchbuf[0], matchct, sizeof(char *),
+ (int (*) _((const void *, const void *)))notstrcmp);
+
+ matchptr = matchbuf;
+ while (matchct--) /* insert matches in the arg list */
+ insertlinknode(list, node, *matchptr++);
+ free(matchbuf);
+}
+
+/* Return the order of two strings, taking into account *
+ * possible numeric order if NUMERICGLOBSORT is set. *
+ * The comparison here is reversed. */
+
+/**/
+static int
+notstrcmp(char **a, char **b)
+{
+ char *c = *b, *d = *a;
+ int cmp;
+
+#ifdef HAVE_STRCOLL
+ cmp = strcoll(c, d);
+#endif
+ for (; *c == *d && *c; c++, d++);
+#ifndef HAVE_STRCOLL
+ cmp = (int)STOUC(*c) - (int)STOUC(*d);
+#endif
+ if (isset(NUMERICGLOBSORT) && (idigit(*c) || idigit(*d))) {
+ for (; c > *b && idigit(c[-1]); c--, d--);
+ if (idigit(*c) && idigit(*d)) {
+ while (*c == '0')
+ c++;
+ while (*d == '0')
+ d++;
+ for (; idigit(*c) && *c == *d; c++, d++);
+ if (idigit(*c) || idigit(*d)) {
+ cmp = (int)STOUC(*c) - (int)STOUC(*d);
+ while (idigit(*c) && idigit(*d))
+ c++, d++;
+ if (idigit(*c) && !idigit(*d))
+ return 1;
+ if (idigit(*d) && !idigit(*c))
+ return -1;
+ }
+ }
+ }
+ return cmp;
+}
+
+/* Return the trailing character for marking file types */
+
+/**/
+char
+file_type(mode_t filemode)
+{
+ if(S_ISBLK(filemode))
+ return '#';
+ else if(S_ISCHR(filemode))
+ return '%';
+ else if(S_ISDIR(filemode))
+ return '/';
+ else if(S_ISFIFO(filemode))
+ return '|';
+ else if(S_ISLNK(filemode))
+ return '@';
+ else if(S_ISREG(filemode))
+ return (filemode & S_IXUGO) ? '*' : ' ';
+ else if(S_ISSOCK(filemode))
+ return '=';
+ else
+ return '?';
+}
+
+/* check to see if str is eligible for brace expansion */
+
+/**/
+int
+hasbraces(char *str)
+{
+ char *lbr, *mbr, *comma;
+
+ if (isset(BRACECCL)) {
+ /* In this case, any properly formed brace expression *
+ * will match and expand to the characters in between. */
+ int bc;
+
+ for (bc = 0; *str; ++str)
+ if (*str == Inbrace) {
+ if (!bc && str[1] == Outbrace)
+ *str++ = '{', *str = '}';
+ else
+ bc++;
+ } else if (*str == Outbrace)
+ if (!bc)
+ *str = '}';
+ else if (!--bc)
+ return 1;
+ return 0;
+ }
+ /* Otherwise we need to look for... */
+ lbr = mbr = comma = NULL;
+ for (;;) {
+ switch (*str++) {
+ case Inbrace:
+ if (!lbr) {
+ lbr = str - 1;
+ while (idigit(*str))
+ str++;
+ if (*str == '.' && str[1] == '.') {
+ str++;
+ while (idigit(*++str));
+ if (*str == Outbrace &&
+ (idigit(lbr[1]) || idigit(str[-1])))
+ return 1;
+ }
+ } else {
+ char *s = --str;
+
+ if (skipparens(Inbrace, Outbrace, &str)) {
+ *lbr = *s = '{';
+ if (comma)
+ str = comma;
+ if (mbr && mbr < str)
+ str = mbr;
+ lbr = mbr = comma = NULL;
+ } else if (!mbr)
+ mbr = s;
+ }
+ break;
+ case Outbrace:
+ if (!lbr)
+ str[-1] = '}';
+ else if (comma)
+ return 1;
+ else {
+ *lbr = '{';
+ str[-1] = '}';
+ if (mbr)
+ str = mbr;
+ mbr = lbr = NULL;
+ }
+ break;
+ case Comma:
+ if (!lbr)
+ str[-1] = ',';
+ else if (!comma)
+ comma = str - 1;
+ break;
+ case '\0':
+ if (lbr)
+ *lbr = '{';
+ if (!mbr && !comma)
+ return 0;
+ if (comma)
+ str = comma;
+ if (mbr && mbr < str)
+ str = mbr;
+ lbr = mbr = comma = NULL;
+ break;
+ }
+ }
+}
+
+/* expand stuff like >>*.c */
+
+/**/
+int
+xpandredir(struct redir *fn, LinkList tab)
+{
+ LinkList fake;
+ char *nam;
+ struct redir *ff;
+ int ret = 0;
+
+ /* Stick the name in a list... */
+ fake = newlinklist();
+ addlinknode(fake, fn->name);
+ /* ...which undergoes all the usual shell expansions */
+ prefork(fake, isset(MULTIOS) ? 0 : 4);
+ /* Globbing is only done for multios. */
+ if (!errflag && isset(MULTIOS))
+ globlist(fake);
+ if (errflag)
+ return 0;
+ if (nonempty(fake) && !nextnode(firstnode(fake))) {
+ /* Just one match, the usual case. */
+ char *s = peekfirst(fake);
+ fn->name = s;
+ untokenize(s);
+ if (fn->type == MERGEIN || fn->type == MERGEOUT) {
+ if (s[0] == '-' && !s[1])
+ fn->type = CLOSE;
+ else if (s[0] == 'p' && !s[1])
+ fn->fd2 = (fn->type == MERGEOUT) ? coprocout : coprocin;
+ else {
+ while (idigit(*s))
+ s++;
+ if (!*s && s > fn->name)
+ fn->fd2 = zstrtol(fn->name, NULL, 10);
+ else if (fn->type == MERGEIN)
+ zerr("file number expected", NULL, 0);
+ else
+ fn->type = ERRWRITE;
+ }
+ }
+ } else if (fn->type == MERGEIN)
+ zerr("file number expected", NULL, 0);
+ else {
+ if (fn->type == MERGEOUT)
+ fn->type = ERRWRITE;
+ while ((nam = (char *)ugetnode(fake))) {
+ /* Loop over matches, duplicating the *
+ * redirection for each file found. */
+ ff = (struct redir *)alloc(sizeof *ff);
+ *ff = *fn;
+ ff->name = nam;
+ addlinknode(tab, ff);
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+/* concatenate s1 and s2 in dynamically allocated buffer */
+
+/**/
+char *
+dyncat(char *s1, char *s2)
+{
+ /* This version always uses space from the current heap. */
+ char *ptr;
+ int l1 = strlen(s1);
+
+ ptr = (char *)ncalloc(l1 + strlen(s2) + 1);
+ strcpy(ptr, s1);
+ strcpy(ptr + l1, s2);
+ return ptr;
+}
+
+/* concatenate s1, s2, and s3 in dynamically allocated buffer */
+
+/**/
+char *
+tricat(char const *s1, char const *s2, char const *s3)
+{
+ /* This version always uses permanently-allocated space. */
+ char *ptr;
+
+ ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
+ strcpy(ptr, s1);
+ strcat(ptr, s2);
+ strcat(ptr, s3);
+ return ptr;
+}
+
+/* brace expansion */
+
+/**/
+void
+xpandbraces(LinkList list, LinkNode *np)
+{
+ LinkNode node = (*np), last = prevnode(node);
+ char *str = (char *)getdata(node), *str3 = str, *str2;
+ int prev, bc, comma, dotdot;
+
+ for (; *str != Inbrace; str++);
+ /* First, match up braces and see what we have. */
+ for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2)
+ if (*str2 == Inbrace)
+ ++bc;
+ else if (*str2 == Outbrace) {
+ if (--bc == 0)
+ break;
+ } else if (bc == 1)
+ if (*str2 == Comma)
+ ++comma; /* we have {foo,bar} */
+ else if (*str2 == '.' && str2[1] == '.')
+ dotdot++; /* we have {num1..num2} */
+ DPUTS(bc, "BUG: unmatched brace in xpandbraces()");
+ if (!comma && dotdot) {
+ /* Expand range like 0..10 numerically: comma or recursive
+ brace expansion take precedence. */
+ char *dots, *p;
+ LinkNode olast = last;
+ /* Get the first number of the range */
+ int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0;
+ int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2;
+ int strp = str - str3;
+
+ if (dots == str + 1 || *dots != '.' || dots[1] != '.')
+ err++;
+ else {
+ /* Get the last number of the range */
+ rend = zstrtol(dots+2,&p,10);
+ if (p == dots+2 || p != str2)
+ err++;
+ }
+ if (!err) {
+ /* If either no. begins with a zero, pad the output with *
+ * zeroes. Otherwise, choose a min width to suppress them. */
+ int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 :
+ (wid2 > wid1) ? wid1 : wid2;
+ if (rstart > rend) {
+ /* Handle decreasing ranges correctly. */
+ int rt = rend;
+ rend = rstart;
+ rstart = rt;
+ rev = 1;
+ }
+ uremnode(list, node);
+ for (; rend >= rstart; rend--) {
+ /* Node added in at end, so do highest first */
+ p = dupstring(str3);
+ sprintf(p + strp, "%0*d", minw, rend);
+ strcat(p + strp, str2 + 1);
+ insertlinknode(list, last, p);
+ if (rev) /* decreasing: add in reverse order. */
+ last = nextnode(last);
+ }
+ *np = nextnode(olast);
+ return;
+ }
+ }
+ if (!comma && isset(BRACECCL)) { /* {a-mnop} */
+ /* Here we expand each character to a separate node, *
+ * but also ranges of characters like a-m. ccl is a *
+ * set of flags saying whether each character is present; *
+ * the final list is in lexical order. */
+ char ccl[256], *p;
+ unsigned char c1, c2, lastch;
+ unsigned int len, pl;
+
+ uremnode(list, node);
+ memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
+ for (p = str + 1, lastch = 0; p < str2;) {
+ if (itok(c1 = *p++))
+ c1 = ztokens[c1 - STOUC(Pound)];
+ if ((char) c1 == Meta)
+ c1 = 32 ^ *p++;
+ if (itok(c2 = *p))
+ c2 = ztokens[c2 - STOUC(Pound)];
+ if ((char) c2 == Meta)
+ c2 = 32 ^ p[1];
+ if (c1 == '-' && lastch && p < str2 && (int)lastch <= (int)c2) {
+ while ((int)lastch < (int)c2)
+ ccl[lastch++] = 1;
+ lastch = 0;
+ } else
+ ccl[lastch = c1] = 1;
+ }
+ pl = str - str3;
+ len = pl + strlen(++str2) + 2;
+ for (p = ccl + 255; p-- > ccl;)
+ if (*p) {
+ c1 = p - ccl;
+ if (imeta(c1)) {
+ str = ncalloc(len + 1);
+ str[pl] = Meta;
+ str[pl+1] = c1 ^ 32;
+ strcpy(str + pl + 2, str2);
+ } else {
+ str = ncalloc(len);
+ str[pl] = c1;
+ strcpy(str + pl + 1, str2);
+ }
+ memcpy(str, str3, pl);
+ insertlinknode(list, last, str);
+ }
+ *np = nextnode(last);
+ return;
+ }
+ prev = str++ - str3;
+ str2++;
+ uremnode(list, node);
+ node = last;
+ /* Finally, normal comma expansion *
+ * str1{foo,bar}str2 -> str1foostr2 str1barstr2. *
+ * Any number of intervening commas is allowed. */
+ for (;;) {
+ char *zz, *str4;
+ int cnt;
+
+ for (str4 = str, cnt = 0; cnt || (*str != Comma && *str !=
+ Outbrace); str++) {
+ if (*str == Inbrace)
+ cnt++;
+ else if (*str == Outbrace)
+ cnt--;
+ DPUTS(!*str, "BUG: illegal brace expansion");
+ }
+ /* Concatenate the string before the braces (str3), the section *
+ * just found (str4) and the text after the braces (str2) */
+ zz = (char *)ncalloc(prev + (str - str4) + strlen(str2) + 1);
+ ztrncpy(zz, str3, prev);
+ strncat(zz, str4, str - str4);
+ strcat(zz, str2);
+ /* and add this text to the argument list. */
+ insertlinknode(list, node, zz);
+ incnode(node);
+ if (*str != Outbrace)
+ str++;
+ else
+ break;
+ }
+ *np = nextnode(last);
+}
+
+/* check to see if a matches b (b is not a filename pattern) */
+
+/**/
+int
+matchpat(char *a, char *b)
+{
+ Comp c = parsereg(b);
+
+ if (!c) {
+ zerr("bad pattern: %s", b, 0);
+ return 0;
+ }
+ return domatch(a, c, 0);
+}
+
+/* do the ${foo%%bar}, ${foo#bar} stuff */
+/* please do not laugh at this code. */
+
+/* Having found a match in getmatch, decide what part of string
+ * to return. The matched part starts b characters into string s
+ * and finishes e characters in: 0 <= b <= e <= strlen(s)
+ * (yes, empty matches should work).
+ * Bits 3 and higher in fl are used: the flags are
+ * 8: Result is matched portion.
+ * 16: Result is unmatched portion.
+ * (N.B. this should be set for standard ${foo#bar} etc. matches.)
+ * 32: Result is numeric position of start of matched portion.
+ * 64: Result is numeric position of end of matched portion.
+ * 128: Result is length of matched portion.
+ */
+
+/**/
+static char *
+get_match_ret(char *s, int b, int e, int fl)
+{
+ char buf[80], *r, *p, *rr;
+ int ll = 0, l = strlen(s), bl = 0, t = 0, i;
+
+ if (fl & 8) /* matched portion */
+ ll += 1 + (e - b);
+ if (fl & 16) /* unmatched portion */
+ ll += 1 + (l - (e - b));
+ if (fl & 32) {
+ /* position of start of matched portion */
+ sprintf(buf, "%d ", b + 1);
+ ll += (bl = strlen(buf));
+ }
+ if (fl & 64) {
+ /* position of end of matched portion */
+ sprintf(buf + bl, "%d ", e + 1);
+ ll += (bl = strlen(buf));
+ }
+ if (fl & 128) {
+ /* length of matched portion */
+ sprintf(buf + bl, "%d ", e - b);
+ ll += (bl = strlen(buf));
+ }
+ if (bl)
+ buf[bl - 1] = '\0';
+
+ rr = r = (char *)ncalloc(ll);
+
+ if (fl & 8) {
+ /* copy matched portion to new buffer */
+ for (i = b, p = s + b; i < e; i++)
+ *rr++ = *p++;
+ t = 1;
+ }
+ if (fl & 16) {
+ /* Copy unmatched portion to buffer. If both portions *
+ * requested, put a space in between (why?) */
+ if (t)
+ *rr++ = ' ';
+ /* there may be unmatched bits at both beginning and end of string */
+ for (i = 0, p = s; i < b; i++)
+ *rr++ = *p++;
+ for (i = e, p = s + e; i < l; i++)
+ *rr++ = *p++;
+ t = 1;
+ }
+ *rr = '\0';
+ if (bl) {
+ /* if there was a buffer (with a numeric result), add it; *
+ * if there was other stuff too, stick in a space first. */
+ if (t)
+ *rr++ = ' ';
+ strcpy(rr, buf);
+ }
+ return r;
+}
+
+/* It is called from paramsubst to get the match for ${foo#bar} etc.
+ * Bits of fl determines the required action:
+ * bit 0: match the end instead of the beginning (% or %%)
+ * bit 1: % or # was doubled so get the longest match
+ * bit 2: substring match
+ * bit 3: include the matched portion
+ * bit 4: include the unmatched portion
+ * bit 5: the index of the beginning
+ * bit 6: the index of the end
+ * bit 7: the length of the match
+ * bit 8: match the complete string
+ * *sp points to the string we have to modify. The n'th match will be
+ * returned in *sp. ncalloc is used to get memory for the result string.
+ */
+
+/**/
+int
+getmatch(char **sp, char *pat, int fl, int n)
+{
+ Comp c;
+ char *s = *sp, *t, sav;
+ int i, j, l = strlen(*sp);
+
+ c = parsereg(pat);
+ if (!c) {
+ zerr("bad pattern: %s", pat, 0);
+ return 1;
+ }
+ if (fl & 256) {
+ i = domatch(s, c, 0);
+ *sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl);
+ if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i)))
+ return 0;
+ return 1;
+ }
+ switch (fl & 7) {
+ case 0:
+ /* Smallest possible match at head of string: *
+ * start adding characters until we get a match. */
+ for (i = 0, t = s; i <= l; i++, t++) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, 0, i, fl);
+ return 1;
+ }
+ if ((*t = sav) == Meta)
+ i++, t++;
+ }
+ break;
+
+ case 1:
+ /* Smallest possible match at tail of string: *
+ * move back down string until we get a match. */
+ for (t = s + l; t >= s; t--) {
+ if (domatch(t, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, t - s, l, fl);
+ return 1;
+ }
+ if (t > s+1 && t[-2] == Meta)
+ t--;
+ }
+ break;
+
+ case 2:
+ /* Largest possible match at head of string: *
+ * delete characters from end until we get a match. */
+ for (t = s + l; t > s; t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, 0, t - s, fl);
+ return 1;
+ }
+ *t = sav;
+ if (t >= s+2 && t[-2] == Meta)
+ t--;
+ }
+ break;
+
+ case 3:
+ /* Largest possible match at tail of string: *
+ * move forward along string until we get a match. */
+ for (i = 0, t = s; i < l; i++, t++) {
+ if (domatch(t, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, i, l, fl);
+ return 1;
+ }
+ if (*t == Meta)
+ i++, t++;
+ }
+ break;
+
+ case 4:
+ /* Smallest at start, but matching substrings. */
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, 0, 0, fl);
+ return 1;
+ }
+ for (i = 1; i <= l; i++) {
+ for (t = s, j = i; j <= l; j++, t++) {
+ sav = s[j];
+ s[j] = '\0';
+ if (domatch(t, c, 0) && !--n) {
+ s[j] = sav;
+ *sp = get_match_ret(*sp, t - s, j, fl);
+ return 1;
+ }
+ if ((s[j] = sav) == Meta)
+ j++;
+ if (*t == Meta)
+ t++;
+ }
+ if (s[i] == Meta)
+ i++;
+ }
+ break;
+
+ case 5:
+ /* Smallest at end, matching substrings */
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, l, l, fl);
+ return 1;
+ }
+ for (i = l; i--;) {
+ if (i && s[i-1] == Meta)
+ i--;
+ for (t = s + l, j = i; j >= 0; j--, t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s + j, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, j, t - s, fl);
+ return 1;
+ }
+ *t = sav;
+ if (t >= s+2 && t[-2] == Meta)
+ t--;
+ if (j >= 2 && s[j-2] == Meta)
+ j--;
+ }
+ }
+ break;
+
+ case 6:
+ /* Largest at start, matching substrings. */
+ for (i = l; i; i--) {
+ for (t = s, j = i; j <= l; j++, t++) {
+ sav = s[j];
+ s[j] = '\0';
+ if (domatch(t, c, 0) && !--n) {
+ s[j] = sav;
+ *sp = get_match_ret(*sp, t - s, j, fl);
+ return 1;
+ }
+ if ((s[j] = sav) == Meta)
+ j++;
+ if (*t == Meta)
+ t++;
+ }
+ if (i >= 2 && s[i-2] == Meta)
+ i--;
+ }
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, 0, 0, fl);
+ return 1;
+ }
+ break;
+
+ case 7:
+ /* Largest at end, matching substrings. */
+ for (i = 0; i < l; i++) {
+ for (t = s + l, j = i; j >= 0; j--, t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s + j, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, j, t - s, fl);
+ return 1;
+ }
+ *t = sav;
+ if (t >= s+2 && t[-2] == Meta)
+ t--;
+ if (j >= 2 && s[j-2] == Meta)
+ j--;
+ }
+ if (s[i] == Meta)
+ i++;
+ }
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, l, l, fl);
+ return 1;
+ }
+ break;
+ }
+ /* munge the whole string */
+ *sp = get_match_ret(*sp, 0, 0, fl);
+ return 1;
+}
+
+/* The main entry point for matching a string str against *
+ * a compiled pattern c. `fist' indicates whether leading *
+ * dots are special. */
+
+/**/
+int
+domatch(char *str, Comp c, int fist)
+{
+ int ret;
+ pptr = str;
+ first = fist;
+ if (*pptr == Nularg)
+ pptr++;
+ PERMALLOC {
+ ret = doesmatch(c);
+ } LASTALLOC;
+ return ret;
+}
+
+#define untok(C) (itok(C) ? ztokens[(C) - Pound] : (C))
+
+/* See if pattern has a matching exclusion (~...) part */
+
+/**/
+static int
+excluded(Comp c, char *eptr)
+{
+ char *saves = pptr;
+ int savei = first, ret;
+
+ first = 0;
+ if (PATHADDP(c) && pathpos) {
+ VARARR(char, buf, pathpos + strlen(eptr) + 1);
+
+ strcpy(buf, pathbuf);
+ strcpy(buf + pathpos, eptr);
+ pptr = buf;
+ ret = doesmatch(c->exclude);
+ } else {
+ pptr = eptr;
+ ret = doesmatch(c->exclude);
+ }
+ if (*pptr)
+ ret = 0;
+
+ pptr = saves;
+ first = savei;
+
+ return ret;
+}
+
+struct gclose {
+ char *start;
+ char *end;
+};
+typedef struct gclose *Gclose;
+
+static int inclosure; /* see comment in doesmatch() */
+
+/* Add a list of matches that fit the closure. trystring is a string of
+ * the same length as the target string; a non-zero in that string
+ * indicates that we have already tried to match the patterns following
+ * the closure (c->next) at that point and failed. This means that not
+ * only should we not bother using the corresponding match, we should
+ * also not bother going any further, since the first time we got to
+ * that position (when it was marked), we must already have failed on
+ * and backtracked over any further closure matches beyond that point.
+ */
+
+/**/
+static void
+addclosures(Comp c, LinkList closlist, int *pdone, char *trystring)
+{
+ Gclose gcnode;
+ char *opptr = pptr;
+
+ while (*pptr) {
+ if (STARP(c)) {
+ if (trystring[(pptr+1)-opptr])
+ break;
+ gcnode = (Gclose)zalloc(sizeof(struct gclose));
+ gcnode->start = pptr;
+ gcnode->end = ++pptr;
+ } else {
+ char *saves = pptr;
+ if (OPTIONALP(c) && *pdone >= 1)
+ return;
+ if (!matchonce(c) || saves == pptr ||
+ trystring[pptr-opptr]) {
+ pptr = saves;
+ break;
+ }
+ gcnode = (Gclose)zalloc(sizeof(struct gclose));
+ gcnode->start = saves;
+ gcnode->end = pptr;
+ }
+ pushnode(closlist, gcnode);
+ (*pdone)++;
+ }
+}
+
+/* see if current string in pptr matches c */
+
+/**/
+static int
+doesmatch(Comp c)
+{
+ if (CLOSUREP(c)) {
+ int done, retflag = 0;
+ char *saves, *trystring, *opptr;
+ LinkList closlist;
+ Gclose gcnode;
+
+ if (first && *pptr == '.')
+ return 0;
+
+ if (!inclosure && !c->left) {
+ /* We are not inside another closure, and the current
+ * pattern is a simple string. We handle this very common
+ * case specially: otherwise, matches like *foo* are
+ * extremely slow. Here, instead of backtracking, we track
+ * forward until we get a match. At top level, we are bound
+ * to get there eventually, so this is OK.
+ */
+ char looka;
+
+ if (STARP(c) && c->next &&
+ !c->next->left && (looka = *c->next->str) &&
+ !itok(looka)) {
+ /* Another simple optimisation for a very common case:
+ * we are processing a * and there is
+ * an ordinary character match next. We look ahead for
+ * that character, taking care of Meta bytes.
+ */
+ while (*pptr) {
+ for (; *pptr; pptr++) {
+ if (*pptr == Meta)
+ pptr++;
+ else if (*pptr == looka)
+ break;
+ }
+ if (!*(saves = pptr))
+ break;
+ if (doesmatch(c->next))
+ return 1;
+ pptr = saves+1;
+ }
+ } else {
+ /* Standard track-forward code */
+ for (done = 0; ; done++) {
+ saves = pptr;
+ if ((done || ONEHASHP(c) || OPTIONALP(c)) &&
+ ((!c->next && (!LASTP(c) || !*pptr)) ||
+ (c->next && doesmatch(c->next))))
+ return 1;
+ if (done && OPTIONALP(c))
+ return 0;
+ pptr = saves;
+ first = 0;
+ if (STARP(c)) {
+ if (!*pptr)
+ return 0;
+ pptr++;
+ } else if (!matchonce(c) || pptr == saves)
+ return 0;
+ }
+ }
+ return 0;
+ }
+ /* The full, gory backtracking code is now necessary. */
+ inclosure++;
+ closlist = newlinklist();
+ trystring = zcalloc(strlen(pptr)+1);
+ opptr = pptr;
+
+ /* Start by making a list where each match is as long
+ * as possible. We later have to take account of the
+ * fact that earlier matches may be too long.
+ */
+ done = 0;
+ addclosures(c, closlist, &done, trystring);
+ for (;;) {
+ if (TWOHASHP(c) && !done)
+ break;
+ saves = pptr;
+ /* do we really want this LASTP here?? */
+ if ((!c->next && (!LASTP(c) || !*pptr)) ||
+ (c->next && doesmatch(c->next))) {
+ retflag = 1;
+ break;
+ }
+ trystring[saves-opptr] = 1;
+ /*
+ * If we failed, the first thing to try is whether we can
+ * shorten the match using the last pattern in the closure.
+ */
+ gcnode = firstnode(closlist) ? peekfirst(closlist) : NULL;
+ if (gcnode && --gcnode->end > gcnode->start
+ && (gcnode->end[-1] != Meta ||
+ --gcnode->end > gcnode->start)) {
+ char savec = *gcnode->end;
+ *gcnode->end = '\0';
+ pptr = gcnode->start;
+ if (matchonce(c) && pptr != gcnode->start
+ && !trystring[pptr-opptr]) {
+ *gcnode->end = savec;
+ gcnode->end = pptr;
+ /* Try again to construct a list based on
+ * this new position
+ */
+ addclosures(c, closlist, &done, trystring+(pptr-opptr));
+ continue;
+ }
+ *gcnode->end = savec;
+ }
+ /* We've now exhausted the possibilities with that match,
+ * backtrack to the previous.
+ */
+ if ((gcnode = (Gclose)getlinknode(closlist))) {
+ pptr = gcnode->start;
+ zfree(gcnode, sizeof(struct gclose));
+ done--;
+ } else
+ break;
+ }
+ freelinklist(closlist, free);
+ zfree(trystring, strlen(opptr)+1);
+ inclosure--;
+
+ return retflag;
+ } else
+ return matchonce(c);
+}
+
+/**/
+static int
+posix_range(char **patptr, int ch)
+{
+ /* Match POSIX ranges, which correspond to ctype macros, *
+ * e.g. [:alpha:] -> isalpha. It just doesn't seem worth *
+ * the palaver of creating a hash table for this. */
+ char *start = *patptr;
+ int len;
+
+ /* we made sure in parsecomp() there was a ':' to search for */
+ *patptr = strchr(start, ':');
+ len = (*patptr)++ - start;
+
+ if (!strncmp(start, "alpha", len))
+ return isalpha(ch);
+ if (!strncmp(start, "alnum", len))
+ return isalnum(ch);
+ if (!strncmp(start, "blank", len))
+ return ch == ' ' || ch == '\t';
+ if (!strncmp(start, "cntrl", len))
+ return iscntrl(ch);
+ if (!strncmp(start, "digit", len))
+ return isdigit(ch);
+ if (!strncmp(start, "graph", len))
+ return isgraph(ch);
+ if (!strncmp(start, "lower", len))
+ return islower(ch);
+ if (!strncmp(start, "print", len))
+ return isprint(ch);
+ if (!strncmp(start, "punct", len))
+ return ispunct(ch);
+ if (!strncmp(start, "space", len))
+ return isspace(ch);
+ if (!strncmp(start, "upper", len))
+ return isupper(ch);
+ if (!strncmp(start, "xdigit", len))
+ return isxdigit(ch);
+ return 0;
+}
+
+/**/
+static void
+rangematch(char **patptr, int ch, int rchar)
+{
+ /* Check for a character in a [...] or [^...]. The [ *
+ * and optional ^ have already been skipped. */
+
+ char *pat = *patptr;
+#ifdef HAVE_STRCOLL
+ char l_buf[2], r_buf[2], ch_buf[2];
+
+ ch_buf[0] = ch;
+ l_buf[1] = r_buf[1] = ch_buf[1] = '\0';
+#endif
+
+#define PAT(X) (pat[X] == Meta ? pat[(X)+1] ^ 32 : untok(pat[X]))
+#define PPAT(X) (pat[(X)-1] == Meta ? pat[X] ^ 32 : untok(pat[X]))
+
+ for (pat++; *pat != Outbrack && *pat;
+ *pat == Meta ? pat += 2 : pat++) {
+ if (*pat == Inbrack) {
+ /* Inbrack can only occur inside a range if we found [:...:]. */
+ pat += 2;
+ if (posix_range(&pat, ch))
+ break;
+ } else if (*pat == '-' && pat[-1] != rchar &&
+ pat[1] != Outbrack) {
+#ifdef HAVE_STRCOLL
+ l_buf[0] = PPAT(-1);
+ r_buf[0] = PAT(1);
+ if (strcoll(l_buf, ch_buf) <= 0 &&
+ strcoll(ch_buf, r_buf) <= 0)
+#else
+ if (PPAT(-1) <= ch && PAT(1) >= ch)
+#endif
+ break;
+ } else if (ch == PAT(0))
+ break;
+ }
+
+ *patptr = pat;
+}
+
+/**/
+static int
+matchonce(Comp c)
+{
+ char *pat = c->str;
+ for (;;) {
+ /* loop until success or failure of pattern */
+ if (!pat || !*pat) {
+ /* No current pattern (c->str). */
+ char *saves;
+ int savei;
+
+ if (errflag)
+ return 0;
+ /* Remember state in case we need to go back and *
+ * check for exclusion of pattern or alternatives. */
+ saves = pptr;
+ savei = first;
+ /* Loop over alternatives with exclusions: (foo~bar|...). *
+ * Exclusions apply to the pattern in c->left. */
+ if (c->left || c->right) {
+ int ret = 0, ret2 = 0;
+ if (c->exclude) {
+ char *exclend = 0;
+
+ /* We may need to back up on things like `(*~foo)'
+ * if the `*' matched `foo' but needs to match `fo'.
+ * exclend marks the end of the shortened text. We
+ * need to restore it to match the tail.
+ * We set `inclosure' because we need the more
+ * sophisticated code in doesmatch() for any nested
+ * closures.
+ */
+ inclosure++;
+
+ while (!exclend || exclend >= pptr) {
+ char exclsav = 0;
+ if (exclend) {
+ exclsav = *exclend;
+ *exclend = '\0';
+ }
+ if ((ret = doesmatch(c->left))) {
+ if (exclend)
+ *exclend = exclsav;
+ exclsav = *(exclend = pptr);
+ *exclend = '\0';
+ ret2 = !excluded(c, saves);
+ }
+ if (exclend)
+ *exclend = exclsav;
+
+ if (!ret)
+ break;
+ if ((ret = ret2 &&
+ ((!c->next && (!LASTP(c) || !*pptr))
+ || (c->next && doesmatch(c->next)))) ||
+ (!c->next && LASTP(c)))
+ break;
+ /* Back up if necessary: exclend gives the position
+ * of the end of the match we are excluding,
+ * so only try to match to there.
+ */
+ exclend--;
+ pptr = saves;
+ }
+ inclosure--;
+ if (ret)
+ return 1;
+ } else
+ ret = doesmatch(c->left);
+ ret2 = 0;
+ if (c->right && (!ret || inclosure)) {
+ /* If in a closure, we always want the longest match. */
+ char *newpptr = pptr;
+ pptr = saves;
+ first = savei;
+ ret2 = doesmatch(c->right);
+ if (ret && (!ret2 || pptr < newpptr))
+ pptr = newpptr;
+ }
+ if (!ret && !ret2)
+ return 0;
+ }
+ if (CLOSUREP(c))
+ return 1;
+ if (!c->next) /* no more patterns left */
+ return (!LASTP(c) || !*pptr);
+ /* optimisation when next pattern is not a closure */
+ if (!CLOSUREP(c->next)) {
+ c = c->next;
+ pat = c->str;
+ continue;
+ }
+ return doesmatch(c->next);
+ }
+ /* Don't match leading dot if first is set */
+ if (first && *pptr == '.' && *pat != '.')
+ return 0;
+ if (*pat == Star) { /* final * is not expanded to ?#; returns success */
+ while (*pptr)
+ pptr++;
+ return 1;
+ }
+ first = 0; /* finished checking start of pattern */
+ if (*pat == Quest && *pptr) {
+ /* match exactly one character */
+ if (*pptr == Meta)
+ pptr++;
+ pptr++;
+ pat++;
+ continue;
+ }
+ if (*pat == Inbrack) {
+ /* Match groups of characters */
+ char ch;
+
+ if (!*pptr)
+ break;
+ ch = *pptr == Meta ? pptr[1] ^ 32 : *pptr;
+ if (pat[1] == Hat || pat[1] == '^' || pat[1] == '!') {
+ /* group is negated */
+ *++pat = Hat;
+ rangematch(&pat, ch, Hat);
+ DPUTS(!*pat, "BUG: something is very wrong in doesmatch()");
+ if (*pat != Outbrack)
+ break;
+ pat++;
+ *pptr == Meta ? pptr += 2 : pptr++;
+ continue;
+ } else {
+ /* pattern is not negated (affirmed? asserted?) */
+ rangematch(&pat, ch, Inbrack);
+ DPUTS(!pat || !*pat, "BUG: something is very wrong in doesmatch()");
+ if (*pat == Outbrack)
+ break;
+ for (*pptr == Meta ? pptr += 2 : pptr++;
+ *pat != Outbrack; pat++);
+ pat++;
+ continue;
+ }
+ }
+ if (*pat == Inang) {
+ /* Numeric globbing. */
+ unsigned long t1, t2, t3;
+ char *ptr;
+
+ if (!idigit(*pptr))
+ break;
+ if (*++pat == Outang ||
+ (*pat == '-' && pat[1] == Outang && ++pat)) {
+ /* <> or <->: any number matches */
+ while (idigit(*++pptr));
+ pat++;
+ } else {
+ /* Flag that there is no upper limit */
+ int not3 = 0;
+ char *opptr = pptr;
+ /*
+ * Form is <a-b>, where a or b are numbers or blank.
+ * t1 = number supplied: must be positive, so use
+ * unsigned arithmetic.
+ */
+ t1 = (unsigned long)zstrtol(pptr, &ptr, 10);
+ pptr = ptr;
+ /* t2 = lower limit */
+ if (idigit(*pat))
+ t2 = (unsigned long)zstrtol(pat, &ptr, 10);
+ else
+ t2 = 0, ptr = pat;
+ if (*ptr != '-' || (not3 = (ptr[1] == Outang)))
+ /* exact match or no upper limit */
+ t3 = t2, pat = ptr + not3;
+ else /* t3 = upper limit */
+ t3 = (unsigned long)zstrtol(ptr + 1, &pat, 10);
+ DPUTS(*pat != Outang, "BUG: wrong internal range pattern");
+ pat++;
+ /*
+ * If the number found is too large for the pattern,
+ * try matching just the first part. This way
+ * we always get the longest possible match.
+ */
+ while (!not3 && t1 > t3 && pptr > opptr+1) {
+ pptr--;
+ t1 /= 10;
+ }
+ if (t1 < t2 || (!not3 && t1 > t3))
+ break;
+ }
+ continue;
+ }
+ if (*pptr == *pat) {
+ /* just plain old characters */
+ pptr++;
+ pat++;
+ continue;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* turn a string into a Comp struct: this doesn't treat / specially */
+
+/**/
+Comp
+parsereg(char *str)
+{
+ remnulargs(str);
+ mode = 1; /* no path components */
+ pptr = str;
+ tail = NULL;
+ return parsecompsw(GF_TOPLEV);
+}
+
+/* blindly turn a string into a tokenised expression without lexing */
+
+/**/
+void
+tokenize(char *s)
+{
+ char *t;
+ int bslash = 0;
+
+ for (; *s; s++) {
+ cont:
+ switch (*s) {
+ case Bnull:
+ case '\\':
+ if (bslash) {
+ s[-1] = Bnull;
+ break;
+ }
+ bslash = 1;
+ continue;
+ case '<':
+ if (isset(SHGLOB))
+ break;
+ if (bslash) {
+ s[-1] = Bnull;
+ break;
+ }
+ t = s;
+ while (idigit(*++s));
+ if (*s != '-')
+ goto cont;
+ while (idigit(*++s));
+ if (*s != '>')
+ goto cont;
+ *t = Inang;
+ *s = Outang;
+ break;
+ case '^':
+ case '#':
+ case '~':
+ if (unset(EXTENDEDGLOB))
+ break;
+ case '(':
+ case '|':
+ case ')':
+ if (isset(SHGLOB))
+ break;
+ case '[':
+ case ']':
+ case '*':
+ case '?':
+ for (t = ztokens; *t; t++)
+ if (*t == *s) {
+ if (bslash)
+ s[-1] = Bnull;
+ else
+ *s = (t - ztokens) + Pound;
+ break;
+ }
+ }
+ bslash = 0;
+ }
+}
+
+/* remove unnecessary Nulargs */
+
+/**/
+void
+remnulargs(char *s)
+{
+ int nl = *s;
+ char *t = s;
+
+ while (*s)
+ if (INULL(*s))
+ chuck(s);
+ else
+ s++;
+ if (!*t && nl) {
+ t[0] = Nularg;
+ t[1] = '\0';
+ }
+}
+
+/* qualifier functions: mostly self-explanatory, see glob(). */
+
+/* device number */
+
+/**/
+static int
+qualdev(struct stat *buf, long dv)
+{
+ return buf->st_dev == dv;
+}
+
+/* number of hard links to file */
+
+/**/
+static int
+qualnlink(struct stat *buf, long ct)
+{
+ return (range < 0 ? buf->st_nlink < ct :
+ range > 0 ? buf->st_nlink > ct :
+ buf->st_nlink == ct);
+}
+
+/* user ID */
+
+/**/
+static int
+qualuid(struct stat *buf, long uid)
+{
+ return buf->st_uid == uid;
+}
+
+/* group ID */
+
+/**/
+static int
+qualgid(struct stat *buf, long gid)
+{
+ return buf->st_gid == gid;
+}
+
+/* device special file? */
+
+/**/
+static int
+qualisdev(struct stat *buf, long junk)
+{
+ return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
+}
+
+/* block special file? */
+
+/**/
+static int
+qualisblk(struct stat *buf, long junk)
+{
+ return S_ISBLK(buf->st_mode);
+}
+
+/* character special file? */
+
+/**/
+static int
+qualischr(struct stat *buf, long junk)
+{
+ return S_ISCHR(buf->st_mode);
+}
+
+/* directory? */
+
+/**/
+static int
+qualisdir(struct stat *buf, long junk)
+{
+ return S_ISDIR(buf->st_mode);
+}
+
+/* FIFO? */
+
+/**/
+static int
+qualisfifo(struct stat *buf, long junk)
+{
+ return S_ISFIFO(buf->st_mode);
+}
+
+/* symbolic link? */
+
+/**/
+static int
+qualislnk(struct stat *buf, long junk)
+{
+ return S_ISLNK(buf->st_mode);
+}
+
+/* regular file? */
+
+/**/
+static int
+qualisreg(struct stat *buf, long junk)
+{
+ return S_ISREG(buf->st_mode);
+}
+
+/* socket? */
+
+/**/
+static int
+qualissock(struct stat *buf, long junk)
+{
+ return S_ISSOCK(buf->st_mode);
+}
+
+/* given flag is set in mode */
+
+/**/
+static int
+qualflags(struct stat *buf, long mod)
+{
+ return mode_to_octal(buf->st_mode) & mod;
+}
+
+/* mode matches number supplied exactly */
+
+/**/
+static int
+qualeqflags(struct stat *buf, long mod)
+{
+ return mode_to_octal(buf->st_mode) == mod;
+}
+
+/* regular executable file? */
+
+/**/
+static int
+qualiscom(struct stat *buf, long mod)
+{
+ return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
+}
+
+/* size in required range? */
+
+/**/
+static int
+qualsize(struct stat *buf, long size)
+{
+ unsigned long scaled = buf->st_size;
+
+ switch (units) {
+ case TT_POSIX_BLOCKS:
+ scaled += 511l;
+ scaled /= 512l;
+ break;
+ case TT_KILOBYTES:
+ scaled += 1023l;
+ scaled /= 1024l;
+ break;
+ case TT_MEGABYTES:
+ scaled += 1048575l;
+ scaled /= 1048576l;
+ break;
+ }
+
+ return (range < 0 ? scaled < (unsigned long) size :
+ range > 0 ? scaled > (unsigned long) size :
+ scaled == (unsigned long) size);
+}
+
+/* time in required range? */
+
+/**/
+static int
+qualtime(struct stat *buf, long days)
+{
+ time_t now, diff;
+
+ time(&now);
+ diff = now - (amc == 0 ? buf->st_atime : amc == 1 ? buf->st_mtime :
+ buf->st_ctime);
+ /* handle multipliers indicating units */
+ switch (units) {
+ case TT_DAYS:
+ diff /= 86400l;
+ break;
+ case TT_HOURS:
+ diff /= 3600l;
+ break;
+ case TT_MINS:
+ diff /= 60l;
+ break;
+ case TT_WEEKS:
+ diff /= 604800l;
+ break;
+ case TT_MONTHS:
+ diff /= 2592000l;
+ break;
+ }
+
+ return (range < 0 ? diff < days :
+ range > 0 ? diff > days :
+ diff == days);
+}
diff --git a/Src/hashtable.c b/Src/hashtable.c
new file mode 100644
index 000000000..4adf3904d
--- /dev/null
+++ b/Src/hashtable.c
@@ -0,0 +1,1285 @@
+/*
+ * hashtable.c - hash tables
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "../config.h"
+
+#ifdef ZSH_HASH_DEBUG
+# define HASHTABLE_DEBUG_MEMBERS \
+ /* Members of struct hashtable used for debugging hash tables */ \
+ HashTable next, last; /* linked list of all hash tables */ \
+ char *tablename; /* string containing name of the hash table */ \
+ PrintTableStats printinfo; /* pointer to function to print table stats */
+#else /* !ZSH_HASH_DEBUG */
+# define HASHTABLE_DEBUG_MEMBERS
+#endif /* !ZSH_HASH_DEBUG */
+
+#define HASHTABLE_INTERNAL_MEMBERS \
+ ScanStatus scan; /* status of a scan over this hashtable */ \
+ HASHTABLE_DEBUG_MEMBERS
+
+typedef struct scanstatus *ScanStatus;
+
+#include "zsh.mdh"
+#include "hashtable.pro"
+
+/* Structure for recording status of a hashtable scan in progress. When a *
+ * scan starts, the .scan member of the hashtable structure points to one *
+ * of these. That member being non-NULL disables resizing of the *
+ * hashtable (when adding elements). When elements are deleted, the *
+ * contents of this structure is used to make sure the scan won't stumble *
+ * into the deleted element. */
+
+struct scanstatus {
+ int sorted;
+ union {
+ struct {
+ HashNode *tab;
+ int ct;
+ } s;
+ HashNode u;
+ } u;
+};
+
+/********************************/
+/* Generic Hash Table functions */
+/********************************/
+
+#ifdef ZSH_HASH_DEBUG
+static HashTable firstht, lastht;
+#endif /* ZSH_HASH_DEBUG */
+
+/* Generic hash function */
+
+/**/
+unsigned
+hasher(char *str)
+{
+ unsigned hashval = 0;
+
+ while (*str)
+ hashval += (hashval << 5) + ((unsigned) *str++);
+
+ return hashval;
+}
+
+/* Get a new hash table */
+
+/**/
+HashTable
+newhashtable(int size, char const *name, PrintTableStats printinfo)
+{
+ HashTable ht;
+
+ ht = (HashTable) zcalloc(sizeof *ht);
+#ifdef ZSH_HASH_DEBUG
+ ht->next = NULL;
+ if(!firstht)
+ firstht = ht;
+ ht->last = lastht;
+ if(lastht)
+ lastht->next = ht;
+ lastht = ht;
+ ht->printinfo = printinfo ? printinfo : printhashtabinfo;
+ ht->tablename = ztrdup(name);
+#endif /* ZSH_HASH_DEBUG */
+ ht->nodes = (HashNode *) zcalloc(size * sizeof(HashNode));
+ ht->hsize = size;
+ ht->ct = 0;
+ ht->scan = NULL;
+ return ht;
+}
+
+/* Delete a hash table. After this function has been used, any *
+ * existing pointers to the hash table are invalid. */
+
+/**/
+void
+deletehashtable(HashTable ht)
+{
+ ht->emptytable(ht);
+#ifdef ZSH_HASH_DEBUG
+ if(ht->next)
+ ht->next->last = ht->last;
+ else
+ lastht = ht->last;
+ if(ht->last)
+ ht->last->next = ht->next;
+ else
+ firstht = ht->next;
+#endif /* ZSH_HASH_DEBUG */
+ zfree(ht->nodes, ht->hsize * sizeof(HashNode));
+ zfree(ht, sizeof(*ht));
+}
+
+/* Add a node to a hash table. *
+ * nam is the key to use in hashing. dat is a pointer *
+ * to the node to add. If there is already a node in *
+ * the table with the same key, it is first freed, and *
+ * then the new node is added. If the number of nodes *
+ * is now greater than twice the number of hash values, *
+ * the table is then expanded. */
+
+/**/
+void
+addhashnode(HashTable ht, char *nam, void *nodeptr)
+{
+ unsigned hashval;
+ HashNode hn, hp, hq;
+
+ hn = (HashNode) nodeptr;
+ hn->nam = nam;
+
+ hashval = ht->hash(hn->nam) % ht->hsize;
+ hp = ht->nodes[hashval];
+
+ /* check if this is the first node for this hash value */
+ if (!hp) {
+ hn->next = NULL;
+ ht->nodes[hashval] = hn;
+ if (++ht->ct >= ht->hsize * 2 && !ht->scan)
+ expandhashtable(ht);
+ return;
+ }
+
+ /* else check if the first node contains the same key */
+ if (!strcmp(hp->nam, hn->nam)) {
+ ht->nodes[hashval] = hn;
+ replacing:
+ hn->next = hp->next;
+ if(ht->scan)
+ if(ht->scan->sorted) {
+ HashNode *tab = ht->scan->u.s.tab;
+ int i;
+ for(i = ht->scan->u.s.ct; i--; )
+ if(tab[i] == hp)
+ tab[i] = hn;
+ } else if(ht->scan->u.u == hp)
+ ht->scan->u.u = hn;
+ ht->freenode(hp);
+ return;
+ }
+
+ /* else run through the list and check all the keys */
+ hq = hp;
+ hp = hp->next;
+ for (; hp; hq = hp, hp = hp->next) {
+ if (!strcmp(hp->nam, hn->nam)) {
+ hq->next = hn;
+ goto replacing;
+ }
+ }
+
+ /* else just add it at the front of the list */
+ hn->next = ht->nodes[hashval];
+ ht->nodes[hashval] = hn;
+ if (++ht->ct >= ht->hsize * 2 && !ht->scan)
+ expandhashtable(ht);
+}
+
+/* Get an enabled entry in a hash table. *
+ * If successful, it returns a pointer to *
+ * the hashnode. If the node is DISABLED *
+ * or isn't found, it returns NULL */
+
+/**/
+HashNode
+gethashnode(HashTable ht, char *nam)
+{
+ unsigned hashval;
+ HashNode hp;
+
+ hashval = ht->hash(nam) % ht->hsize;
+ for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
+ if (!strcmp(hp->nam, nam)) {
+ if (hp->flags & DISABLED)
+ return NULL;
+ else
+ return hp;
+ }
+ }
+ return NULL;
+}
+
+/* Get an entry in a hash table. It will *
+ * ignore the DISABLED flag and return a *
+ * pointer to the hashnode if found, else *
+ * it returns NULL. */
+
+/**/
+HashNode
+gethashnode2(HashTable ht, char *nam)
+{
+ unsigned hashval;
+ HashNode hp;
+
+ hashval = ht->hash(nam) % ht->hsize;
+ for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
+ if (!strcmp(hp->nam, nam))
+ return hp;
+ }
+ return NULL;
+}
+
+/* Remove an entry from a hash table. *
+ * If successful, it removes the node from the *
+ * table and returns a pointer to it. If there *
+ * is no such node, then it returns NULL */
+
+/**/
+HashNode
+removehashnode(HashTable ht, char *nam)
+{
+ unsigned hashval;
+ HashNode hp, hq;
+
+ hashval = ht->hash(nam) % ht->hsize;
+ hp = ht->nodes[hashval];
+
+ /* if no nodes at this hash value, return NULL */
+ if (!hp)
+ return NULL;
+
+ /* else check if the key in the first one matches */
+ if (!strcmp(hp->nam, nam)) {
+ ht->nodes[hashval] = hp->next;
+ gotit:
+ ht->ct--;
+ if(ht->scan)
+ if(ht->scan->sorted) {
+ HashNode *tab = ht->scan->u.s.tab;
+ int i;
+ for(i = ht->scan->u.s.ct; i--; )
+ if(tab[i] == hp)
+ tab[i] = NULL;
+ } else if(ht->scan->u.u == hp)
+ ht->scan->u.u = hp->next;
+ return hp;
+ }
+
+ /* else run through the list and check the rest of the keys */
+ hq = hp;
+ hp = hp->next;
+ for (; hp; hq = hp, hp = hp->next) {
+ if (!strcmp(hp->nam, nam)) {
+ hq->next = hp->next;
+ goto gotit;
+ }
+ }
+
+ /* else it is not in the list, so return NULL */
+ return NULL;
+}
+
+/* Disable a node in a hash table */
+
+/**/
+void
+disablehashnode(HashNode hn, int flags)
+{
+ hn->flags |= DISABLED;
+}
+
+/* Enable a node in a hash table */
+
+/**/
+void
+enablehashnode(HashNode hn, int flags)
+{
+ hn->flags &= ~DISABLED;
+}
+
+/* Compare two hash table entries */
+
+/**/
+static int
+hnamcmp(const void *ap, const void *bp)
+{
+ HashNode a = *(HashNode *)ap;
+ HashNode b = *(HashNode *)bp;
+ return ztrcmp((unsigned char *) a->nam, (unsigned char *) b->nam);
+}
+
+/* Scan the nodes in a hash table and execute scanfunc on nodes based on
+ * the flags that are set/unset. scanflags is passed unchanged to
+ * scanfunc (if executed).
+ *
+ * If sorted != 0, then sort entries of hash table before scanning.
+ * If flags1 > 0, then execute scanfunc on a node only if at least one of
+ * these flags is set.
+ * If flags2 > 0, then execute scanfunc on a node only if all of
+ * these flags are NOT set.
+ * The conditions above for flags1/flags2 must both be true.
+ *
+ * It is safe to add, remove or replace hash table elements from within
+ * the scanfunc. Replaced elements will appear in the scan exactly once,
+ * the new version if it was not scanned before the replacement was made.
+ * Added elements might or might not appear in the scan.
+ */
+
+/**/
+void
+scanhashtable(HashTable ht, int sorted, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
+{
+ struct scanstatus st;
+
+ if (sorted) {
+ int i, ct = ht->ct;
+ VARARR(HashNode, hnsorttab, ct);
+ HashNode *htp, hn;
+
+ for (htp = hnsorttab, i = 0; i < ht->hsize; i++)
+ for (hn = ht->nodes[i]; hn; hn = hn->next)
+ *htp++ = hn;
+ qsort((void *)hnsorttab, ct, sizeof(HashNode), hnamcmp);
+
+ st.sorted = 1;
+ st.u.s.tab = hnsorttab;
+ st.u.s.ct = ct;
+ ht->scan = &st;
+
+ for (htp = hnsorttab, i = 0; i < ct; i++, htp++)
+ if (*htp && ((*htp)->flags & flags1) + !flags1 &&
+ !((*htp)->flags & flags2))
+ scanfunc(*htp, scanflags);
+
+ ht->scan = NULL;
+ } else {
+ int i, hsize = ht->hsize;
+ HashNode *nodes = ht->nodes;
+
+ st.sorted = 0;
+ ht->scan = &st;
+
+ for (i = 0; i < hsize; i++)
+ for (st.u.u = nodes[i]; st.u.u; ) {
+ HashNode hn = st.u.u;
+ st.u.u = st.u.u->next;
+ if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2))
+ scanfunc(hn, scanflags);
+ }
+
+ ht->scan = NULL;
+ }
+}
+
+/* Scan all nodes in a hash table and executes scanfunc on the *
+ * nodes which meet all the following criteria: *
+ * The hash key must match the glob pattern given by `com'. *
+ * If (flags1 > 0), then any flag in flags1 must be set. *
+ * If (flags2 > 0), then all flags in flags2 must NOT be set. *
+ * *
+ * scanflags is passed unchanged to scanfunc (if executed). *
+ * The return value is the number of matches. */
+
+/**/
+int
+scanmatchtable(HashTable ht, Comp com, int flags1, int flags2, ScanFunc scanfunc, int scanflags)
+{
+ int i, hsize = ht->hsize;
+ HashNode *nodes = ht->nodes;
+ int match = 0;
+ struct scanstatus st;
+
+ st.sorted = 0;
+ ht->scan = &st;
+
+ for (i = 0; i < hsize; i++)
+ for (st.u.u = nodes[i]; st.u.u; ) {
+ HashNode hn = st.u.u;
+ st.u.u = st.u.u->next;
+ if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2) &&
+ domatch(hn->nam, com, 0))
+ scanfunc(hn, scanflags);
+ match++;
+ }
+
+ ht->scan = NULL;
+
+ return match;
+}
+
+/* Expand hash tables when they get too many entries. *
+ * The new size is 4 times the previous size. */
+
+/**/
+static void
+expandhashtable(HashTable ht)
+{
+ struct hashnode **onodes, **ha, *hn, *hp;
+ int i, osize;
+
+ osize = ht->hsize;
+ onodes = ht->nodes;
+
+ ht->hsize = osize * 4;
+ ht->nodes = (HashNode *) zcalloc(ht->hsize * sizeof(HashNode));
+ ht->ct = 0;
+
+ /* scan through the old list of nodes, and *
+ * rehash them into the new list of nodes */
+ for (i = 0, ha = onodes; i < osize; i++, ha++) {
+ for (hn = *ha; hn;) {
+ hp = hn->next;
+ ht->addnode(ht, hn->nam, hn);
+ hn = hp;
+ }
+ }
+ zfree(onodes, osize * sizeof(HashNode));
+}
+
+/* Empty the hash table and resize it if necessary */
+
+/**/
+static void
+resizehashtable(HashTable ht, int newsize)
+{
+ struct hashnode **ha, *hn, *hp;
+ int i;
+
+ /* free all the hash nodes */
+ ha = ht->nodes;
+ for (i = 0; i < ht->hsize; i++, ha++) {
+ for (hn = *ha; hn;) {
+ hp = hn->next;
+ ht->freenode(hn);
+ hn = hp;
+ }
+ }
+
+ /* If new size desired is different from current size, *
+ * we free it and allocate a new nodes array. */
+ if (ht->hsize != newsize) {
+ zfree(ht->nodes, ht->hsize * sizeof(HashNode));
+ ht->nodes = (HashNode *) zcalloc(newsize * sizeof(HashNode));
+ ht->hsize = newsize;
+ } else {
+ /* else we just re-zero the current nodes array */
+ memset(ht->nodes, 0, newsize * sizeof(HashNode));
+ }
+
+ ht->ct = 0;
+}
+
+/* Generic method to empty a hash table */
+
+/**/
+void
+emptyhashtable(HashTable ht)
+{
+ resizehashtable(ht, ht->hsize);
+}
+
+#ifdef ZSH_HASH_DEBUG
+
+/* Print info about hash table */
+
+#define MAXDEPTH 7
+
+/**/
+static void
+printhashtabinfo(HashTable ht)
+{
+ HashNode hn;
+ int chainlen[MAXDEPTH + 1];
+ int i, tmpcount, total;
+
+ printf("name of table : %s\n", ht->tablename);
+ printf("size of nodes[] : %d\n", ht->hsize);
+ printf("number of nodes : %d\n\n", ht->ct);
+
+ memset(chainlen, 0, sizeof(chainlen));
+
+ /* count the number of nodes just to be sure */
+ total = 0;
+ for (i = 0; i < ht->hsize; i++) {
+ tmpcount = 0;
+ for (hn = ht->nodes[i]; hn; hn = hn->next)
+ tmpcount++;
+ if (tmpcount >= MAXDEPTH)
+ chainlen[MAXDEPTH]++;
+ else
+ chainlen[tmpcount]++;
+ total += tmpcount;
+ }
+
+ for (i = 0; i < MAXDEPTH; i++)
+ printf("number of hash values with chain of length %d : %4d\n", i, chainlen[i]);
+ printf("number of hash values with chain of length %d+ : %4d\n", MAXDEPTH, chainlen[MAXDEPTH]);
+ printf("total number of nodes : %4d\n", total);
+}
+
+/**/
+int
+bin_hashinfo(char *nam, char **args, char *ops, int func)
+{
+ HashTable ht;
+ printf("----------------------------------------------------\n");
+ for(ht = firstht; ht; ht = ht->next) {
+ ht->printinfo(ht);
+ printf("----------------------------------------------------\n");
+ }
+ return 0;
+}
+
+#endif /* ZSH_HASH_DEBUG */
+
+/********************************/
+/* Command Hash Table Functions */
+/********************************/
+
+/* hash table containing external commands */
+
+/**/
+HashTable cmdnamtab;
+
+/* how far we've hashed the PATH so far */
+
+/**/
+char **pathchecked;
+
+/* Create a new command hash table */
+
+/**/
+void
+createcmdnamtable(void)
+{
+ cmdnamtab = newhashtable(201, "cmdnamtab", NULL);
+
+ cmdnamtab->hash = hasher;
+ cmdnamtab->emptytable = emptycmdnamtable;
+ cmdnamtab->filltable = fillcmdnamtable;
+ cmdnamtab->addnode = addhashnode;
+ cmdnamtab->getnode = gethashnode2;
+ cmdnamtab->getnode2 = gethashnode2;
+ cmdnamtab->removenode = removehashnode;
+ cmdnamtab->disablenode = NULL;
+ cmdnamtab->enablenode = NULL;
+ cmdnamtab->freenode = freecmdnamnode;
+ cmdnamtab->printnode = printcmdnamnode;
+
+ pathchecked = path;
+}
+
+/**/
+static void
+emptycmdnamtable(HashTable ht)
+{
+ emptyhashtable(ht);
+ pathchecked = path;
+}
+
+/* Add all commands in a given directory *
+ * to the command hashtable. */
+
+/**/
+void
+hashdir(char **dirp)
+{
+ Cmdnam cn;
+ DIR *dir;
+ char *fn;
+
+ if (isrelative(*dirp) || !(dir = opendir(unmeta(*dirp))))
+ return;
+
+ while ((fn = zreaddir(dir, 1))) {
+ if (!cmdnamtab->getnode(cmdnamtab, fn)) {
+ cn = (Cmdnam) zcalloc(sizeof *cn);
+ cn->flags = 0;
+ cn->u.name = dirp;
+ cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn);
+ }
+ }
+ closedir(dir);
+}
+
+/* Go through user's PATH and add everything to *
+ * the command hashtable. */
+
+/**/
+static void
+fillcmdnamtable(HashTable ht)
+{
+ char **pq;
+
+ for (pq = pathchecked; *pq; pq++)
+ hashdir(pq);
+
+ pathchecked = pq;
+}
+
+/**/
+static void
+freecmdnamnode(HashNode hn)
+{
+ Cmdnam cn = (Cmdnam) hn;
+
+ zsfree(cn->nam);
+ if (cn->flags & HASHED)
+ zsfree(cn->u.cmd);
+
+ zfree(cn, sizeof(struct cmdnam));
+}
+
+/* Print an element of the cmdnamtab hash table (external command) */
+
+/**/
+static void
+printcmdnamnode(HashNode hn, int printflags)
+{
+ Cmdnam cn = (Cmdnam) hn;
+
+ if (printflags & PRINT_WHENCE_WORD) {
+ printf("%s: %s\n", cn->nam, (cn->flags & HASHED) ?
+ "hashed" : "command");
+ return;
+ }
+
+ if ((printflags & PRINT_WHENCE_CSH) || (printflags & PRINT_WHENCE_SIMPLE)) {
+ if (cn->flags & HASHED) {
+ zputs(cn->u.cmd, stdout);
+ putchar('\n');
+ } else {
+ zputs(*(cn->u.name), stdout);
+ putchar('/');
+ zputs(cn->nam, stdout);
+ putchar('\n');
+ }
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ if (cn->flags & HASHED) {
+ nicezputs(cn->nam, stdout);
+ printf(" is hashed to ");
+ nicezputs(cn->u.cmd, stdout);
+ putchar('\n');
+ } else {
+ nicezputs(cn->nam, stdout);
+ printf(" is ");
+ nicezputs(*(cn->u.name), stdout);
+ putchar('/');
+ nicezputs(cn->nam, stdout);
+ putchar('\n');
+ }
+ return;
+ }
+
+ if (cn->flags & HASHED) {
+ quotedzputs(cn->nam, stdout);
+ putchar('=');
+ quotedzputs(cn->u.cmd, stdout);
+ putchar('\n');
+ } else {
+ quotedzputs(cn->nam, stdout);
+ putchar('=');
+ quotedzputs(*(cn->u.name), stdout);
+ putchar('/');
+ quotedzputs(cn->nam, stdout);
+ putchar('\n');
+ }
+}
+
+/***************************************/
+/* Shell Function Hash Table Functions */
+/***************************************/
+
+/* hash table containing the shell functions */
+
+/**/
+HashTable shfunctab;
+
+/**/
+void
+createshfunctable(void)
+{
+ shfunctab = newhashtable(7, "shfunctab", NULL);
+
+ shfunctab->hash = hasher;
+ shfunctab->emptytable = NULL;
+ shfunctab->filltable = NULL;
+ shfunctab->addnode = addhashnode;
+ shfunctab->getnode = gethashnode;
+ shfunctab->getnode2 = gethashnode2;
+ shfunctab->removenode = removeshfuncnode;
+ shfunctab->disablenode = disableshfuncnode;
+ shfunctab->enablenode = enableshfuncnode;
+ shfunctab->freenode = freeshfuncnode;
+ shfunctab->printnode = printshfuncnode;
+}
+
+/* Remove an entry from the shell function hash table. *
+ * It checks if the function is a signal trap and if so, *
+ * it will disable the trapping of that signal. */
+
+/**/
+static HashNode
+removeshfuncnode(HashTable ht, char *nam)
+{
+ HashNode hn;
+
+ if ((hn = removehashnode(shfunctab, nam))) {
+ if (!strncmp(hn->nam, "TRAP", 4))
+ unsettrap(getsignum(hn->nam + 4));
+ return hn;
+ } else
+ return NULL;
+}
+
+/* Disable an entry in the shell function hash table. *
+ * It checks if the function is a signal trap and if so, *
+ * it will disable the trapping of that signal. */
+
+/**/
+static void
+disableshfuncnode(HashNode hn, int flags)
+{
+ hn->flags |= DISABLED;
+ if (!strncmp(hn->nam, "TRAP", 4)) {
+ int signum = getsignum(hn->nam + 4);
+ sigtrapped[signum] &= ~ZSIG_FUNC;
+ sigfuncs[signum] = NULL;
+ unsettrap(signum);
+ }
+}
+
+/* Re-enable an entry in the shell function hash table. *
+ * It checks if the function is a signal trap and if so, *
+ * it will re-enable the trapping of that signal. */
+
+/**/
+static void
+enableshfuncnode(HashNode hn, int flags)
+{
+ Shfunc shf = (Shfunc) hn;
+ int signum;
+
+ shf->flags &= ~DISABLED;
+ if (!strncmp(shf->nam, "TRAP", 4)) {
+ signum = getsignum(shf->nam + 4);
+ if (signum != -1) {
+ settrap(signum, shf->funcdef);
+ sigtrapped[signum] |= ZSIG_FUNC;
+ }
+ }
+}
+
+/**/
+static void
+freeshfuncnode(HashNode hn)
+{
+ Shfunc shf = (Shfunc) hn;
+
+ zsfree(shf->nam);
+ if (shf->funcdef)
+ freestruct(shf->funcdef);
+ zfree(shf, sizeof(struct shfunc));
+}
+
+/* Print a shell function */
+
+/**/
+static void
+printshfuncnode(HashNode hn, int printflags)
+{
+ Shfunc f = (Shfunc) hn;
+ char *t;
+
+ if ((printflags & PRINT_NAMEONLY) ||
+ ((printflags & PRINT_WHENCE_SIMPLE) &&
+ !(printflags & PRINT_WHENCE_FUNCDEF))) {
+ zputs(f->nam, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if ((printflags & (PRINT_WHENCE_VERBOSE|PRINT_WHENCE_WORD)) &&
+ !(printflags & PRINT_WHENCE_FUNCDEF)) {
+ nicezputs(f->nam, stdout);
+ printf((printflags & PRINT_WHENCE_WORD) ? ": function\n" :
+ " is a shell function\n");
+ return;
+ }
+
+ if (f->flags & PM_UNDEFINED)
+ printf("undefined ");
+ if (f->flags & PM_TAGGED)
+ printf("traced ");
+ if ((f->flags & PM_UNDEFINED) || !f->funcdef) {
+ nicezputs(f->nam, stdout);
+ printf(" () { }\n");
+ return;
+ }
+
+ t = getpermtext((void *) dupstruct((void *) f->funcdef));
+ quotedzputs(f->nam, stdout);
+ printf(" () {\n\t");
+ zputs(t, stdout);
+ printf("\n}\n");
+ zsfree(t);
+}
+
+/**************************************/
+/* Reserved Word Hash Table Functions */
+/**************************************/
+
+/* Nodes for reserved word hash table */
+
+static struct reswd reswds[] = {
+ {NULL, "!", 0, BANG},
+ {NULL, "[[", 0, DINBRACK},
+ {NULL, "{", 0, INBRACE},
+ {NULL, "}", 0, OUTBRACE},
+ {NULL, "case", 0, CASE},
+ {NULL, "coproc", 0, COPROC},
+ {NULL, "do", 0, DO},
+ {NULL, "done", 0, DONE},
+ {NULL, "elif", 0, ELIF},
+ {NULL, "else", 0, ELSE},
+ {NULL, "end", 0, ZEND},
+ {NULL, "esac", 0, ESAC},
+ {NULL, "fi", 0, FI},
+ {NULL, "for", 0, FOR},
+ {NULL, "foreach", 0, FOREACH},
+ {NULL, "function", 0, FUNC},
+ {NULL, "if", 0, IF},
+ {NULL, "nocorrect", 0, NOCORRECT},
+ {NULL, "repeat", 0, REPEAT},
+ {NULL, "select", 0, SELECT},
+ {NULL, "then", 0, THEN},
+ {NULL, "time", 0, TIME},
+ {NULL, "until", 0, UNTIL},
+ {NULL, "while", 0, WHILE},
+ {NULL, NULL}
+};
+
+/* hash table containing the reserved words */
+
+/**/
+HashTable reswdtab;
+
+/* Build the hash table containing zsh's reserved words. */
+
+/**/
+void
+createreswdtable(void)
+{
+ Reswd rw;
+
+ reswdtab = newhashtable(23, "reswdtab", NULL);
+
+ reswdtab->hash = hasher;
+ reswdtab->emptytable = NULL;
+ reswdtab->filltable = NULL;
+ reswdtab->addnode = addhashnode;
+ reswdtab->getnode = gethashnode;
+ reswdtab->getnode2 = gethashnode2;
+ reswdtab->removenode = NULL;
+ reswdtab->disablenode = disablehashnode;
+ reswdtab->enablenode = enablehashnode;
+ reswdtab->freenode = NULL;
+ reswdtab->printnode = printreswdnode;
+
+ for (rw = reswds; rw->nam; rw++)
+ reswdtab->addnode(reswdtab, rw->nam, rw);
+}
+
+/* Print a reserved word */
+
+/**/
+static void
+printreswdnode(HashNode hn, int printflags)
+{
+ Reswd rw = (Reswd) hn;
+
+ if (printflags & PRINT_WHENCE_WORD) {
+ printf("%s: reserved\n", rw->nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_CSH) {
+ printf("%s: shell reserved word\n", rw->nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ printf("%s is a reserved word\n", rw->nam);
+ return;
+ }
+
+ /* default is name only */
+ printf("%s\n", rw->nam);
+}
+
+/********************************/
+/* Aliases Hash Table Functions */
+/********************************/
+
+/* hash table containing the aliases */
+
+/**/
+HashTable aliastab;
+
+/* Create new hash table for aliases */
+
+/**/
+void
+createaliastable(void)
+{
+ aliastab = newhashtable(23, "aliastab", NULL);
+
+ aliastab->hash = hasher;
+ aliastab->emptytable = NULL;
+ aliastab->filltable = NULL;
+ aliastab->addnode = addhashnode;
+ aliastab->getnode = gethashnode;
+ aliastab->getnode2 = gethashnode2;
+ aliastab->removenode = removehashnode;
+ aliastab->disablenode = disablehashnode;
+ aliastab->enablenode = enablehashnode;
+ aliastab->freenode = freealiasnode;
+ aliastab->printnode = printaliasnode;
+
+ /* add the default aliases */
+ aliastab->addnode(aliastab, ztrdup("run-help"), createaliasnode(ztrdup("man"), 0));
+ aliastab->addnode(aliastab, ztrdup("which-command"), createaliasnode(ztrdup("whence"), 0));
+}
+
+/* Create a new alias node */
+
+/**/
+Alias
+createaliasnode(char *txt, int flags)
+{
+ Alias al;
+
+ al = (Alias) zcalloc(sizeof *al);
+ al->flags = flags;
+ al->text = txt;
+ al->inuse = 0;
+ return al;
+}
+
+/**/
+static void
+freealiasnode(HashNode hn)
+{
+ Alias al = (Alias) hn;
+
+ zsfree(al->nam);
+ zsfree(al->text);
+ zfree(al, sizeof(struct alias));
+}
+
+/* Print an alias */
+
+/**/
+static void
+printaliasnode(HashNode hn, int printflags)
+{
+ Alias a = (Alias) hn;
+
+ if (printflags & PRINT_NAMEONLY) {
+ zputs(a->nam, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_WORD) {
+ printf("%s: alias\n", a->nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_SIMPLE) {
+ zputs(a->text, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_CSH) {
+ nicezputs(a->nam, stdout);
+ if (a->flags & ALIAS_GLOBAL)
+ printf(": globally aliased to ");
+ else
+ printf(": aliased to ");
+ nicezputs(a->text, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ nicezputs(a->nam, stdout);
+ if (a->flags & ALIAS_GLOBAL)
+ printf(" is a global alias for ");
+ else
+ printf(" is an alias for ");
+ nicezputs(a->text, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_LIST) {
+ printf("alias ");
+ if (a->flags & ALIAS_GLOBAL)
+ printf("-g ");
+
+ /* If an alias begins with `-', then we must output `-- ' *
+ * first, so that it is not interpreted as an option. */
+ if(a->nam[0] == '-')
+ printf("-- ");
+ }
+
+ quotedzputs(a->nam, stdout);
+ putchar('=');
+ quotedzputs(a->text, stdout);
+ putchar('\n');
+}
+
+/**********************************/
+/* Parameter Hash Table Functions */
+/**********************************/
+
+/**/
+void
+freeparamnode(HashNode hn)
+{
+ Param pm = (Param) hn;
+
+ zsfree(pm->nam);
+ zfree(pm, sizeof(struct param));
+}
+
+/* Print a parameter */
+
+/**/
+void
+printparamnode(HashNode hn, int printflags)
+{
+ Param p = (Param) hn;
+ char *t, **u;
+
+ if (p->flags & PM_UNSET)
+ return;
+
+ /* Print the attributes of the parameter */
+ if (printflags & PRINT_TYPE) {
+ if (p->flags & PM_INTEGER)
+ printf("integer ");
+ if (p->flags & PM_ARRAY)
+ printf("array ");
+ if (p->flags & PM_LEFT)
+ printf("left justified %d ", p->ct);
+ if (p->flags & PM_RIGHT_B)
+ printf("right justified %d ", p->ct);
+ if (p->flags & PM_RIGHT_Z)
+ printf("zero filled %d ", p->ct);
+ if (p->flags & PM_LOWER)
+ printf("lowercase ");
+ if (p->flags & PM_UPPER)
+ printf("uppercase ");
+ if (p->flags & PM_READONLY)
+ printf("readonly ");
+ if (p->flags & PM_TAGGED)
+ printf("tagged ");
+ if (p->flags & PM_EXPORTED)
+ printf("exported ");
+ }
+
+ if (printflags & PRINT_NAMEONLY) {
+ zputs(p->nam, stdout);
+ putchar('\n');
+ return;
+ }
+
+ /* How the value is displayed depends *
+ * on the type of the parameter */
+ quotedzputs(p->nam, stdout);
+ putchar('=');
+ switch (PM_TYPE(p->flags)) {
+ case PM_SCALAR:
+ /* string: simple output */
+ if (p->gets.cfn && (t = p->gets.cfn(p)))
+ quotedzputs(t, stdout);
+ putchar('\n');
+ break;
+ case PM_INTEGER:
+ /* integer */
+ printf("%ld\n", p->gets.ifn(p));
+ break;
+ case PM_ARRAY:
+ /* array */
+ putchar('(');
+ u = p->gets.afn(p);
+ if(*u) {
+ quotedzputs(*u++, stdout);
+ while (*u) {
+ putchar(' ');
+ quotedzputs(*u++, stdout);
+ }
+ }
+ printf(")\n");
+ break;
+ }
+}
+
+/****************************************/
+/* Named Directory Hash Table Functions */
+/****************************************/
+
+/* hash table containing named directories */
+
+/**/
+HashTable nameddirtab;
+
+/* != 0 if all the usernames have already been *
+ * added to the named directory hash table. */
+
+static int allusersadded;
+
+/* Create new hash table for named directories */
+
+/**/
+void
+createnameddirtable(void)
+{
+ nameddirtab = newhashtable(201, "nameddirtab", NULL);
+
+ nameddirtab->hash = hasher;
+ nameddirtab->emptytable = emptynameddirtable;
+ nameddirtab->filltable = fillnameddirtable;
+ nameddirtab->addnode = addnameddirnode;
+ nameddirtab->getnode = gethashnode;
+ nameddirtab->getnode2 = gethashnode2;
+ nameddirtab->removenode = removenameddirnode;
+ nameddirtab->disablenode = NULL;
+ nameddirtab->enablenode = NULL;
+ nameddirtab->freenode = freenameddirnode;
+ nameddirtab->printnode = printnameddirnode;
+
+ allusersadded = 0;
+ finddir(NULL); /* clear the finddir cache */
+}
+
+/* Empty the named directories table */
+
+/**/
+static void
+emptynameddirtable(HashTable ht)
+{
+ emptyhashtable(ht);
+ allusersadded = 0;
+ finddir(NULL); /* clear the finddir cache */
+}
+
+/* Add all the usernames in the password file/database *
+ * to the named directories table. */
+
+/**/
+static void
+fillnameddirtable(HashTable ht)
+{
+#ifdef HAVE_GETPWENT
+ if (!allusersadded) {
+ struct passwd *pw;
+
+ setpwent();
+
+ /* loop through the password file/database *
+ * and add all entries returned. */
+ while ((pw = getpwent()) && !errflag)
+ adduserdir(ztrdup(pw->pw_name), pw->pw_dir, ND_USERNAME, 1);
+
+ endpwent();
+ allusersadded = 1;
+ }
+ return;
+#endif /* HAVE_GETPWENT */
+}
+
+/* Add an entry to the named directory hash *
+ * table, clearing the finddir() cache and *
+ * initialising the `diff' member. */
+
+/**/
+static void
+addnameddirnode(HashTable ht, char *nam, void *nodeptr)
+{
+ Nameddir nd = (Nameddir) nodeptr;
+
+ nd->diff = strlen(nd->dir) - strlen(nam);
+ finddir(NULL); /* clear the finddir cache */
+ addhashnode(ht, nam, nodeptr);
+}
+
+/* Remove an entry from the named directory *
+ * hash table, clearing the finddir() cache. */
+
+/**/
+static HashNode
+removenameddirnode(HashTable ht, char *nam)
+{
+ HashNode hn = removehashnode(ht, nam);
+
+ if(hn)
+ finddir(NULL); /* clear the finddir cache */
+ return hn;
+}
+
+/* Free up the memory used by a named directory hash node. */
+
+/**/
+static void
+freenameddirnode(HashNode hn)
+{
+ Nameddir nd = (Nameddir) hn;
+
+ zsfree(nd->nam);
+ zsfree(nd->dir);
+ zfree(nd, sizeof(struct nameddir));
+}
+
+/* Print a named directory */
+
+/**/
+static void
+printnameddirnode(HashNode hn, int printflags)
+{
+ Nameddir nd = (Nameddir) hn;
+
+ if (printflags & PRINT_NAMEONLY) {
+ zputs(nd->nam, stdout);
+ putchar('\n');
+ return;
+ }
+
+ quotedzputs(nd->nam, stdout);
+ putchar('=');
+ quotedzputs(nd->dir, stdout);
+ putchar('\n');
+}
diff --git a/Src/hashtable.h b/Src/hashtable.h
new file mode 100644
index 000000000..5b78c9c18
--- /dev/null
+++ b/Src/hashtable.h
@@ -0,0 +1,62 @@
+/*
+ * hashtable.h - header file for hash table handling code
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/* Builtin function numbers; used by handler functions that handle more *
+ * than one builtin. Note that builtins such as compctl, that are not *
+ * overloaded, don't get a number. */
+
+#define BIN_TYPESET 0
+#define BIN_BG 1
+#define BIN_FG 2
+#define BIN_JOBS 3
+#define BIN_WAIT 4
+#define BIN_DISOWN 5
+#define BIN_BREAK 6
+#define BIN_CONTINUE 7
+#define BIN_EXIT 8
+#define BIN_RETURN 9
+#define BIN_CD 10
+#define BIN_POPD 11
+#define BIN_PUSHD 12
+#define BIN_PRINT 13
+#define BIN_EVAL 14
+#define BIN_SCHED 15
+#define BIN_FC 16
+#define BIN_PUSHLINE 17
+#define BIN_LOGOUT 18
+#define BIN_TEST 19
+#define BIN_BRACKET 20
+#define BIN_EXPORT 21
+#define BIN_ECHO 22
+#define BIN_DISABLE 23
+#define BIN_ENABLE 24
+
+/* These currently depend on being 0 and 1. */
+#define BIN_SETOPT 0
+#define BIN_UNSETOPT 1
diff --git a/Src/hist.c b/Src/hist.c
new file mode 100644
index 000000000..a4c5735c1
--- /dev/null
+++ b/Src/hist.c
@@ -0,0 +1,1670 @@
+/*
+ * hist.c - history expansion
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "hist.pro"
+
+/* != 0 means history substitution is turned off */
+
+/**/
+int stophist;
+
+/* this line began with a space, so junk it if HISTIGNORESPACE is on */
+
+/**/
+int spaceflag;
+
+/* if != 0, we are expanding the current line */
+
+/**/
+int expanding;
+
+/* these are used to modify the cursor position during expansion */
+
+/**/
+int excs, exlast;
+
+/*
+ * Current history event number
+ *
+ * Note on curhist: with history inactive, this points to the
+ * last line actually added to the history list. With history active,
+ * the line does not get added to the list until hend(), if at all.
+ * However, curhist is incremented to reflect the current line anyway.
+ * Thus if the line is not added to the list, curhist must be
+ * decremented in hend().
+ */
+
+/**/
+int curhist;
+
+/* number of history entries */
+
+/**/
+int histentct;
+
+/* array of history entries */
+
+/**/
+Histent histentarr;
+
+/* capacity of history lists */
+
+/**/
+int histsiz;
+
+/* if = 1, we have performed history substitution on the current line *
+ * if = 2, we have used the 'p' modifier */
+
+/**/
+int histdone;
+
+/* state of the history mechanism */
+
+/**/
+int histactive;
+
+/* Bits of histactive variable */
+#define HA_ACTIVE (1<<0) /* History mechanism is active */
+#define HA_NOSTORE (1<<1) /* Don't store the line when finished */
+#define HA_JUNKED (1<<2) /* Last history line was already junked */
+#define HA_NOINC (1<<3) /* Don't store, curhist not incremented */
+
+/* Array of word beginnings and endings in current history line. */
+
+/**/
+short *chwords;
+
+/* Max, actual position in chwords.
+ * nwords = chwordpos/2 because we record beginning and end of words.
+ */
+
+/**/
+int chwordlen, chwordpos;
+
+/* the last l for s/l/r/ history substitution */
+
+/**/
+char *hsubl;
+
+/* the last r for s/l/r/ history substitution */
+
+/**/
+char *hsubr;
+
+/* pointer into the history line */
+
+/**/
+char *hptr;
+
+/* the current history line */
+
+/**/
+char *chline;
+
+/* true if the last character returned by hgetc was an escaped bangchar *
+ * if it is set and NOBANGHIST is unset hwaddc escapes bangchars */
+
+/**/
+int qbang;
+
+/* max size of histline */
+
+/**/
+int hlinesz;
+
+/* default event (usually curhist-1, that is, "!!") */
+
+static int defev;
+
+/* add a character to the current history word */
+
+/**/
+void
+hwaddc(int c)
+{
+ /* Only if history line exists and lexing has not finished. */
+ if (chline && !(errflag || lexstop)) {
+ /* Quote un-expanded bangs in the history line. */
+ if (c == bangchar && stophist < 2 && qbang)
+ /* If qbang is not set, we do not escape this bangchar as it's *
+ * not mecessary (e.g. it's a bang in !=, or it is followed *
+ * by a space). Roughly speaking, qbang is zero only if the *
+ * history interpreter has already digested this bang and *
+ * found that it is not necessary to escape it. */
+ hwaddc('\\');
+ *hptr++ = c;
+
+ /* Resize history line if necessary */
+ if (hptr - chline >= hlinesz) {
+ int oldsiz = hlinesz;
+
+ chline = realloc(chline, hlinesz = oldsiz + 16);
+ hptr = chline + oldsiz;
+ }
+ }
+}
+
+/* This function adds a character to the zle input line. It is used when *
+ * zsh expands history (see doexpandhist() in zle_tricky.c). It also *
+ * calculates the new cursor position after the expansion. It is called *
+ * from hgetc() and from gettok() in lex.c for characters in comments. */
+
+/**/
+void
+addtoline(int c)
+{
+ if (! expanding || lexstop)
+ return;
+ if (qbang && c == bangchar && stophist < 2) {
+ exlast--;
+ spaceinline(1);
+ line[cs++] = '\\';
+ }
+ if (excs > cs) {
+ excs += 1 + inbufct - exlast;
+ if (excs < cs)
+ /* this case could be handled better but it is *
+ * so rare that it does not worth it */
+ excs = cs;
+ }
+ exlast = inbufct;
+ spaceinline(1);
+ line[cs++] = itok(c) ? ztokens[c - Pound] : c;
+}
+
+/**/
+int
+hgetc(void)
+{
+ int c = ingetc();
+
+ qbang = 0;
+ if (!stophist && !(inbufflags & INP_ALIAS)) {
+ /* If necessary, expand history characters. */
+ c = histsubchar(c);
+ if (c < 0) {
+ /* bad expansion */
+ errflag = lexstop = 1;
+ return ' ';
+ }
+ }
+ if ((inbufflags & INP_HIST) && !stophist) {
+ /* the current character c came from a history expansion *
+ * (inbufflags && INP_HIST) and history is not disabled *
+ * (e.g. we are not inside single quotes). In that case, \! *
+ * should be treated as ! (since this \! came from a previous *
+ * history line where \ was used to escape the bang). So if *
+ * c == '\\' we fetch one more character to see if it's a bang, *
+ * and if it is not, we unget it and reset c back to '\\' */
+ qbang = 0;
+ if (c == '\\' && !(qbang = (c = ingetc()) == bangchar))
+ safeinungetc(c), c = '\\';
+ } else if (stophist || (inbufflags & INP_ALIAS))
+ /* If the result is a bangchar which came from history or alias *
+ * expansion, we treat it as an escaped bangchar, unless history *
+ * is disabled. If stophist == 1 it only means that history is *
+ * temporarily disabled by a !" which won't appear in in the *
+ * history, so we still have an escaped bang. stophist > 1 if *
+ * history is disabled with NOBANGHIST or by someone else (e.g. *
+ * when the lexer scans single quoted text). */
+ qbang = c == bangchar && (stophist < 2);
+ hwaddc(c);
+ addtoline(c);
+
+ return c;
+}
+
+/**/
+static void
+safeinungetc(int c)
+{
+ if (lexstop)
+ lexstop = 0;
+ else
+ inungetc(c);
+}
+
+/**/
+void
+herrflush(void)
+{
+ while (!lexstop && inbufct)
+ hwaddc(ingetc());
+}
+
+/* extract :s/foo/bar/ delimiters and arguments */
+
+/**/
+static int
+getsubsargs(char *subline)
+{
+ int del;
+ char *ptr1, *ptr2;
+
+ del = ingetc();
+ ptr1 = hdynread2(del);
+ if (!ptr1)
+ return 1;
+ ptr2 = hdynread2(del);
+ if (strlen(ptr1)) {
+ zsfree(hsubl);
+ hsubl = ptr1;
+ }
+ zsfree(hsubr);
+ hsubr = ptr2;
+ if (hsubl && !strstr(subline, hsubl)) {
+ herrflush();
+ zerr("substitution failed", NULL, 0);
+ return 1;
+ }
+ return 0;
+}
+
+/* Get the maximum no. of words for a history entry. */
+
+/**/
+static int
+getargc(Histent ehist)
+{
+ return ehist->nwords ? ehist->nwords-1 : 0;
+}
+
+/* Perform history substitution, returning the next character afterwards. */
+
+/**/
+static int
+histsubchar(int c)
+{
+ int ev, farg, evset = -1, larg, argc, cflag = 0, bflag = 0;
+ static int mev = -1, marg = -1;
+ char buf[256], *ptr;
+ char *sline;
+ Histent ehist;
+
+ /* look, no goto's */
+ if (isfirstch && c == hatchar) {
+ /* Line begins ^foo^bar */
+ isfirstch = 0;
+ inungetc(hatchar);
+ if (!(ehist = gethist(defev))
+ || !(sline = getargs(ehist, 0, getargc(ehist)))
+ || getsubsargs(sline) || !hsubl)
+ return -1;
+ subst(&sline, hsubl, hsubr, 0);
+ } else {
+ /* Line doesn't begin ^foo^bar */
+ if (c != ' ')
+ isfirstch = 0;
+ if (c == '\\') {
+ int g = ingetc();
+
+ if (g != bangchar)
+ safeinungetc(g);
+ else {
+ qbang = 1;
+ return bangchar;
+ }
+ }
+ if (c != bangchar)
+ return c;
+ *hptr = '\0';
+ if ((c = ingetc()) == '{') {
+ bflag = cflag = 1;
+ c = ingetc();
+ }
+ if (c == '\"') {
+ stophist = 1;
+ return ingetc();
+ }
+ if ((!cflag && inblank(c)) || c == '=' || c == '(' || lexstop) {
+ safeinungetc(c);
+ return bangchar;
+ }
+ cflag = 0;
+ ptr = buf;
+
+ /* get event number */
+
+ if (c == '?') {
+ for (;;) {
+ c = ingetc();
+ if (c == '?' || c == '\n' || lexstop)
+ break;
+ else
+ *ptr++ = c;
+ }
+ if (c != '\n' && !lexstop)
+ c = ingetc();
+ *ptr = '\0';
+ mev = ev = hconsearch(hsubl = ztrdup(buf), &marg);
+ evset = 0;
+ if (ev == -1) {
+ herrflush();
+ zerr("no such event: %s", buf, 0);
+ return -1;
+ }
+ } else {
+ int t0;
+
+ for (;;) {
+ if (inblank(c) || c == ';' || c == ':' || c == '^' ||
+ c == '$' || c == '*' || c == '%' || c == '}' ||
+ c == '\'' || c == '"' || c == '`' || lexstop)
+ break;
+ if (ptr != buf) {
+ if (c == '-')
+ break;
+ if ((idigit(buf[0]) || buf[0] == '-') && !idigit(c))
+ break;
+ }
+ *ptr++ = c;
+ if (c == '#' || c == bangchar) {
+ c = ingetc();
+ break;
+ }
+ c = ingetc();
+ }
+ *ptr = 0;
+ if (!*buf)
+ if (c != '%') {
+ if (isset(CSHJUNKIEHISTORY))
+ ev = curhist - 1;
+ else
+ ev = defev;
+ if (c == ':' && evset == -1)
+ evset = 0;
+ else
+ evset = 1;
+ } else {
+ if (marg != -1)
+ ev = mev;
+ else
+ ev = defev;
+ evset = 0;
+ } else if ((t0 = atoi(buf))) {
+ ev = (t0 < 0) ? curhist + t0 : t0;
+ evset = 1;
+ } else if ((unsigned)*buf == bangchar) {
+ ev = curhist - 1;
+ evset = 1;
+ } else if (*buf == '#') {
+ ev = curhist;
+ evset = 1;
+ } else if ((ev = hcomsearch(buf)) == -1) {
+ herrflush();
+ zerr("event not found: %s", buf, 0);
+ return -1;
+ } else
+ evset = 1;
+ }
+
+ /* get the event */
+
+ if (!(ehist = gethist(defev = ev)))
+ return -1;
+
+ /* extract the relevant arguments */
+
+ argc = getargc(ehist);
+ if (c == ':') {
+ cflag = 1;
+ c = ingetc();
+ if (c == '%' && marg != -1) {
+ if (!evset) {
+ ehist = gethist(defev = mev);
+ argc = getargc(ehist);
+ } else {
+ herrflush();
+ zerr("Ambiguous history reference", NULL, 0);
+ return -1;
+ }
+
+ }
+ }
+ if (c == '*') {
+ farg = 1;
+ larg = argc;
+ cflag = 0;
+ } else {
+ inungetc(c);
+ larg = farg = getargspec(argc, marg, evset);
+ if (larg == -2)
+ return -1;
+ if (farg != -1)
+ cflag = 0;
+ c = ingetc();
+ if (c == '*') {
+ cflag = 0;
+ larg = argc;
+ } else if (c == '-') {
+ cflag = 0;
+ larg = getargspec(argc, marg, evset);
+ if (larg == -2)
+ return -1;
+ if (larg == -1)
+ larg = argc - 1;
+ } else
+ inungetc(c);
+ }
+ if (farg == -1)
+ farg = 0;
+ if (larg == -1)
+ larg = argc;
+ if (!(sline = getargs(ehist, farg, larg)))
+ return -1;
+ }
+
+ /* do the modifiers */
+
+ for (;;) {
+ c = (cflag) ? ':' : ingetc();
+ cflag = 0;
+ if (c == ':') {
+ int gbal = 0;
+
+ if ((c = ingetc()) == 'g') {
+ gbal = 1;
+ c = ingetc();
+ }
+ switch (c) {
+ case 'p':
+ histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC;
+ break;
+ case 'h':
+ if (!remtpath(&sline)) {
+ herrflush();
+ zerr("modifier failed: h", NULL, 0);
+ return -1;
+ }
+ break;
+ case 'e':
+ if (!rembutext(&sline)) {
+ herrflush();
+ zerr("modifier failed: e", NULL, 0);
+ return -1;
+ }
+ break;
+ case 'r':
+ if (!remtext(&sline)) {
+ herrflush();
+ zerr("modifier failed: r", NULL, 0);
+ return -1;
+ }
+ break;
+ case 't':
+ if (!remlpaths(&sline)) {
+ herrflush();
+ zerr("modifier failed: t", NULL, 0);
+ return -1;
+ }
+ break;
+ case 's':
+ if (getsubsargs(sline))
+ return -1; /* fall through */
+ case '&':
+ if (hsubl && hsubr)
+ subst(&sline, hsubl, hsubr, gbal);
+ else {
+ herrflush();
+ zerr("no previous substitution", NULL, 0);
+ return -1;
+ }
+ break;
+ case 'q':
+ quote(&sline);
+ break;
+ case 'x':
+ quotebreak(&sline);
+ break;
+ case 'l':
+ downcase(&sline);
+ break;
+ case 'u':
+ upcase(&sline);
+ break;
+ default:
+ herrflush();
+ zerr("illegal modifier: %c", NULL, c);
+ return -1;
+ }
+ } else {
+ if (c != '}' || !bflag)
+ inungetc(c);
+ if (c != '}' && bflag) {
+ zerr("'}' expected", NULL, 0);
+ return -1;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Push the expanded value onto the input stack,
+ * marking this as a history word for purposes of the alias stack.
+ */
+
+ lexstop = 0;
+ /* this function is called only called from hgetc and only if *
+ * !(inbufflags & INP_ALIAS). History expansion should never be *
+ * done with INP_ALIAS (to prevent recursive history expansion and *
+ * histoty expansion of aliases). Escapes are not removed here. *
+ * This is now handled in hgetc. */
+ inpush(sline, INP_HIST, NULL); /* sline from heap, don't free */
+ histdone |= HISTFLAG_DONE;
+ if (isset(HISTVERIFY))
+ histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL;
+
+ /* Don't try and re-expand line. */
+ return ingetc();
+}
+
+/* unget a char and remove it from chline. It can only be used *
+ * to unget a character returned by hgetc. */
+
+/**/
+void
+hungetc(int c)
+{
+ int doit = 1;
+
+ while (!lexstop) {
+ if (hptr[-1] != (char) c && stophist < 4 &&
+ hptr > chline + 1 && hptr[-1] == '\n' && hptr[-2] == '\\')
+ hungetc('\n'), hungetc('\\');
+
+ if (expanding) {
+ cs--;
+ ll--;
+ exlast++;
+ }
+ DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start");
+ hptr--;
+ DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() ");
+ qbang = (c == bangchar && stophist < 2 &&
+ hptr > chline && hptr[-1] == '\\');
+ if (doit)
+ inungetc(c);
+ if (!qbang)
+ return;
+ doit = !stophist && ((inbufflags & INP_HIST) ||
+ !(inbufflags & INP_ALIAS));
+ c = '\\';
+ }
+}
+
+/* begin reading a string */
+
+/**/
+void
+strinbeg(void)
+{
+ strin++;
+ hbegin();
+ lexinit();
+}
+
+/* done reading a string */
+
+/**/
+void
+strinend(void)
+{
+ hend();
+ DPUTS(!strin, "BUG: strinend() called without strinbeg()");
+ strin--;
+ isfirstch = 1;
+ histdone = 0;
+}
+
+/* initialize the history mechanism */
+
+/**/
+void
+hbegin(void)
+{
+ Histent curhistent;
+
+ isfirstln = isfirstch = 1;
+ errflag = histdone = spaceflag = 0;
+ stophist = (!interact || unset(BANGHIST) || unset(SHINSTDIN)) << 1;
+ chline = hptr = zcalloc(hlinesz = 16);
+ chwords = zalloc((chwordlen = 16)*sizeof(short));
+ chwordpos = 0;
+
+ if (histactive & HA_JUNKED)
+ curhist--;
+ curhistent = gethistent(curhist);
+ if (!curhistent->ftim)
+ curhistent->ftim = time(NULL);
+ histactive = HA_ACTIVE;
+ if (interact && isset(SHINSTDIN) && !strin) {
+ attachtty(mypgrp);
+ defev = curhist;
+ curhist++;
+ } else
+ histactive |= HA_NOINC;
+}
+
+/* compare current line with history entry using only text in words */
+
+/**/
+static int
+histcmp(Histent he)
+{
+ int kword, lword;
+ int nwords = chwordpos/2;
+
+ /* If the history entry came from a file, the words were not
+ * divided by the lexer so we have to resort to strcmp.
+ */
+ if (he->flags & HIST_READ)
+ return strcmp(he->text, chline);
+
+ if (nwords != he->nwords)
+ return 1;
+
+ for (kword = 0; kword < 2*nwords; kword += 2)
+ if ((lword = chwords[kword+1]-chwords[kword])
+ != he->words[kword+1]-he->words[kword] ||
+ memcmp(he->text+he->words[kword], chline+chwords[kword], lword))
+ return 1;
+
+ return 0;
+}
+
+/**/
+void
+histreduceblanks(void)
+{
+ int i, len, pos, needblank;
+
+ for (i = 0, len = 0; i < chwordpos; i += 2) {
+ len += chwords[i+1] - chwords[i]
+ + (i > 0 && chwords[i] > chwords[i-1]);
+ }
+ if (chline[len] == '\0')
+ return;
+
+ for (i = 0, pos = 0; i < chwordpos; i += 2) {
+ len = chwords[i+1] - chwords[i];
+ needblank = (i < chwordpos-2 && chwords[i+2] > chwords[i+1]);
+ if (pos != chwords[i]) {
+ memcpy(chline + pos, chline + chwords[i], len + needblank);
+ chwords[i] = pos;
+ chwords[i+1] = chwords[i] + len;
+ }
+ pos += len + needblank;
+ }
+ chline[pos] = '\0';
+}
+
+/* say we're done using the history mechanism */
+
+/**/
+int
+hend(void)
+{
+ int flag, save = 1;
+
+ DPUTS(!chline, "BUG: chline is NULL in hend()");
+ if (histactive & (HA_NOSTORE|HA_NOINC)) {
+ zfree(chline, hlinesz);
+ zfree(chwords, chwordlen*sizeof(short));
+ chline = NULL;
+ if (!(histactive & HA_NOINC))
+ curhist--;
+ histactive = 0;
+ return 1;
+ }
+ flag = histdone;
+ histdone = 0;
+ if (hptr < chline + 1)
+ save = 0;
+ else {
+ *hptr = '\0';
+ if (hptr[-1] == '\n')
+ if (chline[1]) {
+ *--hptr = '\0';
+ } else
+ save = 0;
+ if (!*chline || !strcmp(chline, "\n") ||
+ (isset(HISTIGNORESPACE) && spaceflag))
+ save = 0;
+ }
+ if (flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) {
+ char *ptr;
+
+ ptr = ztrdup(chline);
+ if ((flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) == HISTFLAG_DONE) {
+ zputs(ptr, shout);
+ fputc('\n', shout);
+ fflush(shout);
+ }
+ if (flag & HISTFLAG_RECALL) {
+ PERMALLOC {
+ pushnode(bufstack, ptr);
+ } LASTALLOC;
+ save = 0;
+ } else
+ zsfree(ptr);
+ }
+ if (save) {
+ Histent he;
+ int keepflags = 0;
+
+#ifdef DEBUG
+ /* debugging only */
+ if (chwordpos%2) {
+ hwend();
+ DPUTS(1, "BUG: uncompleted line in history");
+ }
+#endif
+ /* get rid of pesky \n which we've already nulled out */
+ if (!chline[chwords[chwordpos-2]])
+ chwordpos -= 2;
+ /* strip superfluous blanks, if desired */
+ if (isset(HISTREDUCEBLANKS))
+ histreduceblanks();
+
+ if (isset(HISTIGNOREDUPS) && (he = gethistent(curhist - 1))
+ && he->text && !histcmp(he)) {
+ /* This history entry compares the same as the previous.
+ * In case minor changes were made, we overwrite the
+ * previous one with the current one. This also gets
+ * the timestamp right. However, keep the old flags.
+ */
+ keepflags = he->flags;
+ curhist--;
+ }
+
+ he = gethistent(curhist);
+ zsfree(he->text);
+ he->text = ztrdup(chline);
+ if (he->nwords)
+ zfree(he->words, he->nwords*2*sizeof(short));
+ he->stim = time(NULL);
+ he->ftim = 0L;
+ he->flags = keepflags;
+
+ if ((he->nwords = chwordpos/2)) {
+ he->words = (short *)zalloc(chwordpos * sizeof(short));
+ memcpy(he->words, chwords, chwordpos * sizeof(short));
+ }
+ } else
+ curhist--;
+ zfree(chline, hlinesz);
+ zfree(chwords, chwordlen*sizeof(short));
+ chline = NULL;
+ histactive = 0;
+ return !(flag & HISTFLAG_NOEXEC || errflag);
+}
+
+/* remove the current line from the history List */
+
+/**/
+void
+remhist(void)
+{
+ if (!(histactive & HA_ACTIVE)) {
+ if (!(histactive & HA_JUNKED)) {
+ /* make sure this doesn't show up when we do firsthist() */
+ Histent he = gethistent(curhist);
+ zsfree(he->text);
+ he->text = NULL;
+ histactive |= HA_JUNKED;
+ /* curhist-- is delayed until the next hbegin() */
+ }
+ } else
+ histactive |= HA_NOSTORE;
+}
+
+/* Gives current expansion word if not last word before chwordpos. */
+
+/**/
+int hwgetword = -1;
+
+/* begin a word */
+
+/**/
+void
+hwbegin(int offset)
+{
+ if (chwordpos%2)
+ chwordpos--; /* make sure we're on a word start, not end */
+ /* If we're expanding an alias, we should overwrite the expansion
+ * in the history.
+ */
+ if ((inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST))
+ hwgetword = chwordpos;
+ else
+ hwgetword = -1;
+ chwords[chwordpos++] = hptr - chline + offset;
+}
+
+/* add a word to the history List */
+
+/**/
+void
+hwend(void)
+{
+ if (chwordpos%2 && chline) {
+ /* end of word reached and we've already begun a word */
+ if (hptr > chline + chwords[chwordpos-1]) {
+ chwords[chwordpos++] = hptr - chline;
+ if (chwordpos >= chwordlen) {
+ chwords = (short *) realloc(chwords,
+ (chwordlen += 16)*sizeof(short));
+ }
+ if (hwgetword > -1) {
+ /* We want to reuse the current word position */
+ chwordpos = hwgetword;
+ /* Start from where previous word ended, if possible */
+ hptr = chline + chwords[chwordpos ? chwordpos - 1 : 0];
+ }
+ } else {
+ /* scrub that last word, it doesn't exist */
+ chwordpos--;
+ }
+ }
+}
+
+/* Go back to immediately after the last word, skipping space. */
+
+/**/
+void
+histbackword(void)
+{
+ if (!(chwordpos%2) && chwordpos)
+ hptr = chline + chwords[chwordpos-1];
+}
+
+/* Get the start and end point of the current history word */
+
+/**/
+static void
+hwget(char **startptr)
+{
+ int pos = hwgetword > -1 ? hwgetword : chwordpos - 2;
+
+#ifdef DEBUG
+ /* debugging only */
+ if (hwgetword == -1 && !chwordpos) {
+ /* no words available */
+ DPUTS(1, "BUG: hwget() called with no words");
+ *startptr = "";
+ return;
+ }
+ else if (hwgetword == -1 && chwordpos%2) {
+ DPUTS(1, "BUG: hwget() called in middle of word");
+ *startptr = "";
+ return;
+ }
+#endif
+
+ *startptr = chline + chwords[pos];
+ chline[chwords[++pos]] = '\0';
+}
+
+/* Replace the current history word with rep, if different */
+
+/**/
+void
+hwrep(char *rep)
+{
+ char *start;
+ hwget(&start);
+
+ if (!strcmp(rep, start))
+ return;
+
+ hptr = start;
+ chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2;
+ hwbegin(0);
+ qbang = 1;
+ while (*rep)
+ hwaddc(*rep++);
+ hwend();
+}
+
+/* Get the entire current line, deleting it in the history. */
+
+/**/
+char *
+hgetline(void)
+{
+ /* Currently only used by pushlineoredit().
+ * It's necessary to prevent that from getting too pally with
+ * the history code.
+ */
+ char *ret;
+
+ if (!chline || hptr == chline)
+ return NULL;
+ *hptr = '\0';
+ ret = dupstring(chline);
+
+ /* reset line */
+ hptr = chline;
+ chwordpos = 0;
+ hwgetword = -1;
+
+ return ret;
+}
+
+/* get an argument specification */
+
+/**/
+static int
+getargspec(int argc, int marg, int evset)
+{
+ int c, ret = -1;
+
+ if ((c = ingetc()) == '0')
+ return 0;
+ if (idigit(c)) {
+ ret = 0;
+ while (idigit(c)) {
+ ret = ret * 10 + c - '0';
+ c = ingetc();
+ }
+ inungetc(c);
+ } else if (c == '^')
+ ret = 1;
+ else if (c == '$')
+ ret = argc;
+ else if (c == '%') {
+ if (evset) {
+ herrflush();
+ zerr("Ambiguous history reference", NULL, 0);
+ return -2;
+ }
+ if (marg == -1) {
+ herrflush();
+ zerr("%% with no previous word matched", NULL, 0);
+ return -2;
+ }
+ ret = marg;
+ } else
+ inungetc(c);
+ return ret;
+}
+
+/* do ?foo? search */
+
+/**/
+static int
+hconsearch(char *str, int *marg)
+{
+ int t0, t1 = 0;
+ char *s;
+ Histent he;
+
+ for (t0 = curhist - 1; (he = quietgethist(t0)); t0--)
+ if ((s = strstr(he->text, str))) {
+ int pos = s - he->text;
+ while (t1 < he->nwords && he->words[2*t1] <= pos)
+ t1++;
+ *marg = t1 - 1;
+ return t0;
+ }
+ return -1;
+}
+
+/* do !foo search */
+
+/**/
+int
+hcomsearch(char *str)
+{
+ int t0;
+ char *hs;
+
+ for (t0 = curhist - 1; (hs = quietgetevent(t0)); t0--)
+ if (!strncmp(hs, str, strlen(str)))
+ return t0;
+ return -1;
+}
+
+/* various utilities for : modifiers */
+
+/**/
+int
+remtpath(char **junkptr)
+{
+ char *str = *junkptr, *remcut;
+
+ if ((remcut = strrchr(str, '/'))) {
+ if (str != remcut)
+ *remcut = '\0';
+ else
+ str[1] = '\0';
+ return 1;
+ }
+ return 0;
+}
+
+/**/
+int
+remtext(char **junkptr)
+{
+ char *str = *junkptr, *remcut;
+
+ if ((remcut = strrchr(str, '.')) && remcut != str) {
+ *remcut = '\0';
+ return 1;
+ }
+ return 0;
+}
+
+/**/
+int
+rembutext(char **junkptr)
+{
+ char *str = *junkptr, *remcut;
+
+ if ((remcut = strrchr(str, '.')) && remcut != str) {
+ *junkptr = dupstring(remcut + 1); /* .xx or xx? */
+ return 1;
+ }
+ return 0;
+}
+
+/**/
+int
+remlpaths(char **junkptr)
+{
+ char *str = *junkptr, *remcut;
+
+ if ((remcut = strrchr(str, '/'))) {
+ *remcut = '\0';
+ *junkptr = dupstring(remcut + 1);
+ return 1;
+ }
+ return 0;
+}
+
+/**/
+int
+makeuppercase(char **junkptr)
+{
+ char *str = *junkptr;
+
+ for (; *str; str++)
+ *str = tuupper(*str);
+ return 1;
+}
+
+/**/
+int
+makelowercase(char **junkptr)
+{
+ char *str = *junkptr;
+
+ for (; *str; str++)
+ *str = tulower(*str);
+ return 1;
+}
+
+/**/
+int
+makecapitals(char **junkptr)
+{
+ char *str = *junkptr;
+
+ for (; *str;) {
+ for (; *str && !ialnum(*str); str++);
+ if (*str)
+ *str = tuupper(*str), str++;
+ for (; *str && ialnum(*str); str++)
+ *str = tulower(*str);
+ }
+ return 1;
+}
+
+/**/
+void
+subst(char **strptr, char *in, char *out, int gbal)
+{
+ char *str = *strptr, *instr = *strptr, *substcut, *sptr, *oldstr;
+ int off, inlen, outlen;
+
+ if (!*in)
+ in = str, gbal = 0;
+ if (!(substcut = (char *)strstr(str, in)))
+ return;
+ inlen = strlen(in);
+ sptr = convamps(out, in, inlen);
+ outlen = strlen(sptr);
+
+ do {
+ *substcut = '\0';
+ off = substcut - *strptr + outlen;
+ substcut += inlen;
+ *strptr = tricat(oldstr = *strptr, sptr, substcut);
+ if (oldstr != instr)
+ zsfree(oldstr);
+ str = (char *)*strptr + off;
+ } while (gbal && (substcut = (char *)strstr(str, in)));
+}
+
+/**/
+static char *
+convamps(char *out, char *in, int inlen)
+{
+ char *ptr, *ret, *pp;
+ int slen, sdup = 0;
+
+ for (ptr = out, slen = 0; *ptr; ptr++, slen++)
+ if (*ptr == '\\')
+ ptr++, sdup = 1;
+ else if (*ptr == '&')
+ slen += inlen - 1, sdup = 1;
+ if (!sdup)
+ return out;
+ ret = pp = (char *)alloc(slen + 1);
+ for (ptr = out; *ptr; ptr++)
+ if (*ptr == '\\')
+ *pp++ = *++ptr;
+ else if (*ptr == '&') {
+ strcpy(pp, in);
+ pp += inlen;
+ } else
+ *pp++ = *ptr;
+ *pp = '\0';
+ return ret;
+}
+
+/**/
+struct histent *
+quietgethist(int ev)
+{
+ static struct histent storehist;
+
+ if (ev < firsthist() || ev > curhist)
+ return NULL;
+ if (ev == curhist && (histactive & HA_ACTIVE)) {
+ /* The current history line has not been stored. Build it up
+ * from other variables.
+ */
+ storehist.text = chline;
+ storehist.nwords = chwordpos/2;
+ storehist.words = chwords;
+
+ return &storehist;
+ } else
+ return gethistent(ev);
+}
+
+/**/
+char *
+quietgetevent(int ev)
+{
+ Histent ent = quietgethist(ev);
+
+ return ent ? ent->text : NULL;
+}
+
+/**/
+static Histent
+gethist(int ev)
+{
+ Histent ret;
+
+ ret = quietgethist(ev);
+ if (!ret) {
+ herrflush();
+ zerr("no such event: %d", NULL, ev);
+ }
+ return ret;
+}
+
+/**/
+static char *
+getargs(Histent elist, int arg1, int arg2)
+{
+ short *words = elist->words;
+ int pos1, nwords = elist->nwords;
+
+ if (arg2 < arg1 || arg1 >= nwords || arg2 >= nwords) {
+ /* remember, argN is indexed from 0, nwords is total no. of words */
+ herrflush();
+ zerr("no such word in event", NULL, 0);
+ return NULL;
+ }
+
+ pos1 = words[2*arg1];
+ return dupstrpfx(elist->text + pos1, words[2*arg2+1] - pos1);
+}
+
+/**/
+void
+upcase(char **x)
+{
+ char *pp = *(char **)x;
+
+ for (; *pp; pp++)
+ *pp = tuupper(*pp);
+}
+
+/**/
+void
+downcase(char **x)
+{
+ char *pp = *(char **)x;
+
+ for (; *pp; pp++)
+ *pp = tulower(*pp);
+}
+
+/**/
+int
+quote(char **tr)
+{
+ char *ptr, *rptr, **str = (char **)tr;
+ int len = 3;
+ int inquotes = 0;
+
+ for (ptr = *str; *ptr; ptr++, len++)
+ if (*ptr == '\'') {
+ len += 3;
+ if (!inquotes)
+ inquotes = 1;
+ else
+ inquotes = 0;
+ } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\')
+ len += 2;
+ ptr = *str;
+ *str = rptr = (char *)alloc(len);
+ *rptr++ = '\'';
+ for (; *ptr; ptr++)
+ if (*ptr == '\'') {
+ if (!inquotes)
+ inquotes = 1;
+ else
+ inquotes = 0;
+ *rptr++ = '\'';
+ *rptr++ = '\\';
+ *rptr++ = '\'';
+ *rptr++ = '\'';
+ } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') {
+ *rptr++ = '\'';
+ *rptr++ = *ptr;
+ *rptr++ = '\'';
+ } else
+ *rptr++ = *ptr;
+ *rptr++ = '\'';
+ *rptr++ = 0;
+ str[1] = NULL;
+ return 0;
+}
+
+/**/
+static int
+quotebreak(char **tr)
+{
+ char *ptr, *rptr, **str = (char **)tr;
+ int len = 3;
+
+ for (ptr = *str; *ptr; ptr++, len++)
+ if (*ptr == '\'')
+ len += 3;
+ else if (inblank(*ptr))
+ len += 2;
+ ptr = *str;
+ *str = rptr = (char *)alloc(len);
+ *rptr++ = '\'';
+ for (; *ptr;)
+ if (*ptr == '\'') {
+ *rptr++ = '\'';
+ *rptr++ = '\\';
+ *rptr++ = '\'';
+ *rptr++ = '\'';
+ ptr++;
+ } else if (inblank(*ptr)) {
+ *rptr++ = '\'';
+ *rptr++ = *ptr++;
+ *rptr++ = '\'';
+ } else
+ *rptr++ = *ptr++;
+ *rptr++ = '\'';
+ *rptr++ = '\0';
+ return 0;
+}
+
+#if 0
+/* read an arbitrary amount of data into a buffer until stop is found */
+
+/**/
+char *
+hdynread(int stop)
+{
+ int bsiz = 256, ct = 0, c;
+ char *buf = (char *)zalloc(bsiz), *ptr;
+
+ ptr = buf;
+ while ((c = ingetc()) != stop && c != '\n' && !lexstop) {
+ if (c == '\\')
+ c = ingetc();
+ *ptr++ = c;
+ if (++ct == bsiz) {
+ buf = realloc(buf, bsiz *= 2);
+ ptr = buf + ct;
+ }
+ }
+ *ptr = 0;
+ if (c == '\n') {
+ inungetc('\n');
+ zerr("delimiter expected", NULL, 0);
+ zfree(buf, bsiz);
+ return NULL;
+ }
+ return buf;
+}
+#endif
+
+/**/
+static char *
+hdynread2(int stop)
+{
+ int bsiz = 256, ct = 0, c;
+ char *buf = (char *)zalloc(bsiz), *ptr;
+
+ ptr = buf;
+ while ((c = ingetc()) != stop && c != '\n' && !lexstop) {
+ if (c == '\n') {
+ inungetc(c);
+ break;
+ }
+ if (c == '\\')
+ c = ingetc();
+ *ptr++ = c;
+ if (++ct == bsiz) {
+ buf = realloc(buf, bsiz *= 2);
+ ptr = buf + ct;
+ }
+ }
+ *ptr = 0;
+ if (c == '\n')
+ inungetc('\n');
+ return buf;
+}
+
+/**/
+void
+inithist(void)
+{
+ histentct = histsiz;
+ histentarr = (Histent) zcalloc(histentct * sizeof *histentarr);
+}
+
+/**/
+void
+resizehistents(void)
+{
+ int newentct, t0, t1, firstlex;
+ Histent newarr;
+
+ newentct = histsiz;
+ newarr = (Histent) zcalloc(newentct * sizeof *newarr);
+ firstlex = curhist - histsiz + 1;
+ t0 = firsthist();
+ if (t0 < curhist - newentct)
+ t0 = curhist - newentct;
+ t1 = t0 % newentct;
+ for (; t0 <= curhist; t0++) {
+ newarr[t1] = *gethistent(t0);
+ if (t0 < firstlex) {
+ zsfree(newarr[t1].text);
+ newarr[t1].text = NULL;
+ }
+ t1++;
+ if (t1 == newentct)
+ t1 = 0;
+ }
+ free(histentarr);
+ histentarr = newarr;
+ histentct = newentct;
+}
+
+/**/
+void
+readhistfile(char *s, int err)
+{
+ char *buf;
+ FILE *in;
+ Histent ent;
+ time_t tim = time(NULL);
+ short *wordlist;
+ int nwordpos, nwordlist, bufsiz;
+
+ if (!s)
+ return;
+ if ((in = fopen(unmeta(s), "r"))) {
+ nwordlist = 16;
+ wordlist = (short *)zalloc(nwordlist*sizeof(short));
+ bufsiz = 1024;
+ buf = zalloc(bufsiz);
+
+ while (fgets(buf, bufsiz, in)) {
+ int l = strlen(buf);
+ char *pt, *start;
+
+ while (l) {
+ while (buf[l - 1] != '\n') {
+ buf = zrealloc(buf, 2 * bufsiz);
+ bufsiz = 2 * bufsiz;
+ if (!fgets(buf + l, bufsiz - l, in)) {
+ l++;
+ break;
+ }
+ l = strlen(buf);
+ }
+ buf[l - 1] = '\0';
+ if (l > 1 && buf[l - 2] == '\\') {
+ buf[l - 2] = '\n';
+ fgets(buf + l - 1, bufsiz - (l - 1), in);
+ l = strlen(buf);
+ } else
+ break;
+ }
+
+ ent = gethistent(++curhist);
+ pt = buf;
+ if (*pt == ':') {
+ pt++;
+ ent->stim = zstrtol(pt, NULL, 0);
+ for (; *pt != ':' && *pt; pt++);
+ if (*pt) {
+ pt++;
+ ent->ftim = zstrtol(pt, NULL, 0);
+ for (; *pt != ';' && *pt; pt++);
+ if (*pt)
+ pt++;
+ } else {
+ ent->ftim = tim;
+ }
+ if (ent->stim == 0)
+ ent->stim = tim;
+ if (ent->ftim == 0)
+ ent->ftim = tim;
+ } else {
+ ent->ftim = ent->stim = tim;
+ }
+
+ zsfree(ent->text);
+ ent->text = ztrdup(pt);
+ ent->flags = HIST_OLD|HIST_READ;
+ if (ent->nwords)
+ zfree(ent->words, ent->nwords*2*sizeof(short));
+
+ /* Divide up the words. We don't know how it lexes,
+ so just look for spaces.
+ */
+ nwordpos = 0;
+ start = pt;
+ do {
+ while (*pt == ' ')
+ pt++;
+ if (*pt) {
+ if (nwordpos >= nwordlist)
+ wordlist = (short *) realloc(wordlist,
+ (nwordlist += 16)*sizeof(short));
+ wordlist[nwordpos++] = pt - start;
+ while (*pt && *pt != ' ')
+ pt++;
+ wordlist[nwordpos++] = pt - start;
+ }
+ } while (*pt);
+
+ ent->nwords = nwordpos/2;
+ if (ent->nwords) {
+ ent->words = (short *)zalloc(nwordpos*sizeof(short));
+ memcpy(ent->words, wordlist, nwordpos*sizeof(short));
+ } else
+ ent->words = (short *)NULL;
+ }
+ fclose(in);
+
+ zfree(wordlist, nwordlist*sizeof(short));
+ zfree(buf, bufsiz);
+ } else if (err)
+ zerr("can't read history file", s, 0);
+}
+
+/**/
+void
+savehistfile(char *s, int err, int app)
+{
+ char *t;
+ FILE *out;
+ int ev;
+ Histent ent;
+ int savehist = getiparam("SAVEHIST");
+
+ if (!s || !interact || savehist <= 0)
+ return;
+ ev = curhist - savehist + 1;
+ if (ev < firsthist())
+ ev = firsthist();
+ if (app & 1)
+ out = fdopen(open(unmeta(s),
+ O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a");
+ else
+ out = fdopen(open(unmeta(s),
+ O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w");
+ if (out) {
+ for (; ev <= curhist - !!(histactive & HA_ACTIVE); ev++) {
+ ent = gethistent(ev);
+ if (app & 2) {
+ if (ent->flags & HIST_OLD)
+ continue;
+ ent->flags |= HIST_OLD;
+ }
+ t = ent->text;
+ if (isset(EXTENDEDHISTORY)) {
+ fprintf(out, ": %ld:%ld;",
+ (long)ent->stim,
+ (long)ent->ftim);
+ } else if (*t == ':')
+ fputc('\\', out);
+
+ for (; *t; t++) {
+ if (*t == '\n')
+ fputc('\\', out);
+ fputc(*t, out);
+ }
+ fputc('\n', out);
+ }
+ fclose(out);
+
+ if (app & 2 && (out = fopen(unmeta(s), "r"))) {
+ char **store, buf[1024], **ptr;
+ int i, l, histnum = 0;
+
+ store = (char **)zcalloc((savehist + 1) * sizeof *store);
+ while (fgets(buf, sizeof(buf), out)) {
+ char *t;
+
+ if (store[i = histnum % savehist])
+ free(store[i]);
+ store[i] = ztrdup(buf);
+ l = strlen(buf);
+ if (l > 1) {
+ t = store[i] + l;
+ while ((t[-1] != '\n' ||
+ (t[-1] == '\n' && t[-2] == '\\')) &&
+ fgets(buf, sizeof(buf), out)) {
+ l += strlen(buf);
+ store[i] = zrealloc(store[i], l + 1);
+ t = store[i] + l;
+ strcat(store[i], buf);
+ }
+ }
+ histnum++;
+ }
+ fclose(out);
+ if ((out = fdopen(open(unmeta(s),
+ O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"))) {
+ if (histnum < savehist)
+ for (i = 0; i < histnum; i++)
+ fprintf(out, "%s", store[i]);
+ else
+ for (i = histnum; i < histnum + savehist; i++)
+ fprintf(out, "%s", store[i % savehist]);
+ fclose(out);
+ }
+ for (ptr = store; *ptr; ptr++)
+ zsfree(*ptr);
+ free(store);
+ }
+ } else if (err)
+ zerr("can't write history file %s", s, 0);
+}
+
+/**/
+int
+firsthist(void)
+{
+ int ev;
+ Histent ent;
+
+ ev = curhist - histentct + 1;
+ if (ev < 1)
+ ev = 1;
+ do {
+ ent = gethistent(ev);
+ if (ent->text)
+ break;
+ ev++;
+ }
+ while (ev < curhist);
+ return ev;
+}
+
diff --git a/Src/init.c b/Src/init.c
new file mode 100644
index 000000000..33496adc6
--- /dev/null
+++ b/Src/init.c
@@ -0,0 +1,936 @@
+/*
+ * init.c - main loop and initialization routines
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "init.pro"
+
+#include "zshpaths.h"
+#include "zshxmods.h"
+
+/**/
+int noexitct = 0;
+
+/* what level of sourcing we are at */
+
+/**/
+int sourcelevel;
+
+/* the shell tty fd */
+
+/**/
+int SHTTY;
+
+/* the FILE attached to the shell tty */
+
+/**/
+FILE *shout;
+
+/* termcap strings */
+
+/**/
+char *tcstr[TC_COUNT];
+
+/* lengths of each termcap string */
+
+/**/
+int tclen[TC_COUNT];
+
+/* Values of the li, co and am entries */
+
+/**/
+int tclines, tccolumns, hasam;
+
+#ifdef DEBUG
+/* depth of allocation type stack */
+
+/**/
+int alloc_stackp;
+#endif
+
+/* keep executing lists until EOF found */
+
+/**/
+void
+loop(int toplevel, int justonce)
+{
+ List list;
+#ifdef DEBUG
+ int oasp = toplevel ? 0 : alloc_stackp;
+#endif
+
+ pushheap();
+ for (;;) {
+ freeheap();
+ errflag = 0;
+ if (isset(SHINSTDIN)) {
+ setblock_stdin();
+ if (interact)
+ preprompt();
+ }
+ hbegin(); /* init history mech */
+ intr(); /* interrupts on */
+ lexinit(); /* initialize lexical state */
+ if (!(list = parse_event())) { /* if we couldn't parse a list */
+ hend();
+ if ((tok == ENDINPUT && !errflag) ||
+ (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) ||
+ justonce)
+ break;
+ continue;
+ }
+ if (hend()) {
+ int toksav = tok;
+ List prelist;
+
+ if (toplevel && (prelist = getshfunc("preexec")) != &dummy_list) {
+ Histent he = gethistent(curhist);
+ LinkList args;
+ PERMALLOC {
+ args = newlinklist();
+ addlinknode(args, "preexec");
+ if (he && he->text)
+ addlinknode(args, he->text);
+ } LASTALLOC;
+ doshfunc(prelist, args, 0, 1);
+ freelinklist(args, (FreeFunc) NULL);
+ errflag = 0;
+ }
+ if (stopmsg) /* unset 'you have stopped jobs' flag */
+ stopmsg--;
+ execlist(list, 0, 0);
+ tok = toksav;
+ if (toplevel)
+ noexitct = 0;
+ }
+ DPUTS(alloc_stackp != oasp, "BUG: alloc_stackp changed in loop()");
+ if (ferror(stderr)) {
+ zerr("write error", NULL, 0);
+ clearerr(stderr);
+ }
+ if (subsh) /* how'd we get this far in a subshell? */
+ exit(lastval);
+ if (((!interact || sourcelevel) && errflag) || retflag)
+ break;
+ if (trapreturn) {
+ lastval = trapreturn;
+ trapreturn = 0;
+ }
+ if (isset(SINGLECOMMAND) && toplevel) {
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ exit(lastval);
+ }
+ if (justonce)
+ break;
+ }
+ popheap();
+}
+
+static char *cmd;
+static int restricted;
+
+/**/
+void
+parseargs(char **argv)
+{
+ char **x;
+ int action, optno;
+ LinkList paramlist;
+ int bourne = (emulation == EMULATE_KSH || emulation == EMULATE_SH);
+
+ argzero = *argv++;
+ SHIN = 0;
+
+ /* There's a bit of trickery with opts[INTERACTIVE] here. It starts *
+ * at a value of 2 (instead of 1) or 0. If it is explicitly set on *
+ * the command line, it goes to 1 or 0. If input is coming from *
+ * somewhere that normally makes the shell non-interactive, we do *
+ * "opts[INTERACTIVE] &= 1", so that only a *default* on state will *
+ * be changed. At the end of the function, a value of 2 gets *
+ * changed to 1. */
+ opts[INTERACTIVE] = isatty(0) ? 2 : 0;
+ opts[SHINSTDIN] = 0;
+ opts[SINGLECOMMAND] = 0;
+
+ /* loop through command line options (begins with "-" or "+") */
+ while (*argv && (**argv == '-' || **argv == '+')) {
+ action = (**argv == '-');
+ if(!argv[0][1])
+ *argv = "--";
+ while (*++*argv) {
+ /* The pseudo-option `--' signifies the end of options. *
+ * `-b' does too, csh-style, unless we're emulating a *
+ * Bourne style shell. */
+ if (**argv == '-' || (!bourne && **argv == 'b')) {
+ argv++;
+ goto doneoptions;
+ }
+
+ if (**argv == 'c') { /* -c command */
+ if (!*++argv) {
+ zerr("string expected after -c", NULL, 0);
+ exit(1);
+ }
+ cmd = *argv++;
+ opts[INTERACTIVE] &= 1;
+ opts[SHINSTDIN] = 0;
+ goto doneoptions;
+ } else if (**argv == 'o') {
+ if (!*++*argv)
+ argv++;
+ if (!*argv) {
+ zerr("string expected after -o", NULL, 0);
+ exit(1);
+ }
+ if(!(optno = optlookup(*argv)))
+ zerr("no such option: %s", *argv, 0);
+ else if (optno == RESTRICTED)
+ restricted = action;
+ else
+ dosetopt(optno, action, 1);
+ break;
+ } else {
+ if (!(optno = optlookupc(**argv))) {
+ zerr("bad option: -%c", NULL, **argv);
+ exit(1);
+ } else if (optno == RESTRICTED)
+ restricted = action;
+ else
+ dosetopt(optno, action, 1);
+ }
+ }
+ argv++;
+ }
+ doneoptions:
+ paramlist = newlinklist();
+ if (*argv) {
+ if (unset(SHINSTDIN)) {
+ argzero = *argv;
+ if (!cmd)
+ SHIN = movefd(open(unmeta(argzero), O_RDONLY | O_NOCTTY));
+ if (SHIN == -1) {
+ zerr("can't open input file: %s", argzero, 0);
+ exit(1);
+ }
+ opts[INTERACTIVE] &= 1;
+ argv++;
+ }
+ while (*argv)
+ addlinknode(paramlist, ztrdup(*argv++));
+ } else
+ opts[SHINSTDIN] = 1;
+ if(isset(SINGLECOMMAND))
+ opts[INTERACTIVE] &= 1;
+ opts[INTERACTIVE] = !!opts[INTERACTIVE];
+ pparams = x = (char **) zcalloc((countlinknodes(paramlist) + 1) * sizeof(char *));
+
+ while ((*x++ = (char *)getlinknode(paramlist)));
+ free(paramlist);
+ argzero = ztrdup(argzero);
+}
+
+
+/**/
+void
+init_io(void)
+{
+ long ttpgrp;
+ static char outbuf[BUFSIZ], errbuf[BUFSIZ];
+
+#ifdef RSH_BUG_WORKAROUND
+ int i;
+#endif
+
+/* stdout, stderr fully buffered */
+#ifdef _IOFBF
+ setvbuf(stdout, outbuf, _IOFBF, BUFSIZ);
+ setvbuf(stderr, errbuf, _IOFBF, BUFSIZ);
+#else
+ setbuffer(stdout, outbuf, BUFSIZ);
+ setbuffer(stderr, errbuf, BUFSIZ);
+#endif
+
+/* This works around a bug in some versions of in.rshd. *
+ * Currently this is not defined by default. */
+#ifdef RSH_BUG_WORKAROUND
+ if (cmd) {
+ for (i = 3; i < 10; i++)
+ close(i);
+ }
+#endif
+
+ if (shout) {
+ fclose(shout);
+ shout = 0;
+ }
+ if (SHTTY != -1) {
+ zclose(SHTTY);
+ SHTTY = -1;
+ }
+
+ /* Make sure the tty is opened read/write. */
+ if (isatty(0)) {
+ zsfree(ttystrname);
+ if ((ttystrname = ztrdup(ttyname(0))))
+ SHTTY = movefd(open(ttystrname, O_RDWR | O_NOCTTY));
+ }
+ if (SHTTY == -1 &&
+ (SHTTY = movefd(open("/dev/tty", O_RDWR | O_NOCTTY))) != -1) {
+ zsfree(ttystrname);
+ ttystrname = ztrdup("/dev/tty");
+ }
+ if (SHTTY == -1) {
+ zsfree(ttystrname);
+ ttystrname = ztrdup("");
+ }
+
+ /* We will only use zle if shell is interactive, *
+ * SHTTY != -1, and shout != 0 */
+ if (interact && SHTTY != -1) {
+ init_shout();
+ if(!shout)
+ opts[USEZLE] = 0;
+ } else
+ opts[USEZLE] = 0;
+
+#ifdef JOB_CONTROL
+ /* If interactive, make the shell the foreground process */
+ if (opts[MONITOR] && interact && (SHTTY != -1)) {
+ attachtty(GETPGRP());
+ if ((mypgrp = GETPGRP()) > 0) {
+ while ((ttpgrp = gettygrp()) != -1 && ttpgrp != mypgrp) {
+ sleep(1);
+ mypgrp = GETPGRP();
+ if (mypgrp == gettygrp())
+ break;
+ killpg(mypgrp, SIGTTIN);
+ mypgrp = GETPGRP();
+ }
+ } else
+ opts[MONITOR] = 0;
+ } else
+ opts[MONITOR] = 0;
+#else
+ opts[MONITOR] = 0;
+#endif
+}
+
+/**/
+void
+init_shout(void)
+{
+ static char shoutbuf[BUFSIZ];
+#if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC)
+ int ldisc = NTTYDISC;
+
+ ioctl(SHTTY, TIOCSETD, (char *)&ldisc);
+#endif
+
+ /* Associate terminal file descriptor with a FILE pointer */
+ shout = fdopen(SHTTY, "w");
+#ifdef _IOFBF
+ setvbuf(shout, shoutbuf, _IOFBF, BUFSIZ);
+#endif
+
+ gettyinfo(&shttyinfo); /* get tty state */
+#if defined(__sgi)
+ if (shttyinfo.tio.c_cc[VSWTCH] <= 0) /* hack for irises */
+ shttyinfo.tio.c_cc[VSWTCH] = CSWTCH;
+#endif
+}
+
+/* names of the termcap strings we want */
+
+static char *tccapnams[TC_COUNT] = {
+ "cl", "le", "LE", "nd", "RI", "up", "UP", "do",
+ "DO", "dc", "DC", "ic", "IC", "cd", "ce", "al", "dl", "ta",
+ "md", "so", "us", "me", "se", "ue"
+};
+
+/* Initialise termcap */
+
+/**/
+int
+init_term(void)
+{
+#ifndef TGETENT_ACCEPTS_NULL
+ static char termbuf[2048]; /* the termcap buffer */
+#endif
+
+ if (!*term) {
+ termflags |= TERM_UNKNOWN;
+ return 0;
+ }
+
+ /* unset zle if using zsh under emacs */
+ if (!strcmp(term, "emacs"))
+ opts[USEZLE] = 0;
+
+#ifdef TGETENT_ACCEPTS_NULL
+ /* If possible, we let tgetent allocate its own termcap buffer */
+ if (tgetent(NULL, term) != 1) {
+#else
+ if (tgetent(termbuf, term) != 1) {
+#endif
+
+ if (isset(INTERACTIVE))
+ zerr("can't find termcap info for %s", term, 0);
+ errflag = 0;
+ termflags |= TERM_BAD;
+ return 0;
+ } else {
+ char tbuf[1024], *pp;
+ int t0;
+
+ termflags &= ~TERM_BAD;
+ termflags &= ~TERM_UNKNOWN;
+ for (t0 = 0; t0 != TC_COUNT; t0++) {
+ pp = tbuf;
+ zsfree(tcstr[t0]);
+ /* AIX tgetstr() ignores second argument */
+ if (!(pp = tgetstr(tccapnams[t0], &pp)))
+ tcstr[t0] = NULL, tclen[t0] = 0;
+ else {
+ tclen[t0] = strlen(pp);
+ tcstr[t0] = (char *) zalloc(tclen[t0] + 1);
+ memcpy(tcstr[t0], pp, tclen[t0] + 1);
+ }
+ }
+
+ /* check whether terminal has automargin (wraparound) capability */
+ hasam = tgetflag("am");
+
+ tclines = tgetnum("li");
+ tccolumns = tgetnum("co");
+
+ /* if there's no termcap entry for cursor up, use single line mode: *
+ * this is flagged by termflags which is examined in zle_refresh.c *
+ */
+ if (tccan(TCUP))
+ termflags &= ~TERM_NOUP;
+ else {
+ tcstr[TCUP] = NULL;
+ termflags |= TERM_NOUP;
+ }
+
+ /* if there's no termcap entry for cursor left, use \b. */
+ if (!tccan(TCLEFT)) {
+ tcstr[TCLEFT] = ztrdup("\b");
+ tclen[TCLEFT] = 1;
+ }
+
+ /* if the termcap entry for down is \n, don't use it. */
+ if (tccan(TCDOWN) && tcstr[TCDOWN][0] == '\n') {
+ tclen[TCDOWN] = 0;
+ zsfree(tcstr[TCDOWN]);
+ tcstr[TCDOWN] = NULL;
+ }
+
+ /* if there's no termcap entry for clear, use ^L. */
+ if (!tccan(TCCLEARSCREEN)) {
+ tcstr[TCCLEARSCREEN] = ztrdup("\14");
+ tclen[TCCLEARSCREEN] = 1;
+ }
+ }
+ return 1;
+}
+
+/* Initialize lots of global variables and hash tables */
+
+/**/
+void
+setupvals(void)
+{
+#ifdef HAVE_GETPWUID
+ struct passwd *pswd;
+#endif
+ struct timezone dummy_tz;
+ char *ptr;
+#ifdef HAVE_GETRLIMIT
+ int i;
+#endif
+
+ noeval = 0;
+ curhist = 0;
+ histsiz = DEFAULT_HISTSIZE;
+ inithist();
+
+ cmdstack = (unsigned char *) zalloc(256);
+ cmdsp = 0;
+
+ bangchar = '!';
+ hashchar = '#';
+ hatchar = '^';
+ termflags = TERM_UNKNOWN;
+ curjob = prevjob = coprocin = coprocout = -1;
+ gettimeofday(&shtimer, &dummy_tz); /* init $SECONDS */
+ srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */
+
+ hostnam = (char *) zalloc(256);
+ gethostname(hostnam, 256);
+
+ /* Set default path */
+ path = (char **) zalloc(sizeof(*path) * 5);
+ path[0] = ztrdup("/bin");
+ path[1] = ztrdup("/usr/bin");
+ path[2] = ztrdup("/usr/ucb");
+ path[3] = ztrdup("/usr/local/bin");
+ path[4] = NULL;
+
+ cdpath = mkarray(NULL);
+ manpath = mkarray(NULL);
+ fignore = mkarray(NULL);
+ fpath = mkarray(NULL);
+ mailpath = mkarray(NULL);
+ watch = mkarray(NULL);
+ psvar = mkarray(NULL);
+#ifdef DYNAMIC
+ module_path = mkarray(ztrdup(MODULE_DIR));
+ modules = newlinklist();
+#endif
+
+ /* Set default prompts */
+ if(unset(INTERACTIVE)) {
+ prompt = ztrdup("");
+ prompt2 = ztrdup("");
+ } else if (emulation == EMULATE_KSH || emulation == EMULATE_SH) {
+ prompt = ztrdup(privasserted() ? "# " : "$ ");
+ prompt2 = ztrdup("> ");
+ } else {
+ prompt = ztrdup("%m%# ");
+ prompt2 = ztrdup("%_> ");
+ }
+ prompt3 = ztrdup("?# ");
+ prompt4 = ztrdup("+ ");
+ sprompt = ztrdup("zsh: correct '%R' to '%r' [nyae]? ");
+
+ ifs = ztrdup(DEFAULT_IFS);
+ wordchars = ztrdup(DEFAULT_WORDCHARS);
+ postedit = ztrdup("");
+ underscore = ztrdup("");
+
+ zoptarg = ztrdup("");
+ zoptind = 1;
+
+ ppid = (long) getppid();
+ mypid = (long) getpid();
+ term = ztrdup("");
+
+ /* The following variable assignments cause zsh to behave more *
+ * like Bourne and Korn shells when invoked as "sh" or "ksh". *
+ * NULLCMD=":" and READNULLCMD=":" */
+
+ if (emulation == EMULATE_KSH || emulation == EMULATE_SH) {
+ nullcmd = ztrdup(":");
+ readnullcmd = ztrdup(":");
+ } else {
+ nullcmd = ztrdup("cat");
+ readnullcmd = ztrdup("more");
+ }
+
+ /* We cache the uid so we know when to *
+ * recheck the info for `USERNAME' */
+ cached_uid = getuid();
+
+ /* Get password entry and set info for `HOME' and `USERNAME' */
+#ifdef HAVE_GETPWUID
+ if ((pswd = getpwuid(cached_uid))) {
+ home = metafy(pswd->pw_dir, -1, META_DUP);
+ cached_username = ztrdup(pswd->pw_name);
+ } else
+#endif /* HAVE_GETPWUID */
+ {
+ home = ztrdup("/");
+ cached_username = ztrdup("");
+ }
+
+ /* Try a cheap test to see if we can *
+ * initialize `PWD' from `HOME' */
+ if (ispwd(home))
+ pwd = ztrdup(home);
+ else if ((ptr = zgetenv("PWD")) && ispwd(ptr))
+ pwd = ztrdup(ptr);
+ else
+ pwd = metafy(zgetcwd(), -1, META_DUP);
+
+ oldpwd = ztrdup(pwd); /* initialize `OLDPWD' = `PWD' */
+
+ inittyptab(); /* initialize the ztypes table */
+ initlextabs(); /* initialize lexing tables */
+
+ createreswdtable(); /* create hash table for reserved words */
+ createaliastable(); /* create hash table for aliases */
+ createcmdnamtable(); /* create hash table for external commands */
+ createshfunctable(); /* create hash table for shell functions */
+ createbuiltintable(); /* create hash table for builtin commands */
+ createnameddirtable(); /* create hash table for named directories */
+ createparamtable(); /* create paramater hash table */
+
+#ifdef TIOCGWINSZ
+ adjustwinsize();
+#else
+ /* Using zero below sets the defaults from termcap */
+ setiparam("COLUMNS", 0);
+ setiparam("LINES", 0);
+#endif
+
+#ifdef HAVE_GETRLIMIT
+ for (i = 0; i != RLIM_NLIMITS; i++) {
+ getrlimit(i, current_limits + i);
+ limits[i] = current_limits[i];
+ }
+#endif
+
+ breaks = loops = 0;
+ lastmailcheck = time(NULL);
+ locallevel = sourcelevel = 0;
+ trapreturn = 0;
+ noerrexit = -1;
+ nohistsave = 1;
+ dirstack = newlinklist();
+ bufstack = newlinklist();
+ prepromptfns = newlinklist();
+ hsubl = hsubr = NULL;
+ lastpid = 0;
+ bshin = SHIN ? fdopen(SHIN, "r") : stdin;
+ if (isset(SHINSTDIN) && !SHIN && unset(INTERACTIVE)) {
+#ifdef _IONBF
+ setvbuf(stdin, NULL, _IONBF, 0);
+#else
+ setlinebuf(stdin);
+#endif
+ }
+
+ times(&shtms);
+}
+
+/* Initialize signal handling */
+
+/**/
+void
+init_signals(void)
+{
+ intr();
+
+#ifndef QDEBUG
+ signal_ignore(SIGQUIT);
+#endif
+
+ install_handler(SIGHUP);
+ install_handler(SIGCHLD);
+#ifdef SIGWINCH
+ install_handler(SIGWINCH);
+#endif
+ if (interact) {
+ install_handler(SIGALRM);
+ signal_ignore(SIGTERM);
+ }
+ if (jobbing) {
+ long ttypgrp;
+
+ while ((ttypgrp = gettygrp()) != -1 && ttypgrp != mypgrp)
+ kill(0, SIGTTIN);
+ if (ttypgrp == -1) {
+ opts[MONITOR] = 0;
+ } else {
+ signal_ignore(SIGTTOU);
+ signal_ignore(SIGTSTP);
+ signal_ignore(SIGTTIN);
+ attachtty(mypgrp);
+ }
+ }
+ if (islogin) {
+ signal_setmask(signal_mask(0));
+ } else if (interact) {
+ sigset_t set;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, SIGQUIT);
+ signal_unblock(set);
+ }
+}
+
+/* Source the init scripts. If called as "ksh" or "sh" *
+ * then we source the standard sh/ksh scripts instead of *
+ * the standard zsh scripts */
+
+/**/
+void
+run_init_scripts(void)
+{
+ noerrexit = -1;
+
+ if (emulation == EMULATE_KSH || emulation == EMULATE_SH) {
+ if (islogin)
+ source("/etc/profile");
+ if (unset(PRIVILEGED)) {
+ char *s = getsparam("ENV");
+ if (islogin)
+ sourcehome(".profile");
+ noerrs = 1;
+ if (s && !parsestr(s)) {
+ singsub(&s);
+ noerrs = 0;
+ source(s);
+ }
+ noerrs = 0;
+ } else
+ source("/etc/suid_profile");
+ } else {
+#ifdef GLOBAL_ZSHENV
+ source(GLOBAL_ZSHENV);
+#endif
+ if (isset(RCS)) {
+ if (unset(PRIVILEGED))
+ sourcehome(".zshenv");
+ if (islogin) {
+#ifdef GLOBAL_ZPROFILE
+ source(GLOBAL_ZPROFILE);
+#endif
+ if (unset(PRIVILEGED))
+ sourcehome(".zprofile");
+ }
+ if (interact) {
+#ifdef GLOBAL_ZSHRC
+ source(GLOBAL_ZSHRC);
+#endif
+ if (unset(PRIVILEGED))
+ sourcehome(".zshrc");
+ }
+ if (islogin) {
+#ifdef GLOBAL_ZLOGIN
+ source(GLOBAL_ZLOGIN);
+#endif
+ if (unset(PRIVILEGED))
+ sourcehome(".zlogin");
+ }
+ }
+ }
+ noerrexit = 0;
+ nohistsave = 0;
+}
+
+/* Miscellaneous initializations that happen after init scripts are run */
+
+/**/
+void
+init_misc(void)
+{
+ if (*zsh_name == 'r' || restricted)
+ dosetopt(RESTRICTED, 1, 0);
+ if (cmd) {
+ if (SHIN >= 10)
+ fclose(bshin);
+ SHIN = movefd(open("/dev/null", O_RDONLY | O_NOCTTY));
+ bshin = fdopen(SHIN, "r");
+ execstring(cmd, 0, 1);
+ stopmsg = 1;
+ zexit(lastval, 0);
+ }
+
+ if (interact && isset(RCS))
+ readhistfile(getsparam("HISTFILE"), 0);
+}
+
+/* source a file */
+
+/**/
+int
+source(char *s)
+{
+ int tempfd, fd, cj, oldlineno;
+ int oldshst, osubsh, oloops;
+ FILE *obshin;
+ char *old_scriptname = scriptname;
+
+ if (!s || (tempfd = movefd(open(unmeta(s), O_RDONLY | O_NOCTTY))) == -1) {
+ return 1;
+ }
+
+ /* save the current shell state */
+ fd = SHIN; /* store the shell input fd */
+ obshin = bshin; /* store file handle for buffered shell input */
+ osubsh = subsh; /* store whether we are in a subshell */
+ cj = thisjob; /* store our current job number */
+ oldlineno = lineno; /* store our current lineno */
+ oloops = loops; /* stored the # of nested loops we are in */
+ oldshst = opts[SHINSTDIN]; /* store current value of this option */
+
+ SHIN = tempfd;
+ bshin = fdopen(SHIN, "r");
+ subsh = 0;
+ lineno = 0;
+ loops = 0;
+ dosetopt(SHINSTDIN, 0, 1);
+ scriptname = s;
+
+ sourcelevel++;
+ loop(0, 0); /* loop through the file to be sourced */
+ sourcelevel--;
+ fclose(bshin);
+ fdtable[SHIN] = 0;
+
+ /* restore the current shell state */
+ SHIN = fd; /* the shell input fd */
+ bshin = obshin; /* file handle for buffered shell input */
+ subsh = osubsh; /* whether we are in a subshell */
+ thisjob = cj; /* current job number */
+ lineno = oldlineno; /* our current lineno */
+ loops = oloops; /* the # of nested loops we are in */
+ dosetopt(SHINSTDIN, oldshst, 1); /* SHINSTDIN option */
+ errflag = 0;
+ retflag = 0;
+ scriptname = old_scriptname;
+
+ return 0;
+}
+
+/* Try to source a file in the home directory */
+
+/**/
+void
+sourcehome(char *s)
+{
+ char buf[PATH_MAX];
+ char *h;
+
+ if (emulation == EMULATE_SH || emulation == EMULATE_KSH ||
+ !(h = getsparam("ZDOTDIR")))
+ h = home;
+ if (strlen(h) + strlen(s) + 1 >= PATH_MAX) {
+ zerr("path too long: %s", s, 0);
+ return;
+ }
+ sprintf(buf, "%s/%s", h, s);
+ source(buf);
+}
+
+/**/
+void
+init_bltinmods(void)
+{
+ static struct module mod = { NULL, 0, NULL, NULL };
+#include "bltinmods.list"
+ mod.nam = NULL;
+}
+
+/* ZLE entry point pointers. They are defined here because the initial *
+ * values depend on whether ZLE is linked in or not -- if it is, we *
+ * avoid wasting space with the fallback functions. No other source *
+ * file needs to know which modules are linked in. */
+
+#ifdef LINKED_XMOD_zle
+
+/**/
+ZleVoidFn trashzleptr;
+/**/
+ZleVoidFn gotwordptr;
+/**/
+ZleVoidFn refreshptr;
+/**/
+ZleVoidIntFn spaceinlineptr;
+/**/
+ZleReadFn zlereadptr;
+
+#else /* !LINKED_XMOD_zle */
+
+ZleVoidFn trashzleptr = noop_function;
+ZleVoidFn gotwordptr = noop_function;
+ZleVoidFn refreshptr = noop_function;
+ZleVoidIntFn spaceinlineptr = noop_function_int;
+# ifdef UNLINKED_XMOD_zle
+ZleReadFn zlereadptr = autoload_zleread;
+# else /* !UNLINKED_XMOD_zle */
+ZleReadFn zlereadptr = fallback_zleread;
+# endif /* !UNLINKED_XMOD_zle */
+
+/**/
+void
+noop_function(void)
+{
+ /* do nothing */
+}
+
+/**/
+void
+noop_function_int(int nothing)
+{
+ /* do nothing */
+}
+
+# ifdef UNLINKED_XMOD_zle
+
+/**/
+static unsigned char *
+autoload_zleread(char *lp, char *rp, int ha)
+{
+ zlereadptr = fallback_zleread;
+ load_module("zle");
+ return zleread(lp, rp, ha);
+}
+
+# endif /* UNLINKED_XMOD_zle */
+
+/**/
+unsigned char *
+fallback_zleread(char *lp, char *rp, int ha)
+{
+ char *pptbuf;
+ int pptlen;
+
+ pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen);
+ write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+ free(pptbuf);
+ return (unsigned char *)shingetline();
+}
+
+#endif /* !LINKED_XMOD_zle */
+
+/* compctl entry point pointers. Similar to the ZLE ones. */
+
+#ifdef LINKED_XMOD_comp1
+
+/**/
+CompctlReadFn compctlreadptr;
+
+#else /* !LINKED_XMOD_comp1 */
+
+CompctlReadFn compctlreadptr = fallback_compctlread;
+
+/**/
+int
+fallback_compctlread(char *name, char **args, char *ops, char *reply)
+{
+ zwarnnam(name, "option valid only in functions called from completion",
+ NULL, 0);
+ return 1;
+}
+
+#endif /* !LINKED_XMOD_comp1 */
diff --git a/Src/input.c b/Src/input.c
new file mode 100644
index 000000000..576341a7c
--- /dev/null
+++ b/Src/input.c
@@ -0,0 +1,530 @@
+/*
+ * input.c - read and store lines of input
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+
+/*
+ * This file deals with input buffering, supplying characters to the
+ * history expansion code a character at a time. Input is stored on a
+ * stack, which allows insertion of strings into the input, possibly with
+ * flags marking the end of alias expansion, with minimal copying of
+ * strings. The same stack is used to record the fact that the input
+ * is a history or alias expansion and to store the alias while it is in use.
+ *
+ * Input is taken either from zle, if appropriate, or read directly from
+ * the input file, or may be supplied by some other part of the shell (such
+ * as `eval' or $(...) substitution). In the last case, it should be
+ * supplied by pushing a new level onto the stack, via inpush(input_string,
+ * flag, alias); if the current input really needs to be altered, use
+ * inputsetline(input_string, flag). `Flag' can include or's of INP_FREE
+ * (if the input string is to be freed when used), INP_CONT (if the input
+ * is to continue onto what's already in the input queue), INP_ALIAS
+ * (push supplied alias onto stack) or INP_HIST (ditto, but used to
+ * mark history expansion). `alias' is ignored unless INP_ALIAS or
+ * INP_HIST is supplied. INP_ALIAS is always set if INP_HIST is.
+ *
+ * Note that the input string is itself used as the input buffer: it is not
+ * copied, nor is it every written back to, so using a constant string
+ * should work. Consequently, when passing areas of memory from the heap
+ * it is necessary that that heap last as long as the operation of reading
+ * the string. After the string is read, the stack should be popped with
+ * inpop(), which effectively flushes any unread input as well as restoring
+ * the previous input state.
+ *
+ * The internal flag INP_ALCONT shows that the stack element was pushed
+ * by an alias expansion; it should not be needed elsewhere.
+ *
+ * The global variable inalmore is set to indicate aliases should
+ * continue to be expanded because the last alias expansion ended
+ * in a space. It is only reset after a complete word was read
+ * without expanding a new alias, in exalias().
+ *
+ * PWS 1996/12/10
+ */
+
+#include "zsh.mdh"
+#include "input.pro"
+
+/* the shell input fd */
+
+/**/
+int SHIN;
+
+/* buffered shell input for non-interactive shells */
+
+/**/
+FILE *bshin;
+
+/* != 0 means we are reading input from a string */
+
+/**/
+int strin;
+
+/* total # of characters waiting to be read. */
+
+/**/
+int inbufct;
+
+/* the flags controlling the input routines in input.c: see INP_* in zsh.h */
+
+/**/
+int inbufflags;
+
+static char *inbuf; /* Current input buffer */
+static char *inbufptr; /* Pointer into input buffer */
+static char *inbufpush; /* Character at which to re-push alias */
+static int inbufleft; /* Characters left in current input
+ stack element */
+
+
+ /* Input must be stacked since the input queue is used by
+ * various different parts of the shell.
+ */
+
+struct instacks {
+ char *buf, *bufptr;
+ Alias alias;
+ int bufleft, bufct, flags;
+};
+static struct instacks *instack, *instacktop;
+/*
+ * Input stack size. We need to push the stack for aliases, history
+ * expansion, and reading from internal strings: only if these operations
+ * are nested do we need more than one extra level. Thus we shouldn't need
+ * too much space as a rule. Initially, INSTACK_INITIAL is allocated; if
+ * more is required, an extra INSTACK_EXPAND is added each time.
+ */
+#define INSTACK_INITIAL 4
+#define INSTACK_EXPAND 4
+
+static int instacksz = INSTACK_INITIAL;
+
+/* Read a line from bshin. Convert tokens and *
+ * null characters to Meta c^32 character pairs. */
+
+/**/
+char *
+shingetline(void)
+{
+ char *line = NULL;
+ int ll = 0;
+ int c;
+ char buf[BUFSIZ];
+ char *p;
+
+ p = buf;
+ for (;;) {
+ do {
+ errno = 0;
+ c = fgetc(bshin);
+ } while (c < 0 && errno == EINTR);
+ if (c < 0 || c == '\n') {
+ if (c == '\n')
+ *p++ = '\n';
+ if (p > buf) {
+ *p++ = '\0';
+ line = zrealloc(line, ll + (p - buf));
+ memcpy(line + ll, buf, p - buf);
+ }
+ return line;
+ }
+ if (imeta(c)) {
+ *p++ = Meta;
+ *p++ = c ^ 32;
+ } else
+ *p++ = c;
+ if (p >= buf + BUFSIZ - 1) {
+ line = zrealloc(line, ll + (p - buf) + 1);
+ memcpy(line + ll, buf, p - buf);
+ ll += p - buf;
+ line[ll] = '\0';
+ p = buf;
+ }
+ }
+}
+
+/* Get the next character from the input.
+ * Will call inputline() to get a new line where necessary.
+ */
+
+/**/
+int
+ingetc(void)
+{
+ char lastc;
+
+ if (lexstop)
+ return ' ';
+ for (;;) {
+ if (inbufleft) {
+ inbufleft--;
+ inbufct--;
+ if (itok(lastc = STOUC(*inbufptr++)))
+ continue;
+ return lastc;
+ }
+
+ /* If the next element down the input stack is a continuation of
+ * this, use it.
+ */
+ if (inbufflags & INP_CONT) {
+ inpoptop();
+ continue;
+ }
+ /*
+ * Otherwise, see if we have reached the end of input
+ * (due to an error, or to reading from a single string).
+ */
+ if (strin || errflag) {
+ lexstop = 1;
+ return ' ';
+ }
+ /* As a last resort, get some more input */
+ if (inputline())
+ return ' ';
+ }
+}
+
+/* Read a line from the current command stream and store it as input */
+
+/**/
+static int
+inputline(void)
+{
+ char *ingetcline, *ingetcpmptl = NULL, *ingetcpmptr = NULL;
+
+ /* If reading code interactively, work out the prompts. */
+ if (interact && isset(SHINSTDIN))
+ if (!isfirstln)
+ ingetcpmptl = prompt2;
+ else {
+ ingetcpmptl = prompt;
+ if (rprompt)
+ ingetcpmptr = rprompt;
+ }
+ if (!(interact && isset(SHINSTDIN) && SHTTY != -1 && isset(USEZLE))) {
+ /*
+ * If not using zle, read the line straight from the input file.
+ * Possibly we don't get the whole line at once: in that case,
+ * we get another chunk with the next call to inputline().
+ */
+
+ if (interact && isset(SHINSTDIN)) {
+ /*
+ * We may still be interactive (e.g. running under emacs),
+ * so output a prompt if necessary. We don't know enough
+ * about the input device to be able to handle an rprompt,
+ * though.
+ */
+ char *pptbuf;
+ int pptlen;
+ pptbuf = unmetafy(promptexpand(ingetcpmptl, 0, NULL, NULL), &pptlen);
+ write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+ free(pptbuf);
+ }
+ ingetcline = shingetline();
+ } else
+ ingetcline = (char *)zleread(ingetcpmptl, ingetcpmptr, 1);
+ if (!ingetcline) {
+ return lexstop = 1;
+ }
+ if (errflag) {
+ free(ingetcline);
+ return lexstop = errflag = 1;
+ }
+ /* Look for a space, to see if this shouldn't be put into history */
+ if (isfirstln)
+ spaceflag = *ingetcline == ' ';
+ if (isset(VERBOSE)) {
+ /* Output the whole line read so far. */
+ zputs(ingetcline, stderr);
+ fflush(stderr);
+ }
+ if (*ingetcline && ingetcline[strlen(ingetcline) - 1] == '\n') {
+ /* We've now read a complete line. */
+ lineno++;
+ if (interact && isset(SUNKEYBOARDHACK) && isset(SHINSTDIN) &&
+ SHTTY != -1 && *ingetcline && ingetcline[1] &&
+ ingetcline[strlen(ingetcline) - 2] == '`') {
+ /* Junk an unmatched "`" at the end of the line. */
+ int ct;
+ char *ptr;
+
+ for (ct = 0, ptr = ingetcline; *ptr; ptr++)
+ if (*ptr == '`')
+ ct++;
+ if (ct & 1) {
+ ptr[-2] = '\n';
+ ptr[-1] = '\0';
+ }
+ }
+ }
+ isfirstch = 1;
+ /* Put this into the input channel. */
+ inputsetline(ingetcline, INP_FREE);
+
+ return 0;
+}
+
+/*
+ * Put a string in the input queue:
+ * inbuf is only freeable if the flags include INP_FREE.
+ */
+
+/**/
+static void
+inputsetline(char *str, int flags)
+{
+ if ((inbufflags & INP_FREE) && inbuf) {
+ free(inbuf);
+ }
+ inbuf = inbufptr = str;
+ inbufleft = strlen(inbuf);
+
+ /*
+ * inbufct must reflect the total number of characters left,
+ * as it used by other parts of the shell, so we need to take account
+ * of whether the input stack continues, and whether there
+ * is an extra space to add on at the end.
+ */
+ if (flags & INP_CONT)
+ inbufct += inbufleft;
+ else
+ inbufct = inbufleft;
+ inbufflags = flags;
+}
+
+/*
+ * Backup one character of the input.
+ * The last character can always be backed up, provided we didn't just
+ * expand an alias or a history reference.
+ * In fact, the character is ignored and the previous character is used.
+ * (If that's wrong, the bug is in the calling code. Use the #ifdef DEBUG
+ * code to check.)
+ */
+
+/**/
+void
+inungetc(int c)
+{
+ if (!lexstop) {
+ if (inbufptr != inbuf) {
+#ifdef DEBUG
+ /* Just for debugging: enable only if foul play suspected. */
+ if (inbufptr[-1] != (char) c)
+ fprintf(stderr, "Warning: backing up wrong character.\n");
+#endif
+ /* Just decrement the pointer: if it's not the same
+ * character being pushed back, we're in trouble anyway.
+ */
+ inbufptr--;
+ inbufct++;
+ inbufleft++;
+ }
+#ifdef DEBUG
+ else if (!(inbufflags & INP_CONT)) {
+ /* Just for debugging */
+ fprintf(stderr, "Attempt to inungetc() at start of input.\n");
+ }
+#endif
+ else {
+ /*
+ * The character is being backed up from a previous input stack
+ * layer. However, there was an expansion in the middle, so we
+ * can't back up where we want to. Instead, we just push it
+ * onto the input stack as an extra character.
+ */
+ char *cback = (char *)zcalloc(2);
+ cback[0] = (char) c;
+ inpush(cback, INP_FREE|INP_CONT, NULL);
+ }
+ /* If we are back at the start of a segment,
+ * we may need to restore an alias popped from the stack.
+ * Note this may be a dummy (history expansion) entry.
+ */
+ if (inbufptr == inbufpush && inbufflags & INP_ALCONT) {
+ /*
+ * Go back up the stack over all entries which were alias
+ * expansions and were pushed with nothing remaining to read.
+ */
+ do {
+ if (instacktop->alias)
+ instacktop->alias->inuse = 1;
+ instacktop++;
+ } while ((instacktop->flags & INP_ALCONT) && !instacktop->bufleft);
+ inbufflags = INP_CONT|INP_ALIAS;
+ inbufleft = 0;
+ inbuf = inbufptr = "";
+ }
+ }
+}
+
+/* stuff a whole file into the input queue and print it */
+
+/**/
+int
+stuff(char *fn)
+{
+ FILE *in;
+ char *buf;
+ int len;
+
+ if (!(in = fopen(unmeta(fn), "r"))) {
+ zerr("can't open %s", fn, 0);
+ return 1;
+ }
+ fseek(in, 0, 2);
+ len = ftell(in);
+ fseek(in, 0, 0);
+ buf = (char *)zalloc(len + 1);
+ if (!(fread(buf, len, 1, in))) {
+ zerr("read error on %s", fn, 0);
+ fclose(in);
+ zfree(buf, len + 1);
+ return 1;
+ }
+ fclose(in);
+ buf[len] = '\0';
+ fwrite(buf, len, 1, stderr);
+ fflush(stderr);
+ inputsetline(metafy(buf, len, META_REALLOC), INP_FREE);
+ return 0;
+}
+
+/* flush input queue */
+
+/**/
+void
+inerrflush(void)
+{
+ while (!lexstop && inbufct)
+ ingetc();
+}
+
+/* Set some new input onto a new element of the input stack */
+
+/**/
+void
+inpush(char *str, int flags, Alias inalias)
+{
+ if (!instack) {
+ /* Initial stack allocation */
+ instack = (struct instacks *)zalloc(instacksz*sizeof(struct instacks));
+ instacktop = instack;
+ }
+
+ instacktop->buf = inbuf;
+ instacktop->bufptr = inbufptr;
+ instacktop->bufleft = inbufleft;
+ instacktop->bufct = inbufct;
+ inbufflags &= ~INP_ALCONT;
+ if (flags & (INP_ALIAS|INP_HIST)) {
+ /*
+ * Text is expansion for history or alias, so continue
+ * back to old level when done. Also mark stack top
+ * as alias continuation so as to back up if necessary,
+ * and mark alias as in use.
+ */
+ flags |= INP_CONT|INP_ALIAS;
+ instacktop->flags = inbufflags | INP_ALCONT;
+ if ((instacktop->alias = inalias))
+ inalias->inuse = 1;
+ } else {
+ /* If we are continuing an alias expansion, record the alias
+ * expansion in new set of flags (do we need this?)
+ */
+ if (((instacktop->flags = inbufflags) & INP_ALIAS) &&
+ (flags & INP_CONT))
+ flags |= INP_ALIAS;
+ }
+
+ instacktop++;
+ if (instacktop == instack + instacksz) {
+ /* Expand the stack */
+ instack = (struct instacks *)
+ realloc(instack,
+ (instacksz + INSTACK_EXPAND)*sizeof(struct instacks));
+ instacktop = instack + instacksz;
+ instacksz += INSTACK_EXPAND;
+ }
+ /*
+ * We maintain the entry above the highest one with real
+ * text as a flag to inungetc() that it can stop re-pushing the stack.
+ */
+ instacktop->flags = 0;
+
+ inbufpush = inbuf = NULL;
+
+ inputsetline(str, flags);
+}
+
+/* Remove the top element of the stack */
+
+/**/
+static void
+inpoptop(void)
+{
+ if (inbuf && (inbufflags & INP_FREE))
+ free(inbuf);
+
+ instacktop--;
+
+ inbuf = instacktop->buf;
+ inbufptr = inbufpush = instacktop->bufptr;
+ inbufleft = instacktop->bufleft;
+ inbufct = instacktop->bufct;
+ inbufflags = instacktop->flags;
+
+ if (!(inbufflags & INP_ALCONT))
+ return;
+
+ if (instacktop->alias) {
+ char *t = instacktop->alias->text;
+ /* a real alias: mark it as unused. */
+ instacktop->alias->inuse = 0;
+ if (*t && t[strlen(t) - 1] == ' ') {
+ inalmore = 1;
+ histbackword();
+ }
+ }
+}
+
+/* Remove the top element of the stack and all its continuations. */
+
+/**/
+void
+inpop(void)
+{
+ int remcont;
+
+ do {
+ remcont = inbufflags & INP_CONT;
+
+ inpoptop();
+ } while (remcont);
+}
diff --git a/Src/jobs.c b/Src/jobs.c
new file mode 100644
index 000000000..331902d9f
--- /dev/null
+++ b/Src/jobs.c
@@ -0,0 +1,1361 @@
+/*
+ * jobs.c - job control
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "jobs.pro"
+
+/* the process group of the shell */
+
+/**/
+pid_t mypgrp;
+
+/* the job we are working on */
+
+/**/
+int thisjob;
+
+/* the current job (+) */
+
+/**/
+int curjob;
+
+/* the previous job (-) */
+
+/**/
+int prevjob;
+
+/* the job table */
+
+/**/
+struct job jobtab[MAXJOB];
+
+/* shell timings */
+
+/**/
+struct tms shtms;
+
+/* 1 if ttyctl -f has been executed */
+
+/**/
+int ttyfrozen;
+
+/* empty job structure for quick clearing of jobtab entries */
+
+static struct job zero; /* static variables are initialized to zero */
+
+static struct timeval dtimeval, now;
+
+/* Diff two timevals for elapsed-time computations */
+
+/**/
+static struct timeval *
+dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
+{
+ dt->tv_sec = t2->tv_sec - t1->tv_sec;
+ dt->tv_usec = t2->tv_usec - t1->tv_usec;
+ if (dt->tv_usec < 0) {
+ dt->tv_usec += 1000000.0;
+ dt->tv_sec -= 1.0;
+ }
+ return dt;
+}
+
+/* change job table entry from stopped to running */
+
+/**/
+void
+makerunning(Job jn)
+{
+ Process pn;
+
+ jn->stat &= ~STAT_STOPPED;
+ for (pn = jn->procs; pn; pn = pn->next)
+ if (WIFSTOPPED(pn->status) &&
+ (!(jn->stat & STAT_SUPERJOB) || pn->next))
+ pn->status = SP_RUNNING;
+
+ if (jn->stat & STAT_SUPERJOB)
+ makerunning(jobtab + jn->other);
+}
+
+/* Find process and job associated with pid. *
+ * Return 1 if search was successful, else return 0. */
+
+/**/
+int
+findproc(pid_t pid, Job *jptr, Process *pptr)
+{
+ Process pn;
+ int i;
+
+ for (i = 1; i < MAXJOB; i++)
+ for (pn = jobtab[i].procs; pn; pn = pn->next)
+ if (pn->pid == pid) {
+ *pptr = pn;
+ *jptr = jobtab + i;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Update status of process that we have just WAIT'ed for */
+
+/**/
+void
+update_process(Process pn, int status)
+{
+ struct timezone dummy_tz;
+ long childs, childu;
+
+ childs = shtms.tms_cstime;
+ childu = shtms.tms_cutime;
+ times(&shtms); /* get time-accounting info */
+
+ pn->status = status; /* save the status returned by WAIT */
+ pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */
+ pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */
+
+ gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */
+}
+
+/* Update status of job, possibly printing it */
+
+/**/
+void
+update_job(Job jn)
+{
+ Process pn;
+ int job;
+ int val = 0, status = 0;
+ int somestopped = 0, inforeground = 0;
+
+ for (pn = jn->procs; pn; pn = pn->next) {
+ if (pn->status == SP_RUNNING) /* some processes in this job are running */
+ return; /* so no need to update job table entry */
+ if (WIFSTOPPED(pn->status)) /* some processes are stopped */
+ somestopped = 1; /* so job is not done, but entry needs updating */
+ if (!pn->next) /* last job in pipeline determines exit status */
+ val = (WIFSIGNALED(pn->status)) ? 0200 | WTERMSIG(pn->status) :
+ WEXITSTATUS(pn->status);
+ if (pn->pid == jn->gleader) /* if this process is process group leader */
+ status = pn->status;
+ }
+
+ job = jn - jobtab; /* compute job number */
+
+ if (somestopped) {
+ if (jn->stty_in_env && !jn->ty) {
+ jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo));
+ gettyinfo(jn->ty);
+ }
+ if (jn->stat & STAT_STOPPED)
+ return;
+ } else { /* job is done, so remember return value */
+ lastval2 = val;
+ /* If last process was run in the current shell, keep old status
+ * and let it handle its own traps
+ */
+ if (job == thisjob && !(jn->stat & STAT_CURSH)) {
+ lastval = val;
+ inforeground = 1;
+ }
+ }
+
+ if (shout && !ttyfrozen && !jn->stty_in_env && !zleactive &&
+ job == thisjob && !somestopped && !(jn->stat & STAT_NOSTTY))
+ gettyinfo(&shttyinfo);
+
+ if (isset(MONITOR)) {
+ pid_t pgrp = gettygrp(); /* get process group of tty */
+
+ /* is this job in the foreground of an interactive shell? */
+ if (mypgrp != pgrp && inforeground &&
+ (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) {
+ attachtty(mypgrp);
+ adjustwinsize(); /* check window size and adjust if necessary */
+ }
+ }
+
+ if (somestopped && jn->stat & STAT_SUPERJOB)
+ return;
+ jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
+ STAT_CHANGED | STAT_DONE;
+ if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
+ prevjob = curjob;
+ curjob = job;
+ }
+ if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
+ printjob(jn, !!isset(LONGLISTJOBS), 0);
+ if (zleactive)
+ refresh();
+ }
+ if (sigtrapped[SIGCHLD] && job != thisjob)
+ dotrap(SIGCHLD);
+
+ /* When MONITOR is set, the foreground process runs in a different *
+ * process group from the shell, so the shell will not receive *
+ * terminal signals, therefore we we pretend that the shell got *
+ * the signal too. */
+ if (inforeground && isset(MONITOR) && WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+
+ if (sig == SIGINT || sig == SIGQUIT) {
+ if (sigtrapped[sig]) {
+ dotrap(sig);
+ /* We keep the errflag as set or not by dotrap.
+ * This is to fulfil the promise to carry on
+ * with the jobs if trap returns zero.
+ * Setting breaks = loops ensures a consistent return
+ * status if inside a loop. Maybe the code in loops
+ * should be changed.
+ */
+ if (errflag)
+ breaks = loops;
+ } else {
+ breaks = loops;
+ errflag = 1;
+ }
+ }
+ }
+}
+
+/* set the previous job to something reasonable */
+
+/**/
+static void
+setprevjob(void)
+{
+ int i;
+
+ for (i = MAXJOB - 1; i; i--)
+ if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
+ i != curjob && i != thisjob) {
+ prevjob = i;
+ return;
+ }
+
+ for (i = MAXJOB - 1; i; i--)
+ if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) {
+ prevjob = i;
+ return;
+ }
+
+ prevjob = -1;
+}
+
+static long clktck = 0;
+
+/**/
+static void
+set_clktck(void)
+{
+#ifdef _SC_CLK_TCK
+ if (!clktck)
+ /* fetch clock ticks per second from *
+ * sysconf only the first time */
+ clktck = sysconf(_SC_CLK_TCK);
+#else
+# ifdef __NeXT__
+ /* NeXTStep 3.3 defines CLK_TCK wrongly */
+ clktck = 60;
+# else
+# ifdef CLK_TCK
+ clktck = CLK_TCK;
+# else
+# ifdef HZ
+ clktck = HZ;
+# else
+ clktck = 60;
+# endif
+# endif
+# endif
+#endif
+}
+
+/**/
+static void
+printhhmmss(double secs)
+{
+ int mins = (int) secs / 60;
+ int hours = mins / 60;
+
+ secs -= 60 * mins;
+ mins -= 60 * hours;
+ if (hours)
+ fprintf(stderr, "%d:%02d:%05.2f", hours, mins, secs);
+ else if (mins)
+ fprintf(stderr, "%d:%05.2f", mins, secs);
+ else
+ fprintf(stderr, "%.3f", secs);
+}
+
+/**/
+static void
+printtime(struct timeval *real, struct timeinfo *ti, char *desc)
+{
+ char *s;
+ double elapsed_time, user_time, system_time;
+ int percent;
+
+ if (!desc)
+ desc = "";
+
+ set_clktck();
+ /* go ahead and compute these, since almost every TIMEFMT will have them */
+ elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
+ user_time = ti->ut / (double) clktck;
+ system_time = ti->st / (double) clktck;
+ percent = 100.0 * (ti->ut + ti->st)
+ / (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0);
+
+ if (!(s = getsparam("TIMEFMT")))
+ s = DEFAULT_TIMEFMT;
+
+ for (; *s; s++)
+ if (*s == '%')
+ switch (*++s) {
+ case 'E':
+ fprintf(stderr, "%4.2fs", elapsed_time);
+ break;
+ case 'U':
+ fprintf(stderr, "%4.2fs", user_time);
+ break;
+ case 'S':
+ fprintf(stderr, "%4.2fs", system_time);
+ break;
+ case '*':
+ switch (*++s) {
+ case 'E':
+ printhhmmss(elapsed_time);
+ break;
+ case 'U':
+ printhhmmss(user_time);
+ break;
+ case 'S':
+ printhhmmss(system_time);
+ break;
+ default:
+ fprintf(stderr, "%%*");
+ s--;
+ break;
+ }
+ break;
+ case 'P':
+ fprintf(stderr, "%d%%", percent);
+ break;
+ case 'J':
+ fprintf(stderr, "%s", desc);
+ break;
+ case '%':
+ putc('%', stderr);
+ break;
+ case '\0':
+ s--;
+ break;
+ default:
+ fprintf(stderr, "%%%c", *s);
+ break;
+ } else
+ putc(*s, stderr);
+ putc('\n', stderr);
+ fflush(stderr);
+}
+
+/**/
+static void
+dumptime(Job jn)
+{
+ Process pn;
+
+ if (!jn->procs)
+ return;
+ for (pn = jn->procs; pn; pn = pn->next)
+ printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, pn->text);
+}
+
+/* Check whether shell should report the amount of time consumed *
+ * by job. This will be the case if we have preceded the command *
+ * with the keyword time, or if REPORTTIME is non-negative and the *
+ * amount of time consumed by the job is greater than REPORTTIME */
+
+/**/
+static int
+should_report_time(Job j)
+{
+ Value v;
+ char *s = "REPORTTIME";
+ int reporttime;
+
+ /* if the time keyword was used */
+ if (j->stat & STAT_TIMED)
+ return 1;
+
+ if (!(v = getvalue(&s, 0)) || (reporttime = getintvalue(v)) < 0)
+ return 0;
+
+ /* can this ever happen? */
+ if (!j->procs)
+ return 0;
+
+ set_clktck();
+ return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime);
+}
+
+/* !(lng & 3) means jobs *
+ * (lng & 1) means jobs -l *
+ * (lng & 2) means jobs -p
+ * (lng & 4) means jobs -d
+ *
+ * synch = 0 means asynchronous
+ * synch = 1 means synchronous
+ * synch = 2 means called synchronously from jobs
+*/
+
+/**/
+void
+printjob(Job jn, int lng, int synch)
+{
+ Process pn;
+ int job = jn - jobtab, len = 9, sig, sflag = 0, llen;
+ int conted = 0, lineleng = columns, skip = 0, doputnl = 0;
+ FILE *fout = (synch == 2) ? stdout : shout;
+
+ if (jn->stat & STAT_NOPRINT)
+ return;
+
+ if (lng < 0) {
+ conted = 1;
+ lng = 0;
+ }
+
+/* find length of longest signame, check to see */
+/* if we really need to print this job */
+
+ for (pn = jn->procs; pn; pn = pn->next) {
+ if (jn->stat & STAT_SUPERJOB &&
+ jn->procs->status == SP_RUNNING && !pn->next)
+ pn->status = SP_RUNNING;
+ if (pn->status != SP_RUNNING)
+ if (WIFSIGNALED(pn->status)) {
+ sig = WTERMSIG(pn->status);
+ llen = strlen(sigmsg[sig]);
+ if (WCOREDUMP(pn->status))
+ llen += 14;
+ if (llen > len)
+ len = llen;
+ if (sig != SIGINT && sig != SIGPIPE)
+ sflag = 1;
+ if (job == thisjob && sig == SIGINT)
+ doputnl = 1;
+ } else if (WIFSTOPPED(pn->status)) {
+ sig = WSTOPSIG(pn->status);
+ if ((int)strlen(sigmsg[sig]) > len)
+ len = strlen(sigmsg[sig]);
+ if (job == thisjob && sig == SIGTSTP)
+ doputnl = 1;
+ } else if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
+ WEXITSTATUS(pn->status))
+ sflag = 1;
+ }
+
+/* print if necessary */
+
+ if (interact && jobbing && ((jn->stat & STAT_STOPPED) || sflag ||
+ job != thisjob)) {
+ int len2, fline = 1;
+ Process qn;
+
+ if (!synch)
+ trashzle();
+ if (doputnl && !synch)
+ putc('\n', fout);
+ for (pn = jn->procs; pn;) {
+ len2 = ((job == thisjob) ? 5 : 10) + len; /* 2 spaces */
+ if (lng & 3)
+ qn = pn->next;
+ else
+ for (qn = pn->next; qn; qn = qn->next) {
+ if (qn->status != pn->status)
+ break;
+ if ((int)strlen(qn->text) + len2 + ((qn->next) ? 3 : 0) > lineleng)
+ break;
+ len2 += strlen(qn->text) + 2;
+ }
+ if (job != thisjob)
+ if (fline)
+ fprintf(fout, "[%ld] %c ",
+ (long)(jn - jobtab),
+ (job == curjob) ? '+'
+ : (job == prevjob) ? '-' : ' ');
+ else
+ fprintf(fout, (job > 9) ? " " : " ");
+ else
+ fprintf(fout, "zsh: ");
+ if (lng & 1)
+ fprintf(fout, "%ld ", (long) pn->pid);
+ else if (lng & 2) {
+ pid_t x = jn->gleader;
+
+ fprintf(fout, "%ld ", (long) x);
+ do
+ skip++;
+ while ((x /= 10));
+ skip++;
+ lng &= ~3;
+ } else
+ fprintf(fout, "%*s", skip, "");
+ if (pn->status == SP_RUNNING)
+ if (!conted)
+ fprintf(fout, "running%*s", len - 7 + 2, "");
+ else
+ fprintf(fout, "continued%*s", len - 9 + 2, "");
+ else if (WIFEXITED(pn->status))
+ if (WEXITSTATUS(pn->status))
+ fprintf(fout, "exit %-4d%*s", WEXITSTATUS(pn->status),
+ len - 9 + 2, "");
+ else
+ fprintf(fout, "done%*s", len - 4 + 2, "");
+ else if (WIFSTOPPED(pn->status))
+ fprintf(fout, "%-*s", len + 2, sigmsg[WSTOPSIG(pn->status)]);
+ else if (WCOREDUMP(pn->status))
+ fprintf(fout, "%s (core dumped)%*s",
+ sigmsg[WTERMSIG(pn->status)],
+ (int)(len - 14 + 2 - strlen(sigmsg[WTERMSIG(pn->status)])), "");
+ else
+ fprintf(fout, "%-*s", len + 2, sigmsg[WTERMSIG(pn->status)]);
+ for (; pn != qn; pn = pn->next)
+ fprintf(fout, (pn->next) ? "%s | " : "%s", pn->text);
+ putc('\n', fout);
+ fline = 0;
+ }
+ fflush(fout);
+ } else if (doputnl && interact && !synch) {
+ putc('\n', fout);
+ fflush(fout);
+ }
+
+/* print "(pwd now: foo)" messages: with (lng & 4) we are printing
+ * the directory where the job is running, otherwise the current directory
+ */
+
+ if ((lng & 4) || (interact && job == thisjob && strcmp(jn->pwd, pwd))) {
+ fprintf(shout, "(pwd %s: ", (lng & 4) ? "" : "now");
+ fprintdir((lng & 4) ? jn->pwd : pwd, shout);
+ fprintf(shout, ")\n");
+ fflush(shout);
+ }
+/* delete job if done */
+
+ if (jn->stat & STAT_DONE) {
+ if (should_report_time(jn))
+ dumptime(jn);
+ deletejob(jn);
+ if (job == curjob) {
+ curjob = prevjob;
+ prevjob = job;
+ }
+ if (job == prevjob)
+ setprevjob();
+ } else
+ jn->stat &= ~STAT_CHANGED;
+}
+
+/**/
+void
+deletefilelist(LinkList file_list)
+{
+ char *s;
+ if (file_list) {
+ while ((s = (char *)getlinknode(file_list))) {
+ unlink(s);
+ zsfree(s);
+ }
+ zfree(file_list, sizeof(struct linklist));
+ }
+}
+
+/**/
+void
+deletejob(Job jn)
+{
+ struct process *pn, *nx;
+
+ pn = jn->procs;
+ jn->procs = NULL;
+ for (; pn; pn = nx) {
+ nx = pn->next;
+ zfree(pn, sizeof(struct process));
+ }
+ zsfree(jn->pwd);
+
+ deletefilelist(jn->filelist);
+
+ if (jn->ty)
+ zfree(jn->ty, sizeof(struct ttyinfo));
+
+ *jn = zero;
+}
+
+/* add a process to the current job */
+
+/**/
+void
+addproc(pid_t pid, char *text)
+{
+ Process pn;
+ struct timezone dummy_tz;
+
+ pn = (Process) zcalloc(sizeof *pn);
+ pn->pid = pid;
+ if (text)
+ strcpy(pn->text, text);
+ else
+ *pn->text = '\0';
+ gettimeofday(&pn->bgtime, &dummy_tz);
+ pn->status = SP_RUNNING;
+ pn->next = NULL;
+
+ /* if this is the first process we are adding to *
+ * the job, then it's the group leader. */
+ if (!jobtab[thisjob].gleader)
+ jobtab[thisjob].gleader = pid;
+
+ /* attach this process to end of process list of current job */
+ if (jobtab[thisjob].procs) {
+ Process n;
+
+ for (n = jobtab[thisjob].procs; n->next; n = n->next);
+ pn->next = NULL;
+ n->next = pn;
+ } else {
+ /* first process for this job */
+ jobtab[thisjob].procs = pn;
+ }
+ /* If the first process in the job finished before any others were *
+ * added, maybe STAT_DONE got set incorrectly. This can happen if *
+ * a $(...) was waited for and the last existing job in the *
+ * pipeline was already finished. We need to be very careful that *
+ * there was no call to printjob() between then and now, else *
+ * the job will already have been deleted from the table. */
+ jobtab[thisjob].stat &= ~STAT_DONE;
+}
+
+/* Check if we have files to delete. We need to check this to see *
+ * if it's all right to exec a command without forking in the last *
+ * component of subshells or after the `-c' option. */
+
+/**/
+int
+havefiles(void)
+{
+ int i;
+
+ for (i = 1; i < MAXJOB; i++)
+ if (jobtab[i].stat && jobtab[i].filelist)
+ return 1;
+ return 0;
+
+}
+
+/* wait for a particular process */
+
+/**/
+void
+waitforpid(pid_t pid)
+{
+ int first = 1;
+
+ /* child_block() around this loop in case #ifndef WNOHANG */
+ child_block(); /* unblocked in child_suspend() */
+ while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) {
+ if (first)
+ first = 0;
+ else
+ kill(pid, SIGCONT);
+
+ child_suspend(SIGINT);
+ child_block();
+ }
+ child_unblock();
+}
+
+/* wait for a job to finish */
+
+/**/
+static void
+waitjob(int job, int sig)
+{
+ Job jn = jobtab + job;
+
+ child_block(); /* unblocked during child_suspend() */
+ if (jn->procs) { /* if any forks were done */
+ jn->stat |= STAT_LOCKED;
+ if (jn->stat & STAT_CHANGED)
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ while (!errflag && jn->stat &&
+ !(jn->stat & STAT_DONE) &&
+ !(interact && (jn->stat & STAT_STOPPED))) {
+ child_suspend(sig);
+ /* Commenting this out makes ^C-ing a job started by a function
+ stop the whole function again. But I guess it will stop
+ something else from working properly, we have to find out
+ what this might be. --oberon
+
+ errflag = 0; */
+ if (jn->stat & STAT_SUPERJOB) {
+ Job sj = jobtab + jn->other;
+ if (sj->stat & STAT_DONE) {
+ struct process *p;
+
+ for (p = sj->procs; p; p = p->next)
+ if (WIFSIGNALED(p->status)) {
+ killpg(jn->gleader, WTERMSIG(p->status));
+ kill(sj->other, SIGCONT);
+ kill(sj->other, WTERMSIG(p->status));
+ break;
+ }
+ if (!p) {
+ jn->stat &= ~STAT_SUPERJOB;
+ kill(sj->other, SIGCONT);
+ deletejob(sj);
+ }
+ curjob = jn - jobtab;
+ }
+ else if (sj->stat & STAT_STOPPED) {
+ struct process *p;
+
+ jn->stat |= STAT_STOPPED;
+ for (p = jn->procs; p; p = p->next)
+ p->status = sj->procs->status;
+ curjob = jn - jobtab;
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ break;
+ }
+ }
+ child_block();
+ }
+ } else
+ deletejob(jn);
+ child_unblock();
+}
+
+/* wait for running job to finish */
+
+/**/
+void
+waitjobs(void)
+{
+ waitjob(thisjob, 0);
+ thisjob = -1;
+}
+
+/* clear job table when entering subshells */
+
+/**/
+void
+clearjobtab(void)
+{
+ int i;
+
+ for (i = 1; i < MAXJOB; i++) {
+ if (jobtab[i].pwd)
+ zsfree(jobtab[i].pwd);
+ if (jobtab[i].ty)
+ zfree(jobtab[i].ty, sizeof(struct ttyinfo));
+ }
+
+ memset(jobtab, 0, sizeof(jobtab)); /* zero out table */
+}
+
+/* Get a free entry in the job table and initialize it. */
+
+/**/
+int
+initjob(void)
+{
+ int i;
+
+ for (i = 1; i < MAXJOB; i++)
+ if (!jobtab[i].stat) {
+ jobtab[i].stat = STAT_INUSE;
+ jobtab[i].pwd = ztrdup(pwd);
+ jobtab[i].gleader = 0;
+ return i;
+ }
+
+ zerr("job table full or recursion limit exceeded", NULL, 0);
+ return -1;
+}
+
+/* print pids for & */
+
+/**/
+void
+spawnjob(void)
+{
+ Process pn;
+
+ /* if we are not in a subshell */
+ if (!subsh) {
+ if (curjob == -1 || !(jobtab[curjob].stat & STAT_STOPPED)) {
+ curjob = thisjob;
+ setprevjob();
+ } else if (prevjob == -1 || !(jobtab[prevjob].stat & STAT_STOPPED))
+ prevjob = thisjob;
+ if (interact && jobbing && jobtab[thisjob].procs) {
+ fprintf(stderr, "[%d]", thisjob);
+ for (pn = jobtab[thisjob].procs; pn; pn = pn->next)
+ fprintf(stderr, " %ld", (long) pn->pid);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ }
+ }
+ if (!jobtab[thisjob].procs)
+ deletejob(jobtab + thisjob);
+ else
+ jobtab[thisjob].stat |= STAT_LOCKED;
+ thisjob = -1;
+}
+
+/**/
+void
+shelltime(void)
+{
+ struct timeinfo ti;
+ struct timezone dummy_tz;
+ struct tms buf;
+
+ times(&buf);
+ ti.ut = buf.tms_utime;
+ ti.st = buf.tms_stime;
+ gettimeofday(&now, &dummy_tz);
+ printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell");
+ ti.ut = buf.tms_cutime;
+ ti.st = buf.tms_cstime;
+ printtime(dtime(&dtimeval, &shtimer, &now), &ti, "children");
+}
+
+/* see if jobs need printing */
+
+/**/
+void
+scanjobs(void)
+{
+ int i;
+
+ for (i = 1; i < MAXJOB; i++)
+ if (jobtab[i].stat & STAT_CHANGED)
+ printjob(jobtab + i, 0, 1);
+}
+
+/**** job control builtins ****/
+
+/* This simple function indicates whether or not s may represent *
+ * a number. It returns true iff s consists purely of digits and *
+ * minuses. Note that minus may appear more than once, and the empty *
+ * string will produce a `true' response. */
+
+/**/
+static int
+isanum(char *s)
+{
+ while (*s == '-' || idigit(*s))
+ s++;
+ return *s == '\0';
+}
+
+/* Make sure we have a suitable current and previous job set. */
+
+/**/
+static void
+setcurjob(void)
+{
+ if (curjob == thisjob ||
+ (curjob != -1 && !(jobtab[curjob].stat & STAT_INUSE))) {
+ curjob = prevjob;
+ setprevjob();
+ if (curjob == thisjob ||
+ (curjob != -1 && !((jobtab[curjob].stat & STAT_INUSE) &&
+ curjob != thisjob))) {
+ curjob = prevjob;
+ setprevjob();
+ }
+ }
+}
+
+/* Convert a job specifier ("%%", "%1", "%foo", "%?bar?", etc.) *
+ * to a job number. */
+
+/**/
+static int
+getjob(char *s, char *prog)
+{
+ int jobnum, returnval;
+
+ /* if there is no %, treat as a name */
+ if (*s != '%')
+ goto jump;
+ s++;
+ /* "%%", "%+" and "%" all represent the current job */
+ if (*s == '%' || *s == '+' || !*s) {
+ if (curjob == -1) {
+ zwarnnam(prog, "no current job", NULL, 0);
+ returnval = -1;
+ goto done;
+ }
+ returnval = curjob;
+ goto done;
+ }
+ /* "%-" represents the previous job */
+ if (*s == '-') {
+ if (prevjob == -1) {
+ zwarnnam(prog, "no previous job", NULL, 0);
+ returnval = -1;
+ goto done;
+ }
+ returnval = prevjob;
+ goto done;
+ }
+ /* a digit here means we have a job number */
+ if (idigit(*s)) {
+ jobnum = atoi(s);
+ if (jobnum && jobnum < MAXJOB && jobtab[jobnum].stat &&
+ !(jobtab[jobnum].stat & STAT_SUBJOB) && jobnum != thisjob) {
+ returnval = jobnum;
+ goto done;
+ }
+ zwarnnam(prog, "%%%s: no such job", s, 0);
+ returnval = -1;
+ goto done;
+ }
+ /* "%?" introduces a search string */
+ if (*s == '?') {
+ struct process *pn;
+
+ for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
+ if (jobtab[jobnum].stat && !(jobtab[jobnum].stat & STAT_SUBJOB) &&
+ jobnum != thisjob)
+ for (pn = jobtab[jobnum].procs; pn; pn = pn->next)
+ if (strstr(pn->text, s + 1)) {
+ returnval = jobnum;
+ goto done;
+ }
+ zwarnnam(prog, "job not found: %s", s, 0);
+ returnval = -1;
+ goto done;
+ }
+ jump:
+ /* anything else is a job name, specified as a string that begins the
+ job's command */
+ if ((jobnum = findjobnam(s)) != -1) {
+ returnval = jobnum;
+ goto done;
+ }
+ /* if we get here, it is because none of the above succeeded and went
+ to done */
+ zwarnnam(prog, "job not found: %s", s, 0);
+ returnval = -1;
+ done:
+ return returnval;
+}
+
+/* For jobs -Z (which modifies the shell's name as seen in ps listings). *
+ * hackzero is the start of the safely writable space, and hackspace is *
+ * its length, excluding a final NUL terminator that will always be left. */
+
+static char *hackzero;
+static int hackspace;
+
+/* Initialise the jobs -Z system. The technique is borrowed from perl: *
+ * check through the argument and environment space, to see how many of *
+ * the strings are in contiguous space. This determines the value of *
+ * hackspace. */
+
+/**/
+void
+init_hackzero(char **argv, char **envp)
+{
+ char *p, *q;
+
+ hackzero = *argv;
+ p = strchr(hackzero, 0);
+ while(*++argv) {
+ q = *argv;
+ if(q != p+1)
+ goto done;
+ p = strchr(q, 0);
+ }
+ for(; *envp; envp++) {
+ q = *envp;
+ if(q != p+1)
+ goto done;
+ p = strchr(q, 0);
+ }
+ done:
+ hackspace = p - hackzero;
+}
+
+/* bg, disown, fg, jobs, wait: most of the job control commands are *
+ * here. They all take the same type of argument. Exception: wait can *
+ * take a pid or a job specifier, whereas the others only work on jobs. */
+
+/**/
+int
+bin_fg(char *name, char **argv, char *ops, int func)
+{
+ int job, lng, firstjob = -1, retval = 0;
+
+ if (ops['Z']) {
+ int len;
+
+ if(isset(RESTRICTED)) {
+ zwarnnam(name, "-Z is restricted", NULL, 0);
+ return 1;
+ }
+ if(!argv[0] || argv[1]) {
+ zwarnnam(name, "-Z requires one argument", NULL, 0);
+ return 1;
+ }
+ unmetafy(*argv, &len);
+ if(len > hackspace)
+ len = hackspace;
+ memcpy(hackzero, *argv, len);
+ memset(hackzero + len, 0, hackspace - len);
+ return 0;
+ }
+
+ lng = (ops['l']) ? 1 : (ops['p']) ? 2 : 0;
+ if (ops['d'])
+ lng |= 4;
+
+ if ((func == BIN_FG || func == BIN_BG) && !jobbing) {
+ /* oops... maybe bg and fg should have been disabled? */
+ zwarnnam(name, "no job control in this shell.", NULL, 0);
+ return 1;
+ }
+
+ /* If necessary, update job table. */
+ if (unset(NOTIFY))
+ scanjobs();
+
+ setcurjob();
+
+ if (func == BIN_JOBS)
+ /* If you immediately type "exit" after "jobs", this *
+ * will prevent zexit from complaining about stopped jobs */
+ stopmsg = 2;
+ if (!*argv)
+ /* This block handles all of the default cases (no arguments). bg,
+ fg and disown act on the current job, and jobs and wait act on all the
+ jobs. */
+ if (func == BIN_FG || func == BIN_BG || func == BIN_DISOWN) {
+ /* W.r.t. the above comment, we'd better have a current job at this
+ point or else. */
+ if (curjob == -1 || (jobtab[curjob].stat & STAT_NOPRINT)) {
+ zwarnnam(name, "no current job", NULL, 0);
+ return 1;
+ }
+ firstjob = curjob;
+ } else if (func == BIN_JOBS) {
+ /* List jobs. */
+ for (job = 0; job != MAXJOB; job++)
+ if (job != thisjob && jobtab[job].stat) {
+ if ((!ops['r'] && !ops['s']) ||
+ (ops['r'] && ops['s']) ||
+ (ops['r'] && !(jobtab[job].stat & STAT_STOPPED)) ||
+ (ops['s'] && jobtab[job].stat & STAT_STOPPED))
+ printjob(job + jobtab, lng, 2);
+ }
+ return 0;
+ } else { /* Must be BIN_WAIT, so wait for all jobs */
+ for (job = 0; job != MAXJOB; job++)
+ if (job != thisjob && jobtab[job].stat)
+ waitjob(job, SIGINT);
+ return 0;
+ }
+
+ /* Defaults have been handled. We now have an argument or two, or three...
+ In the default case for bg, fg and disown, the argument will be provided by
+ the above routine. We now loop over the arguments. */
+ for (; (firstjob != -1) || *argv; (void)(*argv && argv++)) {
+ int stopped, ocj = thisjob;
+
+ if (func == BIN_WAIT && isanum(*argv)) {
+ /* wait can take a pid; the others can't. */
+ waitforpid((long)atoi(*argv));
+ retval = lastval2;
+ thisjob = ocj;
+ continue;
+ }
+ /* The only type of argument allowed now is a job spec. Check it. */
+ job = (*argv) ? getjob(*argv, name) : firstjob;
+ firstjob = -1;
+ if (job == -1) {
+ retval = 1;
+ break;
+ }
+ if (!(jobtab[job].stat & STAT_INUSE) ||
+ (jobtab[job].stat & STAT_NOPRINT)) {
+ zwarnnam(name, "no such job: %d", 0, job);
+ return 1;
+ }
+ /* We have a job number. Now decide what to do with it. */
+ switch (func) {
+ case BIN_FG:
+ case BIN_BG:
+ case BIN_WAIT:
+ if (func == BIN_BG)
+ jobtab[job].stat |= STAT_NOSTTY;
+ if ((stopped = (jobtab[job].stat & STAT_STOPPED)))
+ makerunning(jobtab + job);
+ else if (func == BIN_BG) {
+ /* Silly to bg a job already running. */
+ zwarnnam(name, "job already in background", NULL, 0);
+ thisjob = ocj;
+ return 1;
+ }
+ /* It's time to shuffle the jobs around! Reset the current job,
+ and pick a sensible secondary job. */
+ if (curjob == job) {
+ curjob = prevjob;
+ prevjob = (func == BIN_BG) ? -1 : job;
+ }
+ if (prevjob == job || prevjob == -1)
+ setprevjob();
+ if (curjob == -1) {
+ curjob = prevjob;
+ setprevjob();
+ }
+ if (func != BIN_WAIT)
+ /* for bg and fg -- show the job we are operating on */
+ printjob(jobtab + job, (stopped) ? -1 : 0, 1);
+ if (func != BIN_BG) { /* fg or wait */
+ if (strcmp(jobtab[job].pwd, pwd)) {
+ fprintf(shout, "(pwd : ");
+ fprintdir(jobtab[job].pwd, shout);
+ fprintf(shout, ")\n");
+ }
+ fflush(shout);
+ if (func != BIN_WAIT) { /* fg */
+ thisjob = job;
+ attachtty(jobtab[job].gleader);
+ }
+ }
+ if (stopped) {
+ if (func != BIN_BG && jobtab[job].ty)
+ settyinfo(jobtab[job].ty);
+ killjb(jobtab + job, SIGCONT);
+ }
+ if (func == BIN_WAIT)
+ waitjob(job, SIGINT);
+ if (func != BIN_BG) {
+ waitjobs();
+ retval = lastval2;
+ }
+ break;
+ case BIN_JOBS:
+ printjob(job + jobtab, lng, 2);
+ break;
+ case BIN_DISOWN:
+ deletejob(jobtab + job);
+ break;
+ }
+ thisjob = ocj;
+ }
+ return retval;
+}
+
+/* kill: send a signal to a process. The process(es) may be specified *
+ * by job specifier (see above) or pid. A signal, defaulting to *
+ * SIGTERM, may be specified by name or number, preceded by a dash. */
+
+/**/
+int
+bin_kill(char *nam, char **argv, char *ops, int func)
+{
+ int sig = SIGTERM;
+ int returnval = 0;
+
+ /* check for, and interpret, a signal specifier */
+ if (*argv && **argv == '-') {
+ if (idigit((*argv)[1]))
+ /* signal specified by number */
+ sig = atoi(*argv + 1);
+ else if ((*argv)[1] != '-' || (*argv)[2]) {
+ char *signame;
+
+ /* with argument "-l" display the list of signal names */
+ if ((*argv)[1] == 'l' && (*argv)[2] == '\0') {
+ if (argv[1]) {
+ while (*++argv) {
+ sig = zstrtol(*argv, &signame, 10);
+ if (signame == *argv) {
+ for (sig = 1; sig <= SIGCOUNT; sig++)
+ if (!cstrpcmp(sigs + sig, &signame))
+ break;
+ if (sig > SIGCOUNT) {
+ zwarnnam(nam, "unknown signal: SIG%s",
+ signame, 0);
+ returnval++;
+ } else
+ printf("%d\n", sig);
+ } else {
+ if (*signame) {
+ zwarnnam(nam, "unknown signal: SIG%s",
+ signame, 0);
+ returnval++;
+ } else {
+ if (WIFSIGNALED(sig))
+ sig = WTERMSIG(sig);
+ else if (WIFSTOPPED(sig))
+ sig = WSTOPSIG(sig);
+ if (1 <= sig && sig <= SIGCOUNT)
+ printf("%s\n", sigs[sig]);
+ else
+ printf("%d\n", sig);
+ }
+ }
+ }
+ return returnval;
+ }
+ printf("%s", sigs[1]);
+ for (sig = 2; sig <= SIGCOUNT; sig++)
+ printf(" %s", sigs[sig]);
+ putchar('\n');
+ return 0;
+ }
+ if ((*argv)[1] == 's' && (*argv)[2] == '\0')
+ signame = *++argv;
+ else
+ signame = *argv + 1;
+
+ /* check for signal matching specified name */
+ for (sig = 1; sig <= SIGCOUNT; sig++)
+ if (!cstrpcmp(sigs + sig, &signame))
+ break;
+ if (*signame == '0' && !signame[1])
+ sig = 0;
+ if (sig > SIGCOUNT) {
+ zwarnnam(nam, "unknown signal: SIG%s", signame, 0);
+ zwarnnam(nam, "type kill -l for a List of signals", NULL, 0);
+ return 1;
+ }
+ }
+ argv++;
+ }
+
+ setcurjob();
+
+ /* Remaining arguments specify processes. Loop over them, and send the
+ signal (number sig) to each process. */
+ for (; *argv; argv++) {
+ if (**argv == '%') {
+ /* job specifier introduced by '%' */
+ int p;
+
+ if ((p = getjob(*argv, nam)) == -1) {
+ returnval++;
+ continue;
+ }
+ if (killjb(jobtab + p, sig) == -1) {
+ zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+ returnval++;
+ continue;
+ }
+ /* automatically update the job table if sending a SIGCONT to a
+ job, and send the job a SIGCONT if sending it a non-stopping
+ signal. */
+ if (jobtab[p].stat & STAT_STOPPED) {
+ if (sig == SIGCONT)
+ jobtab[p].stat &= ~STAT_STOPPED;
+ if (sig != SIGKILL && sig != SIGCONT && sig != SIGTSTP
+ && sig != SIGTTOU && sig != SIGTTIN && sig != SIGSTOP)
+ killjb(jobtab + p, SIGCONT);
+ }
+ } else if (!isanum(*argv)) {
+ zwarnnam("kill", "illegal pid: %s", *argv, 0);
+ returnval++;
+ } else if (kill(atoi(*argv), sig) == -1) {
+ zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+ returnval++;
+ }
+ }
+ return returnval < 126 ? returnval : 1;
+}
+
+/* Suspend this shell */
+
+/**/
+int
+bin_suspend(char *name, char **argv, char *ops, int func)
+{
+ /* won't suspend a login shell, unless forced */
+ if (islogin && !ops['f']) {
+ zwarnnam(name, "can't suspend login shell", NULL, 0);
+ return 1;
+ }
+ if (jobbing) {
+ /* stop ignoring signals */
+ signal_default(SIGTTIN);
+ signal_default(SIGTSTP);
+ signal_default(SIGTTOU);
+ }
+ /* suspend ourselves with a SIGTSTP */
+ kill(0, SIGTSTP);
+ if (jobbing) {
+ /* stay suspended */
+ while (gettygrp() != mypgrp) {
+ sleep(1);
+ if (gettygrp() != mypgrp)
+ kill(0, SIGTTIN);
+ }
+ /* restore signal handling */
+ signal_ignore(SIGTTOU);
+ signal_ignore(SIGTSTP);
+ signal_ignore(SIGTTIN);
+ }
+ return 0;
+}
+
+/* find a job named s */
+
+/**/
+int
+findjobnam(char *s)
+{
+ int jobnum;
+
+ for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
+ if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) &&
+ jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob &&
+ jobtab[jobnum].procs->text && strpfx(s, jobtab[jobnum].procs->text))
+ return jobnum;
+ return -1;
+}
diff --git a/Src/lex.c b/Src/lex.c
new file mode 100644
index 000000000..6f4f2dd20
--- /dev/null
+++ b/Src/lex.c
@@ -0,0 +1,1489 @@
+/*
+ * lex.c - lexical analysis
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "lex.pro"
+
+/* tokens */
+
+/**/
+char ztokens[] = "#$^*()$=|{}[]`<>?~`,'\"\\";
+
+/* parts of the current token */
+
+/**/
+char *yytext, *tokstr;
+/**/
+int tok, tokfd;
+
+/* lexical analyzer error flag */
+
+/**/
+int lexstop;
+
+/* if != 0, this is the first line of the command */
+
+/**/
+int isfirstln;
+
+/* if != 0, this is the first char of the command (not including white space) */
+
+/**/
+int isfirstch;
+
+/* flag that an alias should be expanded after expansion ending in space */
+
+/**/
+int inalmore;
+
+/* don't do spelling correction */
+
+/**/
+int nocorrect;
+
+/* the line buffer */
+
+/**/
+unsigned char *line;
+
+/* cursor position and line length */
+
+/**/
+int cs, ll;
+
+/* inwhat says what exactly we are in *
+ * (its value is one of the IN_* things). */
+
+/**/
+int inwhat;
+
+/* 1 if x added to complete in a blank between words */
+
+/**/
+int addedx;
+
+/* 1 if aliases should not be expanded */
+
+/**/
+int noaliases;
+
+/* we are parsing a line sent to use by the editor */
+
+/**/
+int zleparse;
+
+/**/
+int wordbeg;
+
+/**/
+int parbegin;
+
+/**/
+int parend;
+
+/* text of puctuation tokens */
+
+static char *tokstrings[WHILE + 1] = {
+ NULL, /* NULLTOK 0 */
+ ";", /* SEPER */
+ "\\n", /* NEWLIN */
+ ";", /* SEMI */
+ ";;", /* DSEMI */
+ "&", /* AMPER 5 */
+ "(", /* INPAR */
+ ")", /* OUTPAR */
+ "||", /* DBAR */
+ "&&", /* DAMPER */
+ ")", /* OUTANG 10 */
+ ">|", /* OUTANGBANG */
+ ">>", /* DOUTANG */
+ ">>|", /* DOUTANGBANG */
+ "<", /* INANG */
+ "<>", /* INOUTANG 15 */
+ "<<", /* DINANG */
+ "<<-", /* DINANGDASH */
+ "<&", /* INANGAMP */
+ ">&", /* OUTANGAMP */
+ "&>", /* AMPOUTANG 20 */
+ "&>|", /* OUTANGAMPBANG */
+ ">>&", /* DOUTANGAMP */
+ ">>&|", /* DOUTANGAMPBANG */
+ "<<<", /* TRINANG */
+ "|", /* BAR 25 */
+ "|&", /* BARAMP */
+ "()", /* INOUTPAR */
+ "((", /* DINPAR */
+ "))", /* DOUTPAR */
+ "&|", /* AMPERBANG 30 */
+ ";&", /* SEMIAMP */
+};
+
+/* lexical state */
+
+static int dbparens;
+static int len = 0, bsiz = 256;
+static char *bptr;
+
+struct lexstack {
+ struct lexstack *next;
+
+ int incmdpos;
+ int incond;
+ int incasepat;
+ int dbparens;
+ int isfirstln;
+ int isfirstch;
+ int histactive;
+ int histdone;
+ int spaceflag;
+ int stophist;
+ int hlinesz;
+ char *hline;
+ char *hptr;
+ int tok;
+ int isnewlin;
+ char *tokstr;
+ char *yytext;
+ char *bptr;
+ int bsiz;
+ short *chwords;
+ int chwordlen;
+ int chwordpos;
+ int hwgetword;
+ int lexstop;
+ struct heredocs *hdocs;
+
+ unsigned char *cstack;
+ int csp;
+};
+
+static struct lexstack *lstack = NULL;
+
+/* save the lexical state */
+
+/* is this a hack or what? */
+
+/**/
+void
+lexsave(void)
+{
+ struct lexstack *ls;
+
+ ls = (struct lexstack *)malloc(sizeof(struct lexstack));
+
+ ls->incmdpos = incmdpos;
+ ls->incond = incond;
+ ls->incasepat = incasepat;
+ ls->dbparens = dbparens;
+ ls->isfirstln = isfirstln;
+ ls->isfirstch = isfirstch;
+ ls->histactive = histactive;
+ ls->histdone = histdone;
+ ls->spaceflag = spaceflag;
+ ls->stophist = stophist;
+ ls->hline = chline;
+ ls->hptr = hptr;
+ ls->hlinesz = hlinesz;
+ ls->cstack = cmdstack;
+ ls->csp = cmdsp;
+ cmdstack = (unsigned char *)zalloc(256);
+ ls->tok = tok;
+ ls->isnewlin = isnewlin;
+ ls->tokstr = tokstr;
+ ls->yytext = yytext;
+ ls->bptr = bptr;
+ ls->bsiz = bsiz;
+ ls->chwords = chwords;
+ ls->chwordlen = chwordlen;
+ ls->chwordpos = chwordpos;
+ ls->hwgetword = hwgetword;
+ ls->lexstop = lexstop;
+ ls->hdocs = hdocs;
+ cmdsp = 0;
+ inredir = 0;
+ hdocs = NULL;
+
+ ls->next = lstack;
+ lstack = ls;
+}
+
+/* restore lexical state */
+
+/**/
+void
+lexrestore(void)
+{
+ struct lexstack *ln;
+
+ DPUTS(!lstack, "BUG: lexrestore() without lexsave()");
+ incmdpos = lstack->incmdpos;
+ incond = lstack->incond;
+ incasepat = lstack->incasepat;
+ dbparens = lstack->dbparens;
+ isfirstln = lstack->isfirstln;
+ isfirstch = lstack->isfirstch;
+ histactive = lstack->histactive;
+ histdone = lstack->histdone;
+ spaceflag = lstack->spaceflag;
+ stophist = lstack->stophist;
+ chline = lstack->hline;
+ hptr = lstack->hptr;
+ if (cmdstack)
+ free(cmdstack);
+ cmdstack = lstack->cstack;
+ cmdsp = lstack->csp;
+ tok = lstack->tok;
+ isnewlin = lstack->isnewlin;
+ tokstr = lstack->tokstr;
+ yytext = lstack->yytext;
+ bptr = lstack->bptr;
+ bsiz = lstack->bsiz;
+ chwords = lstack->chwords;
+ chwordlen = lstack->chwordlen;
+ chwordpos = lstack->chwordpos;
+ hwgetword = lstack->hwgetword;
+ lexstop = lstack->lexstop;
+ hdocs = lstack->hdocs;
+ hlinesz = lstack->hlinesz;
+ errflag = 0;
+
+ ln = lstack->next;
+ free(lstack);
+ lstack = ln;
+}
+
+/**/
+void
+yylex(void)
+{
+ if (tok == LEXERR)
+ return;
+ do
+ tok = gettok();
+ while (tok != ENDINPUT && exalias());
+ if (tok == NEWLIN || tok == ENDINPUT) {
+ while (hdocs) {
+ struct heredocs *next = hdocs->next;
+
+ hwbegin(0);
+ cmdpush(hdocs->rd->type == HEREDOC ? CS_HEREDOC : CS_HEREDOCD);
+ STOPHIST
+ hdocs->rd->name = gethere(hdocs->rd->name, hdocs->rd->type);
+ ALLOWHIST
+ cmdpop();
+ hwend();
+ hdocs->rd->type = HERESTR;
+ zfree(hdocs, sizeof(struct heredocs));
+ hdocs = next;
+ }
+ }
+ if (tok != NEWLIN)
+ isnewlin = 0;
+ else
+ isnewlin = (inbufct) ? -1 : 1;
+ if (tok == SEMI || tok == NEWLIN)
+ tok = SEPER;
+}
+
+/**/
+void
+ctxtlex(void)
+{
+ static int oldpos;
+
+ yylex();
+ switch (tok) {
+ case SEPER:
+ case NEWLIN:
+ case SEMI:
+ case DSEMI:
+ case SEMIAMP:
+ case AMPER:
+ case AMPERBANG:
+ case INPAR:
+ case INBRACE:
+ case DBAR:
+ case DAMPER:
+ case BAR:
+ case BARAMP:
+ case INOUTPAR:
+ case DO:
+ case THEN:
+ case ELIF:
+ case ELSE:
+ case DOUTBRACK:
+ incmdpos = 1;
+ break;
+ case STRING:
+ /* case ENVSTRING: */
+ case ENVARRAY:
+ case OUTPAR:
+ case CASE:
+ case DINBRACK:
+ incmdpos = 0;
+ break;
+ }
+ if (tok != DINPAR)
+ infor = tok == FOR ? 2 : 0;
+ if (IS_REDIROP(tok) || tok == FOR || tok == FOREACH || tok == SELECT) {
+ inredir = 1;
+ oldpos = incmdpos;
+ incmdpos = 0;
+ } else if (inredir) {
+ incmdpos = oldpos;
+ inredir = 0;
+ }
+}
+
+#define LX1_BKSLASH 0
+#define LX1_COMMENT 1
+#define LX1_NEWLIN 2
+#define LX1_SEMI 3
+#define LX1_AMPER 5
+#define LX1_BAR 6
+#define LX1_INPAR 7
+#define LX1_OUTPAR 8
+#define LX1_INANG 13
+#define LX1_OUTANG 14
+#define LX1_OTHER 15
+
+#define LX2_BREAK 0
+#define LX2_OUTPAR 1
+#define LX2_BAR 2
+#define LX2_STRING 3
+#define LX2_INBRACK 4
+#define LX2_OUTBRACK 5
+#define LX2_TILDE 6
+#define LX2_INPAR 7
+#define LX2_INBRACE 8
+#define LX2_OUTBRACE 9
+#define LX2_OUTANG 10
+#define LX2_INANG 11
+#define LX2_EQUALS 12
+#define LX2_BKSLASH 13
+#define LX2_QUOTE 14
+#define LX2_DQUOTE 15
+#define LX2_BQUOTE 16
+#define LX2_COMMA 17
+#define LX2_OTHER 18
+#define LX2_META 19
+
+static unsigned char lexact1[256], lexact2[256], lextok2[256];
+
+/**/
+void
+initlextabs(void)
+{
+ int t0;
+ static char *lx1 = "\\q\n;!&|(){}[]<>";
+ static char *lx2 = ";)|$[]~({}><=\\\'\"`,";
+
+ for (t0 = 0; t0 != 256; t0++) {
+ lexact1[t0] = LX1_OTHER;
+ lexact2[t0] = LX2_OTHER;
+ lextok2[t0] = t0;
+ }
+ for (t0 = 0; lx1[t0]; t0++)
+ lexact1[(int)lx1[t0]] = t0;
+ for (t0 = 0; lx2[t0]; t0++)
+ lexact2[(int)lx2[t0]] = t0;
+ lexact2['&'] = LX2_BREAK;
+ lexact2[STOUC(Meta)] = LX2_META;
+ lextok2['*'] = Star;
+ lextok2['?'] = Quest;
+ lextok2['{'] = Inbrace;
+ lextok2['['] = Inbrack;
+ lextok2['$'] = String;
+ lextok2['~'] = Tilde;
+ lextok2['#'] = Pound;
+ lextok2['^'] = Hat;
+}
+
+/* initialize lexical state */
+
+/**/
+void
+lexinit(void)
+{
+ incond = incasepat = nocorrect =
+ infor = dbparens = lexstop = 0;
+ incmdpos = 1;
+ tok = ENDINPUT;
+}
+
+/* add a char to the string buffer */
+
+/**/
+void
+add(int c)
+{
+ *bptr++ = c;
+ if (bsiz == ++len) {
+ int newbsiz;
+
+ newbsiz = bsiz * 8;
+ while (newbsiz < inbufct)
+ newbsiz *= 2;
+ bptr = len + (tokstr = (char *)hrealloc(tokstr, bsiz, newbsiz));
+ bsiz = newbsiz;
+ }
+}
+
+#define SETPARBEGIN {if (zleparse && !(inbufflags & INP_ALIAS) && cs >= ll+1-inbufct) parbegin = inbufct;}
+#define SETPAREND {\
+ if (zleparse && !(inbufflags & INP_ALIAS) && parbegin != -1 && parend == -1)\
+ if (cs >= ll + 1 - inbufct)\
+ parbegin = -1;\
+ else\
+ parend = inbufct;}
+
+static int
+cmd_or_math(int cs_type)
+{
+ int oldlen = len;
+ int c;
+
+ cmdpush(cs_type);
+ c = dquote_parse(')', 0);
+ cmdpop();
+ *bptr = '\0';
+ if (!c) {
+ c = hgetc();
+ if (c == ')')
+ return 1;
+ hungetc(c);
+ lexstop = 0;
+ c = ')';
+ }
+ hungetc(c);
+ lexstop = 0;
+ while (len > oldlen) {
+ len--;
+ hungetc(itok(*--bptr) ? ztokens[*bptr - Pound] : *bptr);
+ }
+ hungetc('(');
+ return 0;
+}
+
+static int
+cmd_or_math_sub(void)
+{
+ int c = hgetc();
+
+ if (c == '(') {
+ add(Inpar);
+ add('(');
+ if (cmd_or_math(CS_MATHSUBST)) {
+ add(')');
+ return 0;
+ }
+ bptr -= 2;
+ len -= 2;
+ } else {
+ hungetc(c);
+ lexstop = 0;
+ }
+ return skipcomm();
+}
+
+/**/
+int
+gettok(void)
+{
+ int c, d;
+ int peekfd = -1, peek;
+
+ MUSTUSEHEAP("gettok");
+ beginning:
+ tokstr = NULL;
+ while (iblank(c = hgetc()) && !lexstop);
+ if (lexstop)
+ return (errflag) ? LEXERR : ENDINPUT;
+ isfirstln = 0;
+ wordbeg = inbufct - (qbang && c == bangchar);
+ hwbegin(-1-(qbang && c == bangchar));
+ /* word includes the last character read and possibly \ before ! */
+ if (dbparens) {
+ len = 0;
+ bptr = tokstr = (char *)ncalloc(bsiz = 256);
+ hungetc(c);
+ cmdpush(CS_MATH);
+ c = dquote_parse(infor ? ';' : ')', 0);
+ cmdpop();
+ *bptr = '\0';
+ if (!c && infor) {
+ infor--;
+ return DINPAR;
+ }
+ if (c || (c = hgetc()) != ')') {
+ hungetc(c);
+ return LEXERR;
+ }
+ dbparens = 0;
+ return DOUTPAR;
+ } else if (idigit(c)) { /* handle 1< foo */
+ d = hgetc();
+ if (d == '>' || d == '<') {
+ peekfd = c - '0';
+ c = d;
+ } else {
+ hungetc(d);
+ lexstop = 0;
+ }
+ }
+
+ /* chars in initial position in word */
+
+ if (c == hashchar &&
+ (isset(INTERACTIVECOMMENTS) ||
+ (!zleparse && !expanding &&
+ (!interact || unset(SHINSTDIN) || strin)))) {
+ /* History is handled here to prevent extra *
+ * newlines being inserted into the history. */
+
+ while ((c = ingetc()) != '\n' && !lexstop) {
+ hwaddc(c);
+ addtoline(c);
+ }
+
+ if (errflag)
+ peek = LEXERR;
+ else {
+ hwend();
+ hwbegin(0);
+ hwaddc('\n');
+ addtoline('\n');
+ peek = NEWLIN;
+ }
+ return peek;
+ }
+ switch (lexact1[STOUC(c)]) {
+ case LX1_BKSLASH:
+ d = hgetc();
+ if (d == '\n')
+ goto beginning;
+ hungetc(d);
+ lexstop = 0;
+ break;
+ case LX1_NEWLIN:
+ return NEWLIN;
+ case LX1_SEMI:
+ d = hgetc();
+ if(d == ';')
+ return DSEMI;
+ else if(d == '&')
+ return SEMIAMP;
+ hungetc(d);
+ lexstop = 0;
+ return SEMI;
+ case LX1_AMPER:
+ d = hgetc();
+ if (d == '&')
+ return DAMPER;
+ else if (d == '!' || d == '|')
+ return AMPERBANG;
+ else if (d == '>') {
+ d = hgetc();
+ if (d == '!' || d == '|')
+ return OUTANGAMPBANG;
+ else if (d == '>') {
+ d = hgetc();
+ if (d == '!' || d == '|')
+ return DOUTANGAMPBANG;
+ hungetc(d);
+ lexstop = 0;
+ return DOUTANGAMP;
+ }
+ hungetc(d);
+ lexstop = 0;
+ return AMPOUTANG;
+ }
+ hungetc(d);
+ lexstop = 0;
+ return AMPER;
+ case LX1_BAR:
+ d = hgetc();
+ if (d == '|')
+ return DBAR;
+ else if (d == '&')
+ return BARAMP;
+ hungetc(d);
+ lexstop = 0;
+ return BAR;
+ case LX1_INPAR:
+ d = hgetc();
+ if (d == '(') {
+ if (infor) {
+ dbparens = 1;
+ return DINPAR;
+ }
+ if (incmdpos) {
+ len = 0;
+ bptr = tokstr = (char *)ncalloc(bsiz = 256);
+ return cmd_or_math(CS_MATH) ? DINPAR : INPAR;
+ }
+ } else if (d == ')')
+ return INOUTPAR;
+ hungetc(d);
+ lexstop = 0;
+ if (!(incond == 1 || incmdpos))
+ break;
+ return INPAR;
+ case LX1_OUTPAR:
+ return OUTPAR;
+ case LX1_INANG:
+ d = hgetc();
+ if (!incmdpos && d == '(') {
+ hungetc(d);
+ lexstop = 0;
+ break;
+ }
+ if (d == '>')
+ peek = INOUTANG;
+ else if (idigit(d) || d == '-') {
+ int tbs = 256, n = 0, nc;
+ char *tbuf, *tbp, *ntb;
+
+ tbuf = tbp = (char *)zalloc(tbs);
+ hungetc(d);
+
+ while ((nc = hgetc()) && !lexstop) {
+ if (!idigit(nc) && nc != '-')
+ break;
+ *tbp++ = (char)nc;
+ if (++n == tbs) {
+ ntb = (char *)realloc(tbuf, tbs *= 2);
+ tbp += ntb - tbuf;
+ tbuf = ntb;
+ }
+ }
+ if (nc == '>' && !lexstop) {
+ hungetc(nc);
+ while (n--)
+ hungetc(*--tbp);
+ zfree(tbuf, tbs);
+ break;
+ }
+ if (nc && !lexstop)
+ hungetc(nc);
+ lexstop = 0;
+ while (n--)
+ hungetc(*--tbp);
+ zfree(tbuf, tbs);
+ peek = INANG;
+ } else if (d == '<') {
+ int e = hgetc();
+
+ if (e == '(') {
+ hungetc(e);
+ hungetc(d);
+ peek = INANG;
+ } else if (e == '<')
+ peek = TRINANG;
+ else if (e == '-')
+ peek = DINANGDASH;
+ else {
+ hungetc(e);
+ lexstop = 0;
+ peek = DINANG;
+ }
+ } else if (d == '&')
+ peek = INANGAMP;
+ else {
+ peek = INANG;
+ hungetc(d);
+ lexstop = 0;
+ }
+ tokfd = peekfd;
+ return peek;
+ case LX1_OUTANG:
+ d = hgetc();
+ if (d == '(') {
+ hungetc(d);
+ break;
+ } else if (d == '&') {
+ d = hgetc();
+ if (d == '!' || d == '|')
+ peek = OUTANGAMPBANG;
+ else {
+ hungetc(d);
+ lexstop = 0;
+ peek = OUTANGAMP;
+ }
+ } else if (d == '!' || d == '|')
+ peek = OUTANGBANG;
+ else if (d == '>') {
+ d = hgetc();
+ if (d == '&') {
+ d = hgetc();
+ if (d == '!' || d == '|')
+ peek = DOUTANGAMPBANG;
+ else {
+ hungetc(d);
+ lexstop = 0;
+ peek = DOUTANGAMP;
+ }
+ } else if (d == '!' || d == '|')
+ peek = DOUTANGBANG;
+ else if (d == '(') {
+ hungetc(d);
+ hungetc('>');
+ peek = OUTANG;
+ } else {
+ hungetc(d);
+ lexstop = 0;
+ peek = DOUTANG;
+ if (isset(HISTALLOWCLOBBER))
+ hwaddc('|');
+ }
+ } else {
+ hungetc(d);
+ lexstop = 0;
+ peek = OUTANG;
+ if (!incond && isset(HISTALLOWCLOBBER))
+ hwaddc('|');
+ }
+ tokfd = peekfd;
+ return peek;
+ }
+
+ /* we've started a string, now get the *
+ * rest of it, performing tokenization */
+ return gettokstr(c, 0);
+}
+
+/**/
+static int
+gettokstr(int c, int sub)
+{
+ int bct = 0, pct = 0, brct = 0;
+ int intpos = 1, in_brace_param = 0;
+ int peek, inquote;
+#ifdef DEBUG
+ int ocmdsp = cmdsp;
+#endif
+
+ peek = STRING;
+ if (!sub) {
+ len = 0;
+ bptr = tokstr = (char *)ncalloc(bsiz = 256);
+ }
+ for (;;) {
+ int act;
+ int e;
+
+ if (inblank(c) && !in_brace_param && !pct)
+ act = LX2_BREAK;
+ else {
+ act = lexact2[STOUC(c)];
+ c = lextok2[STOUC(c)];
+ }
+ switch (act) {
+ case LX2_BREAK:
+ if (!in_brace_param && !sub)
+ goto brk;
+ break;
+ case LX2_META:
+ c = hgetc();
+#ifdef DEBUG
+ if (lexstop) {
+ fputs("BUG: input terminated by Meta\n", stderr);
+ fflush(stderr);
+ goto brk;
+ }
+#endif
+ add(Meta);
+ break;
+ case LX2_OUTPAR:
+ if ((sub || in_brace_param) && isset(SHGLOB))
+ break;
+ if (!in_brace_param && !pct--)
+ if (sub) {
+ pct = 0;
+ break;
+ } else
+ goto brk;
+ c = Outpar;
+ break;
+ case LX2_BAR:
+ if (!pct && !in_brace_param)
+ if (sub)
+ break;
+ else
+ goto brk;
+ if (unset(SHGLOB) || (!sub && !in_brace_param))
+ c = Bar;
+ break;
+ case LX2_STRING:
+ e = hgetc();
+ if (e == '[') {
+ cmdpush(CS_MATHSUBST);
+ add(String);
+ add(Inbrack);
+ c = dquote_parse(']', sub);
+ cmdpop();
+ if (c) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outbrack;
+ } else if (e == '(') {
+ add(String);
+ c = cmd_or_math_sub();
+ if (c) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outpar;
+ } else {
+ if (e == '{') {
+ add(c);
+ c = Inbrace;
+ ++bct;
+ cmdpush(CS_BRACEPAR);
+ if (!in_brace_param)
+ in_brace_param = bct;
+ } else {
+ hungetc(e);
+ lexstop = 0;
+ }
+ }
+ break;
+ case LX2_INBRACK:
+ if (!in_brace_param)
+ brct++;
+ c = Inbrack;
+ break;
+ case LX2_OUTBRACK:
+ if (!in_brace_param)
+ brct--;
+ if (brct < 0)
+ brct = 0;
+ c = Outbrack;
+ break;
+ case LX2_INPAR:
+ if ((sub || in_brace_param) && isset(SHGLOB))
+ break;
+ if (!in_brace_param) {
+ if (!sub) {
+ e = hgetc();
+ hungetc(e);
+ lexstop = 0;
+ if (e == ')' ||
+ (incmdpos && !brct && peek != ENVSTRING))
+ goto brk;
+ }
+ pct++;
+ }
+ c = Inpar;
+ break;
+ case LX2_INBRACE:
+ if (isset(IGNOREBRACES) || sub)
+ c = '{';
+ else {
+ if (!len && incmdpos) {
+ add('{');
+ *bptr = '\0';
+ return STRING;
+ }
+ if (in_brace_param)
+ cmdpush(CS_BRACE);
+ bct++;
+ }
+ break;
+ case LX2_OUTBRACE:
+ if ((isset(IGNOREBRACES) || sub) && !in_brace_param)
+ break;
+ if (!bct)
+ break;
+ if (in_brace_param)
+ cmdpop();
+ if (bct-- == in_brace_param)
+ in_brace_param = 0;
+ c = Outbrace;
+ break;
+ case LX2_COMMA:
+ if (unset(IGNOREBRACES) && !sub && bct > in_brace_param)
+ c = Comma;
+ break;
+ case LX2_OUTANG:
+ if (!intpos)
+ if (in_brace_param || sub)
+ break;
+ else
+ goto brk;
+ e = hgetc();
+ if (e != '(') {
+ hungetc(e);
+ lexstop = 0;
+ goto brk;
+ }
+ add(Outang);
+ if (skipcomm()) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outpar;
+ break;
+ case LX2_INANG:
+ if (isset(SHGLOB) && sub)
+ break;
+ e = hgetc();
+ if (!(idigit(e) || e == '-' || (e == '(' && intpos))) {
+ hungetc(e);
+ lexstop = 0;
+ if (in_brace_param || sub)
+ break;
+ goto brk;
+ }
+ c = Inang;
+ if (e == '(') {
+ add(c);
+ if (skipcomm()) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outpar;
+ } else {
+ add(c);
+ c = e;
+ while (c != '>' && !lexstop)
+ add(c), c = hgetc();
+ c = Outang;
+ }
+ break;
+ case LX2_EQUALS:
+ if (intpos) {
+ e = hgetc();
+ if (e != '(') {
+ hungetc(e);
+ lexstop = 0;
+ c = Equals;
+ } else {
+ add(Equals);
+ if (skipcomm()) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outpar;
+ }
+ } else if (!sub && peek != ENVSTRING &&
+ incmdpos && !bct && !brct) {
+ char *t = tokstr;
+ if (idigit(*t))
+ while (++t < bptr && idigit(*t));
+ else {
+ while (iident(*t) && ++t < bptr);
+ if (t < bptr) {
+ *bptr = '\0';
+ skipparens(Inbrack, Outbrack, &t);
+ }
+ }
+ if (t == bptr) {
+ e = hgetc();
+ if (e == '(' && incmdpos) {
+ *bptr = '\0';
+ return ENVARRAY;
+ }
+ hungetc(e);
+ lexstop = 0;
+ peek = ENVSTRING;
+ intpos = 2;
+ } else
+ c = Equals;
+ } else
+ c = Equals;
+ break;
+ case LX2_BKSLASH:
+ c = hgetc();
+ if (c == '\n') {
+ c = hgetc();
+ if (!lexstop)
+ continue;
+ } else
+ add(Bnull);
+ if (lexstop)
+ goto brk;
+ break;
+ case LX2_QUOTE: {
+ int strquote = (len && bptr[-1] == String);
+
+ add(Snull);
+ cmdpush(CS_QUOTE);
+ for (;;) {
+ STOPHIST
+ while ((c = hgetc()) != '\'' && !lexstop) {
+ if (strquote && c == '\\') {
+ add(c);
+ c = hgetc();
+ if (lexstop)
+ break;
+ } else if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') {
+ if (bptr[-1] == '\\')
+ bptr--, len--;
+ else
+ break;
+ }
+ add(c);
+ }
+ ALLOWHIST
+ if (c != '\'') {
+ zerr("unmatched \'", NULL, 0);
+ peek = LEXERR;
+ cmdpop();
+ goto brk;
+ }
+ e = hgetc();
+ if (e != '\'' || unset(RCQUOTES))
+ break;
+ add(c);
+ }
+ cmdpop();
+ hungetc(e);
+ lexstop = 0;
+ c = Snull;
+ break;
+ }
+ case LX2_DQUOTE:
+ add(Dnull);
+ cmdpush(CS_DQUOTE);
+ c = dquote_parse('"', sub);
+ cmdpop();
+ if (c) {
+ zerr("unmatched \"", NULL, 0);
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Dnull;
+ break;
+ case LX2_BQUOTE:
+ add(Tick);
+ cmdpush(CS_BQUOTE);
+ SETPARBEGIN
+ inquote = 0;
+ while ((c = hgetc()) != '`' && !lexstop)
+ if (c == '\\') {
+ c = hgetc();
+ if (c != '\n') {
+ add(c == '`' || c == '\\' || c == '$' ? Bnull : '\\');
+ add(c);
+ }
+ else if (!sub && isset(CSHJUNKIEQUOTES))
+ add(c);
+ } else {
+ if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') {
+ break;
+ }
+ add(c);
+ if (c == '\'')
+ if ((inquote = !inquote))
+ STOPHIST
+ else
+ ALLOWHIST
+ }
+ if (inquote)
+ ALLOWHIST
+ cmdpop();
+ if (c != '`') {
+ zerr("unmatched `", NULL, 0);
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Tick;
+ SETPAREND
+ break;
+ }
+ add(c);
+ c = hgetc();
+ if (intpos)
+ intpos--;
+ if (lexstop)
+ break;
+ }
+ brk:
+ hungetc(c);
+ if (in_brace_param) {
+ while(bct-- >= in_brace_param)
+ cmdpop();
+ zerr("closing brace expected", NULL, 0);
+ } else if (unset(IGNOREBRACES) && !sub && len > 1 &&
+ peek == STRING && bptr[-1] == '}' && bptr[-2] != Bnull) {
+ /* hack to get {foo} command syntax work */
+ bptr--;
+ len--;
+ lexstop = 0;
+ hungetc('}');
+ }
+ *bptr = '\0';
+ DPUTS(cmdsp != ocmdsp, "BUG: gettok: cmdstack changed.");
+ return peek;
+}
+
+/**/
+static int
+dquote_parse(char endchar, int sub)
+{
+ int pct = 0, brct = 0, bct = 0, intick = 0, err = 0;
+ int c;
+ int math = endchar == ')' || endchar == ']';
+ int zlemath = math && cs > ll + addedx - inbufct;
+
+ while (((c = hgetc()) != endchar || bct ||
+ (math && ((pct > 0) || (brct > 0))) ||
+ intick) && !lexstop) {
+ cont:
+ switch (c) {
+ case '\\':
+ c = hgetc();
+ if (c != '\n') {
+ if (c == '$' || c == '\\' || (c == '}' && !intick && bct) ||
+ c == endchar || c == '`')
+ add(Bnull);
+ else {
+ /* lexstop is implicitely handled here */
+ add('\\');
+ goto cont;
+ }
+ } else if (sub || unset(CSHJUNKIEQUOTES) || endchar != '"')
+ continue;
+ break;
+ case '\n':
+ err = !sub && isset(CSHJUNKIEQUOTES) && endchar == '"';
+ break;
+ case '$':
+ if (intick)
+ break;
+ c = hgetc();
+ if (c == '(') {
+ add(Qstring);
+ err = cmd_or_math_sub();
+ c = Outpar;
+ } else if (c == '[') {
+ add(String);
+ add(Inbrack);
+ cmdpush(CS_MATHSUBST);
+ err = dquote_parse(']', sub);
+ cmdpop();
+ c = Outbrack;
+ } else if (c == '{') {
+ add(Qstring);
+ c = Inbrace;
+ cmdpush(CS_BRACEPAR);
+ bct++;
+ } else if (c == '$')
+ add(Qstring);
+ else {
+ hungetc(c);
+ lexstop = 0;
+ c = Qstring;
+ }
+ break;
+ case '}':
+ if (intick || !bct)
+ break;
+ c = Outbrace;
+ bct--;
+ cmdpop();
+ break;
+ case '`':
+ c = Qtick;
+ if (intick == 2)
+ ALLOWHIST
+ if ((intick = !intick)) {
+ SETPARBEGIN
+ cmdpush(CS_BQUOTE);
+ } else {
+ SETPAREND
+ cmdpop();
+ }
+ break;
+ case '\'':
+ if (!intick)
+ break;
+ if (intick == 1)
+ intick = 2, STOPHIST
+ else
+ intick = 1, ALLOWHIST
+ break;
+ case '(':
+ pct++;
+ break;
+ case ')':
+ err = (!pct-- && math);
+ break;
+ case '[':
+ brct++;
+ break;
+ case ']':
+ err = (!brct-- && math);
+ break;
+ case '"':
+ if (intick || (!endchar && !bct))
+ break;
+ if (bct) {
+ add(Dnull);
+ err = dquote_parse('"', sub);
+ c = Dnull;
+ } else
+ err = 1;
+ break;
+ }
+ if (err || lexstop)
+ break;
+ add(c);
+ }
+ if (intick == 2)
+ ALLOWHIST
+ if (intick)
+ cmdpop();
+ while (bct--)
+ cmdpop();
+ if (lexstop)
+ err = intick || endchar || err;
+ else if (err == 1)
+ err = c;
+ if (zlemath && cs <= ll + 1 - inbufct)
+ inwhat = IN_MATH;
+ return err;
+}
+
+/* Tokenize a string given in s. Parsing is done as in double *
+ * quotes. This is usually called before singsub(). */
+
+/**/
+int
+parsestr(char *s)
+{
+ int l = strlen(s), err;
+
+ HEAPALLOC {
+ lexsave();
+ untokenize(s);
+ inpush(dupstring(s), 0, NULL);
+ strinbeg();
+ stophist = 2;
+ len = 0;
+ bptr = tokstr = s;
+ bsiz = l + 1;
+ err = dquote_parse('\0', 1);
+ *bptr = '\0';
+ strinend();
+ inpop();
+ DPUTS(cmdsp, "BUG: parsestr: cmdstack not empty.");
+ lexrestore();
+ if (err) {
+ untokenize(s);
+ if (err > 32 && err < 127)
+ zerr("parse error near `%c'", NULL, err);
+ else
+ zerr("parse error", NULL, 0);
+ }
+ } LASTALLOC;
+ return err;
+}
+
+/* Tokenize a string given in s. Parsing is done as if s were a normal *
+ * command-line argument but it may contain separators. This is used *
+ * to parse the right-hand side of ${...%...} substitutions. */
+
+/**/
+int
+parse_subst_string(char *s)
+{
+ int c, l = strlen(s), err;
+
+ if (! *s)
+ return 0;
+ lexsave();
+ untokenize(s);
+ inpush(dupstring(s), 0, NULL);
+ strinbeg();
+ stophist = 2;
+ len = 0;
+ bptr = tokstr = s;
+ bsiz = l + 1;
+ c = hgetc();
+ c = gettokstr(c, 1);
+ err = errflag;
+ strinend();
+ inpop();
+ DPUTS(cmdsp, "BUG: parse_subst_string: cmdstack not empty.");
+ lexrestore();
+ errflag = err;
+ if (c == LEXERR) {
+ untokenize(s);
+ return 1;
+ }
+#ifdef DEBUG
+ if (c != STRING || len != l || errflag) {
+ fprintf(stderr, "Oops. Bug in parse_subst_string: %s\n",
+ len < l ? "len < l" : errflag ? "errflag" : "c != STRING");
+ fflush(stderr);
+ untokenize(s);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/* expand aliases and reserved words */
+
+/**/
+int
+exalias(void)
+{
+ Alias an;
+ Reswd rw;
+
+ hwend();
+ if (interact && isset(SHINSTDIN) && !strin && !incasepat &&
+ tok == STRING && !nocorrect && !(inbufflags & INP_ALIAS) &&
+ (isset(CORRECTALL) || (isset(CORRECT) && incmdpos)))
+ spckword(&tokstr, 1, incmdpos, 1);
+
+ if (!tokstr) {
+ yytext = tokstrings[tok];
+ if (yytext)
+ yytext = dupstring(yytext);
+ return 0;
+ }
+
+ if (has_token(tokstr)) {
+ char *p, *t;
+
+ yytext = p = ncalloc(strlen(tokstr) + 1);
+ for (t = tokstr; (*p++ = itok(*t) ? ztokens[*t++ - Pound] : *t++););
+ } else
+ yytext = tokstr;
+
+ if (zleparse && !(inbufflags & INP_ALIAS)) {
+ int zp = zleparse;
+
+ gotword();
+ if (zp == 1 && !zleparse) {
+ return 0;
+ }
+ }
+
+ if (tok == STRING) {
+ /* Check for an alias */
+ an = noaliases ? NULL : (Alias) aliastab->getnode(aliastab, yytext);
+ if (an && !an->inuse && ((an->flags & ALIAS_GLOBAL) || incmdpos ||
+ inalmore)) {
+ inpush(an->text, INP_ALIAS, an);
+ /* remove from history if it begins with space */
+ if (isset(HISTIGNORESPACE) && an->text[0] == ' ')
+ remhist();
+ lexstop = 0;
+ return 1;
+ }
+
+ /* Then check for a reserved word */
+ if ((incmdpos ||
+ (unset(IGNOREBRACES) && yytext[0] == '}' && !yytext[1])) &&
+ (rw = (Reswd) reswdtab->getnode(reswdtab, yytext))) {
+ tok = rw->token;
+ if (tok == DINBRACK)
+ incond = 1;
+ } else if (incond && !strcmp(yytext, "]]")) {
+ tok = DOUTBRACK;
+ incond = 0;
+ } else if (incond && yytext[0] == '!' && !yytext[1])
+ tok = BANG;
+ }
+ inalmore = 0;
+ return 0;
+}
+
+/* skip (...) */
+
+/**/
+static int
+skipcomm(void)
+{
+ int pct = 1, c;
+
+ cmdpush(CS_CMDSUBST);
+ SETPARBEGIN
+ c = Inpar;
+ do {
+ add(c);
+ c = hgetc();
+ if (itok(c) || lexstop)
+ break;
+ switch (c) {
+ case '(':
+ pct++;
+ break;
+ case ')':
+ pct--;
+ break;
+ case '\\':
+ add(c);
+ c = hgetc();
+ break;
+ case '\'': {
+ int strquote = bptr[-1] == '$';
+ add(c);
+ STOPHIST
+ while ((c = hgetc()) != '\'' && !lexstop) {
+ if (c == '\\' && strquote) {
+ add(c);
+ c = hgetc();
+ }
+ add(c);
+ }
+ ALLOWHIST
+ break;
+ }
+ case '\"':
+ add(c);
+ while ((c = hgetc()) != '\"' && !lexstop)
+ if (c == '\\') {
+ add(c);
+ add(hgetc());
+ } else
+ add(c);
+ break;
+ case '`':
+ add(c);
+ while ((c = hgetc()) != '`' && !lexstop)
+ if (c == '\\')
+ add(c), add(hgetc());
+ else
+ add(c);
+ break;
+ }
+ }
+ while (pct);
+ if (!lexstop)
+ SETPAREND
+ cmdpop();
+ return lexstop;
+}
diff --git a/Src/linklist.c b/Src/linklist.c
new file mode 100644
index 000000000..62a962595
--- /dev/null
+++ b/Src/linklist.c
@@ -0,0 +1,222 @@
+/*
+ * linklist.c - linked lists
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "linklist.pro"
+
+/* Get an empty linked list header */
+
+/**/
+LinkList
+newlinklist(void)
+{
+ LinkList list;
+
+ list = (LinkList) alloc(sizeof *list);
+ list->first = NULL;
+ list->last = (LinkNode) list;
+ return list;
+}
+
+/* Insert a node in a linked list after a given node */
+
+/**/
+LinkNode
+insertlinknode(LinkList list, LinkNode node, void *dat)
+{
+ LinkNode tmp, new;
+
+ tmp = node->next;
+ node->next = new = (LinkNode) alloc(sizeof *tmp);
+ new->last = node;
+ new->dat = dat;
+ new->next = tmp;
+ if (tmp)
+ tmp->last = new;
+ else
+ list->last = new;
+ return new;
+}
+
+/* Insert an already-existing node into a linked list after a given node */
+
+/**/
+LinkNode
+uinsertlinknode(LinkList list, LinkNode node, LinkNode new)
+{
+ LinkNode tmp = node->next;
+ node->next = new;
+ new->last = node;
+ new->next = tmp;
+ if (tmp)
+ tmp->last = new;
+ else
+ list->last = new;
+ return new;
+}
+
+/* Insert a list in another list */
+
+/**/
+void
+insertlinklist(LinkList l, LinkNode where, LinkList x)
+{
+ LinkNode nx;
+
+ nx = where->next;
+ if (!l->first)
+ return;
+ where->next = l->first;
+ l->last->next = nx;
+ l->first->last = where;
+ if (nx)
+ nx->last = l->last;
+ else
+ x->last = l->last;
+}
+
+/* Get top node in a linked list */
+
+/**/
+void *
+getlinknode(LinkList list)
+{
+ void *dat;
+ LinkNode node;
+
+ if (!(node = list->first))
+ return NULL;
+ dat = node->dat;
+ list->first = node->next;
+ if (node->next)
+ node->next->last = (LinkNode) list;
+ else
+ list->last = (LinkNode) list;
+ zfree(node, sizeof(struct linknode));
+ return dat;
+}
+
+/* Get top node in a linked list without freeing */
+
+/**/
+void *
+ugetnode(LinkList list)
+{
+ void *dat;
+ LinkNode node;
+
+ if (!(node = list->first))
+ return NULL;
+ dat = node->dat;
+ list->first = node->next;
+ if (node->next)
+ node->next->last = (LinkNode) list;
+ else
+ list->last = (LinkNode) list;
+ return dat;
+}
+
+/* Remove a node from a linked list */
+
+/**/
+void *
+remnode(LinkList list, LinkNode nd)
+{
+ void *dat;
+
+ nd->last->next = nd->next;
+ if (nd->next)
+ nd->next->last = nd->last;
+ else
+ list->last = nd->last;
+ dat = nd->dat;
+ zfree(nd, sizeof(struct linknode));
+
+ return dat;
+}
+
+/* Remove a node from a linked list without freeing */
+
+/**/
+void *
+uremnode(LinkList list, LinkNode nd)
+{
+ void *dat;
+
+ nd->last->next = nd->next;
+ if (nd->next)
+ nd->next->last = nd->last;
+ else
+ list->last = nd->last;
+ dat = nd->dat;
+ return dat;
+}
+
+/* Free a linked list */
+
+/**/
+void
+freelinklist(LinkList list, FreeFunc freefunc)
+{
+ LinkNode node, next;
+
+ for (node = list->first; node; node = next) {
+ next = node->next;
+ if (freefunc)
+ freefunc(node->dat);
+ zfree(node, sizeof(struct linknode));
+ }
+ zfree(list, sizeof(struct linklist));
+}
+
+/* Count the number of nodes in a linked list */
+
+/**/
+int
+countlinknodes(LinkList list)
+{
+ LinkNode nd;
+ int ct = 0;
+
+ for (nd = firstnode(list); nd; incnode(nd), ct++);
+ return ct;
+}
+
+/**/
+void
+rolllist(LinkList l, LinkNode nd)
+{
+ l->last->next = l->first;
+ l->first->last = l->last;
+ l->first = nd;
+ l->last = nd->last;
+ nd->last = (LinkNode) l;
+ l->last->next = 0;
+}
+
diff --git a/Src/loop.c b/Src/loop.c
new file mode 100644
index 000000000..5fbf2b841
--- /dev/null
+++ b/Src/loop.c
@@ -0,0 +1,421 @@
+/*
+ * loop.c - loop execution
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "loop.pro"
+
+/* # of nested loops we are in */
+
+/**/
+int loops;
+
+/* # of continue levels */
+
+/**/
+int contflag;
+
+/* # of break levels */
+
+/**/
+int breaks;
+
+/**/
+int
+execfor(Cmd cmd)
+{
+ List list;
+ Forcmd node;
+ char *str;
+ int val;
+ LinkList args;
+
+ node = cmd->u.forcmd;
+ args = cmd->args;
+ if (node->condition) {
+ str = node->name;
+ singsub(&str);
+ if (!errflag)
+ matheval(str);
+ if (errflag)
+ return lastval = errflag;
+ } else if (!node->inflag) {
+ char **x;
+
+ args = newlinklist();
+ for (x = pparams; *x; x++)
+ addlinknode(args, ztrdup(*x));
+ }
+ lastval = 0;
+ loops++;
+ pushheap();
+ for (;;) {
+ if (node->condition) {
+ str = dupstring(node->condition);
+ singsub(&str);
+ if (!errflag) {
+ while (iblank(*str))
+ str++;
+ if (*str)
+ val = matheval(str);
+ else
+ val = 1;
+ }
+ if (errflag) {
+ if (breaks)
+ breaks--;
+ lastval = 1;
+ break;
+ }
+ if (!val)
+ break;
+ } else {
+ str = (char *) ugetnode(args);
+ if (!str)
+ break;
+ setsparam(node->name, ztrdup(str));
+ }
+ list = (List) dupstruct(node->list);
+ execlist(list, 1, (cmd->flags & CFLAG_EXEC) && empty(args));
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (node->condition && !errflag) {
+ str = dupstring(node->advance);
+ singsub(&str);
+ if (!errflag)
+ matheval(str);
+ }
+ if (errflag) {
+ if (breaks)
+ breaks--;
+ lastval = 1;
+ break;
+ }
+ freeheap();
+ }
+ popheap();
+ loops--;
+ return lastval;
+}
+
+/**/
+int
+execselect(Cmd cmd)
+{
+ List list;
+ Forcmd node;
+ char *str, *s;
+ LinkList args;
+ LinkNode n;
+ int i;
+ FILE *inp;
+
+ node = cmd->u.forcmd;
+ args = cmd->args;
+ if (!node->inflag) {
+ char **x;
+
+ args = newlinklist();
+ for (x = pparams; *x; x++)
+ addlinknode(args, ztrdup(*x));
+ }
+ if (empty(args))
+ return 1;
+ loops++;
+ lastval = 0;
+ pushheap();
+ inp = fdopen(dup((SHTTY == -1) ? 0 : SHTTY), "r");
+ selectlist(args);
+ for (;;) {
+ for (;;) {
+ if (empty(bufstack)) {
+ if (interact && SHTTY != -1 && isset(USEZLE)) {
+ isfirstln = 1;
+ str = (char *)zleread(prompt3, NULL, 0);
+ } else {
+ str = promptexpand(prompt3, 0, NULL, NULL);
+ zputs(str, stderr);
+ free(str);
+ fflush(stderr);
+ str = fgets(zalloc(256), 256, inp);
+ }
+ } else
+ str = (char *)getlinknode(bufstack);
+ if (!str || errflag) {
+ if (breaks)
+ breaks--;
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ goto done;
+ }
+ if ((s = strchr(str, '\n')))
+ *s = '\0';
+ if (*str)
+ break;
+ selectlist(args);
+ }
+ setsparam("REPLY", ztrdup(str));
+ i = atoi(str);
+ if (!i)
+ str = "";
+ else {
+ for (i--, n = firstnode(args); n && i; incnode(n), i--);
+ if (n)
+ str = (char *) getdata(n);
+ else
+ str = "";
+ }
+ setsparam(node->name, ztrdup(str));
+ list = (List) dupstruct(node->list);
+ execlist(list, 1, 0);
+ freeheap();
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (errflag)
+ break;
+ }
+ done:
+ popheap();
+ fclose(inp);
+ loops--;
+ return lastval;
+}
+
+/* And this is used to print select lists. */
+
+/**/
+static void
+selectlist(LinkList l)
+{
+ size_t longest = 1, fct, fw = 0, colsz, t0, t1, ct;
+ LinkNode n;
+ char **arr, **ap;
+
+ trashzle();
+ ct = countlinknodes(l);
+ ap = arr = (char **)alloc((countlinknodes(l) + 1) * sizeof(char **));
+
+ for (n = (LinkNode) firstnode(l); n; incnode(n))
+ *ap++ = (char *)getdata(n);
+ *ap = NULL;
+ for (ap = arr; *ap; ap++)
+ if (strlen(*ap) > longest)
+ longest = strlen(*ap);
+ t0 = ct;
+ longest++;
+ while (t0)
+ t0 /= 10, longest++;
+ /* to compensate for added ')' */
+ fct = (columns - 1) / (longest + 3);
+ if (fct == 0)
+ fct = 1;
+ else
+ fw = (columns - 1) / fct;
+ colsz = (ct + fct - 1) / fct;
+ for (t1 = 0; t1 != colsz; t1++) {
+ ap = arr + t1;
+ do {
+ int t2 = strlen(*ap) + 2, t3;
+
+ fprintf(stderr, "%d) %s", t3 = ap - arr + 1, *ap);
+ while (t3)
+ t2++, t3 /= 10;
+ for (; t2 < fw; t2++)
+ fputc(' ', stderr);
+ for (t0 = colsz; t0 && *ap; t0--, ap++);
+ }
+ while (*ap);
+ fputc('\n', stderr);
+ }
+
+ /* Below is a simple attempt at doing it the Korn Way..
+ ap = arr;
+ t0 = 0;
+ do {
+ t0++;
+ fprintf(stderr,"%d) %s\n",t0,*ap);
+ ap++;
+ }
+ while (*ap);*/
+ fflush(stderr);
+}
+
+/**/
+int
+execwhile(Cmd cmd)
+{
+ List list;
+ struct whilecmd *node;
+ int olderrexit, oldval;
+
+ olderrexit = noerrexit;
+ node = cmd->u.whilecmd;
+ oldval = 0;
+ pushheap();
+ loops++;
+ for (;;) {
+ list = (List) dupstruct(node->cont);
+ noerrexit = 1;
+ execlist(list, 1, 0);
+ noerrexit = olderrexit;
+ if (!((lastval == 0) ^ node->cond)) {
+ if (breaks)
+ breaks--;
+ lastval = oldval;
+ break;
+ }
+ list = (List) dupstruct(node->loop);
+ execlist(list, 1, 0);
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ freeheap();
+ if (errflag) {
+ lastval = 1;
+ break;
+ }
+ oldval = lastval;
+ }
+ popheap();
+ loops--;
+ return lastval;
+}
+
+/**/
+int
+execrepeat(Cmd cmd)
+{
+ List list;
+ int count;
+
+ lastval = 0;
+ if (empty(cmd->args) || nextnode(firstnode(cmd->args))) {
+ zerr("bad argument for repeat", NULL, 0);
+ return 1;
+ }
+ count = atoi(peekfirst(cmd->args));
+ pushheap();
+ loops++;
+ while (count--) {
+ list = (List) dupstruct(cmd->u.list);
+ execlist(list, 1, 0);
+ freeheap();
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (errflag) {
+ lastval = 1;
+ break;
+ }
+ }
+ popheap();
+ loops--;
+ return lastval;
+}
+
+/**/
+int
+execif(Cmd cmd)
+{
+ struct ifcmd *node;
+ int olderrexit;
+ List *i, *t;
+
+ olderrexit = noerrexit;
+ node = cmd->u.ifcmd;
+ i = node->ifls;
+ t = node->thenls;
+
+ if (!noerrexit)
+ noerrexit = 1;
+ while (*i) {
+ execlist(*i, 1, 0);
+ if (!lastval)
+ break;
+ i++;
+ t++;
+ }
+ noerrexit = olderrexit;
+
+ if (*t)
+ execlist(*t, 1, cmd->flags & CFLAG_EXEC);
+ else
+ lastval = 0;
+
+ return lastval;
+}
+
+/**/
+int
+execcase(Cmd cmd)
+{
+ struct casecmd *node;
+ char *word;
+ List *l;
+ char **p;
+
+ node = cmd->u.casecmd;
+ l = node->lists;
+ p = node->pats;
+
+ word = *p++;
+ singsub(&word);
+ untokenize(word);
+ lastval = 0;
+
+ if (node) {
+ while (*p) {
+ char *pat = *p + 1;
+ singsub(&pat);
+ if (matchpat(word, pat)) {
+ do {
+ execlist(*l++, 1, **p == ';' && (cmd->flags & CFLAG_EXEC));
+ } while(**p++ == '&' && *p);
+ break;
+ }
+ p++;
+ l++;
+ }
+ }
+ return lastval;
+}
+
diff --git a/Src/main.c b/Src/main.c
new file mode 100644
index 000000000..7ec8f07bd
--- /dev/null
+++ b/Src/main.c
@@ -0,0 +1,99 @@
+/*
+ * main.c - the main() function
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "main.pro"
+
+/**/
+int
+main(int argc, char **argv)
+{
+ char **t;
+#ifdef LC_ALL
+ setlocale(LC_ALL, "");
+#endif
+
+ global_permalloc();
+
+ init_hackzero(argv, environ);
+
+ for (t = argv; *t; *t = metafy(*t, -1, META_ALLOC), t++);
+
+ if (!(zsh_name = strrchr(argv[0], '/')))
+ zsh_name = argv[0];
+ else
+ zsh_name++;
+ if (*zsh_name == '-')
+ zsh_name++;
+
+ fdtable_size = OPEN_MAX;
+ fdtable = zcalloc(fdtable_size);
+
+ createoptiontable();
+ emulate(zsh_name, 1); /* initialises most options */
+ opts[LOGINSHELL] = (**argv == '-');
+ opts[MONITOR] = 1; /* may be unset in init_io() */
+ opts[PRIVILEGED] = (getuid() != geteuid() || getgid() != getegid());
+ opts[USEZLE] = 1; /* may be unset in init_io() */
+ parseargs(argv); /* sets INTERACTIVE, SHINSTDIN and SINGLECOMMAND */
+
+ SHTTY = -1;
+ init_io();
+ setupvals();
+ init_signals();
+ global_heapalloc();
+ init_bltinmods();
+ run_init_scripts();
+ init_misc();
+
+ for (;;) {
+ do
+ loop(1,0);
+ while (tok != ENDINPUT && (tok != LEXERR || isset(SHINSTDIN)));
+ if (tok == LEXERR) {
+ stopmsg = 1;
+ zexit(lastval, 0);
+ }
+ if (!(isset(IGNOREEOF) && interact)) {
+#if 0
+ if (interact)
+ fputs(islogin ? "logout\n" : "exit\n", shout);
+#endif
+ zexit(lastval, 0);
+ continue;
+ }
+ noexitct++;
+ if (noexitct >= 10) {
+ stopmsg = 1;
+ zexit(lastval, 0);
+ }
+ zerrnam("zsh", (!islogin) ? "use 'exit' to exit."
+ : "use 'logout' to logout.", NULL, 0);
+ }
+}
diff --git a/Src/makepro.awk b/Src/makepro.awk
new file mode 100644
index 000000000..b5d2f3dc4
--- /dev/null
+++ b/Src/makepro.awk
@@ -0,0 +1,146 @@
+#
+# makepro.awk - generate prototype lists
+#
+
+BEGIN {
+ aborting = 0
+
+ # arg 1 is the name of the file to process
+ # arg 2 is the name of the subdirectory it is in
+ if(ARGC != 3) {
+ aborting = 1
+ exit 1
+ }
+ name = ARGV[1]
+ gsub(/^.*\//, "", name)
+ gsub(/\.c$/, "", name)
+ name = ARGV[2] "_" name
+ gsub(/\//, "_", name)
+ ARGC--
+
+ # `locals' is a list of local declarations, built up while global
+ # declarations are output.
+ locals = ""
+
+ printf "#ifndef have_%s_globals\n", name
+ printf "#define have_%s_globals\n", name
+ printf "\n"
+}
+
+# all relevant declarations are preceded by "/**/" on a line by itself
+
+/^\/\*\*\/$/ {
+ # The declaration is on following lines. The interesting part might
+ # be terminated by a `{' (`int foo(void) { }' or `int bar[] = {')
+ # or `;' (`int x;').
+ line = ""
+ isfunc = 0
+ while(1) {
+ if(getline <= 0) {
+ aborting = 1
+ exit 1
+ }
+ gsub(/\t/, " ")
+ line = line " " $0
+ gsub(/\/\*([^*]|\*+[^*\/])*\*+\//, " ", line)
+ if(line ~ /\/\*/)
+ continue
+ # If it is a function definition, note so.
+ if(line ~ /\) *[{].*$/) #}
+ isfunc = 1
+ if(sub(/ *[{;].*$/, "", line)) #}
+ break
+ }
+ # Put spaces around each identifier.
+ while(match(line, /[^_0-9A-Za-z ][_0-9A-Za-z]/) ||
+ match(line, /[_0-9A-Za-z][^_0-9A-Za-z ]/))
+ line = substr(line, 1, RSTART) " " substr(line, RSTART+1)
+ # Separate declarations into a type and a list of declarators.
+ # In each declarator, "@{" and "@}" are used in place of parens to
+ # mark function parameter lists, and "@!" is used in place of commas
+ # in parameter lists. "@<" and "@>" are used in place of
+ # non-parameter list parens.
+ gsub(/ _ +/, " _ ", line)
+ while(1) {
+ if(isfunc && match(line, /\([^()]*\)$/))
+ line = substr(line, 1, RSTART-1) " _ (" substr(line, RSTART) ")"
+ else if(match(line, / _ \(\([^,()]*,/))
+ line = substr(line, 1, RSTART+RLENGTH-2) "@!" substr(line, RSTART+RLENGTH)
+ else if(match(line, / _ \(\([^,()]*\)\)/))
+ line = substr(line, 1, RSTART-1) "@{" substr(line, RSTART+5, RLENGTH-7) "@}" substr(line, RSTART+RLENGTH)
+ else if(match(line, /\([^,()]*\)/))
+ line = substr(line, 1, RSTART-1) "@<" substr(line, RSTART+1, RLENGTH-2) "@>" substr(line, RSTART+RLENGTH)
+ else
+ break
+ }
+ sub(/^ */, "", line)
+ match(line, /^((const|enum|static|struct|union) +)*([_0-9A-Za-z]+ +|((char|double|float|int|long|short|unsigned|void) +)+)((const|static) +)*/)
+ dtype = substr(line, 1, RLENGTH)
+ sub(/ *$/, "", dtype)
+ islocal = " " dtype " " ~ / static /
+ line = substr(line, RLENGTH+1) ","
+ # Handle each declarator.
+ output = ""
+ while(match(line, /^[^,]*,/)) {
+ # Separate out the name from the declarator. Use "@+" and "@-"
+ # to bracket the name within the declarator. Strip off any
+ # initialiser.
+ dcltor = substr(line, 1, RLENGTH-1)
+ line = substr(line, RLENGTH+1)
+ sub(/\=.*$/, "", dcltor)
+ match(dcltor, /^([^_0-9A-Za-z]| const )*/)
+ dcltor = substr(dcltor, 1, RLENGTH) "@+" substr(dcltor, RLENGTH+1)
+ match(dcltor, /^.*@\+[_0-9A-Za-z]+/)
+ dcltor = substr(dcltor, 1, RLENGTH) "@-" substr(dcltor, RLENGTH+1)
+ dnam = dcltor
+ sub(/^.*@\+/, "", dnam)
+ sub(/@-.*$/, "", dnam)
+
+ # Put parens etc. back
+ gsub(/@[{]/, " _((", dcltor)
+ gsub(/@}/, "))", dcltor)
+ gsub(/@</, "(", dcltor)
+ gsub(/@>/, ")", dcltor)
+ gsub(/@!/, ",", dcltor)
+
+ # If this is a module boot/cleanup function, conditionally rename it.
+ if(" " dtype " " ~ / int / && dcltor ~ / *@\+(boot|cleanup)_[_0-9A-Za-z]+@- *_\(\( *Module +[_0-9A-Za-z]+ *\)\) */) {
+ modtype = dnam
+ sub(/_.*$/, "", modtype)
+ output = output "# if defined(DYNAMIC_NAME_CLASH_OK) && defined(MODULE)\n"
+ output = output "# define " dnam " " modtype "_\n"
+ output = output "# endif\n"
+ }
+
+ # Format the declaration for output
+ dcl = dtype " " dcltor ";"
+ if(!islocal)
+ dcl = "extern " dcl
+ gsub(/@[+-]/, "", dcl)
+ gsub(/ +/, " ", dcl)
+ while(match(dcl, /[^_0-9A-Za-z] ./) || match(dcl, /. [^_0-9A-Za-z]/))
+ dcl = substr(dcl, 1, RSTART) substr(dcl, RSTART+2)
+ output = output dcl "\n"
+ }
+
+ # Output global declarations now, but save up locals until the end.
+ if(islocal)
+ locals = locals output
+ else
+ printf "%s", output
+}
+
+END {
+ if(aborting)
+ exit 1
+ printf "\n"
+ printf "#endif /* !have_%s_globals */\n", name
+ if(locals != "") {
+ printf "\n"
+ printf "#ifndef GLOBAL_PROTOTYPES\n"
+ printf "\n"
+ printf locals
+ printf "\n"
+ printf "#endif /* !GLOBAL_PROTOTYPES */\n"
+ }
+}
diff --git a/Src/math.c b/Src/math.c
new file mode 100644
index 000000000..7a0a1f9bd
--- /dev/null
+++ b/Src/math.c
@@ -0,0 +1,883 @@
+/*
+ * math.c - mathematical expression evaluation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "math.pro"
+
+/* nonzero means we are not evaluating, just parsing */
+
+/**/
+int noeval;
+
+/* last input base we used */
+
+/**/
+int lastbase;
+
+static char *ptr;
+
+static long yyval;
+static LV yylval;
+
+static int mlevel = 0;
+
+/* != 0 means recognize unary plus, minus, etc. */
+
+static int unary = 1;
+
+/* LR = left-to-right associativity *
+ * RL = right-to-left associativity *
+ * BOO = short-circuiting boolean */
+
+#define LR 0
+#define RL 1
+#define BOOL 2
+
+#define M_INPAR 0
+#define M_OUTPAR 1
+#define NOT 2
+#define COMP 3
+#define POSTPLUS 4
+#define POSTMINUS 5
+#define UPLUS 6
+#define UMINUS 7
+#define AND 8
+#define XOR 9
+#define OR 10
+#define MUL 11
+#define DIV 12
+#define MOD 13
+#define PLUS 14
+#define MINUS 15
+#define SHLEFT 16
+#define SHRIGHT 17
+#define LES 18
+#define LEQ 19
+#define GRE 20
+#define GEQ 21
+#define DEQ 22
+#define NEQ 23
+#define DAND 24
+#define DOR 25
+#define DXOR 26
+#define QUEST 27
+#define COLON 28
+#define EQ 29
+#define PLUSEQ 30
+#define MINUSEQ 31
+#define MULEQ 32
+#define DIVEQ 33
+#define MODEQ 34
+#define ANDEQ 35
+#define XOREQ 36
+#define OREQ 37
+#define SHLEFTEQ 38
+#define SHRIGHTEQ 39
+#define DANDEQ 40
+#define DOREQ 41
+#define DXOREQ 42
+#define COMMA 43
+#define EOI 44
+#define PREPLUS 45
+#define PREMINUS 46
+#define NUM 47
+#define ID 48
+#define POWER 49
+#define CID 50
+#define POWEREQ 51
+#define TOKCOUNT 52
+
+/* precedences */
+
+static int prec[TOKCOUNT] =
+{
+ 1, 137, 2, 2, 2,
+ 2, 2, 2, 4, 5,
+ 6, 8, 8, 8, 9,
+ 9, 3, 3, 10, 10,
+ 10, 10, 11, 11, 12,
+ 13, 13, 14, 14, 15,
+ 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15,
+ 15, 15, 15, 16, 200,
+ 2, 2, 0, 0, 7,
+ 0, 15
+};
+
+#define TOPPREC 16
+#define ARGPREC (TOPPREC-1)
+
+static int type[TOKCOUNT] =
+{
+ LR, LR, RL, RL, RL,
+ RL, RL, RL, LR, LR,
+ LR, LR, LR, LR, LR,
+ LR, LR, LR, LR, LR,
+ LR, LR, LR, LR, BOOL,
+ BOOL, LR, RL, RL, RL,
+ RL, RL, RL, RL, RL,
+ RL, RL, RL, RL, RL,
+ BOOL, BOOL, RL, RL, RL,
+ RL, RL, LR, LR, RL,
+ LR, RL
+};
+
+#define LVCOUNT 32
+
+/* list of lvalues (variables) */
+
+static int lvc;
+static char **lvals;
+
+
+/**/
+static int
+zzlex(void)
+{
+ int cct = 0;
+
+ for (;; cct = 0)
+ switch (*ptr++) {
+ case '+':
+ if (*ptr == '+' && (unary || !ialnum(*ptr))) {
+ ptr++;
+ return (unary) ? PREPLUS : POSTPLUS;
+ }
+ if (*ptr == '=') {
+ unary = 1;
+ ptr++;
+ return PLUSEQ;
+ }
+ return (unary) ? UPLUS : PLUS;
+ case '-':
+ if (*ptr == '-' && (unary || !ialnum(*ptr))) {
+ ptr++;
+ return (unary) ? PREMINUS : POSTMINUS;
+ }
+ if (*ptr == '=') {
+ unary = 1;
+ ptr++;
+ return MINUSEQ;
+ }
+ return (unary) ? UMINUS : MINUS;
+ case '(':
+ unary = 1;
+ return M_INPAR;
+ case ')':
+ return M_OUTPAR;
+ case '!':
+ if (*ptr == '=') {
+ unary = 1;
+ ptr++;
+ return NEQ;
+ }
+ return NOT;
+ case '~':
+ return COMP;
+ case '&':
+ unary = 1;
+ if (*ptr == '&') {
+ if (*++ptr == '=') {
+ ptr++;
+ return DANDEQ;
+ }
+ return DAND;
+ } else if (*ptr == '=') {
+ ptr++;
+ return ANDEQ;
+ }
+ return AND;
+ case '|':
+ unary = 1;
+ if (*ptr == '|') {
+ if (*++ptr == '=') {
+ ptr++;
+ return DOREQ;
+ }
+ return DOR;
+ } else if (*ptr == '=') {
+ ptr++;
+ return OREQ;
+ }
+ return OR;
+ case '^':
+ unary = 1;
+ if (*ptr == '^') {
+ if (*++ptr == '=') {
+ ptr++;
+ return DXOREQ;
+ }
+ return DXOR;
+ } else if (*ptr == '=') {
+ ptr++;
+ return XOREQ;
+ }
+ return XOR;
+ case '*':
+ unary = 1;
+ if (*ptr == '*') {
+ if (*++ptr == '=') {
+ ptr++;
+ return POWEREQ;
+ }
+ return POWER;
+ }
+ if (*ptr == '=') {
+ ptr++;
+ return MULEQ;
+ }
+ return MUL;
+ case '/':
+ unary = 1;
+ if (*ptr == '=') {
+ ptr++;
+ return DIVEQ;
+ }
+ return DIV;
+ case '%':
+ unary = 1;
+ if (*ptr == '=') {
+ ptr++;
+ return MODEQ;
+ }
+ return MOD;
+ case '<':
+ unary = 1;
+ if (*ptr == '<') {
+ if (*++ptr == '=') {
+ ptr++;
+ return SHLEFTEQ;
+ }
+ return SHLEFT;
+ } else if (*ptr == '=') {
+ ptr++;
+ return LEQ;
+ }
+ return LES;
+ case '>':
+ unary = 1;
+ if (*ptr == '>') {
+ if (*++ptr == '=') {
+ ptr++;
+ return SHRIGHTEQ;
+ }
+ return SHRIGHT;
+ } else if (*ptr == '=') {
+ ptr++;
+ return GEQ;
+ }
+ return GRE;
+ case '=':
+ unary = 1;
+ if (*ptr == '=') {
+ ptr++;
+ return DEQ;
+ }
+ return EQ;
+ case '$':
+ unary = 0;
+ yyval = mypid;
+ return NUM;
+ case '?':
+ if (unary) {
+ yyval = lastval;
+ unary = 0;
+ return NUM;
+ }
+ unary = 1;
+ return QUEST;
+ case ':':
+ unary = 1;
+ return COLON;
+ case ',':
+ unary = 1;
+ return COMMA;
+ case '\0':
+ unary = 1;
+ ptr--;
+ return EOI;
+ case '[':
+ unary = 0;
+ {
+ int base = zstrtol(ptr, &ptr, 10);
+
+ if (*ptr == ']')
+ ptr++;
+ yyval = zstrtol(ptr, &ptr, lastbase = base);
+ return NUM;
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ case '0':
+ if (*ptr == 'x' || *ptr == 'X') {
+ unary = 0;
+ /* Should we set lastbase here? */
+ yyval = zstrtol(++ptr, &ptr, lastbase = 16);
+ return NUM;
+ }
+ /* Fall through! */
+ default:
+ if (idigit(*--ptr)) {
+ unary = 0;
+ yyval = zstrtol(ptr, &ptr, 10);
+
+ if (*ptr == '#') {
+ ptr++;
+ yyval = zstrtol(ptr, &ptr, lastbase = yyval);
+ }
+ return NUM;
+ }
+ if (*ptr == '#') {
+ if (*++ptr == '\\') {
+ ptr++;
+ yyval = *ptr == Meta ? *++ptr ^ 32 : *ptr;
+ ptr++;
+ unary = 0;
+ return NUM;
+ }
+ cct = 1;
+ }
+ if (iident(*ptr)) {
+ char *p, q;
+
+ p = ptr;
+ if (lvc == LVCOUNT) {
+ zerr("too many identifiers (complain to author)", NULL, 0);
+ return EOI;
+ }
+ unary = 0;
+ while (iident(*++ptr));
+ if (*ptr == '[') {
+ int l;
+ for (ptr++, l = 1; *ptr && l; ptr++) {
+ if (*ptr == '[')
+ l++;
+ if (*ptr == ']')
+ l--;
+ if (*ptr == '\\' && ptr[1])
+ ptr++;
+ }
+ }
+ q = *ptr;
+ *ptr = '\0';
+ lvals[yylval = lvc++] = ztrdup(p);
+ *ptr = q;
+ return cct ? CID : ID;
+ }
+ else if (cct) {
+ yyval = poundgetfn(NULL);
+ unary = 0;
+ return NUM;
+ }
+ return EOI;
+ }
+}
+
+/* the value stack */
+
+#define STACKSZ 100
+static int mtok; /* last token */
+static int sp = -1; /* stack pointer */
+
+struct mathvalue {
+ LV lval;
+ long val;
+};
+
+static struct mathvalue *stack;
+
+/**/
+static void
+push(long val, LV lval)
+{
+ if (sp == STACKSZ - 1)
+ zerr("stack overflow", NULL, 0);
+ else
+ sp++;
+ stack[sp].val = val;
+ stack[sp].lval = lval;
+}
+
+
+/**/
+static long
+getcvar(LV s)
+{
+ char *t;
+
+ if (!(t = getsparam(lvals[s])))
+ return 0;
+ return STOUC(*t == Meta ? t[1] ^ 32 : *t);
+}
+
+
+/**/
+static long
+setvar(LV s, long v)
+{
+ if (s == -1 || s >= lvc) {
+ zerr("lvalue required", NULL, 0);
+ return 0;
+ }
+ if (noeval)
+ return v;
+ setiparam(lvals[s], v);
+ return v;
+}
+
+
+/**/
+static int
+notzero(long a)
+{
+ if (a == 0) {
+ zerr("division by zero", NULL, 0);
+ return 0;
+ }
+ return 1;
+}
+
+/* macro to pop two values off the value stack */
+#define pop2() { \
+ if (sp < 1) { \
+ zerr("bad math expression: unbalanced stack", NULL, 0); \
+ return; \
+ } \
+ b = stack[sp--].val; \
+ a = stack[sp--].val; \
+ }
+
+/* macro to pop three values off the value stack */
+#define pop3() { \
+ if (sp < 2) { \
+ zerr("bad math expression: unbalanced stack", NULL, 0); \
+ return; \
+ } \
+ c = stack[sp--].val; \
+ b = stack[sp--].val; \
+ a = stack[sp--].val; \
+ }
+
+#define nolval() {stack[sp].lval= -1;}
+#define pushv(X) { push(X,-1); }
+#define pop2lv() { pop2() lv = stack[sp+1].lval; }
+#define set(X) { push(setvar(lv,X),lv); }
+
+
+/**/
+void
+op(int what)
+{
+ long a, b, c;
+ LV lv;
+
+ if (sp < 0) {
+ zerr("bad math expression: stack empty", NULL, 0);
+ return;
+ }
+ switch (what) {
+ case NOT:
+ stack[sp].val = !stack[sp].val;
+ nolval();
+ break;
+ case COMP:
+ stack[sp].val = ~stack[sp].val;
+ nolval();
+ break;
+ case POSTPLUS:
+ (void)setvar(stack[sp].lval, stack[sp].val + 1);
+ break;
+ case POSTMINUS:
+ (void)setvar(stack[sp].lval, stack[sp].val - 1);
+ break;
+ case UPLUS:
+ nolval();
+ break;
+ case UMINUS:
+ stack[sp].val = -stack[sp].val;
+ nolval();
+ break;
+ case AND:
+ pop2();
+ pushv(a & b);
+ break;
+ case XOR:
+ pop2();
+ pushv(a ^ b);
+ break;
+ case OR:
+ pop2();
+ pushv(a | b);
+ break;
+ case MUL:
+ pop2();
+ pushv(a * b);
+ break;
+ case DIV:
+ pop2();
+ if (notzero(b))
+ pushv(a / b);
+ break;
+ case MOD:
+ pop2();
+ if (notzero(b))
+ pushv(a % b);
+ break;
+ case PLUS:
+ pop2();
+ pushv(a + b);
+ break;
+ case MINUS:
+ pop2();
+ pushv(a - b);
+ break;
+ case SHLEFT:
+ pop2();
+ pushv(a << b);
+ break;
+ case SHRIGHT:
+ pop2();
+ pushv(a >> b);
+ break;
+ case LES:
+ pop2();
+ pushv((long)(a < b));
+ break;
+ case LEQ:
+ pop2();
+ pushv((long)(a <= b));
+ break;
+ case GRE:
+ pop2();
+ pushv((long)(a > b));
+ break;
+ case GEQ:
+ pop2();
+ pushv((long)(a >= b));
+ break;
+ case DEQ:
+ pop2();
+ pushv((long)(a == b));
+ break;
+ case NEQ:
+ pop2();
+ pushv((long)(a != b));
+ break;
+ case DAND:
+ pop2();
+ pushv((long)(a && b));
+ break;
+ case DOR:
+ pop2();
+ pushv((long)(a || b));
+ break;
+ case DXOR:
+ pop2();
+ pushv((long)((a && !b) || (!a && b)));
+ break;
+ case QUEST:
+ pop3();
+ pushv((a) ? b : c);
+ break;
+ case COLON:
+ break;
+ case EQ:
+ pop2();
+ lv = stack[sp + 1].lval;
+ set(b);
+ break;
+ case PLUSEQ:
+ pop2lv();
+ set(a + b);
+ break;
+ case MINUSEQ:
+ pop2lv();
+ set(a - b);
+ break;
+ case MULEQ:
+ pop2lv();
+ set(a * b);
+ break;
+ case DIVEQ:
+ pop2lv();
+ if (notzero(b))
+ set(a / b);
+ break;
+ case MODEQ:
+ pop2lv();
+ if (notzero(b))
+ set(a % b);
+ break;
+ case ANDEQ:
+ pop2lv();
+ set(a & b);
+ break;
+ case XOREQ:
+ pop2lv();
+ set(a ^ b);
+ break;
+ case OREQ:
+ pop2lv();
+ set(a | b);
+ break;
+ case SHLEFTEQ:
+ pop2lv();
+ set(a << b);
+ break;
+ case SHRIGHTEQ:
+ pop2lv();
+ set(a >> b);
+ break;
+ case DANDEQ:
+ pop2lv();
+ set((long)(a && b));
+ break;
+ case DOREQ:
+ pop2lv();
+ set((long)(a || b));
+ break;
+ case DXOREQ:
+ pop2lv();
+ set((long)((a && !b) || (!a && b)));
+ break;
+ case COMMA:
+ pop2();
+ pushv(b);
+ break;
+ case PREPLUS:
+ stack[sp].val = setvar(stack[sp].lval,
+ stack[sp].val + 1);
+ break;
+ case PREMINUS:
+ stack[sp].val = setvar(stack[sp].lval,
+ stack[sp].val - 1);
+ break;
+ case POWER:
+ pop2();
+ if (b < 0) {
+ zerr("can't handle negative exponents", NULL, 0);
+ return;
+ }
+ for (c = 1; b--; c *= a);
+ pushv(c);
+ break;
+ case POWEREQ:
+ pop2lv();
+ if (b < 0) {
+ zerr("can't handle negative exponents", NULL, 0);
+ return;
+ }
+ for (c = 1; b--; c *= a);
+ set(c);
+ break;
+ default:
+ zerr("out of integers", NULL, 0);
+ return;
+ }
+}
+
+
+/**/
+static void
+bop(int tk)
+{
+ switch (tk) {
+ case DAND:
+ case DANDEQ:
+ if (!stack[sp].val)
+ noeval++;
+ break;
+ case DOR:
+ case DOREQ:
+ if (stack[sp].val)
+ noeval++;
+ break;
+ };
+}
+
+
+/**/
+static long
+mathevall(char *s, int prek, char **ep)
+{
+ int t0;
+ int xlastbase, xnoeval, xunary, xlvc;
+ char *xptr;
+ long xyyval;
+ LV xyylval;
+ char **xlvals = 0;
+ int xsp;
+ struct mathvalue *xstack = 0;
+ long ret;
+
+ xlastbase = xnoeval = xunary = xlvc = xyyval = xyylval = xsp = 0;
+ xptr = NULL;
+ if (mlevel++) {
+ xlastbase = lastbase;
+ xnoeval = noeval;
+ xunary = unary;
+ xlvc = lvc;
+ xptr = ptr;
+ xyyval = yyval;
+ xyylval = yylval;
+ xlvals = lvals;
+
+ xsp = sp;
+ xstack = stack;
+ }
+ stack = (struct mathvalue *)zalloc(STACKSZ*sizeof(struct mathvalue));
+ lastbase = -1;
+ lvals = (char **)zcalloc(LVCOUNT*sizeof(char *));
+ lvc = 0;
+ ptr = s;
+ sp = -1;
+ unary = 1;
+ mathparse(prek);
+ *ep = ptr;
+ if (sp)
+ zerr("bad math expression: unbalanced stack", NULL, 0);
+ for (t0 = 0; t0 != lvc; t0++)
+ zsfree(lvals[t0]);
+
+ ret = stack[0].val;
+
+ zfree(lvals, LVCOUNT*sizeof(char *));
+ zfree(stack, STACKSZ*sizeof(struct mathvalue));
+ if (--mlevel) {
+ lastbase = xlastbase;
+ noeval = xnoeval;
+ unary = xunary;
+ lvc = xlvc;
+ ptr = xptr;
+ yyval = xyyval;
+ yylval = xyylval;
+ lvals = xlvals;
+
+ sp = xsp;
+ stack = xstack;
+ }
+ return ret;
+}
+
+
+/**/
+long
+matheval(char *s)
+{
+ char *junk;
+ long x;
+ int xmtok = mtok;
+
+ if (!*s)
+ return 0;
+ x = mathevall(s, TOPPREC, &junk);
+ mtok = xmtok;
+ if (*junk)
+ zerr("bad math expression: illegal character: %c", NULL, *junk);
+ return x;
+}
+
+
+/**/
+long
+mathevalarg(char *s, char **ss)
+{
+ long x;
+ int xmtok = mtok;
+
+ x = mathevall(s, ARGPREC, ss);
+ if (mtok == COMMA)
+ (*ss)--;
+ mtok = xmtok;
+ return x;
+}
+
+
+/* operator-precedence parse the string and execute */
+
+/**/
+static void
+mathparse(int pc)
+{
+ int q, otok, onoeval;
+
+ if (errflag)
+ return;
+ mtok = zzlex();
+ while (prec[mtok] <= pc) {
+ if (errflag)
+ return;
+ switch (mtok) {
+ case NUM:
+ push(yyval, -1);
+ break;
+ case ID:
+ push(getiparam(lvals[yylval]), yylval);
+ break;
+ case CID:
+ push(getcvar(yylval), yylval);
+ break;
+ case M_INPAR:
+ mathparse(TOPPREC);
+ if (mtok != M_OUTPAR) {
+ if (!errflag)
+ zerr("')' expected", NULL, 0);
+ return;
+ }
+ break;
+ case QUEST:
+ q = stack[sp].val;
+
+ if (!q)
+ noeval++;
+ mathparse(prec[QUEST] - 1);
+ if (!q)
+ noeval--;
+ else
+ noeval++;
+ mathparse(prec[QUEST]);
+ if (q)
+ noeval--;
+ op(QUEST);
+ continue;
+ default:
+ otok = mtok;
+ onoeval = noeval;
+ if (type[otok] == BOOL)
+ bop(otok);
+ mathparse(prec[otok] - (type[otok] != RL));
+ noeval = onoeval;
+ op(otok);
+ continue;
+ }
+ mtok = zzlex();
+ }
+}
diff --git a/Src/mem.c b/Src/mem.c
new file mode 100644
index 000000000..1145f8c5e
--- /dev/null
+++ b/Src/mem.c
@@ -0,0 +1,1254 @@
+/*
+ * mem.c - memory management
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "mem.pro"
+
+/*
+ There are two ways to allocate memory in zsh. The first way is
+ to call zalloc/zcalloc, which call malloc/calloc directly. It
+ is legal to call realloc() or free() on memory allocated this way.
+ The second way is to call halloc/hcalloc, which allocates memory
+ from one of the memory pools on the heap stack. Such memory pools
+ will automatically created when the heap allocation routines are
+ called. To be sure that they are freed at appropriate times
+ one should call pushheap() before one starts using heaps and
+ popheap() after that (when the memory allocated on the heaps since
+ the last pushheap() isn't needed anymore).
+ pushheap() saves the states of all currently allocated heaps and
+ popheap() resets them to the last state saved and destroys the
+ information about that state. If you called pushheap() and
+ allocated some memory on the heaps and then come to a place where
+ you don't need the allocated memory anymore but you still want
+ to allocate memory on the heap, you should call freeheap(). This
+ works like popheap(), only that it doesn't free the information
+ about the heap states (i.e. the heaps are like after the call to
+ pushheap() and you have to call popheap some time later).
+
+ Memory allocated in this way does not have to be freed explicitly;
+ it will all be freed when the pool is destroyed. In fact,
+ attempting to free this memory may result in a core dump.
+ The pair of pointers ncalloc and alloc may point to either
+ zalloc & zcalloc or halloc & hcalloc; permalloc() sets them to the
+ former, and heapalloc() sets them to the latter. This can be useful.
+ For example, the dupstruct() routine duplicates a syntax tree,
+ allocating the new memory for the tree using alloc(). If you want
+ to duplicate a structure for a one-time use (i.e. to execute the list
+ in a for loop), call heapalloc(), then dupstruct(). If you want
+ to duplicate a structure in order to preserve it (i.e. a function
+ definition), call permalloc(), then dupstruct().
+
+ If we use zsh's own allocator we use a simple trick to avoid that
+ the (*real*) heap fills up with empty zsh-heaps: we allocate a
+ large block of memory before allocating a heap pool, this memory
+ is freed again immediately after the pool is allocated. If there
+ are only small blocks on the free list this guarantees that the
+ memory for the pool is at the end of the memory which means that
+ we can give it back to the system when the pool is freed.
+*/
+
+/* != 0 if we are allocating in the heaplist */
+
+/**/
+int useheap;
+
+/* Current allocation pointers. ncalloc() is either zalloc() or halloc(); *
+ * alloc() is either zcalloc() or hcalloc(). */
+
+/**/
+void *(*ncalloc) _((size_t)), *(*alloc) _((size_t));
+
+#ifdef ZSH_MEM_WARNING
+# ifndef DEBUG
+# define DEBUG 1
+# endif
+#endif
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+
+static int h_m[1025], h_push, h_pop, h_free;
+
+#endif
+
+#define H_ISIZE sizeof(long)
+#define HEAPSIZE (8192 - H_ISIZE)
+#define HEAP_ARENA_SIZE (HEAPSIZE - sizeof(struct heap))
+#define HEAPFREE (16384 - H_ISIZE)
+
+/* set default allocation to heap stack */
+
+/**/
+int
+global_heapalloc(void)
+{
+ int luh = useheap;
+
+ alloc = hcalloc;
+ ncalloc = halloc;
+ useheap = 1;
+ return luh;
+}
+
+/* set default allocation to malloc() */
+
+/**/
+int
+global_permalloc(void)
+{
+ int luh = useheap;
+
+ alloc = zcalloc;
+ ncalloc = zalloc;
+ useheap = 0;
+ return luh;
+}
+
+/* heappush saves the current heap state using this structure */
+
+struct heapstack {
+ struct heapstack *next; /* next one in list for this heap */
+ size_t used;
+};
+
+/* A zsh heap. */
+
+struct heap {
+ struct heap *next; /* next one */
+ size_t used; /* bytes used from the heap */
+ struct heapstack *sp; /* used by pushheap() to save the value used */
+#define arena(X) ((char *) (X) + sizeof(struct heap))
+};
+
+/* list of zsh heap */
+
+static Heap heaps;
+
+/* save states of zsh heaps */
+
+/**/
+void
+pushheap(void)
+{
+ Heap h;
+ Heapstack hs;
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_push++;
+#endif
+
+ for (h = heaps; h; h = h->next) {
+ DPUTS(!h->used, "BUG: empty heap");
+ hs = (Heapstack) zalloc(sizeof(*hs));
+ hs->next = h->sp;
+ h->sp = hs;
+ hs->used = h->used;
+ }
+}
+
+/* reset heaps to previous state */
+
+/**/
+void
+freeheap(void)
+{
+ Heap h, hn, hl = NULL;
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_free++;
+#endif
+ for (h = heaps; h; h = hn) {
+ hn = h->next;
+ if (h->sp) {
+#ifdef ZSH_MEM_DEBUG
+ memset(arena(h) + h->sp->used, 0xff, h->used - h->sp->used);
+#endif
+ h->used = h->sp->used;
+ hl = h;
+ } else
+ zfree(h, HEAPSIZE);
+ }
+ if (hl)
+ hl->next = NULL;
+ else
+ heaps = NULL;
+}
+
+/* reset heap to previous state and destroy state information */
+
+/**/
+void
+popheap(void)
+{
+ Heap h, hn, hl = NULL;
+ Heapstack hs;
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_pop++;
+#endif
+
+ for (h = heaps; h; h = hn) {
+ hn = h->next;
+ if ((hs = h->sp)) {
+ h->sp = hs->next;
+#ifdef ZSH_MEM_DEBUG
+ memset(arena(h) + hs->used, 0xff, h->used - hs->used);
+#endif
+ h->used = hs->used;
+ zfree(hs, sizeof(*hs));
+
+ hl = h;
+ } else
+ zfree(h, HEAPSIZE);
+ }
+ if (hl)
+ hl->next = NULL;
+ else
+ heaps = NULL;
+}
+
+/* allocate memory from the current memory pool */
+
+/**/
+void *
+halloc(size_t size)
+{
+ Heap h;
+ size_t n;
+
+ size = (size + H_ISIZE - 1) & ~(H_ISIZE - 1);
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_m[size < 1024 ? (size / H_ISIZE) : 1024]++;
+#endif
+
+ /* find a heap with enough free space */
+
+ for (h = heaps; h; h = h->next) {
+ if (HEAP_ARENA_SIZE >= (n = size + h->used)) {
+ h->used = n;
+ return arena(h) + n - size;
+ }
+ }
+
+ {
+ Heap hp;
+ /* not found, allocate new heap */
+#ifdef ZSH_MEM
+ static int called = 0;
+ void *foo = called ? (void *)malloc(HEAPFREE) : NULL;
+ /* tricky, see above */
+#endif
+
+ queue_signals();
+ n = HEAP_ARENA_SIZE > size ? HEAPSIZE : size + sizeof(*h);
+ for (hp = NULL, h = heaps; h; hp = h, h = h->next);
+
+ h = (Heap) zalloc(n);
+
+#ifdef ZSH_MEM
+ if (called)
+ zfree(foo, HEAPFREE);
+ called = 1;
+#endif
+
+ h->used = size;
+ h->next = NULL;
+ h->sp = NULL;
+
+ if (hp)
+ hp->next = h;
+ else
+ heaps = h;
+
+ unqueue_signals();
+ return arena(h);
+ }
+}
+
+/**/
+void *
+hrealloc(char *p, size_t old, size_t new)
+{
+ Heap h, ph;
+
+ old = (old + H_ISIZE - 1) & ~(H_ISIZE - 1);
+ new = (new + H_ISIZE - 1) & ~(H_ISIZE - 1);
+
+ if (old == new)
+ return p;
+ if (!old && !p)
+ return halloc(new);
+
+ /* find the heap with p */
+
+ for (h = heaps, ph = NULL; h; ph = h, h = h->next)
+ if (p >= arena(h) && p < arena(h) + HEAP_ARENA_SIZE)
+ break;
+
+ DPUTS(!h, "BUG: hrealloc() called for non-heap memory.");
+ DPUTS(h->sp && arena(h) + h->sp->used > p,
+ "BUG: hrealloc() wants to realloc pushed memory");
+
+ if (p + old < arena(h) + h->used) {
+ if (new > old) {
+ char *ptr = (char *) halloc(new);
+ memcpy(ptr, p, old);
+ return ptr;
+ } else
+ return new ? p : NULL;
+ }
+
+ DPUTS(p + old != arena(h) + h->used, "BUG: hrealloc more than allocated");
+
+ if (p == arena(h)) {
+ if (!new) {
+ if (ph)
+ ph->next = h->next;
+ else
+ heaps = h->next;
+ zfree(h, HEAPSIZE);
+ return NULL;
+ }
+ if (old > HEAP_ARENA_SIZE || new > HEAP_ARENA_SIZE) {
+ size_t n = HEAP_ARENA_SIZE > new ? HEAPSIZE : new + sizeof(*h);
+
+ if (ph)
+ ph->next = h = (Heap) realloc(h, n);
+ else
+ heaps = h = (Heap) realloc(h, n);
+ }
+ h->used = new;
+ return arena(h);
+ }
+ DPUTS(h->used > HEAP_ARENA_SIZE, "BUG: hrealloc at invalid address");
+ if (h->used + (new - old) <= HEAP_ARENA_SIZE) {
+ h->used += new - old;
+ return p;
+ } else {
+ char *t = halloc(new);
+ memcpy(t, p, old > new ? new : old);
+ h->used -= old;
+#ifdef ZSH_MEM_DEBUG
+ memset(p, 0xff, old);
+#endif
+ return t;
+ }
+}
+
+/* allocate memory from the current memory pool and clear it */
+
+/**/
+void *
+hcalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = halloc(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+/* allocate permanent memory */
+
+/**/
+void *
+zalloc(size_t size)
+{
+ void *ptr;
+
+ if (!size)
+ size = 1;
+ if (!(ptr = (void *) malloc(size))) {
+ zerr("fatal error: out of memory", NULL, 0);
+ exit(1);
+ }
+
+ return ptr;
+}
+
+/**/
+void *
+zcalloc(size_t size)
+{
+ void *ptr;
+
+ if (!size)
+ size = 1;
+ if (!(ptr = (void *) malloc(size))) {
+ zerr("fatal error: out of memory", NULL, 0);
+ exit(1);
+ }
+ memset(ptr, 0, size);
+
+ return ptr;
+}
+
+/* This front-end to realloc is used to make sure we have a realloc *
+ * that conforms to POSIX realloc. Older realloc's can fail if *
+ * passed a NULL pointer, but POSIX realloc should handle this. A *
+ * better solution would be for configure to check if realloc is *
+ * POSIX compliant, but I'm not sure how to do that. */
+
+/**/
+void *
+zrealloc(void *ptr, size_t size)
+{
+ if (ptr) {
+ if (size) {
+ /* Do normal realloc */
+ if (!(ptr = (void *) realloc(ptr, size))) {
+ zerr("fatal error: out of memory", NULL, 0);
+ exit(1);
+ }
+ return ptr;
+ }
+ else
+ /* If ptr is not NULL, but size is zero, *
+ * then object pointed to is freed. */
+ free(ptr);
+ } else {
+ /* If ptr is NULL, then behave like malloc */
+ return malloc(size);
+ }
+
+ return NULL;
+}
+
+/**/
+char *
+dupstring(const char *s)
+{
+ char *t;
+
+ if (!s)
+ return NULL;
+ t = (char *)ncalloc(strlen((char *)s) + 1);
+ strcpy(t, s);
+ return t;
+}
+
+/**/
+char *
+ztrdup(const char *s)
+{
+ char *t;
+
+ if (!s)
+ return NULL;
+ t = (char *)zalloc(strlen((char *)s) + 1);
+ strcpy(t, s);
+ return t;
+}
+
+#ifdef ZSH_MEM
+
+/*
+ Below is a simple segment oriented memory allocator for systems on
+ which it is better than the system's one. Memory is given in blocks
+ aligned to an integer multiple of sizeof(long) (4 bytes on most machines,
+ but 8 bytes on e.g. a dec alpha). Each block is preceded by a header
+ which contains the length of the data part (in bytes). In allocated
+ blocks only this field of the structure m_hdr is senseful. In free
+ blocks the second field (next) is a pointer to the next free segment
+ on the free list.
+
+ On top of this simple allocator there is a second allocator for small
+ chunks of data. It should be both faster and less space-consuming than
+ using the normal segment mechanism for such blocks.
+ For the first M_NSMALL-1 possible sizes memory is allocated in arrays
+ that can hold M_SNUM blocks. Each array is stored in one segment of the
+ main allocator. In these segments the third field of the header structure
+ (free) contains a pointer to the first free block in the array. The
+ last field (used) gives the number of already used blocks in the array.
+
+ If the macro name ZSH_MEM_DEBUG is defined, some information about the memory
+ usage is stored. This information can than be viewed by calling the
+ builtin `mem' (which is only available if ZSH_MEM_DEBUG is set).
+
+ If ZSH_MEM_WARNING is defined, error messages are printed in case of errors.
+
+ If ZSH_SECURE_FREE is defined, free() checks if the given address is really
+ one that was returned by malloc(), it ignores it if it wasn't (printing
+ an error message if ZSH_MEM_WARNING is also defined).
+*/
+#if !defined(__hpux) && !defined(DGUX) && !defined(__osf__)
+# if defined(_BSD)
+# ifndef HAVE_BRK_PROTO
+ extern int brk _((caddr_t));
+# endif
+# ifndef HAVE_SBRK_PROTO
+ extern caddr_t sbrk _((int));
+# endif
+# else
+# ifndef HAVE_BRK_PROTO
+ extern int brk _((void *));
+# endif
+# ifndef HAVE_SBRK_PROTO
+ extern void *sbrk _((int));
+# endif
+# endif
+#endif
+
+#if defined(_BSD) && !defined(STDC_HEADERS)
+# define FREE_RET_T int
+# define FREE_ARG_T char *
+# define FREE_DO_RET
+# define MALLOC_RET_T char *
+# define MALLOC_ARG_T size_t
+#else
+# define FREE_RET_T void
+# define FREE_ARG_T void *
+# define MALLOC_RET_T void *
+# define MALLOC_ARG_T size_t
+#endif
+
+/* structure for building free list in blocks holding small blocks */
+
+struct m_shdr {
+ struct m_shdr *next; /* next one on free list */
+};
+
+struct m_hdr {
+ long len; /* length of memory block */
+ struct m_hdr *next; /* if free: next on free list
+ if block of small blocks: next one with
+ small blocks of same size*/
+ struct m_shdr *free; /* if block of small blocks: free list */
+ long used; /* if block of small blocks: number of used
+ blocks */
+};
+
+
+/* alignment for memory blocks */
+
+#define M_ALIGN (sizeof(long))
+
+/* length of memory header, length of first field of memory header and
+ minimal size of a block left free (if we allocate memory and take a
+ block from the free list that is larger than needed, it must have at
+ least M_MIN extra bytes to be splitted; if it has, the rest is put on
+ the free list) */
+
+#define M_HSIZE (sizeof(struct m_hdr))
+#define M_ISIZE (sizeof(long))
+#define M_MIN (2 * M_ISIZE)
+
+/* a pointer to the last free block, a pointer to the free list (the blocks
+ on this list are kept in order - lowest address first) */
+
+static struct m_hdr *m_lfree, *m_free;
+
+/* system's pagesize */
+
+static long m_pgsz = 0;
+
+/* the highest and the lowest valid memory addresses, kept for fast validity
+ checks in free() and to find out if and when we can give memory back to
+ the system */
+
+static char *m_high, *m_low;
+
+/* Management of blocks for small blocks:
+ Such blocks are kept in lists (one list for each of the sizes that are
+ allocated in such blocks). The lists are stored in the m_small array.
+ M_SIDX() calculates the index into this array for a given size. M_SNUM
+ is the size (in small blocks) of such blocks. M_SLEN() calculates the
+ size of the small blocks held in a memory block, given a pointer to the
+ header of it. M_SBLEN() gives the size of a memory block that can hold
+ an array of small blocks, given the size of these small blocks. M_BSLEN()
+ caculates the size of the small blocks held in a memory block, given the
+ length of that block (including the header of the memory block. M_NSMALL
+ is the number of possible block sizes that small blocks should be used
+ for. */
+
+
+#define M_SIDX(S) ((S) / M_ISIZE)
+#define M_SNUM 50
+#define M_SLEN(M) ((M)->len / M_SNUM)
+#define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) + \
+ sizeof(long) + sizeof(struct m_hdr *))
+#define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) - \
+ sizeof(long) - sizeof(struct m_hdr *)) / M_SNUM)
+#define M_NSMALL 8
+
+static struct m_hdr *m_small[M_NSMALL];
+
+#ifdef ZSH_MEM_DEBUG
+
+static int m_s = 0, m_b = 0;
+static int m_m[1025], m_f[1025];
+
+static struct m_hdr *m_l;
+
+#endif /* ZSH_MEM_DEBUG */
+
+MALLOC_RET_T
+malloc(MALLOC_ARG_T size)
+{
+ struct m_hdr *m, *mp, *mt;
+ long n, s, os = 0;
+ struct heap *h, *hp, *hf = NULL, *hfp = NULL;
+
+ /* some systems want malloc to return the highest valid address plus one
+ if it is called with an argument of zero */
+
+ if (!size)
+ return (MALLOC_RET_T) m_high;
+
+ queue_signals(); /* just queue signals rather than handling them */
+
+ /* first call, get page size */
+
+ if (!m_pgsz) {
+
+#ifdef _SC_PAGESIZE
+ m_pgsz = sysconf(_SC_PAGESIZE); /* SVR4 */
+#else
+# ifdef _SC_PAGE_SIZE
+ m_pgsz = sysconf(_SC_PAGE_SIZE); /* HPUX */
+# else
+ m_pgsz = getpagesize();
+# endif
+#endif
+
+ m_free = m_lfree = NULL;
+ }
+ size = (size + M_ALIGN - 1) & ~(M_ALIGN - 1);
+
+ /* Do we need a small block? */
+
+ if ((s = M_SIDX(size)) && s < M_NSMALL) {
+ /* yep, find a memory block with free small blocks of the
+ appropriate size (if we find it in this list, this means that
+ it has room for at least one more small block) */
+ for (mp = NULL, m = m_small[s]; m && !m->free; mp = m, m = m->next);
+
+ if (m) {
+ /* we found one */
+ struct m_shdr *sh = m->free;
+
+ m->free = sh->next;
+ m->used++;
+
+ /* if all small blocks in this block are allocated, the block is
+ put at the end of the list blocks wth small blocks of this
+ size (i.e., we try to keep blocks with free blocks at the
+ beginning of the list, to make the search faster */
+
+ if (m->used == M_SNUM && m->next) {
+ for (mt = m; mt->next; mt = mt->next);
+
+ mt->next = m;
+ if (mp)
+ mp->next = m->next;
+ else
+ m_small[s] = m->next;
+ m->next = NULL;
+ }
+#ifdef ZSH_MEM_DEBUG
+ m_m[size / M_ISIZE]++;
+#endif
+
+ unqueue_signals();
+ return (MALLOC_RET_T) sh;
+ }
+ /* we still want a small block but there were no block with a free
+ small block of the requested size; so we use the real allocation
+ routine to allocate a block for small blocks of this size */
+ os = size;
+ size = M_SBLEN(size);
+ } else
+ s = 0;
+
+/* search the free list for an block of at least the requested size */
+ for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next);
+
+ /* if there is an empty zsh heap at a lower address we steal it and take
+ the memory from it, putting the rest on the free list (remember
+ that the blocks on the free list are ordered) */
+
+ for (hp = NULL, h = heaps; h; hp = h, h = h->next)
+ if (!h->used &&
+ (!hf || h < hf) &&
+ (!m || ((char *)m) > ((char *)h)))
+ hf = h, hfp = hp;
+
+ if (hf) {
+ /* we found such a heap */
+ Heapstack hso, hsn;
+
+ /* delete structures on the list holding the heap states */
+ for (hso = hf->sp; hso; hso = hsn) {
+ hsn = hso->next;
+ zfree(hso, sizeof(*hso));
+ }
+ /* take it from the list of heaps */
+ if (hfp)
+ hfp->next = hf->next;
+ else
+ heaps = hf->next;
+ /* now we simply free it and than search the free list again */
+ zfree(hf, HEAPSIZE);
+
+ for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next);
+ }
+ if (!m) {
+ /* no matching free block was found, we have to request new
+ memory from the system */
+ n = (size + M_HSIZE + m_pgsz - 1) & ~(m_pgsz - 1);
+
+ if (((char *)(m = (struct m_hdr *)sbrk(n))) == ((char *)-1)) {
+ DPUTS(1, "MEM: allocation error at sbrk.");
+ unqueue_signals();
+ return NULL;
+ }
+ /* set m_low, for the check in free() */
+ if (!m_low)
+ m_low = (char *)m;
+
+#ifdef ZSH_MEM_DEBUG
+ m_s += n;
+
+ if (!m_l)
+ m_l = m;
+#endif
+
+ /* save new highest address */
+ m_high = ((char *)m) + n;
+
+ /* initialize header */
+ m->len = n - M_ISIZE;
+ m->next = NULL;
+
+ /* put it on the free list and set m_lfree pointing to it */
+ if ((mp = m_lfree))
+ m_lfree->next = m;
+ m_lfree = m;
+ }
+ if ((n = m->len - size) > M_MIN) {
+ /* the block we want to use has more than M_MIN bytes plus the
+ number of bytes that were requested; we split it in two and
+ leave the rest on the free list */
+ struct m_hdr *mtt = (struct m_hdr *)(((char *)m) + M_ISIZE + size);
+
+ mtt->len = n - M_ISIZE;
+ mtt->next = m->next;
+
+ m->len = size;
+
+ /* put the rest on the list */
+ if (m_lfree == m)
+ m_lfree = mtt;
+
+ if (mp)
+ mp->next = mtt;
+ else
+ m_free = mtt;
+ } else if (mp) {
+ /* the block we found wasn't the first one on the free list */
+ if (m == m_lfree)
+ m_lfree = mp;
+ mp->next = m->next;
+ } else {
+ /* it was the first one */
+ m_free = m->next;
+ if (m == m_lfree)
+ m_lfree = m_free;
+ }
+
+ if (s) {
+ /* we are allocating a block that should hold small blocks */
+ struct m_shdr *sh, *shn;
+
+ /* build the free list in this block and set `used' filed */
+ m->free = sh = (struct m_shdr *)(((char *)m) +
+ sizeof(struct m_hdr) + os);
+
+ for (n = M_SNUM - 2; n--; sh = shn)
+ shn = sh->next = sh + s;
+ sh->next = NULL;
+
+ m->used = 1;
+
+ /* put the block on the list of blocks holding small blocks if
+ this size */
+ m->next = m_small[s];
+ m_small[s] = m;
+
+#ifdef ZSH_MEM_DEBUG
+ m_m[os / M_ISIZE]++;
+#endif
+
+ unqueue_signals();
+ return (MALLOC_RET_T) (((char *)m) + sizeof(struct m_hdr));
+ }
+#ifdef ZSH_MEM_DEBUG
+ m_m[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++;
+#endif
+
+ unqueue_signals();
+ return (MALLOC_RET_T) & m->next;
+}
+
+/* this is an internal free(); the second argument may, but need not hold
+ the size of the block the first argument is pointing to; if it is the
+ right size of this block, freeing it will be faster, though; the value
+ 0 for this parameter means: `don't know' */
+
+/**/
+void
+zfree(void *p, int sz)
+{
+ struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt = NULL;
+ int i;
+# ifdef DEBUG
+ int osz = sz;
+# endif
+
+#ifdef ZSH_SECURE_FREE
+ sz = 0;
+#else
+ sz = (sz + M_ALIGN - 1) & ~(M_ALIGN - 1);
+#endif
+
+ if (!p)
+ return;
+
+ /* first a simple check if the given address is valid */
+ if (((char *)p) < m_low || ((char *)p) > m_high ||
+ ((long)p) & (M_ALIGN - 1)) {
+ DPUTS(1, "BUG: attempt to free storage at invalid address");
+ return;
+ }
+
+ queue_signals();
+
+ fr_rec:
+
+ if ((i = sz / M_ISIZE) < M_NSMALL || !sz)
+ /* if the given sizes says that it is a small block, find the
+ memory block holding it; we search all blocks with blocks
+ of at least the given size; if the size parameter is zero,
+ this means, that all blocks are searched */
+ for (; i < M_NSMALL; i++) {
+ for (mp = NULL, mt = m_small[i];
+ mt && (((char *)mt) > ((char *)p) ||
+ (((char *)mt) + mt->len) < ((char *)p));
+ mp = mt, mt = mt->next);
+
+ if (mt) {
+ /* we found the block holding the small block */
+ struct m_shdr *sh = (struct m_shdr *)p;
+
+#ifdef ZSH_SECURE_FREE
+ struct m_shdr *sh2;
+
+ /* check if the given address is equal to the address of
+ the first small block plus an integer multiple of the
+ block size */
+ if ((((char *)p) - (((char *)mt) + sizeof(struct m_hdr))) %
+ M_BSLEN(mt->len)) {
+
+ DPUTS(1, "BUG: attempt to free storage at invalid address");
+ unqueue_signals();
+ return;
+ }
+ /* check, if the address is on the (block-intern) free list */
+ for (sh2 = mt->free; sh2; sh2 = sh2->next)
+ if (((char *)p) == ((char *)sh2)) {
+
+ DPUTS(1, "BUG: attempt to free already free storage");
+ unqueue_signals();
+ return;
+ }
+#endif
+ DPUTS(M_BSLEN(mt->len) < osz,
+ "BUG: attempt to free more than allocated.");
+
+#ifdef ZSH_MEM_DEBUG
+ m_f[M_BSLEN(mt->len) / M_ISIZE]++;
+ memset(sh, 0xff, M_BSLEN(mt->len));
+#endif
+
+ /* put the block onto the free list */
+ sh->next = mt->free;
+ mt->free = sh;
+
+ if (--mt->used) {
+ /* if there are still used blocks in this block, we
+ put it at the beginning of the list with blocks
+ holding small blocks of the same size (since we
+ know that there is at least one free block in it,
+ this will make allocation of small blocks faster;
+ it also guarantees that long living memory blocks
+ are preferred over younger ones */
+ if (mp) {
+ mp->next = mt->next;
+ mt->next = m_small[i];
+ m_small[i] = mt;
+ }
+ unqueue_signals();
+ return;
+ }
+ /* if there are no more used small blocks in this
+ block, we free the whole block */
+ if (mp)
+ mp->next = mt->next;
+ else
+ m_small[i] = mt->next;
+
+ m = mt;
+ p = (void *) & m->next;
+
+ break;
+ } else if (sz) {
+ /* if we didn't find a block and a size was given, try it
+ again as if no size were given */
+ sz = 0;
+ goto fr_rec;
+ }
+ }
+#ifdef ZSH_MEM_DEBUG
+ if (!mt)
+ m_f[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++;
+#endif
+
+#ifdef ZSH_SECURE_FREE
+ /* search all memory blocks, if one of them is at the given address */
+ for (mt = (struct m_hdr *)m_low;
+ ((char *)mt) < m_high;
+ mt = (struct m_hdr *)(((char *)mt) + M_ISIZE + mt->len))
+ if (((char *)p) == ((char *)&mt->next))
+ break;
+
+ /* no block was found at the given address */
+ if (((char *)mt) >= m_high) {
+ DPUTS(1, "BUG: attempt to free storage at invalid address");
+ unqueue_signals();
+ return;
+ }
+#endif
+
+ /* see if the block is on the free list */
+ for (mp = NULL, mt = m_free; mt && mt < m; mp = mt, mt = mt->next);
+
+ if (m == mt) {
+ /* it is, ouch! */
+ DPUTS(1, "BUG: attempt to free already free storage");
+ unqueue_signals();
+ return;
+ }
+ DPUTS(m->len < osz, "BUG: attempt to free more than allocated");
+#ifdef ZSH_MEM_DEBUG
+ memset(p, 0xff, m->len);
+#endif
+ if (mt && ((char *)mt) == (((char *)m) + M_ISIZE + m->len)) {
+ /* the block after the one we are freeing is free, we put them
+ together */
+ m->len += mt->len + M_ISIZE;
+ m->next = mt->next;
+
+ if (mt == m_lfree)
+ m_lfree = m;
+ } else
+ m->next = mt;
+
+ if (mp && ((char *)m) == (((char *)mp) + M_ISIZE + mp->len)) {
+ /* the block before the one we are freeing is free, we put them
+ together */
+ mp->len += m->len + M_ISIZE;
+ mp->next = m->next;
+
+ if (m == m_lfree)
+ m_lfree = mp;
+ } else if (mp)
+ /* otherwise, we just put it on the free list */
+ mp->next = m;
+ else {
+ m_free = m;
+ if (!m_lfree)
+ m_lfree = m_free;
+ }
+
+ /* if the block we have just freed was at the end of the process heap
+ and now there is more than one page size of memory, we can give
+ it back to the system (and we do it ;-) */
+ if ((((char *)m_lfree) + M_ISIZE + m_lfree->len) == m_high &&
+ m_lfree->len >= m_pgsz + M_MIN) {
+ long n = (m_lfree->len - M_MIN) & ~(m_pgsz - 1);
+
+ m_lfree->len -= n;
+ if (brk(m_high -= n) == -1)
+ DPUTS(1, "MEM: allocation error at brk.");
+
+#ifdef ZSH_MEM_DEBUG
+ m_b += n;
+#endif
+ }
+ unqueue_signals();
+}
+
+FREE_RET_T
+free(FREE_ARG_T p)
+{
+ zfree(p, 0); /* 0 means: size is unknown */
+
+#ifdef FREE_DO_RET
+ return 0;
+#endif
+}
+
+/* this one is for strings (and only strings, real strings, real C strings,
+ those that have a zero byte at the end) */
+
+/**/
+void
+zsfree(char *p)
+{
+ if (p)
+ zfree(p, strlen(p) + 1);
+}
+
+MALLOC_RET_T
+realloc(MALLOC_RET_T p, MALLOC_ARG_T size)
+{
+ struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt;
+ char *r;
+ int i, l = 0;
+
+ /* some system..., see above */
+ if (!p && size)
+ return (MALLOC_RET_T) malloc(size);
+ /* and some systems even do this... */
+ if (!p || !size)
+ return (MALLOC_RET_T) p;
+
+ queue_signals(); /* just queue signals caught rather than handling them */
+
+ /* check if we are reallocating a small block, if we do, we have
+ to compute the size of the block from the sort of block it is in */
+ for (i = 0; i < M_NSMALL; i++) {
+ for (mp = NULL, mt = m_small[i];
+ mt && (((char *)mt) > ((char *)p) ||
+ (((char *)mt) + mt->len) < ((char *)p));
+ mp = mt, mt = mt->next);
+
+ if (mt) {
+ l = M_BSLEN(mt->len);
+ break;
+ }
+ }
+ if (!l)
+ /* otherwise the size of the block is in the memory just before
+ the given address */
+ l = m->len;
+
+ /* now allocate the new block, copy the old contents, and free the
+ old block */
+ r = malloc(size);
+ memcpy(r, (char *)p, (size > l) ? l : size);
+ free(p);
+
+ unqueue_signals();
+ return (MALLOC_RET_T) r;
+}
+
+MALLOC_RET_T
+calloc(MALLOC_ARG_T n, MALLOC_ARG_T size)
+{
+ long l;
+ char *r;
+
+ if (!(l = n * size))
+ return (MALLOC_RET_T) m_high;
+
+ r = malloc(l);
+
+ memset(r, 0, l);
+
+ return (MALLOC_RET_T) r;
+}
+
+#ifdef ZSH_MEM_DEBUG
+
+/**/
+int
+bin_mem(char *name, char **argv, char *ops, int func)
+{
+ int i, ii, fi, ui, j;
+ struct m_hdr *m, *mf, *ms;
+ char *b, *c, buf[40];
+ long u = 0, f = 0;
+
+ if (ops['v']) {
+ printf("The lower and the upper addresses of the heap. Diff gives\n");
+ printf("the difference between them, i.e. the size of the heap.\n\n");
+ }
+ printf("low mem %ld\t high mem %ld\t diff %ld\n",
+ (long)m_l, (long)m_high, (long)(m_high - ((char *)m_l)));
+
+ if (ops['v']) {
+ printf("\nThe number of bytes that were allocated using sbrk() and\n");
+ printf("the number of bytes that were given back to the system\n");
+ printf("via brk().\n");
+ }
+ printf("\nsbrk %d\tbrk %d\n", m_s, m_b);
+
+ if (ops['v']) {
+ printf("\nInformation about the sizes that were allocated or freed.\n");
+ printf("For each size that were used the number of mallocs and\n");
+ printf("frees is shown. Diff gives the difference between these\n");
+ printf("values, i.e. the number of blocks of that size that is\n");
+ printf("currently allocated. Total is the product of size and diff,\n");
+ printf("i.e. the number of bytes that are allocated for blocks of\n");
+ printf("this size.\n");
+ }
+ printf("\nsize\tmalloc\tfree\tdiff\ttotal\n");
+ for (i = 0; i < 1024; i++)
+ if (m_m[i] || m_f[i])
+ printf("%ld\t%d\t%d\t%d\t%ld\n", (long)i * M_ISIZE, m_m[i], m_f[i],
+ m_m[i] - m_f[i], (long)i * M_ISIZE * (m_m[i] - m_f[i]));
+
+ if (m_m[i] || m_f[i])
+ printf("big\t%d\t%d\t%d\n", m_m[i], m_f[i], m_m[i] - m_f[i]);
+
+ if (ops['v']) {
+ printf("\nThe list of memory blocks. For each block the following\n");
+ printf("information is shown:\n\n");
+ printf("num\tthe number of this block\n");
+ printf("tnum\tlike num but counted separatedly for used and free\n");
+ printf("\tblocks\n");
+ printf("addr\tthe address of this block\n");
+ printf("len\tthe length of the block\n");
+ printf("state\tthe state of this block, this can be:\n");
+ printf("\t used\tthis block is used for one big block\n");
+ printf("\t free\tthis block is free\n");
+ printf("\t small\tthis block is used for an array of small blocks\n");
+ printf("cum\tthe accumulated sizes of the blocks, counted\n");
+ printf("\tseparatedly for used and free blocks\n");
+ printf("\nFor blocks holding small blocks the number of free\n");
+ printf("blocks, the number of used blocks and the size of the\n");
+ printf("blocks is shown. For otherwise used blocks the first few\n");
+ printf("bytes are shown as an ASCII dump.\n");
+ }
+ printf("\nblock list:\nnum\ttnum\taddr\tlen\tstate\tcum\n");
+ for (m = m_l, mf = m_free, ii = fi = ui = 1; ((char *)m) < m_high;
+ m = (struct m_hdr *)(((char *)m) + M_ISIZE + m->len), ii++) {
+ for (j = 0, ms = NULL; j < M_NSMALL && !ms; j++)
+ for (ms = m_small[j]; ms; ms = ms->next)
+ if (ms == m)
+ break;
+
+ if (m == mf)
+ buf[0] = '\0';
+ else if (m == ms)
+ sprintf(buf, "%ld %ld %ld", M_SNUM - ms->used, ms->used,
+ (m->len - sizeof(struct m_hdr)) / M_SNUM + 1);
+
+ else {
+ for (i = 0, b = buf, c = (char *)&m->next; i < 20 && i < m->len;
+ i++, c++)
+ *b++ = (*c >= ' ' && *c < 127) ? *c : '.';
+ *b = '\0';
+ }
+
+ printf("%d\t%d\t%ld\t%ld\t%s\t%ld\t%s\n", ii,
+ (m == mf) ? fi++ : ui++,
+ (long)m, m->len,
+ (m == mf) ? "free" : ((m == ms) ? "small" : "used"),
+ (m == mf) ? (f += m->len) : (u += m->len),
+ buf);
+
+ if (m == mf)
+ mf = mf->next;
+ }
+
+ if (ops['v']) {
+ printf("\nHere is some information about the small blocks used.\n");
+ printf("For each size the arrays with the number of free and the\n");
+ printf("number of used blocks are shown.\n");
+ }
+ printf("\nsmall blocks:\nsize\tblocks (free/used)\n");
+
+ for (i = 0; i < M_NSMALL; i++)
+ if (m_small[i]) {
+ printf("%ld\t", (long)i * M_ISIZE);
+
+ for (ii = 0, m = m_small[i]; m; m = m->next) {
+ printf("(%ld/%ld) ", M_SNUM - m->used, m->used);
+ if (!((++ii) & 7))
+ printf("\n\t");
+ }
+ putchar('\n');
+ }
+ if (ops['v']) {
+ printf("\n\nBelow is some information about the allocation\n");
+ printf("behaviour of the zsh heaps. First the number of times\n");
+ printf("pushheap(), popheap(), and freeheap() were called.\n");
+ }
+ printf("\nzsh heaps:\n\n");
+
+ printf("push %d\tpop %d\tfree %d\n\n", h_push, h_pop, h_free);
+
+ if (ops['v']) {
+ printf("\nThe next list shows for several sizes the number of times\n");
+ printf("memory of this size were taken from heaps.\n\n");
+ }
+ printf("size\tmalloc\ttotal\n");
+ for (i = 0; i < 1024; i++)
+ if (h_m[i])
+ printf("%ld\t%d\t%ld\n", (long)i * H_ISIZE, h_m[i],
+ (long)i * H_ISIZE * h_m[i]);
+ if (h_m[1024])
+ printf("big\t%d\n", h_m[1024]);
+
+ return 0;
+}
+
+#endif
+
+#else /* not ZSH_MEM */
+
+/**/
+void
+zfree(void *p, int sz)
+{
+ if (p)
+ free(p);
+}
+
+/**/
+void
+zsfree(char *p)
+{
+ if (p)
+ free(p);
+}
+
+#endif
diff --git a/Src/mkbltnmlst.sh b/Src/mkbltnmlst.sh
new file mode 100644
index 000000000..4a90ecd20
--- /dev/null
+++ b/Src/mkbltnmlst.sh
@@ -0,0 +1,60 @@
+#! /bin/sh
+#
+# mkbltnmlst.sh: generate boot code for linked-in modules
+#
+# Written by Andrew Main
+#
+srcdir=${srcdir-`echo $0|sed 's%/[^/][^/]*$%%'`}
+test "x$srcdir" = "x$0" && srcdir=.
+test "x$srcdir" = "x" && srcdir=.
+MODBINS=${MODBINS-modules-bltin}
+XMODCF=${XMODCF-$srcdir/xmods.conf}
+
+bin_mods=" zsh "`sed 's/^/ /;s/$/ /' $MODBINS`
+x_mods=`cat $XMODCF`
+. ./modules.index
+
+trap "rm -f $1; exit 1" 1 2 15
+
+exec > $1
+
+echo "#ifdef DYNAMIC"
+for x_mod in $x_mods; do
+ case $bin_mods in
+ *" $x_mod "*) ;;
+ *) echo "/* non-linked-in known module \`$x_mod' */"
+ eval "loc=\$loc_$x_mod"
+ unset moddeps autobins
+ . $srcdir/../$loc/${x_mod}.mdd
+ for bin in $autobins; do
+ echo " add_autobin(\"$bin\", \"$x_mod\");"
+ done
+ for dep in $moddeps; do
+ case $bin_mods in
+ *" $dep "*)
+ echo " /* depends on \`$dep' */" ;;
+ *) echo " add_dep(\"$x_mod\", \"$dep\");" ;;
+ esac
+ done ;;
+ esac
+done
+echo "#endif /* DYNAMIC */"
+echo
+done_mods=" "
+for bin_mod in $bin_mods; do
+ echo "/* linked-in module \`$bin_mod' */"
+ eval "loc=\$loc_$bin_mod"
+ unset moddeps
+ . $srcdir/../$loc/${bin_mod}.mdd
+ for dep in $moddeps; do
+ case $done_mods in
+ *" $dep "*)
+ echo " /* depends on \`$dep' */" ;;
+ *) echo >&2 "ERROR: linked-in module \`$bin_mod' depends on \`$dep'"
+ rm -f $1
+ exit 1 ;;
+ esac
+ done
+ echo " mod.nam = \"$bin_mod\"; boot_$bin_mod(&mod);"
+ done_mods="$done_mods$bin_mod "
+done
diff --git a/Src/mkmakemod.sh b/Src/mkmakemod.sh
new file mode 100644
index 000000000..b088929fa
--- /dev/null
+++ b/Src/mkmakemod.sh
@@ -0,0 +1,315 @@
+#!/bin/sh
+#
+# mkmakemod.sh: generate Makefile.in files for module building
+#
+# Options:
+# -m = file is already generated; only build the second stage
+# -i = do not build second stage
+#
+# Args:
+# $1 = subdirectory to look in, relative to $top_srcdir
+# $2 = final output filename, within the $1 directory
+#
+# This script must be run from the top-level build directory, and $top_srcdir
+# must be set correctly in the environment.
+#
+# This looks in $1, and uses all the *.mdd files there. Each .mdd file
+# defines one module. The .mdd file is actually a shell script, which will
+# be sourced. It may define the following shell variables:
+#
+# moddeps modules on which this module depends (default none)
+# nozshdep non-empty indicates no dependence on the `zsh' pseudo-module
+# alwayslink if non-empty, always link the module into the executable
+# autobins builtins defined by the module, for autoloading
+# objects .o files making up this module (*must* be defined)
+# proto .pro files for this module (default generated from $objects)
+# headers extra headers for this module (default none)
+# hdrdeps extra headers on which the .mdh depends (default none)
+# otherincs extra headers that are included indirectly (default none)
+#
+# The .mdd file may also include a Makefile.in fragment between lines
+# `:<<\Make' and `Make' -- this will be copied into Makemod.in.
+#
+# The resulting Makemod.in knows how to build each module that is defined.
+# For each module in also knows how to build a .mdh file. Each source file
+# should #include the .mdh file for the module it is a part of. The .mdh
+# file #includes the .mdh files for any module dependencies, then each of
+# $headers, and then each of $proto (for global declarations). It will
+# be recreated if any of the dependency .mdh files changes, or if any of
+# $headers or $hdrdeps changes. When anything depends on it, all of $proto
+# and $otherincs will be made up to date, but the .mdh file won't actually
+# be rebuilt if those files change.
+#
+# The order of sections of the output file is thus:
+# simple generated macros
+# macros generated from *.mdd
+# included Makemod.in.in
+# rules generated from *.mdd
+# The order dependencies are basically that the generated macros are required
+# in Makemod.in.in, but some of the macros that it creates are needed in the
+# later rules.
+#
+
+# sed script to normalise a pathname
+sed_normalise='
+ s,^,/,
+ s,$,/,
+ :1
+ s,/\./,/,
+ t1
+ :2
+ s,/[^/.][^/]*/\.\./,/,
+ s,/\.[^/.][^/]*/\.\./,/,
+ s,/\.\.[^/][^/]*/\.\./,/,
+ t2
+ s,^/$,.,
+ s,^/,,
+ s,\(.\)/$,\1,
+'
+
+# decide which stages to process
+first_stage=true
+second_stage=true
+if test ."$1" = .-m; then
+ shift
+ first_stage=false
+elif test ."$1" = .-i; then
+ shift
+ second_stage=false
+fi
+
+top_srcdir=`echo $top_srcdir | sed "$sed_normalise"`
+the_subdir=$1
+the_makefile=$2
+
+if $first_stage; then
+
+ trap "rm -f $the_subdir/${the_makefile}.in" 1 2 15
+ echo "creating $the_subdir/${the_makefile}.in"
+ exec 3>&1 >$the_subdir/${the_makefile}.in
+ echo "##### ${the_makefile}.in generated automatically by mkmakemod.sh"
+ echo "##### DO NOT EDIT!"
+ echo
+ echo "##### ===== DEFINITIONS ===== #####"
+ echo
+ echo "makefile = ${the_makefile}"
+ echo "dir_top = "`echo $the_subdir | sed 's,[^/][^/]*,..,g'`
+ echo "subdir = $the_subdir"
+ echo
+
+ . Src/modules.index
+ bin_mods=" zsh "`sed 's/^/ /;s/$/ /' Src/modules-bltin`
+ if grep '%@D@%D%' config.status >/dev/null; then
+ is_dynamic=true
+ else
+ is_dynamic=false
+ fi
+
+ here_modules=
+ all_subdirs=
+ all_modobjs=
+ all_modules=
+ all_mdds=
+ all_mdhs=
+ all_proto=
+ lastsub=//
+ for module in $module_list; do
+ eval "loc=\$loc_$module"
+ case $loc in
+ $the_subdir)
+ here_modules="$here_modules $module"
+ build=$is_dynamic
+ case $is_dynamic@$bin_mods in
+ *" $module "*)
+ build=true
+ all_modobjs="$all_modobjs modobjs.${module}" ;;
+ true@*)
+ all_modules="$all_modules ${module}.\$(DL_EXT)" ;;
+ esac
+ all_mdds="$all_mdds ${module}.mdd"
+ $build && all_mdhs="$all_mdhs ${module}.mdh"
+ $build && all_proto="$all_proto proto.${module}"
+ ;;
+ $lastsub | $lastsub/*) ;;
+ $the_subdir/*)
+ all_subdirs="$all_subdirs $loc"
+ lastsub=$loc
+ ;;
+ esac
+ done
+ all_subdirs=`echo "$all_subdirs" | sed "s' $the_subdir/' 'g"`
+ echo "MODOBJS =$all_modobjs"
+ echo "MODULES =$all_modules"
+ echo "MDDS =$all_mdds"
+ echo "MDHS =$all_mdhs"
+ echo "PROTOS =$all_proto"
+ echo "SUBDIRS =$all_subdirs"
+ echo
+
+ echo "##### ===== INCLUDING Makemod.in.in ===== #####"
+ echo
+ cat $top_srcdir/Src/Makemod.in.in
+ echo
+
+ case $the_subdir in
+ Src) modobjs_sed= ;;
+ Src/*) modobjs_sed="| sed 's\" \" "`echo $the_subdir | sed 's,^Src/,,'`"/\"g' " ;;
+ *) modobjs_sed="| sed 's\" \" ../$the_subdir/\"g' " ;;
+ esac
+
+ other_mdhs=
+ remote_mdhs=
+ for module in $here_modules; do
+
+ unset moddeps nozshdep alwayslink
+ unset autobins
+ unset objects proto headers hdrdeps otherincs
+ . $top_srcdir/$the_subdir/${module}.mdd
+ test -n "${moddeps+set}" || moddeps=
+ test -n "$nozshdep" || moddeps="$moddeps zsh"
+ test -n "${proto+set}" ||
+ proto=`echo $objects '' | sed 's,\.o ,.pro ,g'`
+
+ dobjects=`echo $objects '' | sed 's,\.o ,..o ,g'`
+ modhdeps=
+ for dep in $moddeps; do
+ eval "loc=\$loc_$dep"
+ case $the_subdir in
+ $loc)
+ mdh="${dep}.mdh"
+ ;;
+ $loc/*)
+ mdh="\$(dir_top)/$loc/${dep}.mdh"
+ case "$other_mdhs " in
+ *" $mdh "*) ;;
+ *) other_mdhs="$other_mdhs $mdh" ;;
+ esac
+ ;;
+ *)
+ mdh="\$(dir_top)/$loc/${dep}.mdh"
+ case "$remote_mdhs " in
+ *" $mdh "*) ;;
+ *) remote_mdhs="$remote_mdhs $mdh" ;;
+ esac
+ ;;
+ esac
+ modhdeps="$modhdeps $mdh"
+ done
+
+ echo "##### ===== DEPENDENCIES GENERATED FROM ${module}.mdd ===== #####"
+ echo
+ echo "MODOBJS_${module} = $objects"
+ echo "MODDOBJS_${module} = $dobjects"
+ echo "PROTO_${module} = $proto"
+ echo "INCS_${module} = \$(PROTO_${module}) $otherincs"
+ echo
+ echo "proto.${module}: \$(PROTO_${module})"
+ echo "\$(PROTO_${module}): \$(PROTODEPS)"
+ echo
+ echo "modobjs.${module}: \$(MODOBJS_${module})"
+ echo " echo '' \$(MODOBJS_${module}) $modobjs_sed>> \$(dir_src)/stamp-modobjs.tmp"
+ echo
+ if test -z "$alwayslink"; then
+ echo "${module}.\$(DL_EXT): \$(MODDOBJS_${module})"
+ echo ' rm -f $@'
+ echo " \$(DLLINK) \$(MODDOBJS_${module}) \$(LIBS)"
+ echo
+ fi
+ echo "${module}.mdhi: ${module}.mdhs \$(INCS_${module})"
+ echo " @test -f \$@ || echo 'do not delete this file' > \$@"
+ echo
+ echo "${module}.mdhs: ${module}.mdd"
+ echo " @\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${module}.mdh.tmp"
+ echo " @if cmp -s ${module}.mdh ${module}.mdh.tmp; then \\"
+ echo " rm -f ${module}.mdh.tmp; \\"
+ echo " echo \"\\\`${module}.mdh' is up to date.\"; \\"
+ echo " else \\"
+ echo " mv -f ${module}.mdh.tmp ${module}.mdh; \\"
+ echo " echo \"Updated \\\`${module}.mdh'.\"; \\"
+ echo " fi"
+ echo " echo 'timestamp for ${module}.mdh against ${module}.mdd' > \$@"
+ echo
+ echo "${module}.mdh: ${modhdeps} ${headers} ${hdrdeps} ${module}.mdhi"
+ echo " @\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${module}.mdh.tmp"
+ echo " @mv -f ${module}.mdh.tmp ${module}.mdh"
+ echo " @echo \"Updated \\\`${module}.mdh'.\""
+ echo
+ echo "${module}.mdh.tmp:"
+ echo " @( \\"
+ echo " echo '#ifndef have_${module}_module'; \\"
+ echo " echo '#define have_${module}_module'; \\"
+ echo " echo; \\"
+ if test -n "$moddeps"; then
+ echo " echo '/* Module dependencies */'; \\"
+ echo " for mod in $modhdeps; do \\"
+ echo " echo '# define USING_MODULE'; \\"
+ echo " echo '# include \"'\$\$mod'\"'; \\"
+ echo " done; \\"
+ echo " echo '# undef USING_MODULE'; \\"
+ echo " echo; \\"
+ fi
+ if test -n "$headers"; then
+ echo " echo '/* Extra headers for this module */'; \\"
+ echo " for hdr in $headers; do \\"
+ echo " if test -f \$\$hdr; then \\"
+ echo " echo '# include \"'\$\$hdr'\"'; \\"
+ echo " else \\"
+ echo " echo '# include \"\$(sdir)/'\$\$hdr'\"'; \\"
+ echo " fi; \\"
+ echo " done; \\"
+ echo " echo; \\"
+ fi
+ if test -n "$proto"; then
+ echo " echo '# define GLOBAL_PROTOTYPES'; \\"
+ echo " for pro in \$(PROTO_${module}); do \\"
+ echo " echo '# include \"'\$\$pro'\"'; \\"
+ echo " done; \\"
+ echo " echo '# undef GLOBAL_PROTOTYPES'; \\"
+ echo " echo; \\"
+ fi
+ echo " echo '#endif /* !have_${module}_module */'; \\"
+ echo " ) > \$@"
+ echo
+ echo "\$(MODOBJS_${module}) \$(MODDOBJS_${module}): ${module}.mdh"
+ sed -e '/^ *: *<< *\\Make *$/,/^Make$/!d' \
+ -e 's/^ *: *<< *\\Make *$//; /^Make$/d' \
+ < $top_srcdir/$the_subdir/${module}.mdd
+ echo
+
+ done
+
+ if test -n "$remote_mdhs$other_mdhs"; then
+ echo "##### ===== DEPENDENCIES FOR REMOTE MODULES ===== #####"
+ echo
+ for mdh in $remote_mdhs; do
+ echo "$mdh: FORCE"
+ echo " @cd @%@ && \$(MAKE) \$(MAKEDEFS) @%@$mdh"
+ echo
+ done | sed 's,^\(.*\)@%@\(.*\)@%@\(.*\)/\([^/]*\)$,\1\3\2\4,'
+ if test -n "$other_mdhs"; then
+ echo "${other_mdhs}:"
+ echo " false # should only happen with make -n"
+ echo
+ fi
+ fi
+
+ echo "##### End of ${the_makefile}.in"
+
+ exec >&3 3>&-
+
+fi
+
+if $second_stage; then
+
+ trap "rm -f $the_subdir/${the_makefile}" 1 2 15
+
+ # The standard config.status requires the pathname for the .in file to
+ # be relative to the top of the source tree. As we have it in the build
+ # tree, this is a problem. zsh's configure script edits config.status,
+ # adding the feature that an input filename starting with "!" has the
+ # "!" removed and is not mangled further.
+ CONFIG_FILES=$the_subdir/${the_makefile}:\!$the_subdir/${the_makefile}.in CONFIG_HEADERS= ./config.status
+
+fi
+
+exit 0
diff --git a/Src/mkmodindex.sh b/Src/mkmodindex.sh
new file mode 100644
index 000000000..b4616f638
--- /dev/null
+++ b/Src/mkmodindex.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# mkmodindex.sh: search for *.mdd files, and index the modules
+#
+# $@ = directories to search from
+#
+
+echo "# module index generated by mkmodindex.sh"
+echo
+
+module_list=' '
+while test $# -ne 0; do
+ dir=$1
+ shift
+ ( set $dir/*.mdd; test -f $1 ) || continue
+ dosubs=false
+ for mod in `echo '' $dir/*.mdd '' | sed 's, [^ ]*/, ,g;s,\.mdd , ,g'`; do
+ case `echo "$mod@ $module_list " | sed 's,^.*[^_0-9A-Za-z].*@,@@,'` in
+ @@*)
+ echo >&2 "WARNING: illegally named module \`$mod' in $dir"
+ echo >&2 " (ignoring it)"
+ ;;
+ *@*" $mod "*)
+ eval "loc=\$loc_$mod"
+ echo >&2 "WARNING: module \`$mod' (in $loc) duplicated in $dir"
+ echo >&2 " (ignoring duplicate)"
+ dosubs=true
+ ;;
+ *)
+ module_list="$module_list$mod "
+ echo "loc_$mod=$dir"
+ eval "loc_$mod=\$dir"
+ dosubs=true
+ ;;
+ esac
+ done
+ $dosubs && set `echo $dir/*/. '' | sed 's,/\. , ,g'` "$@"
+done
+
+echo
+echo $module_list | sed 's/^/module_list="/;s/$/"/'
+
+exit 0
diff --git a/Src/modentry.c b/Src/modentry.c
new file mode 100644
index 000000000..63c4b825d
--- /dev/null
+++ b/Src/modentry.c
@@ -0,0 +1,15 @@
+#include "zsh.mdh"
+
+int boot_ _((Module));
+int cleanup_ _((Module));
+int modentry _((int boot, Module m));
+
+/**/
+int
+modentry(int boot, Module m)
+{
+ if (boot)
+ return boot_(m);
+ else
+ return cleanup_(m);
+}
diff --git a/Src/module.c b/Src/module.c
new file mode 100644
index 000000000..ce5989f07
--- /dev/null
+++ b/Src/module.c
@@ -0,0 +1,651 @@
+/*
+ * module.c - deal with dynamic modules
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1996-1997 Zoltán Hidvégi
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Zoltán Hidvégi or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Zoltán Hidvégi and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Zoltán Hidvégi and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Zoltán Hidvégi and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "module.pro"
+
+/* The `zsh' module contains all the base code that can't actually be built *
+ * as a separate module. It is initialised by main(), so there's nothing *
+ * for the boot function to do. */
+
+/**/
+int
+boot_zsh(Module m)
+{
+ return 0;
+}
+
+/* addbuiltin() can be used to add a new builtin. It returns zero on *
+ * success, 1 on failure. The only possible type of failure is that *
+ * a builtin with the specified name already exists. An autoloaded *
+ * builtin can be replaced using this function. */
+
+/**/
+int
+addbuiltin(Builtin b)
+{
+ Builtin bn = (Builtin) builtintab->getnode2(builtintab, b->nam);
+ if (bn && (bn->flags & BINF_ADDED))
+ return 1;
+ if (bn)
+ builtintab->freenode(builtintab->removenode(builtintab, b->nam));
+ PERMALLOC {
+ builtintab->addnode(builtintab, b->nam, b);
+ } LASTALLOC;
+ return 0;
+}
+
+/* Add multiple builtins. binl points to a table of `size' builtin *
+ * structures. Those for which (.flags & BINF_ADDED) is false are to be *
+ * added; that flag is set if they succeed. If any fail, an error *
+ * message is printed, using nam as the leading name. Returns 1 if all *
+ * additions succeed, 2 if some succeed and some fail, and 0 if all (and *
+ * at least 1) fail. The usual usage in a boot_*() function would be *
+ * return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); */
+
+/**/
+int
+addbuiltins(char const *nam, Builtin binl, int size)
+{
+ int hads = 0, hadf = 0, n;
+
+ for(n = 0; n < size; n++) {
+ Builtin b = &binl[n];
+ if(b->flags & BINF_ADDED)
+ continue;
+ if(addbuiltin(b)) {
+ zwarnnam(nam, "name clash when adding builtin `%s'", b->nam, 0);
+ hadf = 1;
+ } else {
+ b->flags |= BINF_ADDED;
+ hads = 2;
+ }
+ }
+ return hadf ? hads : 1;
+}
+
+#ifdef DYNAMIC
+
+/* $module_path ($MODULE_PATH) */
+
+/**/
+char **module_path;
+
+/* List of modules */
+
+/**/
+LinkList modules;
+
+/* Define an autoloadable builtin. It returns 0 on success, or 1 on *
+ * failure. The only possible cause of failure is that a builtin *
+ * with the specified name already exists. */
+
+/**/
+int
+add_autobin(char *nam, char *module)
+{
+ Builtin bn = zcalloc(sizeof(*bn));
+ bn->nam = ztrdup(nam);
+ bn->optstr = ztrdup(module);
+ return addbuiltin(bn);
+}
+
+/* Remove the builtin added previously by addbuiltin(). Returns *
+ * zero on succes and -1 if there is no builtin with that name. */
+
+/**/
+int
+deletebuiltin(char *nam)
+{
+ Builtin bn;
+
+ bn = (Builtin) builtintab->removenode(builtintab, nam);
+ if (!bn)
+ return -1;
+ builtintab->freenode((HashNode)bn);
+ return 0;
+}
+
+/* Delete multiple builtins. binl points to a table of `size' builtin *
+ * structures. Those for which (.flags & BINF_ADDED) is true are to be *
+ * deleted; that flag is cleared. If any fail, an error message is *
+ * printed, using nam as the leading name. Returns 1 if all deletions *
+ * succeed, 2 if some succeed and some fail, and 0 if all (and at least *
+ * 1) fail. In normal use, from a cleanup_*() function, this return *
+ * value would be ignored -- the only cause of failure would be that a *
+ * wayward module had deleted our builtin without telling us. */
+
+/**/
+int
+deletebuiltins(char const *nam, Builtin binl, int size)
+{
+ int hads = 0, hadf = 0, n;
+
+ for(n = 0; n < size; n++) {
+ Builtin b = &binl[n];
+ if(!(b->flags & BINF_ADDED))
+ continue;
+ if(deletebuiltin(b->nam)) {
+ zwarnnam(nam, "builtin `%s' already deleted", b->nam, 0);
+ hadf = 1;
+ } else
+ hads = 2;
+ b->flags &= ~BINF_ADDED;
+ }
+ return hadf ? hads : 1;
+}
+
+#ifdef HAVE_DLFCN_H
+# include <dlfcn.h>
+#else
+# include <sys/types.h>
+# include <nlist.h>
+# include <link.h>
+#endif
+#ifndef RTLD_LAZY
+# define RTLD_LAZY 1
+#endif
+#ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+#endif
+#ifndef HAVE_DLCLOSE
+# define dlclose(X) ((X), 0)
+#endif
+
+#ifdef DLSYM_NEEDS_UNDERSCORE
+# define STR_BOOT "_boot_"
+# define STR_BOOT_S "_boot_%s"
+# define STR_CLEANUP "_cleanup_"
+# define STR_CLEANUP_S "_cleanup_%s"
+#else /* !DLSYM_NEEDS_UNDERSCORE */
+# define STR_BOOT "boot_"
+# define STR_BOOT_S "boot_%s"
+# define STR_CLEANUP "cleanup_"
+# define STR_CLEANUP_S "cleanup_%s"
+#endif /* !DLSYM_NEEDS_UNDERSCORE */
+typedef int (*Module_func) _((Module));
+
+/**/
+static void *
+try_load_module(char const *name)
+{
+ char buf[PATH_MAX + 1];
+ char **pp;
+ void *ret = NULL;
+ int l;
+
+ if (strchr(name, '/')) {
+ ret = dlopen(unmeta(name), RTLD_LAZY | RTLD_GLOBAL);
+ if (ret ||
+ unset(PATHDIRS) ||
+ (*name == '/') ||
+ (*name == '.' && name[1] == '/') ||
+ (*name == '.' && name[1] == '.' && name[2] == '/'))
+ return ret;
+ }
+
+ l = strlen(name) + 1;
+ for (pp = module_path; !ret && *pp; pp++) {
+ if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX)
+ continue;
+ sprintf(buf, "%s/%s", **pp ? *pp : ".", name);
+ ret = dlopen(unmeta(buf), RTLD_LAZY | RTLD_GLOBAL);
+ }
+
+ return ret;
+}
+
+/**/
+static void *
+do_load_module(char const *name)
+{
+ void *ret = NULL;
+ char buf[PATH_MAX + 1];
+
+ if (strlen(name) + strlen(DL_EXT) < PATH_MAX) {
+ sprintf(buf, "%s.%s", name, DL_EXT);
+ ret = try_load_module(buf);
+ }
+ if (!ret)
+ ret = try_load_module(name);
+ if (!ret) {
+ int waserr = errflag;
+ zerr("failed to load module: %s", name, 0);
+ errflag = waserr;
+ }
+ return ret;
+}
+
+/**/
+static LinkNode
+find_module(const char *name)
+{
+ Module m;
+ LinkNode node;
+
+ for (node = firstnode(modules); node; incnode(node)) {
+ m = (Module) getdata(node);
+ if (!strcmp(m->nam, name))
+ return node;
+ }
+ return NULL;
+}
+
+/**/
+static int
+init_module(Module m)
+{
+ char *s, *t;
+#ifndef DYNAMIC_NAME_CLASH_OK
+ char buf[PATH_MAX + 1];
+#endif
+ Module_func fn;
+
+ s = strrchr(m->nam, '/');
+ if (s)
+ s = dupstring(++s);
+ else
+ s = m->nam;
+ if ((t = strrchr(s, '.')))
+ *t = '\0';
+#ifdef DYNAMIC_NAME_CLASH_OK
+ fn = (Module_func) dlsym(m->handle, STR_BOOT);
+#else /* !DYNAMIC_NAME_CLASH_OK */
+ if (strlen(s) + 6 > PATH_MAX)
+ return 1;
+ sprintf(buf, STR_BOOT_S, s);
+ fn = (Module_func) dlsym(m->handle, buf);
+#endif /* !DYNAMIC_NAME_CLASH_OK */
+ if(fn)
+ return fn(m);
+ zwarnnam(m->nam, "no boot function", NULL, 0);
+ return 1;
+}
+
+/**/
+Module
+load_module(char const *name)
+{
+ Module m;
+ void *handle;
+ LinkNode node, n;
+
+ if (!(node = find_module(name))) {
+ if (!(handle = do_load_module(name)))
+ return NULL;
+ m = zcalloc(sizeof(*m));
+ m->nam = ztrdup(name);
+ m->handle = handle;
+ if (init_module(m)) {
+ dlclose(handle);
+ zsfree(m->nam);
+ zfree(m, sizeof(*m));
+ return NULL;
+ }
+ PERMALLOC {
+ addlinknode(modules, m);
+ } LASTALLOC;
+ return m;
+ }
+ m = (Module) getdata(node);
+ if (m->handle)
+ return m;
+ if (m->flags & MOD_BUSY) {
+ zerr("circular dependencies for module %s", name, 0);
+ return NULL;
+ }
+ m->flags |= MOD_BUSY;
+ for (n = firstnode(m->deps); n; incnode(n))
+ if (!load_module((char *) getdata(n))) {
+ m->flags &= ~MOD_BUSY;
+ return NULL;
+ }
+ m->flags &= ~MOD_BUSY;
+ if (!(m->handle = do_load_module(name)))
+ return NULL;
+ if (init_module(m)) {
+ dlclose(m->handle);
+ m->handle = NULL;
+ return NULL;
+ }
+ return m;
+}
+
+/**/
+static int
+cleanup_module(Module m)
+{
+ char *s, *t;
+#ifndef DYNAMIC_NAME_CLASH_OK
+ char buf[PATH_MAX + 1];
+#endif
+ Module_func fn;
+
+ s = strrchr(m->nam, '/');
+ if (s)
+ s = dupstring(++s);
+ else
+ s = m->nam;
+ if ((t = strrchr(s, '.')))
+ *t = '\0';
+#ifdef DYNAMIC_NAME_CLASH_OK
+ fn = (Module_func) dlsym(m->handle, STR_CLEANUP);
+#else /* !DYNAMIC_NAME_CLASH_OK */
+ if (strlen(s) + 9 > PATH_MAX)
+ return 1;
+ sprintf(buf, STR_CLEANUP_S, s);
+ fn = (Module_func) dlsym(m->handle, buf);
+#endif /* !DYNAMIC_NAME_CLASH_OK */
+ if(fn)
+ return fn(m);
+ zwarnnam(m->nam, "no cleanup function", NULL, 0);
+ return 1;
+}
+
+/**/
+void
+add_dep(char *name, char *from)
+{
+ LinkNode node;
+ Module m;
+
+ PERMALLOC {
+ if (!(node = find_module(name))) {
+ m = zcalloc(sizeof(*m));
+ m->nam = ztrdup(name);
+ addlinknode(modules, m);
+ } else
+ m = (Module) getdata(node);
+ if (!m->deps)
+ m->deps = newlinklist();
+ for (node = firstnode(m->deps);
+ node && strcmp((char *) getdata(node), from);
+ incnode(node));
+ if (!node)
+ addlinknode(m->deps, ztrdup(from));
+ } LASTALLOC;
+}
+
+/**/
+static void
+autoloadscan(HashNode hn, int printflags)
+{
+ Builtin bn = (Builtin) hn;
+
+ if(bn->flags & BINF_ADDED)
+ return;
+ if(printflags & PRINT_LIST) {
+ fputs("zmodload -a ", stdout);
+ if(bn->optstr[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(bn->optstr, stdout);
+ if(strcmp(bn->nam, bn->optstr)) {
+ putchar(' ');
+ quotedzputs(bn->nam, stdout);
+ }
+ } else {
+ nicezputs(bn->nam, stdout);
+ if(strcmp(bn->nam, bn->optstr)) {
+ fputs(" (", stdout);
+ nicezputs(bn->optstr, stdout);
+ putchar(')');
+ }
+ }
+ putchar('\n');
+}
+
+/**/
+int
+bin_zmodload(char *nam, char **args, char *ops, int func)
+{
+ if(ops['d'] && ops['a']) {
+ zwarnnam(nam, "-d cannot be combined with -a", NULL, 0);
+ return 1;
+ }
+ if (ops['u'] && !*args) {
+ zwarnnam(nam, "what do you want to unload?", NULL, 0);
+ return 1;
+ }
+ if(ops['d'])
+ return bin_zmodload_dep(nam, args, ops);
+ else if(ops['a'])
+ return bin_zmodload_auto(nam, args, ops);
+ else
+ return bin_zmodload_load(nam, args, ops);
+}
+
+/**/
+static int
+bin_zmodload_dep(char *nam, char **args, char *ops)
+{
+ LinkNode node;
+ Module m;
+ if(ops['u']) {
+ /* remove dependencies */
+ char *tnam = *args++;
+ node = find_module(tnam);
+ if (!node)
+ return 0;
+ m = (Module) getdata(node);
+ if(*args && m->deps) {
+ do {
+ for(node = firstnode(m->deps); node; incnode(node))
+ if(!strcmp(*args, getdata(node))) {
+ zsfree(getdata(node));
+ remnode(m->deps, node);
+ break;
+ }
+ } while(*++args);
+ if(empty(m->deps)) {
+ freelinklist(m->deps, freestr);
+ m->deps = NULL;
+ }
+ } else {
+ if (m->deps) {
+ freelinklist(m->deps, freestr);
+ m->deps = NULL;
+ }
+ }
+ if (!m->deps && !m->handle) {
+ remnode(modules, node);
+ zsfree(m->nam);
+ zfree(m, sizeof(*m));
+ }
+ return 0;
+ } else if(!args[0] || !args[1]) {
+ /* list dependencies */
+ for (node = firstnode(modules); node; incnode(node)) {
+ m = (Module) getdata(node);
+ if (m->deps && (!args[0] || !strcmp(args[0], m->nam))) {
+ LinkNode n;
+ if(ops['L']) {
+ printf("zmodload -d ");
+ if(m->nam[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(m->nam, stdout);
+ } else {
+ nicezputs(m->nam, stdout);
+ putchar(':');
+ }
+ for (n = firstnode(m->deps); n; incnode(n)) {
+ putchar(' ');
+ if(ops['L'])
+ quotedzputs((char *) getdata(n), stdout);
+ else
+ nicezputs((char *) getdata(n), stdout);
+ }
+ putchar('\n');
+ }
+ }
+ return 0;
+ } else {
+ /* add dependencies */
+ int ret = 0;
+ char *tnam = *args++;
+
+ for(; *args; args++) {
+ if(isset(RESTRICTED) && strchr(*args, '/')) {
+ zwarnnam(nam, "%s: restricted", *args, 0);
+ ret = 1;
+ } else
+ add_dep(tnam, *args);
+ }
+ return ret;
+ }
+}
+
+/**/
+static int
+bin_zmodload_auto(char *nam, char **args, char *ops)
+{
+ int ret = 0;
+ if(ops['u']) {
+ /* remove autoloaded builtins */
+ for (; *args; args++) {
+ Builtin bn = (Builtin) builtintab->getnode2(builtintab, *args);
+ if (!bn) {
+ if(!ops['i']) {
+ zwarnnam(nam, "%s: no such builtin", *args, 0);
+ ret = 1;
+ }
+ } else if (bn->flags & BINF_ADDED) {
+ zwarnnam(nam, "%s: builtin is already defined", *args, 0);
+ ret = 1;
+ } else
+ deletebuiltin(*args);
+ }
+ return ret;
+ } else if(!*args) {
+ /* list autoloaded builtins */
+ scanhashtable(builtintab, 0, 0, 0,
+ autoloadscan, ops['L'] ? PRINT_LIST : 0);
+ return 0;
+ } else {
+ /* add autoloaded builtins */
+ char *modnam;
+ modnam = *args++;
+ if(isset(RESTRICTED) && strchr(modnam, '/')) {
+ zwarnnam(nam, "%s: restricted", modnam, 0);
+ return 1;
+ }
+ do {
+ char *bnam = *args ? *args++ : modnam;
+ if (strchr(bnam, '/')) {
+ zwarnnam(nam, "%s: `/' is illegal in a builtin", bnam, 0);
+ ret = 1;
+ } else if (add_autobin(bnam, modnam) && !ops['i']) {
+ zwarnnam(nam, "failed to add builtin %s", bnam, 0);
+ ret = 1;
+ }
+ } while(*args);
+ return ret;
+ }
+}
+
+/**/
+static int
+bin_zmodload_load(char *nam, char **args, char *ops)
+{
+ LinkNode node;
+ Module m;
+ int ret = 0;
+ if(ops['u']) {
+ /* unload modules */
+ for(; *args; args++) {
+ node = find_module(*args);
+ if (node) {
+ LinkNode mn, dn;
+
+ for (mn = firstnode(modules); mn; incnode(mn)) {
+ m = (Module) getdata(mn);
+ if (m->deps && m->handle)
+ for (dn = firstnode(m->deps); dn; incnode(dn))
+ if (!strcmp((char *) getdata(dn), *args)) {
+ zwarnnam(nam, "module %s is in use by another module and cannot be unloaded", *args, 0);
+ ret = 1;
+ goto cont;
+ }
+ }
+
+ m = (Module) getdata(node);
+ if (m->handle && cleanup_module(m))
+ ret = 1;
+ else {
+ if (m->handle)
+ dlclose(m->handle);
+ m->handle = NULL;
+ if(!m->deps) {
+ remnode(modules, node);
+ zsfree(m->nam);
+ zfree(m, sizeof(*m));
+ }
+ }
+ } else if (!ops['i']) {
+ zwarnnam(nam, "no such module %s", *args, 0);
+ ret = 1;
+ }
+ cont: ;
+ }
+ return ret;
+ } else if(!*args) {
+ /* list modules */
+ for (node = firstnode(modules); node; incnode(node)) {
+ m = (Module) getdata(node);
+ if (m->handle) {
+ if(ops['L']) {
+ printf("zmodload ");
+ if(m->nam[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(m->nam, stdout);
+ } else
+ nicezputs(m->nam, stdout);
+ putchar('\n');
+ }
+ }
+ return 0;
+ } else {
+ /* load modules */
+ for (; *args; args++) {
+ node = find_module(*args);
+ if (node && ((Module) getdata(node))->handle) {
+ if (!ops['i']) {
+ zwarnnam(nam, "module %s already loaded.", *args, 0);
+ ret = 1;
+ }
+ } else if (isset(RESTRICTED) && strchr(*args, '/')) {
+ zwarnnam(nam, "%s: restricted", *args, 0);
+ ret = 1;
+ } else if (!load_module(*args))
+ ret = 1;
+ }
+ return ret;
+ }
+}
+
+#endif /* DYNAMIC */
diff --git a/Src/options.c b/Src/options.c
new file mode 100644
index 000000000..745a6627c
--- /dev/null
+++ b/Src/options.c
@@ -0,0 +1,663 @@
+/*
+ * options.c - shell options
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "options.pro"
+
+/* current emulation (used to decide which set of option letters is used) */
+
+/**/
+int emulation;
+
+/* the options; e.g. if opts[SHGLOB] != 0, SH_GLOB is turned on */
+
+/**/
+char opts[OPT_SIZE];
+
+/* Option name hash table */
+
+/**/
+HashTable optiontab;
+
+typedef struct optname *Optname;
+
+struct optname {
+ HashNode next; /* next in hash chain */
+ char *nam; /* hash data */
+ int flags;
+ int optno; /* option number */
+};
+
+/* The canonical option name table */
+
+#define OPT_CSH EMULATE_CSH
+#define OPT_KSH EMULATE_KSH
+#define OPT_SH EMULATE_SH
+#define OPT_ZSH EMULATE_ZSH
+
+#define OPT_ALL (OPT_CSH|OPT_KSH|OPT_SH|OPT_ZSH)
+#define OPT_BOURNE (OPT_KSH|OPT_SH)
+#define OPT_BSHELL (OPT_KSH|OPT_SH|OPT_ZSH)
+#define OPT_NONBOURNE (OPT_ALL & ~OPT_BOURNE)
+#define OPT_NONZSH (OPT_ALL & ~OPT_ZSH)
+
+#define OPT_EMULATE (1<<5) /* option is relevant to emulation */
+#define OPT_SPECIAL (1<<6) /* option should never be set by emulate() */
+#define OPT_ALIAS (1<<7) /* option is an alias to an other option */
+
+#define defset(X) (!!((X)->flags & emulation))
+
+static struct optname optns[] = {
+{NULL, "allexport", 0, ALLEXPORT},
+{NULL, "alwayslastprompt", OPT_ALL, ALWAYSLASTPROMPT},
+{NULL, "alwaystoend", 0, ALWAYSTOEND},
+{NULL, "appendhistory", OPT_ALL, APPENDHISTORY},
+{NULL, "autocd", 0, AUTOCD},
+{NULL, "autolist", OPT_ALL, AUTOLIST},
+{NULL, "automenu", OPT_ALL, AUTOMENU},
+{NULL, "autonamedirs", 0, AUTONAMEDIRS},
+{NULL, "autoparamkeys", OPT_ALL, AUTOPARAMKEYS},
+{NULL, "autoparamslash", OPT_ALL, AUTOPARAMSLASH},
+{NULL, "autopushd", 0, AUTOPUSHD},
+{NULL, "autoremoveslash", OPT_ALL, AUTOREMOVESLASH},
+{NULL, "autoresume", 0, AUTORESUME},
+{NULL, "badpattern", OPT_EMULATE|OPT_NONBOURNE, BADPATTERN},
+{NULL, "banghist", OPT_EMULATE|OPT_NONBOURNE, BANGHIST},
+{NULL, "bareglobqual", OPT_EMULATE|OPT_ZSH, BAREGLOBQUAL},
+{NULL, "beep", OPT_ALL, BEEP},
+{NULL, "bgnice", OPT_EMULATE|OPT_NONBOURNE, BGNICE},
+{NULL, "braceccl", 0, BRACECCL},
+{NULL, "bsdecho", OPT_EMULATE|OPT_SH, BSDECHO},
+{NULL, "cdablevars", 0, CDABLEVARS},
+{NULL, "chaselinks", 0, CHASELINKS},
+{NULL, "clobber", OPT_ALL, CLOBBER},
+{NULL, "completealiases", 0, COMPLETEALIASES},
+{NULL, "completeinword", 0, COMPLETEINWORD},
+{NULL, "correct", 0, CORRECT},
+{NULL, "correctall", 0, CORRECTALL},
+{NULL, "cshjunkiehistory", OPT_EMULATE|OPT_CSH, CSHJUNKIEHISTORY},
+{NULL, "cshjunkieloops", OPT_EMULATE|OPT_CSH, CSHJUNKIELOOPS},
+{NULL, "cshjunkiequotes", OPT_EMULATE|OPT_CSH, CSHJUNKIEQUOTES},
+{NULL, "cshnullglob", OPT_EMULATE|OPT_CSH, CSHNULLGLOB},
+{NULL, "equals", OPT_EMULATE|OPT_ZSH, EQUALS},
+{NULL, "errexit", 0, ERREXIT},
+{NULL, "exec", OPT_ALL, EXECOPT},
+{NULL, "extendedglob", 0, EXTENDEDGLOB},
+{NULL, "extendedhistory", OPT_EMULATE|OPT_CSH, EXTENDEDHISTORY},
+{NULL, "flowcontrol", OPT_ALL, FLOWCONTROL},
+{NULL, "functionargzero", OPT_EMULATE|OPT_NONBOURNE, FUNCTIONARGZERO},
+{NULL, "glob", OPT_ALL, GLOBOPT},
+{NULL, "globassign", OPT_EMULATE|OPT_CSH, GLOBASSIGN},
+{NULL, "globcomplete", 0, GLOBCOMPLETE},
+{NULL, "globdots", 0, GLOBDOTS},
+{NULL, "globsubst", OPT_EMULATE|OPT_NONZSH, GLOBSUBST},
+{NULL, "hashcmds", OPT_ALL, HASHCMDS},
+{NULL, "hashdirs", OPT_ALL, HASHDIRS},
+{NULL, "hashlistall", OPT_ALL, HASHLISTALL},
+{NULL, "histallowclobber", 0, HISTALLOWCLOBBER},
+{NULL, "histbeep", OPT_ALL, HISTBEEP},
+{NULL, "histignoredups", 0, HISTIGNOREDUPS},
+{NULL, "histignorespace", 0, HISTIGNORESPACE},
+{NULL, "histnofunctions", 0, HISTNOFUNCTIONS},
+{NULL, "histnostore", 0, HISTNOSTORE},
+{NULL, "histreduceblanks", 0, HISTREDUCEBLANKS},
+{NULL, "histverify", 0, HISTVERIFY},
+{NULL, "hup", OPT_EMULATE|OPT_ZSH, HUP},
+{NULL, "ignorebraces", OPT_EMULATE|OPT_SH, IGNOREBRACES},
+{NULL, "ignoreeof", 0, IGNOREEOF},
+{NULL, "interactive", OPT_SPECIAL, INTERACTIVE},
+{NULL, "interactivecomments", OPT_EMULATE|OPT_BOURNE, INTERACTIVECOMMENTS},
+{NULL, "ksharrays", OPT_EMULATE|OPT_BOURNE, KSHARRAYS},
+{NULL, "kshautoload", OPT_EMULATE|OPT_BOURNE, KSHAUTOLOAD},
+{NULL, "kshglob", OPT_EMULATE|OPT_KSH, KSHGLOB},
+{NULL, "kshoptionprint", OPT_EMULATE|OPT_KSH, KSHOPTIONPRINT},
+{NULL, "listambiguous", OPT_ALL, LISTAMBIGUOUS},
+{NULL, "listbeep", OPT_ALL, LISTBEEP},
+{NULL, "listtypes", OPT_ALL, LISTTYPES},
+{NULL, "localoptions", OPT_EMULATE|OPT_KSH, LOCALOPTIONS},
+{NULL, "login", OPT_SPECIAL, LOGINSHELL},
+{NULL, "longlistjobs", 0, LONGLISTJOBS},
+{NULL, "magicequalsubst", 0, MAGICEQUALSUBST},
+{NULL, "mailwarning", 0, MAILWARNING},
+{NULL, "markdirs", 0, MARKDIRS},
+{NULL, "menucomplete", 0, MENUCOMPLETE},
+{NULL, "monitor", OPT_SPECIAL, MONITOR},
+{NULL, "multios", OPT_EMULATE|OPT_ZSH, MULTIOS},
+{NULL, "nomatch", OPT_EMULATE|OPT_NONBOURNE, NOMATCH},
+{NULL, "notify", OPT_ZSH, NOTIFY},
+{NULL, "nullglob", OPT_EMULATE, NULLGLOB},
+{NULL, "numericglobsort", 0, NUMERICGLOBSORT},
+{NULL, "overstrike", 0, OVERSTRIKE},
+{NULL, "pathdirs", 0, PATHDIRS},
+{NULL, "posixbuiltins", OPT_EMULATE|OPT_BOURNE, POSIXBUILTINS},
+{NULL, "printeightbit", 0, PRINTEIGHTBIT},
+{NULL, "printexitvalue", 0, PRINTEXITVALUE},
+{NULL, "privileged", OPT_SPECIAL, PRIVILEGED},
+{NULL, "promptbang", OPT_EMULATE|OPT_KSH, PROMPTBANG},
+{NULL, "promptcr", OPT_ALL, PROMPTCR},
+{NULL, "promptpercent", OPT_EMULATE|OPT_NONBOURNE, PROMPTPERCENT},
+{NULL, "promptsubst", OPT_EMULATE|OPT_KSH, PROMPTSUBST},
+{NULL, "pushdignoredups", 0, PUSHDIGNOREDUPS},
+{NULL, "pushdminus", 0, PUSHDMINUS},
+{NULL, "pushdsilent", 0, PUSHDSILENT},
+{NULL, "pushdtohome", 0, PUSHDTOHOME},
+{NULL, "rcexpandparam", 0, RCEXPANDPARAM},
+{NULL, "rcquotes", 0, RCQUOTES},
+{NULL, "rcs", OPT_ALL, RCS},
+{NULL, "recexact", 0, RECEXACT},
+{NULL, "restricted", OPT_SPECIAL, RESTRICTED},
+{NULL, "rmstarsilent", OPT_BOURNE, RMSTARSILENT},
+{NULL, "rmstarwait", 0, RMSTARWAIT},
+{NULL, "shfileexpansion", OPT_EMULATE|OPT_BOURNE, SHFILEEXPANSION},
+{NULL, "shglob", OPT_EMULATE|OPT_BOURNE, SHGLOB},
+{NULL, "shinstdin", OPT_SPECIAL, SHINSTDIN},
+{NULL, "shoptionletters", OPT_EMULATE|OPT_BOURNE, SHOPTIONLETTERS},
+{NULL, "shortloops", OPT_ALL, SHORTLOOPS},
+{NULL, "shwordsplit", OPT_EMULATE|OPT_BOURNE, SHWORDSPLIT},
+{NULL, "singlecommand", OPT_SPECIAL, SINGLECOMMAND},
+{NULL, "singlelinezle", OPT_KSH, SINGLELINEZLE},
+{NULL, "sunkeyboardhack", 0, SUNKEYBOARDHACK},
+{NULL, "unset", OPT_EMULATE|OPT_BSHELL, UNSET},
+{NULL, "verbose", 0, VERBOSE},
+{NULL, "xtrace", 0, XTRACE},
+{NULL, "zle", OPT_SPECIAL, USEZLE},
+{NULL, "braceexpand", OPT_ALIAS, /* ksh/bash */ -IGNOREBRACES},
+{NULL, "dotglob", OPT_ALIAS, /* bash */ GLOBDOTS},
+{NULL, "hashall", OPT_ALIAS, /* bash */ HASHCMDS},
+{NULL, "histappend", OPT_ALIAS, /* bash */ APPENDHISTORY},
+{NULL, "histexpand", OPT_ALIAS, /* bash */ BANGHIST},
+{NULL, "log", OPT_ALIAS, /* ksh */ -HISTNOFUNCTIONS},
+{NULL, "mailwarn", OPT_ALIAS, /* bash */ MAILWARNING},
+{NULL, "onecmd", OPT_ALIAS, /* bash */ SINGLECOMMAND},
+{NULL, "physical", OPT_ALIAS, /* ksh/bash */ CHASELINKS},
+{NULL, "promptvars", OPT_ALIAS, /* bash */ PROMPTSUBST},
+{NULL, "stdin", OPT_ALIAS, /* ksh */ SHINSTDIN},
+{NULL, "trackall", OPT_ALIAS, /* ksh */ HASHCMDS},
+{NULL, NULL, 0, 0}
+};
+
+/* Option letters */
+
+#define optletters (isset(SHOPTIONLETTERS) ? kshletters : zshletters)
+
+#define FIRST_OPT '0'
+#define LAST_OPT 'y'
+
+static short zshletters[LAST_OPT - FIRST_OPT + 1] = {
+ /* 0 */ CORRECT,
+ /* 1 */ PRINTEXITVALUE,
+ /* 2 */ -BADPATTERN,
+ /* 3 */ -NOMATCH,
+ /* 4 */ GLOBDOTS,
+ /* 5 */ NOTIFY,
+ /* 6 */ BGNICE,
+ /* 7 */ IGNOREEOF,
+ /* 8 */ MARKDIRS,
+ /* 9 */ AUTOLIST,
+ /* : */ 0,
+ /* ; */ 0,
+ /* < */ 0,
+ /* = */ 0,
+ /* > */ 0,
+ /* ? */ 0,
+ /* @ */ 0,
+ /* A */ 0,
+ /* B */ -BEEP,
+ /* C */ -CLOBBER,
+ /* D */ PUSHDTOHOME,
+ /* E */ PUSHDSILENT,
+ /* F */ -GLOBOPT,
+ /* G */ NULLGLOB,
+ /* H */ RMSTARSILENT,
+ /* I */ IGNOREBRACES,
+ /* J */ AUTOCD,
+ /* K */ -BANGHIST,
+ /* L */ SUNKEYBOARDHACK,
+ /* M */ SINGLELINEZLE,
+ /* N */ AUTOPUSHD,
+ /* O */ CORRECTALL,
+ /* P */ RCEXPANDPARAM,
+ /* Q */ PATHDIRS,
+ /* R */ LONGLISTJOBS,
+ /* S */ RECEXACT,
+ /* T */ CDABLEVARS,
+ /* U */ MAILWARNING,
+ /* V */ -PROMPTCR,
+ /* W */ AUTORESUME,
+ /* X */ LISTTYPES,
+ /* Y */ MENUCOMPLETE,
+ /* Z */ USEZLE,
+ /* [ */ 0,
+ /* \ */ 0,
+ /* ] */ 0,
+ /* ^ */ 0,
+ /* _ */ 0,
+ /* ` */ 0,
+ /* a */ ALLEXPORT,
+ /* b */ 0,
+ /* c */ 0,
+ /* d */ 0,
+ /* e */ ERREXIT,
+ /* f */ -RCS,
+ /* g */ HISTIGNORESPACE,
+ /* h */ HISTIGNOREDUPS,
+ /* i */ INTERACTIVE,
+ /* j */ 0,
+ /* k */ INTERACTIVECOMMENTS,
+ /* l */ LOGINSHELL,
+ /* m */ MONITOR,
+ /* n */ -EXECOPT,
+ /* o */ 0,
+ /* p */ PRIVILEGED,
+ /* q */ 0,
+ /* r */ RESTRICTED,
+ /* s */ SHINSTDIN,
+ /* t */ SINGLECOMMAND,
+ /* u */ -UNSET,
+ /* v */ VERBOSE,
+ /* w */ CHASELINKS,
+ /* x */ XTRACE,
+ /* y */ SHWORDSPLIT,
+};
+
+static short kshletters[LAST_OPT - FIRST_OPT + 1] = {
+ /* 0 */ 0,
+ /* 1 */ 0,
+ /* 2 */ 0,
+ /* 3 */ 0,
+ /* 4 */ 0,
+ /* 5 */ 0,
+ /* 6 */ 0,
+ /* 7 */ 0,
+ /* 8 */ 0,
+ /* 9 */ 0,
+ /* : */ 0,
+ /* ; */ 0,
+ /* < */ 0,
+ /* = */ 0,
+ /* > */ 0,
+ /* ? */ 0,
+ /* @ */ 0,
+ /* A */ 0,
+ /* B */ 0,
+ /* C */ -CLOBBER,
+ /* D */ 0,
+ /* E */ 0,
+ /* F */ 0,
+ /* G */ 0,
+ /* H */ 0,
+ /* I */ 0,
+ /* J */ 0,
+ /* K */ 0,
+ /* L */ 0,
+ /* M */ 0,
+ /* N */ 0,
+ /* O */ 0,
+ /* P */ 0,
+ /* Q */ 0,
+ /* R */ 0,
+ /* S */ 0,
+ /* T */ 0,
+ /* U */ 0,
+ /* V */ 0,
+ /* W */ 0,
+ /* X */ MARKDIRS,
+ /* Y */ 0,
+ /* Z */ 0,
+ /* [ */ 0,
+ /* \ */ 0,
+ /* ] */ 0,
+ /* ^ */ 0,
+ /* _ */ 0,
+ /* ` */ 0,
+ /* a */ ALLEXPORT,
+ /* b */ NOTIFY,
+ /* c */ 0,
+ /* d */ 0,
+ /* e */ ERREXIT,
+ /* f */ -GLOBOPT,
+ /* g */ 0,
+ /* h */ 0,
+ /* i */ INTERACTIVE,
+ /* j */ 0,
+ /* k */ 0,
+ /* l */ LOGINSHELL,
+ /* m */ MONITOR,
+ /* n */ -EXECOPT,
+ /* o */ 0,
+ /* p */ PRIVILEGED,
+ /* q */ 0,
+ /* r */ RESTRICTED,
+ /* s */ SHINSTDIN,
+ /* t */ SINGLECOMMAND,
+ /* u */ -UNSET,
+ /* v */ VERBOSE,
+ /* w */ 0,
+ /* x */ XTRACE,
+ /* y */ 0,
+};
+
+/* Initialisation of the option name hash table */
+
+/**/
+static void
+printoptionnode(HashNode hn, int set)
+{
+ Optname on = (Optname) hn;
+ int optno = on->optno;
+
+ if (optno < 0)
+ optno = -optno;
+ if (isset(KSHOPTIONPRINT)) {
+ if (defset(on))
+ printf("no%-20s%s\n", on->nam, isset(optno) ? "off" : "on");
+ else
+ printf("%-22s%s\n", on->nam, isset(optno) ? "on" : "off");
+ } else if (set == (isset(optno) ^ defset(on))) {
+ if (set ^ isset(optno))
+ fputs("no", stdout);
+ puts(on->nam);
+ }
+}
+
+/**/
+void
+createoptiontable(void)
+{
+ Optname on;
+
+ optiontab = newhashtable(101, "optiontab", NULL);
+
+ optiontab->hash = hasher;
+ optiontab->emptytable = NULL;
+ optiontab->filltable = NULL;
+ optiontab->addnode = addhashnode;
+ optiontab->getnode = gethashnode;
+ optiontab->getnode2 = gethashnode2;
+ optiontab->removenode = NULL;
+ optiontab->disablenode = disablehashnode;
+ optiontab->enablenode = enablehashnode;
+ optiontab->freenode = NULL;
+ optiontab->printnode = printoptionnode;
+
+ for (on = optns; on->nam; on++)
+ optiontab->addnode(optiontab, on->nam, on);
+}
+
+/* Setting of default options */
+
+/**/
+static void
+setemulate(HashNode hn, int fully)
+{
+ Optname on = (Optname) hn;
+
+ /* Set options: each non-special option is set according to the *
+ * current emulation mode if either it is considered relevant *
+ * to emulation or we are doing a full emulation (as indicated *
+ * by the `fully' parameter). */
+ if (!(on->flags & OPT_ALIAS) &&
+ ((fully && !(on->flags & OPT_SPECIAL)) ||
+ (on->flags & OPT_EMULATE)))
+ opts[on->optno] = defset(on);
+}
+
+/**/
+void
+emulate(const char *zsh_name, int fully)
+{
+ char ch = *zsh_name;
+
+ if (ch == 'r')
+ ch = zsh_name[1];
+
+ /* Work out the new emulation mode */
+ if (ch == 'c')
+ emulation = EMULATE_CSH;
+ else if (ch == 'k')
+ emulation = EMULATE_KSH;
+ else if (ch == 's' || ch == 'b')
+ emulation = EMULATE_SH;
+ else
+ emulation = EMULATE_ZSH;
+
+ scanhashtable(optiontab, 0, 0, 0, setemulate, fully);
+}
+
+/* setopt, unsetopt */
+
+/**/
+static void
+setoption(HashNode hn, int value)
+{
+ dosetopt(((Optname) hn)->optno, value, 0);
+}
+
+/**/
+int
+bin_setopt(char *nam, char **args, char *ops, int isun)
+{
+ int action, optno, match = 0;
+
+ /* With no arguments or options, display options. */
+ if (!*args) {
+ scanhashtable(optiontab, 1, 0, OPT_ALIAS, optiontab->printnode, !isun);
+ return 0;
+ }
+
+ /* loop through command line options (begins with "-" or "+") */
+ while (*args && (**args == '-' || **args == '+')) {
+ action = (**args == '-') ^ isun;
+ if(!args[0][1])
+ *args = "--";
+ while (*++*args) {
+ if(**args == Meta)
+ *++*args ^= 32;
+ /* The pseudo-option `--' signifies the end of options. */
+ if (**args == '-') {
+ args++;
+ goto doneoptions;
+ } else if (**args == 'o') {
+ if (!*++*args)
+ args++;
+ if (!*args) {
+ zwarnnam(nam, "string expected after -o", NULL, 0);
+ inittyptab();
+ return 1;
+ }
+ if(!(optno = optlookup(*args)))
+ zwarnnam(nam, "no such option: %s", *args, 0);
+ else if(dosetopt(optno, action, 0))
+ zwarnnam(nam, "can't change option: %s", *args, 0);
+ break;
+ } else if(**args == 'm') {
+ match = 1;
+ } else {
+ if (!(optno = optlookupc(**args)))
+ zwarnnam(nam, "bad option: -%c", NULL, **args);
+ else if(dosetopt(optno, action, 0))
+ zwarnnam(nam, "can't change option: -%c", NULL, **args);
+ }
+ }
+ args++;
+ }
+ doneoptions:
+
+ if (!match) {
+ /* Not globbing the arguments -- arguments are simply option names. */
+ while (*args) {
+ if(!(optno = optlookup(*args++)))
+ zwarnnam(nam, "no such option: %s", args[-1], 0);
+ else if(dosetopt(optno, !isun, 0))
+ zwarnnam(nam, "can't change option: %s", args[-1], 0);
+ }
+ } else {
+ /* Globbing option (-m) set. */
+ while (*args) {
+ Comp com;
+
+ /* Expand the current arg. */
+ tokenize(*args);
+ if (!(com = parsereg(*args))) {
+ untokenize(*args);
+ zwarnnam(nam, "bad pattern: %s", *args, 0);
+ continue;
+ }
+ /* Loop over expansions. */
+ scanmatchtable(optiontab, com, 0, OPT_ALIAS, setoption, !isun);
+ args++;
+ }
+ }
+ inittyptab();
+ return 0;
+}
+
+/* Identify an option name */
+
+/**/
+int
+optlookup(char const *name)
+{
+ char *s, *t;
+ Optname n;
+
+ s = t = dupstring(name);
+
+ /* exorcise underscores, and change to lowercase */
+ while (*t)
+ if (*t == '_')
+ chuck(t);
+ else {
+ *t = tulower(*t);
+ t++;
+ }
+
+ /* look up name in the table */
+ if (s[0] == 'n' && s[1] == 'o' &&
+ (n = (Optname) optiontab->getnode(optiontab, s + 2))) {
+ return -n->optno;
+ } else if ((n = (Optname) optiontab->getnode(optiontab, s)))
+ return n->optno;
+ else
+ return OPT_INVALID;
+}
+
+/* Identify an option letter */
+
+/**/
+int
+optlookupc(char c)
+{
+ if(c < FIRST_OPT || c > LAST_OPT)
+ return 0;
+
+ return optletters[c - FIRST_OPT];
+}
+
+/**/
+static void
+restrictparam(char *nam)
+{
+ Param pm = (Param) paramtab->getnode(paramtab, nam);
+
+ if (pm) {
+ pm->flags |= PM_SPECIAL | PM_RESTRICTED;
+ return;
+ }
+ createparam(nam, PM_SCALAR | PM_UNSET | PM_SPECIAL | PM_RESTRICTED);
+}
+
+/* list of restricted parameters which are not otherwise special */
+static char *rparams[] = {
+ "SHELL", "HISTFILE", "LD_LIBRARY_PATH", "LD_AOUT_LIBRARY_PATH",
+ "LD_PRELOAD", "LD_AOUT_PRELOAD", NULL
+};
+
+/* Set or unset an option, as a result of user request. The option *
+ * number may be negative, indicating that the sense is reversed *
+ * from the usual meaning of the option. */
+
+/**/
+int
+dosetopt(int optno, int value, int force)
+{
+ if(!optno)
+ return -1;
+ if(optno < 0) {
+ optno = -optno;
+ value = !value;
+ }
+ if (optno == RESTRICTED) {
+ if (isset(RESTRICTED))
+ return value ? 0 : -1;
+ if (value) {
+ char **s;
+
+ for (s = rparams; *s; s++)
+ restrictparam(*s);
+ }
+ } else if(!force && (optno == INTERACTIVE || optno == SHINSTDIN ||
+ optno == SINGLECOMMAND)) {
+ /* it is not permitted to change the value of these options */
+ return -1;
+ } else if(!force && optno == USEZLE && value) {
+ /* we require a terminal in order to use ZLE */
+ if(!interact || SHTTY == -1 || !shout)
+ return -1;
+ } else if(optno == PRIVILEGED && !value) {
+ /* unsetting PRIVILEGED causes the shell to make itself unprivileged */
+#ifdef HAVE_SETUID
+ setuid(getuid());
+ setgid(getgid());
+#endif /* HAVE_SETUID */
+ }
+ opts[optno] = value;
+ if (optno == BANGHIST || optno == SHINSTDIN)
+ inittyptab();
+ return 0;
+}
+
+/* Function to get value for special parameter `-' */
+
+/**/
+char *
+dashgetfn(Param pm)
+{
+ static char buf[LAST_OPT - FIRST_OPT + 2];
+ char *val = buf;
+ int i;
+
+ for(i = 0; i <= LAST_OPT - FIRST_OPT; i++) {
+ int optno = optletters[i];
+ if(optno && ((optno > 0) ? isset(optno) : unset(-optno)))
+ *val++ = FIRST_OPT + i;
+ }
+ *val = '\0';
+ return buf;
+}
diff --git a/Src/params.c b/Src/params.c
new file mode 100644
index 000000000..4f7846820
--- /dev/null
+++ b/Src/params.c
@@ -0,0 +1,2191 @@
+/*
+ * params.c - parameters
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "params.pro"
+
+#include "version.h"
+
+/* what level of localness we are at */
+
+/**/
+int locallevel;
+
+/* Variables holding values of special parameters */
+
+/**/
+char **pparams, /* $argv */
+ **cdpath, /* $cdpath */
+ **fignore, /* $fignore */
+ **fpath, /* $fpath */
+ **mailpath, /* $mailpath */
+ **manpath, /* $manpath */
+ **path, /* $path */
+ **psvar, /* $psvar */
+ **watch; /* $watch */
+
+/**/
+char *argzero, /* $0 */
+ *underscore, /* $_ */
+ *home, /* $HOME */
+ *hostnam, /* $HOST */
+ *ifs, /* $IFS */
+ *nullcmd, /* $NULLCMD */
+ *oldpwd, /* $OLDPWD */
+ *zoptarg, /* $OPTARG */
+ *postedit, /* $POSTEDIT */
+ *prompt, /* $PROMPT */
+ *prompt2, /* $PROMPT2 */
+ *prompt3, /* $PROMPT3 */
+ *prompt4, /* $PROMPT4 */
+ *pwd, /* $PWD */
+ *readnullcmd, /* $READNULLCMD */
+ *rprompt, /* $RPROMPT */
+ *sprompt, /* $SPROMPT */
+ *term, /* $TERM */
+ *ttystrname, /* $TTY */
+ *wordchars, /* $WORDCHARS */
+ *zsh_name; /* $ZSH_NAME */
+
+/**/
+long lastval, /* $? */
+ mypid, /* $$ */
+ lastpid, /* $! */
+ columns, /* $COLUMNS */
+ lineno, /* $LINENO */
+ lines, /* $LINES */
+ zoptind, /* $OPTIND */
+ ppid, /* $PPID */
+ shlvl; /* $SHLVL */
+
+/* $histchars */
+
+/**/
+unsigned char bangchar, hatchar, hashchar;
+
+/* $SECONDS = time(NULL) - shtimer.tv_sec */
+
+/**/
+struct timeval shtimer;
+
+/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
+
+/**/
+int termflags;
+
+/* Nodes for special parameters for parameter hash table */
+
+static
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+struct param
+#else
+# define BR(X) X
+struct iparam {
+ struct hashnode *next;
+ char *nam; /* hash data */
+ int flags; /* PM_* flags (defined in zsh.h) */
+ void *value;
+ void (*func1) _((void)); /* set func */
+ char *(*func2) _((void)); /* get func */
+ void (*unsetfn) _((Param, int)); /* unset func */
+ int ct; /* output base or field width */
+ char *env; /* location in environment, if exported */
+ char *ename; /* name of corresponding environment var */
+ Param old; /* old struct for use with local */
+ int level; /* if (old != NULL), level of localness */
+}
+#endif
+special_params[] ={
+#define SFN(X) BR(((void (*)_((Param, char *)))(X)))
+#define GFN(X) BR(((char *(*)_((Param)))(X)))
+#define IPDEF1(A,B,C,D) {NULL,A,PM_INTEGER|PM_SPECIAL|D,BR(NULL),SFN(C),GFN(B),stdunsetfn,10,NULL,NULL,NULL,0}
+IPDEF1("#", poundgetfn, nullsetfn, PM_READONLY),
+IPDEF1("ERRNO", errnogetfn, nullsetfn, PM_READONLY),
+IPDEF1("GID", gidgetfn, gidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EGID", egidgetfn, egidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("HISTSIZE", histsizegetfn, histsizesetfn, PM_RESTRICTED),
+IPDEF1("RANDOM", randomgetfn, randomsetfn, 0),
+IPDEF1("SECONDS", secondsgetfn, secondssetfn, 0),
+IPDEF1("UID", uidgetfn, uidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EUID", euidgetfn, euidsetfn, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("TTYIDLE", ttyidlegetfn, nullsetfn, PM_READONLY),
+
+#define IPDEF2(A,B,C,D) {NULL,A,PM_SCALAR|PM_SPECIAL|D,BR(NULL),SFN(C),GFN(B),stdunsetfn,0,NULL,NULL,NULL,0}
+IPDEF2("USERNAME", usernamegetfn, usernamesetfn, PM_DONTIMPORT|PM_RESTRICTED),
+IPDEF2("-", dashgetfn, nullsetfn, PM_READONLY),
+IPDEF2("histchars", histcharsgetfn, histcharssetfn, PM_DONTIMPORT),
+IPDEF2("HOME", homegetfn, homesetfn, 0),
+IPDEF2("TERM", termgetfn, termsetfn, 0),
+IPDEF2("WORDCHARS", wordcharsgetfn, wordcharssetfn, 0),
+IPDEF2("IFS", ifsgetfn, ifssetfn, PM_DONTIMPORT),
+IPDEF2("_", underscoregetfn, nullsetfn, PM_READONLY),
+
+#ifdef LC_ALL
+# define LCIPDEF(name) IPDEF2(name, strgetfn, lcsetfn, PM_UNSET)
+IPDEF2("LANG", strgetfn, langsetfn, PM_UNSET),
+IPDEF2("LC_ALL", strgetfn, lc_allsetfn, PM_UNSET),
+# ifdef LC_COLLATE
+LCIPDEF("LC_COLLATE"),
+# endif
+# ifdef LC_CTYPE
+LCIPDEF("LC_CTYPE"),
+# endif
+# ifdef LC_MESSAGES
+LCIPDEF("LC_MESSAGES"),
+# endif
+# ifdef LC_TIME
+LCIPDEF("LC_TIME"),
+# endif
+#endif
+
+#define IPDEF4(A,B) {NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL,BR((void *)B),SFN(nullsetfn),GFN(intvargetfn),stdunsetfn,10,NULL,NULL,NULL,0}
+IPDEF4("!", &lastpid),
+IPDEF4("$", &mypid),
+IPDEF4("?", &lastval),
+IPDEF4("LINENO", &lineno),
+IPDEF4("PPID", &ppid),
+
+#define IPDEF5(A,B,F) {NULL,A,PM_INTEGER|PM_SPECIAL,BR((void *)B),SFN(F),GFN(intvargetfn),stdunsetfn,10,NULL,NULL,NULL,0}
+IPDEF5("COLUMNS", &columns, zlevarsetfn),
+IPDEF5("LINES", &lines, zlevarsetfn),
+IPDEF5("OPTIND", &zoptind, intvarsetfn),
+IPDEF5("SHLVL", &shlvl, intvarsetfn),
+
+#define IPDEF7(A,B) {NULL,A,PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(strvarsetfn),GFN(strvargetfn),stdunsetfn,0,NULL,NULL,NULL,0}
+IPDEF7("OPTARG", &zoptarg),
+IPDEF7("NULLCMD", &nullcmd),
+IPDEF7("POSTEDIT", &postedit),
+IPDEF7("READNULLCMD", &readnullcmd),
+IPDEF7("RPROMPT", &rprompt),
+IPDEF7("PS1", &prompt),
+IPDEF7("PS2", &prompt2),
+IPDEF7("PS3", &prompt3),
+IPDEF7("PS4", &prompt4),
+IPDEF7("RPS1", &rprompt),
+IPDEF7("SPROMPT", &sprompt),
+IPDEF7("0", &argzero),
+
+#define IPDEF8(A,B,C,D) {NULL,A,D|PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(colonarrsetfn),GFN(colonarrgetfn),stdunsetfn,0,NULL,C,NULL,0}
+IPDEF8("CDPATH", &cdpath, "cdpath", 0),
+IPDEF8("FIGNORE", &fignore, "fignore", 0),
+IPDEF8("FPATH", &fpath, "fpath", 0),
+IPDEF8("MAILPATH", &mailpath, "mailpath", 0),
+IPDEF8("WATCH", &watch, "watch", 0),
+IPDEF8("PATH", &path, "path", PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, "psvar", 0),
+
+#ifdef DYNAMIC
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED),
+#endif
+
+#define IPDEF9F(A,B,C,D) {NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT,BR((void *)B),SFN(arrvarsetfn),GFN(arrvargetfn),stdunsetfn,0,NULL,C,NULL,0}
+#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
+IPDEF9("*", &pparams, NULL),
+IPDEF9("@", &pparams, NULL),
+{NULL, NULL},
+
+/* The following parameters are not avaible in sh/ksh compatibility *
+ * mode. All of these has sh compatible equivalents. */
+IPDEF1("ARGC", poundgetfn, nullsetfn, PM_READONLY),
+IPDEF2("HISTCHARS", histcharsgetfn, histcharssetfn, PM_DONTIMPORT),
+IPDEF4("status", &lastval),
+IPDEF7("prompt", &prompt),
+IPDEF7("PROMPT", &prompt),
+IPDEF7("PROMPT2", &prompt2),
+IPDEF7("PROMPT3", &prompt3),
+IPDEF7("PROMPT4", &prompt4),
+IPDEF8("MANPATH", &manpath, "manpath", 0),
+IPDEF9("argv", &pparams, NULL),
+IPDEF9("fignore", &fignore, "FIGNORE"),
+IPDEF9("cdpath", &cdpath, "CDPATH"),
+IPDEF9("fpath", &fpath, "FPATH"),
+IPDEF9("mailpath", &mailpath, "MAILPATH"),
+IPDEF9("manpath", &manpath, "MANPATH"),
+IPDEF9("psvar", &psvar, "PSVAR"),
+IPDEF9("watch", &watch, "WATCH"),
+
+#ifdef DYNAMIC
+IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
+#endif
+IPDEF9F("path", &path, "PATH", PM_RESTRICTED),
+
+{NULL, NULL}
+};
+#undef BR
+
+static Param argvparam;
+
+/* hash table containing the parameters */
+
+/**/
+HashTable paramtab;
+
+/* Set up parameter hash table. This will add predefined *
+ * parameter entries as well as setting up parameter table *
+ * entries for environment variables we inherit. */
+
+/**/
+void
+createparamtable(void)
+{
+ Param ip, pm;
+ char **new_environ, **envp, **envp2, **sigptr, **t;
+ char buf[50], *str, *iname;
+ int num_env;
+
+ paramtab = newhashtable(151, "paramtab", NULL);
+
+ paramtab->hash = hasher;
+ paramtab->emptytable = NULL;
+ paramtab->filltable = NULL;
+ paramtab->addnode = addhashnode;
+ paramtab->getnode = gethashnode2;
+ paramtab->getnode2 = gethashnode2;
+ paramtab->removenode = removehashnode;
+ paramtab->disablenode = NULL;
+ paramtab->enablenode = NULL;
+ paramtab->freenode = freeparamnode;
+ paramtab->printnode = printparamnode;
+
+ /* Add the special parameters to the hash table */
+ for (ip = special_params; ip->nam; ip++)
+ paramtab->addnode(paramtab, ztrdup(ip->nam), ip);
+ if (emulation != EMULATE_SH && emulation != EMULATE_KSH)
+ while ((++ip)->nam)
+ paramtab->addnode(paramtab, ztrdup(ip->nam), ip);
+
+ argvparam = (Param) paramtab->getnode(paramtab, "*");
+
+ noerrs = 1;
+
+ HEAPALLOC {
+ /* Add the standard non-special parameters which have to *
+ * be initialized before we copy the environment variables. *
+ * We don't want to override whatever values the users has *
+ * given them in the environment. */
+ setiparam("MAILCHECK", 60);
+ setiparam("LOGCHECK", 60);
+ setiparam("KEYTIMEOUT", 40);
+ setiparam("LISTMAX", 100);
+#ifdef HAVE_SELECT
+ setiparam("BAUD", getbaudrate(&shttyinfo)); /* get the output baudrate */
+#endif
+ setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT));
+ setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX));
+ setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT));
+ setsparam("WATCHFMT", ztrdup(default_watchfmt));
+ setsparam("HOST", ztrdup(hostnam));
+ setsparam("LOGNAME", ztrdup((str = getlogin()) && *str ? str : cached_username));
+
+ /* Copy the environment variables we are inheriting to dynamic *
+ * memory, so we can do mallocs and frees on it. */
+ num_env = arrlen(environ);
+ new_environ = (char **) zalloc(sizeof(char *) * (num_env + 1));
+ *new_environ = NULL;
+
+ /* Now incorporate environment variables we are inheriting *
+ * into the parameter hash table. */
+ for (envp = new_environ, envp2 = environ; *envp2; envp2++) {
+ for (str = *envp2; *str && *str != '='; str++);
+ if (*str == '=') {
+ iname = NULL;
+ *str = '\0';
+ if (!idigit(**envp2) && isident(*envp2) && !strchr(*envp2, '[')) {
+ iname = *envp2;
+ if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
+ !(pm->flags & PM_DONTIMPORT)) &&
+ (pm = setsparam(iname, metafy(str + 1, -1, META_DUP))) &&
+ !(pm->flags & PM_EXPORTED)) {
+ *str = '=';
+ pm->flags |= PM_EXPORTED;
+ pm->env = *envp++ = ztrdup(*envp2);
+ *envp = NULL;
+ if (pm->flags & PM_SPECIAL)
+ pm->env = replenv(pm->env, getsparam(pm->nam));
+ }
+ }
+ *str = '=';
+ }
+ }
+ environ = new_environ;
+
+ pm = (Param) paramtab->getnode(paramtab, "HOME");
+ if (!(pm->flags & PM_EXPORTED)) {
+ pm->flags |= PM_EXPORTED;
+ pm->env = addenv("HOME", home);
+ }
+ pm = (Param) paramtab->getnode(paramtab, "LOGNAME");
+ if (!(pm->flags & PM_EXPORTED)) {
+ pm->flags |= PM_EXPORTED;
+ pm->env = addenv("LOGNAME", pm->u.str);
+ }
+ pm = (Param) paramtab->getnode(paramtab, "SHLVL");
+ if (!(pm->flags & PM_EXPORTED))
+ pm->flags |= PM_EXPORTED;
+ sprintf(buf, "%d", (int)++shlvl);
+ pm->env = addenv("SHLVL", buf);
+
+ /* Add the standard non-special parameters */
+ set_pwd_env();
+ setsparam("MACHTYPE", ztrdup(MACHTYPE));
+ setsparam("OSTYPE", ztrdup(OSTYPE));
+ setsparam("TTY", ztrdup(ttystrname));
+ setsparam("VENDOR", ztrdup(VENDOR));
+ setsparam("ZSH_NAME", ztrdup(zsh_name));
+ setsparam("ZSH_VERSION", ztrdup(ZSH_VERSION));
+ setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
+ for (t = sigs; (*sigptr++ = ztrdup(*t++)); );
+ } LASTALLOC;
+
+ noerrs = 0;
+}
+
+/* Create a parameter, so that it can be assigned to. Returns NULL if the *
+ * parameter already exists or can't be created, otherwise returns the *
+ * parameter node. If a parameter of the same name exists in an outer *
+ * scope, it is hidden by a newly created parameter. An already existing *
+ * parameter node at the current level may be `created' and returned *
+ * provided it is unset and not special. If the parameter can't be *
+ * created because it already exists, the PM_UNSET flag is cleared. */
+
+/**/
+Param
+createparam(char *name, int flags)
+{
+ Param pm, oldpm;
+
+ if (name != nulstring) {
+ oldpm = (Param) paramtab->getnode(paramtab, name);
+
+ if (oldpm && oldpm->level == locallevel) {
+ if (!(oldpm->flags & PM_UNSET) || (oldpm->flags & PM_SPECIAL)) {
+ oldpm->flags &= ~PM_UNSET;
+ return NULL;
+ }
+ if ((oldpm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", name, 0);
+ return NULL;
+ }
+
+ pm = oldpm;
+ pm->ct = 0;
+ oldpm = pm->old;
+ } else {
+ pm = (Param) zcalloc(sizeof *pm);
+ if ((pm->old = oldpm)) {
+ /* needed to avoid freeing oldpm */
+ paramtab->removenode(paramtab, name);
+ }
+ paramtab->addnode(paramtab, ztrdup(name), pm);
+ }
+
+ if (isset(ALLEXPORT) && !oldpm)
+ flags |= PM_EXPORTED;
+ } else
+ pm = (Param) alloc(sizeof *pm);
+ pm->flags = flags;
+
+ if(!(pm->flags & PM_SPECIAL)) {
+ switch (PM_TYPE(flags)) {
+ case PM_SCALAR:
+ pm->sets.cfn = strsetfn;
+ pm->gets.cfn = strgetfn;
+ break;
+ case PM_INTEGER:
+ pm->sets.ifn = intsetfn;
+ pm->gets.ifn = intgetfn;
+ break;
+ case PM_ARRAY:
+ pm->sets.afn = arrsetfn;
+ pm->gets.afn = arrgetfn;
+ break;
+ default:
+ DPUTS(1, "BUG: tried to create param node without valid flag");
+ break;
+ }
+ pm->unsetfn = stdunsetfn;
+ }
+ return pm;
+}
+
+/* Return 1 if the string s is a valid identifier, else return 0. */
+
+/**/
+int
+isident(char *s)
+{
+ char *ss;
+ int ne;
+
+ ne = noeval; /* save the current value of noeval */
+ if (!*s) /* empty string is definitely not valid */
+ return 0;
+
+ /* find the first character in `s' not in the iident type table */
+ for (ss = s; *ss; ss++)
+ if (!iident(*ss))
+ break;
+
+ /* If this exhaust `s' or the next two characters *
+ * are [(, then it is a valid identifier. */
+ if (!*ss || (*ss == '[' && ss[1] == '('))
+ return 1;
+
+ /* Else if the next character is not [, then it is *
+ * definitely not a valid identifier. */
+ if (*ss != '[')
+ return 0;
+ noeval = 1;
+ (void)mathevalarg(++ss, &ss);
+ if (*ss == ',')
+ (void)mathevalarg(++ss, &ss);
+ noeval = ne; /* restore the value of noeval */
+ if (*ss != ']' || ss[1])
+ return 0;
+ return 1;
+}
+
+static char **garr;
+
+/**/
+static long
+getarg(char **str, int *inv, Value v, int a2, long *w)
+{
+ int num = 1, word = 0, rev = 0, ind = 0, down = 0, l, i;
+ char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt;
+ long r = 0;
+ Comp c;
+
+ /* first parse any subscription flags */
+ if (*s == '(' || *s == Inpar) {
+ int escapes = 0;
+ int waste;
+ for (s++; *s != ')' && *s != Outpar && s != *str; s++) {
+ switch (*s) {
+ case 'r':
+ rev = 1;
+ down = ind = 0;
+ break;
+ case 'R':
+ rev = down = 1;
+ ind = 0;
+ break;
+ case 'i':
+ rev = ind = 1;
+ down = 0;
+ break;
+ case 'I':
+ rev = ind = down = 1;
+ break;
+ case 'w':
+ /* If the parameter is a scalar, then make subscription *
+ * work on a per-word basis instead of characters. */
+ word = 1;
+ break;
+ case 'f':
+ word = 1;
+ sep = "\n";
+ break;
+ case 'e':
+ /* obsolate compatibility flag without any real effect */
+ break;
+ case 'n':
+ t = get_strarg(++s);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ num = mathevalarg(s + 1, &d);
+ if (!num)
+ num = 1;
+ *t = sav;
+ s = t;
+ break;
+ case 'p':
+ escapes = 1;
+ break;
+ case 's':
+ /* This gives the string that separates words *
+ * (for use with the `w' flag. */
+ t = get_strarg(++s);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ sep = escapes ? getkeystring(s + 1, &waste, 1, &waste) :
+ dupstring(s + 1);
+ *t = sav;
+ s = t;
+ break;
+ default:
+ flagerr:
+ num = 1;
+ word = rev = ind = down = 0;
+ sep = NULL;
+ s = *str - 1;
+ }
+ }
+ if (s != *str)
+ s++;
+ }
+ if (num < 0) {
+ down = !down;
+ num = -num;
+ }
+ *inv = ind;
+
+ for (t=s, i=0; *t && ((*t != ']' && *t != Outbrack && *t != ',') || i); t++)
+ if (*t == '[' || *t == Inbrack)
+ i++;
+ else if (*t == ']' || *t == Outbrack)
+ i--;
+
+ if (!*t)
+ return 0;
+ s = dupstrpfx(s, t - s);
+ *str = tt = t;
+ if (parsestr(s))
+ return 0;
+ singsub(&s);
+
+ if (!rev) {
+ if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0))
+ r++;
+ if (word && !v->isarr) {
+ s = t = getstrvalue(v);
+ i = wordcount(s, sep, 0);
+ if (r < 0)
+ r += i + 1;
+ if (r < 1)
+ r = 1;
+ if (r > i)
+ r = i;
+ if (!s || !*s)
+ return 0;
+ while ((d = findword(&s, sep)) && --r);
+ if (!d)
+ return 0;
+
+ if (!a2 && *tt != ',')
+ *w = (long)(s - t) - 1;
+
+ return (a2 ? s : d + 1) - t;
+ } else if (!v->isarr && !word) {
+ s = getstrvalue(v);
+ if (r > 0) {
+ for (t = s + r - 1; *s && s < t;)
+ if (*s++ == Meta)
+ s++, t++, r++;
+ } else {
+ r += ztrlen(s);
+ for (t = s + r; *s && s < t; r--)
+ if (*s++ == Meta)
+ t++, r++;
+ r -= strlen(s);
+ }
+ }
+ } else {
+ if (!v->isarr && !word) {
+ l = strlen(s);
+ if (a2) {
+ if (!l || *s != '*') {
+ d = (char *) ncalloc(l + 2);
+ *d = '*';
+ strcpy(d + 1, s);
+ s = d;
+ }
+ } else {
+ if (!l || s[l - 1] != '*') {
+ d = (char *) ncalloc(l + 2);
+ strcpy(d, s);
+ strcat(d, "*");
+ s = d;
+ }
+ }
+ }
+ tokenize(s);
+
+ if ((c = parsereg(s))) {
+ if (v->isarr) {
+ ta = getarrvalue(v);
+ if (!ta || !*ta)
+ return 0;
+ if (down)
+ for (r = -1, p = ta + arrlen(ta) - 1; p >= ta; r--, p--) {
+ if (domatch(*p, c, 0) && !--num)
+ return r;
+ } else
+ for (r = 1, p = ta; *p; r++, p++)
+ if (domatch(*p, c, 0) && !--num)
+ return r;
+ } else if (word) {
+ ta = sepsplit(d = s = getstrvalue(v), sep, 1);
+ if (down) {
+ for (p = ta + (r = arrlen(ta)) - 1; p >= ta; p--, r--)
+ if (domatch(*p, c, 0) && !--num)
+ break;
+ if (p < ta)
+ return 0;
+ } else {
+ for (r = 1, p = ta; *p; r++, p++)
+ if (domatch(*p, c, 0) && !--num)
+ break;
+ if (!*p)
+ return 0;
+ }
+ if (a2)
+ r++;
+ for (i = 0; (t = findword(&d, sep)) && *t; i++)
+ if (!--r) {
+ r = (long)(t - s + (a2 ? -1 : 1));
+ if (!a2 && *tt != ',')
+ *w = r + strlen(ta[i]) - 2;
+ return r;
+ }
+ return a2 ? -1 : 0;
+ } else {
+ d = getstrvalue(v);
+ if (!d || !*d)
+ return 0;
+ if (a2) {
+ if (down)
+ for (r = -2, t = d + strlen(d) - 1; t >= d; r--, t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(d, c, 0) && !--num) {
+ *t = sav;
+ return r;
+ }
+ *t = sav;
+ } else
+ for (r = 0, t = d; *t; r++, t++) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(d, c, 0) && !--num) {
+ *t = sav;
+ return r;
+ }
+ *t = sav;
+ }
+ } else {
+ if (down)
+ for (r = -1, t = d + strlen(d) - 1; t >= d; r--, t--) {
+ if (domatch(t, c, 0) && !--num)
+ return r;
+ } else
+ for (r = 1, t = d; *t; r++, t++)
+ if (domatch(t, c, 0) && !--num)
+ return r;
+ }
+ return 0;
+ }
+ }
+ }
+ return r;
+}
+
+/**/
+int
+getindex(char **pptr, Value v)
+{
+ int a, b, inv = 0;
+ char *s = *pptr, *tbrack;
+
+ *s++ = '[';
+ for (tbrack = s; *tbrack && *tbrack != ']' && *tbrack != Outbrack; tbrack++)
+ if (itok(*tbrack))
+ *tbrack = ztokens[*tbrack - Pound];
+ if (*tbrack == Outbrack)
+ *tbrack = ']';
+ if ((s[0] == '*' || s[0] == '@') && s[1] == ']') {
+ if (v->isarr)
+ v->isarr = (s[0] == '*') ? 1 : -1;
+ v->a = 0;
+ v->b = -1;
+ s += 2;
+ } else {
+ long we = 0, dummy;
+
+ a = getarg(&s, &inv, v, 0, &we);
+
+ if (inv) {
+ if (!v->isarr && a != 0) {
+ char *t, *p;
+ t = getstrvalue(v);
+ if (a > 0) {
+ for (p = t + a - 1; p-- > t; )
+ if (*p == Meta)
+ a--;
+ } else
+ a = -ztrlen(t + a + strlen(t));
+ }
+ if (a > 0 && isset(KSHARRAYS))
+ a--;
+ v->inv = 1;
+ v->isarr = 0;
+ v->a = v->b = a;
+ if (*s == ',') {
+ zerr("invalid subscript", NULL, 0);
+ while (*s != ']' && *s != Outbrack)
+ s++;
+ *pptr = s;
+ return 1;
+ }
+ if (*s == ']' || *s == Outbrack)
+ s++;
+ } else {
+ if (a > 0)
+ a--;
+ if (*s == ',') {
+ s++;
+ b = getarg(&s, &inv, v, 1, &dummy);
+ if (b > 0)
+ b--;
+ } else {
+ b = we ? we : a;
+ }
+ if (*s == ']' || *s == Outbrack) {
+ s++;
+ if (v->isarr && a == b)
+ v->isarr = 0;
+ v->a = a;
+ v->b = b;
+ } else
+ s = *pptr;
+ }
+ }
+ *pptr = s;
+ return 0;
+}
+
+
+/**/
+Value
+getvalue(char **pptr, int bracks)
+{
+ char *s, *t;
+ char sav;
+ Value v;
+ int ppar = 0;
+
+ s = t = *pptr;
+ garr = NULL;
+
+ if (idigit(*s))
+ if (bracks >= 0)
+ ppar = zstrtol(s, &s, 10);
+ else
+ ppar = *s++ - '0';
+ else if (iident(*s))
+ while (iident(*s))
+ s++;
+ else if (*s == Quest)
+ *s++ = '?';
+ else if (*s == Pound)
+ *s++ = '#';
+ else if (*s == String)
+ *s++ = '$';
+ else if (*s == Qstring)
+ *s++ = '$';
+ else if (*s == Star)
+ *s++ = '*';
+ else if (*s == '#' || *s == '-' || *s == '?' || *s == '$' ||
+ *s == '_' || *s == '!' || *s == '@' || *s == '*')
+ s++;
+ else
+ return NULL;
+
+ if ((sav = *s))
+ *s = '\0';
+ if (ppar) {
+ v = (Value) hcalloc(sizeof *v);
+ v->pm = argvparam;
+ v->inv = 0;
+ v->a = v->b = ppar - 1;
+ if (sav)
+ *s = sav;
+ } else {
+ Param pm;
+ int isvarat;
+
+ isvarat = !strcmp(t, "@");
+ pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
+ if (sav)
+ *s = sav;
+ *pptr = s;
+ if (!pm || (pm->flags & PM_UNSET))
+ return NULL;
+ v = (Value) hcalloc(sizeof *v);
+ if (PM_TYPE(pm->flags) == PM_ARRAY)
+ v->isarr = isvarat ? -1 : 1;
+ v->pm = pm;
+ v->inv = 0;
+ v->a = 0;
+ v->b = -1;
+ if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
+ if (getindex(&s, v)) {
+ *pptr = s;
+ return v;
+ }
+ } else if (v->isarr && iident(*t) && isset(KSHARRAYS))
+ v->b = 0, v->isarr = 0;
+ }
+ if (!bracks && *s)
+ return NULL;
+ *pptr = s;
+ if (v->a > MAX_ARRLEN ||
+ v->a < -MAX_ARRLEN) {
+ zerr("subscript to %s: %d", (v->a < 0) ? "small" : "big", v->a);
+ return NULL;
+ }
+ if (v->b > MAX_ARRLEN ||
+ v->b < -MAX_ARRLEN) {
+ zerr("subscript to %s: %d", (v->b < 0) ? "small" : "big", v->b);
+ return NULL;
+ }
+ return v;
+}
+
+/**/
+char *
+getstrvalue(Value v)
+{
+ char *s, **ss;
+ static char buf[(sizeof(long) * 8) + 4];
+
+ if (!v)
+ return hcalloc(1);
+ HEAPALLOC {
+ if (v->inv) {
+ sprintf(buf, "%d", v->a);
+ s = dupstring(buf);
+ LASTALLOC_RETURN s;
+ }
+
+ switch(PM_TYPE(v->pm->flags)) {
+ case PM_ARRAY:
+ if (v->isarr)
+ s = sepjoin(v->pm->gets.afn(v->pm), NULL);
+ else {
+ ss = v->pm->gets.afn(v->pm);
+ if (v->a < 0)
+ v->a += arrlen(ss);
+ s = (v->a >= arrlen(ss) || v->a < 0) ? (char *) hcalloc(1) : ss[v->a];
+ }
+ LASTALLOC_RETURN s;
+ case PM_INTEGER:
+ convbase(s = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
+ break;
+ case PM_SCALAR:
+ s = v->pm->gets.cfn(v->pm);
+ break;
+ default:
+ s = NULL;
+ DPUTS(1, "BUG: param node without valid type");
+ break;
+ }
+
+ if (v->a == 0 && v->b == -1)
+ LASTALLOC_RETURN s;
+ if (v->a < 0)
+ v->a += strlen(s);
+ if (v->b < 0)
+ v->b += strlen(s);
+ s = (v->a > (int)strlen(s)) ? dupstring("") : dupstring(s + v->a);
+ if (v->b < v->a)
+ s[0] = '\0';
+ else if (v->b - v->a < (int)strlen(s))
+ s[v->b - v->a + 1 + (s[v->b - v->a] == Meta)] = '\0';
+ } LASTALLOC;
+ return s;
+}
+
+static char *nular[] = {"", NULL};
+
+/**/
+char **
+getarrvalue(Value v)
+{
+ char **s;
+
+ if (!v)
+ return arrdup(nular);
+ if (v->inv) {
+ char buf[DIGBUFSIZE];
+
+ s = arrdup(nular);
+ sprintf(buf, "%d", v->a);
+ s[0] = dupstring(buf);
+ return s;
+ }
+ s = v->pm->gets.afn(v->pm);
+ if (v->a == 0 && v->b == -1)
+ return s;
+ if (v->a < 0)
+ v->a += arrlen(s);
+ if (v->b < 0)
+ v->b += arrlen(s);
+ if (v->a > arrlen(s) || v->a < 0)
+ s = arrdup(nular);
+ else
+ s = arrdup(s) + v->a;
+ if (v->b < v->a)
+ s[0] = NULL;
+ else if (v->b - v->a < arrlen(s))
+ s[v->b - v->a + 1] = NULL;
+ return s;
+}
+
+/**/
+long
+getintvalue(Value v)
+{
+ if (!v || v->isarr)
+ return 0;
+ if (v->inv)
+ return v->a;
+ if (PM_TYPE(v->pm->flags) == PM_INTEGER)
+ return v->pm->gets.ifn(v->pm);
+ return matheval(getstrvalue(v));
+}
+
+/**/
+static void
+setstrvalue(Value v, char *val)
+{
+ char buf[(sizeof(long) * 8) + 4];
+
+ if (v->pm->flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->nam, 0);
+ zsfree(val);
+ return;
+ }
+ if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->nam, 0);
+ zsfree(val);
+ return;
+ }
+ switch (PM_TYPE(v->pm->flags)) {
+ case PM_SCALAR:
+ MUSTUSEHEAP("setstrvalue");
+ if (v->a == 0 && v->b == -1) {
+ (v->pm->sets.cfn) (v->pm, val);
+ if (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z) && !v->pm->ct)
+ v->pm->ct = strlen(val);
+ } else {
+ char *z, *x;
+ int zlen;
+
+ z = dupstring((v->pm->gets.cfn) (v->pm));
+ zlen = strlen(z);
+ if (v->inv && unset(KSHARRAYS))
+ v->a--, v->b--;
+ if (v->a < 0) {
+ v->a += zlen;
+ if (v->a < 0)
+ v->a = 0;
+ }
+ if (v->a > zlen)
+ v->a = zlen;
+ if (v->b < 0)
+ v->b += zlen;
+ if (v->b > zlen - 1)
+ v->b = zlen - 1;
+ x = (char *) zalloc(v->a + strlen(val) + zlen - v->b);
+ strncpy(x, z, v->a);
+ strcpy(x + v->a, val);
+ strcat(x + v->a, z + v->b + 1);
+ (v->pm->sets.cfn) (v->pm, x);
+ zsfree(val);
+ }
+ break;
+ case PM_INTEGER:
+ if (val) {
+ (v->pm->sets.ifn) (v->pm, matheval(val));
+ zsfree(val);
+ }
+ if (!v->pm->ct && lastbase != -1)
+ v->pm->ct = lastbase;
+ break;
+ case PM_ARRAY:
+ MUSTUSEHEAP("setstrvalue");
+ {
+ char **ss = (char **) zalloc(2 * sizeof(char *));
+
+ ss[0] = val;
+ ss[1] = NULL;
+ setarrvalue(v, ss);
+ }
+ break;
+ }
+ if ((!v->pm->env && !(v->pm->flags & PM_EXPORTED) &&
+ !(isset(ALLEXPORT) && !v->pm->old)) ||
+ (v->pm->flags & PM_ARRAY) || v->pm->ename)
+ return;
+ if (PM_TYPE(v->pm->flags) == PM_INTEGER)
+ convbase(val = buf, v->pm->gets.ifn(v->pm), v->pm->ct);
+ else
+ val = v->pm->gets.cfn(v->pm);
+ if (v->pm->env)
+ v->pm->env = replenv(v->pm->env, val);
+ else {
+ v->pm->flags |= PM_EXPORTED;
+ v->pm->env = addenv(v->pm->nam, val);
+ }
+}
+
+/**/
+static void
+setintvalue(Value v, long val)
+{
+ char buf[DIGBUFSIZE];
+
+ if (v->pm->flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->nam, 0);
+ return;
+ }
+ if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->nam, 0);
+ return;
+ }
+ switch (PM_TYPE(v->pm->flags)) {
+ case PM_SCALAR:
+ case PM_ARRAY:
+ sprintf(buf, "%ld", val);
+ setstrvalue(v, ztrdup(buf));
+ break;
+ case PM_INTEGER:
+ (v->pm->sets.ifn) (v->pm, val);
+ setstrvalue(v, NULL);
+ break;
+ }
+}
+
+/**/
+static void
+setarrvalue(Value v, char **val)
+{
+ if (v->pm->flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->nam, 0);
+ freearray(val);
+ return;
+ }
+ if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->nam, 0);
+ freearray(val);
+ return;
+ }
+ if (PM_TYPE(v->pm->flags) != PM_ARRAY) {
+ freearray(val);
+ zerr("attempt to assign array value to non-array", NULL, 0);
+ return;
+ }
+ if (v->a == 0 && v->b == -1)
+ (v->pm->sets.afn) (v->pm, val);
+ else {
+ char **old, **new, **p, **q, **r;
+ int n, ll, i;
+
+ if (v->inv && unset(KSHARRAYS))
+ v->a--, v->b--;
+ q = old = v->pm->gets.afn(v->pm);
+ n = arrlen(old);
+ if (v->a < 0)
+ v->a += n;
+ if (v->b < 0)
+ v->b += n;
+ if (v->a < 0)
+ v->a = 0;
+ if (v->b < 0)
+ v->b = 0;
+
+ ll = v->a + arrlen(val);
+ if (v->b < n)
+ ll += n - v->b;
+
+ p = new = (char **) zcalloc(sizeof(char *) * (ll + 1));
+
+ for (i = 0; i < v->a; i++)
+ *p++ = i < n ? ztrdup(*q++) : ztrdup("");
+ for (r = val; *r;)
+ *p++ = ztrdup(*r++);
+ if (v->b + 1 < n)
+ for (q = old + v->b + 1; *q;)
+ *p++ = ztrdup(*q++);
+ *p = NULL;
+
+ (v->pm->sets.afn) (v->pm, new);
+ freearray(val);
+ }
+}
+
+/* Retrieve an integer parameter */
+
+/**/
+long
+getiparam(char *s)
+{
+ Value v;
+
+ if (!(v = getvalue(&s, 1)))
+ return 0;
+ return getintvalue(v);
+}
+
+/* Retrieve a scalar (string) parameter */
+
+/**/
+char *
+getsparam(char *s)
+{
+ Value v;
+
+ if (!(v = getvalue(&s, 0)))
+ return NULL;
+ return getstrvalue(v);
+}
+
+/* Retrieve an array parameter */
+
+/**/
+char **
+getaparam(char *s)
+{
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&s, 0)) &&
+ PM_TYPE(v->pm->flags) == PM_ARRAY)
+ return v->pm->gets.afn(v->pm);
+ return NULL;
+}
+
+/**/
+Param
+setsparam(char *s, char *val)
+{
+ Value v;
+ char *t = s;
+ char *ss;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s, 0);
+ zsfree(val);
+ errflag = 1;
+ return NULL;
+ }
+ if ((ss = strchr(s, '['))) {
+ *ss = '\0';
+ if (!(v = getvalue(&s, 1)))
+ createparam(t, PM_ARRAY);
+ *ss = '[';
+ v = NULL;
+ } else {
+ if (!(v = getvalue(&s, 1)))
+ createparam(t, PM_SCALAR);
+ else if (PM_TYPE(v->pm->flags) == PM_ARRAY &&
+ !(v->pm->flags & PM_SPECIAL) && unset(KSHARRAYS)) {
+ unsetparam(t);
+ createparam(t, PM_SCALAR);
+ v = NULL;
+ }
+ }
+ if (!v && !(v = getvalue(&t, 1))) {
+ zsfree(val);
+ return NULL;
+ }
+ setstrvalue(v, val);
+ return v->pm;
+}
+
+/**/
+Param
+setaparam(char *s, char **val)
+{
+ Value v;
+ char *t = s;
+ char *ss;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s, 0);
+ freearray(val);
+ errflag = 1;
+ return NULL;
+ }
+ if ((ss = strchr(s, '['))) {
+ *ss = '\0';
+ if (!(v = getvalue(&s, 1)))
+ createparam(t, PM_ARRAY);
+ *ss = '[';
+ v = NULL;
+ } else {
+ if (!(v = getvalue(&s, 1)))
+ createparam(t, PM_ARRAY);
+ else if (PM_TYPE(v->pm->flags) != PM_ARRAY &&
+ !(v->pm->flags & PM_SPECIAL)) {
+ int uniq = v->pm->flags & PM_UNIQUE;
+ unsetparam(t);
+ createparam(t, PM_ARRAY | uniq);
+ v = NULL;
+ }
+ }
+ if (!v)
+ if (!(v = getvalue(&t, 1)))
+ return NULL;
+ if (isset(KSHARRAYS) && !ss)
+ /* the whole array should be set instead of only the first element */
+ v->b = -1;
+ setarrvalue(v, val);
+ return v->pm;
+}
+
+/**/
+Param
+setiparam(char *s, long val)
+{
+ Value v;
+ char *t = s;
+ Param pm;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s, 0);
+ errflag = 1;
+ return NULL;
+ }
+ if (!(v = getvalue(&s, 1))) {
+ pm = createparam(t, PM_INTEGER);
+ DPUTS(!pm, "BUG: parameter not created");
+ pm->u.val = val;
+ return pm;
+ }
+ setintvalue(v, val);
+ return v->pm;
+}
+
+/* Unset a parameter */
+
+/**/
+void
+unsetparam(char *s)
+{
+ Param pm;
+
+ if ((pm = (Param) paramtab->getnode(paramtab, s)))
+ unsetparam_pm(pm, 0, 1);
+}
+
+/* Unset a parameter */
+
+/**/
+void
+unsetparam_pm(Param pm, int altflag, int exp)
+{
+ Param oldpm, altpm;
+
+ if ((pm->flags & PM_READONLY) && pm->level <= locallevel) {
+ zerr("read-only variable: %s", pm->nam, 0);
+ return;
+ }
+ if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", pm->nam, 0);
+ return;
+ }
+ pm->unsetfn(pm, exp);
+ if ((pm->flags & PM_EXPORTED) && pm->env) {
+ delenv(pm->env);
+ zsfree(pm->env);
+ pm->env = NULL;
+ }
+
+ /* remove it under its alternate name if necessary */
+ if (pm->ename && !altflag) {
+ altpm = (Param) paramtab->getnode(paramtab, pm->ename);
+ if (altpm)
+ unsetparam_pm(altpm, 1, exp);
+ }
+
+ /* If this was a local variable, we need to keep the old *
+ * struct so that it is resurrected at the right level. *
+ * This is partly because when an array/scalar value is set *
+ * and the parameter used to be the other sort, unsetparam() *
+ * is called. Beyond that, there is an ambiguity: should *
+ * foo() { local bar; unset bar; } make the global bar *
+ * available or not? The following makes the answer "no". */
+ if (locallevel >= pm->level)
+ return;
+
+ paramtab->removenode(paramtab, pm->nam); /* remove parameter node from table */
+
+ if (pm->old) {
+ oldpm = pm->old;
+ paramtab->addnode(paramtab, oldpm->nam, oldpm);
+ if ((PM_TYPE(oldpm->flags) == PM_SCALAR) && oldpm->sets.cfn == strsetfn)
+ adduserdir(oldpm->nam, oldpm->u.str, 0, 0);
+ }
+
+ paramtab->freenode((HashNode) pm); /* free parameter node */
+}
+
+/* Standard function to unset a parameter. This is mostly delegated to *
+ * the specific set function. */
+
+/**/
+void
+stdunsetfn(Param pm, int exp)
+{
+ switch (PM_TYPE(pm->flags)) {
+ case PM_SCALAR: pm->sets.cfn(pm, NULL); break;
+ case PM_ARRAY: pm->sets.afn(pm, NULL); break;
+ }
+ pm->flags |= PM_UNSET;
+}
+
+/* Function to get value of an integer parameter */
+
+/**/
+static long
+intgetfn(Param pm)
+{
+ return pm->u.val;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+intsetfn(Param pm, long x)
+{
+ pm->u.val = x;
+}
+
+/* Function to get value of a scalar (string) parameter */
+
+/**/
+char *
+strgetfn(Param pm)
+{
+ return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+}
+
+/* Function to set value of a scalar (string) parameter */
+
+/**/
+static void
+strsetfn(Param pm, char *x)
+{
+ zsfree(pm->u.str);
+ pm->u.str = x;
+ adduserdir(pm->nam, x, 0, 0);
+}
+
+/* Function to get value of an array parameter */
+
+/**/
+static char **
+arrgetfn(Param pm)
+{
+ static char *nullarray = NULL;
+
+ return pm->u.arr ? pm->u.arr : &nullarray;
+}
+
+/* Function to set value of an array parameter */
+
+/**/
+static void
+arrsetfn(Param pm, char **x)
+{
+ if (pm->u.arr && pm->u.arr != x)
+ freearray(pm->u.arr);
+ if (pm->flags & PM_UNIQUE)
+ uniqarray(x);
+ pm->u.arr = x;
+}
+
+/* This function is used as the set function for *
+ * special parameters that cannot be set by the user. */
+
+/**/
+void
+nullsetfn(Param pm, char *x)
+{
+ zsfree(x);
+}
+
+/* Function to get value of generic special integer *
+ * parameter. data is pointer to global variable *
+ * containing the integer value. */
+
+/**/
+long
+intvargetfn(Param pm)
+{
+ return *((long *)pm->u.data);
+}
+
+/* Function to set value of generic special integer *
+ * parameter. data is pointer to global variable *
+ * where the value is to be stored. */
+
+/**/
+void
+intvarsetfn(Param pm, long x)
+{
+ *((long *)pm->u.data) = x;
+}
+
+/* Function to set value of any ZLE-related integer *
+ * parameter. data is pointer to global variable *
+ * where the value is to be stored. */
+
+/**/
+void
+zlevarsetfn(Param pm, long x)
+{
+ if ((long *)pm->u.data == & columns) {
+ if(x <= 0)
+ x = tccolumns > 0 ? tccolumns : 80;
+ if (x > 2)
+ termflags &= ~TERM_NARROW;
+ else
+ termflags |= TERM_NARROW;
+ } else if ((long *)pm->u.data == & lines) {
+ if(x <= 0)
+ x = tclines > 0 ? tclines : 24;
+ if (x > 2)
+ termflags &= ~TERM_SHORT;
+ else
+ termflags |= TERM_SHORT;
+ }
+
+ *((long *)pm->u.data) = x;
+}
+
+/* Function to set value of generic special scalar *
+ * parameter. data is pointer to a character pointer *
+ * representing the scalar (string). */
+
+/**/
+void
+strvarsetfn(Param pm, char *x)
+{
+ char **q = ((char **)pm->u.data);
+
+ zsfree(*q);
+ *q = x;
+}
+
+/* Function to get value of generic special scalar *
+ * parameter. data is pointer to a character pointer *
+ * representing the scalar (string). */
+
+/**/
+char *
+strvargetfn(Param pm)
+{
+ char *s = *((char **)pm->u.data);
+
+ if (!s)
+ return hcalloc(1);
+ return s;
+}
+
+/* Function to get value of generic special array *
+ * parameter. data is a pointer to the pointer to *
+ * a pointer (a pointer to a variable length array *
+ * of pointers). */
+
+/**/
+char **
+arrvargetfn(Param pm)
+{
+ return *((char ***)pm->u.data);
+}
+
+/* Function to set value of generic special array parameter. *
+ * data is pointer to a variable length array of pointers which *
+ * represents this array of scalars (strings). If pm->ename is *
+ * non NULL, then it is a colon separated environment variable *
+ * version of this array which will need to be updated. */
+
+/**/
+void
+arrvarsetfn(Param pm, char **x)
+{
+ char ***dptr = (char ***)pm->u.data;
+
+ if (*dptr != x)
+ freearray(*dptr);
+ if (pm->flags & PM_UNIQUE)
+ uniqarray(x);
+ *dptr = x ? x : mkarray(NULL);
+ if (pm->ename && x)
+ arrfixenv(pm->ename, x);
+}
+
+/**/
+char *
+colonarrgetfn(Param pm)
+{
+ return zjoin(*(char ***)pm->u.data, ':');
+}
+
+/**/
+void
+colonarrsetfn(Param pm, char *x)
+{
+ char ***dptr = (char ***)pm->u.data;
+
+ freearray(*dptr);
+ *dptr = x ? colonsplit(x, pm->flags & PM_UNIQUE) : mkarray(NULL);
+ if (pm->ename)
+ arrfixenv(pm->nam, *dptr);
+ zsfree(x);
+}
+
+/**/
+int
+uniqarray(char **x)
+{
+ int changes = 0;
+ char **t, **p = x;
+
+ if (!x || !*x)
+ return 0;
+ while (*++p)
+ for (t = x; t < p; t++)
+ if (!strcmp(*p, *t)) {
+ zsfree(*p);
+ for (t = p--; (*t = t[1]) != NULL; t++);
+ changes++;
+ break;
+ }
+ return changes;
+}
+
+/* Function to get value of special parameter `#' and `ARGC' */
+
+/**/
+long
+poundgetfn(Param pm)
+{
+ return arrlen(pparams);
+}
+
+/* Function to get value for special parameter `RANDOM' */
+
+/**/
+long
+randomgetfn(Param pm)
+{
+ return rand() & 0x7fff;
+}
+
+/* Function to set value of special parameter `RANDOM' */
+
+/**/
+void
+randomsetfn(Param pm, long v)
+{
+ srand((unsigned int)v);
+}
+
+/* Function to get value for special parameter `SECONDS' */
+
+/**/
+long
+secondsgetfn(Param pm)
+{
+ return time(NULL) - shtimer.tv_sec;
+}
+
+/* Function to set value of special parameter `SECONDS' */
+
+/**/
+void
+secondssetfn(Param pm, long x)
+{
+ shtimer.tv_sec = time(NULL) - x;
+ shtimer.tv_usec = 0;
+}
+
+/* Function to get value for special parameter `USERNAME' */
+
+/**/
+char *
+usernamegetfn(Param pm)
+{
+ return get_username();
+}
+
+/* Function to set value of special parameter `USERNAME' */
+
+/**/
+void
+usernamesetfn(Param pm, char *x)
+{
+#if defined(HAVE_SETUID) && defined(HAVE_GETPWNAM)
+ struct passwd *pswd;
+
+ if (x && (pswd = getpwnam(x)) && (pswd->pw_uid != cached_uid)) {
+# ifdef HAVE_INITGROUPS
+ initgroups(x, pswd->pw_gid);
+# endif
+ if(!setgid(pswd->pw_gid) && !setuid(pswd->pw_uid)) {
+ zsfree(cached_username);
+ cached_username = ztrdup(pswd->pw_name);
+ cached_uid = pswd->pw_uid;
+ }
+ }
+#endif /* HAVE_SETUID && HAVE_GETPWNAM */
+}
+
+/* Function to get value for special parameter `UID' */
+
+/**/
+long
+uidgetfn(Param pm)
+{
+ return getuid();
+}
+
+/* Function to set value of special parameter `UID' */
+
+/**/
+void
+uidsetfn(Param pm, uid_t x)
+{
+#ifdef HAVE_SETUID
+ setuid(x);
+#endif
+}
+
+/* Function to get value for special parameter `EUID' */
+
+/**/
+long
+euidgetfn(Param pm)
+{
+ return geteuid();
+}
+
+/* Function to set value of special parameter `EUID' */
+
+/**/
+void
+euidsetfn(Param pm, uid_t x)
+{
+#ifdef HAVE_SETEUID
+ seteuid(x);
+#endif
+}
+
+/* Function to get value for special parameter `GID' */
+
+/**/
+long
+gidgetfn(Param pm)
+{
+ return getgid();
+}
+
+/* Function to set value of special parameter `GID' */
+
+/**/
+void
+gidsetfn(Param pm, gid_t x)
+{
+#ifdef HAVE_SETUID
+ setgid(x);
+#endif
+}
+
+/* Function to get value for special parameter `EGID' */
+
+/**/
+long
+egidgetfn(Param pm)
+{
+ return getegid();
+}
+
+/* Function to set value of special parameter `EGID' */
+
+/**/
+void
+egidsetfn(Param pm, gid_t x)
+{
+#ifdef HAVE_SETEUID
+ setegid(x);
+#endif
+}
+
+/**/
+long
+ttyidlegetfn(Param pm)
+{
+ struct stat ttystat;
+
+ if (SHTTY == -1 || fstat(SHTTY, &ttystat))
+ return -1;
+ return time(NULL) - ttystat.st_atime;
+}
+
+/* Function to get value for special parameter `IFS' */
+
+/**/
+char *
+ifsgetfn(Param pm)
+{
+ return ifs;
+}
+
+/* Function to set value of special parameter `IFS' */
+
+/**/
+void
+ifssetfn(Param pm, char *x)
+{
+ zsfree(ifs);
+ ifs = x;
+ inittyptab();
+}
+
+/* Functions to set value of special parameters `LANG' and `LC_*' */
+
+#ifdef LC_ALL
+static struct localename {
+ char *name;
+ int category;
+} lc_names[] = {
+#ifdef LC_COLLATE
+ {"LC_COLLATE", LC_COLLATE},
+#endif
+#ifdef LC_CTYPE
+ {"LC_CTYPE", LC_CTYPE},
+#endif
+#ifdef LC_MESSAGES
+ {"LC_MESSAGES", LC_MESSAGES},
+#endif
+#ifdef LC_TIME
+ {"LC_TIME", LC_TIME},
+#endif
+ {NULL, 0}
+};
+
+/**/
+static void
+setlang(char *x)
+{
+ struct localename *ln;
+
+ setlocale(LC_ALL, x ? x : "");
+ for (ln = lc_names; ln->name; ln++)
+ if ((x = getsparam(ln->name)))
+ setlocale(ln->category, x);
+}
+
+/**/
+void
+lc_allsetfn(Param pm, char *x)
+{
+ strsetfn(pm, x);
+ if (!x)
+ setlang(getsparam("LANG"));
+ else
+ setlocale(LC_ALL, x);
+}
+
+/**/
+void
+langsetfn(Param pm, char *x)
+{
+ strsetfn(pm, x);
+ setlang(x);
+}
+
+/**/
+void
+lcsetfn(Param pm, char *x)
+{
+ struct localename *ln;
+
+ strsetfn(pm, x);
+ if (getsparam("LC_ALL"))
+ return;
+ if (!x)
+ x = getsparam("LANG");
+
+ for (ln = lc_names; ln->name; ln++)
+ if (!strcmp(ln->name, pm->nam))
+ setlocale(ln->category, x ? x : "");
+}
+#endif
+
+/* Function to get value for special parameter `HISTSIZE' */
+
+/**/
+long
+histsizegetfn(Param pm)
+{
+ return histsiz;
+}
+
+/* Function to set value of special parameter `HISTSIZE' */
+
+/**/
+void
+histsizesetfn(Param pm, long v)
+{
+ if ((histsiz = v) <= 2)
+ histsiz = 2;
+ resizehistents();
+}
+
+/* Function to get value for special parameter `ERRNO' */
+
+/**/
+long
+errnogetfn(Param pm)
+{
+ return errno;
+}
+
+/* Function to get value for special parameter `histchar' */
+
+/**/
+char *
+histcharsgetfn(Param pm)
+{
+ static char buf[4];
+
+ buf[0] = bangchar;
+ buf[1] = hatchar;
+ buf[2] = hashchar;
+ buf[3] = '\0';
+ return buf;
+}
+
+/* Function to set value of special parameter `histchar' */
+
+/**/
+void
+histcharssetfn(Param pm, char *x)
+{
+ if (x) {
+ bangchar = x[0];
+ hatchar = (bangchar) ? x[1] : '\0';
+ hashchar = (hatchar) ? x[2] : '\0';
+ zsfree(x);
+ } else {
+ bangchar = '!';
+ hashchar = '#';
+ hatchar = '^';
+ }
+ inittyptab();
+}
+
+/* Function to get value for special parameter `HOME' */
+
+/**/
+char *
+homegetfn(Param pm)
+{
+ return home;
+}
+
+/* Function to set value of special parameter `HOME' */
+
+/**/
+void
+homesetfn(Param pm, char *x)
+{
+ zsfree(home);
+ if (x && isset(CHASELINKS) && (home = xsymlink(x)))
+ zsfree(x);
+ else
+ home = x ? x : ztrdup("");
+ finddir(NULL);
+}
+
+/* Function to get value for special parameter `WORDCHARS' */
+
+/**/
+char *
+wordcharsgetfn(Param pm)
+{
+ return wordchars;
+}
+
+/* Function to set value of special parameter `WORDCHARS' */
+
+/**/
+void
+wordcharssetfn(Param pm, char *x)
+{
+ zsfree(wordchars);
+ wordchars = x;
+ inittyptab();
+}
+
+/* Function to get value for special parameter `_' */
+
+/**/
+char *
+underscoregetfn(Param pm)
+{
+ return underscore;
+}
+
+/* Function to get value for special parameter `TERM' */
+
+/**/
+char *
+termgetfn(Param pm)
+{
+ return term;
+}
+
+/* Function to set value of special parameter `TERM' */
+
+/**/
+void
+termsetfn(Param pm, char *x)
+{
+ zsfree(term);
+ term = x ? x : ztrdup("");
+
+ /* If non-interactive, delay setting up term till we need it. */
+ if (unset(INTERACTIVE) || !*term)
+ termflags |= TERM_UNKNOWN;
+ else
+ init_term();
+}
+
+/* We could probably replace the replenv with the actual code to *
+ * do the replacing, since we've already scanned for the string. */
+
+/**/
+static void
+arrfixenv(char *s, char **t)
+{
+ char **ep, *u;
+ int len_s;
+ Param pm;
+
+ MUSTUSEHEAP("arrfixenv");
+ if (t == path)
+ cmdnamtab->emptytable(cmdnamtab);
+ u = zjoin(t, ':');
+ len_s = strlen(s);
+ pm = (Param) paramtab->getnode(paramtab, s);
+ for (ep = environ; *ep; ep++)
+ if (!strncmp(*ep, s, len_s) && (*ep)[len_s] == '=') {
+ pm->env = replenv(*ep, u);
+ return;
+ }
+ if (isset(ALLEXPORT))
+ pm->flags |= PM_EXPORTED;
+ if (pm->flags & PM_EXPORTED)
+ pm->env = addenv(s, u);
+}
+
+/* Given *name = "foo", it searchs the environment for string *
+ * "foo=bar", and returns a pointer to the beginning of "bar" */
+
+/**/
+char *
+zgetenv(char *name)
+{
+ char **ep, *s, *t;
+
+ for (ep = environ; *ep; ep++) {
+ for (s = *ep, t = name; *s && *s == *t; s++, t++);
+ if (*s == '=' && !*t)
+ return s + 1;
+ }
+ return NULL;
+}
+
+/* Change the value of an existing environment variable */
+
+/**/
+char *
+replenv(char *e, char *value)
+{
+ char **ep, *s;
+ int len_value;
+
+ for (ep = environ; *ep; ep++)
+ if (*ep == e) {
+ for (len_value = 0, s = value;
+ *s && (*s++ != Meta || *s++ != 32); len_value++);
+ s = e;
+ while (*s++ != '=');
+ *ep = (char *) zrealloc(e, s - e + len_value + 1);
+ s = s - e + *ep - 1;
+ while (*s++)
+ if ((*s = *value++) == Meta)
+ *s = *value++ ^ 32;
+ return *ep;
+ }
+ return NULL;
+}
+
+/* Given strings *name = "foo", *value = "bar", *
+ * return a new string *str = "foo=bar". */
+
+/**/
+static char *
+mkenvstr(char *name, char *value)
+{
+ char *str, *s;
+ int len_name, len_value;
+
+ len_name = strlen(name);
+ for (len_value = 0, s = value;
+ *s && (*s++ != Meta || *s++ != 32); len_value++);
+ s = str = (char *) zalloc(len_name + len_value + 2);
+ strcpy(s, name);
+ s += len_name;
+ *s = '=';
+ while (*s++)
+ if ((*s = *value++) == Meta)
+ *s = *value++ ^ 32;
+ return str;
+}
+
+/* Given *name = "foo", *value = "bar", add the *
+ * string "foo=bar" to the environment. Return a *
+ * pointer to the location of this new environment *
+ * string. */
+
+/**/
+char *
+addenv(char *name, char *value)
+{
+ char **ep, *s, *t;
+ int num_env;
+
+ /* First check if there is already an environment *
+ * variable matching string `name'. */
+ for (ep = environ; *ep; ep++) {
+ for (s = *ep, t = name; *s && *s == *t; s++, t++);
+ if (*s == '=' && !*t) {
+ zsfree(*ep);
+ return *ep = mkenvstr(name, value);
+ }
+ }
+
+ /* Else we have to make room and add it */
+ num_env = arrlen(environ);
+ environ = (char **) zrealloc(environ, (sizeof(char *)) * (num_env + 2));
+
+ /* Now add it at the end */
+ ep = environ + num_env;
+ *ep = mkenvstr(name, value);
+ *(ep + 1) = NULL;
+ return *ep;
+}
+
+/* Delete a pointer from the list of pointers to environment *
+ * variables by shifting all the other pointers up one slot. */
+
+/**/
+void
+delenv(char *x)
+{
+ char **ep;
+
+ for (ep = environ; *ep; ep++) {
+ if (*ep == x)
+ break;
+ }
+ if (*ep)
+ for (; (ep[0] = ep[1]); ep++);
+}
+
+/**/
+static void
+convbase(char *s, long v, int base)
+{
+ int digs = 0;
+ unsigned long x;
+
+ if (v < 0)
+ *s++ = '-', v = -v;
+ if (base <= 1)
+ base = 10;
+
+ if (base != 10) {
+ sprintf(s, "%d#", base);
+ s += strlen(s);
+ }
+ for (x = v; x; digs++)
+ x /= base;
+ if (!digs)
+ digs = 1;
+ s[digs--] = '\0';
+ x = v;
+ while (digs >= 0) {
+ int dig = x % base;
+
+ s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A';
+ x /= base;
+ }
+}
+
+/* Start a parameter scope */
+
+/**/
+void
+startparamscope(void)
+{
+ locallevel++;
+}
+
+/* End a parameter scope: delete the parameters local to the scope. */
+
+/**/
+void
+endparamscope(void)
+{
+ locallevel--;
+ scanhashtable(paramtab, 0, 0, 0, scanendscope, 0);
+}
+
+/**/
+static void
+scanendscope(HashNode hn, int flags)
+{
+ Param pm = (Param)hn;
+ if(pm->level > locallevel)
+ unsetparam_pm(pm, 0, 0);
+}
diff --git a/Src/parse.c b/Src/parse.c
new file mode 100644
index 000000000..d42be2f2f
--- /dev/null
+++ b/Src/parse.c
@@ -0,0 +1,1379 @@
+/*
+ * parse.c - parser
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "parse.pro"
+
+/* != 0 if we are about to read a command word */
+
+/**/
+int incmdpos;
+
+/* != 0 if we are in the middle of a [[ ... ]] */
+
+/**/
+int incond;
+
+/* != 0 if we are after a redirection (for ctxtlex only) */
+
+/**/
+int inredir;
+
+/* != 0 if we are about to read a case pattern */
+
+/**/
+int incasepat;
+
+/* != 0 if we just read a newline */
+
+/**/
+int isnewlin;
+
+/* != 0 if we are after a for keyword */
+
+/**/
+int infor;
+
+/* list of here-documents */
+
+/**/
+struct heredocs *hdocs;
+
+/* used in arrays of lists instead of NULL pointers */
+
+/**/
+struct list dummy_list;
+
+#define YYERROR { tok = LEXERR; return NULL; }
+#define YYERRORV { tok = LEXERR; return; }
+#define COND_ERROR(X,Y) do{herrflush();zerr(X,Y,0);YYERROR}while(0)
+
+#define make_list() allocnode(N_LIST)
+#define make_sublist() allocnode(N_SUBLIST)
+#define make_pline() allocnode(N_PLINE)
+#define make_cmd() allocnode(N_CMD)
+#define make_forcmd() allocnode(N_FOR)
+#define make_casecmd() allocnode(N_CASE)
+#define make_ifcmd() allocnode(N_IF)
+#define make_whilecmd() allocnode(N_WHILE)
+#define make_varnode() allocnode(N_VARASG)
+#define make_cond() allocnode(N_COND)
+
+/*
+ * event : ENDINPUT
+ * | SEPER
+ * | sublist [ SEPER | AMPER | AMPERBANG ]
+ */
+/**/
+List
+parse_event(void)
+{
+ tok = ENDINPUT;
+ incmdpos = 1;
+ yylex();
+ return par_event();
+}
+
+/**/
+static List
+par_event(void)
+{
+ Sublist sl;
+ List l = NULL;
+
+ while (tok == SEPER) {
+ if (isnewlin > 0)
+ return NULL;
+ yylex();
+ }
+ if (tok == ENDINPUT)
+ return NULL;
+ if ((sl = par_sublist()))
+ if (tok == ENDINPUT) {
+ l = (List) make_list();
+ l->type = Z_SYNC;
+ l->left = sl;
+ } else if (tok == SEPER) {
+ l = (List) make_list();
+ l->type = Z_SYNC;
+ l->left = sl;
+ if (isnewlin <= 0)
+ yylex();
+ } else if (tok == AMPER) {
+ l = (List) make_list();
+ l->type = Z_ASYNC;
+ l->left = sl;
+ yylex();
+ } else if (tok == AMPERBANG) {
+ l = (List) make_list();
+ l->type = Z_ASYNC | Z_DISOWN;
+ l->left = sl;
+ yylex();
+ } else
+ l = NULL;
+ if (!l) {
+ if (errflag) {
+ yyerror();
+ return NULL;
+ }
+ herrflush();
+ yyerror();
+ return NULL;
+ } else {
+ l->right = par_event();
+ }
+ return l;
+}
+
+/**/
+List
+parse_list(void)
+{
+ List ret;
+
+ tok = ENDINPUT;
+ incmdpos = 1;
+ yylex();
+ ret = par_list();
+ if (tok == LEXERR) {
+ yyerror();
+ return NULL;
+ }
+ return ret;
+}
+
+/*
+ * list : { SEPER } [ sublist [ { SEPER | AMPER | AMPERBANG } list ] ]
+ */
+
+/**/
+static List
+par_list(void)
+{
+ Sublist sl;
+ List l = NULL;
+
+ while (tok == SEPER)
+ yylex();
+ if ((sl = par_sublist()))
+ if (tok == SEPER || tok == AMPER || tok == AMPERBANG) {
+ l = (List) make_list();
+ l->left = sl;
+ l->type = (tok == SEPER) ? Z_SYNC :
+ (tok == AMPER) ? Z_ASYNC : Z_ASYNC | Z_DISOWN;
+ incmdpos = 1;
+ do {
+ yylex();
+ } while (tok == SEPER);
+ l->right = par_list();
+ } else {
+ l = (List) make_list();
+ l->left = sl;
+ l->type = Z_SYNC;
+ }
+ return l;
+}
+
+/**/
+static List
+par_list1(void)
+{
+ Sublist sl;
+ List l = NULL;
+
+ if ((sl = par_sublist())) {
+ l = (List) make_list();
+ l->type = Z_SYNC;
+ l->left = sl;
+ }
+ return l;
+}
+
+/*
+ * sublist : sublist2 [ ( DBAR | DAMPER ) { SEPER } sublist ]
+ */
+
+/**/
+static Sublist
+par_sublist(void)
+{
+ Sublist sl;
+
+ if ((sl = par_sublist2()))
+ if (tok == DBAR || tok == DAMPER) {
+ int qtok = tok;
+
+ cmdpush(tok == DBAR ? CS_CMDOR : CS_CMDAND);
+ yylex();
+ while (tok == SEPER)
+ yylex();
+ sl->right = par_sublist();
+ sl->type = (qtok == DBAR) ? ORNEXT : ANDNEXT;
+ cmdpop();
+ }
+ return sl;
+}
+
+/*
+ * sublist2 : [ COPROC | BANG ] pline
+ */
+
+/**/
+static Sublist
+par_sublist2(void)
+{
+ Sublist sl;
+ Pline p;
+
+ sl = (Sublist) make_sublist();
+ if (tok == COPROC) {
+ sl->flags |= PFLAG_COPROC;
+ yylex();
+ } else if (tok == BANG) {
+ sl->flags |= PFLAG_NOT;
+ yylex();
+ }
+ if (!(p = par_pline()) && !sl->flags)
+ return NULL;
+ sl->left = p;
+ return sl;
+}
+
+/*
+ * pline : cmd [ ( BAR | BARAMP ) { SEPER } pline ]
+ */
+
+/**/
+static Pline
+par_pline(void)
+{
+ Cmd c;
+ Pline p, p2;
+
+ if (!(c = par_cmd()))
+ return NULL;
+ if (tok == BAR) {
+ cmdpush(CS_PIPE);
+ yylex();
+ while (tok == SEPER)
+ yylex();
+ p2 = par_pline();
+ cmdpop();
+ p = (Pline) make_pline();
+ p->left = c;
+ p->right = p2;
+ p->type = PIPE;
+ return p;
+ } else if (tok == BARAMP) {
+ struct redir *rdr = (struct redir *)allocnode(N_REDIR);
+
+ rdr->type = MERGEOUT;
+ rdr->fd1 = 2;
+ rdr->name = dupstring("1");
+ addlinknode(c->redir, rdr);
+
+ cmdpush(CS_ERRPIPE);
+ yylex();
+ p2 = par_pline();
+ cmdpop();
+ p = (Pline) make_pline();
+ p->left = c;
+ p->right = p2;
+ p->type = PIPE;
+ return p;
+ } else {
+ p = (Pline) make_pline();
+ p->left = c;
+ p->type = END;
+ return p;
+ }
+}
+
+/*
+ * cmd : { redir } ( for | case | if | while | repeat |
+ * subsh | funcdef | time | dinbrack | dinpar | simple ) { redir }
+ */
+
+/**/
+static Cmd
+par_cmd(void)
+{
+ Cmd c;
+
+ c = (Cmd) make_cmd();
+ c->lineno = lineno;
+ c->args = newlinklist();
+ c->redir = newlinklist();
+ c->vars = newlinklist();
+ while (IS_REDIROP(tok))
+ par_redir(c->redir);
+ switch (tok) {
+ case FOR:
+ cmdpush(CS_FOR);
+ par_for(c);
+ cmdpop();
+ break;
+ case FOREACH:
+ cmdpush(CS_FOREACH);
+ par_for(c);
+ cmdpop();
+ break;
+ case SELECT:
+ cmdpush(CS_SELECT);
+ par_for(c);
+ cmdpop();
+ break;
+ case CASE:
+ cmdpush(CS_CASE);
+ par_case(c);
+ cmdpop();
+ break;
+ case IF:
+ par_if(c);
+ break;
+ case WHILE:
+ cmdpush(CS_WHILE);
+ par_while(c);
+ cmdpop();
+ break;
+ case UNTIL:
+ cmdpush(CS_UNTIL);
+ par_while(c);
+ cmdpop();
+ break;
+ case REPEAT:
+ cmdpush(CS_REPEAT);
+ par_repeat(c);
+ cmdpop();
+ break;
+ case INPAR:
+ cmdpush(CS_SUBSH);
+ par_subsh(c);
+ cmdpop();
+ break;
+ case INBRACE:
+ cmdpush(CS_CURSH);
+ par_subsh(c);
+ cmdpop();
+ break;
+ case FUNC:
+ cmdpush(CS_FUNCDEF);
+ par_funcdef(c);
+ cmdpop();
+ break;
+ case TIME:
+ par_time(c);
+ break;
+ case DINBRACK:
+ cmdpush(CS_COND);
+ par_dinbrack(c);
+ cmdpop();
+ break;
+ case DINPAR:
+ c->type = CARITH;
+ addlinknode(c->args, tokstr);
+ yylex();
+ break;
+ default:
+ if (!par_simple(c))
+ return NULL;
+ break;
+ }
+ while (IS_REDIROP(tok))
+ par_redir(c->redir);
+ incmdpos = 1;
+ incasepat = 0;
+ incond = 0;
+ return c;
+}
+
+/*
+ * for : ( FOR DINPAR expr SEMI expr SEMI expr DOUTPAR |
+ * ( FOR[EACH] | SELECT ) name ( "in" wordlist | INPAR wordlist OUTPAR ) )
+ * { SEPER } ( DO list DONE | INBRACE list OUTBRACE | list ZEND | list1 )
+ */
+
+/**/
+static void
+par_for(Cmd c)
+{
+ Forcmd f;
+ int csh = (tok == FOREACH);
+
+ f = (Forcmd) make_forcmd();
+ c->type = (tok == SELECT) ? CSELECT : CFOR;
+ incmdpos = 0;
+ infor = tok == FOR ? 2 : 0;
+ yylex();
+ if (tok == DINPAR) {
+ yylex();
+ if (tok != DINPAR)
+ YYERRORV;
+ f->name = tokstr;
+ yylex();
+ if (tok != DINPAR)
+ YYERRORV;
+ f->condition = tokstr;
+ yylex();
+ if (tok != DOUTPAR)
+ YYERRORV;
+ f->advance = tokstr;
+ infor = 0;
+ incmdpos = 1;
+ yylex();
+ } else {
+ infor = 0;
+ if (tok != STRING || !isident(tokstr))
+ YYERRORV;
+ f->name = tokstr;
+ incmdpos = 1;
+ yylex();
+ if (tok == STRING && !strcmp(tokstr, "in")) {
+ f->inflag = 1;
+ incmdpos = 0;
+ yylex();
+ c->args = par_wordlist();
+ if (tok != SEPER)
+ YYERRORV;
+ } else if (tok == INPAR) {
+ f->inflag = 1;
+ incmdpos = 0;
+ yylex();
+ c->args = par_nl_wordlist();
+ if (tok != OUTPAR)
+ YYERRORV;
+ incmdpos = 1;
+ yylex();
+ }
+ }
+ incmdpos = 1;
+ while (tok == SEPER)
+ yylex();
+ if (tok == DO) {
+ yylex();
+ f->list = par_list();
+ if (tok != DONE)
+ YYERRORV;
+ yylex();
+ } else if (tok == INBRACE) {
+ yylex();
+ f->list = par_list();
+ if (tok != OUTBRACE)
+ YYERRORV;
+ yylex();
+ } else if (csh || isset(CSHJUNKIELOOPS)) {
+ f->list = par_list();
+ if (tok != ZEND)
+ YYERRORV;
+ yylex();
+ } else if (unset(SHORTLOOPS)) {
+ YYERRORV;
+ } else
+ f->list = par_list1();
+ c->u.forcmd = f;
+}
+
+/*
+ * case : CASE STRING { SEPER } ( "in" | INBRACE )
+ { { SEPER } STRING { BAR STRING } OUTPAR
+ list [ DSEMI | SEMIAMP ] }
+ { SEPER } ( "esac" | OUTBRACE )
+ */
+
+/**/
+static void
+par_case(Cmd c)
+{
+ int brflag;
+ LinkList pats, lists;
+ int n = 1;
+ char **pp;
+ List *ll;
+ LinkNode no;
+ struct casecmd *cc;
+
+ c->type = CCASE;
+ incmdpos = 0;
+ yylex();
+ if (tok != STRING)
+ YYERRORV;
+ pats = newlinklist();
+ addlinknode(pats, tokstr);
+ incmdpos = 1;
+ yylex();
+ while (tok == SEPER)
+ yylex();
+ if (!(tok == STRING && !strcmp(tokstr, "in")) && tok != INBRACE)
+ YYERRORV;
+ brflag = (tok == INBRACE);
+ incasepat = 1;
+ incmdpos = 0;
+ yylex();
+ cc = c->u.casecmd = (struct casecmd *)make_casecmd();
+ lists = newlinklist();
+ for (;;) {
+ char *str;
+
+ while (tok == SEPER)
+ yylex();
+ if (tok == OUTBRACE)
+ break;
+ if (tok != STRING)
+ YYERRORV;
+ if (!strcmp(tokstr, "esac"))
+ break;
+ str = ncalloc(strlen(tokstr) + 2);
+ *str = ';';
+ strcpy(str + 1, tokstr);
+ incasepat = 0;
+ incmdpos = 1;
+ for (;;) {
+ yylex();
+ if (tok == OUTPAR) {
+ incasepat = 0;
+ incmdpos = 1;
+ yylex();
+ break;
+ } else if (tok == BAR) {
+ char *str2;
+ int sl = strlen(str);
+
+ incasepat = 1;
+ incmdpos = 0;
+ str2 = ncalloc(sl + 2);
+ strcpy(str2, str);
+ str2[sl] = Bar;
+ str2[sl+1] = '\0';
+ str = str2;
+ } else {
+ int sl = strlen(str);
+
+ if (str[sl - 1] != Bar) {
+ /* POSIX allows (foo*) patterns */
+ int pct;
+ char *s;
+
+ for (s = str + 1, pct = 0; *s; s++) {
+ if (*s == Inpar)
+ pct++;
+ if (!pct)
+ break;
+ if (pct == 1) {
+ if (*s == Bar || *s == Inpar)
+ while (iblank(s[1]))
+ chuck(s+1);
+ if (*s == Bar || *s == Outpar)
+ while (iblank(s[-1]) &&
+ (s < str+2 || s[-2] != Meta))
+ chuck(--s);
+ }
+ if (*s == Outpar)
+ pct--;
+ }
+ if (*s || pct || s == str + 1)
+ YYERRORV;
+ break;
+ } else {
+ char *str2;
+
+ if (tok != STRING)
+ YYERRORV;
+ str2 = ncalloc(sl + strlen(tokstr) + 1);
+ strcpy(str2, str);
+ strcpy(str2 + sl, tokstr);
+ str = str2;
+ }
+ }
+ }
+ addlinknode(pats, str);
+ addlinknode(lists, par_list());
+ n++;
+ if ((tok == ESAC && !brflag) || (tok == OUTBRACE && brflag))
+ break;
+ if(tok == SEMIAMP)
+ *str = '&';
+ else if (tok != DSEMI)
+ YYERRORV;
+ incasepat = 1;
+ incmdpos = 0;
+ yylex();
+ }
+
+ incmdpos = 1;
+ yylex();
+
+ cc->pats = (char **)alloc((n + 1) * sizeof(char *));
+
+ for (pp = cc->pats, no = firstnode(pats); no; incnode(no))
+ *pp++ = (char *)getdata(no);
+ *pp = NULL;
+ cc->lists = (List *) alloc((n + 1) * sizeof(List));
+ for (ll = cc->lists, no = firstnode(lists); no; incnode(no), ll++)
+ if (!(*ll = (List) getdata(no)))
+ *ll = &dummy_list;
+ *ll = NULL;
+}
+
+/*
+ * if : { ( IF | ELIF ) { SEPER } ( INPAR list OUTPAR | list )
+ { SEPER } ( THEN list | INBRACE list OUTBRACE | list1 ) }
+ [ FI | ELSE list FI | ELSE { SEPER } INBRACE list OUTBRACE ]
+ (you get the idea...?)
+ */
+
+/**/
+static void
+par_if(Cmd c)
+{
+ struct ifcmd *i;
+ int xtok;
+ unsigned char nc;
+ LinkList ifsl, thensl;
+ LinkNode no;
+ int ni = 0, nt = 0, usebrace = 0;
+ List l, *ll;
+
+ ifsl = newlinklist();
+ thensl = newlinklist();
+
+ c->type = CIF;
+ for (;;) {
+ xtok = tok;
+ cmdpush(xtok == IF ? CS_IF : CS_ELIF);
+ yylex();
+ if (xtok == FI)
+ break;
+ if (xtok == ELSE)
+ break;
+ while (tok == SEPER)
+ yylex();
+ if (!(xtok == IF || xtok == ELIF)) {
+ cmdpop();
+ YYERRORV;
+ }
+ addlinknode(ifsl, par_list());
+ ni++;
+ incmdpos = 1;
+ while (tok == SEPER)
+ yylex();
+ xtok = FI;
+ nc = cmdstack[cmdsp - 1] == CS_IF ? CS_IFTHEN : CS_ELIFTHEN;
+ if (tok == THEN) {
+ usebrace = 0;
+ cmdpop();
+ cmdpush(nc);
+ yylex();
+ addlinknode(thensl, par_list());
+ nt++;
+ incmdpos = 1;
+ cmdpop();
+ } else {
+ if (tok == INBRACE) {
+ usebrace = 1;
+ cmdpop();
+ cmdpush(nc);
+ yylex();
+ l = par_list();
+ if (tok != OUTBRACE) {
+ cmdpop();
+ YYERRORV;
+ }
+ addlinknode(thensl, l);
+ nt++;
+ yylex();
+ incmdpos = 1;
+ if (tok == SEPER)
+ break;
+ cmdpop();
+ } else if (unset(SHORTLOOPS)) {
+ cmdpop();
+ YYERRORV;
+ } else {
+ cmdpop();
+ cmdpush(nc);
+ addlinknode(thensl, par_list1());
+ nt++;
+ incmdpos = 1;
+ break;
+ }
+ }
+ }
+ cmdpop();
+ if (xtok == ELSE) {
+ cmdpush(CS_ELSE);
+ while (tok == SEPER)
+ yylex();
+ if (tok == INBRACE && usebrace) {
+ yylex();
+ l = par_list();
+ if (tok != OUTBRACE) {
+ cmdpop();
+ YYERRORV;
+ }
+ } else {
+ l = par_list();
+ if (tok != FI) {
+ cmdpop();
+ YYERRORV;
+ }
+ }
+ addlinknode(thensl, l);
+ nt++;
+ yylex();
+ cmdpop();
+ }
+ i = (struct ifcmd *)make_ifcmd();
+ i->ifls = (List *) alloc((ni + 1) * sizeof(List));
+ i->thenls = (List *) alloc((nt + 1) * sizeof(List));
+
+ for (ll = i->ifls, no = firstnode(ifsl); no; incnode(no), ll++)
+ if (!(*ll = (List) getdata(no)))
+ *ll = &dummy_list;
+ *ll = NULL;
+ for (ll = i->thenls, no = firstnode(thensl); no; incnode(no), ll++)
+ if (!(*ll = (List) getdata(no)))
+ *ll = &dummy_list;
+ *ll = NULL;
+
+ c->u.ifcmd = i;
+}
+
+/*
+ * while : ( WHILE | UNTIL ) ( INPAR list OUTPAR | list ) { SEPER }
+ ( DO list DONE | INBRACE list OUTBRACE | list ZEND )
+ */
+
+/**/
+static void
+par_while(Cmd c)
+{
+ struct whilecmd *w;
+
+ c->type = CWHILE;
+ w = c->u.whilecmd = (struct whilecmd *)make_whilecmd();
+ w->cond = (tok == UNTIL);
+ yylex();
+ w->cont = par_list();
+ incmdpos = 1;
+ while (tok == SEPER)
+ yylex();
+ if (tok == DO) {
+ yylex();
+ w->loop = par_list();
+ if (tok != DONE)
+ YYERRORV;
+ yylex();
+ } else if (tok == INBRACE) {
+ yylex();
+ w->loop = par_list();
+ if (tok != OUTBRACE)
+ YYERRORV;
+ yylex();
+ } else if (isset(CSHJUNKIELOOPS)) {
+ w->loop = par_list();
+ if (tok != ZEND)
+ YYERRORV;
+ yylex();
+ } else
+ YYERRORV;
+}
+
+/*
+ * repeat : REPEAT STRING { SEPER } ( DO list DONE | list1 )
+ */
+
+/**/
+static void
+par_repeat(Cmd c)
+{
+ c->type = CREPEAT;
+ incmdpos = 0;
+ yylex();
+ if (tok != STRING)
+ YYERRORV;
+ addlinknode(c->args, tokstr);
+ incmdpos = 1;
+ yylex();
+ while (tok == SEPER)
+ yylex();
+ if (tok == DO) {
+ yylex();
+ c->u.list = par_list();
+ if (tok != DONE)
+ YYERRORV;
+ yylex();
+ } else if (tok == INBRACE) {
+ yylex();
+ c->u.list = par_list();
+ if (tok != OUTBRACE)
+ YYERRORV;
+ yylex();
+ } else if (isset(CSHJUNKIELOOPS)) {
+ c->u.list = par_list();
+ if (tok != ZEND)
+ YYERRORV;
+ yylex();
+ } else if (unset(SHORTLOOPS)) {
+ YYERRORV;
+ } else
+ c->u.list = par_list1();
+}
+
+/*
+ * subsh : ( INPAR | INBRACE ) list ( OUTPAR | OUTBRACE )
+ */
+
+/**/
+static void
+par_subsh(Cmd c)
+{
+ c->type = (tok == INPAR) ? SUBSH : CURSH;
+ yylex();
+ c->u.list = par_list();
+ if (tok != ((c->type == SUBSH) ? OUTPAR : OUTBRACE))
+ YYERRORV;
+ incmdpos = 1;
+ yylex();
+}
+
+/*
+ * funcdef : FUNCTION wordlist [ INOUTPAR ] { SEPER }
+ * ( list1 | INBRACE list OUTBRACE )
+ */
+
+/**/
+static void
+par_funcdef(Cmd c)
+{
+ nocorrect = 1;
+ incmdpos = 0;
+ yylex();
+ c->type = FUNCDEF;
+ c->args = newlinklist();
+ incmdpos = 1;
+ while (tok == STRING) {
+ if (*tokstr == Inbrace && !tokstr[1]) {
+ tok = INBRACE;
+ break;
+ }
+ addlinknode(c->args, tokstr);
+ yylex();
+ }
+ nocorrect = 0;
+ if (tok == INOUTPAR)
+ yylex();
+ while (tok == SEPER)
+ yylex();
+ if (tok == INBRACE) {
+ yylex();
+ c->u.list = par_list();
+ if (tok != OUTBRACE)
+ YYERRORV;
+ yylex();
+ } else if (unset(SHORTLOOPS)) {
+ YYERRORV;
+ } else
+ c->u.list = par_list1();
+}
+
+/*
+ * time : TIME sublist2
+ */
+
+/**/
+static void
+par_time(Cmd c)
+{
+ yylex();
+ c->type = ZCTIME;
+ c->u.pline = par_sublist2();
+}
+
+/*
+ * dinbrack : DINBRACK cond DOUTBRACK
+ */
+
+/**/
+static void
+par_dinbrack(Cmd c)
+{
+ c->type = COND;
+ incond = 1;
+ incmdpos = 0;
+ yylex();
+ c->u.cond = par_cond();
+ if (tok != DOUTBRACK)
+ YYERRORV;
+ incond = 0;
+ incmdpos = 1;
+ yylex();
+}
+
+/*
+ * simple : { COMMAND | EXEC | NOGLOB | NOCORRECT | DASH }
+ { STRING | ENVSTRING | ENVARRAY wordlist OUTPAR | redir }
+ [ INOUTPAR { SEPER } ( list1 | INBRACE list OUTBRACE ) ]
+ */
+
+/**/
+static Cmd
+par_simple(Cmd c)
+{
+ int isnull = 1;
+
+ c->type = SIMPLE;
+ for (;;) {
+ if (tok == NOCORRECT)
+ nocorrect = 1;
+ else if (tok == ENVSTRING) {
+ struct varasg *v = (struct varasg *)make_varnode();
+
+ v->type = PM_SCALAR;
+ equalsplit(v->name = tokstr, &v->str);
+ addlinknode(c->vars, v);
+ isnull = 0;
+ } else if (tok == ENVARRAY) {
+ struct varasg *v = (struct varasg *)make_varnode();
+ int oldcmdpos = incmdpos;
+
+ v->type = PM_ARRAY;
+ incmdpos = 0;
+ v->name = tokstr;
+ cmdpush(CS_ARRAY);
+ yylex();
+ v->arr = par_nl_wordlist();
+ cmdpop();
+ if (tok != OUTPAR)
+ YYERROR;
+ incmdpos = oldcmdpos;
+ addlinknode(c->vars, v);
+ isnull = 0;
+ } else
+ break;
+ yylex();
+ }
+ if (tok == AMPER || tok == AMPERBANG)
+ YYERROR;
+ for (;;) {
+ if (tok == STRING) {
+ incmdpos = 0;
+ addlinknode(c->args, tokstr);
+ yylex();
+ } else if (IS_REDIROP(tok)) {
+ par_redir(c->redir);
+ } else if (tok == INOUTPAR) {
+ incmdpos = 1;
+ cmdpush(CS_FUNCDEF);
+ yylex();
+ while (tok == SEPER)
+ yylex();
+ if (tok == INBRACE) {
+ yylex();
+ c->u.list = par_list();
+ if (tok != OUTBRACE) {
+ cmdpop();
+ YYERROR;
+ }
+ yylex();
+ } else
+ c->u.list = (List) expandstruct((struct node *) par_cmd(), N_LIST);
+ cmdpop();
+ c->type = FUNCDEF;
+ } else
+ break;
+ isnull = 0;
+ }
+ if (isnull && empty(c->redir))
+ return NULL;
+ incmdpos = 1;
+ return c;
+}
+
+/*
+ * condlex is yylex for normal parsing, but is altered to allow
+ * the test builtin to use par_cond.
+ */
+
+/**/
+void (*condlex) _((void)) = yylex;
+
+/*
+ * cond : cond_1 { SEPER } [ DBAR { SEPER } cond ]
+ */
+
+/**/
+Cond
+par_cond(void)
+{
+ Cond c, c2;
+
+ c = par_cond_1();
+ while (tok == SEPER)
+ condlex();
+ if (tok == DBAR) {
+ condlex();
+ while (tok == SEPER)
+ condlex();
+ c2 = (Cond) make_cond();
+ c2->left = (void *) c;
+ c2->right = (void *) par_cond();
+ c2->type = COND_OR;
+ return c2;
+ }
+ return c;
+}
+
+/*
+ * cond_1 : cond_2 { SEPER } [ DAMPER { SEPER } cond_1 ]
+ */
+
+/**/
+static Cond
+par_cond_1(void)
+{
+ Cond c, c2;
+
+ c = par_cond_2();
+ while (tok == SEPER)
+ condlex();
+ if (tok == DAMPER) {
+ condlex();
+ while (tok == SEPER)
+ condlex();
+ c2 = (Cond) make_cond();
+ c2->left = (void *) c;
+ c2->right = (void *) par_cond_1();
+ c2->type = COND_AND;
+ return c2;
+ }
+ return c;
+}
+
+/*
+ * cond_2 : BANG cond_2
+ | INPAR { SEPER } cond_2 { SEPER } OUTPAR
+ | STRING STRING STRING
+ | STRING STRING
+ | STRING ( INANG | OUTANG ) STRING
+ */
+
+/**/
+static Cond
+par_cond_2(void)
+{
+ Cond c, c2;
+ char *s1, *s2, *s3;
+ int dble = 0;
+
+ if (condlex == testlex) {
+ /* See the description of test in POSIX 1003.2 */
+ if (tok == NULLTOK)
+ /* no arguments: false */
+ return par_cond_double(dupstring("-n"), dupstring(""));
+ if (!*testargs) {
+ /* one argument: [ foo ] is equivalent to [ -n foo ] */
+ s1 = tokstr;
+ condlex();
+ return par_cond_double(dupstring("-n"), s1);
+ }
+ if (testargs[1] && !testargs[2]) {
+ /* three arguments: if the second argument is a binary operator, *
+ * perform that binary test on the first and the trird argument */
+ if (!strcmp(*testargs, "=") ||
+ !strcmp(*testargs, "==") ||
+ !strcmp(*testargs, "!=") ||
+ (**testargs == '-' && get_cond_num(*testargs + 1) >= 0)) {
+ s1 = tokstr;
+ condlex();
+ s2 = tokstr;
+ condlex();
+ s3 = tokstr;
+ condlex();
+ return par_cond_triple(s1, s2, s3);
+ }
+ }
+ }
+ if (tok == BANG) {
+ condlex();
+ c = par_cond_2();
+ c2 = (Cond) make_cond();
+ c2->left = (void *) c;
+ c2->type = COND_NOT;
+ return c2;
+ }
+ if (tok == INPAR) {
+ condlex();
+ while (tok == SEPER)
+ condlex();
+ c = par_cond();
+ while (tok == SEPER)
+ condlex();
+ if (tok != OUTPAR)
+ YYERROR;
+ condlex();
+ return c;
+ }
+ if (tok != STRING)
+ if (tok && tok != LEXERR && condlex == testlex) {
+ s1 = tokstr;
+ condlex();
+ return par_cond_double("-n", s1);
+ } else
+ YYERROR;
+ s1 = tokstr;
+ if (condlex == testlex)
+ dble = (*s1 == '-' && strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1
+ && !s1[2]);
+ condlex();
+ if (tok == INANG || tok == OUTANG) {
+ int xtok = tok;
+ condlex();
+ if (tok != STRING)
+ YYERROR;
+ s3 = tokstr;
+ condlex();
+ c = (Cond) make_cond();
+ c->left = (void *) s1;
+ c->right = (void *) s3;
+ c->type = (xtok == INANG) ? COND_STRLT : COND_STRGTR;
+ c->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0);
+ return c;
+ }
+ if (tok != STRING)
+ if (tok != LEXERR && condlex == testlex) {
+ if (!dble)
+ return par_cond_double("-n", s1);
+ else if (!strcmp(s1, "-t"))
+ return par_cond_double(s1, "1");
+ } else
+ YYERROR;
+ s2 = tokstr;
+ incond++; /* parentheses do globbing */
+ condlex();
+ incond--; /* parentheses do grouping */
+ if (tok == STRING && !dble) {
+ s3 = tokstr;
+ condlex();
+ return par_cond_triple(s1, s2, s3);
+ } else
+ return par_cond_double(s1, s2);
+}
+
+/*
+ * redir : ( OUTANG | ... | TRINANG ) STRING
+ */
+
+static int redirtab[TRINANG - OUTANG + 1] = {
+ WRITE,
+ WRITENOW,
+ APP,
+ APPNOW,
+ READ,
+ READWRITE,
+ HEREDOC,
+ HEREDOCDASH,
+ MERGEIN,
+ MERGEOUT,
+ ERRWRITE,
+ ERRWRITENOW,
+ ERRAPP,
+ ERRAPPNOW,
+ HERESTR,
+};
+
+/**/
+static void
+par_redir(LinkList l)
+{
+ struct redir *fn = (struct redir *)allocnode(N_REDIR);
+ int oldcmdpos, oldnc;
+
+ oldcmdpos = incmdpos;
+ incmdpos = 0;
+ oldnc = nocorrect;
+ if (tok != INANG && tok != INOUTANG)
+ nocorrect = 1;
+ fn->type = redirtab[tok - OUTANG];
+ fn->fd1 = tokfd;
+ yylex();
+ if (tok != STRING && tok != ENVSTRING)
+ YYERRORV;
+ incmdpos = oldcmdpos;
+ nocorrect = oldnc;
+
+ /* assign default fd */
+ if (fn->fd1 == -1)
+ fn->fd1 = IS_READFD(fn->type) ? 0 : 1;
+
+ fn->name = tokstr;
+
+ switch (fn->type) {
+ case HEREDOC:
+ case HEREDOCDASH: {
+ /* <<[-] name */
+ struct heredocs **hd;
+
+ for (hd = &hdocs; *hd; hd = &(*hd)->next);
+ *hd = zalloc(sizeof(struct heredocs));
+ (*hd)->next = NULL;
+ (*hd)->rd = fn;
+ break;
+ }
+ case WRITE:
+ case WRITENOW:
+ if (tokstr[0] == Outang && tokstr[1] == Inpar)
+ /* > >(...) */
+ fn->type = OUTPIPE;
+ else if (tokstr[0] == Inang && tokstr[1] == Inpar)
+ YYERRORV;
+ break;
+ case READ:
+ if (tokstr[0] == Inang && tokstr[1] == Inpar)
+ /* < <(...) */
+ fn->type = INPIPE;
+ else if (tokstr[0] == Outang && tokstr[1] == Inpar)
+ YYERRORV;
+ break;
+ case READWRITE:
+ if ((tokstr[0] == Inang || tokstr[0] == Outang) && tokstr[1] == Inpar)
+ fn->type = tokstr[0] == Inang ? INPIPE : OUTPIPE;
+ break;
+ }
+ yylex();
+ addlinknode(l, fn);
+}
+
+/*
+ * wordlist : { STRING }
+ */
+
+/**/
+static LinkList
+par_wordlist(void)
+{
+ LinkList l;
+
+ l = newlinklist();
+ while (tok == STRING) {
+ addlinknode(l, tokstr);
+ yylex();
+ }
+ return l;
+}
+
+/*
+ * nl_wordlist : { STRING | SEPER }
+ */
+
+/**/
+static LinkList
+par_nl_wordlist(void)
+{
+ LinkList l;
+
+ l = newlinklist();
+ while (tok == STRING || tok == SEPER) {
+ if (tok != SEPER)
+ addlinknode(l, tokstr);
+ yylex();
+ }
+ return l;
+}
+
+/**/
+static Cond
+par_cond_double(char *a, char *b)
+{
+ Cond n = (Cond) make_cond();
+
+ if (a[0] != '-' || !a[1] || a[2])
+ COND_ERROR("parse error: condition expected: %s", a);
+ n->left = (void *) b;
+ n->type = a[1];
+ n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0);
+ return n;
+}
+
+/**/
+static int
+get_cond_num(char *tst)
+{
+ static char *condstrs[] =
+ {
+ "nt", "ot", "ef", "eq", "ne", "lt", "gt", "le", "ge", NULL
+ };
+ int t0;
+
+ for (t0 = 0; condstrs[t0]; t0++)
+ if (!strcmp(condstrs[t0], tst))
+ return t0;
+ return -1;
+}
+
+/**/
+static Cond
+par_cond_triple(char *a, char *b, char *c)
+{
+ Cond n = (Cond) make_cond();
+ int t0;
+
+ if ((b[0] == Equals || b[0] == '=') &&
+ (!b[1] || ((b[1] == Equals || b[1] == '=') && !b[2])))
+ n->type = COND_STREQ;
+ else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2])
+ n->type = COND_STRNEQ;
+ else if (b[0] == '-') {
+ if ((t0 = get_cond_num(b + 1)) > -1)
+ n->type = t0 + COND_NT;
+ else
+ COND_ERROR("unrecognized condition: %s", b);
+ } else
+ COND_ERROR("condition expected: %s", b);
+ n->left = (void *) a;
+ n->right = (void *) c;
+ n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0);
+ return n;
+}
+
+/**/
+static void
+yyerror(void)
+{
+ int t0;
+
+ for (t0 = 0; t0 != 20; t0++)
+ if (!yytext || !yytext[t0] || yytext[t0] == '\n')
+ break;
+ if (t0 == 20)
+ zerr("parse error near `%l...'", yytext, 20);
+ else if (t0)
+ zerr("parse error near `%l'", yytext, t0);
+ else
+ zerr("parse error", NULL, 0);
+}
diff --git a/Src/prompt.c b/Src/prompt.c
new file mode 100644
index 000000000..8c9216f95
--- /dev/null
+++ b/Src/prompt.c
@@ -0,0 +1,766 @@
+/*
+ * prompt.c - construct zsh prompts
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "prompt.pro"
+
+/* text attribute mask */
+
+/**/
+unsigned txtattrmask;
+
+/* text change - attribute change made by prompts */
+
+/**/
+unsigned txtchange;
+
+/* the command stack for use with %_ in prompts */
+
+/**/
+unsigned char *cmdstack;
+/**/
+int cmdsp;
+
+/* parser states, for %_ */
+
+static char *cmdnames[] = {
+ "for", "while", "repeat", "select",
+ "until", "if", "then", "else",
+ "elif", "math", "cond", "cmdor",
+ "cmdand", "pipe", "errpipe", "foreach",
+ "case", "function", "subsh", "cursh",
+ "array", "quote", "dquote", "bquote",
+ "cmdsubst", "mathsubst", "elif-then", "heredoc",
+ "heredocd", "brace", "braceparam",
+};
+
+/* The buffer into which an expanded and metafied prompt is being written, *
+ * and its size. */
+
+static char *buf;
+static int bufspc;
+
+/* bp is the pointer to the current position in the buffer, where the next *
+ * character will be added. */
+
+static char *bp;
+
+/* bp1 is an auxilliary pointer into the buffer, which when non-NULL is *
+ * moved whenever the buffer is reallocated. It is used when data is *
+ * being temporarily held in the buffer. */
+
+static char *bp1;
+
+/* The format string, for %-expansion. */
+
+static char *fm;
+
+/* Current truncation string (metafied), the length at which truncation *
+ * occurs, and the direction in which it occurs. */
+
+static char *truncstr;
+static int trunclen, truncatleft;
+
+/* Current level of nesting of %{ / %} sequences. */
+
+static int dontcount;
+
+/* Strings to use for %r and %R (for the spelling prompt). */
+
+static char *rstring, *Rstring;
+
+/* If non-zero, Inpar, Outpar and Nularg can be added to the buffer. */
+
+static int nonsp;
+
+/* Perform prompt expansion on a string, putting the result in a *
+ * permanently-allocated string. If ns is non-zero, this string *
+ * may have embedded Inpar and Outpar, which indicate a toggling *
+ * between spacing and non-spacing parts of the prompt, and *
+ * Nularg, which (in a non-spacing sequence) indicates a *
+ * `glitch' space. */
+
+/**/
+char *
+promptexpand(char *s, int ns, char *rs, char *Rs)
+{
+ if(!s)
+ return ztrdup("");
+
+ if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
+ init_term();
+
+ if (isset(PROMPTSUBST)) {
+ int olderr = errflag;
+
+ HEAPALLOC {
+ s = dupstring(s);
+ if (!parsestr(s))
+ singsub(&s);
+ } LASTALLOC;
+ /* Ignore errors in prompt substitution */
+ errflag = olderr;
+ }
+
+ rstring = rs;
+ Rstring = Rs;
+ nonsp = ns;
+ fm = s;
+ bp = buf = zalloc(bufspc = 256);
+ bp1 = NULL;
+ trunclen = 0;
+ putpromptchar(1, '\0');
+ addbufspc(1);
+ if(dontcount)
+ *bp++ = Outpar;
+ *bp = 0;
+ return buf;
+}
+
+/* Perform %- and !-expansion as required on a section of the prompt. The *
+ * section is ended by an instance of endchar. If doprint is 0, the valid *
+ * % sequences are merely skipped over, and nothing is stored. */
+
+/**/
+static int
+putpromptchar(int doprint, int endchar)
+{
+ char *ss, *tmbuf = NULL;
+ int t0, arg, test, sep;
+ struct tm *tm;
+ time_t timet;
+ Nameddir nd;
+
+ for (; *fm && *fm != endchar; fm++) {
+ arg = 0;
+ if (*fm == '%' && isset(PROMPTPERCENT)) {
+ if (idigit(*++fm)) {
+ arg = zstrtol(fm, &fm, 10);
+ }
+ if (*fm == '(') {
+ int tc;
+
+ if (idigit(*++fm)) {
+ arg = zstrtol(fm, &fm, 10);
+ }
+ test = 0;
+ ss = pwd;
+ switch (tc = *fm) {
+ case 'c':
+ case '.':
+ case '~':
+ if ((nd = finddir(ss))) {
+ arg--;
+ ss += strlen(nd->dir);
+ }
+ case '/':
+ case 'C':
+ for (; *ss; ss++)
+ if (*ss == '/')
+ arg--;
+ if (arg <= 0)
+ test = 1;
+ break;
+ case 't':
+ case 'T':
+ case 'd':
+ case 'D':
+ case 'w':
+ timet = time(NULL);
+ tm = localtime(&timet);
+ switch (tc) {
+ case 't':
+ test = (arg == tm->tm_min);
+ break;
+ case 'T':
+ test = (arg == tm->tm_hour);
+ break;
+ case 'd':
+ test = (arg == tm->tm_mday);
+ break;
+ case 'D':
+ test = (arg == tm->tm_mon);
+ break;
+ case 'w':
+ test = (arg == tm->tm_wday);
+ break;
+ }
+ break;
+ case '?':
+ if (lastval == arg)
+ test = 1;
+ break;
+ case '#':
+ if (geteuid() == arg)
+ test = 1;
+ break;
+ case 'g':
+ if (getegid() == arg)
+ test = 1;
+ break;
+ case 'L':
+ if (shlvl >= arg)
+ test = 1;
+ break;
+ case 'S':
+ if (time(NULL) - shtimer.tv_sec >= arg)
+ test = 1;
+ break;
+ case 'v':
+ if (arrlen(psvar) >= arg)
+ test = 1;
+ break;
+ case '_':
+ test = (cmdsp >= arg);
+ break;
+ case '!':
+ test = privasserted();
+ break;
+ default:
+ test = -1;
+ break;
+ }
+ if (!*fm || !(sep = *++fm))
+ return 0;
+ fm++;
+ if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
+ !putpromptchar(test == 0 && doprint, ')')) {
+ return 0;
+ }
+ continue;
+ }
+ if (!doprint)
+ switch(*fm) {
+ case '[':
+ while(idigit(*++fm));
+ while(*++fm != ']');
+ continue;
+ case '<':
+ while(*++fm != '<');
+ continue;
+ case '>':
+ while(*++fm != '>');
+ continue;
+ case 'D':
+ if(fm[1]=='{')
+ while(*++fm != '}');
+ continue;
+ default:
+ continue;
+ }
+ switch (*fm) {
+ case '~':
+ if ((nd = finddir(pwd))) {
+ char *t = tricat("~", nd->nam, pwd + strlen(nd->dir));
+ stradd(t);
+ zsfree(t);
+ break;
+ }
+ case 'd':
+ case '/':
+ stradd(pwd);
+ break;
+ case 'c':
+ case '.':
+ {
+ char *t;
+
+ if ((nd = finddir(pwd)))
+ t = tricat("~", nd->nam, pwd + strlen(nd->dir));
+ else
+ t = ztrdup(pwd);
+ if (!arg)
+ arg++;
+ for (ss = t + strlen(t); ss > t; ss--)
+ if (*ss == '/' && !--arg) {
+ ss++;
+ break;
+ }
+ if(*ss == '/' && ss[1] && ss != t)
+ ss++;
+ stradd(ss);
+ zsfree(t);
+ break;
+ }
+ case 'C':
+ if (!arg)
+ arg++;
+ for (ss = pwd + strlen(pwd); ss > pwd; ss--)
+ if (*ss == '/' && !--arg) {
+ ss++;
+ break;
+ }
+ if (*ss == '/' && ss[1] && (ss != pwd))
+ ss++;
+ stradd(ss);
+ break;
+ case 'h':
+ case '!':
+ addbufspc(DIGBUFSIZE);
+ sprintf(bp, "%d", curhist);
+ bp += strlen(bp);
+ break;
+ case 'M':
+ stradd(hostnam);
+ break;
+ case 'm':
+ if (!arg)
+ arg++;
+ for (ss = hostnam; *ss; ss++)
+ if (*ss == '.' && !--arg)
+ break;
+ t0 = *ss;
+ *ss = '\0';
+ stradd(hostnam);
+ *ss = t0;
+ break;
+ case 'S':
+ txtchangeset(TXTSTANDOUT, TXTNOSTANDOUT);
+ txtset(TXTSTANDOUT);
+ tsetcap(TCSTANDOUTBEG, 1);
+ break;
+ case 's':
+ txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
+ txtset(TXTDIRTY);
+ txtunset(TXTSTANDOUT);
+ tsetcap(TCSTANDOUTEND, 1);
+ break;
+ case 'B':
+ txtchangeset(TXTBOLDFACE, TXTNOBOLDFACE);
+ txtset(TXTDIRTY);
+ txtset(TXTBOLDFACE);
+ tsetcap(TCBOLDFACEBEG, 1);
+ break;
+ case 'b':
+ txtchangeset(TXTNOBOLDFACE, TXTBOLDFACE);
+ txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
+ txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
+ txtset(TXTDIRTY);
+ txtunset(TXTBOLDFACE);
+ tsetcap(TCALLATTRSOFF, 1);
+ break;
+ case 'U':
+ txtchangeset(TXTUNDERLINE, TXTNOUNDERLINE);
+ txtset(TXTUNDERLINE);
+ tsetcap(TCUNDERLINEBEG, 1);
+ break;
+ case 'u':
+ txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
+ txtset(TXTDIRTY);
+ txtunset(TXTUNDERLINE);
+ tsetcap(TCUNDERLINEEND, 1);
+ break;
+ case '[':
+ if (idigit(*++fm))
+ trunclen = zstrtol(fm, &fm, 10);
+ else
+ trunclen = arg;
+ if (trunclen) {
+ truncatleft = *fm && *fm != ']' && *fm++ == '<';
+ bp1 = bp;
+ while (*fm && *fm != ']') {
+ if (*fm == '\\' && fm[1])
+ ++fm;
+ addbufspc(1);
+ *bp++ = *fm++;
+ }
+ addbufspc(2);
+ if (bp1 == bp)
+ *bp++ = '<';
+ *bp = '\0';
+ zsfree(truncstr);
+ truncstr = ztrdup(bp = bp1);
+ bp1 = NULL;
+ } else {
+ while (*fm && *fm != ']') {
+ if (*fm == '\\' && fm[1])
+ fm++;
+ fm++;
+ }
+ }
+ if(!*fm)
+ return 0;
+ break;
+ case '<':
+ case '>':
+ if((trunclen = arg)) {
+ char ch = *fm++;
+ truncatleft = ch == '<';
+ bp1 = bp;
+ while (*fm && *fm != ch) {
+ if (*fm == '\\' && fm[1])
+ ++fm;
+ addbufspc(1);
+ *bp++ = *fm++;
+ }
+ addbufspc(1);
+ *bp = '\0';
+ zsfree(truncstr);
+ truncstr = ztrdup(bp = bp1);
+ bp1 = NULL;
+ } else {
+ char ch = *fm++;
+ while(*fm && *fm != ch) {
+ if (*fm == '\\' && fm[1])
+ fm++;
+ fm++;
+ }
+ }
+ if(!*fm)
+ return 0;
+ break;
+ case '{': /*}*/
+ if (!dontcount++ && nonsp) {
+ addbufspc(1);
+ *bp++ = Inpar;
+ }
+ break;
+ case /*{*/ '}':
+ if (dontcount && !--dontcount && nonsp) {
+ addbufspc(1);
+ *bp++ = Outpar;
+ }
+ break;
+ case 't':
+ case '@':
+ case 'T':
+ case '*':
+ case 'w':
+ case 'W':
+ case 'D':
+ {
+ char *tmfmt, *dd;
+
+ switch (*fm) {
+ case 'T':
+ tmfmt = "%K:%M";
+ break;
+ case '*':
+ tmfmt = "%K:%M:%S";
+ break;
+ case 'w':
+ tmfmt = "%a %f";
+ break;
+ case 'W':
+ tmfmt = "%m/%d/%y";
+ break;
+ case 'D':
+ if (fm[1] == '{') /*}*/ {
+ for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++)
+ if(*ss == '\\' && ss[1])
+ ss++;
+ dd = tmfmt = tmbuf = zalloc(ss - fm);
+ for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++) {
+ if(*ss == '\\' && ss[1])
+ ss++;
+ *dd++ = *ss;
+ }
+ *dd = 0;
+ fm = ss - !*ss;
+ } else
+ tmfmt = "%y-%m-%d";
+ break;
+ default:
+ tmfmt = "%l:%M%p";
+ break;
+ }
+ timet = time(NULL);
+ tm = localtime(&timet);
+ for(t0=80; ; t0*=2) {
+ addbufspc(t0);
+ if(ztrftime(bp, t0, tmfmt, tm) != t0)
+ break;
+ }
+ bp += strlen(bp);
+ free(tmbuf);
+ tmbuf = NULL;
+ break;
+ }
+ case 'n':
+ stradd(get_username());
+ break;
+ case 'l':
+ if (*ttystrname) {
+ ss = (strncmp(ttystrname, "/dev/tty", 8) ?
+ ttystrname + 5 : ttystrname + 8);
+ stradd(ss);
+ } else
+ stradd("()");
+ break;
+ case 'L':
+ addbufspc(DIGBUFSIZE);
+ sprintf(bp, "%ld", (long)shlvl);
+ bp += strlen(bp);
+ break;
+ case '?':
+ addbufspc(DIGBUFSIZE);
+ sprintf(bp, "%ld", (long)lastval);
+ bp += strlen(bp);
+ break;
+ case '%':
+ case ')':
+ addbufspc(1);
+ *bp++ = *fm;
+ break;
+ case '#':
+ addbufspc(1);
+ *bp++ = privasserted() ? '#' : '%';
+ break;
+ case 'v':
+ if (!arg)
+ arg = 1;
+ if (arrlen(psvar) >= arg)
+ stradd(psvar[arg - 1]);
+ break;
+ case 'E':
+ tsetcap(TCCLEAREOL, 1);
+ break;
+ case '_':
+ if (cmdsp) {
+ if (arg > cmdsp || arg <= 0)
+ arg = cmdsp;
+ for (t0 = cmdsp - arg; arg--; t0++) {
+ stradd(cmdnames[cmdstack[t0]]);
+ if (arg) {
+ addbufspc(1);
+ *bp++=' ';
+ }
+ }
+ }
+ break;
+ case 'r':
+ if(rstring)
+ stradd(rstring);
+ break;
+ case 'R':
+ if(Rstring)
+ stradd(Rstring);
+ break;
+ case '\0':
+ return 0;
+ case Meta:
+ fm++;
+ break;
+ }
+ } else if(*fm == '!' && isset(PROMPTBANG)) {
+ if(doprint)
+ if(fm[1] == '!') {
+ fm++;
+ addbufspc(1);
+ pputc('!');
+ } else {
+ addbufspc(DIGBUFSIZE);
+ sprintf(bp, "%d", curhist);
+ bp += strlen(bp);
+ }
+ } else {
+ char c = *fm == Meta ? *++fm ^ 32 : *fm;
+
+ if (doprint) {
+ addbufspc(1);
+ pputc(c);
+ }
+ }
+ }
+
+ return *fm;
+}
+
+/* pputc adds a character to the buffer, metafying. There must *
+ * already be space. */
+
+/**/
+static void
+pputc(char c)
+{
+ if(imeta(STOUC(c))) {
+ *bp++ = Meta;
+ c ^= 32;
+ }
+ *bp++ = c;
+}
+
+/* Make sure there is room for `need' more characters in the buffer. */
+
+/**/
+static void
+addbufspc(int need)
+{
+ need *= 2; /* for metafication */
+ if((bp - buf) + need > bufspc) {
+ int bo = bp - buf;
+ int bo1 = bp1 ? bp1 - buf : -1;
+
+ if(need & 255)
+ need = (need | 255) + 1;
+ buf = realloc(buf, bufspc += need);
+ bp = buf + bo;
+ if(bo1 != -1)
+ bp1 = buf + bo1;
+ }
+}
+
+/* stradd() adds a metafied string to the prompt, *
+ * in a visible representation, doing truncation. */
+
+/**/
+void
+stradd(char *d)
+{
+ /* dlen is the full length of the string we want to add */
+ int dlen = niceztrlen(d);
+ char *ps, *pd, *pc, *t;
+ int tlen, maxlen;
+ addbufspc(dlen);
+ /* This loop puts the nice representation of the string into the prompt *
+ * buffer. It might be modified later. Note that bp isn't changed. */
+ for(ps=d, pd=bp; *ps; ps++)
+ for(pc=nicechar(*ps == Meta ? STOUC(*++ps)^32 : STOUC(*ps)); *pc; pc++)
+ *pd++ = *pc;
+ if(!trunclen || dlen <= trunclen) {
+ /* No truncation is needed, so update bp and return, *
+ * leaving the full string in the prompt. */
+ bp += dlen;
+ return;
+ }
+ /* We need to truncate. t points to the truncation string -- which is *
+ * inserted literally, without nice representation. tlen is its *
+ * length, and maxlen is the amout of the main string that we want to *
+ * keep. Note that if the truncation string is longer than the *
+ * truncation length (tlen > trunclen), the truncation string is used *
+ * in full. */
+ addbufspc(tlen = ztrlen(t = truncstr));
+ maxlen = tlen < trunclen ? trunclen - tlen : 0;
+ if(truncatleft) {
+ memmove(bp + strlen(t), bp + dlen - maxlen, maxlen);
+ while(*t)
+ *bp++ = *t++;
+ bp += maxlen;
+ } else {
+ bp += maxlen;
+ while(*t)
+ *bp++ = *t++;
+ }
+}
+
+/* tsetcap(), among other things, can write a termcap string into the buffer. */
+
+/**/
+void
+tsetcap(int cap, int flag)
+{
+ if (!(termflags & TERM_SHORT) && tcstr[cap]) {
+ switch(flag) {
+ case -1:
+ tputs(tcstr[cap], 1, putraw);
+ break;
+ case 0:
+ tputs(tcstr[cap], 1, putshout);
+ break;
+ case 1:
+ if (!dontcount && nonsp) {
+ addbufspc(1);
+ *bp++ = Inpar;
+ }
+ tputs(tcstr[cap], 1, putstr);
+ if (!dontcount && nonsp) {
+ int glitch = 0;
+
+ if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
+ glitch = tgetnum("sg");
+ else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
+ glitch = tgetnum("ug");
+ if(glitch < 0)
+ glitch = 0;
+ addbufspc(glitch + 1);
+ while(glitch--)
+ *bp++ = Nularg;
+ *bp++ = Outpar;
+ }
+ break;
+ }
+
+ if (txtisset(TXTDIRTY)) {
+ txtunset(TXTDIRTY);
+ if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
+ tsetcap(TCBOLDFACEBEG, flag);
+ if (txtisset(TXTSTANDOUT))
+ tsetcap(TCSTANDOUTBEG, flag);
+ if (txtisset(TXTUNDERLINE))
+ tsetcap(TCUNDERLINEBEG, flag);
+ }
+ }
+}
+
+/**/
+int
+putstr(int d)
+{
+ addbufspc(1);
+ pputc(d);
+ return 0;
+}
+
+/* Count height etc. of a prompt string returned by promptexpand(). *
+ * This depends on the current terminal width, and tabs and *
+ * newlines require nontrivial processing. */
+
+/**/
+void
+countprompt(char *str, int *wp, int *hp)
+{
+ int w = 0, h = 1;
+ int s = 1;
+ for(; *str; str++) {
+ if(*str == Meta)
+ str++;
+ if(*str == Inpar)
+ s = 0;
+ else if(*str == Outpar)
+ s = 1;
+ else if(*str == Nularg)
+ w++;
+ else if(s) {
+ if(*str == '\t')
+ w = (w | 7) + 1;
+ else if(*str == '\n')
+ w = columns;
+ else
+ w++;
+ }
+ if(w >= columns) {
+ w = 0;
+ h++;
+ }
+ }
+ if(wp)
+ *wp = w;
+ if(hp)
+ *hp = h;
+}
diff --git a/Src/prototypes.h b/Src/prototypes.h
new file mode 100644
index 000000000..f7f560111
--- /dev/null
+++ b/Src/prototypes.h
@@ -0,0 +1,120 @@
+/*
+ * prototypes.h - prototypes header file
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#ifndef HAVE_STDLIB_H
+char *malloc _((size_t));
+char *realloc _((void *, size_t));
+char *calloc _((size_t, size_t));
+#endif
+
+#ifndef HAVE_TERMCAP_H
+extern int tgetent _((char *bp, char *name));
+extern int tgetnum _((char *id));
+extern int tgetflag _((char *id));
+extern char *tgetstr _((char *id, char **area));
+extern char *tgoto _((char *cm, int destcol, int destline));
+extern int tputs _((char *cp, int affcnt, int (*outc) (int)));
+#endif
+
+/* MISSING PROTOTYPES FOR VARIOUS OPERATING SYSTEMS */
+
+/* HP/UX 9 c89 */
+#if defined(__hpux) && defined(_XPG3) && !defined(_POSIX1_1988)
+# define WRITE_ARG_2_T void *
+#else
+# define WRITE_ARG_2_T char *
+#endif
+
+#if defined(__hpux) && defined(_HPUX_SOURCE)
+# define SELECT_ARG_2_T int *
+#else
+# define SELECT_ARG_2_T fd_set *
+#endif
+
+#ifdef __osf__
+char *mktemp _((char *));
+#endif
+
+#if defined(__osf__) && defined(__alpha) && defined(__GNUC__)
+/* Digital cc does not need these prototypes, gcc does need them */
+# ifndef HAVE_IOCTL_PROTO
+int ioctl _((int d, unsigned long request, void *argp));
+# endif
+int mknod _((const char *pathname, int mode, dev_t device));
+int nice _((int increment));
+int select _((int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval *timeout));
+#endif
+
+#if defined(DGUX) && defined(__STDC__)
+/* Just plain missing. */
+extern int getrlimit _((int resource, struct rlimit *rlp));
+extern int setrlimit _((int resource, const struct rlimit *rlp));
+extern int getrusage _((int who, struct rusage *rusage));
+extern int gettimeofday _((struct timeval *tv, struct timezone *tz));
+extern int wait3 _((union wait *wait_status, int options, struct rusage *rusage));
+extern int getdomainname _((char *name, int maxlength));
+extern int select _((int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval *timeout));
+#endif /* DGUX and __STDC__ */
+
+#ifdef __NeXT__
+extern pid_t getppid(void);
+#endif
+
+#if defined(__sun__) && !defined(__SVR4) /* SunOS */
+extern char *strerror _((int errnum));
+#endif
+
+/**************************************************/
+/*** prototypes for functions built in compat.c ***/
+#ifndef HAVE_STRSTR
+extern char *strstr _((const char *s, const char *t));
+#endif
+
+#ifndef HAVE_GETHOSTNAME
+extern int gethostname _((char *name, size_t namelen));
+#endif
+
+#ifndef HAVE_GETTIMEOFDAY
+extern int gettimeofday _((struct timeval *tv, struct timezone *tz));
+#endif
+
+#ifndef HAVE_DIFFTIME
+extern double difftime _((time_t t2, time_t t1));
+#endif
+
+#ifndef HAVE_STRERROR
+extern char *strerror _((int errnum));
+#endif
+
+/*** end of prototypes for functions in compat.c ***/
+/***************************************************/
+
+#ifndef HAVE_MEMMOVE
+extern void bcopy _((const void *, void *, int));
+#endif
diff --git a/Src/signals.c b/Src/signals.c
new file mode 100644
index 000000000..5dc19dd22
--- /dev/null
+++ b/Src/signals.c
@@ -0,0 +1,748 @@
+/*
+ * signals.c - signals handling code
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "signals.pro"
+
+/* Array describing the state of each signal: an element contains *
+ * 0 for the default action or some ZSIG_* flags ored together. */
+
+/**/
+int sigtrapped[VSIGCOUNT];
+
+/* trap functions for each signal */
+
+/**/
+List sigfuncs[VSIGCOUNT];
+
+/* Variables used by signal queueing */
+
+/**/
+int queueing_enabled, queue_front, queue_rear;
+/**/
+int signal_queue[MAX_QUEUE_SIZE];
+/**/
+sigset_t signal_mask_queue[MAX_QUEUE_SIZE];
+
+/* This is only used on machines that don't understand signal sets. *
+ * On SYSV machines this will represent the signals that are blocked *
+ * (held) using sighold. On machines which can't block signals at *
+ * all, we will simulate this by ignoring them and remembering them *
+ * in this variable. */
+#if !defined(POSIX_SIGNALS) && !defined(BSD_SIGNALS)
+static sigset_t blocked_set;
+#endif
+
+#ifdef POSIX_SIGNALS
+# define signal_jmp_buf sigjmp_buf
+# define signal_setjmp(b) sigsetjmp((b),1)
+# define signal_longjmp(b,n) siglongjmp((b),(n))
+#else
+# define signal_jmp_buf jmp_buf
+# define signal_setjmp(b) setjmp(b)
+# define signal_longjmp(b,n) longjmp((b),(n))
+#endif
+
+#ifdef NO_SIGNAL_BLOCKING
+# define signal_process(sig) signal_ignore(sig)
+# define signal_reset(sig) install_handler(sig)
+#else
+# define signal_process(sig) ;
+# define signal_reset(sig) ;
+#endif
+
+/* Install signal handler for given signal. *
+ * If possible, we want to make sure that interrupted *
+ * system calls are not restarted. */
+
+/**/
+void
+install_handler(int sig)
+{
+#ifdef POSIX_SIGNALS
+ struct sigaction act;
+
+ act.sa_handler = (SIGNAL_HANDTYPE) handler;
+ sigemptyset(&act.sa_mask); /* only block sig while in handler */
+ act.sa_flags = 0;
+# ifdef SA_INTERRUPT /* SunOS 4.x */
+ if (interact)
+ act.sa_flags |= SA_INTERRUPT; /* make sure system calls are not restarted */
+# endif
+ sigaction(sig, &act, (struct sigaction *)NULL);
+#else
+# ifdef BSD_SIGNALS
+ struct sigvec vec;
+
+ vec.sv_handler = (SIGNAL_HANDTYPE) handler;
+ vec.sv_mask = sigmask(sig); /* mask out this signal while in handler */
+# ifdef SV_INTERRUPT
+ vec.sv_flags = SV_INTERRUPT; /* make sure system calls are not restarted */
+# endif
+ sigvec(sig, &vec, (struct sigvec *)NULL);
+# else
+# ifdef SYSV_SIGNALS
+ /* we want sigset rather than signal because it will *
+ * block sig while in handler. signal usually doesn't */
+ sigset(sig, handler);
+# else /* NO_SIGNAL_BLOCKING (bummer) */
+ signal(sig, handler);
+
+# endif /* SYSV_SIGNALS */
+# endif /* BSD_SIGNALS */
+#endif /* POSIX_SIGNALS */
+}
+
+/* enable ^C interrupts */
+
+/**/
+void
+intr(void)
+{
+ if (interact)
+ install_handler(SIGINT);
+}
+
+#if 0
+/* disable ^C interrupts */
+
+/**/
+void
+nointr(void)
+{
+ if (interact)
+ signal_ignore(SIGINT);
+}
+#endif
+
+/* temporarily block ^C interrupts */
+
+/**/
+void
+holdintr(void)
+{
+ if (interact)
+ signal_block(signal_mask(SIGINT));
+}
+
+/* release ^C interrupts */
+
+/**/
+void
+noholdintr(void)
+{
+ if (interact)
+ signal_unblock(signal_mask(SIGINT));
+}
+
+/* create a signal mask containing *
+ * only the given signal */
+
+/**/
+sigset_t
+signal_mask(int sig)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ if (sig)
+ sigaddset(&set, sig);
+ return set;
+}
+
+/* Block the signals in the given signal *
+ * set. Return the old signal set. */
+
+/**/
+sigset_t
+signal_block(sigset_t set)
+{
+ sigset_t oset;
+
+#ifdef POSIX_SIGNALS
+ sigprocmask(SIG_BLOCK, &set, &oset);
+#else
+# ifdef BSD_SIGNALS
+ oset = sigblock(set);
+# else
+# ifdef SYSV_SIGNALS
+ int i;
+
+ oset = blocked_set;
+ for (i = 1; i <= NSIG; ++i) {
+ if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+ sigaddset(&blocked_set, i);
+ sighold(i);
+ }
+ }
+# else /* NO_SIGNAL_BLOCKING */
+/* We will just ignore signals if the system doesn't have *
+ * the ability to block them. */
+ int i;
+
+ oset = blocked_set;
+ for (i = 1; i <= NSIG; ++i) {
+ if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+ sigaddset(&blocked_set, i);
+ signal_ignore(i);
+ }
+ }
+# endif /* SYSV_SIGNALS */
+# endif /* BSD_SIGNALS */
+#endif /* POSIX_SIGNALS */
+
+ return oset;
+}
+
+/* Unblock the signals in the given signal *
+ * set. Return the old signal set. */
+
+/**/
+sigset_t
+signal_unblock(sigset_t set)
+{
+ sigset_t oset;
+
+#ifdef POSIX_SIGNALS
+ sigprocmask(SIG_UNBLOCK, &set, &oset);
+#else
+# ifdef BSD_SIGNALS
+ sigfillset(&oset);
+ oset = sigsetmask(oset);
+ sigsetmask(oset & ~set);
+# else
+# ifdef SYSV_SIGNALS
+ int i;
+
+ oset = blocked_set;
+ for (i = 1; i <= NSIG; ++i) {
+ if (sigismember(&set, i) && sigismember(&blocked_set, i)) {
+ sigdelset(&blocked_set, i);
+ sigrelse(i);
+ }
+ }
+# else /* NO_SIGNAL_BLOCKING */
+/* On systems that can't block signals, we are just ignoring them. So *
+ * to unblock signals, we just reenable the signal handler for them. */
+ int i;
+
+ oset = blocked_set;
+ for (i = 1; i <= NSIG; ++i) {
+ if (sigismember(&set, i) && sigismember(&blocked_set, i)) {
+ sigdelset(&blocked_set, i);
+ install_handler(i);
+ }
+ }
+# endif /* SYSV_SIGNALS */
+# endif /* BSD_SIGNALS */
+#endif /* POSIX_SIGNALS */
+
+ return oset;
+}
+
+/* set the process signal mask to *
+ * be the given signal mask */
+
+/**/
+sigset_t
+signal_setmask(sigset_t set)
+{
+ sigset_t oset;
+
+#ifdef POSIX_SIGNALS
+ sigprocmask(SIG_SETMASK, &set, &oset);
+#else
+# ifdef BSD_SIGNALS
+ oset = sigsetmask(set);
+# else
+# ifdef SYSV_SIGNALS
+ int i;
+
+ oset = blocked_set;
+ for (i = 1; i <= NSIG; ++i) {
+ if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+ sigaddset(&blocked_set, i);
+ sighold(i);
+ } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) {
+ sigdelset(&blocked_set, i);
+ sigrelse(i);
+ }
+ }
+# else /* NO_SIGNAL_BLOCKING */
+ int i;
+
+ oset = blocked_set;
+ for (i = 1; i < NSIG; ++i) {
+ if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
+ sigaddset(&blocked_set, i);
+ signal_ignore(i);
+ } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) {
+ sigdelset(&blocked_set, i);
+ install_handler(i);
+ }
+ }
+# endif /* SYSV_SIGNALS */
+# endif /* BSD_SIGNALS */
+#endif /* POSIX_SIGNALS */
+
+ return oset;
+}
+
+#if defined(NO_SIGNAL_BLOCKING)
+static int suspend_longjmp = 0;
+static signal_jmp_buf suspend_jmp_buf;
+#endif
+
+/**/
+int
+signal_suspend(int sig, int sig2)
+{
+ int ret;
+
+#ifdef POSIX_SIGNALS
+ sigset_t set;
+
+ sigfillset(&set);
+ sigdelset(&set, sig);
+ sigdelset(&set, SIGHUP); /* still don't know why we add this? */
+ if (sig2)
+ sigdelset(&set, sig2);
+ ret = sigsuspend(&set);
+#else
+# ifdef BSD_SIGNALS
+ sigset_t set;
+
+ sigfillset(&set);
+ sigdelset(&set, sig);
+ if (sig2)
+ sigdelset(&set, sig2);
+ ret = sigpause(set);
+# else
+# ifdef SYSV_SIGNALS
+ ret = sigpause(sig);
+
+# else /* NO_SIGNAL_BLOCKING */
+ /* need to use signal_longjmp to make this race-free *
+ * between the child_unblock() and pause() */
+ if (signal_setjmp(suspend_jmp_buf) == 0) {
+ suspend_longjmp = 1; /* we want to signal_longjmp after catching signal */
+ child_unblock(); /* do we need to unblock sig2 as well? */
+ ret = pause();
+ }
+ suspend_longjmp = 0; /* turn off using signal_longjmp since we are past *
+ * the pause() function. */
+# endif /* SYSV_SIGNALS */
+# endif /* BSD_SIGNALS */
+#endif /* POSIX_SIGNALS */
+
+ return ret;
+}
+
+/* What flavor of waitpid/wait3/wait shall we use? */
+
+#ifdef HAVE_WAITPID
+# define WAIT(pid, statusp, options) waitpid(pid, statusp, options)
+#else
+# ifdef HAVE_WAIT3
+# define WAIT(pid, statusp, options) wait3((void *) statusp, options, NULL)
+# else
+# define WAIT(pid, statusp, options) wait(statusp)
+# endif
+#endif
+
+/* the signal handler */
+
+/**/
+RETSIGTYPE
+handler(int sig)
+{
+ sigset_t newmask, oldmask;
+
+#if defined(NO_SIGNAL_BLOCKING)
+ int do_jump;
+ signal_jmp_buf jump_to;
+#endif
+
+ signal_process(sig);
+
+ sigfillset(&newmask);
+ oldmask = signal_block(newmask); /* Block all signals temporarily */
+
+#if defined(NO_SIGNAL_BLOCKING)
+ do_jump = suspend_longjmp; /* do we need to longjmp to signal_suspend */
+ suspend_longjmp = 0; /* In case a SIGCHLD somehow arrives */
+
+ if (sig == SIGCHLD) { /* Traps can cause nested child_suspend() */
+ if (do_jump)
+ jump_to = suspend_jmp_buf; /* Copy suspend_jmp_buf */
+ }
+#endif
+
+ if (queueing_enabled) { /* Are we queueing signals now? */
+ int temp_rear = ++queue_rear % MAX_QUEUE_SIZE;
+
+ DPUTS(temp_rear == queue_front, "BUG: signal queue full");
+ if (temp_rear != queue_front) { /* Make sure it's not full (extremely unlikely) */
+ queue_rear = temp_rear; /* ok, not full, so add to queue */
+ signal_queue[queue_rear] = sig; /* save signal caught */
+ signal_mask_queue[queue_rear] = oldmask; /* save current signal mask */
+ }
+ signal_reset(sig);
+ return;
+ }
+
+ signal_setmask(oldmask); /* Reset signal mask, signal traps ok now */
+
+ switch (sig) {
+ case SIGCHLD:
+
+ /* keep WAITING until no more child processes to reap */
+ for (;;)
+ cont: {
+ int old_errno = errno; /* save the errno, since WAIT may change it */
+ int status;
+ Job jn;
+ Process pn;
+ pid_t pid;
+ pid_t *procsubpid = &cmdoutpid;
+ int *procsubval = &cmdoutval;
+ struct execstack *es = exstack;
+
+ pid = WAIT(-1, &status, WNOHANG|WUNTRACED); /* reap the child process */
+
+ if (!pid) /* no more children to reap */
+ break;
+
+ /* check if child returned was from process substitution */
+ for (;;) {
+ if (pid == *procsubpid) {
+ *procsubpid = 0;
+ if (WIFSIGNALED(status))
+ *procsubval = (0200 | WTERMSIG(status));
+ else
+ *procsubval = WEXITSTATUS(status);
+ times(&shtms);
+ goto cont;
+ }
+ if (!es)
+ break;
+ procsubpid = &es->cmdoutpid;
+ procsubval = &es->cmdoutval;
+ es = es->next;
+ }
+
+ /* check for WAIT error */
+ if (pid == -1) {
+ if (errno != ECHILD)
+ zerr("wait failed: %e", NULL, errno);
+ errno = old_errno; /* WAIT changed errno, so restore the original */
+ break;
+ }
+
+ /* Find the process and job containing this pid and update it. */
+ if (findproc(pid, &jn, &pn)) {
+ update_process(pn, status);
+ update_job(jn);
+ } else {
+ /* If not found, update the shell record of time spent by
+ * children in sub processes anyway: otherwise, this
+ * will get added on to the next found process that terminates.
+ */
+ times(&shtms);
+ }
+ }
+ break;
+
+ case SIGHUP:
+ if (sigtrapped[SIGHUP])
+ dotrap(SIGHUP);
+ else {
+ stopmsg = 1;
+ zexit(SIGHUP, 1);
+ }
+ break;
+
+ case SIGINT:
+ if (sigtrapped[SIGINT])
+ dotrap(SIGINT);
+ else {
+ if ((isset(PRIVILEGED) || isset(RESTRICTED)) &&
+ isset(INTERACTIVE) && noerrexit < 0)
+ zexit(SIGINT, 1);
+ if (list_pipe || chline || simple_pline) {
+ breaks = loops;
+ errflag = 1;
+ inerrflush();
+ }
+ }
+ break;
+
+#ifdef SIGWINCH
+ case SIGWINCH:
+ adjustwinsize(); /* check window size and adjust */
+ if (sigtrapped[SIGWINCH])
+ dotrap(SIGWINCH);
+ break;
+#endif
+
+ case SIGALRM:
+ if (sigtrapped[SIGALRM]) {
+ int tmout;
+ dotrap(SIGALRM);
+ if ((tmout = getiparam("TMOUT")))
+ alarm(tmout); /* reset the alarm */
+ } else {
+ int idle = ttyidlegetfn(NULL);
+ int tmout = getiparam("TMOUT");
+ if (idle >= 0 && idle < tmout)
+ alarm(tmout - idle);
+ else {
+ errflag = noerrs = 0;
+ zerr("timeout", NULL, 0);
+ errflag = 0;
+ stopmsg = 1;
+ zexit(SIGALRM, 1);
+ }
+ }
+ break;
+
+ default:
+ dotrap(sig);
+ break;
+ } /* end of switch(sig) */
+
+ signal_reset(sig);
+
+/* This is used to make signal_suspend() race-free */
+#if defined(NO_SIGNAL_BLOCKING)
+ if (do_jump)
+ signal_longjmp(jump_to, 1);
+#endif
+
+} /* handler */
+
+
+/* SIGHUP any jobs left running */
+
+/**/
+void
+killrunjobs(int from_signal)
+{
+ int i, killed = 0;
+
+ if (unset(HUP))
+ return;
+ for (i = 1; i < MAXJOB; i++)
+ if ((from_signal || i != thisjob) && (jobtab[i].stat & STAT_LOCKED) &&
+ !(jobtab[i].stat & STAT_NOPRINT) &&
+ !(jobtab[i].stat & STAT_STOPPED)) {
+ if (killpg(jobtab[i].gleader, SIGHUP) != -1)
+ killed++;
+ }
+ if (killed)
+ zerr("warning: %d jobs SIGHUPed", NULL, killed);
+}
+
+
+/* send a signal to a job (simply involves kill if monitoring is on) */
+
+/**/
+int
+killjb(Job jn, int sig)
+{
+ Process pn;
+ int err = 0;
+
+ if (jobbing) {
+ if (jn->stat & STAT_SUPERJOB) {
+ if (sig == SIGCONT) {
+ for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
+ kill(pn->pid, sig);
+
+ for (pn = jn->procs; pn->next; pn = pn->next)
+ err = kill(pn->pid, sig);
+
+ return err;
+ }
+
+ killpg(jobtab[jn->other].gleader, sig);
+ return killpg(jn->gleader, sig);
+ }
+ else
+ return (killpg(jn->gleader, sig));
+ }
+ for (pn = jn->procs; pn; pn = pn->next)
+ if ((err = kill(pn->pid, sig)) == -1 && errno != ESRCH)
+ return -1;
+ return err;
+}
+
+/**/
+int
+settrap(int sig, List l)
+{
+ if (sig == -1)
+ return 1;
+ if (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)) {
+ zerr("can't trap SIG%s in interactive shells", sigs[sig], 0);
+ return 1;
+ }
+ if (sigfuncs[sig])
+ unsettrap(sig);
+ sigfuncs[sig] = l;
+ if (!l) {
+ sigtrapped[sig] = ZSIG_IGNORED;
+ if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+ sig != SIGWINCH &&
+#endif
+ sig != SIGCHLD)
+ signal_ignore(sig);
+ } else {
+ sigtrapped[sig] = ZSIG_TRAPPED;
+ if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+ sig != SIGWINCH &&
+#endif
+ sig != SIGCHLD)
+ install_handler(sig);
+ }
+ return 0;
+}
+
+/**/
+void
+unsettrap(int sig)
+{
+ int trapped;
+
+ if (sig == -1 || !(trapped = sigtrapped[sig]) ||
+ (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN))) {
+ return;
+ }
+ sigtrapped[sig] = 0;
+ if (sig == SIGINT && interact) {
+ /* PWS 1995/05/16: added test for interactive, also noholdintr() *
+ * as subshells ignoring SIGINT have it blocked from delivery */
+ intr();
+ noholdintr();
+ } else if (sig == SIGHUP)
+ install_handler(sig);
+ else if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+ sig != SIGWINCH &&
+#endif
+ sig != SIGCHLD)
+ signal_default(sig);
+ if (trapped & ZSIG_FUNC) {
+ char func[20];
+ HashNode hn;
+
+ sprintf(func, "TRAP%s", sigs[sig]);
+ if ((hn = shfunctab->removenode(shfunctab, func)))
+ shfunctab->freenode(hn);
+ } else if (sigfuncs[sig]) {
+ freestruct(sigfuncs[sig]);
+ sigfuncs[sig] = NULL;
+ }
+}
+
+/* Execute a trap function for a given signal, possibly
+ * with non-standard sigtrapped & sigfuncs values
+ */
+
+/**/
+void
+dotrapargs(int sig, int *sigtr, void *sigfn)
+{
+ LinkList args;
+ char *name, num[4];
+ int trapret = 0;
+ int obreaks = breaks;
+
+ /* if signal is being ignored or the trap function *
+ * is NULL, then return *
+ * *
+ * Also return if errflag is set. In fact, the code in the *
+ * function will test for this, but this way we keep status flags *
+ * intact without working too hard. Special cases (e.g. calling *
+ * a trap for SIGINT after the error flag was set) are handled *
+ * by the calling code. (PWS 1995/06/08). */
+ if ((*sigtr & ZSIG_IGNORED) || !sigfn || errflag)
+ return;
+
+ *sigtr |= ZSIG_IGNORED;
+
+ lexsave();
+ execsave();
+ breaks = 0;
+ if (*sigtr & ZSIG_FUNC) {
+ PERMALLOC {
+ args = newlinklist();
+ name = (char *) zalloc(5 + strlen(sigs[sig]));
+ sprintf(name, "TRAP%s", sigs[sig]);
+ addlinknode(args, name);
+ sprintf(num, "%d", sig);
+ addlinknode(args, num);
+ } LASTALLOC;
+ trapreturn = -1;
+ doshfunc(sigfn, args, 0, 1);
+ freelinklist(args, (FreeFunc) NULL);
+ zsfree(name);
+ } else HEAPALLOC {
+ execlist(dupstruct(sigfn), 1, 0);
+ } LASTALLOC;
+ if (trapreturn > 0)
+ trapret = trapreturn;
+ else if (errflag)
+ trapret = 1;
+ execrestore();
+ lexrestore();
+
+ if (trapret > 0) {
+ breaks = loops;
+ errflag = 1;
+ } else {
+ breaks += obreaks;
+ if (breaks > loops)
+ breaks = loops;
+ }
+
+ if (*sigtr != ZSIG_IGNORED)
+ *sigtr &= ~ZSIG_IGNORED;
+}
+
+/* Standard call to execute a trap for a given signal */
+
+/**/
+void
+dotrap(int sig)
+{
+ dotrapargs(sig, sigtrapped+sig, sigfuncs[sig]);
+}
diff --git a/Src/signals.h b/Src/signals.h
new file mode 100644
index 000000000..b6485e6b3
--- /dev/null
+++ b/Src/signals.h
@@ -0,0 +1,94 @@
+/*
+ * signals.h - header file for signals handling code
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#define SIGNAL_HANDTYPE RETSIGTYPE (*)_((int))
+
+#ifndef HAVE_KILLPG
+# define killpg(pgrp,sig) kill(-(pgrp),sig)
+#endif
+
+#define SIGZERR (SIGCOUNT+1)
+#define SIGDEBUG (SIGCOUNT+2)
+#define VSIGCOUNT (SIGCOUNT+3)
+#define SIGEXIT 0
+
+#ifdef SV_BSDSIG
+# define SV_INTERRUPT SV_BSDSIG
+#endif
+
+/* If not a POSIX machine, then we create our *
+ * own POSIX style signal sets functions. */
+#ifndef POSIX_SIGNALS
+# define sigemptyset(s) (*(s) = 0)
+# if NSIG == 32
+# define sigfillset(s) (*(s) = ~(sigset_t)0, 0)
+# else
+# define sigfillset(s) (*(s) = (1 << NSIG) - 1, 0)
+# endif
+# define sigaddset(s,n) (*(s) |= (1 << ((n) - 1)), 0)
+# define sigdelset(s,n) (*(s) &= ~(1 << ((n) - 1)), 0)
+# define sigismember(s,n) ((*(s) & (1 << ((n) - 1))) != 0)
+#endif /* ifndef POSIX_SIGNALS */
+
+#define child_block() signal_block(signal_mask(SIGCHLD))
+#define child_unblock() signal_unblock(signal_mask(SIGCHLD))
+#define child_suspend(S) signal_suspend(SIGCHLD, S)
+
+/* ignore a signal */
+#define signal_ignore(S) signal(S, SIG_IGN)
+
+/* return a signal to it default action */
+#define signal_default(S) signal(S, SIG_DFL)
+
+/* Use a circular queue to save signals caught during *
+ * critical sections of code. You call queue_signals to *
+ * start queueing, and unqueue_signals to process the *
+ * queue and stop queueing. Since the kernel doesn't *
+ * queue signals, it is probably overkill for zsh to do *
+ * this, but it shouldn't hurt anything to do it anyway. */
+
+/* Right now I'm queueing all signals, but maybe we only *
+ * need to queue SIGCHLD. Anybody know? */
+
+#define MAX_QUEUE_SIZE 16
+
+#define queue_signals() (queueing_enabled++)
+
+#define unqueue_signals() do { \
+ DPUTS(!queueing_enabled, "BUG: unqueue_signals called but not queueing"); \
+ if (!--queueing_enabled) { \
+ while (queue_front != queue_rear) { /* while signals in queue */ \
+ sigset_t oset; \
+ queue_front = (queue_front + 1) % MAX_QUEUE_SIZE; \
+ oset = signal_setmask(signal_mask_queue[queue_front]); \
+ handler(signal_queue[queue_front]); /* handle queued signal */ \
+ signal_setmask(oset); \
+ } \
+ } \
+} while (0)
diff --git a/Src/signames.awk b/Src/signames.awk
new file mode 100755
index 000000000..5d2eeb61e
--- /dev/null
+++ b/Src/signames.awk
@@ -0,0 +1,98 @@
+#
+# {g,n}awk script to generate signames.c
+#
+# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
+# Without 0 + hacks some nawks compare numbers as strings
+#
+/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ]*[1-9][0-9]*/ {
+ sigindex = index($0, "SIG")
+ sigtail = substr($0, sigindex, 80)
+ split(sigtail, tmp)
+ signam = substr(tmp[1], 4, 20)
+ signum = tmp[2]
+ if (sig[signum] == "") {
+ sig[signum] = signam
+ if (0 + max < 0 + signum && signum < 60)
+ max = signum
+ if (signam == "ABRT") { msg[signum] = "abort" }
+ if (signam == "ALRM") { msg[signum] = "alarm" }
+ if (signam == "BUS") { msg[signum] = "bus error" }
+ if (signam == "CHLD") { msg[signum] = "death of child" }
+ if (signam == "CLD") { msg[signum] = "death of child" }
+ if (signam == "CONT") { msg[signum] = "continued" }
+ if (signam == "EMT") { msg[signum] = "EMT instruction" }
+ if (signam == "FPE") { msg[signum] = "floating point exception" }
+ if (signam == "HUP") { msg[signum] = "hangup" }
+ if (signam == "ILL") { msg[signum] = "illegal hardware instruction" }
+ if (signam == "INFO") { msg[signum] = "status request from keyboard" }
+ if (signam == "INT") { msg[signum] = "interrupt" }
+ if (signam == "IO") { msg[signum] = "i/o ready" }
+ if (signam == "IOT") { msg[signum] = "IOT instruction" }
+ if (signam == "KILL") { msg[signum] = "killed" }
+ if (signam == "LOST") { msg[signum] = "resource lost" }
+ if (signam == "PIPE") { msg[signum] = "broken pipe" }
+ if (signam == "POLL") { msg[signum] = "pollable event occurred" }
+ if (signam == "PROF") { msg[signum] = "profile signal" }
+ if (signam == "PWR") { msg[signum] = "power fail" }
+ if (signam == "QUIT") { msg[signum] = "quit" }
+ if (signam == "SEGV") { msg[signum] = "segmentation fault" }
+ if (signam == "SYS") { msg[signum] = "invalid system call" }
+ if (signam == "TERM") { msg[signum] = "terminated" }
+ if (signam == "TRAP") { msg[signum] = "trace trap" }
+ if (signam == "URG") { msg[signum] = "urgent condition" }
+ if (signam == "USR1") { msg[signum] = "user-defined signal 1" }
+ if (signam == "USR2") { msg[signum] = "user-defined signal 2" }
+ if (signam == "VTALRM") { msg[signum] = "virtual time alarm" }
+ if (signam == "WINCH") { msg[signum] = "window size changed" }
+ if (signam == "XCPU") { msg[signum] = "cpu limit exceeded" }
+ if (signam == "XFSZ") { msg[signum] = "file size limit exceeded" }
+ }
+}
+
+END {
+ ps = "%s"
+ ifdstr = sprintf("# ifdef USE_SUSPENDED\n\t%csuspended%s%c,\n%s else\n\t%cstopped%s%c,\n# endif\n", 34, ps, 34, "#", 34, ps, 34)
+
+ printf "/** signames.c **/\n"
+ printf "/** architecture-customized signames.c for zsh **/\n"
+ printf "\n"
+ printf "#define SIGCOUNT\t%d\n", max
+ printf "\n"
+ printf "#include %czsh.mdh%c\n", 34, 34
+ printf "\n"
+ printf "/**/\n"
+ printf "char *sigmsg[SIGCOUNT+2] = {\n"
+ printf "\t%c%s%c,\n", 34, "done", 34
+
+ for (i = 1; i <= 0 + max; i++)
+ if (msg[i] == "") {
+ if (sig[i] == "")
+ printf("\t%c%c,\n", 34, 34)
+ else if (sig[i] == "STOP")
+ printf ifdstr, " (signal)", " (signal)"
+ else if (sig[i] == "TSTP")
+ printf ifdstr, "", ""
+ else if (sig[i] == "TTIN")
+ printf ifdstr, " (tty input)", " (tty input)"
+ else if (sig[i] == "TTOU")
+ printf ifdstr, " (tty output)", " (tty output)"
+ else
+ printf("\t%cSIG%s%c,\n", 34, sig[i], 34)
+ } else
+ printf("\t%c%s%c,\n", 34, msg[i], 34)
+ print "\tNULL"
+ print "};"
+ print ""
+ print "/**/"
+ printf "char *sigs[SIGCOUNT+4] = {\n"
+ printf("\t%cEXIT%c,\n", 34, 34)
+ for (i = 1; i <= 0 + max; i++)
+ if (sig[i] == "")
+ printf("\t%c%d%c,\n", 34, i, 34)
+ else
+ printf("\t%c%s%c,\n", 34, sig[i], 34)
+ printf("\t%cZERR%c,\n", 34, 34)
+ printf("\t%cDEBUG%c,\n", 34, 34)
+ print "\tNULL"
+ print "};"
+}
diff --git a/Src/signames1.awk b/Src/signames1.awk
new file mode 100644
index 000000000..27d21ac7b
--- /dev/null
+++ b/Src/signames1.awk
@@ -0,0 +1,19 @@
+# This is an awk script which finds out what the possibilities for
+# the signal names are, and dumps them out so that cpp can turn them
+# into numbers. Since we don't need to decide here what the
+# real signals are, we can afford to be generous about definitions,
+# in case the definitions are in terms of other definitions.
+# However, we need to avoid definitions with parentheses, which will
+# mess up the syntax.
+BEGIN { printf "#include <signal.h>\n\n" }
+
+/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ][\t ]*[^(\t ]/ {
+ sigindex = index($0, "SIG")
+ sigtail = substr($0, sigindex, 80)
+ split(sigtail, tmp)
+ signam = substr(tmp[1], 4, 20)
+ if (substr($0, sigindex-1, 1) == "_")
+ printf("XXNAMES XXSIG%s _SIG%s\n", signam, signam)
+ else
+ printf("XXNAMES XXSIG%s SIG%s\n", signam, signam)
+}
diff --git a/Src/signames2.awk b/Src/signames2.awk
new file mode 100644
index 000000000..3aea76ee3
--- /dev/null
+++ b/Src/signames2.awk
@@ -0,0 +1,100 @@
+#
+# {g,n}awk script to generate signames.c
+# This version relies on the previous output of the preprocessor
+# on sigtmp.c, sigtmp.out, which is in turn generated by signames1.awk.
+#
+# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems
+# Without 0 + hacks some nawks compare numbers as strings
+#
+/^XXNAMES XXSIG[A-Z][A-Z0-9]* [1-9][0-9]*/ {
+ sigindex = index($0, "SIG")
+ sigtail = substr($0, sigindex, 80)
+ split(sigtail, tmp)
+ signam = substr(tmp[1], 4, 20)
+ signum = tmp[2]
+ if (sig[signum] == "") {
+ sig[signum] = signam
+ if (0 + max < 0 + signum && signum < 60)
+ max = signum
+ if (signam == "ABRT") { msg[signum] = "abort" }
+ if (signam == "ALRM") { msg[signum] = "alarm" }
+ if (signam == "BUS") { msg[signum] = "bus error" }
+ if (signam == "CHLD") { msg[signum] = "death of child" }
+ if (signam == "CLD") { msg[signum] = "death of child" }
+ if (signam == "CONT") { msg[signum] = "continued" }
+ if (signam == "EMT") { msg[signum] = "EMT instruction" }
+ if (signam == "FPE") { msg[signum] = "floating point exception" }
+ if (signam == "HUP") { msg[signum] = "hangup" }
+ if (signam == "ILL") { msg[signum] = "illegal hardware instruction" }
+ if (signam == "INFO") { msg[signum] = "status request from keyboard" }
+ if (signam == "INT") { msg[signum] = "interrupt" }
+ if (signam == "IO") { msg[signum] = "i/o ready" }
+ if (signam == "IOT") { msg[signum] = "IOT instruction" }
+ if (signam == "KILL") { msg[signum] = "killed" }
+ if (signam == "LOST") { msg[signum] = "resource lost" }
+ if (signam == "PIPE") { msg[signum] = "broken pipe" }
+ if (signam == "POLL") { msg[signum] = "pollable event occurred" }
+ if (signam == "PROF") { msg[signum] = "profile signal" }
+ if (signam == "PWR") { msg[signum] = "power fail" }
+ if (signam == "QUIT") { msg[signum] = "quit" }
+ if (signam == "SEGV") { msg[signum] = "segmentation fault" }
+ if (signam == "SYS") { msg[signum] = "invalid system call" }
+ if (signam == "TERM") { msg[signum] = "terminated" }
+ if (signam == "TRAP") { msg[signum] = "trace trap" }
+ if (signam == "URG") { msg[signum] = "urgent condition" }
+ if (signam == "USR1") { msg[signum] = "user-defined signal 1" }
+ if (signam == "USR2") { msg[signum] = "user-defined signal 2" }
+ if (signam == "VTALRM") { msg[signum] = "virtual time alarm" }
+ if (signam == "WINCH") { msg[signum] = "window size changed" }
+ if (signam == "XCPU") { msg[signum] = "cpu limit exceeded" }
+ if (signam == "XFSZ") { msg[signum] = "file size limit exceeded" }
+ }
+}
+
+END {
+ ps = "%s"
+ ifdstr = sprintf("# ifdef USE_SUSPENDED\n\t%csuspended%s%c,\n%s else\n\t%cstopped%s%c,\n# endif\n", 34, ps, 34, "#", 34, ps, 34)
+
+ printf "/** signames.c **/\n"
+ printf "/** architecture-customized signames.c for zsh **/\n"
+ printf "\n"
+ printf "#define SIGCOUNT\t%d\n", max
+ printf "\n"
+ printf "#include %czsh.mdh%c\n", 34, 34
+ printf "\n"
+ printf "/**/\n"
+ printf "char *sigmsg[SIGCOUNT+2] = {\n"
+ printf "\t%c%s%c,\n", 34, "done", 34
+
+ for (i = 1; i <= 0 + max; i++)
+ if (msg[i] == "") {
+ if (sig[i] == "")
+ printf("\t%c%c,\n", 34, 34)
+ else if (sig[i] == "STOP")
+ printf ifdstr, " (signal)", " (signal)"
+ else if (sig[i] == "TSTP")
+ printf ifdstr, "", ""
+ else if (sig[i] == "TTIN")
+ printf ifdstr, " (tty input)", " (tty input)"
+ else if (sig[i] == "TTOU")
+ printf ifdstr, " (tty output)", " (tty output)"
+ else
+ printf("\t%cSIG%s%c,\n", 34, sig[i], 34)
+ } else
+ printf("\t%c%s%c,\n", 34, msg[i], 34)
+ print "\tNULL"
+ print "};"
+ print ""
+ print "/**/"
+ printf "char *sigs[SIGCOUNT+4] = {\n"
+ printf("\t%cEXIT%c,\n", 34, 34)
+ for (i = 1; i <= 0 + max; i++)
+ if (sig[i] == "")
+ printf("\t%c%d%c,\n", 34, i, 34)
+ else
+ printf("\t%c%s%c,\n", 34, sig[i], 34)
+ printf("\t%cZERR%c,\n", 34, 34)
+ printf("\t%cDEBUG%c,\n", 34, 34)
+ print "\tNULL"
+ print "};"
+}
diff --git a/Src/subst.c b/Src/subst.c
new file mode 100644
index 000000000..8f840d266
--- /dev/null
+++ b/Src/subst.c
@@ -0,0 +1,1773 @@
+/*
+ * subst.c - various substitutions
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "subst.pro"
+
+/**/
+char nulstring[] = {Nularg, '\0'};
+
+/* Do substitutions before fork. These are:
+ * - Process substitution: <(...), >(...), =(...)
+ * - Parameter substitution
+ * - Command substitution
+ * Followed by
+ * - Quote removal
+ * - Brace expansion
+ * - Tilde and equals substitution
+ *
+ * Bits 0 and 1 of flags are used in filesub.
+ * bit 0 is set when we are doing MAGIC_EQUALSUBST or normal
+ * assignment but not a typeset.
+ * bit 1 is set on a real assignment (both typeset and normal).
+ * bit 2 is a flag to paramsubst (single word sub)
+ */
+
+/**/
+void
+prefork(LinkList list, int flags)
+{
+ LinkNode node;
+
+ MUSTUSEHEAP("prefork");
+ for (node = firstnode(list); node; incnode(node)) {
+ char *str, *str3;
+
+ str = str3 = (char *)getdata(node);
+ if ((*str == Inang || *str == Outang || *str == Equals) &&
+ str[1] == Inpar) {
+ if (*str == Inang || *str == Outang)
+ setdata(node, (void *) getproc(str)); /* <(...) or >(...) */
+ else
+ setdata(node, (void *) getoutputfile(str)); /* =(...) */
+ if (!getdata(node))
+ return;
+ } else {
+ if (isset(SHFILEEXPANSION))
+ filesub((char **)getaddrdata(node), flags & 3);
+ if (!(node = stringsubst(list, node, flags & 4)))
+ return;
+ }
+ }
+ for (node = firstnode(list); node; incnode(node)) {
+ if (*(char *)getdata(node)) {
+ remnulargs(getdata(node));
+ if (unset(IGNOREBRACES) && !(flags & 4))
+ while (hasbraces(getdata(node)))
+ xpandbraces(list, &node);
+ if (unset(SHFILEEXPANSION))
+ filesub((char **)getaddrdata(node), flags & 3);
+ } else if (!(flags & 4))
+ uremnode(list, node);
+ if (errflag)
+ return;
+ }
+}
+
+/**/
+static LinkNode
+stringsubst(LinkList list, LinkNode node, int ssub)
+{
+ int qt;
+ char *str3 = (char *)getdata(node);
+ char *str = str3;
+
+ while (!errflag && *str) {
+ if ((qt = *str == Qstring) || *str == String)
+ if (str[1] == Inpar) {
+ str++;
+ goto comsub;
+ } else if (str[1] == Inbrack) {
+ /* $[...] */
+ char *str2 = str;
+ str2++;
+ if (skipparens(Inbrack, Outbrack, &str2)) {
+ zerr("closing bracket missing", NULL, 0);
+ return NULL;
+ }
+ str2[-1] = *str = '\0';
+ str = arithsubst(str + 2, &str3, str2);
+ setdata(node, (void *) str3);
+ continue;
+ } else if (str[1] == Snull) {
+ str = getkeystring(str, NULL, 4, NULL);
+ continue;
+ } else {
+ node = paramsubst(list, node, &str, qt, ssub);
+ if (errflag || !node)
+ return NULL;
+ str3 = (char *)getdata(node);
+ continue;
+ }
+ else if ((qt = *str == Qtick) || *str == Tick)
+ comsub: {
+ LinkList pl;
+ char *s, *str2 = str;
+ char endchar;
+ int l1, l2;
+
+ if (*str == Inpar) {
+ endchar = Outpar;
+ str[-1] = '\0';
+ if (skipparens(Inpar, Outpar, &str))
+ DPUTS(1, "BUG: parse error in command substitution");
+ str--;
+ } else {
+ endchar = *str;
+ *str = '\0';
+
+ while (*++str != endchar)
+ DPUTS(!*str, "BUG: parse error in command substitution");
+ }
+ *str++ = '\0';
+ if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') {
+ /* Math substitution of the form $((...)) */
+ str = arithsubst(str2 + 1, &str3, str);
+ setdata(node, (void *) str3);
+ continue;
+ }
+
+ /* It is a command substitution, which will be parsed again *
+ * by the lexer, so we untokenize it first, but we cannot use *
+ * untokenize() since in the case of `...` some Bnulls should *
+ * be left unchanged. Note that the lexer doesn't tokenize *
+ * the body of a command substitution so if there are some *
+ * tokens here they are from a ${(e)~...} substitution. */
+ for (str = str2; *++str; )
+ if (itok(*str) && *str != Nularg &&
+ !(endchar != Outpar && *str == Bnull &&
+ (str[1] == '$' || str[1] == '\\' || str[1] == '`' ||
+ (qt && str[1] == '"'))))
+ *str = ztokens[*str - Pound];
+ str++;
+ if (!(pl = getoutput(str2 + 1, qt || ssub))) {
+ zerr("parse error in command substitution", NULL, 0);
+ return NULL;
+ }
+ if (endchar == Outpar)
+ str2--;
+ if (!(s = (char *) ugetnode(pl))) {
+ str = strcpy(str2, str);
+ continue;
+ }
+ if (!qt && ssub && isset(GLOBSUBST))
+ tokenize(s);
+ l1 = str2 - str3;
+ l2 = strlen(s);
+ if (nonempty(pl)) {
+ LinkNode n = lastnode(pl);
+ str2 = (char *) ncalloc(l1 + l2 + 1);
+ strcpy(str2, str3);
+ strcpy(str2 + l1, s);
+ setdata(node, str2);
+ insertlinklist(pl, node, list);
+ s = (char *) getdata(node = n);
+ l1 = 0;
+ l2 = strlen(s);
+ }
+ str2 = (char *) ncalloc(l1 + l2 + strlen(str) + 1);
+ if (l1)
+ strcpy(str2, str3);
+ strcpy(str2 + l1, s);
+ str = strcpy(str2 + l1 + l2, str);
+ str3 = str2;
+ setdata(node, str3);
+ continue;
+ }
+ str++;
+ }
+ return errflag ? NULL : node;
+}
+
+/**/
+void
+globlist(LinkList list)
+{
+ LinkNode node, next;
+
+ badcshglob = 0;
+ for (node = firstnode(list); !errflag && node; node = next) {
+ next = nextnode(node);
+ glob(list, node);
+ }
+ if (badcshglob == 1)
+ zerr("no match", NULL, 0);
+}
+
+/* perform substitution on a single word */
+
+/**/
+void
+singsub(char **s)
+{
+ LinkList foo;
+
+ foo = newlinklist();
+ addlinknode(foo, *s);
+ prefork(foo, 4);
+ if (errflag)
+ return;
+ *s = (char *) ugetnode(foo);
+ DPUTS(nonempty(foo), "BUG: singsub() produced more than one word!");
+}
+
+/* Perform substitution on a single word. Unlike with singsub, the *
+ * result can have more than one words. A single word result is sroted *
+ * in *s and *isarr is set to zero; otherwise *isarr is set to 1 and *
+ * the result is stored in *a. If `a' is zero a multiple word result is *
+ * joined using sep or the IFS parameter if sep is zero and the result *
+ * is returned in *s. The return value is true iff the expansion *
+ * resulted in an empty list */
+
+/**/
+static int
+multsub(char **s, char ***a, int *isarr, char *sep)
+{
+ LinkList foo;
+ int l;
+ char **r, **p;
+
+ foo = newlinklist();
+ addlinknode(foo, *s);
+ prefork(foo, 0);
+ if (errflag) {
+ if (isarr)
+ *isarr = 0;
+ return 0;
+ }
+ if ((l = countlinknodes(foo)) > 1) {
+ p = r = ncalloc((l + 1) * sizeof(char*));
+ while (nonempty(foo))
+ *p++ = (char *)ugetnode(foo);
+ *p = NULL;
+ if (a) {
+ *a = r;
+ *isarr = 1;
+ return 0;
+ }
+ *s = sepjoin(r, NULL);
+ return 0;
+ }
+ if (l)
+ *s = (char *) ugetnode(foo);
+ else
+ *s = dupstring("");
+ if (isarr)
+ *isarr = 0;
+ return !l;
+}
+
+/* ~, = subs: assign = 2 => typeset; assign = 1 => something that looks
+ like an assignment but may not be; assign = 3 => normal assignment */
+
+/**/
+void
+filesub(char **namptr, int assign)
+{
+ char *sub = NULL, *str, *ptr;
+ int len;
+
+ filesubstr(namptr, assign);
+
+ if (!assign)
+ return;
+
+ if (assign < 3)
+ if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) {
+ if (assign == 1)
+ for (ptr = *namptr; ptr != sub; ptr++)
+ if (!iident(*ptr) && !INULL(*ptr))
+ return;
+ str = sub + 1;
+ if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
+ sub[1] = '\0';
+ *namptr = dyncat(*namptr, str);
+ }
+ } else
+ return;
+
+ ptr = *namptr;
+ while ((sub = strchr(ptr, ':'))) {
+ str = sub + 1;
+ len = sub - *namptr;
+ if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
+ sub[1] = '\0';
+ *namptr = dyncat(*namptr, str);
+ }
+ ptr = *namptr + len + 1;
+ }
+}
+
+/**/
+int
+filesubstr(char **namptr, int assign)
+{
+#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') )
+#define isend2(c) ( !(c) || (c)==Inpar || (assign && (c)==':') )
+ char *str = *namptr;
+
+ if (*str == Tilde && str[1] != '=' && str[1] != Equals) {
+ char *ptr;
+ int val;
+
+ val = zstrtol(str + 1, &ptr, 10);
+ if (isend(str[1])) { /* ~ */
+ *namptr = dyncat(home, str + 1);
+ return 1;
+ } else if (str[1] == '+' && isend(str[2])) { /* ~+ */
+ *namptr = dyncat(pwd, str + 2);
+ return 1;
+ } else if (str[1] == '-' && isend(str[2])) { /* ~- */
+ char *tmp;
+ *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2);
+ return 1;
+ } else if (!inblank(str[1]) && isend(*ptr) &&
+ (!idigit(str[1]) || (ptr - str < 4))) {
+ char *ds;
+
+ if (val < 0)
+ val = -val;
+ ds = dstackent(str[1], val);
+ if (!ds)
+ return 0;
+ *namptr = dyncat(ds, ptr);
+ return 1;
+ } else if (iuser(str[1])) { /* ~foo */
+ char *ptr, *hom, save;
+
+ for (ptr = ++str; *ptr && iuser(*ptr); ptr++);
+ save = *ptr;
+ if (!isend(save))
+ return 0;
+ *ptr = 0;
+ if (!(hom = getnameddir(str))) {
+ if (isset(NOMATCH))
+ zerr("no such user or named directory: %s", str, 0);
+ *ptr = save;
+ return 0;
+ }
+ *ptr = save;
+ *namptr = dyncat(hom, ptr);
+ return 1;
+ }
+ } else if (*str == Equals && isset(EQUALS) && str[1]) { /* =foo */
+ char sav, *pp, *cnam;
+
+ for (pp = str + 1; !isend2(*pp); pp++);
+ sav = *pp;
+ *pp = 0;
+ if (!(cnam = findcmd(str + 1))) {
+ Alias a = (Alias) aliastab->getnode(aliastab, str + 1);
+
+ if (a)
+ cnam = ztrdup(a->text);
+ else {
+ if (isset(NOMATCH))
+ zerr("%s not found", str + 1, 0);
+ return 0;
+ }
+ }
+ *namptr = dupstring(cnam);
+ zsfree(cnam);
+ if (sav) {
+ *pp = sav;
+ *namptr = dyncat(*namptr, pp);
+ }
+ return 1;
+ }
+ return 0;
+#undef isend
+#undef isend2
+}
+
+/**/
+static char *
+strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub)
+{
+ int pl = pe - pb;
+ char *dest = ncalloc(pl + l + (s ? strlen(s) : 0) + 1);
+
+ *d = dest;
+ strncpy(dest, pb, pl);
+ dest += pl;
+ strcpy(dest, src);
+ if (glbsub)
+ tokenize(dest);
+ dest += l;
+ if (s)
+ strcpy(dest, s);
+ return dest;
+}
+
+typedef int (*CompareFn) _((const void *, const void *));
+
+/**/
+int
+strpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+ return strcoll(*(char **)a, *(char **)b);
+#else
+ return strcmp(*(char **)a, *(char **)b);
+#endif
+}
+
+/**/
+int
+invstrpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+ return -strcoll(*(char **)a, *(char **)b);
+#else
+ return -strcmp(*(char **)a, *(char **)b);
+#endif
+}
+
+/**/
+int
+cstrpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+ VARARR(char, c, strlen(*(char **) a) + 1);
+ VARARR(char, d, strlen(*(char **) b) + 1);
+ char *s, *t;
+ int cmp;
+
+ for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
+ for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
+
+ cmp = strcoll(c, d);
+
+ return cmp;
+#else
+ char *c = *(char **)a, *d = *(char **)b;
+
+ for (; *c && tulower(*c) == tulower(*d); c++, d++);
+
+ return (int)STOUC(tulower(*c)) - (int)STOUC(tulower(*d));
+#endif
+}
+
+/**/
+int
+invcstrpcmp(const void *a, const void *b)
+{
+#ifdef HAVE_STRCOLL
+ VARARR(char, c, strlen(*(char **) a) + 1);
+ VARARR(char, d, strlen(*(char **) b) + 1);
+ char *s, *t;
+ int cmp;
+
+ for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
+ for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
+
+ cmp = strcoll(c, d);
+
+ return -cmp;
+#else
+ char *c = *(char **)a, *d = *(char **)b;
+
+ for (; *c && tulower(*c) == tulower(*d); c++, d++);
+
+ return (int)STOUC(tulower(*d)) - (int)STOUC(tulower(*c));
+#endif
+}
+
+/**/
+static char *
+dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char *premul, char *postmul)
+{
+ char def[3], *ret, *t, *r;
+ int ls, ls2, lpreone, lpostone, lpremul, lpostmul, lr, f, m, c, cc;
+
+ def[0] = *ifs ? *ifs : ' ';
+ def[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0';
+ def[2] = '\0';
+ if (preone && !*preone)
+ preone = def;
+ if (postone && !*postone)
+ postone = def;
+ if (!premul || !*premul)
+ premul = def;
+ if (!postmul || !*postmul)
+ postmul = def;
+
+ ls = strlen(str);
+ lpreone = preone ? strlen(preone) : 0;
+ lpostone = postone ? strlen(postone) : 0;
+ lpremul = strlen(premul);
+ lpostmul = strlen(postmul);
+
+ lr = prenum + postnum;
+
+ if (lr == ls)
+ return str;
+
+ r = ret = (char *)halloc(lr + 1);
+
+ if (prenum) {
+ if (postnum) {
+ ls2 = ls / 2;
+
+ f = prenum - ls2;
+ if (f <= 0)
+ for (str -= f, c = prenum; c--; *r++ = *str++);
+ else {
+ if (f <= lpreone)
+ for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
+ else {
+ f -= lpreone;
+ if ((m = f % lpremul))
+ for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
+ for (cc = f / lpremul; cc--;)
+ for (c = lpremul, t = premul; c--; *r++ = *t++);
+ for (c = lpreone; c--; *r++ = *preone++);
+ }
+ for (c = ls2; c--; *r++ = *str++);
+ }
+ ls2 = ls - ls2;
+ f = postnum - ls2;
+ if (f <= 0)
+ for (c = postnum; c--; *r++ = *str++);
+ else {
+ for (c = ls2; c--; *r++ = *str++);
+ if (f <= lpostone)
+ for (c = f; c--; *r++ = *postone++);
+ else {
+ f -= lpostone;
+ for (c = lpostone; c--; *r++ = *postone++);
+ for (cc = f / lpostmul; cc--;)
+ for (c = lpostmul, t = postmul; c--; *r++ = *t++);
+ if ((m = f % lpostmul))
+ for (; m--; *r++ = *postmul++);
+ }
+ }
+ } else {
+ f = prenum - ls;
+ if (f <= 0)
+ for (c = prenum, str -= f; c--; *r++ = *str++);
+ else {
+ if (f <= lpreone)
+ for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
+ else {
+ f -= lpreone;
+ if ((m = f % lpremul))
+ for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
+ for (cc = f / lpremul; cc--;)
+ for (c = lpremul, t = premul; c--; *r++ = *t++);
+ for (c = lpreone; c--; *r++ = *preone++);
+ }
+ for (c = ls; c--; *r++ = *str++);
+ }
+ }
+ } else if (postnum) {
+ f = postnum - ls;
+ if (f <= 0)
+ for (c = postnum; c--; *r++ = *str++);
+ else {
+ for (c = ls; c--; *r++ = *str++);
+ if (f <= lpostone)
+ for (c = f; c--; *r++ = *postone++);
+ else {
+ f -= lpostone;
+ for (c = lpostone; c--; *r++ = *postone++);
+ for (cc = f / lpostmul; cc--;)
+ for (c = lpostmul, t = postmul; c--; *r++ = *t++);
+ if ((m = f % lpostmul))
+ for (; m--; *r++ = *postmul++);
+ }
+ }
+ }
+ *r = '\0';
+
+ return ret;
+}
+
+/**/
+char *
+get_strarg(char *s)
+{
+ char t = *s++;
+
+ if (!t)
+ return s - 1;
+
+ switch (t) {
+ case '(':
+ t = ')';
+ break;
+ case '[':
+ t = ']';
+ break;
+ case '{':
+ t = '}';
+ break;
+ case '<':
+ t = '>';
+ break;
+ case Inpar:
+ t = Outpar;
+ break;
+ case Inang:
+ t = Outang;
+ break;
+ case Inbrace:
+ t = Outbrace;
+ break;
+ case Inbrack:
+ t = Outbrack;
+ break;
+ }
+
+ while (*s && *s != t)
+ s++;
+
+ return s;
+}
+
+/**/
+static int
+get_intarg(char **s)
+{
+ char *t = get_strarg(*s + 1);
+ char *p, sav;
+ long ret;
+
+ if (!*t)
+ return -1;
+ sav = *t;
+ *t = '\0';
+ p = dupstring(*s + 2);
+ *s = t;
+ *t = sav;
+ if (parsestr(p))
+ return -1;
+ singsub(&p);
+ if (errflag)
+ return -1;
+ ret = matheval(p);
+ if (errflag)
+ return -1;
+ if (ret < 0)
+ ret = -ret;
+ return ret < 0 ? -ret : ret;
+}
+
+/* parameter substitution */
+
+#define isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring)
+#define isbrack(c) ((c) == '[' || (char)(c) == Inbrack)
+
+/**/
+LinkNode
+paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
+{
+ char *aptr = *str;
+ char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n);
+ int colf; /* != 0 means we found a colon after the name */
+ int doub = 0; /* != 0 means we have %%, not %, or ##, not # */
+ int isarr = 0;
+ int plan9 = isset(RCEXPANDPARAM);
+ int globsubst = isset(GLOBSUBST);
+ int getlen = 0;
+ int whichlen = 0;
+ int chkset = 0;
+ int vunset = 0;
+ int spbreak = isset(SHWORDSPLIT) && !ssub && !qt;
+ char *val = NULL, **aval = NULL;
+ unsigned int fwidth = 0;
+ Value v;
+ int flags = 0;
+ int flnum = 0;
+ int substr = 0;
+ int sortit = 0, casind = 0;
+ int casmod = 0;
+ char *sep = NULL, *spsep = NULL;
+ char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL;
+ long prenum = 0, postnum = 0;
+ int copied = 0;
+ int arrasg = 0;
+ int eval = 0;
+ int nojoin = 0;
+ char inbrace = 0; /* != 0 means ${...}, otherwise $... */
+
+ *s++ = '\0';
+ if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' &&
+ *s != '!' && *s != '$' && *s != String && *s != Qstring &&
+ *s != '?' && *s != Quest && *s != '_' &&
+ *s != '*' && *s != Star && *s != '@' && *s != '{' &&
+ *s != Inbrace && *s != '=' && *s != Equals && *s != Hat &&
+ *s != '^' && *s != '~' && *s != Tilde && *s != '+') {
+ s[-1] = '$';
+ *str = s;
+ return n;
+ }
+ DPUTS(*s == '{', "BUG: inbrace == '{' in paramsubst()");
+ if (*s == Inbrace) {
+ inbrace = 1;
+ s++;
+ if (*s == '(' || *s == Inpar) {
+ char *t, sav;
+ int tt = 0;
+ long num;
+ int escapes = 0;
+ int klen;
+#define UNTOK_AND_ESCAPE(X) {\
+ untokenize(X = dupstring(s + 1));\
+ if (escapes) {\
+ X = getkeystring(X, &klen, 3, NULL);\
+ X = metafy(X, klen, META_HREALLOC);\
+ }\
+ }
+
+ for (s++; *s != ')' && *s != Outpar; s++, tt = 0) {
+ switch (*s) {
+ case ')':
+ case Outpar:
+ break;
+ case 'A':
+ arrasg = 1;
+ break;
+ case '@':
+ nojoin = 1;
+ break;
+ case 'M':
+ flags |= 8;
+ break;
+ case 'R':
+ flags |= 16;
+ break;
+ case 'B':
+ flags |= 32;
+ break;
+ case 'E':
+ flags |= 64;
+ break;
+ case 'N':
+ flags |= 128;
+ break;
+ case 'S':
+ substr = 1;
+ break;
+ case 'I':
+ flnum = get_intarg(&s);
+ if (flnum < 0)
+ goto flagerr;
+ break;
+
+ case 'L':
+ casmod = 2;
+ break;
+ case 'U':
+ casmod = 1;
+ break;
+ case 'C':
+ casmod = 3;
+ break;
+
+ case 'o':
+ sortit = 1;
+ break;
+ case 'O':
+ sortit = 2;
+ break;
+ case 'i':
+ casind = 1;
+ break;
+ case 'e':
+ eval = 1;
+ break;
+
+ case 'c':
+ whichlen = 1;
+ break;
+ case 'w':
+ whichlen = 2;
+ break;
+ case 'W':
+ whichlen = 3;
+ break;
+
+ case 'f':
+ spsep = "\n";
+ break;
+ case 'F':
+ sep = "\n";
+ break;
+
+ case 's':
+ tt = 1;
+ /* fall through */
+ case 'j':
+ t = get_strarg(++s);
+ if (*t) {
+ sav = *t;
+ *t = '\0';
+ if (tt)
+ UNTOK_AND_ESCAPE(spsep)
+ else
+ UNTOK_AND_ESCAPE(sep)
+ *t = sav;
+ s = t;
+ } else
+ goto flagerr;
+ break;
+
+ case 'l':
+ tt = 1;
+ /* fall through */
+ case 'r':
+ sav = s[1];
+ num = get_intarg(&s);
+ if (num < 0)
+ goto flagerr;
+ if (tt)
+ prenum = num;
+ else
+ postnum = num;
+ if (s[1] != sav)
+ break;
+ t = get_strarg(++s);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ if (tt)
+ UNTOK_AND_ESCAPE(premul)
+ else
+ UNTOK_AND_ESCAPE(postmul)
+ *t = sav;
+ sav = *s;
+ s = t + 1;
+ if (*s != sav) {
+ s--;
+ break;
+ }
+ t = get_strarg(s);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ if (tt)
+ UNTOK_AND_ESCAPE(preone)
+ else
+ UNTOK_AND_ESCAPE(postone)
+ *t = sav;
+ s = t;
+ break;
+
+ case 'p':
+ escapes = 1;
+ break;
+
+ default:
+ flagerr:
+ zerr("error in flags", NULL, 0);
+ return NULL;
+ }
+ }
+ s++;
+ }
+ }
+ if (sortit)
+ sortit += (casind << 1);
+
+ if (!premul)
+ premul = " ";
+ if (!postmul)
+ postmul = " ";
+
+ for (;;) {
+ if (*s == '^' || *s == Hat) {
+ if (*++s == '^' || *s == Hat) {
+ plan9 = 0;
+ s++;
+ } else
+ plan9 = 1;
+ } else if (*s == '=' || *s == Equals) {
+ if (*++s == '=' || *s == Equals) {
+ spbreak = 0;
+ s++;
+ } else
+ spbreak = 1;
+ } else if ((*s == '#' || *s == Pound) &&
+ (iident(s[1])
+ || s[1] == '*' || s[1] == Star || s[1] == '@'
+ || (isstring(s[1]) && (s[2] == Inbrace || s[2] == Inpar))))
+ getlen = 1 + whichlen, s++;
+ else if (*s == '~' || *s == Tilde) {
+ if (*++s == '~' || *s == Tilde) {
+ globsubst = 0;
+ s++;
+ } else
+ globsubst = 1;
+ } else if (*s == '+')
+ if (iident(s[1]))
+ chkset = 1, s++;
+ else if (!inbrace) {
+ *aptr = '$';
+ *str = aptr + 1;
+ return n;
+ } else {
+ zerr("bad substitution", NULL, 0);
+ return NULL;
+ }
+ else
+ break;
+ }
+ globsubst = globsubst && !qt;
+
+ idbeg = s;
+ if (s[-1] && isstring(*s) && (s[1] == Inbrace || s[1] == Inpar)) {
+ int sav;
+ int quoted = *s == Qstring;
+
+ val = s++;
+ skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
+ sav = *s;
+ *s = 0;
+ if (multsub(&val, &aval, &isarr, NULL) && quoted) {
+ isarr = -1;
+ aval = alloc(sizeof(char *));
+ }
+ if (isarr)
+ isarr = -1;
+ copied = 1;
+ *s = sav;
+ v = (Value) NULL;
+ } else if (!(v = getvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1)))
+ vunset = 1;
+ while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) {
+ if (!v) {
+ Param pm;
+ char *os = s;
+
+ if (!isbrack(*s))
+ break;
+ if (vunset) {
+ val = dupstring("");
+ isarr = 0;
+ }
+ pm = createparam(nulstring, isarr ? PM_ARRAY : PM_SCALAR);
+ if (isarr)
+ pm->u.arr = aval;
+ else
+ pm->u.str = val;
+ v = (Value) hcalloc(sizeof *v);
+ v->isarr = isarr;
+ v->pm = pm;
+ v->b = -1;
+ if (getindex(&s, v) || s == os)
+ break;
+ }
+ if ((isarr = v->isarr))
+ aval = getarrvalue(v);
+ else {
+ if (v->pm->flags & PM_ARRAY) {
+ int tmplen = arrlen(v->pm->gets.afn(v->pm));
+
+ if (v->a < 0)
+ v->a += tmplen + v->inv;
+ if (!v->inv && (v->a >= tmplen || v->a < 0))
+ vunset = 1;
+ }
+ if (!vunset) {
+ val = getstrvalue(v);
+ fwidth = v->pm->ct ? v->pm->ct : strlen(val);
+ switch (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
+ char *t;
+ unsigned int t0;
+
+ case PM_LEFT:
+ case PM_LEFT | PM_RIGHT_Z:
+ t = val;
+ if (v->pm->flags & PM_RIGHT_Z)
+ while (*t == '0')
+ t++;
+ else
+ while (iblank(*t))
+ t++;
+ val = (char *)ncalloc(fwidth + 1);
+ val[fwidth] = '\0';
+ if ((t0 = strlen(t)) > fwidth)
+ t0 = fwidth;
+ memset(val, ' ', fwidth);
+ strncpy(val, t, t0);
+ break;
+ case PM_RIGHT_B:
+ case PM_RIGHT_Z:
+ case PM_RIGHT_Z | PM_RIGHT_B:
+ if (strlen(val) < fwidth) {
+ t = (char *)ncalloc(fwidth + 1);
+ memset(t, (v->pm->flags & PM_RIGHT_B) ? ' ' : '0', fwidth);
+ if ((t0 = strlen(val)) > fwidth)
+ t0 = fwidth;
+ strcpy(t + (fwidth - t0), val);
+ val = t;
+ } else {
+ t = (char *)ncalloc(fwidth + 1);
+ t[fwidth] = '\0';
+ strncpy(t, val + strlen(val) - fwidth, fwidth);
+ val = t;
+ }
+ break;
+ }
+ switch (v->pm->flags & (PM_LOWER | PM_UPPER)) {
+ char *t;
+
+ case PM_LOWER:
+ t = val;
+ for (; *t; t++)
+ *t = tulower(*t);
+ break;
+ case PM_UPPER:
+ t = val;
+ for (; *t; t++)
+ *t = tuupper(*t);
+ break;
+ }
+ }
+ }
+ v = NULL;
+ if (!inbrace)
+ break;
+ }
+ if (isarr) {
+ if (nojoin)
+ isarr = -1;
+ if (qt && !getlen && isarr > 0) {
+ val = sepjoin(aval, sep);
+ isarr = 0;
+ }
+ }
+
+ idend = s;
+ if ((colf = *s == ':'))
+ s++;
+
+
+ /* fstr is to be the text following the substitution. If we have *
+ * braces, we look for it here, else we infer it later on. */
+ fstr = s;
+ if (inbrace) {
+ int bct;
+ for (bct = 1;; fstr++) {
+ if (!*fstr)
+ break;
+ else if (*fstr == Inbrace)
+ bct++;
+ else if (*fstr == Outbrace && !--bct)
+ break;
+ }
+
+ if (bct) {
+ noclosebrace:
+ zerr("closing brace expected", NULL, 0);
+ return NULL;
+ }
+ if (*fstr)
+ *fstr++ = '\0';
+ }
+
+ /* Check for ${..?..} or ${..=..} or one of those. *
+ * Only works if the name is in braces. */
+
+ if (inbrace && (*s == '-' ||
+ *s == '+' ||
+ *s == ':' ||
+ *s == '=' || *s == Equals ||
+ *s == '%' ||
+ *s == '#' || *s == Pound ||
+ *s == '?' || *s == Quest)) {
+
+ if (!flnum)
+ flnum++;
+ if (*s == '%')
+ flags |= 1;
+
+ /* Check for ${..%%..} or ${..##..} */
+ if ((*s == '%' || *s == '#' || *s == Pound) && *s == s[1]) {
+ s++;
+ doub = 1;
+ }
+ s++;
+
+ flags |= (doub << 1) | (substr << 2) | (colf << 8);
+ if (!(flags & 0xf8))
+ flags |= 16;
+
+ if (colf && !vunset)
+ vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]);
+
+ switch (s[-1]) {
+ case '+':
+ if (vunset) {
+ val = dupstring("");
+ copied = 1;
+ isarr = 0;
+ break;
+ }
+ vunset = 1;
+ /* Fall Through! */
+ case '-':
+ if (vunset) {
+ val = dupstring(s);
+ multsub(&val, &aval, &isarr, NULL);
+ copied = 1;
+ }
+ break;
+ case ':':
+ if (*s != '=' && *s != Equals)
+ goto noclosebrace;
+ vunset = 1;
+ s++;
+ /* Fall through */
+ case '=':
+ case Equals:
+ if (vunset) {
+ char sav = *idend;
+ int l;
+
+ *idend = '\0';
+ val = dupstring(s);
+ isarr = 0;
+ if (spsep || spbreak || !arrasg)
+ multsub(&val, NULL, NULL, sep);
+ else
+ multsub(&val, &aval, &isarr, NULL);
+ if (arrasg) {
+ char *arr[2], **t, **a, **p;
+ if (spsep || spbreak) {
+ aval = sepsplit(val, spsep, 0);
+ isarr = 2;
+ sep = spsep = NULL;
+ spbreak = 0;
+ l = arrlen(aval);
+ if (l && !*(aval[l-1]))
+ l--;
+ if (l && !**aval)
+ l--, t = aval + 1;
+ else
+ t = aval;
+ } else if (!isarr) {
+ arr[0] = val;
+ arr[1] = NULL;
+ t = aval = arr;
+ l = 1;
+ } else
+ l = arrlen(aval), t = aval;
+ p = a = zalloc(sizeof(char *) * (l + 1));
+ while (l--) {
+ untokenize(*t);
+ *p++ = ztrdup(*t++);
+ }
+ *p++ = NULL;
+ setaparam(idbeg, a);
+ } else {
+ untokenize(val);
+ setsparam(idbeg, ztrdup(val));
+ }
+ *idend = sav;
+ copied = 1;
+ }
+ break;
+ case '?':
+ case Quest:
+ if (vunset) {
+ char *msg;
+
+ *idend = '\0';
+ msg = tricat(idbeg, ": ", *s ? s : "parameter not set");
+ zerr("%s", msg, 0);
+ zsfree(msg);
+ if (!interact)
+ exit(1);
+ return NULL;
+ }
+ break;
+ case '%':
+ case '#':
+ case Pound:
+ if (qt)
+ if (parse_subst_string(s)) {
+ zerr("parse error in ${...%c...} substitution",
+ NULL, s[-1]);
+ return NULL;
+ }
+ singsub(&s);
+
+ if (!vunset && isarr) {
+ char **ap = aval;
+ char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));
+
+ while ((*pp = *ap++)) {
+ if (getmatch(pp, s, flags, flnum))
+ pp++;
+ }
+ copied = 1;
+ } else {
+ if (vunset)
+ val = dupstring("");
+ getmatch(&val, s, flags, flnum);
+ copied = 1;
+ }
+ break;
+ }
+ } else { /* no ${...=...} or anything, but possible modifiers. */
+ if (chkset) {
+ val = dupstring(vunset ? "0" : "1");
+ isarr = 0;
+ } else if (vunset) {
+ if (unset(UNSET)) {
+ *idend = '\0';
+ zerr("%s: parameter not set", idbeg, 0);
+ return NULL;
+ }
+ val = dupstring("");
+ }
+ if (colf) {
+ s--;
+ if (unset(KSHARRAYS) || inbrace) {
+ if (!isarr)
+ modify(&val, &s);
+ else {
+ char *ss;
+ char **ap = aval;
+ char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));
+
+ while ((*pp = *ap++)) {
+ ss = s;
+ modify(pp++, &ss);
+ }
+ if (pp == aval) {
+ char *t = "";
+ ss = s;
+ modify(&t, &ss);
+ }
+ s = ss;
+ }
+ if (inbrace && *s) {
+ if (*s == ':' && !imeta(s[1]))
+ zerr("unrecognized modifier `%c'", NULL, s[1]);
+ else
+ zerr("unrecognized modifier", NULL, 0);
+ return NULL;
+ }
+ }
+ }
+ if (!inbrace)
+ fstr = s;
+ }
+ if (errflag)
+ return NULL;
+ if (getlen) {
+ long len = 0;
+ char buf[14];
+
+ if (isarr) {
+ char **ctr;
+ int sl = sep ? ztrlen(sep) : 1;
+
+ if (getlen == 1)
+ for (ctr = aval; *ctr; ctr++, len++);
+ else if (getlen == 2) {
+ if (*aval)
+ for (len = -sl, ctr = aval;
+ len += sl + ztrlen(*ctr), *++ctr;);
+ }
+ else
+ for (ctr = aval;
+ *ctr;
+ len += wordcount(*ctr, spsep, getlen > 3), ctr++);
+ } else {
+ if (getlen < 3)
+ len = ztrlen(val);
+ else
+ len = wordcount(val, spsep, getlen > 3);
+ }
+
+ sprintf(buf, "%ld", len);
+ val = dupstring(buf);
+ isarr = 0;
+ }
+ if (isarr > 0 && !plan9 && (!aval || !aval[0])) {
+ val = dupstring("");
+ isarr = 0;
+ } else if (isarr && aval && aval[0] && !aval[1]) {
+ val = aval[0];
+ isarr = 0;
+ }
+ /* ssub is true when we are called from singsub (via prefork).
+ * It means that we must join arrays and should not split words. */
+ if (ssub || spbreak || spsep || sep) {
+ if (isarr)
+ val = sepjoin(aval, sep), isarr = 0;
+ if (!ssub && (spbreak || spsep)) {
+ aval = sepsplit(val, spsep, 0);
+ if (!aval || !aval[0])
+ val = dupstring("");
+ else if (!aval[1])
+ val = aval[0];
+ else
+ isarr = 2;
+ }
+ }
+ if (casmod) {
+ if (isarr) {
+ char **ap;
+
+ if (!copied)
+ aval = arrdup(aval), copied = 1;
+ ap = aval;
+
+ if (casmod == 1)
+ for (; *ap; ap++)
+ makeuppercase(ap);
+ else if (casmod == 2)
+ for (; *ap; ap++)
+ makelowercase(ap);
+ else
+ for (; *ap; ap++)
+ makecapitals(ap);
+
+ } else {
+ if (!copied)
+ val = dupstring(val), copied = 1;
+ if (casmod == 1)
+ makeuppercase(&val);
+ else if (casmod == 2)
+ makelowercase(&val);
+ else
+ makecapitals(&val);
+ }
+ }
+ if (isarr) {
+ char *x;
+ char *y;
+ int xlen;
+ int i;
+ LinkNode on = n;
+
+ if (!aval[0] && !plan9) {
+ if (aptr > (char *) getdata(n) &&
+ aptr[-1] == Dnull && *fstr == Dnull)
+ *--aptr = '\0', fstr++;
+ y = (char *)ncalloc((aptr - ostr) + strlen(fstr) + 1);
+ strcpy(y, ostr);
+ *str = y + (aptr - ostr);
+ strcpy(*str, fstr);
+ setdata(n, y);
+ return n;
+ }
+ if (sortit) {
+ static CompareFn sortfn[] = {
+ strpcmp, invstrpcmp, cstrpcmp, invcstrpcmp
+ };
+
+ if (!copied)
+ aval = arrdup(aval);
+
+ i = arrlen(aval);
+ if (i && (*aval[i-1] || --i))
+ qsort(aval, i, sizeof(char *), sortfn[sortit-1]);
+ }
+ if (plan9) {
+ LinkList tl = newlinklist();
+ LinkNode tn;
+
+ *--fstr = Marker;
+ addlinknode(tl, fstr);
+ if (!eval && !stringsubst(tl, firstnode(tl), ssub))
+ return NULL;
+ *str = aptr;
+ tn = firstnode(tl);
+ while ((x = *aval++)) {
+ if (prenum || postnum)
+ x = dopadding(x, prenum, postnum, preone, postone,
+ premul, postmul);
+ if (eval && parsestr(x))
+ return NULL;
+ xlen = strlen(x);
+ for (tn = firstnode(tl);
+ tn && *(y = (char *) getdata(tn)) == Marker;
+ incnode(tn)) {
+ strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst);
+ if (qt && !*y && isarr != 2)
+ y = dupstring(nulstring);
+ if (plan9)
+ setdata(n, (void *) y), plan9 = 0;
+ else
+ insertlinknode(l, n, (void *) y), incnode(n);
+ }
+ }
+ for (; tn; incnode(tn)) {
+ y = (char *) getdata(tn);
+ if (*y == Marker)
+ continue;
+ if (qt && !*y && isarr != 2)
+ y = dupstring(nulstring);
+ if (plan9)
+ setdata(n, (void *) y), plan9 = 0;
+ else
+ insertlinknode(l, n, (void *) y), incnode(n);
+ }
+ if (plan9) {
+ uremnode(l, n);
+ return n;
+ }
+ } else {
+ x = aval[0];
+ if (prenum || postnum)
+ x = dopadding(x, prenum, postnum, preone, postone,
+ premul, postmul);
+ if (eval && parsestr(x))
+ return NULL;
+ xlen = strlen(x);
+ strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst);
+ if (qt && !*y && isarr != 2)
+ y = dupstring(nulstring);
+ setdata(n, (void *) y);
+
+ i = 1;
+ /* aval[1] is non-null here */
+ while (aval[i + 1]) {
+ x = aval[i++];
+ if (prenum || postnum)
+ x = dopadding(x, prenum, postnum, preone, postone,
+ premul, postmul);
+ if (eval && parsestr(x))
+ return NULL;
+ if (qt && !*x && isarr != 2)
+ y = dupstring(nulstring);
+ else {
+ y = dupstring(x);
+ if (globsubst)
+ tokenize(y);
+ }
+ insertlinknode(l, n, (void *) y), incnode(n);
+ }
+
+ x = aval[i];
+ if (prenum || postnum)
+ x = dopadding(x, prenum, postnum, preone, postone,
+ premul, postmul);
+ if (eval && parsestr(x))
+ return NULL;
+ xlen = strlen(x);
+ *str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst);
+ if (qt && !*y && isarr != 2)
+ y = dupstring(nulstring);
+ insertlinknode(l, n, (void *) y), incnode(n);
+ }
+ if (eval)
+ n = on;
+ } else {
+ int xlen;
+ char *x;
+ char *y;
+
+ x = val;
+ if (prenum || postnum)
+ x = dopadding(x, prenum, postnum, preone, postone,
+ premul, postmul);
+ if (eval && parsestr(x))
+ return NULL;
+ xlen = strlen(x);
+ *str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst);
+ if (qt && !*y && isarr != 2)
+ y = dupstring(nulstring);
+ setdata(n, (void *) y);
+ }
+ if (eval)
+ *str = (char *) getdata(n);
+
+ return n;
+}
+
+/*
+ * Arithmetic substitution: `a' is the string to be evaluated, `bptr'
+ * points to the beginning of the string containing it. The tail of
+ * the string is given by `rest'. *bptr is modified with the substituted
+ * string. The function returns a pointer to the tail in the substituted
+ * string.
+ */
+
+/**/
+static char *
+arithsubst(char *a, char **bptr, char *rest)
+{
+ char *s = *bptr, *t, buf[DIGBUFSIZE];
+ char *b = buf;
+ long v;
+
+ singsub(&a);
+ v = matheval(a);
+ sprintf(buf, "%ld", v);
+ t = *bptr = (char *)ncalloc(strlen(*bptr) + strlen(buf) + strlen(rest) + 1);
+ t--;
+ while ((*++t = *s++));
+ t--;
+ while ((*++t = *b++));
+ strcat(t, rest);
+ return t;
+}
+
+/**/
+void
+modify(char **str, char **ptr)
+{
+ char *ptr1, *ptr2, *ptr3, del, *lptr, c, *test, *sep, *t, *tt, tc, *e;
+ char *copy, *all, *tmp, sav;
+ int gbal, wall, rec, al, nl;
+
+ test = NULL;
+
+ if (**ptr == ':')
+ *str = dupstring(*str);
+
+ while (**ptr == ':') {
+ lptr = *ptr;
+ (*ptr)++;
+ wall = gbal = 0;
+ rec = 1;
+ c = '\0';
+ sep = NULL;
+
+ for (; !c && **ptr;) {
+ switch (**ptr) {
+ case 'h':
+ case 'r':
+ case 'e':
+ case 't':
+ case 'l':
+ case 'u':
+ c = **ptr;
+ break;
+
+ case 's':
+ c = **ptr;
+ (*ptr)++;
+ ptr1 = *ptr;
+ del = *ptr1++;
+ for (ptr2 = ptr1; *ptr2 != del && *ptr2; ptr2++);
+ if (!*ptr2) {
+ zerr("bad substitution", NULL, 0);
+ return;
+ }
+ *ptr2++ = '\0';
+ for (ptr3 = ptr2; *ptr3 != del && *ptr3; ptr3++);
+ if ((sav = *ptr3))
+ *ptr3++ = '\0';
+ if (*ptr1) {
+ zsfree(hsubl);
+ hsubl = ztrdup(ptr1);
+ }
+ if (!hsubl) {
+ zerr("no previous substitution", NULL, 0);
+ return;
+ }
+ zsfree(hsubr);
+ for (tt = hsubl; *tt; tt++)
+ if (INULL(*tt))
+ chuck(tt--);
+ untokenize(hsubl);
+ for (tt = hsubr = ztrdup(ptr2); *tt; tt++)
+ if (INULL(*tt))
+ chuck(tt--);
+ ptr2[-1] = del;
+ if (sav)
+ ptr3[-1] = sav;
+ *ptr = ptr3 - 1;
+ break;
+
+ case '&':
+ c = 's';
+ break;
+
+ case 'g':
+ (*ptr)++;
+ gbal = 1;
+ break;
+
+ case 'w':
+ wall = 1;
+ (*ptr)++;
+ break;
+ case 'W':
+ wall = 1;
+ (*ptr)++;
+ ptr1 = get_strarg(ptr2 = *ptr);
+ if ((sav = *ptr1))
+ *ptr1 = '\0';
+ sep = dupstring(ptr2 + 1);
+ if (sav)
+ *ptr1 = sav;
+ *ptr = ptr1 + 1;
+ c = '\0';
+ break;
+
+ case 'f':
+ rec = -1;
+ (*ptr)++;
+ break;
+ case 'F':
+ rec = get_intarg(ptr);
+ (*ptr)++;
+ break;
+ default:
+ *ptr = lptr;
+ return;
+ }
+ }
+ (*ptr)++;
+ if (!c) {
+ *ptr = lptr;
+ return;
+ }
+ if (rec < 0)
+ test = dupstring(*str);
+
+ while (rec--) {
+ if (wall) {
+ al = 0;
+ all = NULL;
+ for (t = e = *str; (tt = findword(&e, sep));) {
+ tc = *e;
+ *e = '\0';
+ copy = dupstring(tt);
+ *e = tc;
+ switch (c) {
+ case 'h':
+ remtpath(&copy);
+ break;
+ case 'r':
+ remtext(&copy);
+ break;
+ case 'e':
+ rembutext(&copy);
+ break;
+ case 't':
+ remlpaths(&copy);
+ break;
+ case 'l':
+ downcase(&copy);
+ break;
+ case 'u':
+ upcase(&copy);
+ break;
+ case 's':
+ if (hsubl && hsubr)
+ subst(&copy, hsubl, hsubr, gbal);
+ break;
+ }
+ tc = *tt;
+ *tt = '\0';
+ nl = al + strlen(t) + strlen(copy);
+ ptr1 = tmp = (char *)halloc(nl + 1);
+ if (all)
+ for (ptr2 = all; *ptr2;)
+ *ptr1++ = *ptr2++;
+ for (ptr2 = t; *ptr2;)
+ *ptr1++ = *ptr2++;
+ *tt = tc;
+ for (ptr2 = copy; *ptr2;)
+ *ptr1++ = *ptr2++;
+ *ptr1 = '\0';
+ al = nl;
+ all = tmp;
+ t = e;
+ }
+ *str = all;
+
+ } else {
+ switch (c) {
+ case 'h':
+ remtpath(str);
+ break;
+ case 'r':
+ remtext(str);
+ break;
+ case 'e':
+ rembutext(str);
+ break;
+ case 't':
+ remlpaths(str);
+ break;
+ case 'l':
+ downcase(str);
+ break;
+ case 'u':
+ upcase(str);
+ break;
+ case 's':
+ if (hsubl && hsubr) {
+ char *oldstr = *str;
+
+ subst(str, hsubl, hsubr, gbal);
+ if (*str != oldstr) {
+ *str = dupstring(oldstr = *str);
+ zsfree(oldstr);
+ }
+ }
+ break;
+ }
+ }
+ if (rec < 0) {
+ if (!strcmp(test, *str))
+ rec = 0;
+ else
+ test = dupstring(*str);
+ }
+ }
+ }
+}
+
+/* get a directory stack entry */
+
+/**/
+static char *
+dstackent(char ch, int val)
+{
+ int backwards;
+ LinkNode end=(LinkNode)dirstack, n;
+
+ backwards = ch == (isset(PUSHDMINUS) ? '+' : '-');
+ if(!backwards && !val--)
+ return pwd;
+ if (backwards)
+ for (n=lastnode(dirstack); n != end && val; val--, n=prevnode(n));
+ else
+ for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n));
+ if (n == end) {
+ if (isset(NOMATCH))
+ zerr("not enough directory stack entries.", NULL, 0);
+ return NULL;
+ }
+ return (char *)getdata(n);
+}
diff --git a/Src/system.h b/Src/system.h
new file mode 100644
index 000000000..e42f3b891
--- /dev/null
+++ b/Src/system.h
@@ -0,0 +1,598 @@
+/*
+ * system.h - system configuration header file
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#ifdef __hpux
+# define _INCLUDE_POSIX_SOURCE 1
+# define _INCLUDE_XOPEN_SOURCE 1
+# define _INCLUDE_HPUX_SOURCE 1
+#endif
+
+#ifdef sinix
+# define _XPG_IV 1
+#endif
+
+/* NeXT has half-implemented POSIX support *
+ * which currently fools configure */
+#ifdef __NeXT__
+# undef HAVE_TERMIOS_H
+# undef HAVE_SYS_UTSNAME_H
+#endif
+
+#ifdef PROTOTYPES
+# define _(Args) Args
+#else
+# define _(Args) ()
+#endif
+
+#ifndef HAVE_ALLOCA
+# define alloca halloc
+#else
+# ifdef __GNUC__
+# define alloca __builtin_alloca
+# else
+# if HAVE_ALLOCA_H
+# include <alloca.h>
+# else
+# ifdef _AIX
+ # pragma alloca
+# else
+# ifndef alloca
+char *alloca _((size_t));
+# endif
+# endif
+# endif
+# endif
+#endif
+
+#ifdef HAVE_LIBC_H /* NeXT */
+# include <libc.h>
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+# include <grp.h>
+#endif
+
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+#else /* !HAVE_DIRENT_H */
+# ifdef HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+# include <ndir.h>
+# endif
+# define dirent direct
+# undef HAVE_STRUCT_DIRENT_D_INO
+# undef HAVE_STRUCT_DIRENT_D_STAT
+# ifdef HAVE_STRUCT_DIRECT_D_INO
+# define HAVE_STRUCT_DIRENT_D_INO HAVE_STRUCT_DIRECT_D_INO
+# endif
+# ifdef HAVE_STRUCT_DIRECT_D_STAT
+# define HAVE_STRUCT_DIRENT_D_STAT HAVE_STRUCT_DIRECT_D_STAT
+# endif
+#endif /* !HAVE_DIRENT_H */
+
+#ifdef HAVE_STDLIB_H
+# ifdef ZSH_MEM
+ /* malloc and calloc are macros in GNU's stdlib.h unless the
+ * the __MALLOC_0_RETURNS_NULL macro is defined */
+# define __MALLOC_0_RETURNS_NULL
+# endif
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_ERRNO_H
+# include <errno.h>
+#endif
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+/* This is needed by some old SCO unices */
+#ifndef HAVE_STRUCT_TIMEZONE
+struct timezone {
+ int tz_minuteswest;
+ int tz_dsttime;
+};
+#endif
+
+/* There's more than one non-standard way to get at this data */
+#if !defined(HAVE_STRUCT_DIRENT_D_INO) && defined(HAVE_STRUCT_DIRENT_D_STAT)
+# define d_ino d_stat.st_ino
+# define HAVE_STRUCT_DIRENT_D_INO HAVE_STRUCT_DIRENT_D_STAT
+#endif /* !HAVE_STRUCT_DIRENT_D_INO && HAVE_STRUCT_DIRENT_D_STAT */
+
+/* Sco needs the following include for struct utimbuf *
+ * which is strange considering we do not use that *
+ * anywhere in the code */
+#ifdef __sco
+# include <utime.h>
+#endif
+
+#ifdef HAVE_SYS_TIMES_H
+# include <sys/times.h>
+#endif
+
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+/* An ANSI string.h and pre-ANSI memory.h might conflict. */
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif /* not STDC_HEADERS and HAVE_MEMORY_H */
+#else /* not STDC_HEADERS and not HAVE_STRING_H */
+# include <strings.h>
+/* memory.h and strings.h conflict on some systems. */
+#endif /* not STDC_HEADERS and not HAVE_STRING_H */
+
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#ifdef HAVE_VARIABLE_LENGTH_ARRAYS
+# define VARARR(X,Y,Z) X (Y)[Z]
+#else
+# define VARARR(X,Y,Z) X *(Y) = (X *) alloca(sizeof(X) * (Z))
+#endif
+
+/* we should be getting this value from pathconf(_PC_PATH_MAX) */
+/* but this is too much trouble */
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+ /* so we will just pick something */
+# define PATH_MAX 1024
+# endif
+#endif
+
+/* we should be getting this value from sysconf(_SC_OPEN_MAX) */
+/* but this is too much trouble */
+#ifndef OPEN_MAX
+# ifdef NOFILE
+# define OPEN_MAX NOFILE
+# else
+ /* so we will just pick something */
+# define OPEN_MAX 64
+# endif
+#endif
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+/* The following will only be defined if <sys/wait.h> is POSIX. *
+ * So we don't have to worry about union wait. But some machines *
+ * (NeXT) include <sys/wait.h> from other include files, so we *
+ * need to undef and then redefine the wait macros if <sys/wait.h> *
+ * is not POSIX. */
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#else
+# undef WIFEXITED
+# undef WEXITSTATUS
+# undef WIFSIGNALED
+# undef WTERMSIG
+# undef WCOREDUMP
+# undef WIFSTOPPED
+# undef WSTOPSIG
+#endif
+
+/* missing macros for wait/waitpid/wait3 */
+#ifndef WIFEXITED
+# define WIFEXITED(X) (((X)&0377)==0)
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(X) (((X)>>8)&0377)
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(X) (((X)&0377)!=0&&((X)&0377)!=0177)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(X) ((X)&0177)
+#endif
+#ifndef WCOREDUMP
+# define WCOREDUMP(X) ((X)&0200)
+#endif
+#ifndef WIFSTOPPED
+# define WIFSTOPPED(X) (((X)&0377)==0177)
+#endif
+#ifndef WSTOPSIG
+# define WSTOPSIG(X) (((X)>>8)&0377)
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+# ifndef TIME_H_SELECT_H_CONFLICTS
+# include <sys/select.h>
+# endif
+#endif
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#ifdef HAVE_TERMIOS_H
+# ifdef __sco
+ /* termios.h includes sys/termio.h instead of sys/termios.h; *
+ * hence the declaration for struct termios is missing */
+# include <sys/termios.h>
+# else
+# include <termios.h>
+# endif
+# ifdef _POSIX_VDISABLE
+# define VDISABLEVAL _POSIX_VDISABLE
+# else
+# define VDISABLEVAL 0
+# endif
+# define HAS_TIO 1
+#else /* not TERMIOS */
+# ifdef HAVE_TERMIO_H
+# include <termio.h>
+# define VDISABLEVAL -1
+# define HAS_TIO 1
+# else /* not TERMIOS and TERMIO */
+# include <sgtty.h>
+# endif /* HAVE_TERMIO_H */
+#endif /* HAVE_TERMIOS_H */
+
+#ifdef HAVE_TERMCAP_H
+# include <termcap.h>
+#endif
+
+#if defined(GWINSZ_IN_SYS_IOCTL) || defined(CLOBBERS_TYPEAHEAD)
+# include <sys/ioctl.h>
+#endif
+#ifdef WINSIZE_IN_PTEM
+# include <sys/stream.h>
+# include <sys/ptem.h>
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#ifdef HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#endif
+
+#define DEFAULT_WORDCHARS "*?_-.[]~=/&;!#$%^(){}<>"
+#define DEFAULT_TIMEFMT "%J %U user %S system %P cpu %*E total"
+
+/* Posix getpgrp takes no argument, while the BSD version *
+ * takes the process ID as an argument */
+#ifdef GETPGRP_VOID
+# define GETPGRP() getpgrp()
+#else
+# define GETPGRP() getpgrp(0)
+#endif
+
+#ifndef HAVE_GETLOGIN
+# define getlogin() cuserid(NULL)
+#endif
+
+#ifdef HAVE_SETPGID
+# define setpgrp setpgid
+#endif
+
+/* can we set the user/group id of a process */
+
+#ifndef HAVE_SETUID
+# ifdef HAVE_SETREUID
+# define setuid(X) setreuid(X,X)
+# define setgid(X) setregid(X,X)
+# define HAVE_SETUID
+# endif
+#endif
+
+/* can we set the effective user/group id of a process */
+
+#ifndef HAVE_SETEUID
+# ifdef HAVE_SETREUID
+# define seteuid(X) setreuid(-1,X)
+# define setegid(X) setregid(-1,X)
+# define HAVE_SETEUID
+# else
+# ifdef HAVE_SETRESUID
+# define seteuid(X) setresuid(-1,X,-1)
+# define setegid(X) setresgid(-1,X,-1)
+# define HAVE_SETEUID
+# endif
+# endif
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+# include <sys/resource.h>
+# if defined(__hpux) && !defined(RLIMIT_CPU)
+/* HPUX does have the BSD rlimits in the kernel. Officially they are *
+ * unsupported but quite a few of them like RLIMIT_CORE seem to work. *
+ * All the following are in the <sys/resource.h> but made visible *
+ * only for the kernel. */
+# define RLIMIT_CPU 0
+# define RLIMIT_FSIZE 1
+# define RLIMIT_DATA 2
+# define RLIMIT_STACK 3
+# define RLIMIT_CORE 4
+# define RLIMIT_RSS 5
+# define RLIMIT_NOFILE 6
+# define RLIMIT_OPEN_MAX RLIMIT_NOFILE
+# define RLIM_NLIMITS 7
+# define RLIM_INFINITY 0x7fffffff
+# endif
+#endif
+
+/* we use the SVR4 constant instead of the BSD one */
+#if !defined(RLIMIT_NOFILE) && defined(RLIMIT_OFILE)
+# define RLIMIT_NOFILE RLIMIT_OFILE
+#endif
+#if !defined(RLIMIT_VMEM) && defined(RLIMIT_AS)
+# define RLIMIT_VMEM RLIMIT_AS
+#endif
+
+#ifdef HAVE_SYS_CAPABILITY_H
+# include <sys/capability.h>
+#endif
+
+/* DIGBUFSIZ is the length of a buffer which can hold the -LONG_MAX-1 *
+ * converted to printable decimal form including the sign and the *
+ * terminating null character. Below 0.30103 > lg 2. */
+#define DIGBUFSIZE ((int)(((sizeof(long) * 8) - 1) * 0.30103) + 3)
+
+/* If your stat macros are broken, we will *
+ * just undefine them. */
+
+#ifdef STAT_MACROS_BROKEN
+# undef S_ISBLK
+# undef S_ISCHR
+# undef S_ISDIR
+# undef S_ISFIFO
+# undef S_ISLNK
+# undef S_ISMPB
+# undef S_ISMPC
+# undef S_ISNWK
+# undef S_ISOFD
+# undef S_ISOFL
+# undef S_ISREG
+# undef S_ISSOCK
+#endif /* STAT_MACROS_BROKEN. */
+
+/* If you are missing the stat macros, we *
+ * define our own */
+
+#ifndef S_IFMT
+# define S_IFMT 0170000
+#endif
+
+#if !defined(S_ISBLK) && defined(S_IFBLK)
+# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+#endif
+#if !defined(S_ISCHR) && defined(S_IFCHR)
+# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+#endif
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+#if !defined(S_ISFIFO) && defined(S_IFIFO)
+# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+#endif
+#if !defined(S_ISLNK) && defined(S_IFLNK)
+# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
+# define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
+#endif
+#if !defined(S_ISMPC) && defined(S_IFMPC) /* V7 */
+# define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
+#endif
+#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
+# define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
+#endif
+#if !defined(S_ISOFD) && defined(S_IFOFD) /* Cray */
+# define S_ISOFD(m) (((m) & S_IFMT) == S_IFOFD)
+#endif
+#if !defined(S_ISOFL) && defined(S_IFOFL) /* Cray */
+# define S_ISOFL(m) (((m) & S_IFMT) == S_IFOFL)
+#endif
+#if !defined(S_ISREG) && defined(S_IFREG)
+# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+#if !defined(S_ISSOCK) && defined(S_IFSOCK)
+# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+#endif
+
+/* We will pretend to have all file types on any system. */
+
+#ifndef S_ISBLK
+# define S_ISBLK(m) ((void)(m), 0)
+#endif
+#ifndef S_ISCHR
+# define S_ISCHR(m) ((void)(m), 0)
+#endif
+#ifndef S_ISDIR
+# define S_ISDIR(m) ((void)(m), 0)
+#endif
+#ifndef S_ISFIFO
+# define S_ISFIFO(m) ((void)(m), 0)
+#endif
+#ifndef S_ISLNK
+# define S_ISLNK(m) ((void)(m), 0)
+#endif
+#ifndef S_ISMPB
+# define S_ISMPB(m) ((void)(m), 0)
+#endif
+#ifndef S_ISMPC
+# define S_ISMPC(m) ((void)(m), 0)
+#endif
+#ifndef S_ISNWK
+# define S_ISNWK(m) ((void)(m), 0)
+#endif
+#ifndef S_ISOFD
+# define S_ISOFD(m) ((void)(m), 0)
+#endif
+#ifndef S_ISOFL
+# define S_ISOFL(m) ((void)(m), 0)
+#endif
+#ifndef S_ISREG
+# define S_ISREG(m) ((void)(m), 0)
+#endif
+#ifndef S_ISSOCK
+# define S_ISSOCK(m) ((void)(m), 0)
+#endif
+
+/* file mode permission bits */
+
+#ifndef S_ISUID
+# define S_ISUID 04000
+#endif
+#ifndef S_ISGID
+# define S_ISGID 02000
+#endif
+#ifndef S_ISVTX
+# define S_ISVTX 01000
+#endif
+#ifndef S_IRUSR
+# define S_IRUSR 00400
+#endif
+#ifndef S_IWUSR
+# define S_IWUSR 00200
+#endif
+#ifndef S_IXUSR
+# define S_IXUSR 00100
+#endif
+#ifndef S_IRGRP
+# define S_IRGRP 00040
+#endif
+#ifndef S_IWGRP
+# define S_IWGRP 00020
+#endif
+#ifndef S_IXGRP
+# define S_IXGRP 00010
+#endif
+#ifndef S_IROTH
+# define S_IROTH 00004
+#endif
+#ifndef S_IWOTH
+# define S_IWOTH 00002
+#endif
+#ifndef S_IXOTH
+# define S_IXOTH 00001
+#endif
+#ifndef S_IRWXU
+# define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR)
+#endif
+#ifndef S_IRWXG
+# define S_IRWXG (S_IRGRP|S_IWGRP|S_IXGRP)
+#endif
+#ifndef S_IRWXO
+# define S_IRWXO (S_IROTH|S_IWOTH|S_IXOTH)
+#endif
+#ifndef S_IRUGO
+# define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
+#endif
+#ifndef S_IWUGO
+# define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
+#endif
+#ifndef S_IXUGO
+# define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)
+#endif
+
+#ifndef HAVE_LSTAT
+# define lstat stat
+#endif
+
+#ifndef HAVE_READLINK
+# define readlink(PATH, BUF, BUFSZ) \
+ ((void)(PATH), (void)(BUF), (void)(BUFSZ), errno = ENOSYS, -1)
+#endif
+
+#ifndef F_OK /* missing macros for access() */
+# define F_OK 0
+# define X_OK 1
+# define W_OK 2
+# define R_OK 4
+#endif
+
+#ifndef HAVE_MEMCPY
+# define memcpy memmove
+#endif
+
+#ifndef HAVE_MEMMOVE
+# define memmove(dest, src, len) bcopy((src), (dest), (len))
+#endif
+
+#ifndef offsetof
+# define offsetof(TYPE, MEM) ((char *)&((TYPE *)0)->MEM - (char *)(TYPE *)0)
+#endif
+
+extern char **environ;
+
+/* These variables are sometimes defined in, *
+ * and needed by, the termcap library. */
+#if MUST_DEFINE_OSPEED
+extern char PC, *BC, *UP;
+extern short ospeed;
+#endif
+
+#ifndef O_NOCTTY
+# define O_NOCTTY 0
+#endif
diff --git a/Src/text.c b/Src/text.c
new file mode 100644
index 000000000..b7df8012f
--- /dev/null
+++ b/Src/text.c
@@ -0,0 +1,526 @@
+/*
+ * text.c - textual representations of syntax trees
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "text.pro"
+
+static char *tptr, *tbuf, *tlim;
+static int tsiz, tindent, tnewlins;
+
+/* add a character to the text buffer */
+
+/**/
+static void
+taddchr(int c)
+{
+ *tptr++ = c;
+ if (tptr == tlim) {
+ if (!tbuf) {
+ tptr--;
+ return;
+ }
+ tbuf = realloc(tbuf, tsiz *= 2);
+ tlim = tbuf + tsiz;
+ tptr = tbuf + tsiz / 2;
+ }
+}
+
+/* add a string to the text buffer */
+
+/**/
+static void
+taddstr(char *s)
+{
+ int sl = strlen(s);
+
+ while (tptr + sl >= tlim) {
+ int x = tptr - tbuf;
+
+ if (!tbuf)
+ return;
+ tbuf = realloc(tbuf, tsiz *= 2);
+ tlim = tbuf + tsiz;
+ tptr = tbuf + x;
+ }
+ strcpy(tptr, s);
+ tptr += sl;
+}
+
+#if 0
+/* add an integer to the text buffer */
+
+/**/
+void
+taddint(int x)
+{
+ char buf[DIGBUFSIZE];
+
+ sprintf(buf, "%d", x);
+ taddstr(buf);
+}
+#endif
+
+/* add a newline, or something equivalent, to the text buffer */
+
+/**/
+static void
+taddnl(void)
+{
+ int t0;
+
+ if (tnewlins) {
+ taddchr('\n');
+ for (t0 = 0; t0 != tindent; t0++)
+ taddchr('\t');
+ } else
+ taddstr("; ");
+}
+
+/* get a permanent textual representation of n */
+
+/**/
+char *
+getpermtext(struct node *n)
+{
+ tnewlins = 1;
+ tbuf = (char *)zalloc(tsiz = 32);
+ tptr = tbuf;
+ tlim = tbuf + tsiz;
+ tindent = 1;
+ gettext2(n);
+ *tptr = '\0';
+ untokenize(tbuf);
+ return tbuf;
+}
+
+/* get a representation of n in a job text buffer */
+
+/**/
+char *
+getjobtext(struct node *n)
+{
+ static char jbuf[JOBTEXTSIZE];
+
+ tnewlins = 0;
+ tbuf = NULL;
+ tptr = jbuf;
+ tlim = tptr + JOBTEXTSIZE - 1;
+ tindent = 1;
+ gettext2(n);
+ *tptr = '\0';
+ untokenize(jbuf);
+ return jbuf;
+}
+
+#define gt2(X) gettext2((struct node *) (X))
+
+/*
+ "gettext2" or "type checking and how to avoid it"
+ an epic function by Paul Falstad
+*/
+
+#define _Cond(X) ((Cond) (X))
+#define _Cmd(X) ((Cmd) (X))
+#define _Pline(X) ((Pline) (X))
+#define _Sublist(X) ((Sublist) (X))
+#define _List(X) ((List) (X))
+#define _casecmd(X) ((struct casecmd *) (X))
+#define _ifcmd(X) ((struct ifcmd *) (X))
+#define _whilecmd(X) ((struct whilecmd *) (X))
+
+/**/
+static void
+gettext2(struct node *n)
+{
+ Cmd nn;
+
+ if (!n || ((List) n) == &dummy_list)
+ return;
+ switch (NT_TYPE(n->ntype)) {
+ case N_LIST:
+ gt2(_List(n)->left);
+ if (_List(n)->type & Z_ASYNC) {
+ taddstr(" &");
+ if (_List(n)->type & Z_DISOWN)
+ taddstr("|");
+ }
+ simplifyright(_List(n));
+ if (_List(n)->right) {
+ if (tnewlins)
+ taddnl();
+ else
+ taddstr((_List(n)->type & Z_ASYNC) ? " " : "; ");
+ gt2(_List(n)->right);
+ }
+ break;
+ case N_SUBLIST:
+ if (_Sublist(n)->flags & PFLAG_NOT)
+ taddstr("! ");
+ if (_Sublist(n)->flags & PFLAG_COPROC)
+ taddstr("coproc ");
+ gt2(_Sublist(n)->left);
+ if (_Sublist(n)->right) {
+ taddstr((_Sublist(n)->type == ORNEXT) ? " || " : " && ");
+ gt2(_Sublist(n)->right);
+ }
+ break;
+ case N_PLINE:
+ gt2(_Pline(n)->left);
+ if (_Pline(n)->type == PIPE) {
+ taddstr(" | ");
+ gt2(_Pline(n)->right);
+ }
+ break;
+ case N_CMD:
+ nn = _Cmd(n);
+ switch (nn->type) {
+ case SIMPLE:
+ getsimptext(nn);
+ break;
+ case SUBSH:
+ taddstr("( ");
+ tindent++;
+ gt2(nn->u.list);
+ tindent--;
+ taddstr(" )");
+ break;
+ case ZCTIME:
+ taddstr("time ");
+ tindent++;
+ gt2(nn->u.pline);
+ tindent--;
+ break;
+ case FUNCDEF:
+ taddlist(nn->args);
+ taddstr(" () {");
+ tindent++;
+ taddnl();
+ gt2(nn->u.list);
+ tindent--;
+ taddnl();
+ taddstr("}");
+ break;
+ case CURSH:
+ taddstr("{ ");
+ tindent++;
+ gt2(nn->u.list);
+ tindent--;
+ taddstr(" }");
+ break;
+ case CFOR:
+ case CSELECT:
+ taddstr((nn->type == CFOR) ? "for " : "select ");
+ if (nn->u.forcmd->condition) {
+ taddstr("((");
+ taddstr(nn->u.forcmd->name);
+ taddstr("; ");
+ taddstr(nn->u.forcmd->condition);
+ taddstr("; ");
+ taddstr(nn->u.forcmd->advance);
+ taddstr(")) do");
+ } else {
+ taddstr(nn->u.forcmd->name);
+ if (nn->u.forcmd->inflag) {
+ taddstr(" in ");
+ taddlist(nn->args);
+ }
+ taddnl();
+ taddstr("do");
+ }
+ tindent++;
+ taddnl();
+ gt2(nn->u.forcmd->list);
+ tindent--;
+ taddnl();
+ taddstr("done");
+ break;
+ case CIF:
+ gt2(nn->u.ifcmd);
+ taddstr("fi");
+ break;
+ case CCASE:
+ gt2(nn->u.casecmd);
+ break;
+ case COND:
+ taddstr("[[ ");
+ gt2(nn->u.cond);
+ taddstr(" ]]");
+ break;
+ case CARITH:
+ taddstr("((");
+ taddlist(nn->args);
+ taddstr("))");
+ break;
+ case CREPEAT:
+ taddstr("repeat ");
+ taddlist(nn->args);
+ taddnl();
+ taddstr("do");
+ tindent++;
+ taddnl();
+ gt2(nn->u.list);
+ tindent--;
+ taddnl();
+ taddstr("done");
+ break;
+ case CWHILE:
+ gt2(nn->u.whilecmd);
+ break;
+ }
+ getredirs(nn);
+ break;
+ case N_COND:
+ getcond(_Cond(n), 0);
+ break;
+ case N_CASE:
+ {
+ List *l;
+ char **p;
+
+ l = _casecmd(n)->lists;
+ p = _casecmd(n)->pats;
+
+ taddstr("case ");
+ taddstr(*p++);
+ taddstr(" in");
+ tindent++;
+ for (; *l; p++, l++) {
+ if (tnewlins)
+ taddnl();
+ else
+ taddchr(' ');
+ taddstr(*p + 1);
+ taddstr(") ");
+ tindent++;
+ gt2(*l);
+ tindent--;
+ taddstr(" ;");
+ taddchr(**p);
+ }
+ tindent--;
+ if (tnewlins)
+ taddnl();
+ else
+ taddchr(' ');
+ taddstr("esac");
+ break;
+ }
+ case N_IF:
+ {
+ List *i, *t;
+
+ taddstr("if ");
+ for (i = _ifcmd(n)->ifls, t = _ifcmd(n)->thenls; *i; i++, t++) {
+ tindent++;
+ gt2(*i);
+ tindent--;
+ taddnl();
+ taddstr("then");
+ tindent++;
+ taddnl();
+ gt2(*t);
+ tindent--;
+ taddnl();
+ if (i[1]) {
+ taddstr("elif ");
+ }
+ }
+ if (*t) {
+ taddstr("else");
+ tindent++;
+ taddnl();
+ gt2(*t);
+ tindent--;
+ taddnl();
+ }
+ break;
+ }
+ case N_WHILE:
+ taddstr((_whilecmd(n)->cond) ? "until " : "while ");
+ tindent++;
+ gt2(_whilecmd(n)->cont);
+ tindent--;
+ taddnl();
+ taddstr("do");
+ tindent++;
+ taddnl();
+ gt2(_whilecmd(n)->loop);
+ tindent--;
+ taddnl();
+ taddstr("done");
+ break;
+ }
+}
+
+/* Print a condition bracketed by [[ ... ]]. *
+ * With addpar non-zero, parenthesise the subexpression. */
+
+/**/
+static void
+getcond(Cond nm, int addpar)
+{
+ static char *c1[] =
+ {
+ "=", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
+ "-ne", "-lt", "-gt", "-le", "-ge"
+ };
+
+ if (addpar)
+ taddstr("( ");
+ switch (nm->type) {
+ case COND_NOT:
+ taddstr("! ");
+ getcond(nm->left, _Cond(nm->left)->type <= COND_OR);
+ break;
+ case COND_AND:
+ getcond(nm->left, _Cond(nm->left)->type == COND_OR);
+ taddstr(" && ");
+ getcond(nm->right, _Cond(nm->right)->type == COND_OR);
+ break;
+ case COND_OR:
+ /* This is deliberately over-generous with parentheses: *
+ * in fact omitting them gives correct precedence. */
+ getcond(nm->left, _Cond(nm->left)->type == COND_AND);
+ taddstr(" || ");
+ getcond(nm->right, _Cond(nm->right)->type == COND_AND);
+ break;
+ default:
+ if (nm->type <= COND_GE) {
+ /* Binary test: `a = b' etc. */
+ taddstr(nm->left);
+ taddstr(" ");
+ taddstr(c1[nm->type - COND_STREQ]);
+ taddstr(" ");
+ taddstr(nm->right);
+ } else {
+ /* Unary test: `-f foo' etc. */
+ char c2[4];
+
+ c2[0] = '-';
+ c2[1] = nm->type;
+ c2[2] = ' ';
+ c2[3] = '\0';
+ taddstr(c2);
+ taddstr(nm->left);
+ }
+ break;
+ }
+ if (addpar)
+ taddstr(" )");
+}
+
+/**/
+static void
+getsimptext(Cmd cmd)
+{
+ LinkNode n;
+
+ for (n = firstnode(cmd->vars); n; incnode(n)) {
+ struct varasg *v = (struct varasg *)getdata(n);
+
+ taddstr(v->name);
+ taddchr('=');
+ if (PM_TYPE(v->type) == PM_ARRAY) {
+ taddchr('(');
+ taddlist(v->arr);
+ taddstr(") ");
+ } else {
+ taddstr(v->str);
+ taddchr(' ');
+ }
+ }
+ taddlist(cmd->args);
+}
+
+/**/
+void
+getredirs(Cmd cmd)
+{
+ LinkNode n;
+ static char *fstr[] =
+ {
+ ">", ">|", ">>", ">>|", "&>", "&>|", "&>>", "&>>|", "<>", "<",
+ "<<", "<<-", "<<<", "<&", ">&", NULL /* >&- */, "<", ">"
+ };
+
+ taddchr(' ');
+ for (n = firstnode(cmd->redir); n; incnode(n)) {
+ struct redir *f = (struct redir *)getdata(n);
+
+ switch (f->type) {
+ case WRITE:
+ case WRITENOW:
+ case APP:
+ case APPNOW:
+ case ERRWRITE:
+ case ERRWRITENOW:
+ case ERRAPP:
+ case ERRAPPNOW:
+ case READ:
+ case READWRITE:
+ case HERESTR:
+ case MERGEIN:
+ case MERGEOUT:
+ case INPIPE:
+ case OUTPIPE:
+ if (f->fd1 != (IS_READFD(f->type) ? 0 : 1))
+ taddchr('0' + f->fd1);
+ taddstr(fstr[f->type]);
+ taddchr(' ');
+ taddstr(f->name);
+ taddchr(' ');
+ break;
+#ifdef DEBUG
+ case CLOSE:
+ DPUTS(1, "BUG: CLOSE in getredirs()");
+ taddchr(f->fd1 + '0');
+ taddstr(">&- ");
+ break;
+ default:
+ DPUTS(1, "BUG: unknown redirection in getredirs()");
+#endif
+ }
+ }
+ tptr--;
+}
+
+/**/
+static void
+taddlist(LinkList l)
+{
+ LinkNode n;
+
+ if (!(n = firstnode(l)))
+ return;
+ for (; n; incnode(n)) {
+ taddstr(getdata(n));
+ taddchr(' ');
+ }
+ tptr--;
+}
diff --git a/Src/utils.c b/Src/utils.c
new file mode 100644
index 000000000..3619fa95d
--- /dev/null
+++ b/Src/utils.c
@@ -0,0 +1,3726 @@
+/*
+ * utils.c - miscellaneous utilities
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "utils.pro"
+
+/* Print an error */
+
+/**/
+void
+zwarnnam(const char *cmd, const char *fmt, const char *str, int num)
+{
+ int waserr;
+
+ waserr = errflag;
+ zerrnam(cmd, fmt, str, num);
+ errflag = waserr;
+}
+
+/* name of script being sourced */
+
+/**/
+char *scriptname;
+
+/**/
+void
+zerr(const char *fmt, const char *str, int num)
+{
+ if (errflag || noerrs)
+ return;
+ errflag = 1;
+ trashzle();
+ /*
+ * scriptname is set when sourcing scripts, so that we get the
+ * correct name instead of the generic name of whatever
+ * program/script is running.
+ */
+ nicezputs(isset(SHINSTDIN) ? "zsh" :
+ scriptname ? scriptname : argzero, stderr);
+ fputs(": ", stderr);
+ zerrnam(NULL, fmt, str, num);
+}
+
+/**/
+void
+zerrnam(const char *cmd, const char *fmt, const char *str, int num)
+{
+ if (cmd) {
+ if (errflag || noerrs)
+ return;
+ errflag = 1;
+ trashzle();
+ if(unset(SHINSTDIN)) {
+ nicezputs(scriptname ? scriptname : argzero, stderr);
+ fputs(": ", stderr);
+ }
+ nicezputs(cmd, stderr);
+ fputs(": ", stderr);
+ }
+ while (*fmt)
+ if (*fmt == '%') {
+ fmt++;
+ switch (*fmt++) {
+ case 's':
+ nicezputs(str, stderr);
+ break;
+ case 'l': {
+ char *s;
+ num = metalen(str, num);
+ s = halloc(num + 1);
+ memcpy(s, str, num);
+ s[num] = '\0';
+ nicezputs(s, stderr);
+ break;
+ }
+ case 'd':
+ fprintf(stderr, "%d", num);
+ break;
+ case '%':
+ putc('%', stderr);
+ break;
+ case 'c':
+ fputs(nicechar(num), stderr);
+ break;
+ case 'e':
+ /* print the corresponding message for this errno */
+ if (num == EINTR) {
+ fputs("interrupt\n", stderr);
+ errflag = 1;
+ return;
+ }
+ /* If the message is not about I/O problems, it looks better *
+ * if we uncapitalize the first letter of the message */
+ if (num == EIO)
+ fputs(strerror(num), stderr);
+ else {
+ char *errmsg = strerror(num);
+ fputc(tulower(errmsg[0]), stderr);
+ fputs(errmsg + 1, stderr);
+ }
+ break;
+ }
+ } else {
+ putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, stderr);
+ fmt++;
+ }
+ if (unset(SHINSTDIN) && lineno)
+ fprintf(stderr, " [%ld]\n", lineno);
+ else
+ putc('\n', stderr);
+ fflush(stderr);
+}
+
+/* Output a single character, for the termcap routines. *
+ * This is used instead of putchar since it can be a macro. */
+
+/**/
+int
+putraw(int c)
+{
+ putc(c, stdout);
+ return 0;
+}
+
+/* Output a single character, for the termcap routines. */
+
+/**/
+int
+putshout(int c)
+{
+ putc(c, shout);
+ return 0;
+}
+
+/* Turn a character into a visible representation thereof. The visible *
+ * string is put together in a static buffer, and this function returns *
+ * a pointer to it. Printable characters stand for themselves, DEL is *
+ * represented as "^?", newline and tab are represented as "\n" and *
+ * "\t", and normal control characters are represented in "^C" form. *
+ * Characters with bit 7 set, if unprintable, are represented as "\M-" *
+ * followed by the visible representation of the character with bit 7 *
+ * stripped off. Tokens are interpreted, rather than being treated as *
+ * literal characters. */
+
+/**/
+char *
+nicechar(int c)
+{
+ static char buf[6];
+ char *s = buf;
+ c &= 0xff;
+ if (isprint(c))
+ goto done;
+ if (c & 0x80) {
+ if (isset(PRINTEIGHTBIT))
+ goto done;
+ *s++ = '\\';
+ *s++ = 'M';
+ *s++ = '-';
+ c &= 0x7f;
+ if(isprint(c))
+ goto done;
+ }
+ if (c == 0x7f) {
+ *s++ = '^';
+ c = '?';
+ } else if (c == '\n') {
+ *s++ = '\\';
+ c = 'n';
+ } else if (c == '\t') {
+ *s++ = '\\';
+ c = 't';
+ } else if (c < 0x20) {
+ *s++ = '^';
+ c += 0x40;
+ }
+ done:
+ *s++ = c;
+ *s = 0;
+ return buf;
+}
+
+#if 0
+/* Output a string's visible representation. */
+
+/**/
+void
+nicefputs(char *s, FILE *f)
+{
+ for (; *s; s++)
+ fputs(nicechar(STOUC(*s)), f);
+}
+#endif
+
+/* Return the length of the visible representation of a string. */
+
+/**/
+size_t
+nicestrlen(char *s)
+{
+ size_t l = 0;
+
+ for (; *s; s++)
+ l += strlen(nicechar(STOUC(*s)));
+ return l;
+}
+
+/* get a symlink-free pathname for s relative to PWD */
+
+/**/
+char *
+findpwd(char *s)
+{
+ char *t;
+
+ if (*s == '/')
+ return xsymlink(s);
+ s = tricat((pwd[1]) ? pwd : "", "/", s);
+ t = xsymlink(s);
+ zsfree(s);
+ return t;
+}
+
+/* Check whether a string contains the *
+ * name of the present directory. */
+
+/**/
+int
+ispwd(char *s)
+{
+ struct stat sbuf, tbuf;
+
+ if (stat(unmeta(s), &sbuf) == 0 && stat(".", &tbuf) == 0)
+ if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino)
+ return 1;
+ return 0;
+}
+
+static char xbuf[PATH_MAX*2];
+
+/**/
+static char **
+slashsplit(char *s)
+{
+ char *t, **r, **q;
+ int t0;
+
+ if (!*s)
+ return (char **) zcalloc(sizeof(char **));
+
+ for (t = s, t0 = 0; *t; t++)
+ if (*t == '/')
+ t0++;
+ q = r = (char **) zalloc(sizeof(char **) * (t0 + 2));
+
+ while ((t = strchr(s, '/'))) {
+ *q++ = ztrduppfx(s, t - s);
+ while (*t == '/')
+ t++;
+ if (!*t) {
+ *q = NULL;
+ return r;
+ }
+ s = t;
+ }
+ *q++ = ztrdup(s);
+ *q = NULL;
+ return r;
+}
+
+/* expands symlinks and .. or . expressions */
+/* if flag = 0, only expand .. and . expressions */
+
+/**/
+static int
+xsymlinks(char *s, int flag)
+{
+ char **pp, **opp;
+ char xbuf2[PATH_MAX*2], xbuf3[PATH_MAX*2];
+ int t0, ret = 0;
+
+ opp = pp = slashsplit(s);
+ for (; *pp; pp++) {
+ if (!strcmp(*pp, ".")) {
+ zsfree(*pp);
+ continue;
+ }
+ if (!strcmp(*pp, "..")) {
+ char *p;
+
+ zsfree(*pp);
+ if (!strcmp(xbuf, "/"))
+ continue;
+ p = xbuf + strlen(xbuf);
+ while (*--p != '/');
+ *p = '\0';
+ continue;
+ }
+ if (unset(CHASELINKS)) {
+ strcat(xbuf, "/");
+ strcat(xbuf, *pp);
+ zsfree(*pp);
+ continue;
+ }
+ sprintf(xbuf2, "%s/%s", xbuf, *pp);
+ t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
+ if (t0 == -1 || !flag) {
+ strcat(xbuf, "/");
+ strcat(xbuf, *pp);
+ zsfree(*pp);
+ } else {
+ ret = 1;
+ metafy(xbuf3, t0, META_NOALLOC);
+ if (*xbuf3 == '/') {
+ strcpy(xbuf, "");
+ xsymlinks(xbuf3 + 1, flag);
+ } else
+ xsymlinks(xbuf3, flag);
+ zsfree(*pp);
+ }
+ }
+ free(opp);
+ return ret;
+}
+
+/* expand symlinks in s, and remove other weird things */
+
+/**/
+char *
+xsymlink(char *s)
+{
+ if (unset(CHASELINKS))
+ return ztrdup(s);
+ if (*s != '/')
+ return NULL;
+ *xbuf = '\0';
+ if (!xsymlinks(s + 1, 1))
+ return ztrdup(s);
+ if (!*xbuf)
+ return ztrdup("/");
+ return ztrdup(xbuf);
+}
+
+/**/
+void
+print_if_link(char *s)
+{
+ int chase;
+
+ if (*s == '/') {
+ chase = opts[CHASELINKS];
+ opts[CHASELINKS] = 1;
+ *xbuf = '\0';
+ if (xsymlinks(s + 1, 1))
+ printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout);
+ opts[CHASELINKS] = chase;
+ }
+}
+
+/* print a directory */
+
+/**/
+void
+fprintdir(char *s, FILE *f)
+{
+ Nameddir d = finddir(s);
+
+ if (!d)
+ fputs(unmeta(s), f);
+ else {
+ putc('~', f);
+ fputs(unmeta(d->nam), f);
+ fputs(unmeta(s + strlen(d->dir)), f);
+ }
+}
+
+/* Returns the current username. It caches the username *
+ * and uid to try to avoid requerying the password files *
+ * or NIS/NIS+ database. */
+
+/**/
+uid_t cached_uid;
+/**/
+char *cached_username;
+
+/**/
+char *
+get_username(void)
+{
+#ifdef HAVE_GETPWUID
+ struct passwd *pswd;
+ uid_t current_uid;
+
+ current_uid = getuid();
+ if (current_uid != cached_uid) {
+ cached_uid = current_uid;
+ zsfree(cached_username);
+ if ((pswd = getpwuid(current_uid)))
+ cached_username = ztrdup(pswd->pw_name);
+ else
+ cached_username = ztrdup("");
+ }
+#else /* !HAVE_GETPWUID */
+ cached_uid = current_uid;
+#endif /* !HAVE_GETPWUID */
+ return cached_username;
+}
+
+/* static variables needed by finddir(). */
+
+static char *finddir_full;
+static Nameddir finddir_last;
+static int finddir_best;
+
+/* ScanFunc used by finddir(). */
+
+/**/
+static void
+finddir_scan(HashNode hn, int flags)
+{
+ Nameddir nd = (Nameddir) hn;
+
+ if(nd->diff > finddir_best && !dircmp(nd->dir, finddir_full)) {
+ finddir_last=nd;
+ finddir_best=nd->diff;
+ }
+}
+
+/* See if a path has a named directory as its prefix. *
+ * If passed a NULL argument, it will invalidate any *
+ * cached information. */
+
+/**/
+Nameddir
+finddir(char *s)
+{
+ static struct nameddir homenode = { NULL, "", 0, NULL, 0 };
+ static int ffsz;
+
+ /* Invalidate directory cache if argument is NULL. This is called *
+ * whenever a node is added to or removed from the hash table, and *
+ * whenever the value of $HOME changes. (On startup, too.) */
+ if (!s) {
+ homenode.dir = home;
+ homenode.diff = strlen(home);
+ if(homenode.diff==1)
+ homenode.diff = 0;
+ if(!finddir_full)
+ finddir_full = zalloc(ffsz = PATH_MAX);
+ finddir_full[0] = 0;
+ return finddir_last = NULL;
+ }
+
+ if(!strcmp(s, finddir_full) && *finddir_full)
+ return finddir_last;
+
+ if(strlen(s) >= ffsz) {
+ free(finddir_full);
+ finddir_full = zalloc(ffsz = strlen(s) * 2);
+ }
+ strcpy(finddir_full, s);
+ finddir_best=0;
+ finddir_last=NULL;
+ finddir_scan((HashNode)&homenode, 0);
+ scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
+ return finddir_last;
+}
+
+/* add a named directory */
+
+/**/
+void
+adduserdir(char *s, char *t, int flags, int always)
+{
+ Nameddir nd;
+
+ /* We don't maintain a hash table in non-interactive shells. */
+ if (!interact)
+ return;
+
+ /* The ND_USERNAME flag means that this possible hash table *
+ * entry is derived from a passwd entry. Such entries are *
+ * subordinate to explicitly generated entries. */
+ if ((flags & ND_USERNAME) && nameddirtab->getnode2(nameddirtab, s))
+ return;
+
+ /* Normal parameter assignments generate calls to this function, *
+ * with always==0. Unless the AUTO_NAME_DIRS option is set, we *
+ * don't let such assignments actually create directory names. *
+ * Instead, a reference to the parameter as a directory name can *
+ * cause the actual creation of the hash table entry. */
+ if (!always && unset(AUTONAMEDIRS) &&
+ !nameddirtab->getnode2(nameddirtab, s))
+ return;
+
+ if (!t || *t != '/' || strlen(t) >= PATH_MAX) {
+ /* We can't use this value as a directory, so simply remove *
+ * the corresponding entry in the hash table, if any. */
+ HashNode hn = nameddirtab->removenode(nameddirtab, s);
+
+ if(hn)
+ nameddirtab->freenode(hn);
+ return;
+ }
+
+ /* add the name */
+ nd = (Nameddir) zcalloc(sizeof *nd);
+ nd->flags = flags;
+ nd->dir = ztrdup(t);
+ nameddirtab->addnode(nameddirtab, ztrdup(s), nd);
+}
+
+/* Get a named directory: this function can cause a directory name *
+ * to be added to the hash table, if it isn't there already. */
+
+/**/
+char *
+getnameddir(char *name)
+{
+ Param pm;
+ char *str;
+ Nameddir nd;
+
+ /* Check if it is already in the named directory table */
+ if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name)))
+ return dupstring(nd->dir);
+
+ /* Check if there is a scalar parameter with this name whose value *
+ * begins with a `/'. If there is, add it to the hash table and *
+ * return the new value. */
+ if ((pm = (Param) paramtab->getnode(paramtab, name)) &&
+ (PM_TYPE(pm->flags) == PM_SCALAR) &&
+ (str = getsparam(name)) && *str == '/') {
+ adduserdir(name, str, 0, 1);
+ return str;
+ }
+
+#ifdef HAVE_GETPWNAM
+ {
+ /* Retrieve an entry from the password table/database for this user. */
+ struct passwd *pw;
+ if ((pw = getpwnam(name))) {
+ char *dir = xsymlink(pw->pw_dir);
+ adduserdir(name, dir, ND_USERNAME, 1);
+ str = dupstring(dir);
+ zsfree(dir);
+ return str;
+ }
+ }
+#endif /* HAVE_GETPWNAM */
+
+ /* There are no more possible sources of directory names, so give up. */
+ return NULL;
+}
+
+/**/
+static int
+dircmp(char *s, char *t)
+{
+ if (s) {
+ for (; *s == *t; s++, t++)
+ if (!*s)
+ return 0;
+ if (!*s && *t == '/')
+ return 0;
+ }
+ return 1;
+}
+
+/* extra functions to call before displaying the prompt */
+
+/**/
+LinkList prepromptfns;
+
+/* the last time we checked mail */
+
+/**/
+time_t lastmailcheck;
+
+/* the last time we checked the people in the WATCH variable */
+
+/**/
+time_t lastwatch;
+
+/* do pre-prompt stuff */
+
+/**/
+void
+preprompt(void)
+{
+ static time_t lastperiodic;
+ LinkNode ln;
+ List list;
+ int period = getiparam("PERIOD");
+ int mailcheck = getiparam("MAILCHECK");
+
+ /* If NOTIFY is not set, then check for completed *
+ * jobs before we print the prompt. */
+ if (unset(NOTIFY))
+ scanjobs();
+ if (errflag)
+ return;
+
+ /* If a shell function named "precmd" exists, *
+ * then execute it. */
+ if ((list = getshfunc("precmd")) != &dummy_list)
+ doshfunc(list, NULL, 0, 1);
+ if (errflag)
+ return;
+
+ /* If 1) the parameter PERIOD exists, 2) the shell function *
+ * "periodic" exists, 3) it's been greater than PERIOD since we *
+ * executed "periodic", then execute it now. */
+ if (period && (time(NULL) > lastperiodic + period) &&
+ (list = getshfunc("periodic")) != &dummy_list) {
+ doshfunc(list, NULL, 0, 1);
+ lastperiodic = time(NULL);
+ }
+ if (errflag)
+ return;
+
+ /* If WATCH is set, then check for the *
+ * specified login/logout events. */
+ if (watch) {
+ if ((int) difftime(time(NULL), lastwatch) > getiparam("LOGCHECK")) {
+ dowatch();
+ lastwatch = time(NULL);
+ }
+ }
+ if (errflag)
+ return;
+
+ /* Check mail */
+ if (mailcheck && (int) difftime(time(NULL), lastmailcheck) > mailcheck) {
+ char *mailfile;
+
+ if (mailpath && *mailpath && **mailpath)
+ checkmailpath(mailpath);
+ else if ((mailfile = getsparam("MAIL")) && *mailfile) {
+ char *x[2];
+
+ x[0] = mailfile;
+ x[1] = NULL;
+ checkmailpath(x);
+ }
+ lastmailcheck = time(NULL);
+ }
+
+ /* Some people have claimed that C performs type *
+ * checking, but they were later found to be lying. */
+ for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln))
+ (**(void (**) _((void)))getdata(ln))();
+}
+
+/**/
+static void
+checkmailpath(char **s)
+{
+ struct stat st;
+ char *v, *u, c;
+
+ while (*s) {
+ for (v = *s; *v && *v != '?'; v++);
+ c = *v;
+ *v = '\0';
+ if (c != '?')
+ u = NULL;
+ else
+ u = v + 1;
+ if (**s == 0) {
+ *v = c;
+ zerr("empty MAILPATH component: %s", *s, 0);
+ } else if (stat(unmeta(*s), &st) == -1) {
+ if (errno != ENOENT)
+ zerr("%e: %s", *s, errno);
+ } else if (S_ISDIR(st.st_mode)) {
+ LinkList l;
+ DIR *lock = opendir(unmeta(*s));
+ char buf[PATH_MAX * 2], **arr, **ap;
+ int ct = 1;
+
+ if (lock) {
+ char *fn;
+ HEAPALLOC {
+ pushheap();
+ l = newlinklist();
+ while ((fn = zreaddir(lock, 1)) && !errflag) {
+ if (u)
+ sprintf(buf, "%s/%s?%s", *s, fn, u);
+ else
+ sprintf(buf, "%s/%s", *s, fn);
+ addlinknode(l, dupstring(buf));
+ ct++;
+ }
+ closedir(lock);
+ ap = arr = (char **) alloc(ct * sizeof(char *));
+
+ while ((*ap++ = (char *)ugetnode(l)));
+ checkmailpath(arr);
+ popheap();
+ } LASTALLOC;
+ }
+ } else {
+ if (st.st_size && st.st_atime <= st.st_mtime &&
+ st.st_mtime > lastmailcheck)
+ if (!u) {
+ fprintf(shout, "You have new mail.\n");
+ fflush(shout);
+ } else {
+ char *usav = underscore;
+
+ underscore = *s;
+ HEAPALLOC {
+ u = dupstring(u);
+ if (! parsestr(u)) {
+ singsub(&u);
+ zputs(u, shout);
+ fputc('\n', shout);
+ fflush(shout);
+ }
+ underscore = usav;
+ } LASTALLOC;
+ }
+ if (isset(MAILWARNING) && st.st_atime > st.st_mtime &&
+ st.st_atime > lastmailcheck && st.st_size) {
+ fprintf(shout, "The mail in %s has been read.\n", unmeta(*s));
+ fflush(shout);
+ }
+ }
+ *v = c;
+ s++;
+ }
+}
+
+/**/
+void
+freestr(void *a)
+{
+ zsfree(a);
+}
+
+/**/
+void
+gettyinfo(struct ttyinfo *ti)
+{
+ if (SHTTY != -1) {
+#ifdef HAVE_TERMIOS_H
+# ifdef HAVE_TCGETATTR
+ if (tcgetattr(SHTTY, &ti->tio) == -1)
+# else
+ if (ioctl(SHTTY, TCGETS, &ti->tio) == -1)
+# endif
+ zerr("bad tcgets: %e", NULL, errno);
+#else
+# ifdef HAVE_TERMIO_H
+ ioctl(SHTTY, TCGETA, &ti->tio);
+# else
+ ioctl(SHTTY, TIOCGETP, &ti->sgttyb);
+ ioctl(SHTTY, TIOCLGET, &ti->lmodes);
+ ioctl(SHTTY, TIOCGETC, &ti->tchars);
+ ioctl(SHTTY, TIOCGLTC, &ti->ltchars);
+# endif
+#endif
+ }
+}
+
+/**/
+void
+settyinfo(struct ttyinfo *ti)
+{
+ if (SHTTY != -1) {
+#ifdef HAVE_TERMIOS_H
+# ifdef HAVE_TCGETATTR
+# ifndef TCSADRAIN
+# define TCSADRAIN 1 /* XXX Princeton's include files are screwed up */
+# endif
+ tcsetattr(SHTTY, TCSADRAIN, &ti->tio);
+ /* if (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1) */
+# else
+ ioctl(SHTTY, TCSETS, &ti->tio);
+ /* if (ioctl(SHTTY, TCSETS, &ti->tio) == -1) */
+# endif
+ /* zerr("settyinfo: %e",NULL,errno)*/ ;
+#else
+# ifdef HAVE_TERMIO_H
+ ioctl(SHTTY, TCSETA, &ti->tio);
+# else
+ ioctl(SHTTY, TIOCSETN, &ti->sgttyb);
+ ioctl(SHTTY, TIOCLSET, &ti->lmodes);
+ ioctl(SHTTY, TIOCSETC, &ti->tchars);
+ ioctl(SHTTY, TIOCSLTC, &ti->ltchars);
+# endif
+#endif
+ }
+}
+
+/* the default tty state */
+
+/**/
+struct ttyinfo shttyinfo;
+
+/* != 0 if we need to call resetvideo() */
+
+/**/
+int resetneeded;
+
+#ifdef TIOCGWINSZ
+/* window size changed */
+
+/**/
+int winchanged;
+#endif
+
+/* check the size of the window and adjust if necessary */
+
+/**/
+void
+adjustwinsize(void)
+{
+#ifdef TIOCGWINSZ
+ int oldcols = columns, oldrows = lines;
+
+ if (SHTTY == -1)
+ return;
+
+ ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize);
+ setiparam("COLUMNS", shttyinfo.winsize.ws_col);
+ setiparam("LINES", shttyinfo.winsize.ws_row);
+ if (zleactive && (oldcols != columns || oldrows != lines)) {
+ resetneeded = winchanged = 1;
+ refresh();
+ }
+#endif /* TIOCGWINSZ */
+}
+
+/* Move a fd to a place >= 10 and mark the new fd in fdtable. If the fd *
+ * is already >= 10, it is not moved. If it is invalid, -1 is returned. */
+
+/**/
+int
+movefd(int fd)
+{
+ if(fd != -1 && fd < 10) {
+#ifdef F_DUPFD
+ int fe = fcntl(fd, F_DUPFD, 10);
+#else
+ int fe = movefd(dup(fd));
+#endif
+ zclose(fd);
+ fd = fe;
+ }
+ if(fd != -1) {
+ if (fd > max_zsh_fd) {
+ while (fd >= fdtable_size)
+ fdtable = zrealloc(fdtable, (fdtable_size *= 2));
+ max_zsh_fd = fd;
+ }
+ fdtable[fd] = 1;
+ }
+ return fd;
+}
+
+/* Move fd x to y. If x == -1, fd y is closed. */
+
+/**/
+void
+redup(int x, int y)
+{
+ if(x < 0)
+ zclose(y);
+ else if (x != y) {
+ while (y >= fdtable_size)
+ fdtable = zrealloc(fdtable, (fdtable_size *= 2));
+ dup2(x, y);
+ if ((fdtable[y] = fdtable[x]) && y > max_zsh_fd)
+ max_zsh_fd = y;
+ zclose(x);
+ }
+}
+
+/* Close the given fd, and clear it from fdtable. */
+
+/**/
+int
+zclose(int fd)
+{
+ if (fd >= 0) {
+ fdtable[fd] = 0;
+ while (max_zsh_fd > 0 && !fdtable[max_zsh_fd])
+ max_zsh_fd--;
+ if (fd == coprocin)
+ coprocin = -1;
+ if (fd == coprocout)
+ coprocout = -1;
+ }
+ return close(fd);
+}
+
+/* Get a file name relative to $TMPPREFIX which *
+ * is unique, for use as a temporary file. */
+
+/**/
+char *
+gettempname(void)
+{
+ char *s;
+
+ if (!(s = getsparam("TMPPREFIX")))
+ s = DEFAULT_TMPPREFIX;
+
+ return ((char *) mktemp(dyncat(unmeta(s), "XXXXXX")));
+}
+
+/* Check if a string contains a token */
+
+/**/
+int
+has_token(const char *s)
+{
+ while(*s)
+ if(itok(*s++))
+ return 1;
+ return 0;
+}
+
+/* Delete a character in a string */
+
+/**/
+void
+chuck(char *str)
+{
+ while ((str[0] = str[1]))
+ str++;
+}
+
+/**/
+int
+tulower(int c)
+{
+ c &= 0xff;
+ return (isupper(c) ? tolower(c) : c);
+}
+
+/**/
+int
+tuupper(int c)
+{
+ c &= 0xff;
+ return (islower(c) ? toupper(c) : c);
+}
+
+/* copy len chars from t into s, and null terminate */
+
+/**/
+void
+ztrncpy(char *s, char *t, int len)
+{
+ while (len--)
+ *s++ = *t++;
+ *s = '\0';
+}
+
+/* copy t into *s and update s */
+
+/**/
+void
+strucpy(char **s, char *t)
+{
+ char *u = *s;
+
+ while ((*u++ = *t++));
+ *s = u - 1;
+}
+
+/**/
+void
+struncpy(char **s, char *t, int n)
+{
+ char *u = *s;
+
+ while (n--)
+ *u++ = *t++;
+ *s = u;
+ *u = '\0';
+}
+
+/* Return the number of elements in an array of pointers. *
+ * It doesn't count the NULL pointer at the end. */
+
+/**/
+int
+arrlen(char **s)
+{
+ int count;
+
+ for (count = 0; *s; s++, count++);
+ return count;
+}
+
+/* Skip over a balanced pair of parenthesis. */
+
+/**/
+int
+skipparens(char inpar, char outpar, char **s)
+{
+ int level;
+
+ if (**s != inpar)
+ return -1;
+
+ for (level = 1; *++*s && level;)
+ if (**s == inpar)
+ ++level;
+ else if (**s == outpar)
+ --level;
+
+ return level;
+}
+
+/* Convert string to long. This function (without the z) *
+ * is contained in the ANSI standard C library, but a lot *
+ * of them seem to be broken. */
+
+/**/
+long
+zstrtol(const char *s, char **t, int base)
+{
+ long ret = 0;
+ int neg;
+
+ while (inblank(*s))
+ s++;
+
+ if ((neg = (*s == '-')))
+ s++;
+ else if (*s == '+')
+ s++;
+
+ if (!base)
+ if (*s != '0')
+ base = 10;
+ else if (*++s == 'x' || *s == 'X')
+ base = 16, s++;
+ else
+ base = 8;
+
+ if (base <= 10)
+ for (; *s >= '0' && *s < ('0' + base); s++)
+ ret = ret * base + *s - '0';
+ else
+ for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
+ || (*s >= 'A' && *s < ('A' + base - 10)); s++)
+ ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
+ if (t)
+ *t = (char *)s;
+ return neg ? -ret : ret;
+}
+
+/**/
+int
+setblock_stdin(void)
+{
+#ifdef O_NDELAY
+# ifdef O_NONBLOCK
+# define NONBLOCK (O_NDELAY|O_NONBLOCK)
+# else /* !O_NONBLOCK */
+# define NONBLOCK O_NDELAY
+# endif /* !O_NONBLOCK */
+#else /* !O_NDELAY */
+# ifdef O_NONBLOCK
+# define NONBLOCK O_NONBLOCK
+# else /* !O_NONBLOCK */
+# define NONBLOCK 0
+# endif /* !O_NONBLOCK */
+#endif /* !O_NDELAY */
+
+#if NONBLOCK
+ struct stat st;
+ long mode;
+
+ if (!fstat(0, &st) && !S_ISREG(st.st_mode)) {
+ mode = fcntl(0, F_GETFL);
+ if (mode != -1 && (mode & NONBLOCK) &&
+ !fcntl(0, F_SETFL, mode & ~NONBLOCK))
+ return 1;
+ }
+#endif /* NONBLOCK */
+ return 0;
+
+#undef NONBLOCK
+}
+
+/**/
+int
+checkrmall(char *s)
+{
+ fprintf(shout, "zsh: sure you want to delete all the files in ");
+ if (*s != '/') {
+ nicezputs(pwd[1] ? unmeta(pwd) : "", shout);
+ fputc('/', shout);
+ }
+ nicezputs(s, shout);
+ if(isset(RMSTARWAIT)) {
+ fputs("? (waiting ten seconds)", shout);
+ fflush(shout);
+ beep();
+ sleep(10);
+ fputc('\n', shout);
+ }
+ fputs(" [yn]? ", shout);
+ fflush(shout);
+ beep();
+ return (getquery("ny", 1) == 'y');
+}
+
+/**/
+int
+getquery(char *valid_chars, int purge)
+{
+ char c, d;
+ int isem = !strcmp(term, "emacs");
+
+#ifdef FIONREAD
+ int val = 0;
+#endif
+
+ attachtty(mypgrp);
+ if (!isem)
+ setcbreak();
+
+#ifdef FIONREAD
+ ioctl(SHTTY, FIONREAD, (char *)&val);
+ if(purge) {
+ while(val--)
+ read(SHTTY, &c, 1);
+ } else if (val) {
+ if (!isem)
+ settyinfo(&shttyinfo);
+ write(SHTTY, "n\n", 2);
+ return 'n';
+ }
+#endif
+ while (read(SHTTY, &c, 1) == 1) {
+ if (c == 'Y' || c == '\t')
+ c = 'y';
+ else if (c == 'N')
+ c = 'n';
+ if (!valid_chars)
+ break;
+ if (c == '\n') {
+ c = *valid_chars;
+ break;
+ }
+ if (strchr(valid_chars, c)) {
+ write(SHTTY, "\n", 1);
+ break;
+ }
+ beep();
+ if (icntrl(c))
+ write(SHTTY, "\b \b", 3);
+ write(SHTTY, "\b \b", 3);
+ }
+ if (isem) {
+ if (c != '\n')
+ while (read(SHTTY, &d, 1) == 1 && d != '\n');
+ } else {
+ settyinfo(&shttyinfo);
+ if (c != '\n' && !valid_chars)
+ write(SHTTY, "\n", 1);
+ }
+ return (int)c;
+}
+
+static int d;
+static char *guess, *best;
+
+/**/
+static void
+spscan(HashNode hn, int scanflags)
+{
+ int nd;
+
+ nd = spdist(hn->nam, guess, (int) strlen(guess) / 4 + 1);
+ if (nd <= d) {
+ best = hn->nam;
+ d = nd;
+ }
+}
+
+/* spellcheck a word */
+/* fix s ; if hist is nonzero, fix the history list too */
+
+/**/
+void
+spckword(char **s, int hist, int cmd, int ask)
+{
+ char *t, *u;
+ int x;
+ char ic = '\0';
+ int ne;
+ int preflen = 0;
+
+ if ((histdone & HISTFLAG_NOEXEC) || **s == '-' || **s == '%')
+ return;
+ if (!strcmp(*s, "in"))
+ return;
+ if (!(*s)[0] || !(*s)[1])
+ return;
+ if (shfunctab->getnode(shfunctab, *s) ||
+ builtintab->getnode(builtintab, *s) ||
+ cmdnamtab->getnode(cmdnamtab, *s) ||
+ aliastab->getnode(aliastab, *s) ||
+ reswdtab->getnode(reswdtab, *s))
+ return;
+ else if (isset(HASHLISTALL)) {
+ cmdnamtab->filltable(cmdnamtab);
+ if (cmdnamtab->getnode(cmdnamtab, *s))
+ return;
+ }
+ t = *s;
+ if (*t == Tilde || *t == Equals || *t == String)
+ t++;
+ for (; *t; t++)
+ if (itok(*t))
+ return;
+ best = NULL;
+ for (t = *s; *t; t++)
+ if (*t == '/')
+ break;
+ if (**s == Tilde && !*t)
+ return;
+ if (**s == String && !*t) {
+ guess = *s + 1;
+ if (*t || !ialpha(*guess))
+ return;
+ ic = String;
+ d = 100;
+ scanhashtable(paramtab, 1, 0, 0, spscan, 0);
+ } else if (**s == Equals) {
+ if (*t)
+ return;
+ if (hashcmd(guess = *s + 1, pathchecked))
+ return;
+ d = 100;
+ ic = Equals;
+ scanhashtable(aliastab, 1, 0, 0, spscan, 0);
+ scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0);
+ } else {
+ guess = *s;
+ if (*guess == Tilde || *guess == String) {
+ ic = *guess;
+ if (!*++t)
+ return;
+ guess = dupstring(guess);
+ ne = noerrs;
+ noerrs = 1;
+ singsub(&guess);
+ noerrs = ne;
+ if (!guess)
+ return;
+ preflen = strlen(guess) - strlen(t);
+ }
+ if (access(unmeta(guess), F_OK) == 0)
+ return;
+ if ((u = spname(guess)) != guess)
+ best = u;
+ if (!*t && cmd) {
+ if (hashcmd(guess, pathchecked))
+ return;
+ d = 100;
+ scanhashtable(reswdtab, 1, 0, 0, spscan, 0);
+ scanhashtable(aliastab, 1, 0, 0, spscan, 0);
+ scanhashtable(shfunctab, 1, 0, 0, spscan, 0);
+ scanhashtable(builtintab, 1, 0, 0, spscan, 0);
+ scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0);
+ }
+ }
+ if (errflag)
+ return;
+ if (best && (int)strlen(best) > 1 && strcmp(best, guess)) {
+ if (ic) {
+ if (preflen) {
+ /* do not correct the result of an expansion */
+ if (strncmp(guess, best, preflen))
+ return;
+ /* replace the temporarily expanded prefix with the original */
+ u = (char *) ncalloc(t - *s + strlen(best + preflen) + 1);
+ strncpy(u, *s, t - *s);
+ strcpy(u + (t - *s), best + preflen);
+ } else {
+ u = (char *) ncalloc(strlen(best) + 2);
+ strcpy(u + 1, best);
+ }
+ best = u;
+ guess = *s;
+ *guess = *best = ztokens[ic - Pound];
+ }
+ if (ask) {
+ char *pptbuf;
+ pptbuf = promptexpand(sprompt, 0, best, guess);
+ zputs(pptbuf, shout);
+ free(pptbuf);
+ fflush(shout);
+ beep();
+ x = getquery("nyae ", 0);
+ } else
+ x = 'y';
+ if (x == 'y' || x == ' ') {
+ *s = dupstring(best);
+ if (hist)
+ hwrep(best);
+ } else if (x == 'a') {
+ histdone |= HISTFLAG_NOEXEC;
+ } else if (x == 'e') {
+ histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL;
+ }
+ if (ic)
+ **s = ic;
+ }
+}
+
+/**/
+int
+ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm)
+{
+ int hr12;
+#ifndef HAVE_STRFTIME
+ static char *astr[] =
+ {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ static char *estr[] =
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
+ "Aug", "Sep", "Oct", "Nov", "Dec"};
+#else
+ char *origbuf = buf;
+#endif
+ char tmp[3];
+
+
+ tmp[0] = '%';
+ tmp[2] = '\0';
+ while (*fmt)
+ if (*fmt == '%') {
+ fmt++;
+ switch (*fmt++) {
+ case 'd':
+ *buf++ = '0' + tm->tm_mday / 10;
+ *buf++ = '0' + tm->tm_mday % 10;
+ break;
+ case 'e':
+ case 'f':
+ if (tm->tm_mday > 9)
+ *buf++ = '0' + tm->tm_mday / 10;
+ else if (fmt[-1] == 'e')
+ *buf++ = ' ';
+ *buf++ = '0' + tm->tm_mday % 10;
+ break;
+ case 'k':
+ case 'K':
+ if (tm->tm_hour > 9)
+ *buf++ = '0' + tm->tm_hour / 10;
+ else if (fmt[-1] == 'k')
+ *buf++ = ' ';
+ *buf++ = '0' + tm->tm_hour % 10;
+ break;
+ case 'l':
+ case 'L':
+ hr12 = tm->tm_hour % 12;
+ if (hr12 == 0)
+ hr12 = 12;
+ if (hr12 > 9)
+ *buf++ = '1';
+ else if (fmt[-1] == 'l')
+ *buf++ = ' ';
+ *buf++ = '0' + (hr12 % 10);
+ break;
+ case 'm':
+ *buf++ = '0' + (tm->tm_mon + 1) / 10;
+ *buf++ = '0' + (tm->tm_mon + 1) % 10;
+ break;
+ case 'M':
+ *buf++ = '0' + tm->tm_min / 10;
+ *buf++ = '0' + tm->tm_min % 10;
+ break;
+ case 'S':
+ *buf++ = '0' + tm->tm_sec / 10;
+ *buf++ = '0' + tm->tm_sec % 10;
+ break;
+ case 'y':
+ *buf++ = '0' + (tm->tm_year / 10) % 10;
+ *buf++ = '0' + tm->tm_year % 10;
+ break;
+#ifndef HAVE_STRFTIME
+ case 'a':
+ strucpy(&buf, astr[tm->tm_wday]);
+ break;
+ case 'b':
+ strucpy(&buf, estr[tm->tm_mon]);
+ break;
+ case 'p':
+ *buf++ = (tm->tm_hour > 11) ? 'p' : 'a';
+ *buf++ = 'm';
+ break;
+ default:
+ *buf++ = '%';
+ if (fmt[-1] != '%')
+ *buf++ = fmt[-1];
+#else
+ default:
+ *buf = '\0';
+ tmp[1] = fmt[-1];
+ strftime(buf, bufsize - strlen(origbuf), tmp, tm);
+ buf += strlen(buf);
+#endif
+ break;
+ }
+ } else
+ *buf++ = *fmt++;
+ *buf = '\0';
+ return 0;
+}
+
+/**/
+char *
+zjoin(char **arr, int delim)
+{
+ int len = 0;
+ char **s, *ret, *ptr;
+
+ for (s = arr; *s; s++)
+ len += strlen(*s) + 1;
+ if (!len)
+ return "";
+ ptr = ret = (char *) ncalloc(len);
+ for (s = arr; *s; s++) {
+ strucpy(&ptr, *s);
+ if (delim)
+ *ptr++ = delim;
+ }
+ ptr[-1] = '\0';
+ return ret;
+}
+
+/* Split a string containing a colon separated list *
+ * of items into an array of strings. */
+
+/**/
+char **
+colonsplit(char *s, int uniq)
+{
+ int ct;
+ char *t, **ret, **ptr, **p;
+
+ for (t = s, ct = 0; *t; t++) /* count number of colons */
+ if (*t == ':')
+ ct++;
+ ptr = ret = (char **) zalloc(sizeof(char **) * (ct + 2));
+
+ t = s;
+ do {
+ s = t;
+ /* move t to point at next colon */
+ for (; *t && *t != ':'; t++);
+ if (uniq)
+ for (p = ret; p < ptr; p++)
+ if (strlen(*p) == t - s && ! strncmp(*p, s, t - s))
+ goto cont;
+ *ptr = (char *) zalloc((t - s) + 1);
+ ztrncpy(*ptr++, s, t - s);
+ cont: ;
+ }
+ while (*t++);
+ *ptr = NULL;
+ return ret;
+}
+
+/**/
+static int
+skipwsep(char **s)
+{
+ char *t = *s;
+ int i = 0;
+
+ while (*t && iwsep(*t == Meta ? t[1] ^ 32 : *t)) {
+ if (*t == Meta)
+ t++;
+ t++;
+ i++;
+ }
+ *s = t;
+ return i;
+}
+
+/**/
+char **
+spacesplit(char *s, int allownull)
+{
+ char *t, **ret, **ptr;
+
+ ptr = ret = (char **) ncalloc(sizeof(*ret) * (wordcount(s, NULL, -!allownull) + 1));
+
+ t = s;
+ skipwsep(&s);
+ if (*s && isep(*s == Meta ? s[1] ^ 32 : *s))
+ *ptr++ = dupstring(allownull ? "" : nulstring);
+ else if (!allownull && t != s)
+ *ptr++ = dupstring("");
+ while (*s) {
+ if (isep(*s == Meta ? s[1] ^ 32 : *s)) {
+ if (*s == Meta)
+ s++;
+ s++;
+ skipwsep(&s);
+ }
+ t = s;
+ findsep(&s, NULL);
+ if (s > t || allownull) {
+ *ptr = (char *) ncalloc((s - t) + 1);
+ ztrncpy(*ptr++, t, s - t);
+ } else
+ *ptr++ = dupstring(nulstring);
+ t = s;
+ skipwsep(&s);
+ }
+ if (!allownull && t != s)
+ *ptr++ = dupstring("");
+ *ptr = NULL;
+ return ret;
+}
+
+/**/
+static int
+findsep(char **s, char *sep)
+{
+ int i;
+ char *t, *tt;
+
+ if (!sep) {
+ for (t = *s; *t; t++) {
+ if (*t == Meta) {
+ if (isep(t[1] ^ 32))
+ break;
+ t++;
+ } else if (isep(*t))
+ break;
+ }
+ i = t - *s;
+ *s = t;
+ return i;
+ }
+ if (!sep[0]) {
+ return **s ? ++*s, 1 : -1;
+ }
+ for (i = 0; **s; i++) {
+ for (t = sep, tt = *s; *t && *tt && *t == *tt; t++, tt++);
+ if (!*t)
+ return i;
+ if (*(*s)++ == Meta) {
+ (*s)++;
+#ifdef DEBUG
+ if (! **s)
+ fprintf(stderr, "BUG: unexpected end of string in findsep()\n");
+#endif
+ }
+ }
+ return -1;
+}
+
+/**/
+char *
+findword(char **s, char *sep)
+{
+ char *r, *t;
+ int sl;
+
+ if (!**s)
+ return NULL;
+
+ if (sep) {
+ sl = strlen(sep);
+ r = *s;
+ while (! findsep(s, sep)) {
+ r = *s += sl;
+ }
+ return r;
+ }
+ for (t = *s; *t; t++) {
+ if (*t == Meta) {
+ if (! isep(t[1] ^ 32))
+ break;
+ t++;
+ } else if (! isep(*t))
+ break;
+ }
+ *s = t;
+ findsep(s, sep);
+ return t;
+}
+
+/**/
+int
+wordcount(char *s, char *sep, int mul)
+{
+ int r, sl, c;
+
+ if (sep) {
+ r = 1;
+ sl = strlen(sep);
+ for (; (c = findsep(&s, sep)) >= 0; s += sl)
+ if ((c && *(s + sl)) || mul)
+ r++;
+ } else {
+ char *t = s;
+
+ r = 0;
+ if (mul <= 0)
+ skipwsep(&s);
+ if ((*s && isep(*s == Meta ? s[1] ^ 32 : *s)) ||
+ (mul < 0 && t != s))
+ r++;
+ for (; *s; r++) {
+ if (isep(*s == Meta ? s[1] ^ 32 : *s)) {
+ if (*s == Meta)
+ s++;
+ s++;
+ if (mul <= 0)
+ skipwsep(&s);
+ }
+ findsep(&s, NULL);
+ t = s;
+ if (mul <= 0)
+ skipwsep(&s);
+ }
+ if (mul < 0 && t != s)
+ r++;
+ }
+ return r;
+}
+
+/**/
+char *
+sepjoin(char **s, char *sep)
+{
+ char *r, *p, **t;
+ int l, sl, elide = 0;
+ char sepbuf[3];
+
+ if (!*s)
+ return "";
+ if (!sep) {
+ elide = 1;
+ sep = sepbuf;
+ sepbuf[0] = *ifs;
+ sepbuf[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0';
+ sepbuf[2] = '\0';
+ }
+ sl = strlen(sep);
+ for (t = s, l = 1 - sl; *t; l += strlen(*t) + sl, t++);
+ r = p = (char *) ncalloc(l);
+ t = s;
+ while (*t) {
+ strucpy(&p, *t);
+ if (*++t)
+ strucpy(&p, sep);
+ }
+ *p = '\0';
+ return r;
+}
+
+/**/
+char **
+sepsplit(char *s, char *sep, int allownull)
+{
+ int n, sl;
+ char *t, *tt, **r, **p;
+
+ if (!sep)
+ return spacesplit(s, allownull);
+
+ sl = strlen(sep);
+ n = wordcount(s, sep, 1);
+ r = p = (char **) ncalloc((n + 1) * sizeof(char *));
+
+ for (t = s; n--;) {
+ tt = t;
+ findsep(&t, sep);
+ *p = (char *) ncalloc(t - tt + 1);
+ strncpy(*p, tt, t - tt);
+ (*p)[t - tt] = '\0';
+ p++;
+ t += sl;
+ }
+ *p = NULL;
+
+ return r;
+}
+
+/* Get the definition of a shell function */
+
+/**/
+List
+getshfunc(char *nam)
+{
+ Shfunc shf;
+
+ if (!(shf = (Shfunc) shfunctab->getnode(shfunctab, nam)))
+ return &dummy_list;
+
+ return shf->funcdef;
+}
+
+/* allocate a tree element */
+
+static int sizetab[N_COUNT] = {
+ sizeof(struct list),
+ sizeof(struct sublist),
+ sizeof(struct pline),
+ sizeof(struct cmd),
+ sizeof(struct redir),
+ sizeof(struct cond),
+ sizeof(struct forcmd),
+ sizeof(struct casecmd),
+ sizeof(struct ifcmd),
+ sizeof(struct whilecmd),
+ sizeof(struct varasg),
+ sizeof(struct autofn),
+};
+
+static int offstab[N_COUNT] = {
+ offsetof(struct list, left),
+ offsetof(struct sublist, left),
+ offsetof(struct pline, left),
+ offsetof(struct cmd, u),
+ offsetof(struct redir, name),
+ offsetof(struct cond, left),
+ offsetof(struct forcmd, name),
+ offsetof(struct casecmd, pats),
+ offsetof(struct ifcmd, ifls),
+ offsetof(struct whilecmd, cont),
+ offsetof(struct varasg, name),
+ sizeof(struct autofn),
+};
+
+static int flagtab[N_COUNT] = {
+ NT_SET(N_LIST, NT_NODE, NT_NODE, 0, 0),
+ NT_SET(N_SUBLIST, NT_NODE, NT_NODE, 0, 0),
+ NT_SET(N_PLINE, NT_NODE, NT_NODE, 0, 0),
+ NT_SET(N_CMD, NT_NODE, NT_STR | NT_LIST, NT_NODE | NT_LIST, NT_NODE | NT_LIST),
+ NT_SET(N_REDIR, NT_STR, 0, 0, 0),
+ NT_SET(N_COND, NT_NODE, NT_NODE, 0, 0),
+ NT_SET(N_FOR, NT_STR, NT_STR, NT_STR, NT_NODE),
+ NT_SET(N_CASE, NT_STR | NT_ARR, NT_NODE | NT_ARR, 0, 0),
+ NT_SET(N_IF, NT_NODE | NT_ARR, NT_NODE | NT_ARR, 0, 0),
+ NT_SET(N_WHILE, NT_NODE, NT_NODE, 0, 0),
+ NT_SET(N_VARASG, NT_STR, NT_STR, NT_STR | NT_LIST, 0),
+ NT_SET(N_AUTOFN, 0, 0, 0, 0),
+};
+
+/**/
+void *
+allocnode(int type)
+{
+ struct node *n;
+
+ n = (struct node *) alloc(sizetab[type]);
+ memset((void *) n, 0, sizetab[type]);
+ n->ntype = flagtab[type];
+ if (useheap)
+ n->ntype |= NT_HEAP;
+
+ return (void *) n;
+}
+
+/**/
+void *
+dupstruct(void *a)
+{
+ struct node *n, *r;
+
+ n = (struct node *) a;
+ if (!a || ((List) a) == &dummy_list)
+ return (void *) a;
+
+ if ((n->ntype & NT_HEAP) && !useheap) {
+ HEAPALLOC {
+ n = (struct node *) dupstruct2((void *) n);
+ } LASTALLOC;
+ n = simplifystruct(n);
+ }
+ r = (struct node *)dupstruct2((void *) n);
+
+ if (!(n->ntype & NT_HEAP) && useheap)
+ r = expandstruct(r, N_LIST);
+
+ return (void *) r;
+}
+
+/**/
+static struct node *
+simplifystruct(struct node *n)
+{
+ if (!n || ((List) n) == &dummy_list)
+ return n;
+
+ switch (NT_TYPE(n->ntype)) {
+ case N_LIST:
+ {
+ List l = (List) n;
+
+ l->left = (Sublist) simplifystruct((struct node *)l->left);
+ if ((l->type & Z_SYNC) && !l->right)
+ return (struct node *)l->left;
+ }
+ break;
+ case N_SUBLIST:
+ {
+ Sublist sl = (Sublist) n;
+
+ sl->left = (Pline) simplifystruct((struct node *)sl->left);
+ if (sl->type == END && !sl->flags && !sl->right)
+ return (struct node *)sl->left;
+ }
+ break;
+ case N_PLINE:
+ {
+ Pline pl = (Pline) n;
+
+ pl->left = (Cmd) simplifystruct((struct node *)pl->left);
+ if (pl->type == END && !pl->right)
+ return (struct node *)pl->left;
+ }
+ break;
+ case N_CMD:
+ {
+ Cmd c = (Cmd) n;
+ int i = 0;
+
+ if (empty(c->args))
+ c->args = NULL, i++;
+ if (empty(c->redir))
+ c->redir = NULL, i++;
+ if (empty(c->vars))
+ c->vars = NULL, i++;
+
+ c->u.list = (List) simplifystruct((struct node *)c->u.list);
+ if (i == 3 && !c->flags &&
+ (c->type == CWHILE || c->type == CIF ||
+ c->type == COND))
+ return (struct node *)c->u.list;
+ }
+ break;
+ case N_FOR:
+ {
+ Forcmd f = (Forcmd) n;
+
+ f->list = (List) simplifystruct((struct node *)f->list);
+ }
+ break;
+ case N_CASE:
+ {
+ struct casecmd *c = (struct casecmd *)n;
+ List *l;
+
+ for (l = c->lists; *l; l++)
+ *l = (List) simplifystruct((struct node *)*l);
+ }
+ break;
+ case N_IF:
+ {
+ struct ifcmd *i = (struct ifcmd *)n;
+ List *l;
+
+ for (l = i->ifls; *l; l++)
+ *l = (List) simplifystruct((struct node *)*l);
+ for (l = i->thenls; *l; l++)
+ *l = (List) simplifystruct((struct node *)*l);
+ }
+ break;
+ case N_WHILE:
+ {
+ struct whilecmd *w = (struct whilecmd *)n;
+
+ w->cont = (List) simplifystruct((struct node *)w->cont);
+ w->loop = (List) simplifystruct((struct node *)w->loop);
+ }
+ }
+
+ return n;
+}
+
+/**/
+struct node *
+expandstruct(struct node *n, int exp)
+{
+ struct node *m;
+
+ if (!n || ((List) n) == &dummy_list)
+ return n;
+
+ if (exp != N_COUNT && exp != NT_TYPE(n->ntype)) {
+ switch (exp) {
+ case N_LIST:
+ {
+ List l;
+
+ m = (struct node *) allocnode(N_LIST);
+ l = (List) m;
+ l->type = Z_SYNC;
+ l->left = (Sublist) expandstruct(n, N_SUBLIST);
+
+ return (struct node *)l;
+ }
+ case N_SUBLIST:
+ {
+ Sublist sl;
+
+ m = (struct node *) allocnode(N_SUBLIST);
+ sl = (Sublist) m;
+ sl->type = END;
+ sl->left = (Pline) expandstruct(n, N_PLINE);
+
+ return (struct node *)sl;
+ }
+ case N_PLINE:
+ {
+ Pline pl;
+
+ m = (struct node *) allocnode(N_PLINE);
+ pl = (Pline) m;
+ pl->type = END;
+ pl->left = (Cmd) expandstruct(n, N_CMD);
+
+ return (struct node *)pl;
+ }
+ case N_CMD:
+ {
+ Cmd c;
+
+ m = (struct node *) allocnode(N_CMD);
+ c = (Cmd) m;
+ switch (NT_TYPE(n->ntype)) {
+ case N_WHILE:
+ c->type = CWHILE;
+ break;
+ case N_IF:
+ c->type = CIF;
+ break;
+ case N_COND:
+ c->type = COND;
+ }
+ c->u.list = (List) expandstruct(n, NT_TYPE(n->ntype));
+ c->args = newlinklist();
+ c->vars = newlinklist();
+ c->redir = newlinklist();
+
+ return (struct node *)c;
+ }
+ }
+ } else
+ switch (NT_TYPE(n->ntype)) {
+ case N_LIST:
+ {
+ List l = (List) n;
+
+ l->left = (Sublist) expandstruct((struct node *)l->left,
+ N_SUBLIST);
+ l->right = (List) expandstruct((struct node *)l->right,
+ N_LIST);
+ }
+ break;
+ case N_SUBLIST:
+ {
+ Sublist sl = (Sublist) n;
+
+ sl->left = (Pline) expandstruct((struct node *)sl->left,
+ N_PLINE);
+ sl->right = (Sublist) expandstruct((struct node *)sl->right,
+ N_SUBLIST);
+ }
+ break;
+ case N_PLINE:
+ {
+ Pline pl = (Pline) n;
+
+ pl->left = (Cmd) expandstruct((struct node *)pl->left,
+ N_CMD);
+ pl->right = (Pline) expandstruct((struct node *)pl->right,
+ N_PLINE);
+ }
+ break;
+ case N_CMD:
+ {
+ Cmd c = (Cmd) n;
+
+ if (!c->args)
+ c->args = newlinklist();
+ if (!c->vars)
+ c->vars = newlinklist();
+ if (!c->redir)
+ c->redir = newlinklist();
+
+ switch (c->type) {
+ case CFOR:
+ case CSELECT:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_FOR);
+ break;
+ case CWHILE:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_WHILE);
+ break;
+ case CIF:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_IF);
+ break;
+ case CCASE:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_CASE);
+ break;
+ case COND:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_COND);
+ break;
+ case ZCTIME:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_SUBLIST);
+ break;
+ case AUTOFN:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_AUTOFN);
+ break;
+ default:
+ c->u.list = (List) expandstruct((struct node *)c->u.list,
+ N_LIST);
+ }
+ }
+ break;
+ case N_FOR:
+ {
+ Forcmd f = (Forcmd) n;
+
+ f->list = (List) expandstruct((struct node *)f->list,
+ N_LIST);
+ }
+ break;
+ case N_CASE:
+ {
+ struct casecmd *c = (struct casecmd *)n;
+ List *l;
+
+ for (l = c->lists; *l; l++)
+ *l = (List) expandstruct((struct node *)*l, N_LIST);
+ }
+ break;
+ case N_IF:
+ {
+ struct ifcmd *i = (struct ifcmd *)n;
+ List *l;
+
+ for (l = i->ifls; *l; l++)
+ *l = (List) expandstruct((struct node *)*l, N_LIST);
+ for (l = i->thenls; *l; l++)
+ *l = (List) expandstruct((struct node *)*l, N_LIST);
+ }
+ break;
+ case N_WHILE:
+ {
+ struct whilecmd *w = (struct whilecmd *)n;
+
+ w->cont = (List) expandstruct((struct node *)w->cont,
+ N_LIST);
+ w->loop = (List) expandstruct((struct node *)w->loop,
+ N_LIST);
+ }
+ }
+
+ return n;
+}
+
+/* duplicate a syntax tree */
+
+/**/
+static void *
+dupstruct2(void *a)
+{
+ void **onodes, **nnodes, *ret, *n, *on;
+ int type, heap;
+ size_t nodeoffs;
+
+ if (!a || ((List) a) == &dummy_list)
+ return a;
+ type = *(int *)a;
+ ret = alloc(sizetab[NT_TYPE(type)]);
+ memcpy(ret, a, nodeoffs = offstab[NT_TYPE(type)]);
+ *(int*)ret = (type & ~NT_HEAP) | (useheap ? NT_HEAP : 0);
+ onodes = (void **) ((char *)a + nodeoffs);
+ nnodes = (void **) ((char *)ret + nodeoffs);
+ heap = type & NT_HEAP;
+ for (type = (type & 0xffff00) >> 4; (type >>= 4); *nnodes++ = n) {
+ if (!(on = *onodes++))
+ n = NULL;
+ else {
+ switch (type & 0xf) {
+ case NT_NODE:
+ n = dupstruct2(on);
+ break;
+ case NT_STR:
+ n = dupstring(on);
+ break;
+ case NT_LIST | NT_NODE:
+ if (heap)
+ if (useheap)
+ n = duplist(on, (VFunc) dupstruct2);
+ else
+ n = list2arr(on, (VFunc) dupstruct2);
+ else if (useheap)
+ n = arr2list(on, (VFunc) dupstruct2);
+ else
+ n = duparray(on, (VFunc) dupstruct2);
+ break;
+ case NT_LIST | NT_STR:
+ if (heap)
+ if (useheap)
+ n = duplist(on, (VFunc) dupstring);
+ else
+ n = list2arr(on, (VFunc) ztrdup);
+ else if (useheap)
+ n = arr2list(on, (VFunc) dupstring);
+ else
+ n = duparray(on, (VFunc) ztrdup);
+ break;
+ case NT_NODE | NT_ARR:
+ n = duparray(on, (VFunc) dupstruct2);
+ break;
+ case NT_STR | NT_ARR:
+ n = duparray(on, (VFunc) (useheap ? dupstring : ztrdup));
+ break;
+ default:
+ DPUTS(1, "BUG: bad node type in dupstruct2()");
+ abort();
+ }
+ }
+ }
+ return ret;
+}
+
+/* free a syntax tree */
+
+/**/
+void
+freestruct(void *a)
+{
+ void **nodes, *n;
+ int type, size;
+
+ if (!a || ((List) a) == &dummy_list)
+ return;
+
+ type = * (int *) a;
+ nodes = (void **) ((char *)a + offstab[NT_TYPE(type)]);
+ size = sizetab[NT_TYPE(type)];
+ for (type = (type & 0xffff00) >> 4; (type >>= 4);) {
+ if ((n = *nodes++)) {
+ switch (type & 0xf) {
+ case NT_NODE:
+ freestruct(n);
+ break;
+ case NT_STR:
+ zsfree((char *) n);
+ break;
+ case NT_LIST | NT_NODE:
+ case NT_NODE | NT_ARR:
+ {
+ void **p = (void **) n;
+
+ while (*p)
+ freestruct(*p++);
+ zfree(n, sizeof(void *) * (p + 1 - (void **) n));
+ break;
+ }
+ case NT_LIST | NT_STR:
+ case NT_STR | NT_ARR:
+ freearray((char **) n);
+ break;
+ default:
+ DPUTS(1, "BUG: bad node type in freenode()");
+ abort();
+ }
+ }
+ }
+ DPUTS(size != ((char *) nodes) - ((char *) a),
+ "BUG: size wrong in freenode()");
+ zfree(a, size);
+}
+
+/**/
+static LinkList
+duplist(LinkList l, VFunc func)
+{
+ LinkList ret;
+ LinkNode node;
+
+ ret = newlinklist();
+ for (node = firstnode(l); node; incnode(node))
+ addlinknode(ret, func(getdata(node)));
+ return ret;
+}
+
+/**/
+static char **
+duparray(char **arr, VFunc func)
+{
+ char **ret, **rr;
+
+ ret = (char **) alloc((arrlen(arr) + 1) * sizeof(char *));
+ for (rr = ret; *arr;)
+ *rr++ = (char *)func(*arr++);
+ *rr = NULL;
+
+ return ret;
+}
+
+/**/
+static char **
+list2arr(LinkList l, VFunc func)
+{
+ char **arr, **r;
+ LinkNode n;
+
+ arr = r = (char **) alloc((countlinknodes(l) + 1) * sizeof(char *));
+
+ for (n = firstnode(l); n; incnode(n))
+ *r++ = (char *)func(getdata(n));
+ *r = NULL;
+
+ return arr;
+}
+
+/**/
+static LinkList
+arr2list(char **arr, VFunc func)
+{
+ LinkList l = newlinklist();
+
+ while (*arr)
+ addlinknode(l, func(*arr++));
+
+ return l;
+}
+
+/**/
+char **
+mkarray(char *s)
+{
+ char **t = (char **) zalloc((s) ? (2 * sizeof s) : (sizeof s));
+
+ if ((*t = s))
+ t[1] = NULL;
+ return t;
+}
+
+/**/
+void
+beep(void)
+{
+ if (isset(BEEP))
+ write(SHTTY, "\07", 1);
+}
+
+/**/
+void
+freearray(char **s)
+{
+ char **t = s;
+
+ while (*s)
+ zsfree(*s++);
+ free(t);
+}
+
+/**/
+int
+equalsplit(char *s, char **t)
+{
+ for (; *s && *s != '='; s++);
+ if (*s == '=') {
+ *s++ = '\0';
+ *t = s;
+ return 1;
+ }
+ return 0;
+}
+
+/* see if the right side of a list is trivial */
+
+/**/
+void
+simplifyright(List l)
+{
+ Cmd c;
+
+ if (l == &dummy_list || !l->right)
+ return;
+ if (l->right->right || l->right->left->right ||
+ l->right->left->flags || l->right->left->left->right ||
+ l->left->flags)
+ return;
+ c = l->left->left->left;
+ if (c->type != SIMPLE || nonempty(c->args) || nonempty(c->redir)
+ || nonempty(c->vars))
+ return;
+ l->right = NULL;
+ return;
+}
+
+/* the ztypes table */
+
+/**/
+short int typtab[256];
+
+/* initialize the ztypes table */
+
+/**/
+void
+inittyptab(void)
+{
+ int t0;
+ char *s;
+
+ for (t0 = 0; t0 != 256; t0++)
+ typtab[t0] = 0;
+ for (t0 = 0; t0 != 32; t0++)
+ typtab[t0] = typtab[t0 + 128] = ICNTRL;
+ typtab[127] = ICNTRL;
+ for (t0 = '0'; t0 <= '9'; t0++)
+ typtab[t0] = IDIGIT | IALNUM | IWORD | IIDENT | IUSER;
+ for (t0 = 'a'; t0 <= 'z'; t0++)
+ typtab[t0] = typtab[t0 - 'a' + 'A'] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
+ for (t0 = 0240; t0 != 0400; t0++)
+ typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
+ typtab['_'] = IIDENT | IUSER;
+ typtab['-'] = IUSER;
+ typtab[' '] |= IBLANK | INBLANK;
+ typtab['\t'] |= IBLANK | INBLANK;
+ typtab['\n'] |= INBLANK;
+ typtab['\0'] |= IMETA;
+ typtab[STOUC(Meta) ] |= IMETA;
+ typtab[STOUC(Marker)] |= IMETA;
+ for (t0 = (int)STOUC(Pound); t0 <= (int)STOUC(Nularg); t0++)
+ typtab[t0] |= ITOK | IMETA;
+ for (s = ifs ? ifs : DEFAULT_IFS; *s; s++) {
+ if (inblank(*s))
+ if (s[1] == *s)
+ s++;
+ else
+ typtab[STOUC(*s)] |= IWSEP;
+ typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= ISEP;
+ }
+ for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++)
+ typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= IWORD;
+ for (s = SPECCHARS; *s; s++)
+ typtab[STOUC(*s)] |= ISPECIAL;
+ if (isset(BANGHIST) && bangchar && interact && isset(SHINSTDIN))
+ typtab[bangchar] |= ISPECIAL;
+}
+
+/**/
+char **
+arrdup(char **s)
+{
+ char **x, **y;
+
+ y = x = (char **) ncalloc(sizeof(char *) * (arrlen(s) + 1));
+
+ while ((*x++ = dupstring(*s++)));
+ return y;
+}
+
+/**/
+static char *
+spname(char *oldname)
+{
+ char *p, spnameguess[PATH_MAX + 1], spnamebest[PATH_MAX + 1];
+ static char newname[PATH_MAX + 1];
+ char *new = newname, *old;
+ int bestdist = 200, thisdist;
+
+ old = oldname;
+ for (;;) {
+ while (*old == '/')
+ *new++ = *old++;
+ *new = '\0';
+ if (*old == '\0')
+ return newname;
+ p = spnameguess;
+ for (; *old != '/' && *old != '\0'; old++)
+ if (p < spnameguess + PATH_MAX)
+ *p++ = *old;
+ *p = '\0';
+ if ((thisdist = mindist(newname, spnameguess, spnamebest)) >= 3) {
+ if (bestdist < 3) {
+ strcpy(new, spnameguess);
+ strcat(new, old);
+ return newname;
+ } else
+ return NULL;
+ } else
+ bestdist = thisdist;
+ for (p = spnamebest; (*new = *p++);)
+ new++;
+ }
+}
+
+/**/
+static int
+mindist(char *dir, char *mindistguess, char *mindistbest)
+{
+ int mindistd, nd;
+ DIR *dd;
+ char *fn;
+ char buf[PATH_MAX];
+
+ if (dir[0] == '\0')
+ dir = ".";
+ mindistd = 100;
+ sprintf(buf, "%s/%s", dir, mindistguess);
+ if (access(unmeta(buf), F_OK) == 0) {
+ strcpy(mindistbest, mindistguess);
+ return 0;
+ }
+ if (!(dd = opendir(unmeta(dir))))
+ return mindistd;
+ while ((fn = zreaddir(dd, 0))) {
+ nd = spdist(fn, mindistguess,
+ (int)strlen(mindistguess) / 4 + 1);
+ if (nd <= mindistd) {
+ strcpy(mindistbest, fn);
+ mindistd = nd;
+ if (mindistd == 0)
+ break;
+ }
+ }
+ closedir(dd);
+ return mindistd;
+}
+
+/**/
+static int
+spdist(char *s, char *t, int thresh)
+{
+ char *p, *q;
+ char *keymap =
+ "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
+\t1234567890-=\t\
+\tqwertyuiop[]\t\
+\tasdfghjkl;'\n\t\
+\tzxcvbnm,./\t\t\t\
+\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
+\t!@#$%^&*()_+\t\
+\tQWERTYUIOP{}\t\
+\tASDFGHJKL:\"\n\t\
+\tZXCVBNM<>?\n\n\t\
+\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+
+ if (!strcmp(s, t))
+ return 0;
+/* any number of upper/lower mistakes allowed (dist = 1) */
+ for (p = s, q = t; *p && tulower(*p) == tulower(*q); p++, q++);
+ if (!*p && !*q)
+ return 1;
+ if (!thresh)
+ return 200;
+ for (p = s, q = t; *p && *q; p++, q++)
+ if (*p == *q)
+ continue; /* don't consider "aa" transposed, ash */
+ else if (p[1] == q[0] && q[1] == p[0]) /* transpositions */
+ return spdist(p + 2, q + 2, thresh - 1) + 1;
+ else if (p[1] == q[0]) /* missing letter */
+ return spdist(p + 1, q + 0, thresh - 1) + 2;
+ else if (p[0] == q[1]) /* missing letter */
+ return spdist(p + 0, q + 1, thresh - 1) + 2;
+ else if (*p != *q)
+ break;
+ if ((!*p && strlen(q) == 1) || (!*q && strlen(p) == 1))
+ return 2;
+ for (p = s, q = t; *p && *q; p++, q++)
+ if (p[0] != q[0] && p[1] == q[1]) {
+ int t0;
+ char *z;
+
+ /* mistyped letter */
+
+ if (!(z = strchr(keymap, p[0])) || *z == '\n' || *z == '\t')
+ return spdist(p + 1, q + 1, thresh - 1) + 1;
+ t0 = z - keymap;
+ if (*q == keymap[t0 - 15] || *q == keymap[t0 - 14] ||
+ *q == keymap[t0 - 13] ||
+ *q == keymap[t0 - 1] || *q == keymap[t0 + 1] ||
+ *q == keymap[t0 + 13] || *q == keymap[t0 + 14] ||
+ *q == keymap[t0 + 15])
+ return spdist(p + 1, q + 1, thresh - 1) + 2;
+ return 200;
+ } else if (*p != *q)
+ break;
+ return 200;
+}
+
+/* set cbreak mode, or the equivalent */
+
+/**/
+void
+setcbreak(void)
+{
+ struct ttyinfo ti;
+
+ ti = shttyinfo;
+#ifdef HAS_TIO
+ ti.tio.c_lflag &= ~ICANON;
+ ti.tio.c_cc[VMIN] = 1;
+ ti.tio.c_cc[VTIME] = 0;
+#else
+ ti.sgttyb.sg_flags |= CBREAK;
+#endif
+ settyinfo(&ti);
+}
+
+/* give the tty to some process */
+
+/**/
+void
+attachtty(pid_t pgrp)
+{
+ static int ep = 0;
+
+ if (jobbing) {
+#ifdef HAVE_TCSETPGRP
+ if (SHTTY != -1 && tcsetpgrp(SHTTY, pgrp) == -1 && !ep)
+#else
+# if ardent
+ if (SHTTY != -1 && setpgrp() == -1 && !ep)
+# else
+ int arg = pgrp;
+
+ if (SHTTY != -1 && ioctl(SHTTY, TIOCSPGRP, &arg) == -1 && !ep)
+# endif
+#endif
+ {
+ if (pgrp != mypgrp && kill(pgrp, 0) == -1)
+ attachtty(mypgrp);
+ else {
+ if (errno != ENOTTY)
+ {
+ zerr("can't set tty pgrp: %e", NULL, errno);
+ fflush(stderr);
+ }
+ opts[MONITOR] = 0;
+ ep = 1;
+ errflag = 0;
+ }
+ }
+ }
+}
+
+/* get the process group associated with the tty */
+
+/**/
+pid_t
+gettygrp(void)
+{
+ pid_t arg;
+
+ if (SHTTY == -1)
+ return -1;
+
+#ifdef HAVE_TCSETPGRP
+ arg = tcgetpgrp(SHTTY);
+#else
+ ioctl(SHTTY, TIOCGPGRP, &arg);
+#endif
+
+ return arg;
+}
+
+/* Return the output baudrate */
+
+#ifdef HAVE_SELECT
+/**/
+long
+getbaudrate(struct ttyinfo *shttyinfo)
+{
+ long speedcode;
+
+#ifdef HAS_TIO
+# if defined(HAVE_TCGETATTR) && defined(HAVE_TERMIOS_H)
+ speedcode = cfgetospeed(&shttyinfo->tio);
+# else
+ speedcode = shttyinfo->tio.c_cflag & CBAUD;
+# endif
+#else
+ speedcode = shttyinfo->sgttyb.sg_ospeed;
+#endif
+
+ switch (speedcode) {
+ case B0:
+ return (0L);
+ case B50:
+ return (50L);
+ case B75:
+ return (75L);
+ case B110:
+ return (110L);
+ case B134:
+ return (134L);
+ case B150:
+ return (150L);
+ case B200:
+ return (200L);
+ case B300:
+ return (300L);
+ case B600:
+ return (600L);
+#ifdef _B900
+ case _B900:
+ return (900L);
+#endif
+ case B1200:
+ return (1200L);
+ case B1800:
+ return (1800L);
+ case B2400:
+ return (2400L);
+#ifdef _B3600
+ case _B3600:
+ return (3600L);
+#endif
+ case B4800:
+ return (4800L);
+#ifdef _B7200
+ case _B7200:
+ return (7200L);
+#endif
+ case B9600:
+ return (9600L);
+#ifdef B19200
+ case B19200:
+ return (19200L);
+#else
+# ifdef EXTA
+ case EXTA:
+ return (19200L);
+# endif
+#endif
+#ifdef B38400
+ case B38400:
+ return (38400L);
+#else
+# ifdef EXTB
+ case EXTB:
+ return (38400L);
+# endif
+#endif
+#ifdef B57600
+ case B57600:
+ return (57600L);
+#endif
+#ifdef B115200
+ case B115200:
+ return (115200L);
+#endif
+#ifdef B230400
+ case B230400:
+ return (230400L);
+#endif
+#ifdef B460800
+ case B460800:
+ return (460800L);
+#endif
+ default:
+ if (speedcode >= 100)
+ return speedcode;
+ break;
+ }
+ return (0L);
+}
+#endif
+
+/* Escape tokens and null characters. Buf is the string which should be *
+ * escaped. len is the length of the string. If len is -1, buf should be *
+ * null terminated. If len is non-negative and the third paramerer is not *
+ * META_DUP, buf should point to an at least len+1 long memory area. The *
+ * return value points to the quoted string. If the given string does not *
+ * contain any special character which should be quoted and the third *
+ * parameter is not META_(HEAP|)DUP, buf is returned unchanged (a *
+ * terminating null character is appended to buf if necessary). Otherwise *
+ * the third `heap' argument determines the method used to allocate space *
+ * for the result. It can have the following values: *
+ * META_REALLOC: use zrealloc on buf *
+ * META_HREALLOC: use hrealloc on buf *
+ * META_USEHEAP: get memory from the heap. This leaves buf unchanged. *
+ * META_NOALLOC: buf points to a memory area which is long enough to hold *
+ * the quoted form, just quote it and return buf. *
+ * META_STATIC: store the quoted string in a static area. The original *
+ * sting should be at most PATH_MAX long. *
+ * META_ALLOC: allocate memory for the new string with zalloc(). *
+ * META_DUP: leave buf unchanged and allocate space for the return *
+ * value even if buf does not contains special characters *
+ * META_HEAPDUP: same as META_DUP, but uses the heap */
+
+/**/
+char *
+metafy(char *buf, int len, int heap)
+{
+ int meta = 0;
+ char *t, *p, *e;
+ static char mbuf[PATH_MAX*2+1];
+
+ if (len == -1) {
+ for (e = buf, len = 0; *e; len++)
+ if (imeta(*e++))
+ meta++;
+ } else
+ for (e = buf; e < buf + len;)
+ if (imeta(*e++))
+ meta++;
+
+ if (meta || heap == META_DUP || heap == META_HEAPDUP) {
+ switch (heap) {
+ case META_REALLOC:
+ buf = zrealloc(buf, len + meta + 1);
+ break;
+ case META_HREALLOC:
+ buf = hrealloc(buf, len, len + meta + 1);
+ break;
+ case META_ALLOC:
+ case META_DUP:
+ buf = memcpy(zalloc(len + meta + 1), buf, len);
+ break;
+ case META_USEHEAP:
+ case META_HEAPDUP:
+ buf = memcpy(halloc(len + meta + 1), buf, len);
+ break;
+ case META_STATIC:
+#ifdef DEBUG
+ if (len > PATH_MAX) {
+ fprintf(stderr, "BUG: len = %d > PATH_MAX in metafy\n", len);
+ fflush(stderr);
+ }
+#endif
+ buf = memcpy(mbuf, buf, len);
+ break;
+#ifdef DEBUG
+ case META_NOALLOC:
+ break;
+ default:
+ fprintf(stderr, "BUG: metafy called with invaild heap value\n");
+ fflush(stderr);
+ break;
+#endif
+ }
+ p = buf + len;
+ e = t = buf + len + meta;
+ while (meta) {
+ if (imeta(*--t = *--p)) {
+ *t-- ^= 32;
+ *t = Meta;
+ meta--;
+ }
+ }
+ }
+ *e = '\0';
+ return buf;
+}
+
+/**/
+char *
+unmetafy(char *s, int *len)
+{
+ char *p, *t;
+
+ for (p = s; *p && *p != Meta; p++);
+ for (t = p; (*t = *p++);)
+ if (*t++ == Meta)
+ t[-1] = *p++ ^ 32;
+ if (len)
+ *len = t - s;
+ return s;
+}
+
+/* Return the character length of a metafied substring, given the *
+ * unmetafied substring length. */
+
+/**/
+int
+metalen(const char *s, int len)
+{
+ int mlen = len;
+
+ while (len--) {
+ if (*s++ == Meta) {
+ mlen++;
+ s++;
+ }
+ }
+ return mlen;
+}
+
+/* This function converts a zsh internal string to a form which can be *
+ * passed to a system call as a filename. The result is stored in a *
+ * single static area. NULL returned if the result is longer than *
+ * 4 * PATH_MAX. */
+
+/**/
+char *
+unmeta(const char *file_name)
+{
+ static char fn[4 * PATH_MAX];
+ char *p;
+ const char *t;
+
+ for (t = file_name, p = fn; *t && p < fn + 4 * PATH_MAX - 1; p++)
+ if ((*p = *t++) == Meta)
+ *p = *t++ ^ 32;
+ if (*t)
+ return NULL;
+ if (p - fn == t - file_name)
+ return (char *) file_name;
+ *p = '\0';
+ return fn;
+}
+
+/* Unmetafy and compare two strings, with unsigned characters. *
+ * "a\0" sorts after "a". */
+
+/**/
+int
+ztrcmp(unsigned char const *s1, unsigned char const *s2)
+{
+ int c1, c2;
+
+ while(*s1 && *s1 == *s2) {
+ s1++;
+ s2++;
+ }
+
+ if(!(c1 = *s1))
+ c1 = -1;
+ else if(c1 == STOUC(Meta))
+ c1 = *++s1 ^ 32;
+ if(!(c2 = *s2))
+ c2 = -1;
+ else if(c2 == STOUC(Meta))
+ c2 = *++s2 ^ 32;
+
+ if(c1 == c2)
+ return 0;
+ else if(c1 < c2)
+ return -1;
+ else
+ return 1;
+}
+
+/* Return zero if the metafied string s and the non-metafied, *
+ * len-long string r are the same. Return -1 if r is a prefix *
+ * of s. Return 1 if r is the lowercase version of s. Return *
+ * 2 is r is the lowercase prefix of s and return 3 otherwise. */
+
+/**/
+int
+metadiffer(char const *s, char const *r, int len)
+{
+ int l = len;
+
+ while (l-- && *s && *r++ == (*s == Meta ? *++s ^ 32 : *s))
+ s++;
+ if (*s && l < 0)
+ return -1;
+ if (l < 0)
+ return 0;
+ if (!*s)
+ return 3;
+ s -= len - l - 1;
+ r -= len - l;
+ while (len-- && *s && *r++ == tulower(*s == Meta ? *++s ^ 32 : *s))
+ s++;
+ if (*s && len < 0)
+ return 2;
+ if (len < 0)
+ return 1;
+ return 3;
+}
+
+/* Return the unmetafied length of a metafied string. */
+
+/**/
+int
+ztrlen(char const *s)
+{
+ int l;
+
+ for (l = 0; *s; l++)
+ if (*s++ == Meta) {
+#ifdef DEBUG
+ if (! *s)
+ fprintf(stderr, "BUG: unexpected end of string in ztrlen()\n");
+ else
+#endif
+ s++;
+ }
+ return l;
+}
+
+/* Subtract two pointers in a metafied string. */
+
+/**/
+int
+ztrsub(char const *t, char const *s)
+{
+ int l = t - s;
+
+ while (s != t)
+ if (*s++ == Meta) {
+#ifdef DEBUG
+ if (! *s || s == t)
+ fprintf(stderr, "BUG: substring ends in the middle of a metachar in ztrsub()\n");
+ else
+#endif
+ s++;
+ l--;
+ }
+ return l;
+}
+
+/**/
+char *
+zreaddir(DIR *dir, int ignoredots)
+{
+ struct dirent *de;
+
+ do {
+ de = readdir(dir);
+ if(!de)
+ return NULL;
+ } while(ignoredots && de->d_name[0] == '.' &&
+ (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2])));
+
+ return metafy(de->d_name, -1, META_STATIC);
+}
+
+/* Unmetafy and output a string. Tokens are skipped. */
+
+/**/
+int
+zputs(char const *s, FILE *stream)
+{
+ int c;
+
+ while (*s) {
+ if (*s == Meta)
+ c = *++s ^ 32;
+ else if(itok(*s)) {
+ s++;
+ continue;
+ } else
+ c = *s;
+ s++;
+ if (fputc(c, stream) < 0)
+ return EOF;
+ }
+ return 0;
+}
+
+/* Create a visibly-represented duplicate of a string. */
+
+/**/
+char *
+niceztrdup(char const *s)
+{
+ int c, len = strlen(s) * 5;
+ char *buf = zalloc(len);
+ char *p = buf, *n, *ret;
+
+ while ((c = *s++)) {
+ if (itok(c))
+ if (c <= Comma)
+ c = ztokens[c - Pound];
+ else
+ continue;
+ if (c == Meta)
+ c = *s++ ^ 32;
+ n = nicechar(c);
+ while(*n)
+ *p++ = *n++;
+ }
+ ret = metafy(buf, p - buf, META_DUP);
+ zfree(buf, len);
+ return ret;
+}
+
+/* Unmetafy and output a string, displaying special characters readably. */
+
+/**/
+int
+nicezputs(char const *s, FILE *stream)
+{
+ int c;
+
+ while ((c = *s++)) {
+ if (itok(c))
+ if (c <= Comma)
+ c = ztokens[c - Pound];
+ else
+ continue;
+ if (c == Meta)
+ c = *s++ ^ 32;
+ if(fputs(nicechar(c), stream) < 0)
+ return EOF;
+ }
+ return 0;
+}
+
+/* Return the length of the visible representation of a metafied string. */
+
+/**/
+size_t
+niceztrlen(char const *s)
+{
+ size_t l = 0;
+ int c;
+
+ while ((c = *s++)) {
+ if (itok(c))
+ if (c <= Comma)
+ c = ztokens[c - Pound];
+ else
+ continue;
+ if (c == Meta)
+ c = *s++ ^ 32;
+ l += strlen(nicechar(STOUC(c)));
+ }
+ return l;
+}
+
+/* check for special characters in the string */
+
+/**/
+int
+hasspecial(char const *s)
+{
+ for (; *s; s++)
+ if (ispecial(*s == Meta ? *++s ^ 32 : *s))
+ return 1;
+ return 0;
+}
+
+/* Unmetafy and output a string, quoted if it contains special characters. */
+
+/**/
+int
+quotedzputs(char const *s, FILE *stream)
+{
+ int inquote = 0, c;
+
+ /* check for empty string */
+ if(!*s)
+ return fputs("''", stream);
+
+ if (!hasspecial(s))
+ return zputs(s, stream);
+
+ if (isset(RCQUOTES)) {
+ /* use rc-style quotes-within-quotes for the whole string */
+ if(fputc('\'', stream) < 0)
+ return EOF;
+ while(*s) {
+ if (*s == Meta)
+ c = *++s ^ 32;
+ else
+ c = *s;
+ s++;
+ if (c == '\'') {
+ if(fputc('\'', stream) < 0)
+ return EOF;
+ } else if(c == '\n' && isset(CSHJUNKIEQUOTES)) {
+ if(fputc('\\', stream) < 0)
+ return EOF;
+ }
+ if(fputc(c, stream) < 0)
+ return EOF;
+ }
+ if(fputc('\'', stream) < 0)
+ return EOF;
+ } else {
+ /* use Bourne-style quoting, avoiding empty quoted strings */
+ while(*s) {
+ if (*s == Meta)
+ c = *++s ^ 32;
+ else
+ c = *s;
+ s++;
+ if (c == '\'') {
+ if(inquote) {
+ if(fputc('\'', stream) < 0)
+ return EOF;
+ inquote=0;
+ }
+ if(fputs("\\'", stream) < 0)
+ return EOF;
+ } else {
+ if (!inquote) {
+ if(fputc('\'', stream) < 0)
+ return EOF;
+ inquote=1;
+ }
+ if(c == '\n' && isset(CSHJUNKIEQUOTES)) {
+ if(fputc('\\', stream) < 0)
+ return EOF;
+ }
+ if(fputc(c, stream) < 0)
+ return EOF;
+ }
+ }
+ if (inquote) {
+ if(fputc('\'', stream) < 0)
+ return EOF;
+ }
+ }
+ return 0;
+}
+
+/* Double-quote a metafied string. */
+
+/**/
+char *
+dquotedztrdup(char const *s)
+{
+ int len = strlen(s) * 4 + 2;
+ char *buf = zalloc(len);
+ char *p = buf, *ret;
+
+ if(isset(CSHJUNKIEQUOTES)) {
+ int inquote = 0;
+
+ while(*s) {
+ int c = *s++;
+
+ if (c == Meta)
+ c = *s++ ^ 32;
+ switch(c) {
+ case '"':
+ case '$':
+ case '`':
+ if(inquote) {
+ *p++ = '"';
+ inquote = 0;
+ }
+ *p++ = '\\';
+ *p++ = c;
+ break;
+ default:
+ if(!inquote) {
+ *p++ = '"';
+ inquote = 1;
+ }
+ if(c == '\n')
+ *p++ = '\\';
+ *p++ = c;
+ break;
+ }
+ }
+ if (inquote)
+ *p++ = '"';
+ } else {
+ int pending = 0;
+
+ *p++ = '"';
+ while(*s) {
+ int c = *s++;
+
+ if (c == Meta)
+ c = *s++ ^ 32;
+ switch(c) {
+ case '\\':
+ if(pending)
+ *p++ = '\\';
+ *p++ = '\\';
+ pending = 1;
+ break;
+ case '"':
+ case '$':
+ case '`':
+ if(pending)
+ *p++ = '\\';
+ *p++ = '\\';
+ /* fall through */
+ default:
+ *p++ = c;
+ pending = 0;
+ break;
+ }
+ }
+ if(pending)
+ *p++ = '\\';
+ *p++ = '"';
+ }
+ ret = metafy(buf, p - buf, META_DUP);
+ zfree(buf, len);
+ return ret;
+}
+
+#if 0
+/* Unmetafy and output a string, double quoting it in its entirety. */
+
+/**/
+int
+dquotedzputs(char const *s, FILE *stream)
+{
+ char *d = dquotedztrdup(s);
+ int ret = zputs(d, stream);
+
+ zsfree(d);
+ return ret;
+}
+#endif
+
+/**/
+char *
+getkeystring(char *s, int *len, int fromwhere, int *misc)
+{
+ char *buf;
+ char *t, *u = NULL;
+ char svchar = '\0';
+ int meta = 0, control = 0;
+
+ if (fromwhere != 4)
+ buf = halloc(strlen(s) + 1);
+ else {
+ buf = s;
+ s += 2;
+ }
+ for (t = buf; *s; s++) {
+ if (*s == '\\' && s[1]) {
+ switch (*++s) {
+ case 'a':
+#ifdef __STDC__
+ *t++ = '\a';
+#else
+ *t++ = '\07';
+#endif
+ break;
+ case 'n':
+ *t++ = '\n';
+ break;
+ case 'b':
+ *t++ = '\b';
+ break;
+ case 't':
+ *t++ = '\t';
+ break;
+ case 'v':
+ *t++ = '\v';
+ break;
+ case 'f':
+ *t++ = '\f';
+ break;
+ case 'r':
+ *t++ = '\r';
+ break;
+ case 'E':
+ if (!fromwhere) {
+ *t++ = '\\', s--;
+ continue;
+ }
+ case 'e':
+ *t++ = '\033';
+ break;
+ case 'M':
+ if (fromwhere) {
+ if (s[1] == '-')
+ s++;
+ meta = 1 + control; /* preserve the order of ^ and meta */
+ } else
+ *t++ = '\\', s--;
+ continue;
+ case 'C':
+ if (fromwhere) {
+ if (s[1] == '-')
+ s++;
+ control = 1;
+ } else
+ *t++ = '\\', s--;
+ continue;
+ case Meta:
+ *t++ = '\\', s--;
+ break;
+ case 'c':
+ if (fromwhere < 2) {
+ *misc = 1;
+ break;
+ }
+ default:
+ if ((idigit(*s) && *s < '8') || *s == 'x') {
+ if (!fromwhere)
+ if (*s == '0')
+ s++;
+ else if (*s != 'x') {
+ *t++ = '\\', s--;
+ continue;
+ }
+ if (s[1] && s[2] && s[3]) {
+ svchar = s[3];
+ s[3] = '\0';
+ u = s;
+ }
+ *t++ = zstrtol(s + (*s == 'x'), &s,
+ (*s == 'x') ? 16 : 8);
+ if (svchar) {
+ u[3] = svchar;
+ svchar = '\0';
+ }
+ s--;
+ } else {
+ if (!fromwhere && *s != '\\')
+ *t++ = '\\';
+ *t++ = *s;
+ }
+ break;
+ }
+ } else if (fromwhere == 4 && *s == Snull) {
+ for (u = t; (*u++ = *s++););
+ return t + 1;
+ } else if (*s == '^' && fromwhere == 2) {
+ control = 1;
+ continue;
+ } else if (*s == Meta)
+ *t++ = *++s ^ 32;
+ else
+ *t++ = *s;
+ if (meta == 2) {
+ t[-1] |= 0x80;
+ meta = 0;
+ }
+ if (control) {
+ if (t[-1] == '?')
+ t[-1] = 0x7f;
+ else
+ t[-1] &= 0x9f;
+ control = 0;
+ }
+ if (meta) {
+ t[-1] |= 0x80;
+ meta = 0;
+ }
+ if (fromwhere == 4 && imeta(t[-1])) {
+ *t = t[-1] ^ 32;
+ t[-1] = Meta;
+ t++;
+ }
+ }
+ DPUTS(fromwhere == 4, "BUG: unterminated $' substitution");
+ *t = '\0';
+ *len = t - buf;
+ return buf;
+}
+
+/* Return non-zero if s is a prefix of t. */
+
+/**/
+int
+strpfx(char *s, char *t)
+{
+ while (*s && *s == *t)
+ s++, t++;
+ return !*s;
+}
+
+/* Return non-zero if s is a suffix of t. */
+
+/**/
+int
+strsfx(char *s, char *t)
+{
+ int ls = strlen(s), lt = strlen(t);
+
+ if (ls <= lt)
+ return !strcmp(t + lt - ls, s);
+ return 0;
+}
+
+/**/
+char *
+dupstrpfx(const char *s, int len)
+{
+ char *r = ncalloc(len + 1);
+
+ memcpy(r, s, len);
+ r[len] = '\0';
+ return r;
+}
+
+/**/
+char *
+ztrduppfx(const char *s, int len)
+{
+ char *r = zalloc(len + 1);
+
+ memcpy(r, s, len);
+ r[len] = '\0';
+ return r;
+}
+
+/* Append a string to an allocated string, reallocating to make room. */
+
+/**/
+char *
+appstr(char *base, char const *append)
+{
+ return strcat(realloc(base, strlen(base) + strlen(append) + 1), append);
+}
+
+/**/
+static int
+upchdir(int n)
+{
+ char buf[PATH_MAX];
+ char *s;
+ int err = -1;
+
+ while (n > 0) {
+ for (s = buf; s < buf + PATH_MAX - 4 && n--; )
+ *s++ = '.', *s++ = '.', *s++ = '/';
+ s[-1] = '\0';
+ if (chdir(buf))
+ return err;
+ err = -2;
+ }
+ return 0;
+}
+
+/* Change directory, without following symlinks. Returns 0 on success, -1 *
+ * on failure. Sets errno to ENOTDIR if any symlinks are encountered. If *
+ * fchdir() fails, or the current directory is unreadable, we might end up *
+ * in an unwanted directory in case of failure. */
+
+/**/
+int
+lchdir(char const *path, struct dirsav *d, int hard)
+{
+ char const *pptr;
+ int level;
+ struct stat st1;
+ struct dirsav ds;
+#ifdef HAVE_LSTAT
+ char buf[PATH_MAX + 1], *ptr;
+ int err;
+ struct stat st2;
+#endif
+
+ if (!d) {
+ ds.ino = ds.dev = 0;
+ ds.dirname = NULL;
+ ds.dirfd = -1;
+ d = &ds;
+ }
+#ifdef HAVE_LSTAT
+ if ((*path == '/' || !hard) &&
+ (d != &ds || hard)){
+#else
+ if (*path == '/') {
+#endif
+ level = -1;
+#ifdef HAVE_FCHDIR
+ if (d->dirfd < 0 && (d->dirfd = open(".", O_RDONLY | O_NOCTTY)) < 0 &&
+ zgetdir(d) && *d->dirname != '/')
+ d->dirfd = open("..", O_RDONLY | O_NOCTTY);
+#else
+ if (!d->dirname)
+ zgetdir(d);
+#endif
+ } else {
+ level = 0;
+ if (!d->dev && !d->ino) {
+ stat(".", &st1);
+ d->dev = st1.st_dev;
+ d->ino = st1.st_ino;
+ }
+ }
+
+#ifdef HAVE_LSTAT
+ if (!hard)
+#endif
+ {
+ if (d != &ds) {
+ for (pptr = path; *pptr; level++) {
+ while (*pptr && *pptr++ != '/');
+ while (*pptr == '/')
+ pptr++;
+ }
+ d->level = level;
+ }
+ return zchdir((char *) path);
+ }
+#ifdef HAVE_LSTAT
+ if (*path == '/')
+ chdir("/");
+ for(;;) {
+ while(*path == '/')
+ path++;
+ if(!*path) {
+ if (d == &ds) {
+ zsfree(ds.dirname);
+ if (ds.dirfd >=0)
+ close(ds.dirfd);
+ } else
+ d->level = level;
+ return 0;
+ }
+ for(pptr = path; *++pptr && *pptr != '/'; ) ;
+ if(pptr - path > PATH_MAX) {
+ err = ENAMETOOLONG;
+ break;
+ }
+ for(ptr = buf; path != pptr; )
+ *ptr++ = *path++;
+ *ptr = 0;
+ if(lstat(buf, &st1)) {
+ err = errno;
+ break;
+ }
+ if(!S_ISDIR(st1.st_mode)) {
+ err = ENOTDIR;
+ break;
+ }
+ if(chdir(buf)) {
+ err = errno;
+ break;
+ }
+ if (level >= 0)
+ level++;
+ if(lstat(".", &st2)) {
+ err = errno;
+ break;
+ }
+ if(st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) {
+ err = ENOTDIR;
+ break;
+ }
+ }
+ if (restoredir(d)) {
+ if (d == &ds) {
+ zsfree(ds.dirname);
+ if (ds.dirfd >=0)
+ close(ds.dirfd);
+ }
+ errno = err;
+ return -2;
+ }
+ if (d == &ds) {
+ zsfree(ds.dirname);
+ if (ds.dirfd >=0)
+ close(ds.dirfd);
+ }
+ errno = err;
+ return -1;
+#endif /* HAVE_LSTAT */
+}
+
+/**/
+int
+restoredir(struct dirsav *d)
+{
+ int err = 0;
+ struct stat sbuf;
+
+ if (d->dirname && *d->dirname == '/')
+ return chdir(d->dirname);
+#ifdef HAVE_FCHDIR
+ if (d->dirfd >= 0) {
+ if (!fchdir(d->dirfd)) {
+ if (!d->dirname) {
+ return 0;
+ } else if (chdir(d->dirname)) {
+ close(d->dirfd);
+ d->dirfd = -1;
+ err = -2;
+ }
+ } else {
+ close(d->dirfd);
+ d->dirfd = err = -1;
+ }
+ } else
+#endif
+ if (d->level > 0)
+ err = upchdir(d->level);
+ else if (d->level < 0)
+ err = -1;
+ if (d->dev || d->ino) {
+ stat(".", &sbuf);
+ if (sbuf.st_ino != d->ino || sbuf.st_dev != d->dev)
+ err = -2;
+ }
+ return err;
+}
+
+/* Get a signal number from a string */
+
+/**/
+int
+getsignum(char *s)
+{
+ int x, i;
+
+ /* check for a signal specified by number */
+ x = atoi(s);
+ if (idigit(*s) && x >= 0 && x < VSIGCOUNT)
+ return x;
+
+ /* search for signal by name */
+ for (i = 0; i < VSIGCOUNT; i++)
+ if (!strcmp(s, sigs[i]))
+ return i;
+
+ /* no matching signal */
+ return -1;
+}
+
+/* Check whether the shell is running with privileges in effect. *
+ * This is the case if EITHER the euid is zero, OR (if the system *
+ * supports POSIX.1e (POSIX.6) capability sets) the process' *
+ * Effective or Inheritable capability sets are non-empty. */
+
+/**/
+int
+privasserted(void)
+{
+ if(!geteuid())
+ return 1;
+#ifdef HAVE_CAP_GET_PROC
+ {
+ cap_t caps = cap_get_proc();
+ if(caps) {
+ /* POSIX doesn't define a way to test whether a capability set *
+ * is empty or not. Typical. I hope this is conforming... */
+ cap_flag_value_t val;
+ cap_value_t n;
+ for(n = 0; !cap_get_flag(caps, n, CAP_EFFECTIVE, &val); n++)
+ if(val ||
+ (!cap_get_flag(caps, n, CAP_INHERITABLE, &val) && val)) {
+ cap_free(&caps);
+ return 1;
+ }
+ cap_free(&caps);
+ }
+ }
+#endif /* HAVE_CAP_GET_PROC */
+ return 0;
+}
+
+#ifdef DEBUG
+
+/**/
+void
+dputs(char *message)
+{
+ fprintf(stderr, "%s\n", message);
+ fflush(stderr);
+}
+
+#endif /* DEBUG */
+
+/**/
+int
+mode_to_octal(mode_t mode)
+{
+ int m = 0;
+
+ if(mode & S_ISUID)
+ m |= 04000;
+ if(mode & S_ISGID)
+ m |= 02000;
+ if(mode & S_ISVTX)
+ m |= 01000;
+ if(mode & S_IRUSR)
+ m |= 00400;
+ if(mode & S_IWUSR)
+ m |= 00200;
+ if(mode & S_IXUSR)
+ m |= 00100;
+ if(mode & S_IRGRP)
+ m |= 00040;
+ if(mode & S_IWGRP)
+ m |= 00020;
+ if(mode & S_IXGRP)
+ m |= 00010;
+ if(mode & S_IROTH)
+ m |= 00004;
+ if(mode & S_IWOTH)
+ m |= 00002;
+ if(mode & S_IXOTH)
+ m |= 00001;
+ return m;
+}
diff --git a/Src/watch.c b/Src/watch.c
new file mode 100644
index 000000000..2532e0a63
--- /dev/null
+++ b/Src/watch.c
@@ -0,0 +1,586 @@
+/*
+ * watch.c - login/logout watching
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+
+/* Headers for utmp/utmpx structures */
+#ifdef HAVE_UTMP_H
+# include <utmp.h>
+#endif
+#ifdef HAVE_UTMPX_H
+# include <utmpx.h>
+#endif
+
+/* Find utmp file */
+#if !defined(REAL_UTMP_FILE) && defined(UTMP_FILE)
+# define REAL_UTMP_FILE UTMP_FILE
+#endif
+#if !defined(REAL_UTMP_FILE) && defined(_PATH_UTMP)
+# define REAL_UTMP_FILE _PATH_UTMP
+#endif
+#if !defined(REAL_UTMP_FILE) && defined(PATH_UTMP_FILE)
+# define REAL_UTMP_FILE PATH_UTMP_FILE
+#endif
+
+/* Find wtmp file */
+#if !defined(REAL_WTMP_FILE) && defined(WTMP_FILE)
+# define REAL_WTMP_FILE WTMP_FILE
+#endif
+#if !defined(REAL_WTMP_FILE) && defined(_PATH_WTMP)
+# define REAL_WTMP_FILE _PATH_WTMP
+#endif
+#if !defined(REAL_WTMP_FILE) && defined(PATH_WTMP_FILE)
+# define REAL_WTMP_FILE PATH_WTMP_FILE
+#endif
+
+/* Find utmpx file */
+#if !defined(REAL_UTMPX_FILE) && defined(UTMPX_FILE)
+# define REAL_UTMPX_FILE UTMPX_FILE
+#endif
+#if !defined(REAL_UTMPX_FILE) && defined(_PATH_UTMPX)
+# define REAL_UTMPX_FILE _PATH_UTMPX
+#endif
+#if !defined(REAL_UTMPX_FILE) && defined(PATH_UTMPX_FILE)
+# define REAL_UTMPX_FILE PATH_UTMPX_FILE
+#endif
+
+/* Find wtmpx file */
+#if !defined(REAL_WTMPX_FILE) && defined(WTMPX_FILE)
+# define REAL_WTMPX_FILE WTMPX_FILE
+#endif
+#if !defined(REAL_WTMPX_FILE) && defined(_PATH_WTMPX)
+# define REAL_WTMPX_FILE _PATH_WTMPX
+#endif
+#if !defined(REAL_WTMPX_FILE) && defined(PATH_WTMPX_FILE)
+# define REAL_WTMPX_FILE PATH_WTMPX_FILE
+#endif
+
+/* Decide which structure to use. We use a structure that exists in *
+ * the headers, and require that its corresponding utmp file exist. *
+ * (wtmp is less important.) */
+
+#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMPX) && defined(REAL_UTMPX_FILE)
+# define WATCH_STRUCT_UTMP struct utmpx
+# ifdef HAVE_STRUCT_UTMPX_UT_XTIME
+# undef ut_time
+# define ut_time ut_xtime
+# else /* !HAVE_STRUCT_UTMPX_UT_XTIME */
+# ifdef HAVE_STRUCT_UTMPX_UT_TV
+# undef ut_time
+# define ut_time ut_tv.tv_sec
+# endif /* HAVE_STRUCT_UTMPX_UT_TV */
+# endif /* !HAVE_STRUCT_UTMPX_UT_XTIME */
+# define WATCH_UTMP_FILE REAL_UTMPX_FILE
+# ifdef REAL_WTMPX_FILE
+# define WATCH_WTMP_FILE REAL_WTMPX_FILE
+# endif
+# ifdef HAVE_STRUCT_UTMPX_UT_HOST
+# define WATCH_UTMP_UT_HOST 1
+# endif
+#endif
+
+#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMP) && defined(REAL_UTMP_FILE)
+# define WATCH_STRUCT_UTMP struct utmp
+# define WATCH_UTMP_FILE REAL_UTMP_FILE
+# ifdef REAL_WTMP_FILE
+# define WATCH_WTMP_FILE REAL_WTMP_FILE
+# endif
+# ifdef HAVE_STRUCT_UTMP_UT_HOST
+# define WATCH_UTMP_UT_HOST 1
+# endif
+#endif
+
+#ifdef WATCH_UTMP_UT_HOST
+# define DEFAULT_WATCHFMT "%n has %a %l from %m."
+#else /* !WATCH_UTMP_UT_HOST */
+# define DEFAULT_WATCHFMT "%n has %a %l."
+#endif /* !WATCH_UTMP_UT_HOST */
+
+/**/
+char const * const default_watchfmt = DEFAULT_WATCHFMT;
+
+#ifdef WATCH_STRUCT_UTMP
+
+# include "watch.pro"
+
+# ifndef WATCH_WTMP_FILE
+# define WATCH_WTMP_FILE "/dev/null"
+# endif
+
+static int wtabsz;
+static WATCH_STRUCT_UTMP *wtab;
+static time_t lastutmpcheck;
+
+/* get the time of login/logout for WATCH */
+
+/**/
+static time_t
+getlogtime(WATCH_STRUCT_UTMP *u, int inout)
+{
+ FILE *in;
+ WATCH_STRUCT_UTMP uu;
+ int first = 1;
+ int srchlimit = 50; /* max number of wtmp records to search */
+
+ if (inout)
+ return u->ut_time;
+ if (!(in = fopen(WATCH_WTMP_FILE, "r")))
+ return time(NULL);
+ fseek(in, 0, 2);
+ do {
+ if (fseek(in, ((first) ? -1 : -2) * sizeof(WATCH_STRUCT_UTMP), 1)) {
+ fclose(in);
+ return time(NULL);
+ }
+ first = 0;
+ if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+ fclose(in);
+ return time(NULL);
+ }
+ if (uu.ut_time < lastwatch || !srchlimit--) {
+ fclose(in);
+ return time(NULL);
+ }
+ }
+ while (memcmp(&uu, u, sizeof(uu)));
+
+ do
+ if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) {
+ fclose(in);
+ return time(NULL);
+ }
+ while (strncmp(uu.ut_line, u->ut_line, sizeof(u->ut_line)));
+ fclose(in);
+ return uu.ut_time;
+}
+
+/* Mutually recursive call to handle ternaries in $WATCHFMT */
+
+# define BEGIN3 '('
+# define END3 ')'
+
+/**/
+static char *
+watch3ary(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt)
+{
+ int truth = 1, sep;
+
+ switch (*fmt++) {
+ case 'n':
+ truth = (u->ut_name[0] != 0);
+ break;
+ case 'a':
+ truth = inout;
+ break;
+ case 'l':
+ if (!strncmp(u->ut_line, "tty", 3))
+ truth = (u->ut_line[3] != 0);
+ else
+ truth = (u->ut_line[0] != 0);
+ break;
+# ifdef WATCH_UTMP_UT_HOST
+ case 'm':
+ case 'M':
+ truth = (u->ut_host[0] != 0);
+ break;
+# endif /* WATCH_UTMP_UT_HOST */
+ default:
+ prnt = 0; /* Skip unknown conditionals entirely */
+ break;
+ }
+ sep = *fmt++;
+ fmt = watchlog2(inout, u, fmt, (truth && prnt), sep);
+ return watchlog2(inout, u, fmt, (!truth && prnt), END3);
+}
+
+/* print a login/logout event */
+
+/**/
+static char *
+watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
+{
+ char buf[40], buf2[80];
+ time_t timet;
+ struct tm *tm;
+ char *fm2;
+# ifdef WATCH_UTMP_UT_HOST
+ char *p;
+ int i;
+# endif /* WATCH_UTMP_UT_HOST */
+
+ while (*fmt)
+ if (*fmt == '\\')
+ if (*++fmt) {
+ if (prnt)
+ putchar(*fmt);
+ ++fmt;
+ } else if (fini)
+ return fmt;
+ else
+ break;
+ else if (*fmt == fini)
+ return ++fmt;
+ else if (*fmt != '%') {
+ if (prnt)
+ putchar(*fmt);
+ ++fmt;
+ } else {
+ if (*++fmt == BEGIN3)
+ fmt = watch3ary(inout, u, ++fmt, prnt);
+ else if (!prnt)
+ ++fmt;
+ else
+ switch (*(fm2 = fmt++)) {
+ case 'n':
+ printf("%.*s", (int)sizeof(u->ut_name), u->ut_name);
+ break;
+ case 'a':
+ printf("%s", (!inout) ? "logged off" : "logged on");
+ break;
+ case 'l':
+ if (!strncmp(u->ut_line, "tty", 3))
+ printf("%.*s", (int)sizeof(u->ut_line) - 3, u->ut_line + 3);
+ else
+ printf("%.*s", (int)sizeof(u->ut_line), u->ut_line);
+ break;
+# ifdef WATCH_UTMP_UT_HOST
+ case 'm':
+ for (p = u->ut_host, i = sizeof(u->ut_host); i && *p; i--, p++) {
+ if (*p == '.' && !idigit(p[1]))
+ break;
+ putchar(*p);
+ }
+ break;
+ case 'M':
+ printf("%.*s", (int)sizeof(u->ut_host), u->ut_host);
+ break;
+# endif /* WATCH_UTMP_UT_HOST */
+ case 'T':
+ case 't':
+ case '@':
+ case 'W':
+ case 'w':
+ case 'D':
+ switch (*fm2) {
+ case '@':
+ case 't':
+ fm2 = "%l:%M%p";
+ break;
+ case 'T':
+ fm2 = "%K:%M";
+ break;
+ case 'w':
+ fm2 = "%a %f";
+ break;
+ case 'W':
+ fm2 = "%m/%d/%y";
+ break;
+ case 'D':
+ if (fm2[1] == '{') {
+ char *dd, *ss;
+ int n = 79;
+
+ for (ss = fm2 + 2, dd = buf2;
+ n-- && *ss && *ss != '}'; ++ss, ++dd)
+ *dd = *((*ss == '\\' && ss[1]) ? ++ss : ss);
+ if (*ss == '}') {
+ *dd = '\0';
+ fmt = ss + 1;
+ fm2 = buf2;
+ }
+ else fm2 = "%y-%m-%d";
+ }
+ else fm2 = "%y-%m-%d";
+ break;
+ }
+ timet = getlogtime(u, inout);
+ tm = localtime(&timet);
+ ztrftime(buf, 40, fm2, tm);
+ printf("%s", (*buf == ' ') ? buf + 1 : buf);
+ break;
+ case '%':
+ putchar('%');
+ break;
+ case 'S':
+ txtset(TXTSTANDOUT);
+ tsetcap(TCSTANDOUTBEG, -1);
+ break;
+ case 's':
+ txtset(TXTDIRTY);
+ txtunset(TXTSTANDOUT);
+ tsetcap(TCSTANDOUTEND, -1);
+ break;
+ case 'B':
+ txtset(TXTDIRTY);
+ txtset(TXTBOLDFACE);
+ tsetcap(TCBOLDFACEBEG, -1);
+ break;
+ case 'b':
+ txtset(TXTDIRTY);
+ txtunset(TXTBOLDFACE);
+ tsetcap(TCALLATTRSOFF, -1);
+ break;
+ case 'U':
+ txtset(TXTUNDERLINE);
+ tsetcap(TCUNDERLINEBEG, -1);
+ break;
+ case 'u':
+ txtset(TXTDIRTY);
+ txtunset(TXTUNDERLINE);
+ tsetcap(TCUNDERLINEEND, -1);
+ break;
+ default:
+ putchar('%');
+ putchar(*fm2);
+ break;
+ }
+ }
+ if (prnt)
+ putchar('\n');
+
+ return fmt;
+}
+
+/* check the List for login/logouts */
+
+/**/
+static void
+watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt)
+{
+ char *v, *vv, sav;
+ int bad;
+
+ if (!*u->ut_name)
+ return;
+
+ if (*w && !strcmp(*w, "all")) {
+ (void)watchlog2(inout, u, fmt, 1, 0);
+ return;
+ }
+ if (*w && !strcmp(*w, "notme") &&
+ strncmp(u->ut_name, get_username(), sizeof(u->ut_name))) {
+ (void)watchlog2(inout, u, fmt, 1, 0);
+ return;
+ }
+ for (; *w; w++) {
+ bad = 0;
+ v = *w;
+ if (*v != '@' && *v != '%') {
+ for (vv = v; *vv && *vv != '@' && *vv != '%'; vv++);
+ sav = *vv;
+ *vv = '\0';
+ if (strncmp(u->ut_name, v, sizeof(u->ut_name)))
+ bad = 1;
+ *vv = sav;
+ v = vv;
+ }
+ for (;;)
+ if (*v == '%') {
+ for (vv = ++v; *vv && *vv != '@'; vv++);
+ sav = *vv;
+ *vv = '\0';
+ if (strncmp(u->ut_line, v, sizeof(u->ut_line)))
+ bad = 1;
+ *vv = sav;
+ v = vv;
+ }
+# ifdef WATCH_UTMP_UT_HOST
+ else if (*v == '@') {
+ for (vv = ++v; *vv && *vv != '%'; vv++);
+ sav = *vv;
+ *vv = '\0';
+ if (strncmp(u->ut_host, v, strlen(v)))
+ bad = 1;
+ *vv = sav;
+ v = vv;
+ }
+# endif /* WATCH_UTMP_UT_HOST */
+ else
+ break;
+ if (!bad) {
+ (void)watchlog2(inout, u, fmt, 1, 0);
+ return;
+ }
+ }
+}
+
+/* compare 2 utmp entries */
+
+/**/
+static int
+ucmp(WATCH_STRUCT_UTMP *u, WATCH_STRUCT_UTMP *v)
+{
+ if (u->ut_time == v->ut_time)
+ return strncmp(u->ut_line, v->ut_line, sizeof(u->ut_line));
+ return u->ut_time - v->ut_time;
+}
+
+/* initialize the user List */
+
+/**/
+static void
+readwtab(void)
+{
+ WATCH_STRUCT_UTMP *uptr;
+ int wtabmax = 32;
+ FILE *in;
+
+ wtabsz = 0;
+ if (!(in = fopen(WATCH_UTMP_FILE, "r")))
+ return;
+ uptr = wtab = (WATCH_STRUCT_UTMP *)zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP));
+ while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in))
+# ifdef USER_PROCESS
+ if (uptr->ut_type == USER_PROCESS)
+# else /* !USER_PROCESS */
+ if (uptr->ut_name[0])
+# endif /* !USER_PROCESS */
+ {
+ uptr++;
+ if (++wtabsz == wtabmax)
+ uptr = (wtab = (WATCH_STRUCT_UTMP *)realloc((void *) wtab, (wtabmax *= 2) *
+ sizeof(WATCH_STRUCT_UTMP))) + wtabsz;
+ }
+ fclose(in);
+
+ if (wtabsz)
+ qsort((void *) wtab, wtabsz, sizeof(WATCH_STRUCT_UTMP),
+ (int (*) _((const void *, const void *)))ucmp);
+}
+
+/* Check for login/logout events; executed before *
+ * each prompt if WATCH is set */
+
+/**/
+void
+dowatch(void)
+{
+ FILE *in;
+ WATCH_STRUCT_UTMP *utab, *uptr, *wptr;
+ struct stat st;
+ char **s;
+ char *fmt;
+ int utabsz = 0, utabmax = wtabsz + 4;
+ int uct, wct;
+
+ s = watch;
+ if (!(fmt = getsparam("WATCHFMT")))
+ fmt = DEFAULT_WATCHFMT;
+
+ holdintr();
+ if (!wtab) {
+ readwtab();
+ noholdintr();
+ return;
+ }
+ if ((stat(WATCH_UTMP_FILE, &st) == -1) || (st.st_mtime <= lastutmpcheck)) {
+ noholdintr();
+ return;
+ }
+ lastutmpcheck = st.st_mtime;
+ uptr = utab = (WATCH_STRUCT_UTMP *) zalloc(utabmax * sizeof(WATCH_STRUCT_UTMP));
+
+ if (!(in = fopen(WATCH_UTMP_FILE, "r"))) {
+ free(utab);
+ noholdintr();
+ return;
+ }
+ while (fread(uptr, sizeof *uptr, 1, in))
+# ifdef USER_PROCESS
+ if (uptr->ut_type == USER_PROCESS)
+# else /* !USER_PROCESS */
+ if (uptr->ut_name[0])
+# endif /* !USER_PROCESS */
+ {
+ uptr++;
+ if (++utabsz == utabmax)
+ uptr = (utab = (WATCH_STRUCT_UTMP *)realloc((void *) utab, (utabmax *= 2) *
+ sizeof(WATCH_STRUCT_UTMP))) + utabsz;
+ }
+ fclose(in);
+ noholdintr();
+ if (errflag) {
+ free(utab);
+ return;
+ }
+ if (utabsz)
+ qsort((void *) utab, utabsz, sizeof(WATCH_STRUCT_UTMP),
+ (int (*) _((const void *, const void *)))ucmp);
+
+ wct = wtabsz;
+ uct = utabsz;
+ uptr = utab;
+ wptr = wtab;
+ if (errflag) {
+ free(utab);
+ return;
+ }
+ while ((uct || wct) && !errflag)
+ if (!uct || (wct && ucmp(uptr, wptr) > 0))
+ wct--, watchlog(0, wptr++, s, fmt);
+ else if (!wct || (uct && ucmp(uptr, wptr) < 0))
+ uct--, watchlog(1, uptr++, s, fmt);
+ else
+ uptr++, wptr++, wct--, uct--;
+ free(wtab);
+ wtab = utab;
+ wtabsz = utabsz;
+ fflush(stdout);
+}
+
+/**/
+int
+bin_log(char *nam, char **argv, char *ops, int func)
+{
+ if (!watch)
+ return 1;
+ if (wtab)
+ free(wtab);
+ wtab = (WATCH_STRUCT_UTMP *)zalloc(1);
+ wtabsz = 0;
+ lastutmpcheck = 0;
+ dowatch();
+ return 0;
+}
+
+#else /* !WATCH_STRUCT_UTMP */
+
+/**/
+void dowatch(void)
+{
+}
+
+/**/
+int
+bin_log(char *nam, char **argv, char *ops, int func)
+{
+ return bin_notavail(nam, argv, ops, func);
+}
+
+#endif /* !WATCH_STRUCT_UTMP */
diff --git a/Src/xmods.conf b/Src/xmods.conf
new file mode 100644
index 000000000..c36105721
--- /dev/null
+++ b/Src/xmods.conf
@@ -0,0 +1,5 @@
+rlimits
+comp1
+zle
+compctl
+sched
diff --git a/Src/zsh.export b/Src/zsh.export
new file mode 100644
index 000000000..8f676c7fd
--- /dev/null
+++ b/Src/zsh.export
@@ -0,0 +1,235 @@
+#!
+SHTTY
+addbuiltins
+addedx
+addhashnode
+aliastab
+alloc_stackp
+appstr
+arrdup
+arrlen
+attachtty
+bangchar
+bin_notavail
+breaks
+bufstack
+builtintab
+chline
+chuck
+clearjobtab
+closem
+cmdnamtab
+columns
+compctlreadptr
+coprocin
+coprocout
+countlinknodes
+countprompt
+createparam
+ctxtlex
+curhist
+current_limits
+deletebuiltins
+deletehashtable
+domatch
+doshfunc
+dputs
+dquotedztrdup
+dummy_list
+dupstring
+dupstrpfx
+dyncat
+emptyhashtable
+endparamscope
+errflag
+excs
+execstring
+exlast
+expanding
+fallback_compctlread
+fallback_zleread
+fignore
+file_type
+filesub
+filesubstr
+findcmd
+firsthist
+freearray
+freeheap
+getaparam
+gethashnode
+gethashnode2
+getiparam
+getkeystring
+getlinknode
+getshfunc
+getsparam
+glob_pre
+glob_suf
+global_heapalloc
+global_permalloc
+globlist
+gotwordptr
+halloc
+hasam
+hashcmd
+hasher
+hasspecial
+haswilds
+hcalloc
+hgetc
+hgetline
+histentarr
+histentct
+hptr
+hrealloc
+inbufct
+incmdpos
+incond
+init_io
+init_shout
+init_term
+inpop
+inpush
+inredir
+insertlinknode
+intr
+inwhat
+isfirstln
+jobtab
+lastpid
+lastval
+lchdir
+lexrestore
+lexsave
+lexstop
+limits
+line
+lines
+locallevel
+metadiffer
+metafy
+metalen
+mode_to_octal
+mypgrp
+mypid
+nameddirtab
+ncalloc
+newhashtable
+newlinklist
+nicechar
+nicezputs
+niceztrdup
+niceztrlen
+noaliases
+noerrs
+noop_function
+noop_function_int
+optiontab
+opts
+paramtab
+parbegin
+parend
+parsereg
+parsestr
+path
+pathchecked
+popheap
+postedit
+ppid
+prefork
+prepromptfns
+printif
+printqt
+promptexpand
+pushheap
+putshout
+pwd
+quietgetevent
+quietgethist
+quotedzputs
+refreshptr
+remlpaths
+remnulargs
+removehashnode
+resetneeded
+restoredir
+reswdtab
+retflag
+scanhashtable
+setaparam
+setlimits
+setsparam
+settyinfo
+shfunctab
+shingetline
+shout
+shttyinfo
+singsub
+skipparens
+spaceinlineptr
+spacesplit
+spckword
+startparamscope
+stdunsetfn
+stophist
+stopmsg
+strinbeg
+strinend
+strpfx
+strsfx
+strucpy
+struncpy
+tclen
+tcstr
+termflags
+tgoto
+tok
+tokenize
+tokstr
+tputs
+trashzleptr
+tricat
+tsetcap
+ttystrname
+tulower
+tuupper
+txtchange
+typtab
+ugetnode
+uinsertlinknode
+unmeta
+unmetafy
+untokenize
+uremnode
+useheap
+winchanged
+wordbeg
+zalloc
+zbeep
+zcalloc
+zchdir
+zerr
+zerrnam
+zexit
+zfree
+zgetdir
+zgetenv
+zjoin
+zleactive
+zleparse
+zlereadptr
+zputs
+zreaddir
+zsetlimit
+zsfree
+zshcs
+zshll
+zstrtol
+ztokens
+ztrdup
+ztrduppfx
+ztrftime
+ztrlen
+ztrsub
+zwarnnam
diff --git a/Src/zsh.h b/Src/zsh.h
new file mode 100644
index 000000000..e96fc6e86
--- /dev/null
+++ b/Src/zsh.h
@@ -0,0 +1,1293 @@
+/*
+ * zsh.h - standard header file
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#define trashzle() trashzleptr()
+#define zleread(X,Y,H) zlereadptr(X,Y,H)
+#define spaceinline(X) spaceinlineptr(X)
+#define gotword() gotwordptr()
+#define refresh() refreshptr()
+
+#define compctlread(N,A,O,R) compctlreadptr(N,A,O,R)
+
+/* A few typical macros */
+#define minimum(a,b) ((a) < (b) ? (a) : (b))
+
+/* math.c */
+typedef int LV;
+
+/* Character tokens are sometimes casted to (unsigned char)'s. *
+ * Unfortunately, some compilers don't correctly cast signed to *
+ * unsigned promotions; i.e. (int)(unsigned char)((char) -1) evaluates *
+ * to -1, instead of 255 like it should. We circumvent the troubles *
+ * of such shameful delinquency by casting to a larger unsigned type *
+ * then back down to unsigned char. */
+
+#ifdef BROKEN_SIGNED_TO_UNSIGNED_CASTING
+# define STOUC(X) ((unsigned char)(unsigned short)(X))
+#else
+# define STOUC(X) ((unsigned char)(X))
+#endif
+
+/* Meta together with the character following Meta denotes the character *
+ * which is the exclusive or of 32 and the character following Meta. *
+ * This is used to represent characters which otherwise has special *
+ * meaning for zsh. These are the characters for which the imeta() test *
+ * is true: the null character, and the characters from Meta to Marker. */
+
+#define Meta ((char) 0x83)
+
+/* Note that the fourth character in DEFAULT_IFS is Meta *
+ * followed by a space which denotes the null character. */
+
+#define DEFAULT_IFS " \t\n\203 "
+
+/* Character tokens */
+#define Pound ((char) 0x84)
+#define String ((char) 0x85)
+#define Hat ((char) 0x86)
+#define Star ((char) 0x87)
+#define Inpar ((char) 0x88)
+#define Outpar ((char) 0x89)
+#define Qstring ((char) 0x8a)
+#define Equals ((char) 0x8b)
+#define Bar ((char) 0x8c)
+#define Inbrace ((char) 0x8d)
+#define Outbrace ((char) 0x8e)
+#define Inbrack ((char) 0x8f)
+#define Outbrack ((char) 0x90)
+#define Tick ((char) 0x91)
+#define Inang ((char) 0x92)
+#define Outang ((char) 0x93)
+#define Quest ((char) 0x94)
+#define Tilde ((char) 0x95)
+#define Qtick ((char) 0x96)
+#define Comma ((char) 0x97)
+#define Snull ((char) 0x98)
+#define Dnull ((char) 0x99)
+#define Bnull ((char) 0x9a)
+#define Nularg ((char) 0x9b)
+
+#define INULL(x) (((x) & 0xfc) == 0x98)
+
+/* Marker used in paramsubst for rc_expand_param */
+#define Marker ((char) 0x9c)
+
+/* chars that need to be quoted if meant literally */
+
+#define SPECCHARS "#$^*()=|{}[]`<>?~;&\n\t \\\'\""
+
+enum {
+ NULLTOK, /* 0 */
+ SEPER,
+ NEWLIN,
+ SEMI,
+ DSEMI,
+ AMPER, /* 5 */
+ INPAR,
+ OUTPAR,
+ DBAR,
+ DAMPER,
+ OUTANG, /* 10 */
+ OUTANGBANG,
+ DOUTANG,
+ DOUTANGBANG,
+ INANG,
+ INOUTANG, /* 15 */
+ DINANG,
+ DINANGDASH,
+ INANGAMP,
+ OUTANGAMP,
+ AMPOUTANG, /* 20 */
+ OUTANGAMPBANG,
+ DOUTANGAMP,
+ DOUTANGAMPBANG,
+ TRINANG,
+ BAR, /* 25 */
+ BARAMP,
+ INOUTPAR,
+ DINPAR,
+ DOUTPAR,
+ AMPERBANG, /* 30 */
+ SEMIAMP,
+ DOUTBRACK,
+ STRING,
+ ENVSTRING,
+ ENVARRAY, /* 35 */
+ ENDINPUT,
+ LEXERR,
+
+ /* Tokens for reserved words */
+ BANG, /* ! */
+ DINBRACK, /* [[ */
+ INBRACE, /* { */ /* 40 */
+ OUTBRACE, /* } */
+ CASE, /* case */
+ COPROC, /* coproc */
+ DO, /* do */
+ DONE, /* done */ /* 45 */
+ ELIF, /* elif */
+ ELSE, /* else */
+ ZEND, /* end */
+ ESAC, /* esac */
+ FI, /* fi */ /* 50 */
+ FOR, /* for */
+ FOREACH, /* foreach */
+ FUNC, /* function */
+ IF, /* if */
+ NOCORRECT, /* nocorrect */ /* 55 */
+ REPEAT, /* repeat */
+ SELECT, /* select */
+ THEN, /* then */
+ TIME, /* time */
+ UNTIL, /* until */ /* 60 */
+ WHILE /* while */
+};
+
+/* Redirection types. If you modify this, you may also have to modify *
+ * redirtab in parse.c and getredirs() in text.c and the IS_* macros *
+ * below. */
+
+enum {
+ WRITE, /* > */
+ WRITENOW, /* >| */
+ APP, /* >> */
+ APPNOW, /* >>| */
+ ERRWRITE, /* &>, >& */
+ ERRWRITENOW, /* >&| */
+ ERRAPP, /* >>& */
+ ERRAPPNOW, /* >>&| */
+ READWRITE, /* <> */
+ READ, /* < */
+ HEREDOC, /* << */
+ HEREDOCDASH, /* <<- */
+ HERESTR, /* <<< */
+ MERGEIN, /* <&n */
+ MERGEOUT, /* >&n */
+ CLOSE, /* >&-, <&- */
+ INPIPE, /* < <(...) */
+ OUTPIPE /* > >(...) */
+};
+
+#define IS_WRITE_FILE(X) ((X)>=WRITE && (X)<=READWRITE)
+#define IS_APPEND_REDIR(X) (IS_WRITE_FILE(X) && ((X) & 2))
+#define IS_CLOBBER_REDIR(X) (IS_WRITE_FILE(X) && ((X) & 1))
+#define IS_ERROR_REDIR(X) ((X)>=ERRWRITE && (X)<=ERRAPPNOW)
+#define IS_READFD(X) (((X)>=READWRITE && (X)<=MERGEIN) || (X)==INPIPE)
+#define IS_REDIROP(X) ((X)>=OUTANG && (X)<=TRINANG)
+
+/* Flags for input stack */
+#define INP_FREE (1<<0) /* current buffer can be free'd */
+#define INP_ALIAS (1<<1) /* expanding alias or history */
+#define INP_HIST (1<<2) /* expanding history */
+#define INP_CONT (1<<3) /* continue onto previously stacked input */
+#define INP_ALCONT (1<<4) /* stack is continued from alias expn. */
+
+/* Flags for metafy */
+#define META_REALLOC 0
+#define META_USEHEAP 1
+#define META_STATIC 2
+#define META_DUP 3
+#define META_ALLOC 4
+#define META_NOALLOC 5
+#define META_HEAPDUP 6
+#define META_HREALLOC 7
+
+
+/**************************/
+/* Abstract types for zsh */
+/**************************/
+
+typedef struct linknode *LinkNode;
+typedef struct linklist *LinkList;
+typedef struct hashnode *HashNode;
+typedef struct hashtable *HashTable;
+
+typedef struct reswd *Reswd;
+typedef struct alias *Alias;
+typedef struct param *Param;
+typedef struct cmdnam *Cmdnam;
+typedef struct shfunc *Shfunc;
+typedef struct builtin *Builtin;
+typedef struct nameddir *Nameddir;
+typedef struct module *Module;
+
+typedef struct process *Process;
+typedef struct job *Job;
+typedef struct value *Value;
+typedef struct varasg *Varasg;
+typedef struct cond *Cond;
+typedef struct cmd *Cmd;
+typedef struct pline *Pline;
+typedef struct sublist *Sublist;
+typedef struct list *List;
+typedef struct comp *Comp;
+typedef struct redir *Redir;
+typedef struct complist *Complist;
+typedef struct heap *Heap;
+typedef struct heapstack *Heapstack;
+typedef struct histent *Histent;
+typedef struct forcmd *Forcmd;
+typedef struct autofn *AutoFn;
+
+typedef struct asgment *Asgment;
+
+
+/********************************/
+/* Definitions for linked lists */
+/********************************/
+
+/* linked list abstract data type */
+
+struct linknode {
+ LinkNode next;
+ LinkNode last;
+ void *dat;
+};
+
+struct linklist {
+ LinkNode first;
+ LinkNode last;
+};
+
+/* Macros for manipulating link lists */
+
+#define addlinknode(X,Y) insertlinknode(X,(X)->last,Y)
+#define uaddlinknode(X,Y) uinsertlinknode(X,(X)->last,Y)
+#define empty(X) ((X)->first == NULL)
+#define nonempty(X) ((X)->first != NULL)
+#define firstnode(X) ((X)->first)
+#define getaddrdata(X) (&((X)->dat))
+#define getdata(X) ((X)->dat)
+#define setdata(X,Y) ((X)->dat = (Y))
+#define lastnode(X) ((X)->last)
+#define nextnode(X) ((X)->next)
+#define prevnode(X) ((X)->last)
+#define peekfirst(X) ((X)->first->dat)
+#define pushnode(X,Y) insertlinknode(X,(LinkNode) X,Y)
+#define incnode(X) (X = nextnode(X))
+#define gethistent(X) (histentarr+((X)%histentct))
+
+
+/********************************/
+/* Definitions for syntax trees */
+/********************************/
+
+/* struct list, struct sublist, struct pline, etc. all fit the form *
+ * of this structure and are used interchangably. The ptrs may hold *
+ * integers or pointers, depending on the type of the node. */
+
+/* Generic node structure for syntax trees */
+struct node {
+ int ntype; /* node type */
+};
+
+#define N_LIST 0
+#define N_SUBLIST 1
+#define N_PLINE 2
+#define N_CMD 3
+#define N_REDIR 4
+#define N_COND 5
+#define N_FOR 6
+#define N_CASE 7
+#define N_IF 8
+#define N_WHILE 9
+#define N_VARASG 10
+#define N_AUTOFN 11
+#define N_COUNT 12
+
+/* values for types[4] */
+
+#define NT_EMPTY 0
+#define NT_NODE 1
+#define NT_STR 2
+#define NT_LIST 4
+#define NT_ARR 8
+
+#define NT_TYPE(T) ((T) & 0xff)
+#define NT_N(T, N) (((T) >> (8 + (N) * 4)) & 0xf)
+#define NT_SET(T0, T1, T2, T3, T4) \
+ ((T0) | ((T1) << 8) | ((T2) << 12) | ((T3) << 16) | ((T4) << 20))
+#define NT_HEAP (1 << 30)
+
+/* tree element for lists */
+
+struct list {
+ int ntype; /* node type */
+ int type;
+ Sublist left;
+ List right;
+};
+
+/* These are control flags that are passed *
+ * down the execution pipeline. */
+#define Z_TIMED (1<<0) /* pipeline is being timed */
+#define Z_SYNC (1<<1) /* run this sublist synchronously (;) */
+#define Z_ASYNC (1<<2) /* run this sublist asynchronously (&) */
+#define Z_DISOWN (1<<3) /* run this sublist without job control (&|) */
+
+/* tree element for sublists */
+
+struct sublist {
+ int ntype; /* node type */
+ int type;
+ int flags; /* see PFLAGs below */
+ Pline left;
+ Sublist right;
+};
+
+#define ORNEXT 10 /* || */
+#define ANDNEXT 11 /* && */
+
+#define PFLAG_NOT 1 /* ! ... */
+#define PFLAG_COPROC 32 /* coproc ... */
+
+/* tree element for pipes */
+
+struct pline {
+ int ntype; /* node type */
+ int type;
+ Cmd left;
+ Pline right;
+};
+
+#define END 0 /* pnode *right is null */
+#define PIPE 1 /* pnode *right is the rest of the pipeline */
+
+/* tree element for commands */
+
+struct cmd {
+ int ntype; /* node type */
+ int type;
+ int flags; /* see CFLAGs below */
+ int lineno; /* lineno of script for command */
+ union {
+ List list; /* for SUBSH/CURSH/SHFUNC */
+ Forcmd forcmd;
+ struct casecmd *casecmd;
+ struct ifcmd *ifcmd;
+ struct whilecmd *whilecmd;
+ Sublist pline;
+ Cond cond;
+ AutoFn autofn;
+ void *generic;
+ } u;
+ LinkList args; /* command & argmument List (char *'s) */
+ LinkList redir; /* i/o redirections (struct redir *'s) */
+ LinkList vars; /* param assignments (struct varasg *'s) */
+};
+
+/* cmd types */
+#define SIMPLE 0
+#define SUBSH 1
+#define CURSH 2
+#define ZCTIME 3
+#define FUNCDEF 4
+#define CFOR 5
+#define CWHILE 6
+#define CREPEAT 7
+#define CIF 8
+#define CCASE 9
+#define CSELECT 10
+#define COND 11
+#define CARITH 12
+#define AUTOFN 13
+
+/* flags for command modifiers */
+#define CFLAG_EXEC (1<<0) /* exec ... */
+
+/* tree element for redirection lists */
+
+struct redir {
+ int ntype; /* node type */
+ int type;
+ int fd1, fd2;
+ char *name;
+};
+
+/* tree element for conditionals */
+
+struct cond {
+ int ntype; /* node type */
+ int type; /* can be cond_type, or a single */
+ /* letter (-a, -b, ...) */
+ void *left, *right;
+};
+
+#define COND_NOT 0
+#define COND_AND 1
+#define COND_OR 2
+#define COND_STREQ 3
+#define COND_STRNEQ 4
+#define COND_STRLT 5
+#define COND_STRGTR 6
+#define COND_NT 7
+#define COND_OT 8
+#define COND_EF 9
+#define COND_EQ 10
+#define COND_NE 11
+#define COND_LT 12
+#define COND_GT 13
+#define COND_LE 14
+#define COND_GE 15
+
+struct forcmd { /* for/select */
+/* Cmd->args contains list of words to loop thru */
+ int ntype; /* node type */
+ int inflag; /* if there is an in ... clause */
+ char *name; /* initializer or parameter name */
+ char *condition; /* arithmetic terminating condition */
+ char *advance; /* evaluated after each loop */
+ List list; /* list to look through for each name */
+};
+
+struct casecmd {
+/* Cmd->args contains word to test */
+ int ntype; /* node type */
+ char **pats;
+ List *lists; /* list to execute */
+};
+
+
+/* A command like "if foo then bar elif baz then fubar else fooble" */
+/* generates a tree like: */
+/* */
+/* struct ifcmd a = { next = &b, ifl = "foo", thenl = "bar" } */
+/* struct ifcmd b = { next = &c, ifl = "baz", thenl = "fubar" } */
+/* struct ifcmd c = { next = NULL, ifl = NULL, thenl = "fooble" } */
+
+struct ifcmd {
+ int ntype; /* node type */
+ List *ifls;
+ List *thenls;
+};
+
+struct whilecmd {
+ int ntype; /* node type */
+ int cond; /* 0 for while, 1 for until */
+ List cont; /* condition */
+ List loop; /* list to execute until condition met */
+};
+
+/* node for autoloading functions */
+
+struct autofn {
+ int ntype; /* node type */
+ Shfunc shf; /* the shell function to define */
+};
+
+/* The number of fds space is allocated for *
+ * each time a multio must increase in size. */
+#define MULTIOUNIT 8
+
+/* A multio is a list of fds associated with a certain fd. *
+ * Thus if you do "foo >bar >ble", the multio for fd 1 will have *
+ * two fds, the result of open("bar",...), and the result of *
+ * open("ble",....). */
+
+/* structure used for multiple i/o redirection */
+/* one for each fd open */
+
+struct multio {
+ int ct; /* # of redirections on this fd */
+ int rflag; /* 0 if open for reading, 1 if open for writing */
+ int pipe; /* fd of pipe if ct > 1 */
+ int fds[MULTIOUNIT]; /* list of src/dests redirected to/from this fd */
+};
+
+/* variable assignment tree element */
+
+struct varasg {
+ int ntype; /* node type */
+ int type; /* nonzero means array */
+ char *name;
+ char *str; /* should've been a union here. oh well */
+ LinkList arr;
+};
+
+/* lvalue for variable assignment/expansion */
+
+struct value {
+ int isarr;
+ Param pm; /* parameter node */
+ int inv; /* should we return the index ? */
+ int a; /* first element of array slice, or -1 */
+ int b; /* last element of array slice, or -1 */
+};
+
+/* structure for foo=bar assignments */
+
+struct asgment {
+ struct asgment *next;
+ char *name;
+ char *value;
+};
+
+#define MAX_ARRLEN 262144
+
+
+/********************************************/
+/* Defintions for job table and job control */
+/********************************************/
+
+/* size of job table */
+#define MAXJOB 50
+
+/* entry in the job table */
+
+struct job {
+ pid_t gleader; /* process group leader of this job */
+ pid_t other; /* subjob id or subshell pid */
+ int stat; /* see STATs below */
+ char *pwd; /* current working dir of shell when *
+ * this job was spawned */
+ struct process *procs; /* list of processes */
+ LinkList filelist; /* list of files to delete when done */
+ int stty_in_env; /* if STTY=... is present */
+ struct ttyinfo *ty; /* the modes specified by STTY */
+};
+
+#define STAT_CHANGED (1<<0) /* status changed and not reported */
+#define STAT_STOPPED (1<<1) /* all procs stopped or exited */
+#define STAT_TIMED (1<<2) /* job is being timed */
+#define STAT_DONE (1<<3) /* job is done */
+#define STAT_LOCKED (1<<4) /* shell is finished creating this job, */
+ /* may be deleted from job table */
+#define STAT_NOPRINT (1<<5) /* job was killed internally, */
+ /* we don't want to show that */
+#define STAT_INUSE (1<<6) /* this job entry is in use */
+#define STAT_SUPERJOB (1<<7) /* job has a subjob */
+#define STAT_SUBJOB (1<<8) /* job is a subjob */
+#define STAT_CURSH (1<<9) /* last command is in current shell */
+#define STAT_NOSTTY (1<<10) /* the tty settings are not inherited */
+ /* from this job when it exits. */
+
+#define SP_RUNNING -1 /* fake status for jobs currently running */
+
+struct timeinfo {
+ long ut; /* user space time */
+ long st; /* system space time */
+};
+
+#define JOBTEXTSIZE 80
+
+/* node in job process lists */
+
+struct process {
+ struct process *next;
+ pid_t pid; /* process id */
+ char text[JOBTEXTSIZE]; /* text to print when 'jobs' is run */
+ int status; /* return code from waitpid/wait3() */
+ struct timeinfo ti;
+ struct timeval bgtime; /* time job was spawned */
+ struct timeval endtime; /* time job exited */
+};
+
+struct execstack {
+ struct execstack *next;
+
+ LinkList args;
+ pid_t list_pipe_pid;
+ int nowait;
+ int pline_level;
+ int list_pipe_child;
+ int list_pipe_job;
+ char list_pipe_text[JOBTEXTSIZE];
+ int lastval;
+ int noeval;
+ int badcshglob;
+ pid_t cmdoutpid;
+ int cmdoutval;
+ int trapreturn;
+ int noerrs;
+ int subsh_close;
+ char *underscore;
+};
+
+struct heredocs {
+ struct heredocs *next;
+ Redir rd;
+};
+
+struct dirsav {
+ int dirfd, level;
+ char *dirname;
+ dev_t dev;
+ ino_t ino;
+};
+
+/*******************************/
+/* Definitions for Hash Tables */
+/*******************************/
+
+typedef void *(*VFunc) _((void *));
+typedef void (*FreeFunc) _((void *));
+
+typedef unsigned (*HashFunc) _((char *));
+typedef void (*TableFunc) _((HashTable));
+typedef void (*AddNodeFunc) _((HashTable, char *, void *));
+typedef HashNode (*GetNodeFunc) _((HashTable, char *));
+typedef HashNode (*RemoveNodeFunc) _((HashTable, char *));
+typedef void (*FreeNodeFunc) _((HashNode));
+
+/* type of function that is passed to *
+ * scanhashtable or scanmatchtable */
+typedef void (*ScanFunc) _((HashNode, int));
+
+typedef void (*PrintTableStats) _((HashTable));
+
+/* hash table for standard open hashing */
+
+struct hashtable {
+ /* HASHTABLE DATA */
+ int hsize; /* size of nodes[] (number of hash values) */
+ int ct; /* number of elements */
+ HashNode *nodes; /* array of size hsize */
+
+ /* HASHTABLE METHODS */
+ HashFunc hash; /* pointer to hash function for this table */
+ TableFunc emptytable; /* pointer to function to empty table */
+ TableFunc filltable; /* pointer to function to fill table */
+ AddNodeFunc addnode; /* pointer to function to add new node */
+ GetNodeFunc getnode; /* pointer to function to get an enabled node */
+ GetNodeFunc getnode2; /* pointer to function to get node */
+ /* (getnode2 will ignore DISABLED flag) */
+ RemoveNodeFunc removenode; /* pointer to function to delete a node */
+ ScanFunc disablenode; /* pointer to function to disable a node */
+ ScanFunc enablenode; /* pointer to function to enable a node */
+ FreeNodeFunc freenode; /* pointer to function to free a node */
+ ScanFunc printnode; /* pointer to function to print a node */
+
+#ifdef HASHTABLE_INTERNAL_MEMBERS
+ HASHTABLE_INTERNAL_MEMBERS /* internal use in hashtable.c */
+#endif
+};
+
+/* generic hash table node */
+
+struct hashnode {
+ HashNode next; /* next in hash chain */
+ char *nam; /* hash key */
+ int flags; /* various flags */
+};
+
+/* The flag to disable nodes in a hash table. Currently *
+ * you can disable builtins, shell functions, aliases and *
+ * reserved words. */
+#define DISABLED (1<<0)
+
+/* node in shell reserved word hash table (reswdtab) */
+
+struct reswd {
+ HashNode next; /* next in hash chain */
+ char *nam; /* name of reserved word */
+ int flags; /* flags */
+ int token; /* corresponding lexer token */
+};
+
+/* node in alias hash table (aliastab) */
+
+struct alias {
+ HashNode next; /* next in hash chain */
+ char *nam; /* hash data */
+ int flags; /* flags for alias types */
+ char *text; /* expansion of alias */
+ int inuse; /* alias is being expanded */
+};
+
+/* is this alias global */
+#define ALIAS_GLOBAL (1<<1)
+
+/* node in command path hash table (cmdnamtab) */
+
+struct cmdnam {
+ HashNode next; /* next in hash chain */
+ char *nam; /* hash data */
+ int flags;
+ union {
+ char **name; /* full pathname for external commands */
+ char *cmd; /* file name for hashed commands */
+ }
+ u;
+};
+
+/* flag for nodes explicitly added to *
+ * cmdnamtab with hash builtin */
+#define HASHED (1<<1)
+
+/* node in shell function hash table (shfunctab) */
+
+struct shfunc {
+ HashNode next; /* next in hash chain */
+ char *nam; /* name of shell function */
+ int flags; /* various flags */
+ List funcdef; /* function definition */
+};
+
+/* node in builtin command hash table (builtintab) */
+
+typedef int (*HandlerFunc) _((char *, char **, char *, int));
+#define NULLBINCMD ((HandlerFunc) 0)
+
+struct builtin {
+ HashNode next; /* next in hash chain */
+ char *nam; /* name of builtin */
+ int flags; /* various flags */
+ HandlerFunc handlerfunc; /* pointer to function that executes this builtin */
+ int minargs; /* minimum number of arguments */
+ int maxargs; /* maximum number of arguments, or -1 for no limit */
+ int funcid; /* xbins (see above) for overloaded handlerfuncs */
+ char *optstr; /* string of legal options */
+ char *defopts; /* options set by default for overloaded handlerfuncs */
+};
+
+#define BUILTIN(name, flags, handler, min, max, funcid, optstr, defopts) \
+ { NULL, name, flags, handler, min, max, funcid, optstr, defopts }
+#define BIN_PREFIX(name, flags) \
+ BUILTIN(name, flags | BINF_PREFIX, NULLBINCMD, 0, 0, 0, NULL, NULL)
+
+/* builtin flags */
+/* DISABLE IS DEFINED AS (1<<0) */
+#define BINF_PLUSOPTS (1<<1) /* +xyz legal */
+#define BINF_R (1<<2) /* this is the builtin `r' (fc -e -) */
+#define BINF_PRINTOPTS (1<<3)
+#define BINF_ADDED (1<<4) /* is in the builtins hash table */
+#define BINF_FCOPTS (1<<5)
+#define BINF_TYPEOPT (1<<6)
+#define BINF_ECHOPTS (1<<7)
+#define BINF_MAGICEQUALS (1<<8) /* needs automatic MAGIC_EQUAL_SUBST substitution */
+#define BINF_PREFIX (1<<9)
+#define BINF_DASH (1<<10)
+#define BINF_BUILTIN (1<<11)
+#define BINF_COMMAND (1<<12)
+#define BINF_EXEC (1<<13)
+#define BINF_NOGLOB (1<<14)
+#define BINF_PSPECIAL (1<<15)
+
+#define BINF_TYPEOPTS (BINF_TYPEOPT|BINF_PLUSOPTS)
+
+struct module {
+ char *nam;
+ int flags;
+ void *handle;
+ LinkList deps;
+};
+
+#define MOD_BUSY (1<<0)
+
+/* node used in parameter hash table (paramtab) */
+
+struct param {
+ HashNode next; /* next in hash chain */
+ char *nam; /* hash data */
+ int flags; /* PM_* flags */
+
+ /* the value of this parameter */
+ union {
+ void *data; /* used by special parameter functions */
+ char **arr; /* value if declared array (PM_ARRAY) */
+ char *str; /* value if declared string (PM_SCALAR) */
+ long val; /* value if declared integer (PM_INTEGER) */
+ } u;
+
+ /* pointer to function to set value of this parameter */
+ union {
+ void (*cfn) _((Param, char *));
+ void (*ifn) _((Param, long));
+ void (*afn) _((Param, char **));
+ } sets;
+
+ /* pointer to function to get value of this parameter */
+ union {
+ char *(*cfn) _((Param));
+ long (*ifn) _((Param));
+ char **(*afn) _((Param));
+ } gets;
+
+ /* pointer to function to unset this parameter */
+ void (*unsetfn) _((Param, int));
+
+ int ct; /* output base or field width */
+ char *env; /* location in environment, if exported */
+ char *ename; /* name of corresponding environment var */
+ Param old; /* old struct for use with local */
+ int level; /* if (old != NULL), level of localness */
+};
+
+/* flags for parameters */
+
+/* parameter types */
+#define PM_SCALAR 0 /* scalar */
+#define PM_ARRAY (1<<0) /* array */
+#define PM_INTEGER (1<<1) /* integer */
+
+#define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY))
+
+#define PM_LEFT (1<<2) /* left justify and remove leading blanks */
+#define PM_RIGHT_B (1<<3) /* right justify and fill with leading blanks */
+#define PM_RIGHT_Z (1<<4) /* right justify and fill with leading zeros */
+#define PM_LOWER (1<<5) /* all lower case */
+
+/* The following are the same since they *
+ * both represent -u option to typeset */
+#define PM_UPPER (1<<6) /* all upper case */
+#define PM_UNDEFINED (1<<6) /* undefined (autoloaded) shell function */
+
+#define PM_READONLY (1<<7) /* readonly */
+#define PM_TAGGED (1<<8) /* tagged */
+#define PM_EXPORTED (1<<9) /* exported */
+#define PM_UNIQUE (1<<10) /* remove duplicates */
+#define PM_SPECIAL (1<<11) /* special builtin parameter */
+#define PM_DONTIMPORT (1<<12) /* do not import this variable */
+#define PM_RESTRICTED (1<<13) /* cannot be changed in restricted mode */
+#define PM_UNSET (1<<14) /* has null value */
+
+/* node for named directory hash table (nameddirtab) */
+
+struct nameddir {
+ HashNode next; /* next in hash chain */
+ char *nam; /* directory name */
+ int flags; /* see below */
+ char *dir; /* the directory in full */
+ int diff; /* strlen(.dir) - strlen(.nam) */
+};
+
+/* flags for named directories */
+/* DISABLED is defined (1<<0) */
+#define ND_USERNAME (1<<1) /* nam is actually a username */
+
+
+/* flags for controlling printing of hash table nodes */
+#define PRINT_NAMEONLY (1<<0)
+#define PRINT_TYPE (1<<1)
+#define PRINT_LIST (1<<2)
+
+/* flags for printing for the whence builtin */
+#define PRINT_WHENCE_CSH (1<<3)
+#define PRINT_WHENCE_VERBOSE (1<<4)
+#define PRINT_WHENCE_SIMPLE (1<<5)
+#define PRINT_WHENCE_FUNCDEF (1<<6)
+#define PRINT_WHENCE_WORD (1<<7)
+
+/***********************************/
+/* Definitions for history control */
+/***********************************/
+
+/* history entry */
+
+struct histent {
+ char *text; /* the history line itself */
+ char *zle_text; /* the edited history line */
+ time_t stim; /* command started time (datestamp) */
+ time_t ftim; /* command finished time */
+ short *words; /* Position of words in history */
+ /* line: as pairs of start, end */
+ int nwords; /* Number of words in history line */
+ int flags; /* Misc flags */
+};
+
+#define HIST_OLD 0x00000001 /* Command is already written to disk*/
+#define HIST_READ 0x00000002 /* Command was read back from disk*/
+
+/* Parts of the code where history expansion is disabled *
+ * should be within a pair of STOPHIST ... ALLOWHIST */
+
+#define STOPHIST (stophist += 4);
+#define ALLOWHIST (stophist -= 4);
+
+#define HISTFLAG_DONE 1
+#define HISTFLAG_NOEXEC 2
+#define HISTFLAG_RECALL 4
+
+
+/******************************************/
+/* Definitions for programable completion */
+/******************************************/
+
+/* Nothing special. */
+#define IN_NOTHING 0
+/* In command position. */
+#define IN_CMD 1
+/* In a mathematical environment. */
+#define IN_MATH 2
+/* In a condition. */
+#define IN_COND 3
+/* In a parameter assignment (e.g. `foo=bar'). */
+#define IN_ENV 4
+
+
+/******************************/
+/* Definition for zsh options */
+/******************************/
+
+/* Possible values of emulation */
+
+#define EMULATE_CSH (1<<1) /* C shell */
+#define EMULATE_KSH (1<<2) /* Korn shell */
+#define EMULATE_SH (1<<3) /* Bourne shell */
+#define EMULATE_ZSH (1<<4) /* `native' mode */
+
+/* option indices */
+
+enum {
+ OPT_INVALID,
+ ALLEXPORT,
+ ALWAYSLASTPROMPT,
+ ALWAYSTOEND,
+ APPENDHISTORY,
+ AUTOCD,
+ AUTOLIST,
+ AUTOMENU,
+ AUTONAMEDIRS,
+ AUTOPARAMKEYS,
+ AUTOPARAMSLASH,
+ AUTOPUSHD,
+ AUTOREMOVESLASH,
+ AUTORESUME,
+ BADPATTERN,
+ BANGHIST,
+ BAREGLOBQUAL,
+ BEEP,
+ BGNICE,
+ BRACECCL,
+ BSDECHO,
+ CDABLEVARS,
+ CHASELINKS,
+ CLOBBER,
+ COMPLETEALIASES,
+ COMPLETEINWORD,
+ CORRECT,
+ CORRECTALL,
+ CSHJUNKIEHISTORY,
+ CSHJUNKIELOOPS,
+ CSHJUNKIEQUOTES,
+ CSHNULLGLOB,
+ EQUALS,
+ ERREXIT,
+ EXECOPT,
+ EXTENDEDGLOB,
+ EXTENDEDHISTORY,
+ FLOWCONTROL,
+ FUNCTIONARGZERO,
+ GLOBOPT,
+ GLOBASSIGN,
+ GLOBCOMPLETE,
+ GLOBDOTS,
+ GLOBSUBST,
+ HASHCMDS,
+ HASHDIRS,
+ HASHLISTALL,
+ HISTALLOWCLOBBER,
+ HISTBEEP,
+ HISTIGNOREDUPS,
+ HISTIGNORESPACE,
+ HISTNOFUNCTIONS,
+ HISTNOSTORE,
+ HISTREDUCEBLANKS,
+ HISTVERIFY,
+ HUP,
+ IGNOREBRACES,
+ IGNOREEOF,
+ INTERACTIVE,
+ INTERACTIVECOMMENTS,
+ KSHARRAYS,
+ KSHAUTOLOAD,
+ KSHGLOB,
+ KSHOPTIONPRINT,
+ LISTAMBIGUOUS,
+ LISTBEEP,
+ LISTTYPES,
+ LOCALOPTIONS,
+ LOGINSHELL,
+ LONGLISTJOBS,
+ MAGICEQUALSUBST,
+ MAILWARNING,
+ MARKDIRS,
+ MENUCOMPLETE,
+ MONITOR,
+ MULTIOS,
+ NOMATCH,
+ NOTIFY,
+ NULLGLOB,
+ NUMERICGLOBSORT,
+ OVERSTRIKE,
+ PATHDIRS,
+ POSIXBUILTINS,
+ PRINTEIGHTBIT,
+ PRINTEXITVALUE,
+ PRIVILEGED,
+ PROMPTBANG,
+ PROMPTCR,
+ PROMPTPERCENT,
+ PROMPTSUBST,
+ PUSHDIGNOREDUPS,
+ PUSHDMINUS,
+ PUSHDSILENT,
+ PUSHDTOHOME,
+ RCEXPANDPARAM,
+ RCQUOTES,
+ RCS,
+ RECEXACT,
+ RESTRICTED,
+ RMSTARSILENT,
+ RMSTARWAIT,
+ SHFILEEXPANSION,
+ SHGLOB,
+ SHINSTDIN,
+ SHOPTIONLETTERS,
+ SHORTLOOPS,
+ SHWORDSPLIT,
+ SINGLECOMMAND,
+ SINGLELINEZLE,
+ SUNKEYBOARDHACK,
+ UNSET,
+ VERBOSE,
+ XTRACE,
+ USEZLE,
+ OPT_SIZE
+};
+
+#undef isset
+#define isset(X) (opts[X])
+#define unset(X) (!opts[X])
+
+#define interact (isset(INTERACTIVE))
+#define jobbing (isset(MONITOR))
+#define islogin (isset(LOGINSHELL))
+
+/***********************************************/
+/* Defintions for terminal and display control */
+/***********************************************/
+
+/* tty state structure */
+
+struct ttyinfo {
+#ifdef HAVE_TERMIOS_H
+ struct termios tio;
+#else
+# ifdef HAVE_TERMIO_H
+ struct termio tio;
+# else
+ struct sgttyb sgttyb;
+ int lmodes;
+ struct tchars tchars;
+ struct ltchars ltchars;
+# endif
+#endif
+#ifdef TIOCGWINSZ
+ struct winsize winsize;
+#endif
+};
+
+/* defines for whether tabs expand to spaces */
+#if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H)
+#define SGTTYFLAG shttyinfo.tio.c_oflag
+#else /* we're using sgtty */
+#define SGTTYFLAG shttyinfo.sgttyb.sg_flags
+#endif
+# ifdef TAB3
+#define SGTABTYPE TAB3
+# else
+# ifdef OXTABS
+#define SGTABTYPE OXTABS
+# else
+#define SGTABTYPE XTABS
+# endif
+# endif
+
+/* flags for termflags */
+
+#define TERM_BAD 0x01 /* terminal has extremely basic capabilities */
+#define TERM_UNKNOWN 0x02 /* unknown terminal type */
+#define TERM_NOUP 0x04 /* terminal has no up capability */
+#define TERM_SHORT 0x08 /* terminal is < 3 lines high */
+#define TERM_NARROW 0x10 /* terminal is < 3 columns wide */
+
+/* interesting termcap strings */
+
+#define TCCLEARSCREEN 0
+#define TCLEFT 1
+#define TCMULTLEFT 2
+#define TCRIGHT 3
+#define TCMULTRIGHT 4
+#define TCUP 5
+#define TCMULTUP 6
+#define TCDOWN 7
+#define TCMULTDOWN 8
+#define TCDEL 9
+#define TCMULTDEL 10
+#define TCINS 11
+#define TCMULTINS 12
+#define TCCLEAREOD 13
+#define TCCLEAREOL 14
+#define TCINSLINE 15
+#define TCDELLINE 16
+#define TCNEXTTAB 17
+#define TCBOLDFACEBEG 18
+#define TCSTANDOUTBEG 19
+#define TCUNDERLINEBEG 20
+#define TCALLATTRSOFF 21
+#define TCSTANDOUTEND 22
+#define TCUNDERLINEEND 23
+#define TC_COUNT 24
+
+#define tccan(X) (tclen[X])
+
+#define TXTBOLDFACE 0x01
+#define TXTSTANDOUT 0x02
+#define TXTUNDERLINE 0x04
+#define TXTDIRTY 0x80
+
+#define txtisset(X) (txtattrmask & (X))
+#define txtset(X) (txtattrmask |= (X))
+#define txtunset(X) (txtattrmask &= ~(X))
+
+#define TXTNOBOLDFACE 0x10
+#define TXTNOSTANDOUT 0x20
+#define TXTNOUNDERLINE 0x40
+
+#define txtchangeisset(X) (txtchange & (X))
+#define txtchangeset(X, Y) (txtchange |= (X), txtchange &= ~(Y))
+
+/****************************************/
+/* Definitions for the %_ prompt escape */
+/****************************************/
+
+#define cmdpush(X) if (!(cmdsp >= 0 && cmdsp < 256)) {;} else cmdstack[cmdsp++]=(X)
+#ifdef DEBUG
+# define cmdpop() if (cmdsp <= 0) { \
+ fputs("BUG: cmdstack empty\n", stderr); \
+ fflush(stderr); \
+ } else cmdsp--
+#else
+# define cmdpop() if (cmdsp <= 0) {;} else cmdsp--
+#endif
+
+#define CS_FOR 0
+#define CS_WHILE 1
+#define CS_REPEAT 2
+#define CS_SELECT 3
+#define CS_UNTIL 4
+#define CS_IF 5
+#define CS_IFTHEN 6
+#define CS_ELSE 7
+#define CS_ELIF 8
+#define CS_MATH 9
+#define CS_COND 10
+#define CS_CMDOR 11
+#define CS_CMDAND 12
+#define CS_PIPE 13
+#define CS_ERRPIPE 14
+#define CS_FOREACH 15
+#define CS_CASE 16
+#define CS_FUNCDEF 17
+#define CS_SUBSH 18
+#define CS_CURSH 19
+#define CS_ARRAY 20
+#define CS_QUOTE 21
+#define CS_DQUOTE 22
+#define CS_BQUOTE 23
+#define CS_CMDSUBST 24
+#define CS_MATHSUBST 25
+#define CS_ELIFTHEN 26
+#define CS_HEREDOC 27
+#define CS_HEREDOCD 28
+#define CS_BRACE 29
+#define CS_BRACEPAR 30
+
+/*********************
+ * Memory management *
+ *********************/
+
+#ifndef DEBUG
+# define HEAPALLOC do { int nonlocal_useheap = global_heapalloc(); do
+
+# define PERMALLOC do { int nonlocal_useheap = global_permalloc(); do
+
+# define LASTALLOC while (0); \
+ if (nonlocal_useheap) global_heapalloc(); \
+ else global_permalloc(); \
+ } while(0)
+
+# define LASTALLOC_RETURN \
+ if ((nonlocal_useheap ? global_heapalloc() : \
+ global_permalloc()), 0) {;} else return
+#else
+# define HEAPALLOC do { int nonlocal_useheap = global_heapalloc(); \
+ alloc_stackp++; do
+
+# define PERMALLOC do { int nonlocal_useheap = global_permalloc(); \
+ alloc_stackp++; do
+
+# define LASTALLOC while (0); alloc_stackp--; \
+ if (nonlocal_useheap) global_heapalloc(); \
+ else global_permalloc(); \
+ } while(0)
+
+# define LASTALLOC_RETURN \
+ if ((nonlocal_useheap ? global_heapalloc() : \
+ global_permalloc()),alloc_stackp--,0){;}else return
+#endif
+
+/****************/
+/* Debug macros */
+/****************/
+
+#ifdef DEBUG
+# define DPUTS(X,Y) if (!(X)) {;} else dputs(Y)
+# define MUSTUSEHEAP(X) if (useheap) {;} else \
+ fprintf(stderr, "BUG: permanent allocation in %s\n", X), \
+ fflush(stderr)
+#else
+# define DPUTS(X,Y)
+# define MUSTUSEHEAP(X)
+#endif
+
+/**************************/
+/* Signal handling macros */
+/**************************/
+
+/* These used in the sigtrapped[] array */
+
+#define ZSIG_TRAPPED (1<<0)
+#define ZSIG_IGNORED (1<<1)
+#define ZSIG_FUNC (1<<2)
+
+/****************/
+/* Entry points */
+/****************/
+
+/* compctl entry point pointers */
+
+typedef int (*CompctlReadFn) _((char *, char **, char *, char *));
+
+/* ZLE entry point pointers */
+
+typedef void (*ZleVoidFn) _((void));
+typedef void (*ZleVoidIntFn) _((int));
+typedef unsigned char * (*ZleReadFn) _((char *, char *, int));
diff --git a/Src/zsh.mdd b/Src/zsh.mdd
new file mode 100644
index 000000000..244029d65
--- /dev/null
+++ b/Src/zsh.mdd
@@ -0,0 +1,71 @@
+nozshdep=1
+alwayslink=1
+
+# autobins not specified because of alwayslink
+
+objects="builtin.o compat.o cond.o exec.o glob.o hashtable.o \
+hist.o init.o input.o jobs.o lex.o linklist.o loop.o math.o \
+mem.o module.o options.o params.o parse.o prompt.o signals.o \
+signames.o subst.o text.o utils.o watch.o"
+
+headers="../config.h system.h zsh.h sigcount.h signals.h \
+prototypes.h hashtable.h ztype.h"
+
+:<<\Make
+signames.c: signames.awk @SIGNAL_H@
+ $(AWK) -f $(sdir)/signames.awk @SIGNAL_H@ > $@
+
+sigcount.h: signames.c
+ grep 'define.*SIGCOUNT' signames.c > $@
+
+init.o: bltinmods.list zshpaths.h zshxmods.h
+
+params.o: version.h
+
+version.h: $(sdir_top)/Config/version.mk
+ echo '#define ZSH_VERSION "'$(VERSION)'"' > $@
+
+zshpaths.h: FORCE
+ @echo '#define MODULE_DIR "'$(MODDIR)'"' > zshpaths.h.tmp
+ @if cmp -s zshpaths.h zshpaths.h.tmp; then \
+ rm -f zshpaths.h.tmp; \
+ echo "\`zshpaths.h' is up to date." ; \
+ else \
+ mv -f zshpaths.h.tmp zshpaths.h; \
+ echo "Updated \`zshpaths.h'." ; \
+ fi
+
+bltinmods.list: modules.stamp modules-bltin xmods.conf mkbltnmlst.sh
+ srcdir='$(sdir)' MODBINS='modules-bltin' \
+ XMODCF='$(sdir)/xmods.conf' $(SHELL) $(sdir)/mkbltnmlst.sh $@
+
+zshxmods.h: modules-bltin xmods.conf
+ @echo "Creating \`$@'."
+ @( \
+ binmods=`sed 's/^/ /;s/$$/ /' modules-bltin`; \
+ for mod in `cat $(sdir_src)/xmods.conf`; do \
+ case $$binmods in \
+ *" $$mod "*) \
+ echo "#define LINKED_XMOD_$$mod 1" ;; \
+ *) echo "#ifdef DYNAMIC"; \
+ echo "# define UNLINKED_XMOD_$$mod 1"; \
+ echo "#endif" ;; \
+ esac; \
+ done; \
+ echo; \
+ for mod in $$binmods; do \
+ echo "int boot_$$mod _((Module));"; \
+ done; \
+ ) > $@
+
+clean-here: clean.zsh
+clean.zsh:
+ rm -f sigcount.h signames.c bltinmods.list version.h zshpaths.h zshxmods.h
+
+# This is not properly part of this module, but it is built as if it were.
+main.o: main.c zsh.mdh main.pro
+ $(CC) -c -I. $(CPPFLAGS) $(DEFS) $(CFLAGS) -o $@ $(sdir)/main.c
+
+main.pro: $(PROTODEPS)
+proto.zsh: main.pro
+Make
diff --git a/Src/ztype.h b/Src/ztype.h
new file mode 100644
index 000000000..595ff0588
--- /dev/null
+++ b/Src/ztype.h
@@ -0,0 +1,58 @@
+/*
+ * ztype.h - character classification macros
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#define IDIGIT (1 << 0)
+#define IALNUM (1 << 1)
+#define IBLANK (1 << 2)
+#define INBLANK (1 << 3)
+#define ITOK (1 << 4)
+#define ISEP (1 << 5)
+#define IALPHA (1 << 6)
+#define IIDENT (1 << 7)
+#define IUSER (1 << 8)
+#define ICNTRL (1 << 9)
+#define IWORD (1 << 10)
+#define ISPECIAL (1 << 11)
+#define IMETA (1 << 12)
+#define IWSEP (1 << 13)
+#define _icom(X,Y) (typtab[STOUC(X)] & Y)
+#define idigit(X) _icom(X,IDIGIT)
+#define ialnum(X) _icom(X,IALNUM)
+#define iblank(X) _icom(X,IBLANK) /* blank, not including \n */
+#define inblank(X) _icom(X,INBLANK) /* blank or \n */
+#define itok(X) _icom(X,ITOK)
+#define isep(X) _icom(X,ISEP)
+#define ialpha(X) _icom(X,IALPHA)
+#define iident(X) _icom(X,IIDENT)
+#define iuser(X) _icom(X,IUSER) /* username char */
+#define icntrl(X) _icom(X,ICNTRL)
+#define iword(X) _icom(X,IWORD)
+#define ispecial(X) _icom(X,ISPECIAL)
+#define imeta(X) _icom(X,IMETA)
+#define iwsep(X) _icom(X,IWSEP)
diff --git a/StartupFiles/.distfiles b/StartupFiles/.distfiles
new file mode 100644
index 000000000..7058eddc0
--- /dev/null
+++ b/StartupFiles/.distfiles
@@ -0,0 +1,4 @@
+DISTFILES_SRC='
+ .distfiles
+ zlogin zshenv zshrc
+'
diff --git a/StartupFiles/zlogin b/StartupFiles/zlogin
new file mode 100644
index 000000000..f510fd825
--- /dev/null
+++ b/StartupFiles/zlogin
@@ -0,0 +1,20 @@
+#
+# Generic .zlogin file for zsh 2.7
+#
+# .zlogin is sourced in login shells. It should
+# contain commands that should be executed only in
+# login shells. It should be used to set the terminal
+# type and run a series of external commands (fortune,
+# msgs, from, etc).
+#
+
+clear
+stty dec new cr0 -tabs
+ttyctl -f # freeze the terminal modes... can't change without a ttyctl -u
+mesg y
+uptime
+fortune
+log
+from 2>/dev/null
+cat notes
+msgs -fp
diff --git a/StartupFiles/zshenv b/StartupFiles/zshenv
new file mode 100644
index 000000000..2214b8e59
--- /dev/null
+++ b/StartupFiles/zshenv
@@ -0,0 +1,17 @@
+#
+# Generic .zshenv file for zsh 2.7
+#
+# .zshenv is sourced on all invocations of the
+# shell, unless the -f option is set. It should
+# contain commands to set the command search path,
+# plus other important environment variables.
+# .zshenv should not contain commands that product
+# output or assume the shell is attached to a tty.
+#
+
+export X11HOME=/usr/X11
+
+path=($X11HOME/bin)
+path=($path /usr/ucb /usr/bin /usr/etc)
+path=($path /usr/local/bin)
+path=($path ~ .)
diff --git a/StartupFiles/zshrc b/StartupFiles/zshrc
new file mode 100644
index 000000000..f9c89503e
--- /dev/null
+++ b/StartupFiles/zshrc
@@ -0,0 +1,121 @@
+#
+# Generic .zshrc file for zsh 2.7
+#
+# .zshrc is sourced in interactive shells. It
+# should contain commands to set up aliases, functions,
+# options, key bindings, etc.
+#
+
+# Search path for the cd command
+cdpath=(.. ~ ~/src ~/zsh)
+
+# Use hard limits, except for a smaller stack and no core dumps
+unlimit
+limit stack 8192
+limit core 0
+limit -s
+
+umask 022
+
+# Set up aliases
+alias mv='nocorrect mv' # no spelling correction on mv
+alias cp='nocorrect cp' # no spelling correction on cp
+alias mkdir='nocorrect mkdir' # no spelling correction on mkdir
+alias j=jobs
+alias pu=pushd
+alias po=popd
+alias d='dirs -v'
+alias h=history
+alias grep=egrep
+alias ll='ls -l'
+alias la='ls -a'
+
+# List only directories and symbolic
+# links that point to directories
+alias lsd='ls -ld *(-/DN)'
+
+# List only file beginning with "."
+alias lsa='ls -ld .*'
+
+# Shell functions
+setenv() { export $1=$2 } # csh compatibility
+
+# Where to look for autoloaded function definitions
+fpath=(~/.zfunc)
+
+# Autoload all shell functions from all directories
+# in $fpath that have the executable bit on
+# (the executable bit is not necessary, but gives
+# you an easy way to stop the autoloading of a
+# particular shell function).
+for dirname in $fpath
+do
+ autoload $dirname/*(.x:t)
+done
+
+# Global aliases -- These do not have to be
+# at the beginning of the command line.
+alias -g M='|more'
+alias -g H='|head'
+alias -g T='|tail'
+
+manpath=($X11HOME/man /usr/man /usr/lang/man /usr/local/man)
+export MANPATH
+
+# Filename suffixes to ignore during completion
+fignore=(.o .c~ .old .pro)
+
+# Hosts to use for completion
+hosts=(`hostname` ftp.math.gatech.edu prep.ai.mit.edu wuarchive.wustl.edu)
+
+# Set prompts
+PROMPT='%m%# ' # default prompt
+RPROMPT=' %~' # prompt for right side of screen
+
+# Some environment variables
+export MAIL=/var/spool/mail/$USERNAME
+export LESS=-cex3M
+export HELPDIR=/usr/local/lib/zsh/help # directory for run-help function to find docs
+
+MAILCHECK=300
+HISTSIZE=200
+DIRSTACKSIZE=20
+
+# Watch for my friends
+#watch=($(cat ~/.friends)) # watch for people in .friends file
+watch=(notme) # watch for everybody but me
+LOGCHECK=300 # check every 5 min for login/logout activity
+WATCHFMT='%n %a %l from %m at %t.'
+
+# Set/unset shell options
+setopt notify globdots correct pushdtohome cdablevars autolist
+setopt correctall autocd recexact longlistjobs
+setopt autoresume histignoredups pushdsilent noclobber
+setopt autopushd pushdminus extendedglob rcquotes mailwarning
+unsetopt bgnice autoparamslash
+
+# Setup some basic programmable completions. To see more examples
+# of these, check Misc/compctl-examples in the zsh distribution.
+compctl -g '*(-/)' cd pushd
+compctl -g '*(/)' rmdir dircmp
+compctl -j -P % -x 's[-] p[1]' -k signals -- kill
+compctl -j -P % fg bg wait jobs disown
+compctl -A shift
+compctl -caF type whence which
+compctl -F unfunction
+compctl -a unalias
+compctl -v unset typeset declare vared readonly export integer
+compctl -e disable
+compctl -d enable
+
+# Some nice key bindings
+#bindkey '^X^Z' universal-argument ' ' magic-space
+#bindkey '^X^A' vi-find-prev-char-skip
+#bindkey '^Z' accept-and-hold
+#bindkey -s '\M-/' \\\\
+#bindkey -s '\M-=' \|
+
+# bindkey -v # vi key bindings
+
+bindkey -e # emacs key bindings
+bindkey ' ' magic-space # also do history expansino on space
diff --git a/Util/.distfiles b/Util/.distfiles
new file mode 100644
index 000000000..1591692ce
--- /dev/null
+++ b/Util/.distfiles
@@ -0,0 +1,4 @@
+DISTFILES_SRC='
+ .distfiles
+ helpfiles mkdisttree.sh reporter zsh-development-guide
+'
diff --git a/Util/helpfiles b/Util/helpfiles
new file mode 100755
index 000000000..472132b29
--- /dev/null
+++ b/Util/helpfiles
@@ -0,0 +1,182 @@
+#!/usr/local/bin/perl -- -*-perl-*-
+
+# helpfiles: make help files for Z-shell builtins from the manual entries.
+
+# Create help files for zsh commands in the current directory;
+# assumes no other files are present.
+# No overwriting check; `.' becomes `dot', `:' becomes `colon'.
+
+# Any command claiming to be `same as <foo>' or `equivalent to <foo>'
+# has its help file appended to the end of <foo>'s and replaced by a
+# link to <foo>. (Arguably the help file should be put at the start
+# instead.)
+
+# Takes one filename argument, or stdin: the zsh manual page as a plain
+# ascii file: `man zshbuiltins | colcrt -' (remember the -) should do
+# the trick.
+
+# If you don't have colcrt, try 'col -bx'. The x is necessary so that
+# spaces don't turn into tabs, which messes up the calculations of
+# indentation on machines which randomly wrap lines round to the
+# previous line (so you see what we're up against).
+
+# Example usage:
+# cd ~/zsh-3.0.0 # or wherever
+# mkdir Help
+# cd Help
+# man zsh | colcrt - | helpfiles
+# run-help() {
+# typeset zhelp=~/zsh-3.0.0/Help # or wherever
+# [[ $1 = . ]] && 1=dot
+# [[ $1 = : ]] && 1=colon
+# if [[ $1 = compctl ]]; then
+# man zshcompctl
+# elif [[ -f $zhelp/$1 ]]; then
+# ${=PAGER:-more} $zhelp/$1
+# else
+# man $1
+# fi
+# }
+# now <Esc>-h works for shell builtins.
+
+while (<>) {
+ last if /^\s*SHELL BUILTIN COMMANDS/;
+ /zshbuiltins/ && $zb++;
+ last if ($zb && /^\s*DESCRIPTIONS/);
+}
+
+$print = 0;
+
+sub namesub {
+ local($cmd) = shift;
+ if ($cmd =~ /^\w+$/) {
+ $cmd;
+ } elsif ($cmd eq '.') {
+ 'dot';
+ } elsif ($cmd eq ':') {
+ 'colon';
+ } else {
+ undef;
+ }
+}
+
+sub getsame {
+ local($_) = shift;
+ if (/same\s*as\s*(\S+)/i || /equivalent\s*to\s*(\S+)/i) {
+ local($name) = $1;
+ ($name =~ /[.,]$/) && chop($name);
+ return $name;
+ } else {
+ return undef;
+ }
+}
+
+sub newcmd {
+ local($_) = shift;
+ local($cmd);
+ # new command
+ if (defined($cmd = &namesub($_))) {
+ # in case there's something nasty here like a link..
+ unlink $cmd;
+ open (OUT, ">$cmd");
+ select OUT;
+ $print = 1;
+ } else {
+ $print = 0;
+ }
+}
+
+sub doprint {
+ local($_) = shift;
+
+ s/^$indentstr//o; # won't work if too many tabs
+ print;
+}
+
+while (<>) { last unless /^\s*$/; }
+
+/^(\s+)(\S+)/;
+$indentstr = $1;
+$indent = length($1);
+&newcmd($2);
+print if $print;
+
+BUILTINS: while (<>) {
+ next if /^\w/;
+
+ undef($undented);
+ if (/^\s*$/ || ($undented = (/^(\s*)/ && length($1) < $indent))) {
+ $undented && &doprint($_);
+ while (defined($_ = <>) && /(^\w)|(^\s*$)/) {
+ last BUILTINS if /^STARTUP\/SHUTDOWN FILES/;
+ }
+ if (/^\s*Page/) {
+ do {
+ $_ = <>;
+ } while (defined($_) && /^\s*$/);
+ if (/^\s*ZSHBUILTINS/) {
+ do {
+ $_ = <>;
+ } while (defined($_) && /^\s*$/);
+ }
+ }
+ # In zshall, the zshcompctl manual page comes after the
+ # builtins.
+ if (/ZSHCOMPCTL\(1\).*ZSHCOMPCTL\(1\)/) {
+ last BUILTINS;
+ }
+ if (/^(\s*)/ && length($1) < $indent) {
+ # This may be just a bug on the SGI, or maybe something
+ # more sinister (don\'t laugh, this is nroff).
+ s/^\s*/ /;
+ $defer = $_;
+ do {
+ $_ = <>;
+ } while (defined($_) && /^\s*$/);
+ last unless defined($_);
+ }
+ if (/^(\s+)(\S+)/ && length($1) == $indent) {
+ &newcmd($2);
+ } else {
+ print "\n";
+ }
+ if ($print) {
+ if (defined($defer)) {
+ chop;
+ &doprint("$_$defer");
+ undef($defer);
+ } else {
+ &doprint($_);
+ }
+ }
+ } else {
+ &doprint($_) if $print;
+ }
+}
+
+select STDOUT;
+close OUT;
+
+foreach $file (<*>) {
+ open (IN, $file);
+ if ($sameas = (&getsame($_ = <IN>) || &getsame($_ = <IN>))) {
+ defined($sameas = &namesub($sameas)) || next;
+# print "$file is the same as $sameas\n";
+ seek (IN, 0, 0);
+
+ # Copy this to base builtin.
+ open (OUT, ">>$sameas");
+ select OUT;
+ print "\n";
+ while (<IN>) { print; }
+ close IN;
+ select STDOUT;
+ close OUT;
+
+ # Make this a link to that.
+ unlink $file;
+ symlink ($sameas, $file);
+ }
+}
+
+__END__
diff --git a/Util/mkdisttree.sh b/Util/mkdisttree.sh
new file mode 100755
index 000000000..837ebbcc2
--- /dev/null
+++ b/Util/mkdisttree.sh
@@ -0,0 +1,76 @@
+#! /bin/sh
+
+if test $# -lt 4; then
+ echo >&2 "Usage: $0 <dist-tree-name> <top-source-dir> <top-build-dir> <type> <make-args>"
+ exit 2
+fi
+
+case "$1" in
+ /*) disttree=$1 ;;
+ *) disttree=`pwd`/$1 ;;
+esac
+
+case "$2" in
+ /*) sdir_top=$2 ;;
+ *) sdir_top=`pwd`/$2 ;;
+esac
+
+case "$3" in
+ /*) dir_top=$3 ;;
+ *) dir_top=`pwd`/$3 ;;
+esac
+
+type=$4
+shift 4
+
+rm -rf $disttree
+
+sed_separate='
+ :1
+ $!{
+ N
+ b1
+ }
+ s/\n/ /g
+ s/^/deplist=;globlist=! /
+ s/$/ !/
+ s/ */ /g
+ s/ \([^?*[!][^?*[!]*\) / !deplist="$deplist \1"! /g
+ s/! !/;/g
+ s/! \([^!]*\) !/;globlist="$globlist \1";/g
+ s/!/;/g
+ s/;;*/;/g
+'
+
+(
+ cd $sdir_top
+ find . \( -name '*.*' -prune -false \) -o \( -name .distfiles -print \)
+) | while read dfn; do
+ subdir=`echo $dfn | sed 's,/\.distfiles$,,'`
+ echo >&2 "Processing directory $subdir..."
+ eval "DISTFILES_$type="
+ . $sdir_top/$dfn
+ eval "distfiles=\$DISTFILES_$type"
+ if test -n "$distfiles"; then
+ cmds=`echo "$distfiles" | sed -e "$sed_separate"`
+ eval "$cmds"
+ if test -n "$deplist" && test -f $dir_top/$subdir/Makefile; then
+ ( cd $dir_top/$subdir && "$@" $deplist ) || exit 1
+ fi
+ $sdir_top/mkinstalldirs $disttree/$subdir || exit 1
+ for f in $deplist `test -z "$globlist" || ( cd $dir_top/$subdir && eval "echo $globlist")`; do
+ if test -f $dir_top/$subdir/$f; then
+ ln $dir_top/$subdir/$f $disttree/$subdir/$f || \
+ cp -p $dir_top/$subdir/$f $disttree/$subdir/$f || exit 1
+ elif test -f $sdir_top/$subdir/$f; then
+ ln $sdir_top/$subdir/$f $disttree/$subdir/$f || \
+ cp -p $sdir_top/$subdir/$f $disttree/$subdir/$f || exit 1
+ else
+ echo >&2 "$0: can't find file $subdir/$f"
+ exit 1
+ fi
+ done
+ fi
+done
+
+exec chmod -R a+rX,u+w,go-w $disttree
diff --git a/Util/reporter b/Util/reporter
new file mode 100644
index 000000000..8f8f530ae
--- /dev/null
+++ b/Util/reporter
@@ -0,0 +1,444 @@
+#!/usr/local/bin/zsh
+#
+# NAME:
+# reporter
+#
+# SYNOPSIS:
+# reporter [all | aliases | bindings | completion | functions |
+# limits | options | variables]
+#
+# DESCRIPTION:
+# "reporter" prints your current environment variables, shell
+# variables, limits, completion settings, and option settings to
+# stdout in the form of a script.
+#
+# If you run into a zsh bug, someone can source the output script to
+# recreate most of the environment under which you were working.
+#
+# IMPORTANT: "source" this script, don't try to run it directly.
+# Otherwise it won't report the settings for your
+# current shell session.
+#
+# OPTIONS:
+# All command-line options can be abbreviated.
+#
+# "aliases" prints only aliases.
+# "bindings" prints only "bindkey" commands.
+# "completion" prints only "compctl" commands.
+# "functions" prints "autoload" commands or actual functions.
+# "limits" prints "limit" commands for things like cputime, etc.
+# "modules" prints "zmodload" commands.
+# "options" prints "setopt" commands.
+# "variables" prints both shell and environment variables.
+#
+# "all" tries to find every useful setting under your shell.
+# This is the default, and it's the same as typing all
+# of the above options on the command line.
+#
+# CAVEATS:
+# Assumes that you have the following programs in your search path:
+# awk, cut, echo, grep, sed, sort
+# Assumes that your C preprocessor lives in /lib/cpp or /usr/ccs/lib/cpp.
+# Uses (and unsets) variables beginning with "reporter_".
+#
+# RESTRICTIONS:
+# DON'T: pretend you wrote it, sell it, or blame me if it breaks.
+# DO: as ye will an' ye harm none.
+# --Wiccan saying, I think
+#
+# BUGS:
+# I'm sure there are more than a few. To be safe, run "zsh -f" before
+# sourcing the output from this script. If you have "screen", you may
+# want to use that, too; I hammered my terminal settings beyond repair
+# when using an early version, and "screen" saved me from having to
+# login on another terminal.
+#
+# HISTORY:
+# The name was ripped off from the Emacs "reporter.el" function.
+# The idea came from a mail message to the ZSH mailing list:
+#
+# Begin Configuration Section
+#
+
+reporter_OSVersion="`uname -s`_`uname -r`"
+
+#
+# Solaris 2.x
+#
+case ${reporter_OSVersion} in
+ SunOS_5.*)
+ CPP=${CPP:-/usr/ccs/lib/cpp}
+ AWK=${AWK:-nawk} # GNU AWK doesn't come standard :-(
+ ;;
+esac
+
+#
+# Default Values
+#
+
+CPP=${CPP:-/lib/cpp}
+AWK=${AWK:-awk}
+
+#
+# End Configuration Section
+#
+
+reporter_do_all=yes
+
+for each in $*
+do
+ case "$each"
+ in
+ ali*) reporter_do_aliases=yes; reporter_do_all=no ;;
+ b*) reporter_do_bindings=yes; reporter_do_all=no ;;
+ c*) reporter_do_compctl=yes; reporter_do_all=no ;;
+ f*) reporter_do_fun=yes; reporter_do_all=no ;;
+ l*) reporter_do_lim=yes; reporter_do_all=no ;;
+ m*) reporter_do_mod=yes; reporter_do_all=no ;;
+ o*) reporter_do_setopt=yes; reporter_do_all=no ;;
+ v*) reporter_do_vars=yes; reporter_do_all=no ;;
+ *) ;;
+ esac
+done
+
+#
+# The "cshjunkiequotes" option can break some of the commands
+# used in the remainder of this script, so we check for that first
+# and disable it. We'll re-enable it later.
+#
+
+reporter_junkiequotes="no"
+
+if setopt | grep "cshjunkiequotes" > /dev/null
+then
+ reporter_junkiequotes="yes"
+ unsetopt cshjunkiequotes
+fi
+
+#
+# UNAME
+#
+# This shows your system name. It's extremely system-dependent, so
+# we need a way to find out what system you're on. The easiest
+# way to do this is by using "uname", but not everyone has that,
+# so first we go through the search path.
+#
+# If we don't find it, then the only thing I can think of is to
+# check what's defined in your C compiler, and code in some exceptions
+# for the location of "uname" or an equivalent. For example, Pyramid
+# has "uname" only in the ATT universe. This code assumes that
+# the "-a" switch is valid for "uname".
+#
+# This section of code sees what is defined by "cpp". It was
+# originally written by brandy@tramp.Colorado.EDU (Carl Brandauer).
+# Additional error checking and sed hacking added by Ken Phelps.
+#
+
+reporter_cppdef=`strings -3 ${CPP} |
+ sed -n '
+ /^[a-zA-Z_][a-zA-Z0-9_]*$/{
+ s/.*/#ifdef &/p
+ s/.* \(.*\)/"\1";/p
+ s/.*/#endif/p
+ }
+ ' | ${CPP} |sed '
+ /^[ ]*$/d
+ /^#/d
+ s/.*"\(.*\)".*/\1/'`
+
+reporter_uname=""
+
+for each in `echo $PATH | sed -e 's/:/ /g'`
+do
+ if [ -x $each/uname ]
+ then
+ reporter_uname="$each/uname"
+ break
+ fi
+done
+
+case "$reporter_uname"
+in
+ "") reporter_uname="echo not found on this system" ;;
+ *) ;;
+esac
+
+for each in $reporter_cppdef
+do
+ case "$each"
+ in
+ pyr) reporter_uname="/bin/att uname" ;;
+ *) ;;
+ esac
+done
+
+str=`eval $reporter_uname -a`
+
+echo '# START zsh saveset'
+echo '# uname: ' $str
+echo
+
+unset reporter_cppdef
+unset reporter_uname
+
+#
+# ALIASES
+#
+# Use "alias -L" to get a listing of the aliases in the form we want.
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_aliases" = "yes"
+then
+ echo '# Aliases.'
+ echo
+
+ alias -L
+fi
+
+#
+# KEY BINDINGS
+#
+# The -L option does most of the work. The subshell is used to
+# avoid modifying things that will be recorded later.
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_bindings" = "yes"
+then
+ echo
+ echo "# Key bindings."
+ echo
+ bindkey -lL
+ (
+ alias bindkey=bindkey
+ bindkey () {
+ [[ "$1" == "-N" ]] || return
+ [[ "$2" == "--" ]] && shift
+ echo
+ builtin bindkey -L -M -- "$2"
+ }
+ eval "`builtin bindkey -lL`"
+ )
+fi
+
+#
+# COMPLETION COMMANDS
+# Warning: this won't work for zsh-2.5.03.
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_compctl" = "yes"
+then
+ echo
+ echo "# Completions."
+ echo
+
+ compctl -L
+fi
+
+#
+# FUNCTIONS
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_fun" = "yes"
+then
+ echo
+ echo "# Undefined functions."
+ echo
+
+ functions | grep "undefined" | ${AWK} '{print "autoload " $2}'
+
+ echo
+ echo "# Defined functions."
+ echo
+
+ functions | grep -v "undefined"
+fi
+
+#
+# LIMITS
+#
+# "cputime" has to be handled specially, because you can specify
+# the time as just hours, or "minutes:seconds".
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_lim" = "yes"
+then
+ echo
+ echo '# Limits.'
+ echo
+
+ (
+ set X `limit | grep "cputime" | grep -v "unlimited" |
+ sed -e 's/:/ /g'`
+
+ if test "$#" -gt 1
+ then
+ hr=$3
+ min=$4
+ sec=$5
+
+ if test "$hr" -gt 0
+ then
+ echo "limit cputime ${hr}h"
+ else
+ echo "limit cputime $min:$sec"
+ fi
+ fi
+ )
+
+ limit | grep -v "cputime" | grep -v "unlimited" |
+ sed -e 's/Mb/m/' -e 's/Kb/k/' |
+ ${AWK} 'NF > 1 {print "limit " $0}'
+fi
+
+#
+# MODULE LOADING COMMANDS
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_mod" = "yes"
+then
+ echo
+ if ( zmodload ) >& /dev/null; then
+ echo "# Modules."
+ echo
+ zmodload -d -L
+ echo
+ zmodload -a -L
+ echo
+ zmodload -L
+ else
+ echo "# Modules: zmodload not available."
+ fi
+fi
+
+#
+# NON-ARRAY VARIABLES
+#
+# We run this in a subshell to preserve the TERMCAP and TERM settings
+# in the current shell. Also, reset the prompt to show you're now
+# in a test shell. I can't find an easy way to do IFS, so I ignore it.
+#
+# Most of the sed nonsense is to make sure that variables are quoted
+# when being set. We also have to make sure that single-quotes and
+# back-quotes are escaped. This is why variable settings are
+# surrounded by double quotes; some variables like SPROMPT have single
+# quotes and back-quotes, and it's just too hard to escape those
+# properly when setting them.
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_vars" = "yes"
+then
+ echo
+ echo "# Non-array variables."
+ echo
+
+ (
+ echo "TERMCAP='$TERMCAP'"
+ echo "TERM='$TERM'"
+ unset TERMCAP
+
+ set | grep '=' | grep -v 'prompt=' |
+ grep -v 'reporter_do' |
+ grep -v '^[!#$*0?@_-]=' |
+ grep -v '=(' | sed -e "s/'/\\\'/g" |
+ sed -e 's/`/\\`/g' |
+ sed -e 's/=/="/' -e 's/$/"/' |
+ grep -v '^IFS=' |
+ grep -v '^TERMCAP=' |
+ grep -v '^TERM='
+
+ echo "prompt='test%'"
+ )
+
+#
+# ARRAY VARIABLES
+#
+# The "grep -v" nonsense is to keep from setting shell variables
+# that caused me some trouble from a script.
+#
+
+ echo
+ echo "# Array variables."
+ echo
+
+ echo "argv=()"
+ set | grep '=' | grep -v 'argv=' |
+ grep -v 'reporter_do' | grep -v '^[!#$*0?@_-]=' |
+ grep '=('
+
+#
+# EXPORTED VARIABLES
+#
+# Run this in a subshell to preserve the TERM and TERMCAP setting in
+# the current shell.
+#
+
+ echo
+ echo "# Exported variables."
+ echo
+
+ (
+ echo "export TERMCAP"
+ echo "export TERM"
+ unset TERMCAP
+
+ export | grep -v '^[!#$*0?@_-]=' |
+ ${AWK} -F='=' '{print "export " $1}' |
+ grep -v '^TERM=' | grep -v '^TERMCAP='
+ )
+fi
+
+#
+# SETOPT
+#
+# We exclude interactive because "setopt interactive" has no effect.
+# The cshjunkiequotes option is dealt with separately; see the
+# comments near the start of the script.
+#
+
+if test "$reporter_do_all" = "yes" -o "$reporter_do_setopt" = "yes"
+then
+ echo
+ echo '# Setopt.'
+ echo
+
+ (
+ setopt | grep -v 'interactive' | ${AWK} '{print "setopt " $0}'
+
+ case "$reporter_junkiequotes"
+ in
+ yes) echo "setopt cshjunkiequotes" ;;
+ *) ;;
+ esac
+ ) | sort
+fi
+
+echo
+echo '# END zsh saveset'
+
+#
+# Don't put an exit here, or you'll get a nasty surprise when you
+# source this thing. Get rid of variables created when processing
+# command line.
+#
+
+unset reporter_do_all
+unset reporter_do_aliases
+unset reporter_do_bindings
+unset reporter_do_compctl
+unset reporter_do_fun
+unset reporter_do_lim
+unset reporter_do_setopt
+unset reporter_do_vars
+
+#
+# Turn cshjunkiequotes back on if necessary.
+#
+
+case "$reporter_junkiequotes"
+in
+ yes) setopt cshjunkiequotes ;;
+ *) ;;
+esac
+
+unset reporter_junkiequotes
+
diff --git a/Util/zsh-development-guide b/Util/zsh-development-guide
new file mode 100644
index 000000000..d574d8af0
--- /dev/null
+++ b/Util/zsh-development-guide
@@ -0,0 +1,138 @@
+------------------------------
+GUIDELINES FOR ZSH DEVELOPMENT
+------------------------------
+
+Zsh is currently developed and maintained by the Zsh Development Group.
+This development takes place by mailing list. Check the META-FAQ for the
+various zsh mailing lists and how to subscribe to them. The development
+is very open and anyone is welcomed and encouraged to join and contribute.
+Because zsh is a very large package whose development can sometimes
+be very rapid, I kindly ask that people observe a few guidelines when
+contributing patches and feedback to the mailing list. These guidelines
+are very simple and hopefully should make for a more orderly development
+of zsh.
+
+Patches
+-------
+
+* Send all patches to the mailing list rather than directly to me.
+
+* Send only context diffs "diff -c oldfile newfile". They are much
+ easier to read and understand while also allowing the patch program
+ to patch more intelligently. Please make sure the filenames in
+ the diff header are relative to the top-level directory of the zsh
+ distribution; for example, it should say "Src/init.c" rather than
+ "init.c" or "zsh/Src/init.c".
+
+* Please put only one bug fix or feature enhancement in a single patch and
+ only one patch per mail message. This helps me to multiplex the many
+ (possibly conflicting) patches that I receive for zsh. You shouldn't
+ needlessly split patches, but send them in the smallest LOGICAL unit.
+
+* If a patch depends on other patches, then please say so. Also please
+ mention what version of zsh this patch is for.
+
+* Please test your patch and make sure it applies cleanly. It takes
+ considerably more time to manually merge a patch into the baseline code.
+
+* There is now a zsh patch archive. To have your patches appear in the
+ archive, send them to the mailing list with a Subject: line starting
+ with "PATCH:".
+
+C coding style
+--------------
+
+* The primary language is ANSI C as defined by the 1989 standard, but the
+ code should always be compatible with late K&R era compilers ("The C
+ Programming Language" 1st edition, plus "void" and "enum"). There are
+ many hacks to avoid the need to actually restrict the code to K&R C --
+ check out the configure tests -- but always bear the compatibility
+ requirements in mind. In particular, preprocessing directives must
+ have the "#" unindented, and string pasting is not available.
+
+* Conversely, there are preprocessor macros to provide safe access to some
+ language features not present in pure ANSI C, such as variable-length
+ arrays. Always use the macros if you want to use these facilities.
+
+* Avoid writing code that generates warnings under gcc with the default
+ options set by the configure script. For example, write
+ "if ((foo = bar))" rather than "if (foo = bar)".
+
+* Please try not using lines longer than 79 characters.
+
+* The indent/brace style is Kernighan and Ritchie with 4 characters
+ indentations (with leading tab characters replacing sequences of
+ 8 spaces). This means that the opening brace is the last character
+ in the line of the if/while/for/do statement and the closing brace
+ has its own line:
+
+ if (foo) {
+ do that
+ }
+
+* Put only one simple statement on a line. The body of an if/while/for/do
+ statement has its own line with 4 characters indentation even if there
+ are no braces.
+
+* Do not use space between the function name and the opening parenthesis.
+ Use space after if/for/while. Use space after type casts.
+
+* Do not use (unsigned char) casts since some compilers do not handle
+ them properly. Use the provided STOUC(X) macro instead.
+
+* If you use emacs 19.30 or newer you can put the following line to your
+ ~/.emacs file to make these formatting rules the default:
+
+ (add-hook 'c-mode-common-hook (function (lambda () (c-set-style "BSD"))))
+
+* Function declarations must look like this:
+
+ /**/
+ int
+ foo(char *s, char **p)
+ {
+ function body
+ }
+
+ There must be an empty line, a line with "/**/", a line with the
+ type of the function, and finally the name of the function with typed
+ arguments. These lines must not be indented. The script generating
+ function prototypes and the ansi2knr program depend on this format.
+ If the function is not used outside the file it is defined in, it
+ should be declared "static"; this keyword goes on the type line,
+ before the return type.
+
+* Global variable declarations must similarly be preceded by a
+ line containing only "/**/", for the prototype generation script.
+ The declaration itself should be all on one line (except for multi-line
+ initialisers).
+
+* Leave a blank line between the declarations and statements in a compound
+ statement, if both are present. Use blank lines elsewhere to separate
+ groups of statements in the interests of clarity. There should never
+ be two consecutive blank lines.
+
+Documentation
+-------------
+
+* Edit only the .yo files. All other formats (man pages, TeXinfo, HTML,
+ etc.) are automatically generated from the yodl source.
+
+* Always use the correct markup. em() is used for emphasis, and bf()
+ for citations. tt() marks text that is literal input to or output
+ from the shell. var() marks metasyntactic variables.
+
+* In addition to appropriate markup, always use quotes (`') where
+ appropriate. Specifically, use quotes to mark text that is not a part
+ of the actual text of the documentation (i.e., that it is being quoted).
+ In principle, all combinations of quotes and markup are possible,
+ because the purposes of the two devices are completely orthogonal.
+ For example,
+
+ Type `tt(xyzzy)' to let zsh know you have played tt(advent).
+ Saying `plugh' aloud doesn't have much effect, however.
+
+ In this case, "zsh" is normal text (a name), "advent" is a command name
+ ocurring in the main text, "plugh" is a normal word that is being quoted
+ (it's the user that says `plugh', not the documentation), and "xyzzy"
+ is some text to be typed literally that is being quoted.