Implementing autocompletion to zsh aliases - bash

I am using the awesome zsh framework oh-my-zsh. Every day I hit gc which is an alias for git commit I hit <Tab><Tab> and it gives me the correct files to commit. I am really interested to know how that is implemented, so I can implement my own idea of navigating to sub-directories using an alias.
I mean navigating into ~/workspace/a-repo using this alias -w a<Tab><Tab> which completes to a-repo and gives me some suggestions which are folders inside ~/workspace. I made this alias already but I need to know how to add the correct autocompletion/suggestion to it.
Here is my alias:
-w() { cd ~/workspace/"$*" }

In the case of gc (which I assume is defined as alias gc='git commit -v' in the git plugin of oh-my-zsh) zsh internally substitutes the alias (gc) with the actual command (git commit -v ) before looking for trying for completions. This works for every alias (unless the shell option COMPLETE_ALIASES is set, which would allow to set separate completions for aliases).
As for what you want to do: Seing that -w is actually a function and not an alias, you indeed would have to write your own completion. But there is a much simpler way to go about it: Static named directories.
Named directories are usually just the home directories of the users on the system. The most commonly known is probably ~ for the home directory of the current user. Other users directories are named ~USERNAME. You can also define your own static named directories. There are two ways to do this:
Use hash -d to explicitly define a name, similar to an alias:
hash -d w=~/workspace
Implicitly define it by defining a string shell parameter whose value begins with an / (this also means you cannot use ~ as shortcut for your home directory)
w="${HOME}/workspace"
In both cases you now can use ~w to reference your workspace, in the second case you can also use $w (for example for use in quoted strings). Typing cd ~w/a-repo will get you to ~/workspace/a-repo. Tab-completion works like it would with any other path, so pressing ~w/a<Tab> will present you ~w/a-repo as completion.
Additionally, if you set the shell option AUTO_CD (setopt AUTO_CD), you can switch into a directory just by typing the path (or directory name) without the need for cd.
% w="/tmp"
% cd ~w
% pwd
/tmp
% mkdir 1 2 3
% setopt AUTO_CD
% ~w/<TAB>
1 2 3
% ~w/1
% pwd
/tmp/1

Related

Change vim's working directory with DIRSTACK

I make heavy use of the DIRSTACK environment array in bash and often change directories with builtins like cd ~2 or cd ~4
How can I configure vim to utilize this functionality? I'd like to be able to change vim's working directory like I do in bash. I see that commands are ran in a subshell so just using !cd doesn't work.
Part of the problem is that bash does not actually export DIRSTACK. A second problem is that I cannot find any way to export array shell variables. A third problem is that Vim does not seem to know the array variable syntax.
However, I just found a way to get around all this using a shell alias. This is not an elegant solution, but I tested it and it successfully exposes DIRSTACK to the Vim instance called via the alias:
alias dirsvim='env D0=${DIRSTACK[0]} D1=${DIRSTACK[1]} D2=${DIRSTACK[2]} D3=${DIRSTACK[3]} vim'
You can extend this to the number of directories you want to support from DIRSTACK.
In Vim, you can then do :cd $D1 to cd to the second directory in DIRSTACK.
If DIRSTACK has two directories, $D2 and $D3 are empty strings. This is not super friendly, because cd $D3 will give you an error message, but it's not too bad since it just stays in the directory where it was.

Navigating to directory in ZSH (bash)

