Insert first word of previous command in ZSH command line - zsh

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.

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.

Recover an interrupted command in 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.

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

Inserting a newline in a multiline zsh command pulled from history

Sometimes I use multiline commands in zsh:
❯ echo \
> a \
> multiline \
> command
When editing the command after pulling it from a history search, I can change the content of individual lines. However, I can't figure out how to insert another line:
# I want to insert another line after "multiline"...
❯ echo \
> a \
> multiline \ # but hitting <return> here just runs the command, even though there's a backslash at the end of the line
> command
How can I insert a newline in the middle of a multiline command pulled from history?
You can use ESC-Return.
FWIW, I tested it on Debian Jessie, zsh 5.0.7 and it works there.
You can use self-insert-unmeta to bind Alt+Return to insert a literal newline without accepting the command:
bindkey '^[^M' self-insert-unmeta
To use your example: Hitting Alt+Return at the cursor position (#)
% echo \
a \
multiline \#
command
will get you this:
% echo \
a \
multiline \
#
command
This works not only when editing history, but also when typing commands. So you can prepare several commands in a script like fashion and accept them with a single Return.
For example pressing Alt+Return instead of # in this example:
% echo command 1#
echo command 2#
echo command 3
will do the same thing as the command echo command 1; echo command 2; echo command 3 and produce this output:
command 1
command 2
command 3
(A summary of answers from https://unix.stackexchange.com/questions/6620/how-to-edit-command-line-in-full-screen-editor-in-zsh)
zsh comes with a function that can be used to open the current command line in your favorite editor. Add the following lines to your .zshrc:
autoload -z edit-command-line
zle -N edit-command-line
bindkey "^X^E" edit-command-line
The first line loads the function. The second line creates a new widget for the Z shell line editor (zle) from the function of the same name. The third line binds the widget to Control-X Control-E. If you use the vi bindings rather than emacs key bindings, use something like
bindkey -M vicmd v edit-command-line
instead (which binds the widget to v in vicmd mode).
If using bindkey -v mode, you can also use the default o/O commands from vicmd mode to add a newline line and enter insert mode in it, respectively above or below the current line.
Just to note, if you want to comment in a multiline command, you could use:
❯ echo `#first comment` \
a `#second comment` \
multiline \
command
CTRL + Enter (Return) for Windows/WSL
CTRL +X CTRL+E for Mac
Edited as per the comment below
Sounds like an appropriate place to use a shell script file instead no?
#!/bin/zsh
my
commands
here
I can even add a new line at a later time.

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