Better Shell Scrollback with Timestamps

Last week I noticed a pattern in some of my shell workloads: run something in a shell, go change some code, run it again. And often I'm looking for some sort of change between runs.

One of the most common things that could change (which I may not always notice) is how long the thing took to run. You could solve this with a utility like time, but you have to remember that, and it also pollutes your shell history, obscuring the actual thing you were trying to run.

Another common workflow is comparing somewhat detailed log outputs for differences. A single shell scrollback buffer is TERRIBLE for this, because you can't compare things side-by-side. So I often use multiple tabs/buffers/windows. This introduces a new problem though: remembering which one you ran last! It sounds silly, but I may go heads down for hours and it's very easy to forget!

I solved both of these with a better shell configuration. I'm using nushell, but something similar should be achievable in most shells:

# Right prompt: duration of the last command.
$env.PROMPT_COMMAND_RIGHT = {||
    let ms = ($env.CMD_DURATION_MS? | default "0" | into int)
    if $ms < 1 {
        ""
    } else {
        $"took (($ms * 1_000_000) | into duration)"
    }
}
$env.TRANSIENT_PROMPT_COMMAND_RIGHT = null

# Inserts a timestamp right before executing a command (after you press enter).
# I tried doing this with a transient prompt, but one interesting (useful?)
# property of nushell is that many things are required to be known and static at some point in time.
# This property applies to prompts.
# The prompt is evaluated only once, so the timestamp is the moment at which the prompt was rendered,
# which may be long before you execute the next command.
# So barring weird terminal tricks to edit previous lines, this is the best solution I've come up with so far.
$env.config = (
    $env.config
    | upsert hooks.pre_execution [ {||
        # $env.repl_commandline = (commandline)
        # print $"Command: ($env.repl_commandline)"
        let stamp = (date now | format date "%Y-%m-%dT%H:%M:%S%:z")
        print $"@($stamp)"
    } ]
)

Once you've made the edits, you can reload your config with exec nu. And now your scrollback is a log! Every entry has a start timestamp and a duration.