eumiro
eumiro

Reputation: 212845

Collapse directories in zsh prompt in a unique way

Is there a way to collapse the current working directory in the zsh prompt in a unique way, so that I could copy and paste it to another terminal, hit TAB and get the original path?

Let's say we have following directories:

/adam/devl
/alice/devl
/alice/docs
/bob/docs

If the prompt is programmed to show the first characters, and I'm in /b/d, then it is unique. On the other hand, /a/d is not unique, so I would need /ad/d, /al/de and /al/do. And even /ali/… as soon as the user alex appears.

Is it possible to hack this directly in zsh or do I need to write a script, that finds the shortest unique beginning of each parent directory?

Thank you for your ideas!

Upvotes: 10

Views: 2356

Answers (2)

Micah W
Micah W

Reputation: 378

Yes it is possible to collapse the directories to a first-unique-letter path and have the Z Shell expand that path upon pressing [Tab]. I simply used compinstall (zsh utility script installed with Zsh) to generate the following code. The important part to take note of for expanding path elements is on the sixth zstyle command, near the end, where the bracket of characters to separate completion points with includes the /, which is, of course, the directory separator. With this, the unique paths you suggested will fully fill out with just one [Tab] press as if the * were at the end of each path-name unique letters.

# The following lines were added by compinstall

zstyle ':completion:*' add-space true
zstyle ':completion:*' completer _list _expand _complete _ignored _match _correct _approximate _prefix
zstyle ':completion:*' completions 1
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|[._-]=* r:|=*' 'm:{[:lower:]}={[:upper:]} m:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._-]=* r:|=*' 'r:|[._-/]=* r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' match-original both
zstyle :compinstall filename '/home/micah/.zsh/.zshrc'

autoload -Uz compinit
compinit
# End of lines added by compinstall

As for creating the unique path in the first place and inserting it into the prompt, it is possible with a zsh script or function, and therefore should be possible for the completer or line-editor also, but just sticking to the prompt, you would add a function to the $precmd_functions array that modifies or adds to the PS1 variable. This special array is a list of function names that are run right before each prompt.

function precmd_unique_pwd {
  local pwd_string="$(upwd)"
  PS1="%B%n@%m $pwd_string => %b"
}
precmd_functions+=( precmd_unique_pwd )

For getting of the current PWD in shortened form, I think this function is clear and easy to follow, though not necessarily optimized for low resource usage.

#!/bin/zsh
function upwd {
        emulate -LR zsh -o nullglob
        local dir Path
        local -a newpwd tmp stack
        local -i length=1 Flag=0
        newpwd=( ${(s./.)PWD} )
        foreach dir ( $newpwd )
                (( length=0, Flag=0 ))
                repeat $#dir
                do
                        (( length += 1 ))
                        tmp=( ${(j.*/.)~stack}/$dir[1,$length]*(/) )
                        if
                                (( $#tmp == 1 ))
                        then
                                Path=$Path/$dir[1,$length]
                                stack+=( /$dir )
                                (( Flag=1 ))
                                break
                        fi
                done
                if
                        (( Flag ))
                then
                        continue
                else
                        Path=$Path/$dir
                fi
        end
        print -- $Path
}
upwd

Notice that it finds unique paths with directory names because of the Zsh feature (/) at the end of the globbing. On the last directory (current) this means you may match something else if there is a file with the same name plus an extension.

Upvotes: 0

Adaline Simonian
Adaline Simonian

Reputation: 4808

I'm not aware that zsh has a built-in function of this sort, but it should be pretty easy to script without resorting to a single subshell or slow pipe:

#!/bin/zsh

paths=(${(s:/:)PWD})

cur_path='/'
cur_short_path='/'
for directory in ${paths[@]}
do
  cur_dir=''
  for (( i=0; i<${#directory}; i++ )); do
    cur_dir+="${directory:$i:1}"
    matching=("$cur_path"/"$cur_dir"*/)
    if [[ ${#matching[@]} -eq 1 ]]; then
      break
    fi
  done
  cur_short_path+="$cur_dir/"
  cur_path+="$directory/"
done

printf %q "${cur_short_path: : -1}"
echo

This script will output the shortest path needed for auto-completion to work.

You can throw it in your .zshrc as a function and then run it from any directory.

function spwd {
  paths=(${(s:/:)PWD})

  cur_path='/'
  cur_short_path='/'
  for directory in ${paths[@]}
  do
    cur_dir=''
    for (( i=0; i<${#directory}; i++ )); do
      cur_dir+="${directory:$i:1}"
      matching=("$cur_path"/"$cur_dir"*/)
      if [[ ${#matching[@]} -eq 1 ]]; then
        break
      fi
    done
    cur_short_path+="$cur_dir/"
    cur_path+="$directory/"
  done

  printf %q "${cur_short_path: : -1}"
  echo
}

Here's a video of it in action:

https://asciinema.org/a/0TyL8foqvQ8ec5ZHS3c1mn5LH

Or if you prefer, some sample output:

~/t $ ls
adam  alice  bob  getshortcwd.zsh

~/t $ ls adam
devl

~/t $ ls alice
devl  docs

~/t $ spwd
/h/v/t

~/t $ cd adam/devl

~/t/adam/devl $ spwd
/h/v/t/ad/d

~/t/adam/devl $ cd ../../alice/devl

~/t/alice/devl $ spwd
/h/v/t/al/de

~/t/alice/devl $ cd ../docs

~/t/alice/docs $ spwd
/h/v/t/al/do

~/t/alice/docs $ `spwd` [TAB]
~/t/alice/docs $ /h/v/t/al/do [TAB]
~/t/alice/docs $ /home/vsimonian/t/alice/docs

Upvotes: 12

Related Questions