zsh: update prompt with current time when a command is started
Asked Answered
F

6

44

I have a zsh prompt I rather like: it evaluates the current time in precmd and displays that on the right side of the prompt:

[Floatie:~] ^_^ 
cbowns%                      [9:28:31 on 2012-10-29]

However, this isn't exactly what I want: as you can see below, this time is actually the time the previous command exited, not the time the command was started:

[Floatie:~] ^_^ 
cbowns% date                           [9:28:26 on 2012-10-29]
Mon Oct 29 09:28:31 PDT 2012
[Floatie:~] ^_^ 
cbowns% date                           [9:28:31 on 2012-10-29]
Mon Oct 29 09:28:37 PDT 2012
[Floatie:~] ^_^ 
cbowns%                                [9:28:37 on 2012-10-29]

Is there a hook in zsh to run a command just before the shell starts a new command so I can update the prompt timestamp then? (I saw Constantly updated clock in zsh prompt?, but I don't need it constantly updated, just updated when I hit enter.)

(The ^_^ is based on the previous command's return code. It shows ;_; in red when there's a nonzero exit status.)

Flirtatious answered 29/10, 2012 at 16:34 Comment(2)
Share the the code for the happy/sad prompt?Pinero
@Pinero Sure. I've changed it to some Unicode, but the concept still applies. local smiley="%(?,%B%F{243}☆%f%b,%B%F{1}☃%f%b)", then that's interpolated into the PS1 var with ${smiley}.Flirtatious
S
20

I had a struggle to make this:

It displays the date on the right side when the command has been executed. It does not overwrite the command shown. Warning: it may overwrite the current RPROMPT.

strlen () {
    FOO=$1
    local zero='%([BSUbfksu]|([FB]|){*})'
    LEN=${#${(S%%)FOO//$~zero/}}
    echo $LEN
}

# show right prompt with date ONLY when command is executed
preexec () {
    DATE=$( date +"[%H:%M:%S]" )
    local len_right=$( strlen "$DATE" )
    len_right=$(( $len_right+1 ))
    local right_start=$(($COLUMNS - $len_right))

    local len_cmd=$( strlen "$@" )
    local len_prompt=$(strlen "$PROMPT" )
    local len_left=$(($len_cmd+$len_prompt))

    RDATE="\033[${right_start}C ${DATE}"

    if [ $len_left -lt $right_start ]; then
        # command does not overwrite right prompt
        # ok to move up one line
        echo -e "\033[1A${RDATE}"
    else
        echo -e "${RDATE}"
    fi

}

Sources:

Southdown answered 27/10, 2014 at 10:44 Comment(3)
This is a really awesome solution, but it chokes hard when I try to add colors into the mix. It looks like the place to add the color setter is in the echo line, but if the color is set after the escape characters, i get extra %{%} characters beforehand. And somehow, no matter where I try to reset the color scheme, i get those characters on the trailing end as well.Thymic
I recommend a couple of enhancements: 1) Add the date string in front of the time string... %Y-%m-%d %H:%M:%S, and 2) Use a linefeed in front of RDATE... i.e. RDATE="\n\033[${right_start}C ${DATE}"Osteitis
This is brilliant. Thank you for contributing such a useful snippet!Hagio
D
45

This is in fact possible without resorting to strange hacks. I've got this in my .zshrc

RPROMPT='[%D{%L:%M:%S %p}]'

TMOUT=1

TRAPALRM() {
    zle reset-prompt
}

