zle reset-prompt not cleaning the prompt - zsh

I have a key binding to go up by one directory (very useful):
# C-M-u: up-directory
up-directory() {
builtin cd .. && zle reset-prompt
}
zle -N up-directory
bindkey '\e\C-u' up-directory
It works well, except that the prompt is not really reset.
Example, starting in a Git repo (~/.dotfiles):
After C-M-u, I get:
So, I'm well one level up (into ~), but the Git info is still there, while not valid anymore -- I'm not in a Git repo anymore…
How to fix this?

You probably need to execute the precmds before resetting the prompt.
fzf's zsh integration does this:
# Ensure `precmds` are run after `cd`
fzf-redraw-prompt() {
local precmd
for precmd in $precmd_functions; do
$precmd
done
zle reset-prompt
}
So, try something like this:
up-directory() {
builtin cd ..
if (( $? == 0 )); then
local precmd
for precmd in $precmd_functions; do
$precmd
done
zle reset-prompt
fi
}

Related

Enter a command for a user

I have written a ZSH function whose output is a command line which runs a program I need the user to be able to interact with.
At the moment I just echo the command line and instruct the user to copy-paste it so that they have the necessary access to its pipes, however is there a way I can just have the function finish by entering the command for the user as if they had copied and pasted it themselves?
I have looked into using zle but that seems to require a key binding, whereas I just want the user to be able to run: myzshfunction arg1 and the ultimate result to be their terminal attached to the program launched as a result of some processing of their arg1.
$ myzshfunction arg2*2
Run this command! foobar baz4
$ foobar baz4
...
The function looks something like this:
myzshfunction() {
if [[ $# = 0 ]]
then
echo "usage: myzshfunction 1.2.3.4"
return
fi
local creds=`curl "https://xxx/$1/latest" | jq -r 'x'`
local cred_arr=("${(#s|/|)creds}")
local pwd_pipe=$(mktemp -u)
mkfifo $pwd_pipe
exec 3<>$pwd_pipe
rm $pwd_pipe
echo $cred_arr[2] >&3
echo "Run this: sshpass -d3 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$cred_arr[1]#$1"
exec 3>&-
}
TIA
Use print -z to add text to the buffer. From the documentation:
-z Push the arguments onto the editing buffer stack, separated by spaces.
Calling foo, defined below, will result in hi being placed on the command line as if the user had typed it. For example,
% foo () { print -z hi; }
% foo
% hi
The best solution I could come up with was to use zle - the Zsh Line Editor.
This lets you update the command the user is currently editing, which to me feels a bit hacky. I would prefer a solution that lets you call a function, hit return, and then cleanly run a function with STDIO attached to your terminal as if you had run the resulting command line.
Perhaps you could emulate this by bindkey'ing the return key and doing some sort of decision/routing from there to see if the user wants to call myfunc. For now, my solution requires the Esc+i sequence is entered after typing a target for $host.
zle-myfunc() {
apikey=$(keychain-environment-variable api-key)
if [ $? -ne 0 ]; then
echo "Add your api-key to the keychain: "
BUFFER='security add-generic-password -U -a ${USER} -D "environment variable" -s "api-key" -w'
zle accept-line
return 1
fi
local host=$BUFFER
zle kill-buffer
local creds=`curl ..." | jq -r ...`
if [ -z creds ]; then
echo "Couldn't get creds, check your network"
return 1
fi
local creds_arr=("${(#s|/|)creds}")
local pwd_pipe=$(mktemp -u)
mkfifo $pwd_pipe
exec 3<>$pwd_pipe
# anonymise the pipe
rm $pwd_pipe
echo "$creds_arr[2]" >&3
BUFFER="sshpass -d3 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $creds_arr[1]#$host"
zle accept-line
# exec 3>&-
}
zle -N zle-myfunc
bindkey '\ei' zle-myfunc

ZSH setopt PROMPT_SUBST not working

I'm trying to get customize my zsh prompt and want to evaluation a function with git commands every time my prompt is generated. I'm using setopt PROMPT_SUBST, but it doesn't seem to be working. This is my zshrc:
setopt PROMPT_SUBST
autoload -U colors && colors # Enable colors
# Show Git branch/tag, or name-rev if on detached head
parse_git_branch() {
echo "PARSING GIT BRANCH"
(git symbolic-ref -q HEAD || git name-rev --name-only --no-undefined --always HEAD) 2> /dev/null
}
prompt() {
echo -n "%/"
echo "$(git status)"
}
PS1="$(prompt)"
And this is my output of setopt:
interactive
login
monitor
nonomatch
promptsubst
shinstdin
zle
You need to delay calling prompt until the prompt is displayed; do that by using single quotes:
PS1='$(prompt)'
A better idea, though, is to define a function that sets PS1, then add that function to the precmd_functions array so that it is executed prior to displaying each prompt.
prompt () {
PS1="%/$(git status)"
}
precmd_functions+=(prompt)

widgets for case manipulation: `gU` in normal and `U` in visual mode

I reckon there's already a widget for the g~ action in zle. So g~iw will invert the case of a word.
I read the zshzle manual and did not find a widget that would give me the behaviour of gU (capitalize action) in vim.
for example: for the word "path_variable", with the cursor on the v, gUiW would change the world to "PATH_VARIABLE", and so on and so forth.
the widget capitalize-word does not seem to be the answer. I've tested it.
I also found that the key U in visual mode does not capitalize the visually selected text/region. I did not find a widget in the manual that would give me the desired behaviour either.
Is this a matter of writing a custom widget, or would one have to submit a patch upstream with c code changes? How can I bind gU in normal and U in visual mode to achieve the desired behaviour in zle vi-mode?
ZSH 5.3 will have pre built widgets for that. But if you can't wait, here it is:
# credits go to Oliver Kiddle <opk#zsh.org>,
# who personally shared these upper/lower widgets.
# I just corrected a small bug.
vi-lowercase() {
local save_cut="$CUTBUFFER"
local save_cur="$CURSOR"
zle .vi-change || return
zle .vi-cmd-mode
CUTBUFFER="${CUTBUFFER:l}"
if [[ $CURSOR = '0' ]]; then
zle .vi-put-before -n 1
else
zle .vi-put-after -n 1
fi
CUTBUFFER="$save_cut"
CURSOR="$save_cur"
}
vi-uppercase() {
local save_cut="$CUTBUFFER"
local save_cur="$CURSOR"
zle .vi-change || return
zle .vi-cmd-mode
CUTBUFFER="${CUTBUFFER:u}"
if [[ $CURSOR = '0' ]]; then
zle .vi-put-before -n 1
else
zle .vi-put-after -n 1
fi
CUTBUFFER="$save_cut"
CURSOR="$save_cur"
}
# can safely disable this after commit zsh commit #a73ae70 (zsh-5.2-301- ga73ae70)
zle -N vi-lowercase
zle -N vi-uppercase
bindkey -a 'gU' vi-uppercase
bindkey -a 'gu' vi-lowercase
bindkey -M visual 'u' vi-lowercase
bindkey -M visual 'U' vi-uppercase

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

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.)
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!)
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:
http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
https://stackoverflow.com/a/10564427/238913
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
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.
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
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} "
}

