From c62668af308b429ee36d3bea14d2776f86c1b96c Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Fri, 2 Oct 2026 13:07:03 -0400 Subject: [PATCH] feat(cable): add jobs.toml for managing shell background jobs --- television/cable/jobs.toml | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 television/cable/jobs.toml diff --git a/television/cable/jobs.toml b/television/cable/jobs.toml new file mode 100644 index 0000000..540a1bc --- /dev/null +++ b/television/cable/jobs.toml @@ -0,0 +1,57 @@ +[metadata] +name = "jobs" +description = """ +The current fish shell's background jobs (stopped or running). tv runs in a +child process and cannot see the shell's job table, so the ctrl-z binding +(_jobs_bind) writes it to the file named by $TV_JOBS; enter prints the job id +and the binding runs `fg %`; ctrl-d kills the job. Opened any other way, +the list is empty. +""" +requirements = ["ps"] + +# Record: "% ", job id, process group (tab separated). +# Newest job first, the order fish's `jobs` prints. +[source] +command = '[ -n "$TV_JOBS" ] && [ -r "$TV_JOBS" ] && cat "$TV_JOBS"' +shell = "bash" +display = "{split:\\t:0}" +output = "{split:\\t:1}" + +[preview] +command = ''' +pg='{split:\t:2}' +# No other braces in this script: tv would read them as templates. procps +# `ps -g` matches sessions, not process groups, so filter on pgid by hand. +pids=$(ps -e -o pgid=,pid= --sort=start_time | while read -r g p; do [ "$g" = "$pg" ] && echo "$p"; done) +if [ -z "$pids" ]; then + echo "process group $pg is gone" + exit 0 +fi +first=$(echo "$pids" | head -n1) +printf '\033[1mcwd\033[0m %s\n' "$(readlink "/proc/$first/cwd" 2>/dev/null || echo '?')" +printf '\033[1mstarted\033[0m %s\n\n' "$(ps -o lstart= -p "$first" | sed 's/^ *//')" +# STAT T = stopped (ctrl-z), S/R = running in the background. +ps -o pid,stat,etime,pcpu,rss,args --forest -p "$(echo $pids | tr ' ' ,)" +''' +shell = "bash" + +[keybindings] +ctrl-d = ["actions:kill", "reload_source"] + +# SIGTERM to the whole process group, then SIGCONT: a stopped job holds TERM +# until it runs again. The row leaves $TV_JOBS only once the group is gone +# (up to 1 s), so reload_source keeps a job that ignored TERM. No braces: tv +# would read them as templates. +[actions.kill] +description = "Kill the job (SIGTERM to its process group)" +command = ''' +pg='{split:\t:2}' +kill -TERM -- "-$pg" 2>/dev/null +kill -CONT -- "-$pg" 2>/dev/null +for i in 1 2 3 4 5 6 7 8 9 10; do kill -0 -- "-$pg" 2>/dev/null || break; sleep 0.1; done +if ! kill -0 -- "-$pg" 2>/dev/null && [ -w "$TV_JOBS" ]; then + awk -F '\t' -v g="$pg" '$3 != g' "$TV_JOBS" >"$TV_JOBS.new" && mv "$TV_JOBS.new" "$TV_JOBS" +fi +''' +shell = "bash" +mode = "fork"