mirror of
https://github.com/zdharma-continuum/zconvey.git
synced 2026-09-10 07:06:21 -04:00
513 lines
16 KiB
Bash
513 lines
16 KiB
Bash
#
|
|
# No plugin manager is needed to use this file. All that is needed is adding:
|
|
# source {where-zconvey-is}/zconvey.plugin.zsh
|
|
#
|
|
# to ~/.zshrc.
|
|
#
|
|
|
|
0="${(%):-%N}" # this gives immunity to functionargzero being unset
|
|
ZCONVEY_REPO_DIR="${0%/*}"
|
|
ZCONVEY_CONFIG_DIR="$HOME/.config/zconvey"
|
|
|
|
#
|
|
# Update FPATH if:
|
|
# 1. Not loading with Zplugin
|
|
# 2. Not having fpath already updated (that would equal: using other plugin manager)
|
|
#
|
|
|
|
if [[ -z "$ZPLG_CUR_PLUGIN" && "${fpath[(r)$ZCONVEY_REPO_DIR]}" != $ZCONVEY_REPO_DIR ]]; then
|
|
fpath+=( "$ZCONVEY_REPO_DIR" )
|
|
fi
|
|
|
|
#
|
|
# Autoloads
|
|
#
|
|
autoload zc zc-rename zc-take zc-ls zc-logo
|
|
|
|
#
|
|
# Global variables
|
|
#
|
|
|
|
typeset -gi ZCONVEY_ID
|
|
typeset -ghH ZCONVEY_FD
|
|
typeset -ghH ZCONVEY_IO_DIR="${ZCONVEY_CONFIG_DIR}/io"
|
|
typeset -ghH ZCONVEY_LOCKS_DIR="${ZCONVEY_CONFIG_DIR}/locks"
|
|
typeset -ghH ZCONVEY_NAMES_DIR="${ZCONVEY_CONFIG_DIR}/names"
|
|
typeset -ghH ZCONVEY_OTHER_DIR="${ZCONVEY_CONFIG_DIR}/other"
|
|
typeset -ghH ZCONVEY_RUN_SECONDS=$(( SECONDS + 4 ))
|
|
typeset -ghH ZCONVEY_SCHEDULE_ORIGIN
|
|
command mkdir -p "$ZCONVEY_IO_DIR" "$ZCONVEY_LOCKS_DIR" "$ZCONVEY_NAMES_DIR" "$ZCONVEY_OTHER_DIR"
|
|
|
|
#
|
|
# Helper functions
|
|
#
|
|
|
|
function pinfo() {
|
|
print -- "\033[1;32m$*\033[0m";
|
|
}
|
|
|
|
function pinfo2() {
|
|
print -- "\033[1;33m$*\033[0m";
|
|
}
|
|
|
|
function __zconvey_resolve_name_to_id() {
|
|
local name="$1"
|
|
|
|
REPLY=""
|
|
local f
|
|
local -a arr
|
|
for f in "$ZCONVEY_NAMES_DIR"/*.name(N); do
|
|
arr=( ${(@f)"$(<$f)"} )
|
|
arr=( "${(@M)arr:#:$name:}" )
|
|
if [ "${#arr}" != "0" ]; then
|
|
REPLY="${${f:t}%.name}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
function __zconvey_get_name_of_id() {
|
|
local id="$1"
|
|
|
|
REPLY=""
|
|
local f="$ZCONVEY_NAMES_DIR/${id}.name"
|
|
if [ -e "$f" ]; then
|
|
REPLY=${(f)"$(<$f)"}
|
|
REPLY="${REPLY#:}"
|
|
REPLY="${REPLY%:}"
|
|
fi
|
|
}
|
|
|
|
function __zconvey_is_session_active() {
|
|
setopt localoptions extendedglob
|
|
local idx="$1"
|
|
|
|
if [[ "$idx" != <-> || "$idx" = "0" || "$idx" -gt "100" ]]; then
|
|
pinfo "Incorrect sesion ID occured: $idx"
|
|
return 2
|
|
fi
|
|
|
|
integer is_locked=0
|
|
local idfile="$ZCONVEY_LOCKS_DIR/zsh_nr${idx}" tmpfd
|
|
|
|
if [ -e "$idfile" ]; then
|
|
# Use zsystem only if non-blocking call is available (Zsh >= 5.3)
|
|
if [ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ]; then
|
|
zsystem flock -t 0 -f tmpfd "$idfile"
|
|
res="$?"
|
|
else
|
|
exec {tmpfd}>"$idfile"
|
|
"${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$tmpfd"
|
|
res="$?"
|
|
fi
|
|
|
|
if [[ "$res" = "101" || "$res" = "1" || "$res" = "2" ]]; then
|
|
is_locked=1
|
|
fi
|
|
|
|
# Close the lock immediately
|
|
[ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ] && zsystem flock -u "$tmpfd" || exec {tmpfd}>&-
|
|
fi
|
|
|
|
return $(( 1-is_locked ))
|
|
}
|
|
|
|
#
|
|
# User functions
|
|
#
|
|
|
|
function zc-id() {
|
|
__zconvey_get_name_of_id "$ZCONVEY_ID"
|
|
if [ -z "$REPLY" ]; then
|
|
print "This Zshell's ID: \033[1;33m<${ZCONVEY_ID}>\033[0m (no name assigned)";
|
|
else
|
|
print "This Zshell's ID: \033[1;33m<${ZCONVEY_ID}>\033[0m, name: \033[1;33m${REPLY}\033[0m";
|
|
fi
|
|
}
|
|
|
|
function zc-logo-all() {
|
|
setopt localoptions extendedglob clobber
|
|
integer idx is_locked counter=0
|
|
local busyfile busywith
|
|
|
|
if [[ "$1" = "-h" || "$1" = "--help" ]]; then
|
|
pinfo "Sends zc-logo or zc-id to all terminals (the latter when argument \"text\" is passed)"
|
|
return 0
|
|
fi
|
|
|
|
for (( idx = 1; idx <= 100; idx ++ )); do
|
|
name=""
|
|
busywith=""
|
|
is_locked=0
|
|
|
|
__zconvey_is_session_active "$idx" && is_locked=1 || is_locked=0
|
|
|
|
if (( is_locked )); then
|
|
busyfile="$ZCONVEY_OTHER_DIR/${idx}.busy"
|
|
if [[ -e "$busyfile" && "$idx" != "$ZCONVEY_ID" ]]; then
|
|
busywith="\033[1;33m$(<$busyfile)\033[0m"
|
|
|
|
__zconvey_get_name_of_id "$idx"
|
|
print "Session $idx (name: $REPLY) busy ($busywith), no logo request for it"
|
|
else
|
|
counter+=1
|
|
[ "$1" = "text" ] && zc -qi "$idx" zc-id || zc -qi "$idx" zc-logo \&\& sleep 18
|
|
fi
|
|
fi
|
|
done
|
|
|
|
pinfo "Sent logo request to $counter sessions, including this one"
|
|
|
|
return 0
|
|
}
|
|
|
|
#
|
|
# Load configuration
|
|
#
|
|
|
|
() {
|
|
setopt localoptions extendedglob
|
|
typeset -gA ZCONVEY_CONFIG
|
|
|
|
local check_interval
|
|
zstyle -s ":plugin:zconvey" check_interval check_interval || check_interval="2"
|
|
[[ "$check_interval" != <-> ]] && check_interval="2"
|
|
ZCONVEY_CONFIG[check_interval]="$check_interval"
|
|
|
|
local use_zsystem_flock
|
|
zstyle -b ":plugin:zconvey" use_zsystem_flock use_zsystem_flock || use_zsystem_flock="yes"
|
|
[ "$use_zsystem_flock" = "yes" ] && use_zsystem_flock="1" || use_zsystem_flock="0"
|
|
ZCONVEY_CONFIG[use_zsystem_flock]="$use_zsystem_flock"
|
|
|
|
local greeting
|
|
zstyle -s ":plugin:zconvey" greeting greeting || greeting="logo"
|
|
[[ "$greeting" != "none" && "$greeting" != "text" && "$greeting" != "logo" ]] && greeting="logo"
|
|
ZCONVEY_CONFIG[greeting]="$greeting"
|
|
|
|
local timestamp_from
|
|
zstyle -s ":plugin:zconvey" timestamp_from timestamp_from || timestamp_from="datetime"
|
|
[[ "$timestamp_from" != "date" && "$timestamp_from" != "datetime" ]] && timestamp_from="datetime"
|
|
ZCONVEY_CONFIG[timestamp_from]="$timestamp_from"
|
|
|
|
local expire_seconds
|
|
zstyle -s ":plugin:zconvey" expire_seconds expire_seconds || expire_seconds="22"
|
|
[[ "$expire_seconds" != <-> ]] && expire_seconds="22"
|
|
ZCONVEY_CONFIG[expire_seconds]="$expire_seconds"
|
|
|
|
local output_method
|
|
zstyle -s ":plugin:zconvey" output_method output_method || output_method="feeder"
|
|
[[ "$output_method" != "feeder" && "$output_method" != "zsh" ]] && output_method="feeder"
|
|
ZCONVEY_CONFIG[output_method]="$output_method"
|
|
}
|
|
|
|
#
|
|
# Compile myflock
|
|
#
|
|
|
|
# Binary flock command that supports 0 second timeout (zsystem's
|
|
# flock in Zsh ver. < 5.3 doesn't) - util-linux/flock stripped
|
|
# of some things, compiles hopefully everywhere (tested on OS X,
|
|
# Linux).
|
|
if [ ! -e "${ZCONVEY_REPO_DIR}/myflock/flock" ]; then
|
|
echo "\033[1;35m""psprint\033[0m/\033[1;33m""zconvey\033[0m is building small locking command for you..."
|
|
make -C "${ZCONVEY_REPO_DIR}/myflock"
|
|
fi
|
|
|
|
# A command that feeds data to command line, via TIOCSTI ioctl
|
|
if [ ! -e "${ZCONVEY_REPO_DIR}/feeder/feeder" ]; then
|
|
echo "\033[1;35m""psprint\033[0m/\033[1;33m""zconvey\033[0m is building small command line feeder for you..."
|
|
make -C "${ZCONVEY_REPO_DIR}/feeder"
|
|
fi
|
|
|
|
#
|
|
# Acquire ID
|
|
#
|
|
|
|
if [ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ]; then
|
|
autoload is-at-least
|
|
if ! is-at-least 5.3; then
|
|
# Use, but not for acquire
|
|
ZCONVEY_CONFIG[use_zsystem_flock]="2"
|
|
fi
|
|
|
|
if ! zmodload zsh/system 2>/dev/null; then
|
|
echo "Zconvey plugin: \033[1;31mzsh/system module not found, will use own flock implementation\033[0m"
|
|
echo "Zconvey plugin: \033[1;31mDisable this warning via: zstyle \":plugin:zconvey\" use_zsystem_flock \"0\"\033[0m"
|
|
ZCONVEY_CONFIG[use_zsystem_flock]="0"
|
|
elif ! zsystem supports flock; then
|
|
echo "Zconvey plugin: \033[1;31mzsh/system module doesn't provide flock, will use own implementation\033[0m"
|
|
echo "Zconvey plugin: \033[1;31mDisable this warning via: zstyle \":plugin:zconvey\" use_zsystem_flock \"0\"\033[0m"
|
|
ZCONVEY_CONFIG[use_zsystem_flock]="0"
|
|
fi
|
|
fi
|
|
|
|
() {
|
|
setopt localoptions extendedglob clobber
|
|
|
|
integer idx try_id res
|
|
local fd lockfile
|
|
|
|
# When in Tmux or Screen then consider every subshell
|
|
# session as new (no inheritance). TODO: detect exec zsh
|
|
[[ -n "$TMUX" || -n "$STY" ]] && ZCONVEY_ID=0 && ZCONVEY_FD=0
|
|
|
|
# Already assigned ID (inherited)?
|
|
idx=0
|
|
if [[ "$ZCONVEY_FD" = <-> && "$ZCONVEY_FD" != "0" && "$ZCONVEY_ID" = <-> && "$ZCONVEY_ID" != "0" ]]; then
|
|
# Inherited FD and ID, no need to perform work
|
|
if print -u "$ZCONVEY_FD" -n 2>/dev/null; then
|
|
# Unbusy this session
|
|
command rm -f "$ZCONVEY_OTHER_DIR/${ZCONVEY_ID}.busy"
|
|
idx=101
|
|
fi
|
|
fi
|
|
|
|
# Supported are 100 shells - acquire takes ~400ms max (zsystem's flock)
|
|
for (( ; idx <= 100; idx ++ )); do
|
|
# First (at first loop) try with $ZCONVEY_ID (the case of inherited ID)
|
|
[[ "$idx" = "0" && "$ZCONVEY_ID" = <-> ]] && try_id="$ZCONVEY_ID" || try_id="$idx"
|
|
[[ "$try_id" = "0" ]] && continue
|
|
|
|
lockfile="${ZCONVEY_LOCKS_DIR}/zsh_nr${try_id}"
|
|
echo "Lock done by Zsh (PID $$)" > "$lockfile"
|
|
|
|
# Use zsystem only if non-blocking call is available (Zsh >= 5.3)
|
|
# -e: preserve file descriptor on exec
|
|
if [ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ]; then
|
|
zsystem flock -t 0 -f ZCONVEY_FD -e "$lockfile"
|
|
res="$?"
|
|
else
|
|
exec {ZCONVEY_FD}>"$lockfile"
|
|
"${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$ZCONVEY_FD"
|
|
res="$?"
|
|
fi
|
|
|
|
if [[ "$res" = "101" || "$res" = "1" || "$res" = "2" ]]; then
|
|
[ "${ZCONVEY_CONFIG[use_zsystem_flock]}" != "1" ] && exec {ZCONVEY_FD}>&-
|
|
|
|
# Is this the special case, i.e. inherition of ZCONVEY_ID?
|
|
# In this case being unable to lock means: we already have
|
|
# that lock, we're at our ZCONVEY_ID, we should use it
|
|
# (process cannot lock files locked by itself, too)
|
|
if [[ "$idx" = "0" ]]; then
|
|
# Export again just to be sure
|
|
export ZCONVEY_ID
|
|
# We will not be able and want to close FD on zshexit
|
|
export ZCONVEY_FD=0
|
|
break
|
|
fi
|
|
else
|
|
# Successful locking in the special case (try_id = ZCONVEY_ID,
|
|
# i.e. idx == 0) means: we don't want to have that lock because
|
|
# it's not inherited (i.e. not already locked by ourselves)
|
|
if [[ "$idx" = "0" ]]; then
|
|
# Release the out of order lock
|
|
exec {ZCONVEY_FD}>&-
|
|
# We will not be able to quick-close FD on zshexit
|
|
ZCONVEY_FD=0
|
|
else
|
|
ZCONVEY_ID=try_id
|
|
# ID and FD will be inherited by subshells and exec zsh calls
|
|
export ZCONVEY_ID
|
|
export ZCONVEY_FD
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Show what is resolved (ID and possibly a NAME)
|
|
[ "$ZCONVEY_CONFIG[greeting]" = "logo" ] && zc-logo echo
|
|
[ "$ZCONVEY_CONFIG[greeting]" = "text" ] && zc-id
|
|
}
|
|
|
|
#
|
|
# Function to paste to command line, used when zle is active
|
|
#
|
|
|
|
function __zconvey_zle_paster() {
|
|
zle .kill-buffer
|
|
LBUFFER+="$*"
|
|
zle .redisplay
|
|
zle .accept-line
|
|
}
|
|
|
|
zle -N __zconvey_zle_paster
|
|
|
|
#
|
|
# Function to check for input commands
|
|
#
|
|
|
|
function __zconvey_on_period_passed() {
|
|
# Reschedule as quickly as possible - user might
|
|
# press Ctrl-C when function is executing
|
|
#
|
|
# Reschedule only if this scheduling sequence
|
|
# comes from approved single origin
|
|
[[ "$ZCONVEY_SCHEDULE_ORIGIN" = "$1" ]] && sched +"${ZCONVEY_CONFIG[check_interval]}" __zconvey_on_period_passed "$ZCONVEY_SCHEDULE_ORIGIN"
|
|
|
|
# ..and block Ctrl-C, this function will not
|
|
# stall, no reason for someone to use Ctrl-C
|
|
setopt localtraps; trap '' INT
|
|
setopt localoptions extendedglob clobber
|
|
|
|
# Remember when the command was run to detect a possible
|
|
# fail in schedule (because of unlucky Ctrl-C press)
|
|
ZCONVEY_RUN_SECONDS="$SECONDS"
|
|
|
|
local fd datafile="${ZCONVEY_IO_DIR}/${ZCONVEY_ID}.io"
|
|
local lockfile="${datafile}.lock"
|
|
|
|
# Quick return when no data
|
|
[ ! -e "$datafile" ] && return 1
|
|
|
|
# Prepare the lock file, follows locking it
|
|
echo "PID $$ ID $ZCONVEY_ID is reading commands" > "$lockfile"
|
|
|
|
# 1. Zsh 5.3 flock that supports timeout 0 (i.e. can be non-blocking)
|
|
if [ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "1" ]; then
|
|
if ! zsystem flock -t 0 -f fd "$lockfile"; then
|
|
LANG=C sleep 0.11
|
|
if ! zsystem flock -t 0 -f fd "$lockfile"; then
|
|
# Examine the situation by waiting long
|
|
LANG=C sleep 0.11
|
|
if ! zsystem flock -t 0 -f fd "$lockfile"; then
|
|
# Waited too long, lock must be broken, remove it
|
|
command rm -f "$lockfile"
|
|
# Will handle this input at next call
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
# 2. Zsh < 5.3 flock that isn't non-blocking
|
|
elif [ "${ZCONVEY_CONFIG[use_zsystem_flock]}" = "2" ]; then
|
|
if ! zsystem flock -t 1 -f fd "$lockfile"; then
|
|
# Waited too long, lock must be broken, remove it
|
|
command rm -f "$lockfile"
|
|
# Will handle this input at next call
|
|
return 1
|
|
fi
|
|
# 3. Provided flock binary
|
|
else
|
|
exec {fd}>"$lockfile"
|
|
"${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$fd"
|
|
if [ "$?" = "101" ]; then
|
|
LANG=C sleep 0.11
|
|
"${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$fd"
|
|
if [ "$?" = "101" ]; then
|
|
# Examine the situation by waiting long
|
|
sleep 1
|
|
"${ZCONVEY_REPO_DIR}/myflock/flock" -nx "$fd"
|
|
if [ "$?" = "101" ]; then
|
|
# Waited too long, lock must be broken, remove it
|
|
command rm -f "$lockfile"
|
|
# Will handle this input at next call
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
local -a commands
|
|
commands=( "${(@f)"$(<$datafile)"}" )
|
|
command rm -f "$datafile"
|
|
exec {fd}>&-
|
|
|
|
# Obtain current time stamp
|
|
local ts
|
|
if [ "$ZCONVEY_CONFIG[timestamp_from]" = "datetime" ]; then
|
|
[[ "${+modules}" = 1 && "${modules[zsh/datetime]}" != "loaded" && "${modules[zsh/datetime]}" != "autoloaded" ]] && zmodload zsh/datetime
|
|
[ "${+modules}" = 0 ] && zmodload zsh/datetime
|
|
ts="$EPOCHSECONDS"
|
|
fi
|
|
# Also a fallback
|
|
if [[ "$ZCONVEY_CONFIG[timestamp_from]" = "date" || -z "$ts" || "$ts" = "0" ]]; then
|
|
ts="$( date +%s )"
|
|
fi
|
|
|
|
# Get timestamp of each command, concatenate
|
|
# remaining parts with "; " as separator
|
|
local line cmdts concat_command=""
|
|
for line in "${commands[@]}"; do
|
|
cmdts="${line%% *}"
|
|
concat_command+="; ${line#* }"
|
|
done
|
|
concat_command="${concat_command#; }"
|
|
[[ -o interactive_comments ]] && concat_command+=" ##"
|
|
|
|
# TODO: a message that command expired
|
|
if (( ts - cmdts <= ZCONVEY_CONFIG[expire_seconds] )); then
|
|
# Two available methods of outputting the command
|
|
if [ "${ZCONVEY_CONFIG[output_method]}" = "zsh" ]; then
|
|
if zle; then
|
|
zle __zconvey_zle_paster "$concat_command"
|
|
else
|
|
print -zr "$concat_command"
|
|
fi
|
|
else
|
|
"${ZCONVEY_REPO_DIR}/feeder/feeder" "$concat_command"
|
|
fi
|
|
fi
|
|
|
|
# Tried: zle .kill-word, .backward-kill-line, .backward-kill-word,
|
|
# .kill-line, .vi-kill-line, .kill-buffer, .kill-whole-line
|
|
|
|
return 0
|
|
}
|
|
|
|
#
|
|
# Preexec hooks
|
|
#
|
|
|
|
# A hook:
|
|
# - detecting failure in re-scheduling
|
|
# - marking the shell as busy
|
|
__zconvey_preexec_hook() {
|
|
# No periodic run for a long time -> schedule
|
|
if (( SECONDS - ZCONVEY_RUN_SECONDS >= 4 )); then
|
|
# Simulate that __zconvey_on_period_passed was just
|
|
# ran and did re-schedule
|
|
ZCONVEY_RUN_SECONDS="$SECONDS"
|
|
|
|
# Schedule with new schedule origin - any duplicate
|
|
# scheduling sequence will be quickly eradicated
|
|
ZCONVEY_SCHEDULE_ORIGIN="$SECONDS"
|
|
sched +"${ZCONVEY_CONFIG[check_interval]}" __zconvey_on_period_passed "$ZCONVEY_SCHEDULE_ORIGIN"
|
|
fi
|
|
|
|
# Mark that the shell is busy
|
|
print -r -- "${1[(w)1]}" >! "$ZCONVEY_OTHER_DIR/${ZCONVEY_ID}.busy"
|
|
}
|
|
|
|
# A hook marking the shell as not busy
|
|
__zconvey_precmd_hook() {
|
|
command rm -f "$ZCONVEY_OTHER_DIR/${ZCONVEY_ID}.busy"
|
|
}
|
|
|
|
#
|
|
# Schedule, other
|
|
#
|
|
|
|
# Not called ideally at say SIGTERM, but
|
|
# at least when "exit" is enterred
|
|
function __zconvey_zshexit() {
|
|
[[ "$ZCONVEY_FD" != "0" && "$SHLVL" = "1" ]] && exec {ZCONVEY_FD}>&-
|
|
}
|
|
|
|
if ! type sched 2>/dev/null 1>&2; then
|
|
if ! zmodload zsh/sched 2>/dev/null; then
|
|
echo "Zconvey plugin: \033[1;31mzsh/sched module not found, Zconvey cannot work with this Zsh build, aborting\033[0m"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
ZCONVEY_SCHEDULE_ORIGIN="$SECONDS"
|
|
sched +"${ZCONVEY_CONFIG[check_interval]}" __zconvey_on_period_passed "$ZCONVEY_SCHEDULE_ORIGIN"
|
|
autoload -Uz add-zsh-hook
|
|
add-zsh-hook zshexit __zconvey_zshexit
|
|
add-zsh-hook preexec __zconvey_preexec_hook
|
|
add-zsh-hook precmd __zconvey_precmd_hook
|
|
|
|
zle -N zc-logo
|
|
bindkey '^O^I' zc-logo
|
|
|
|
# vim:ft=zsh
|