From f02eab9cacb1fad71d35f11742b65c8789502323 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Mon, 26 Aug 2024 12:14:52 -0400 Subject: top exit quit is fit to print --- src/finally.bash | 51 +++++++++++++ src/twopane.bash | 220 +++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 191 insertions(+), 80 deletions(-) create mode 100755 src/finally.bash (limited to 'src') diff --git a/src/finally.bash b/src/finally.bash new file mode 100755 index 0000000..7700c98 --- /dev/null +++ b/src/finally.bash @@ -0,0 +1,51 @@ +#!/bin/bash +# +# Usage: +# +# finally [args...] +# +# What it does: +# +# (1) First, the is executed with its arguments if any. +# +# (2) Then is executed as it would be by eval in +# the most global scope of the shell. (It is actually sent to +# the bash prompt's standard input as a list of quoted words.) +# +# Reason to exist: +# +# If execution of is interrupted by signal, the +# will still run. + +finally() +{ + exec \ + {FINALLY_0}<&0 \ + {FINALLY_1}>&1 \ + {FINALLY_2}>&2 \ + 0< <(finally_unwind "$1") \ + &> /dev/null # silence prompt + # note that finally_unwind still has un-redirected + # stdin/stderr + { + "${@:2}" + } <&$FINALLY_0 >&$FINALLY_1 2>&$FINALLY_2 +} + +finally_unwind() +{ + case $# in + 0 ) + exec \ + 0<&$FINALLY_0 \ + 1>&$FINALLY_1 \ + 2>&$FINALLY_2 \ + {FINALLY_0}<&- \ + {FINALLY_1}>&- \ + {FINALLY_2}>&- + ;; + 1 ) + echo "history -d -1; finally_unwind; $1" + ;; + esac +} diff --git a/src/twopane.bash b/src/twopane.bash index 327d246..c266828 100755 --- a/src/twopane.bash +++ b/src/twopane.bash @@ -6,7 +6,10 @@ set -f set -o pipefail shopt -s lastpipe +PS4='+ \t$LINENO\t ' + . read_chars.bash +. finally.bash kill_tty_forward() { @@ -17,17 +20,39 @@ kill_tty_forward() BOT_SIZE=8 BOT_TITLE=input +DO_RESTART=y +DO_RESTART= +DO_START= +DO_START=y + if [ $# = 0 ] then TOP_CMD="$SHELL -i" - BOT_CMD=start - #TOP_EXIT=quit - #TOP_EXIT=prompt - TOP_EXIT=restart + if [ "$DO_RESTART" ] + then + SIG=INT + BOT_CMD=PROMPT_COMMAND=prompt_command + TOP_EXIT=restart + elif [ "$DO_START" ] + then + BOT_CMD=start + TOP_EXIT=quit + else + BOT_CMD=PROMPT_COMMAND=prompt_command + TOP_EXIT=prompt + fi elif [ "$TWOPANE" -a "$*" = detach ] then kill_tty_forward -TSTP exit +elif [ "$TWOPANE" -a "$*" = focus ] +then + screen -X focus + exit +elif [ "$TWOPANE" -a "$*" = quit ] +then + screen -X quit + exit else TOP_CMD=$* BOT_CMD=start @@ -156,33 +181,42 @@ restart_top_pane() with_screen_pane 1 restart_pane "$@" } +# connect_coproc() -- start a coprocess and connect it to copied file +# descriptors named {NAME}_IN and {NAME}_OUT where {NAME} is the +# supplied first argument or default "COPROC". {NAME} is the name of +# the created bash coprocess. + +# The copied file descriptors (unlike the original coprocess +# file descriptors, in ${NAME[0]} and ${NAME[1]}) can be +# passed to external processes (e.g.: socat(1) instances) and +# used in subshells. + +# The created coprocess is silently disowned from job control before +# bash can make noise about it. connect_coproc() { [ $# -gt 0 ] || set -- COPROC declare -n coproc="$1" shift [ $# -gt 0 ] || set -- "${!coproc}" - declare -n std1="${!coproc}_STDOUT" - declare -n std0="${!coproc}_STDIN" - declare -n opid="${!coproc}_PID" - if ! [ "$opid" ] + declare -n std0="${!coproc}_IN" + declare -n std1="${!coproc}_OUT" + declare -n std2="${!coproc}_ERR" + declare -n pid="${!coproc}_PID" + if ! [ "$pid" ] then i "coproc starting: ${!coproc}" - local STDERR { coproc "${!coproc}" \ { "$@" - } 2>&$STDERR {STDERR}>&- + } 2>&$std2 {std2}>&- + unset std2 disown "%coproc ${!coproc} " - } {STDERR}>&2 2>/dev/null + } {std2}>&2 2>/dev/null else i "coproc already running: ${!coproc}" fi - # The copied file descriptors (unlike the original coprocess - # file descriptors, in ${coproc[0]} and ${coproc[1]}) can be - # passed to external processes (e.g.: socat(1) instances) and - # used in subshells. { exec {std0}<&0 {std1}>&1 } <&${coproc[0]} >&${coproc[1]} @@ -192,8 +226,8 @@ disconnect_coproc() { [ $# -gt 0 ] || set -- COPROC declare -n coproc="$1" - declare -n std0="${!coproc}_STDIN" - declare -n std1="${!coproc}_STDOUT" + declare -n std0="${!coproc}_IN" + declare -n std1="${!coproc}_OUT" declare -n pid="${!coproc}_PID" if [ "$std0" -o "$std1" ] then @@ -209,12 +243,17 @@ disconnect_coproc() connect() { - connect_coproc TOP_PANE + if ! [ "$TOP_PANE_PID" ] + then + disconnect_sink ECHOSEND + connect_coproc TOP_PANE + fi + connect_sink ECHOSEND echo_sender } disconnect() { - : "${TOP_PANE_STDIN@A} ${TOP_PANE_STDOUT@A} ${TOP_PANE_PID@A} ${TOP_PANE[@]@A}" + : "${TOP_PANE_IN@A} ${TOP_PANE_OUT@A} ${TOP_PANE_PID@A} ${TOP_PANE[@]@A}" disconnect_coproc TOP_PANE : "${ECHOSEND@A} ${ECHOSEND_PID@A}" disconnect_sink ECHOSEND @@ -223,23 +262,13 @@ disconnect() sendc() { [ "${TOP_PANE[0]}" ] || connect - printf '%s' "$*" >&${TOP_PANE[1]} + printf '%s' "$*" >&${TOP_PANE_OUT?Internal error} } send() { [ "${TOP_PANE[0]}" ] || connect - printf '%s\n' "$*" >&${TOP_PANE[1]} -} - -restart() -{ - start "$@" -} - -start() -{ - foreground "$@" + printf '%s\n' "$*" >&${TOP_PANE_OUT?Internal error} } tty_forward() @@ -252,18 +281,16 @@ tty_forward() TOP_PANE() { (exec -a bottom-pane-tty-forward socat - UNIX-CONNECT:"$TWOPANE"/socket,forever) - x kill_tty_forward -STOP case "$TOP_EXIT" in restart ) - # x kill -USR1 $$ + x kill_tty_forward -STOP x kill -INT $$ - # with_screen_pane 1 start_screen_pane "$@" ;; quit ) - kill -INT $$ quit ;; prompt | * ) + x kill_tty_forward -STOP focus bottom ;; esac @@ -271,10 +298,14 @@ TOP_PANE() connect_sink() { - [ "$1" ] || return + (( $# > 0 )) || set -- SINK declare -n fd="$1" declare -n pid="${!fd}_PID" - disconnect_sink "$fd" + if [ "$pid" ] + then + i "sink already running: ${!fd}" + return + fi i "sink starting: ${!fd}" exec {fd}> >("${@:2}") pid=$! @@ -282,7 +313,7 @@ connect_sink() disconnect_sink() { - [ "$1" ] || return + (( $# > 0 )) || set -- SINK declare -n fd="$1" [ "$fd" ] || return declare -n pid="${!fd}_PID" @@ -300,51 +331,66 @@ quiet_bg() } {STDERR}>&2 2>/dev/null } +# echo_sender() reads from two inputs and writes to two outputs. +# +# Read from both: +# read from stdin +# read from $TOP_PANE_IN +# Write to both: +# write to stdout +# write to $TOP_PANE_OUT +# I.e.: +# read from /dev/tty +# read from the socat-coproc-forwarded /dev/tty in the top pane. +# write to /dev/tty +# write to the socat-coproc-forwarded /dev/tty in the top pane. echo_sender() { - (exec -a echo_sender socat - fd:${TOP_PANE_STDIN?$0: Internal error}!!-) | - tee >(write-tty) >&${TOP_PANE_STDOUT?$0: Internal error} + (exec -a echo_sender socat - fd:${TOP_PANE_IN?$0: Internal error}!!-) | + tee >(write-tty) >&${TOP_PANE_OUT?$0: Internal error} } -background() +restart_tty_forward() { + { + kill -INT %tty_forward + disown %tty_forward + } &>/dev/null + with_screen_pane 1 start_screen_pane "$@" focus bottom - - disconnect_sink ECHOSEND connect - connect_sink ECHOSEND echo_sender - kill %tty_forward 2>/dev/null - %tty_forward & - disown %tty_forward i 'starting tty_forward' quiet_bg tty_forward >&$ECHOSEND echo $! > "$TWOPANE"/tty_forward.pid } -foreground() +attach() { - while true - do - background "$@" - focus top - set -x - %tty_forward >/dev/null 2>&1 - if [ "$TOP_EXIT" != restart ] || jobs %tty_forward >/dev/null 2>&1 - then - focus bottom - set +x - break - fi - set +x - done + if ! jobs %tty_forward &>/dev/null + then + restart_tty_forward "$@" + fi + focus top + { %tty_forward; } &>/dev/null + focus bottom } -twopane() +detach() { - start "$@" + { + kill -INT %tty_forward + disown %tty_forward + } &>/dev/null + focus bottom } +restart() { attach "$@"; } +start() { attach "$@"; } +foreground() { attach "$@"; } + +twopane() { attach "$@"; } + quit() { screen -X quit @@ -360,16 +406,6 @@ resize() screen -X resize "$@" } -attach() -{ - if jobs %tty_forward >/dev/null 2>&1 - then - %tty_forward - else - foreground - fi -} - SIGUSR1() { i "SIGUSR1: ${BASH_COMMAND@A}" @@ -382,21 +418,45 @@ SIGCHLD() prompt_command() { - set -- 'unset PROMPT_COMMAND; restart' + [ "$TOP_EXIT" = 'restart' ] || return 0 + + if [ "$SIG" = INT ] + then + finally 'unset SIG; start' detach + return + fi + + local job jobnew + if job=$(jobs -n %tty_forward 2>/dev/null) + then + jobnew=y + else + job=$(jobs %tty_forward 2>/dev/null) + jobnew= + fi + + if [ ! "$job" ] + then + finally 'start' : + return + fi - exec {FINALLY_0}<&0 {FINALLY_1}>&1 {FINALLY_2}>&2 - if ! [ "$DEBUG" ] + if [ "$jobnew" ] then - exec &>/dev/null + read _ jobstatus _ <<< "$job" + if [ "$jobstatus" = 'Running' ] + then + jobs -x finally 'attach' kill -CONT %tty_forward + fi + return fi - cmd="history -d -1; exec <&$FINALLY_0 >&$FINALLY_1 >&$FINALLY_2; $*" - exec <<< "$cmd" } SIGINT() { i INT "${BASH_COMMAND@A}" kill $TOP_PANE_PID $ECHOSEND_PID 2>/dev/null + SIG=INT PROMPT_COMMAND=prompt_command } -- cgit v1.2.3