16

All shells understand these commands:

$ cd .
$ cd ..

And zsh will also understand:

$ cd ...
$ cd ....

Provided you say:

$ alias -g ...='../..'
$ alias -g ....='../../..'

Now, how can I make it do proper tab-completion when I've started typing cd ..../<TAB>? I recall it was implemented in oh-my-zsh but I've stopped using it now.

It would also be appreciated if it would work not only for cd, say I want to execute cat ..../a/b/..../c/d | less.

paulmelnikow
  • 16,895
  • 8
  • 63
  • 114
Tarrasch
  • 10,199
  • 6
  • 41
  • 57

4 Answers4

10

I wasn't happy with the other answers so I spent a bit of time getting something more to my liking. The following will expand the dots when you hit (return) or (tab), not as you type the dots.

function expand-dots() {
    local MATCH
    if [[ $LBUFFER =~ '(^| )\.\.\.+' ]]; then
        LBUFFER=$LBUFFER:fs%\.\.\.%../..%
    fi
}

function expand-dots-then-expand-or-complete() {
    zle expand-dots
    zle expand-or-complete
}

function expand-dots-then-accept-line() {
    zle expand-dots
    zle accept-line
}

zle -N expand-dots
zle -N expand-dots-then-expand-or-complete
zle -N expand-dots-then-accept-line
bindkey '^I' expand-dots-then-expand-or-complete
bindkey '^M' expand-dots-then-accept-line
Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
  • 1
    do you mind if I repoify the code in this answer into a zsh-package? – Tarrasch Jan 03 '17 at 09:20
  • @Tarrasch, sure, go ahead. – Paul Ruane Jan 03 '17 at 10:54
  • 1
    Thanks! I [modified it slightly](https://github.com/parkercoates/dotfiles/blob/master/.zsh/expand-multiple-dots.zsh#L5) to have it only apply when the `...` comes at the beginning of the line or after a space. This keeps it from breaking `git` or `p4` commands, at the cost of it not working in the middle of a path, but I never type things like `cd a/b/..../y/z` so I don't mind. – Parker Coates Jan 04 '17 at 15:00
7

What I did to to deal with the same problem is to just let zsh fill in ../.. when I type ... and it makes sense to expand it in that way. It may suit you (or not :-P):

if is-at-least 5.0.0 && [[ ! $UID -eq 0 ]]; then                                                                                                                             
  ## http://www.zsh.org/mla/users/2010/msg00769.html                                                                                                                       
  function rationalise-dot() {                                                                                                                                             
    local MATCH # keep the regex match from leaking to the environment                                                                                                   
    if [[ $LBUFFER =~ '(^|/| |      |'$'\n''|\||;|&)\.\.$' && ! $LBUFFER = p4* ]]; then                                                                                  
        #if [[ ! $LBUFFER = p4* && $LBUFFER = *.. ]]; then                                                                                                               
        LBUFFER+=/..                                                                                                                                                     
    else                                                                                                                                                                 
        zle self-insert                                                                                                                                                  
    fi                                                                                                                                                                   
}                                                                                                                                                                        
zle -N rationalise-dot                                                                                                                                                   
bindkey . rationalise-dot                                                                                                                                                
bindkey -M isearch . self-insert                                                                                                                                         
fi

I also have an alias for ..., but it is not global.

Notice I check if the command line starts with p4 (the Perforce command line tool) and do not mess with it in that case, as Perforce arguments often involve literal .... If you do not use p4 you can obviously remove that check.

Francisco
  • 3,980
  • 1
  • 23
  • 27
  • This is a useful trick, but not exactly what I was looking for. :) – Tarrasch May 06 '14 at 18:42
  • Here is an improved version, which also works for aliases that use `p4` or `git`: https://github.com/blueyed/oh-my-zsh/blob/master/functions/rationalise-dot – blueyed Apr 17 '15 at 15:13
5

A good option is manydots-magic, which expands ... into ../.., etc. but does it intelligently. See the link above for more details, but briefly:

  • It allows you to revert the expansion with a single Backspace (if it were the last thing you typed).
    • But it won't revert explicitly typed ../..
  • You can use it inline, e.g. cd a/b/..../y/z.
  • Nevertheless, it won't expand when it doesn't make sense, e.g. git log branch...
  • It will expand when it might make sense, but revert when you type more. e.g.
    • git diff ... -> git diff ../..
    • git diff ...b -> git diff ...b (for git diff ...branch)
Sparhawk
  • 1,581
  • 1
  • 19
  • 29
2

You have to use compinit and use _expand_alias as completer. Here is an example:

zstyle ':completion:*' completer _complete _ignored _expand_alias
autoload -Uz compinit
compinit

_complete _ignored is the default setting for completer, you could set it to only _expand_alias but then completion would only work for aliases.

If compinit is already configured in your ~/.zshrc, then you just need to add _expand_alias into the list for completer, for example:

zstyle ':completion:*' completer _expand _complete _ignored _approximate _expand_alias

By default _expand_alias expands global and and regular aliases, if you do not want to expand regular aliases, set:

zstyle ':completion:*' regular false

Note: This of course works only, where global aliases would work. So they would not be expanded as part of an entire path like a/b/..../c/d

Adaephon
  • 16,929
  • 1
  • 54
  • 71
  • I couldn't get `cd .../` to work after following these steps: (1) remove `.zshrc`, (2) open zsh, (3) paste in those 3 consecutive lines from your answer, (4) `alias -g ...='../..'`. Still `cd .../` is ignoring the tab-key. Is this working on your machine? Am I missing a step? – Tarrasch May 04 '14 at 21:43
  • @Tarrasch If you are pressing ** after `.../` it will not work if you define `...` as the alias, as the `/` is not part of the alias. Either press ** directly after `...` or just define `alias -g .../='../..'`, too. – Adaephon May 05 '14 at 12:08
  • What I get is that ``-key expands `.../` to `../../`, which is not what I want. I want it to *stay* as `.../` but bring up completion options as if I had typed `../../`. I'm quite sure omz had this behavior ... – Tarrasch May 06 '14 at 18:41
  • I'm unsure how this would work, as global aliases are not expanded - or even recognized as such - inside words. That means, even if you have `...` mapped as above, `/a/b/.../c` will not land you in `/c`. – Adaephon May 13 '14 at 13:47