The TRAPALRM function gets called every TMOUT seconds (in this case 1), and here it performs a prompt refresh, and does so until a command starts execution (and it doesn't interfere with anything you type on the prompt before hitting enter). I know you don't need it constantly refreshed but it still gets the job done without needing a line for itself!

Source: http://www.zsh.org/mla/users/2007/msg00944.html (It's from 2007!)

Disjoint answered 29/7, 2013 at 2:27 Comment(6)
I would suggest enhancing this with a hack from there as well: https://mcmap.net/q/375055/-zsh-menu-completion-causes-problems-after-zle-reset-promptDemotic
Also, note that this might make copy-pasting more annoying.Demotic
This is nice and simple, but setting TMOUT to 1 wreaks havoc on SSH sessions. If I don't run a command within 1 second of logging in, I get kicked out.Lustring
This fails to show the time for long lines, since zsh hides the rprompt when lines are too longHandful
Having an updating time until the command is executed is quite nice. But with this solution, arrow-up history stopped working for me. Does anyone know a fix for that?Brinkman
Thanks, Shevchuk, for the suggestion. Unfortunately, this still breaks my arrow-up history when initial letters have been entered: For example, entering cd, then pressing arrow up in order to browse through the previous cd commands. But thanks anyway.Brinkman
S
20

I had a struggle to make this:

It displays the date on the right side when the command has been executed. It does not overwrite the command shown. Warning: it may overwrite the current RPROMPT.

strlen () {
    FOO=$1
    local zero='%([BSUbfksu]|([FB]|){*})'
    LEN=${#${(S%%)FOO//$~zero/}}
    echo $LEN
}

# show right prompt with date ONLY when command is executed
preexec () {
    DATE=$( date +"[%H:%M:%S]" )
    local len_right=$( strlen "$DATE" )
    len_right=$(( $len_right+1 ))
    local right_start=$(($COLUMNS - $len_right))

    local len_cmd=$( strlen "$@" )
    local len_prompt=$(strlen "$PROMPT" )
    local len_left=$(($len_cmd+$len_prompt))

    RDATE="\033[${right_start}C ${DATE}"

    if [ $len_left -lt $right_start ]; then
        # command does not overwrite right prompt
        # ok to move up one line
        echo -e "\033[1A${RDATE}"
    else
        echo -e "${RDATE}"
    fi

}

Sources:

Southdown answered 27/10, 2014 at 10:44 Comment(3)
This is a really awesome solution, but it chokes hard when I try to add colors into the mix. It looks like the place to add the color setter is in the echo line, but if the color is set after the escape characters, i get extra %{%} characters beforehand. And somehow, no matter where I try to reset the color scheme, i get those characters on the trailing end as well.Thymic
I recommend a couple of enhancements: 1) Add the date string in front of the time string... %Y-%m-%d %H:%M:%S, and 2) Use a linefeed in front of RDATE... i.e. RDATE="\n\033[${right_start}C ${DATE}"Osteitis
This is brilliant. Thank you for contributing such a useful snippet!Hagio
A
15

You can remap the Return key to reset the prompt before accepting the line:

reset-prompt-and-accept-line() {
    zle reset-prompt
    zle accept-line
}

zle -N reset-prompt-and-accept-line

bindkey '^m' reset-prompt-and-accept-line
Attractive answered 25/10, 2015 at 9:46 Comment(4)
Ah, an interesting solution. Thank you!Flirtatious
I've just added this to my zsh theme before PROMPT and RPROMPT lines and it works really well. Thank you!Prerequisite
I like this approach a lot better, doesn't mess with word completionCycloid
Thanks for this solution! It worked great for me except for some reason, it broke history search functionality. Replace the last two lines in Witaut's response with this one to get it to work properly without breaking history search: zle -N accept-line reset-prompt-and-accept-lineNectareous
F
14

zsh will run the preexec function just before executing a line. It would be simple to have that output the current time, a simple version would be just:

preexec() { date }

Modifying an existing prompt would be much more challenging.

Fremantle answered 29/10, 2012 at 17:11 Comment(3)
Yeah, this is looking difficult, but preexec() is a good start. Thanks!Flirtatious
zle reset-prompt redraws the zsh prompt for you.Explain
This works really well combined with curses project that echos out swear words. I set it up as following: preexec() { /usr/bin/ruby /usr/local/curses/curse } to great effect ;)Darkroom
S
10

Building off @vitaŭt-bajaryn's cool ZSH style answer:

I think overriding the accept-line function is probably the most idiomatic zsh solution:

function _reset-prompt-and-accept-line {
  zle reset-prompt
  zle .accept-line     # Note the . meaning the built-in accept-line.
}
zle -N accept-line _reset-prompt-and-accept-line
Stretchy answered 28/1, 2016 at 1:9 Comment(3)
This doesn’t work when I do something like this C-r somestring RET, i.e. run a line straight from reverse search without moving the cursor or modifying it.Separative
I like this solution but it adds just a bit too much delay when pressing enter for me. Do you know if reset-prompt is intrinsically a bit slow or is it just my prompt that's slow to reset? I don't have much there, mostly just the time in RPROMPT (%{$fg[white]%}[%*]%{$reset_color%}) and git_prompt_info in PROMPT (%{$fg[cyan]%}%c%{$fg_bold[blue]%}$(git_prompt_info)%{$fg_bold[blue]%}% %{$reset_color%}:)Playreader
If you want this to work with incremental search (C-r), add the following: function _reset-prompt-and-accept-isearch { zle reset-prompt zle .zle-isearch-exit } zle -N zle-isearch-exit _reset-prompt-and-accept-isearchGarnetgarnett
B
8

You can use ANSI escape sequences to write over the previous line, like this:

preexec () {
  DATE=`date +"%H:%M:%S on %Y-%m-%d"`
  C=$(($COLUMNS-24))
  echo -e "\033[1A\033[${C}C ${DATE} "
}
Breaux answered 29/12, 2012 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.