I'm using Oh-My-ZSH to create some ailises and functions for easing my repetitive work load.
I need to navigate from anywhere in my computer to my Frontend directory. This is what I have:
frontend(){
cd ~/Desktop/Work/Frontend
cd $1
}
Now this works well when I type frontend or frontend myProject, however, all my project folders are postfixed by something like .m, .tablet, etc.
How can I write things that:
Will let me automatically navigate to a folder that is followed by .something
When there are multiple options (like project.m and project.tablet) will prompt me with options similar to if you hit tab in your terminal and are given multiple options for autocomplete.
I hope my question makes sense.
Thanks.
Find a zsh solution first, followed by a bash solution.
Update: Turns out that a zsh implementation (based on builtin compctl) is much simpler than the bash implementation (based on builtin complete).
Save the code of interest to a file (e.g., frontend) and source it (e.g., . ./frontend); either interactively or, preferably, from your bash/zsh profile.
Once in place, auto-completion of subdirectory names in ~/Desktop/Work/Frontend will work as follows:
Type, for instance, frontend myProject and press TAB.
myProject is then prefix-matched against the names of the subdirectories in ~/Desktop/Work/Frontend:
If there's only 1 match, myProject will instantly expand to the full subdirectory name.
Otherwise, a beep sounds to indicate that there are multiple matches:
zsh: The names of all matching subdirectories are listed right away.
bash: Press TAB again to list the names of all matching subdirectories
Continue typing until the prefix match is unambiguous, then press TAB again.
Note: In bash, to also only require pressing TAB once to list multiple matches, add the following to your shell profile bind "set show-all-if-ambiguous on".
zsh solution:
# Define the shell function.
frontend(){
cd ~/Desktop/Work/Frontend/"${1:-}"
}
# Tell zsh to autocomplete directory names in the same directory as
# the function's when typing a command based on the shell function.
compctl -/ -W ~/Desktop/Work/Frontend frontend
bash solution:
Note: complete -o dirnames doesn't take an argument, unfortunately - it always auto-completes for the current directory. Thus, a custom shell function that returns the potential matches, combined with -o filenames, is required.
# Define the main shell function.
frontend(){
local BASEDIR=~/Desktop/Work/Frontend
cd "$BASEDIR/${1:-}"
}
# Define the custom completion function.
_frontend_completions() {
local BASEDIR=~/Desktop/Work/Frontend
# Initialize the array variable through which
# completions must be passed out.
COMPREPLY=()
# Find all matching directories in the base folder that start
# with the name prefix typed so far and return them.
for f in "$BASEDIR/${COMP_WORDS[COMP_CWORD]}"*; do
[[ -d $f ]] && COMPREPLY+=( "$(basename "$f")" )
done
}
# Tell bash to autocomplete directory names as returned by the
# _frontend_completions() helper functoin when typing a command
# based on the main shell function.
complete -o filenames -F _frontend_completions frontend fe
I strongly recommend you use AutoJump
But if you must, maybe you want to use alias
like in your ~/.zshrc add:
alias fend='cd path/to/frontend'

Bash: cd using keyword expressions

