43

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.)

Community
  • 1
  • 1
cbowns
  • 6,295
  • 5
  • 47
  • 64
  • Share the the code for the happy/sad prompt? – slashdottir Jul 16 '14 at 16:49
  • 1
    @slashdottir 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}`. – cbowns Jul 17 '14 at 01:24

6 Answers6

46

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!)

nitarshan
  • 1,465
  • 2
  • 12
  • 11
  • 1
    I would suggest enhancing this with a hack from there as well: http://stackoverflow.com/a/30456173/1091116 – d33tah Jul 01 '15 at 21:29
  • 1
    Also, note that this might make copy-pasting more annoying. – d33tah Jul 01 '15 at 21:30
  • 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. – Spencer Jan 22 '16 at 05:47
  • This fails to show the time for long lines, since zsh hides the rprompt when lines are too long – l0st3d May 05 '16 at 13:54
  • 1
    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? – fiedl Jul 24 '17 at 21:14
  • 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. – fiedl Mar 06 '19 at 14:53
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:

Community
  • 1
  • 1
SamK
  • 2,094
  • 3
  • 17
  • 18
  • 1
    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. – Kevin Feb 19 '15 at 17:42
  • 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}"` – Mike Pennington Aug 12 '16 at 13:30
  • This is brilliant. Thank you for contributing such a useful snippet! – DBedrenko Nov 05 '20 at 14:28
14

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
Witaut Bajaryn
  • 149
  • 1
  • 3
13

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.

qqx
  • 18,947
  • 4
  • 64
  • 68
  • Yeah, this is looking difficult, but `preexec()` is a good start. Thanks! – cbowns Oct 31 '12 at 19:00
  • `zle reset-prompt` redraws the zsh prompt for you. – Zoey Hewll Oct 01 '17 at 02:41
  • This works really well combined with [curses project](https://github.com/mccolin/curses) that echos out swear words. I set it up as following: `preexec() { /usr/bin/ruby /usr/local/curses/curse }` to great effect ;) – james-see Sep 16 '18 at 03:13
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
Community
  • 1
  • 1
NHDaly
  • 7,390
  • 4
  • 40
  • 45
  • 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. – Jonas Schäfer Jun 07 '17 at 11:12
  • 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%}:`) – Rafał Cieślak Jul 09 '19 at 16:39
  • 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-isearch` – gdkrmr Apr 20 '20 at 08:56
7

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} "
}
Dan Berindei
  • 7,054
  • 3
  • 41
  • 48