about summary refs log tree commit diff
path: root/Functions/Chpwd/zsh_directory_name_generic
blob: aa4bf9b84c6deaef9b414cef9a51c988b8fe0cfb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
## zsh_directory_name_generic
#
# This function is useful as a hook function for the zsh_directory_name
# facility.
#
# See the zsh-contrib manual page for more.

emulate -L zsh
setopt extendedglob
local -a match mbegin mend

# The variable containing the top level mapping.
local _zdn_topvar

zmodload -i zsh/parameter
zstyle -s ":zdn:${funcstack[2]}:" mapping _zdn_topvar || _zdn_topvar=zdn_top

if (( ! ${(P)#_zdn_topvar} )); then
  print -r -- "$0: $_zdn_topvar is not set" >&2
  return 1
fi

local _zdn_var=$_zdn_topvar
local -A _zdn_assoc

if [[ $1 = n ]]; then
  # Turning a name into a directory.
  local _zdn_name=$2
  local -a _zdn_words
  local _zdn_dir _zdn_cpt

  _zdn_words=(${(s.:.)_zdn_name})
  while (( ${#_zdn_words} )); do
    if [[ -z ${_zdn_var} ]]; then
      print -r -- "$0: too many components in directory name \`$_zdn_name'" >&2
      return 1
    fi

    # Subscripting (P)_zdn_var directly seems not to work.
    _zdn_assoc=(${(Pkv)_zdn_var})
    _zdn_cpt=${_zdn_assoc[${_zdn_words[1]}]}
    shift _zdn_words

    if [[ -z $_zdn_cpt ]]; then
      # If top level component, just try another expansion
      if [[ $_zdn_var != $_zdn_topvar ]]; then
	# Committed to this expansion, so report failure.
	print -r -- "$0: no expansion for directory name \`$_zdn_name'" >&2
      fi
      return 1
    fi
    if [[ $_zdn_cpt = (#b)(*)/:([[:IDENT:]]##) ]]; then
      _zdn_cpt=$match[1]
      _zdn_var=$match[2]
    else
      # may be empty
      _zdn_var=${${_zdn_assoc[:default:]}##*/:}
    fi
    _zdn_dir=${_zdn_dir:+$_zdn_dir/}$_zdn_cpt
  done
  if (( ${#_zdn_dir} )); then
    typeset -ag reply
    reply=($_zdn_dir)
    return 0
  fi
elif [[ $1 = d ]]; then
  # Turning a directory into a name.
  local _zdn_dir=$2
  local _zdn_rest=$_zdn_dir
  local -a _zdn_cpts
  local _zdn_pref _zdn_pref_raw _zdn_matched _zdn_cpt _zdn_name

  while [[ -n $_zdn_var && -n $_zdn_rest ]]; do
    _zdn_assoc=(${(Pkv)_zdn_var})
    # Sorting in descending order will ensure prefixes
    # come after longer strings with that perfix, so
    # we match more specific directory names preferentially.
    _zdn_cpts=(${(Ov)_zdn_assoc})
    _zdn_cpt=''
    for _zdn_pref_raw in $_zdn_cpts; do
      _zdn_pref=${_zdn_pref_raw%/:*}
      [[ -z $_zdn_pref ]] && continue
      if [[ $_zdn_rest = $_zdn_pref(#b)(/|)(*) ]]; then
	_zdn_cpt=${(k)_zdn_assoc[(r)$_zdn_pref_raw]}
	# if we matched a /, too, add it...
	_zdn_matched+=$_zdn_pref$match[1]
	_zdn_rest=$match[2]
	break
      fi
    done
    if [[ -n $_zdn_cpt ]]; then
      _zdn_name+=${_zdn_name:+${_zdh_name}:}$_zdn_cpt
      if [[ ${_zdn_assoc[$_zdn_cpt]} = (#b)*/:([[:IDENT:]]##) ]]; then
	_zdn_var=$match[1]
      else
	_zdn_var=${${_zdn_assoc[:default:]}##*/:}
      fi
    else
      break
    fi
  done
  if [[ -n $_zdn_name ]]; then
    # matched something, so report that.
    integer _zdn_len=${#_zdn_matched}
    [[ $_zdn_matched[-1] = / ]] && (( _zdn_len-- ))
    typeset -ag reply
    reply=($_zdn_name $_zdn_len)
    return 0
  fi
  # else let someone else have a go.
elif [[ $1 = c ]]; then
  # Completion

  if [[ -n $SUFFIX ]]; then
    _message "Can't complete in the middle of a dynamic directory name"
  else
    local -a _zdn_cpts
    local _zdn_word _zdn_cpt _zdn_desc _zdn_sofar expl

    while [[ -n ${_zdn_var} && ${PREFIX} = (#b)([^:]##):* ]]; do
      _zdn_word=$match[1]
      compset -P '[^:]##:'
      _zdn_assoc=(${(Pkv)_zdn_var})
      _zdn_cpt=${_zdn_assoc[$_zdn_word]}
      # We only complete at the end so must match here
      [[ -z $_zdn_cpt ]] && return 1
      if [[ $_zdn_cpt = (#b)(*)/:([[:IDENT:]]##) ]]; then
	_zdn_cpt=$match[1]
	_zdn_var=$match[2]
      else
	_zdn_var=${${_zdn_assoc[:default:]}##*/:}
      fi
      _zdn_sofar+=${_zdn_sofar:+${_zdn_sofar}/}$_zdn_cpt
    done
    if [[ -n $_zdn_var ]]; then
      _zdn_assoc=(${(Pkv)_zdn_var})
      local -a _zdn_cpts
      for _zdn_cpt _zdn_desc in ${(kv)_zdn_assoc}; do
	[[ $_zdn_cpt = :* ]] && continue
	_zdn_cpts+=(${_zdn_cpt}:${_zdn_desc%/:[[:IDENT:]]##})
      done
      _describe -t dirnames "directory name under ${_zdn_sofar%%/}" \
	_zdn_cpts -S: -r ':]'
      return
    fi
  fi
fi

# Failed
return 1
## end