Recover an interrupted command in zsh - zsh

In zsh, if one accidentally interrupted a command (^C), is there a quick way to recover the full interrupted command line?
For example,
PROMPT $ this is a long command ^C
PROMPT $ [cursor here]
I would like to recover "this is a long command" at the cursor position.

One solution is to
zle-line-init () {
if [[ -n $ZLE_LINE_ABORTED ]]; then
local savebuf="$BUFFER" savecur="$CURSOR"
BUFFER="$ZLE_LINE_ABORTED"
CURSOR="$#BUFFER"
zle split-undo
BUFFER="$savebuf" CURSOR="$savecur"
fi
}
zle -N zle-line-init
Then, in the new input line, undo (C-/ in emacs mode) would give the aborted line.
reference: http://www.zsh.org/mla/users/2015/msg00652.html
I've added a more detailed explanation here: https://www.topbug.net/blog/2016/10/03/restore-the-previously-canceled-command-in-zsh/

It'll be in $ZLE_LINE_ABORTED.
You can bind a widget specifically to restore it. Or create an undo event for it in zle-line-init (using zle split-undo) so that it can be restored by pressing undo.

Related

Using bindkey to call a function in zsh requires pressing enter after function runs

I'm new to zsh and am trying to bind a key sequence to a function with the following in my .zshrc:
say_hello(){
echo "hello"
}
zle -N say_hello
bindkey '^Y' say_hello
Pressing Ctrl-Y will call the function and I'll see "hello" printed to the terminal but after I need to press Enter again before I'm given another zsh prompt. Calling the function by just typing in say_hello at the zsh prompt and pressing Enter does what I want - I see hello printed and then I'm given another zsh prompt. How can I get this behavior when binding the function to a key sequence?
Above is a simple example, really the function I'm trying to write is below:
my_cd() {
if [[ "$#" -ne 0 ]]; then
cd $(autojump $#)
return
fi
dir_to_cd_to=$(fasd_cd -dl | fzf --height 40% --reverse --inline-info)
# above isn't so important - dir_to_cd_to could be obtained in any way
cd "$dir_to_cd_to"
}
zle -N my_cd
bindkey -v '^Y' 'my_cd'
To display messages in a zle widget, you're supposed to use zle -M rather than echo. echo will output your message at whatever the current cursor position is which isn't especially helpful. If you really want to use echo, calling zle reset-prompt afterwards will redraw a fresh prompt. If you don't want a potential mess in your terminal, consider starting with \r to move the cursor to the beginning of the line and ending with $termcap[ce] to clear to the end of the line.

Insert first word of previous command in ZSH command line

In zsh (and bash), I can do ALT+. to insert the last argument of the previous command into the command line.
Is there something similar for the first word?
Example: After running /bin/ls -l /tmp, I want it to insert /bin/ls.
found this in a .zshrc
insert-first-word () { zle insert-last-word -- -1 1 }
zle -N insert-first-word
bindkey '^[_' insert-first-word
then alt + _
I found Alt+number+dot and Alt+comma in Zsh and Bash which basically answers my question:
In zsh, you can do ALT+0+.
It is a bit unwieldy to press three buttons for this, but it is built-in and works.

ZSH: Behavior on Enter

I realize, when I'm in my terminal, I would expect to press Enter on empty input to make a ls or a git status when I'm on a git repos.
How can I achieve that? I mean, have a custom behavior on Empty input -> Enter in zsh?
EDIT: Thanks for the help. Here's my take with preexec...
precmd() {
echo $0;
if ["${0}" -eq ""]; then
if [ -d .git ]; then
git status
else
ls
fi;
else
$1
fi;
}
On Enter zsh calls the accept-line widget, which causes the buffer to be executed as command.
You can write your own widget in order to implement the behaviour you want and rebind Enter:
my-accept-line () {
# check if the buffer does not contain any words
if [ ${#${(z)BUFFER}} -eq 0 ]; then
# put newline so that the output does not start next
# to the prompt
echo
# check if inside git repository
if git rev-parse --git-dir > /dev/null 2>&1 ; then
# if so, execute `git status'
git status
else
# else run `ls'
ls
fi
fi
# in any case run the `accept-line' widget
zle accept-line
}
# create a widget from `my-accept-line' with the same name
zle -N my-accept-line
# rebind Enter, usually this is `^M'
bindkey '^M' my-accept-line
While it would be sufficient to run zle accept-line only in cases where there actually was a command, zsh would not put a new prompt after the output. And while it is possible to redraw the prompt with zle redisplay, this will probably overwrite the last line(s) of the output if you are using multi-line prompts. (Of course there are workarounds for that, too, but nothing as simple as just using zle accept-line.
Warning: This redfines an (the most?) essential part of your shell. While there is nothing wrong with that per se (else I would not have posted it here), it has the very real chance to make your shell unusable if my-accept-line does not run flawlessly. For example, if zle accept-line were to be missing, you could not use Enter to confirm any command (e.g. to redefine my-accept-line or to start an editor). So please, test it before putting it into your ~/.zshrc.
Also, by default accept-line is bound to Ctrl+J, too. I would recommend to leave it that way, to have an easy way to run the default accept-line.
In my .zshrc I use a combination of precmd and preexec found here:
http://zsh.sourceforge.net/Doc/Release/Functions.html#Hook-Functions
I also find that the git-prompt is super useful:
https://github.com/olivierverdier/zsh-git-prompt

Use preexec() to evaluate entered command

I want to use preexec() to modify certain commands before they are run but I need to be able to evaluate the current entered command. Is there a variable that contains the entire command before it is executed? I know !! is the last command but I need the current line before it's saved to history.
An example of what I want to do would probably help
ls -l /root please
And then I want preexec to see I wrote "please" at the end and replace it with
sudo ls -l /root
I think something like
preexec() {
if [[ $CURRENT_LINE =~ please$ ]]; then
$CURRENT_LINE="sudo ${CURRENT_LINE% please}"
fi
Would work but I can't find a variable in zsh that gives me the correct $CURRENT_LINE
For bonus points I also want to be able to enter please on a line by itself and have it run sudo !! but I could probably do that with some form of alias.
I think it might be better to make a please function that I can pipe a command to but I don't think that'll work as well because the command will run and fail (before piping) before it is run again with sudo.
As far as I know that the preexec is not for the right place to modify the command to be executed though. We can not change the commands to be executed from inside of the preexec function…
Although the actual command to be executed are passed as $1, $2 and $3.
preexec
Executed just after a command has been read and is about to be executed. If the history mechanism is active (and the line was not discarded from the history buffer), the string that the user typed is passed as the first argument, otherwise it is an empty string. The actual command that will be executed (including expanded aliases) is passed in two different forms: the second argument is a single-line, size-limited version of the command (with things like function bodies elided); the third argument contains the full text that is being executed.
-- zshmisc(1) 9.3.1 Hook Functions
For example:
alias ls='ls -sF --color=auto'
preexec () {
print ">>>preexec<<<"
print -l ${(qqq)#}
}
If I have above in ~/.zshrc then I will get follows:
% echo test preexec<Esc-Return>
ls<Return>
;# outputs below
>>>preexec<<<
"echo test preexec
ls"
"echo test preexec; ls -sF --color=auto"
"echo test preexec
ls -sF --color=auto"
test preexec
total 1692
...
You could add your own zle widget functions to the zsh line editor for manipulating the line editor buffer. (zshzle(1))
You could add the zle widget function to change the behavior for hitting Enter.
my-accept-line () {
if [[ "$BUFFER" == *" please" ]]; then
BUFFER="sudo ${BUFFER% please}"
fi
zle .accept-line
}
zle -N accept-line my-accept-line
The above snippets changes the functionality for accept-line from the built-in behavior to my-accept-line defined here.
Adding the abbreviations also could help which is described below:
Cloning vim's abbreviation feature
-- “examples:zleiab [ZshWiki]” - http://zshwiki.org/home/examples/zleiab

zsh create a command line within a script but do not execute it

I'm wondering if it's possible to write a zsh script that will write a command to the prompt but NOT execute it, i.e. leave it there for me to edit and then execute when I'm ready. I can do something like this with keybindings by leaving off the final '\C-m'. eg:
bindkey -s "\e[1;3C" "howdy!"
... I press Alt+RightArrow and the text "howdy!" is printed at the prompt and just left there.
I can also do something like what I want by writing my command to the history file and then recalling it with the up arrow. I've tried 'echo -n sometext' but it doesn't work.
Can I write a script that would exit leaving (say) " howdy! " on the command line? In actual fact I want the script to build up a complex command based on several things, but I want the script to leave it on the CLI for final editing, so automatic execution must be prevented.
Thanks in advance.
Turns out the answer is simple:
print -z $string-to-print
If you mean a zsh function and not an external script, you can write a zle (short for zsh line editor) widget and bind it to some key.
# define to function to use
hello () {
BUFFER=hello
zle end-of-line
}
# create a zle widget, which will invoke the function.
zle -N hello
# bindkey Alt-a to that widget
bindkey "\ea" hello
You can learn more from A User's Guide to the Z-Shell, Chapter 4.

Resources