zsh alias expansion

Is it possible to configure zsh to expand global aliases during tab completion? For example, I have the common aliases:
alias -g '...'='../..'
alias -g '....'='../../..'
but when I type, for example, cd .../some<tab> it won't expand to cd .../something or cd ../../something. Consequently, I frequently won't use these handy aliases because they are incompatible with tab completion.
I'm a user of Mikael Magnusson's rationalise-dot. From my zshrc:
# This was written entirely by Mikael Magnusson (Mikachu)
# Basically type '...' to get '../..' with successive .'s adding /..
function rationalise-dot {
local MATCH # keep the regex match from leaking to the environment
if [[ $LBUFFER =~ '(^|/| | |'$'\n''|\||;|&)\.\.$' ]]; then
LBUFFER+=/
zle self-insert
zle self-insert
else
zle self-insert
fi
}
zle -N rationalise-dot
bindkey . rationalise-dot
# without this, typing a . aborts incremental history search
bindkey -M isearch . self-insert
Try looking up zsh abbreviations. They allow you to enter an "abbreviation" which automatically gets replaced with its full form when you hit a magic key such as space. So you can create one which changes ...<SPACE> to ../...
For example, this is what you need in your profile:
typeset -A abbrevs
abbrevs=(
"..." "../.."
"...." "../../.."
)
#create aliases for the abbrevs too
for abbr in ${(k)abbrevs}; do
alias -g $abbr="${abbrevs[$abbr]}"
done
my-expand-abbrev() {
local MATCH
LBUFFER=${LBUFFER%%(#m)[_a-zA-Z0-9]#}
LBUFFER+=${abbrevs[$MATCH]:-$MATCH}
zle self-insert
}
bindkey " " my-expand-abbrev

Resources