~kris/dots

srice

a4bfa48cdda924f7a77a81e8d8e93e326a01b8a0 — Kris Yotam 2 months ago 65bd807
mksh: reliable history hook via HISTFILE growth + binary record parse

PS1 command substitution runs in a subshell so hook state must be
persisted to disk (per-PID file); fc there is lagged one command and
drops each session's last command. Detect new commands by HISTFILE
growth and parse the last record from mksh's on-disk format.
1 files changed, 26 insertions(+), 11 deletions(-)

M .mkshrc
M .mkshrc => .mkshrc +26 -11
@@ 45,18 45,33 @@ if [ -n "$BASH_VERSION" ]; then
	trap '_kh_last_cmd=$(fc -ln -1 2>/dev/null | sed "s/^[[:space:]]*//")' DEBUG
	PROMPT_COMMAND='_kh_log "$_kh_last_cmd" "$?"'
else
	# mksh: no DEBUG trap / PROMPT_COMMAND. Log from PS1 each prompt.
	# The leading $(...) captures $? first, so the exit code is accurate,
	# and _kh_prompt emits nothing so the visible prompt is unchanged.
	# Skip the first render: at shell start fc -ln -1 is a stale HISTFILE
	# entry, not a command run this session.
	_kh_first_prompt=1
	# mksh: no DEBUG trap / PROMPT_COMMAND, and PS1 command substitution
	# runs in a subshell, so hook state cannot live in shell variables --
	# it is persisted to a per-PID file instead. We detect that a new
	# command ran by watching HISTFILE grow, then read the last entry
	# straight from the file (fc in the PS1 subshell is lagged by one and
	# would drop the final command of every session).
	#
	# mksh HISTFILE record layout: 0xFF, 4-byte big-endian counter,
	# command bytes, NUL. So split on 0xFF, take the last record, drop the
	# 4 counter bytes (cut -b5-), strip the trailing NUL.
	[ -d "$HOME/.cache" ] || mkdir -p "$HOME/.cache"
	_kh_state="$HOME/.cache/kh_mksh_$$"
	_kh_prompt() {
		if [ -n "$_kh_first_prompt" ]; then
			unset _kh_first_prompt
			return
		fi
		_kh_log "$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//')" "$1"
		typeset ec="$1" sz prev cmd
		[ -f "$HISTFILE" ] || return
		sz=$(wc -c < "$HISTFILE" 2>/dev/null)
		prev=$(cat "$_kh_state" 2>/dev/null)
		print -r -- "$sz" > "$_kh_state"
		# first prompt of the session: record baseline, do not log
		[ -z "$prev" ] && return
		# only log when a new command was appended to history
		[ "$sz" -gt "$prev" ] 2>/dev/null || return
		cmd=$(tail -c 4096 "$HISTFILE" | tr "\377" "\n" | tail -n1 | cut -b5- | tr -d "\0")
		_kh_log "$cmd" "$ec"
	}
	# leading $(...) captures $? first so the exit code is accurate; the
	# hook prints nothing so the visible prompt is unchanged
	PS1='$(_kh_prompt "$?")'"$PS1"
	trap 'rm -f "$_kh_state"' EXIT
fi