Architecture
Principle
Use native zsh coproc to create processes, then escape the single-p
limitation by duplicating file descriptors into normal numbered descriptors.
Why not a coproc function?
The first design question was whether a shell function named coproc could
inspect tokens and delegate native forms back to zsh.
That does not work cleanly because zsh parses coproc as a reserved word. A
function cannot sit in front of the reserved word while preserving grammar for
native forms such as coproc { ... } and coproc while ....
Disabling the reserved word lets a function named coproc exist, but then zsh
no longer parses native coprocess grammar. That violates the compatibility goal.
Chosen design
- Scriptable API:
co-proc ... - Optional interactive sugar: a ZLE
accept-linewidget - Token inspection:
${(z)BUFFER}instead of a command-line regex - Conservative rewrite: only simple extended forms are rewritten
FD ownership
co-proc start NAME COMMAND performs:
coproc COMMAND
exec {outfd}<&p
exec {infd}>&p
The numbered descriptors are then owned by the registry entry for NAME.
Cleanup
co-proc stop closes both descriptors, terminates the child, waits for it, and
removes the active registry entry.
co-proc cleanup stops every registered process and is registered with
add-zsh-hook zshexit.
Current limitations
- Complex native-like forms should use native
coprocor explicitco-proc start NAME zsh -c '...'. - The interactive rewrite intentionally refuses lines with redirection, pipes, separators, grouped commands, or control operators.
- zsh still retargets the special
phandle whenever any native coprocess is started.