Let's take the following directory listing as an example:
folder-1-one
folder-2-two
folder-3-three
Let's further assume, I want to cd into folder-1-one.
I could use tab completions but this becomes tedious if I have plenty of very similar folder names.
Instead I would like to use some sort of keyword expression. In the example case, 1 and one is unique to the folder I want to access.
Thus I am looking for something like cd 1 or cd one to quickly change into the desired directory without having to pay attention at what point the 1 or the one occur in the file name.
What would be a good solution for that use case?
You could use bash aliases to hardcode the directories you change to freqeuntly:
alias one='cd folder-1-one'
alias two='cd folder-2-two'
Alternatively you could look into using zsh, which supports fuzzy completion via 'oh-my-zsh'. Similar facilities for bash exist (although I can't vouch for them) - such as this one.
You can just use wildcards
cd *1*
cd *2*
cd *three
You can do:
cd *one
cd *two
etc.. but keep in mind that if there is another dir-one then you will get ambiguous warning.

why is "noclobber" appearing in bash command arguments

There are a bunch of folders in ~/path/ with names that begin with prefix, and I need to access them very frequently. I'm trying to create a shortcut in .bashrc that will make "prefix example" run the command "cd ~/path/prefixexample".
I encountered the same issue with both functions and aliases:
function prefix(){ "cd ~/path/prefix$1"; }
alias prefix="cd ~/path/prefix$1"
When I type "prefix 4" (the folder path/prefix4 does exist), I get:
bash: cd: path/prefixnoclobber: No such file or directory
I don't have admin privileges on this machine, so I can't change some things.
.bashrc already contains a bunch of stuff, but the only relevant thing seems to be "set noclobber". I'm pretty sure replacing arguments with the string "noclobber" is not part of the functionality of the noclobber switch, and commenting this switch out had no effect.
The $1 in the alias command is being replaced with whatever the first argument to the setup script happens to be (apparently, "noclobber"), rather than the argument to the alias/function. You need to do two things:
Get rid of the alias. Aliases aren't flexible enough to do what you want, but an alias will override anything else with the same name (and hence interfere with a better solution).
Use a function:
prefix() { cd ~/path/prefix"$1"; }

Directory based environment variable scope - how to implement?

I have a set of tools which I need to pass parameters depending on the project I'm working on. I'd like to be able to automatically set a couple of environment variables based on the current directory. So when I switched between directories, my commonly used env vars would also change. Example:
Let's current directory is foo, thus if I do:
~/foo$ ./myscript --var1=$VAR1
VAR1 would have some foo based value.
Then, let's say I switched to bar directory. If I do:
~/bar$ ./myscript --var1=$VAR1
VAR1 should now have some bar based value.
Is that possible? How?
the ondir program lets you specify actions to run when you enter and leave directories in a terminal
There is direnv which helps you do this stuff much easily and in an elegant way. Just define a .envrc file in your project directory with all the env variables needed and it will source it once you cd into that folder.
I've written another implementation of this, which is somewhat similar to ondir. I didn't actually know about ondir when I started working on it. There are some key differences that may be useful, however.
smartcd is written entirely in shell, and is fully compatible with bash and zsh, even the more esoteric options
smartcd will run scripts all the way down and up the directory hierarchy down to their common ancestor, not just for the two directories you're entering and leaving. This means you can have a ~/foo script that will execute whether you "cd ~/foo" or "cd ~/foo/bar"
it has "variable stashing" which is a more automatic way of dealing with your environment variables, whereas ondir requires you to explicitly and manually remove and/or reset your variables
smartcd can work with "autocd" turned on by hooking your prompt command (PROMPT_COMMAND in bash, precmd in zsh)
You can find smartcd at https://github.com/cxreg/smartcd
This is not something that is directly supported with the built-in features of bash or any other common shell. However, you can create your own "cd" command that will do whatever you want. For example, you could alias cd to do the cd and then run a special script (eg: ~/bin/oncd). That script could look up the new directory in a database and run some commands, or see if there's a special file (eg: .env) in the directory and load it, etc.
I do this sort of thing a lot. I create several identically named batch files in directories where I need them that only set the variables and call the common script. I even have a batch file that creates the other small files.
This is not pretty, but you can use a combination of exported environment variables and the value of $PWD.
For example:
export VAR1=prefix
export prefix${HOME////_}_foo=42
export prefix${HOME////_}_bar=blah
Then myscript needs only to eval echo \${$VAR1${PWD////_}} to get at the directory based value.
How about wrap your script with a function (the function can be placed either in your bash profile/bashrc file in the system ones to make available for all the users ).
myscript () { case $PWD in
/path/to/foo) path/to/myscript --var1=$VAR1 ;;
/path/to/bar) path/to/myscript --var2=$VAR1 ;;
*) ;;
case
}
Hence the function myscript will call the real "myscript" knowing what to do based on the current working directory.
Take this as an example:
hmontoliu#ulises:/tmp$ myscript () { case $PWD in /tmp) echo I\'m in tmp;; /var) echo I\'m in var;; *) echo I\'m neither in tmp nor in bar; esac; }
hmontoliu#ulises:/tmp$ myscript
I'm in tmp
hmontoliu#ulises:/tmp$ cd /var
hmontoliu#ulises:/var$ myscript
I'm in var
hmontoliu#ulises:/var$ cd /etc
hmontoliu#ulises:/etc$ myscript
I'm neither in tmp nor in bar

Resources