I am currently switching from csh to zsh I am writing a .zshrc trying to get all the options I am used to in this new shell.
I use autocd (to go into a directory just typing its name (without the cd command), and I wonder if it is possible that my first propose all the files existing in the current directory (like it's working in csh).
I am quite used to this way of having an overview of the files I can open or directory I can "autocd" into, before typing my command just pressing without anything written in my commandline yet.
Right now when I first press it does not trigger any completion mechanism but it just write an actual tabulation.
I did not find any solution yet, and if anyone has any magic options to get this results, feel free to enlighten me!
Thanks
I found a way!
No need for autocd, though this option exists in zsh
To put in your ~/.zshrc:
first-tab() {
if [[ $#BUFFER == 0 ]]; then
BUFFER="cd "
CURSOR=3
zle list-choices
else
zle expand-or-complete
fi
}
zle -N first-tab
bindkey '^I' first-tab
Thanks to this question: zsh tab completion on empty line
So press tab once and you will get "cd " and the existing directories.
Checkout man zshoptions for other existing options which could be useful
(setopt menucomplete could be useful to save a tab, but change behaviour for other completion as well.)
Here is another more complex variation.
It will list files on on an empty command line, and in the middle of any command.
It will list directories on on an empty command line.
It will list executables on on an empty command line.
It can be configured to prepend "cd " or "./" in those cases with a global variable.
export TAB_LIST_FILES_PREFIX
# List files in zsh with <TAB>
#
# Copyleft 2017 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com>
# GPL licensed (see end of file) * Use at your own risk!
#
# Usage:
# In the middle of the command line:
# (command being typed)<TAB>(resume typing)
#
# At the beginning of the command line:
# <SPACE><TAB>
# <SPACE><SPACE><TAB>
#
# Notes:
# This does not affect other completions
# If you want 'cd ' or './' to be prepended, write in your .zshrc 'export TAB_LIST_FILES_PREFIX'
# I recommend to complement this with push-line-or edit (bindkey '^q' push-line-or-edit)
function tab_list_files
{
if [[ $#BUFFER == 0 ]]; then
BUFFER="ls "
CURSOR=3
zle list-choices
zle backward-kill-word
elif [[ $BUFFER =~ ^[[:space:]][[:space:]].*$ ]]; then
BUFFER="./"
CURSOR=2
zle list-choices
[ -z ${TAB_LIST_FILES_PREFIX+x} ] && BUFFER=" " CURSOR=2
elif [[ $BUFFER =~ ^[[:space:]]*$ ]]; then
BUFFER="cd "
CURSOR=3
zle list-choices
[ -z ${TAB_LIST_FILES_PREFIX+x} ] && BUFFER=" " CURSOR=1
else
BUFFER_=$BUFFER
CURSOR_=$CURSOR
zle expand-or-complete || zle expand-or-complete || {
BUFFER="ls "
CURSOR=3
zle list-choices
BUFFER=$BUFFER_
CURSOR=$CURSOR_
}
fi
}
zle -N tab_list_files
bindkey '^I' tab_list_files
# uncomment the following line to prefix 'cd ' and './'
# when listing dirs and executables respectively
#export TAB_LIST_FILES_PREFIX
# these two lines are usually included by oh-my-zsh, but just in case
autoload -Uz compinit
compinit
# uncomment the following line to complement tab_list_files with ^q
#bindkey '^q' push-line-or-edit
# License
#
# This script is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This script is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this script; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA 02111-1307 USA
More details in this post
Related
I noticed that tab completion for the source command in Zsh tries to complete a LOT of files. Maybe everything in $PATH? I tried using a blank .zshrc file to make sure it wasn't anything in there.
ubuntu% source d
zsh: do you wish to see all 109 possibilities (16 lines)?
I did find this file that seems to control that: /usr/share/zsh/functions/Completion/Zsh/_source
#compdef source .
if [[ CURRENT -ge 3 ]]; then
compset -n 2
_normal
else
if [[ -prefix */ && ! -o pathdirs ]]; then
_files
elif [[ $service = . ]]; then
_files -W path
else
_files -W "(. $path)"
fi
fi
If I change the line in that last "else" statement from _files -W "(. $path)" to _files, it works the way I want it to. The tab completion only looks at files & directories in the current dir.
It doesn't seem like altering this file is the best way to go. I'd rather change something in my .zshrc file. But my knowledge of Zsh completions is a bit lacking and the searching I've done thus far hasn't led me to an answer for this.
Maybe everything in $PATH?
Yes, that is correct. It offers those, because source will search your the current dir and your $PATH for any file name you pass it.
To apply your change without modifying the original file, add this to your .zshrc file after calling compinit:
compdef '
if [[ CURRENT -ge 3 ]]; then
compset -n 2
_normal
else
_files
fi
' source
This tells the completion system to use the inline function you specified for the command source (instead of the default function).
Alternatively, to see file completions for the current dir only, you can type
$ source ./<TAB>
I'm trying to write a completion script for Zsh. I'd like to unit test the completion script. For example, I'd like to test that completions for my-command --h include --help.
For Fish, I can use complete -C 'my-command --h', which would then output --help and any other valid completions.
I can't seem to find an equivalent command for Zsh. Does one exist? I've tried things like _main_complete, _complete and _normal, but either they don't support this or I'm not invoking them in the correct way (I get a lot of can only be called from completion function errors).
I get a lot of can only be called from completion function errors
This is because Zsh's completion commands can run only from inside a completion widget, which in turn can only be called while the Zsh Line Editor is active. We can work around this by activating a completion widget on an active command line inside a so-called pseudo terminal:
# Set up your completions as you would normally.
compdef _my-command my-command
_my-command () {
_arguments '--help[display help text]' # Just an example.
}
# Define our test function.
comptest () {
# Gather all matching completions in this array.
# -U discards duplicates.
typeset -aU completions=()
# Override the builtin compadd command.
compadd () {
# Gather all matching completions for this call in $reply.
# Note that this call overwrites the specified array.
# Therefore we cannot use $completions directly.
builtin compadd -O reply "$#"
completions+=("$reply[#]") # Collect them.
builtin compadd "$#" # Run the actual command.
}
# Bind a custom widget to TAB.
bindkey "^I" complete-word
zle -C {,,}complete-word
complete-word () {
# Make the completion system believe we're on a normal
# command line, not in vared.
unset 'compstate[vared]'
_main_complete "$#" # Generate completions.
# Print out our completions.
# Use of ^B and ^C as delimiters here is arbitrary.
# Just use something that won't normally be printed.
print -n $'\C-B'
print -nlr -- "$completions[#]" # Print one per line.
print -n $'\C-C'
exit
}
vared -c tmp
}
zmodload zsh/zpty # Load the pseudo terminal module.
zpty {,}comptest # Create a new pty and run our function in it.
# Simulate a command being typed, ending with TAB to get completions.
zpty -w comptest $'my-command --h\t'
# Read up to the first delimiter. Discard all of this.
zpty -r comptest REPLY $'*\C-B'
zpty -r comptest REPLY $'*\C-C' # Read up to the second delimiter.
# Print out the results.
print -r -- "${REPLY%$'\C-C'}" # Trim off the ^C, just in case.
zpty -d comptest # Delete the pty.
Running the example above will print out:
--help
If you want to test the entire completion output and not just the strings that would be inserted on the command line, then see https://unix.stackexchange.com/questions/668618/how-to-write-automated-tests-for-zsh-completion/668827#668827
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
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 has a feature (auto_cd) where just typing the directory name will automatically go to (cd) that directory. I'm curious if there would be a way to configure zsh to do something similar with file names, automatically open files with vim if I type only a file name?
There are three possibilities I can think of. First is suffix aliases which may automatically translate
% *.ps
to
% screen -d -m okular *.ps
after you do
alias -s ps='screen -d -m okular'
. But you need to define this alias for every file suffix. It is also processed before most expansions so if
% *.p?
matches same files as *.ps it won’t open anything.
Second is command_not_found handler:
function command_not_found_handler()
{
emulate -L zsh
for file in $# ; do test -e $file && xdg-open $file:A ; done
}
. But this does not work for absolute or relative paths, only for something that does not contain forward slashes.
Third is a hack overriding accept-line widget:
function xdg-open()
{
emulate -L zsh
for arg in $# ; do
command xdg-open $arg
endfor
}
function _-accept-line()
{
emulate -L zsh
FILE="${(z)BUFFER[1]}"
whence $FILE &>/dev/null || BUFFER="xdg-open $BUFFER"
zle .accept-line
}
zle -N accept-line _-accept-line
. The above alters the history (I can show how to avoid this) and is rather hackish. Good it does not disable suffix aliases (whence '*.ps' returns the value of the alias), I used to think it does. It does disable autocd though. I can avoid this (just || test -d $FILE after whence test), but who knows how many other things are getting corrupt as well. If you are fine with the first and second solutions better to use them.
I guess you can use "fasd_cd" which has an alias v which uses viminfo file to identifi files which you have opened at least once. In my environment it works like a charm.
Fast cd has other amazing stuff you will love!
Don't forget to set this alias on vim to open the last edited file:
alias lvim="vim -c \"normal '0\""