1212# Backup the original function
1313eval " function __bak_comp_compgen__call_builtin() { $( declare -f _comp_compgen__call_builtin | tail -n +2) }"
1414
15+ # Expand environment references ("$VAR", "${VAR}") inside completion prefixes
16+ __expand_env_refs () {
17+ local input=" $1 "
18+ # accumulate the expanded characters here
19+ local result=" "
20+ local len=${# input}
21+ local i=0
22+
23+ # Walk through every character
24+ while (( i < len )) ; do
25+ local ch=" ${input: i: 1} "
26+ # Preserve escaped characters verbatim
27+ # "\$HOME" stays "$HOME"
28+ if [[ " $ch " == " \\ " ]]; then
29+ (( i++ ))
30+ if (( i < len )) ; then
31+ result+=" ${input: i: 1} "
32+ (( i++ ))
33+ fi
34+ continue
35+ fi
36+
37+ # Handle environment references beginning with '$'
38+ if [[ " $ch " == ' $' ]]; then
39+ (( i++ ))
40+ if (( i >= len )) ; then
41+ result+=' $'
42+ break
43+ fi
44+
45+ ch=" ${input: i: 1} "
46+ # Expand braces style
47+ # "${VAR}"
48+ if [[ " $ch " == ' {' ]]; then
49+ (( i++ ))
50+ local start=$i
51+ # Consume [A-Za-z0-9_] until we hit '}' or something else
52+ while (( i < len )) ; do
53+ ch=" ${input: i: 1} "
54+ if [[ " $ch " =~ [A-Za-z0-9_] ]]; then
55+ (( i++ ))
56+ continue
57+ fi
58+ break
59+ done
60+ local var_name=" ${input: start: i-start} "
61+ if [[ -z " $var_name " ]]; then
62+ result+=' $'
63+ result+=' {'
64+ continue
65+ fi
66+ if (( i < len )) && [[ " ${input: i: 1} " == ' }' ]]; then
67+ (( i++ ))
68+ # ${VAR} -> ${!VAR-} returns value or empty string.
69+ result+=" ${! var_name-} "
70+ else
71+ result+=' $'
72+ result+=' {'
73+ i=$start
74+ fi
75+ continue
76+ fi
77+
78+ if [[ " $ch " =~ [A-Za-z_] ]]; then
79+ local start=$i
80+ while (( i < len )) && [[ " ${input: i: 1} " =~ [A-Za-z0-9_] ]]; do
81+ (( i++ ))
82+ done
83+ local var_name=" ${input: start: i-start} "
84+ result+=" ${! var_name-} "
85+ continue
86+ fi
87+
88+ # Handle positional parameters ($1)
89+ # or a few common special vars
90+ if [[ " $ch " =~ [0-9@* # ?] ]]; then
91+ local special_name= " $ch "
92+ (( i++ ))
93+ result+= " ${! special_name-} "
94+ continue
95+ fi
96+
97+ # Any other symbol
98+ result+= ' $'
99+ continue
100+ fi
101+
102+ # Normal characters are copied through.
103+ result+= " $ch "
104+ (( i++ ))
105+ done
106+
107+ printf ' %s' " $result "
108+ }
109+
15110
16111_comp_compgen__call_builtin () {
17112 __bak_comp_compgen__call_builtin " $@ "
@@ -85,6 +180,18 @@ _add_completion() {
85180 fi
86181
87182 [[ " $dirpart " == " ." && " ${cur: 0: 2} " != " ./" ]] && dirpart=" "
183+
184+ # Expand environemnt variables
185+ # dirpart_lookup: save the true path after expanded
186+ # NOTE: in the end, the path prefix will be rollbacked to "snapshot".
187+ local dirpart_lookup=" $dirpart "
188+ if [[ -n " $dirpart_lookup " && " $dirpart_lookup " == * ' $' * ]]; then
189+ local expanded_lookup
190+ expanded_lookup=" $( __expand_env_refs " $dirpart_lookup " ) "
191+ if [[ -n " $expanded_lookup " ]]; then
192+ dirpart_lookup=" $expanded_lookup "
193+ fi
194+ fi
88195
89196 local savedPWD=" $PWD "
90197 local resolved_dir
@@ -99,10 +206,10 @@ _add_completion() {
99206 fi
100207 done
101208
102- if [[ -n " $dirpart " ]]; then
209+ if [[ -n " $dirpart_lookup " ]]; then
103210 # Resolve the working directory for compgen use realpath, but remember
104211 # the original textual prefix so completions can stay aligned with what the user typed.
105- resolved_dir=" $( realpath -- " $dirpart " 2> /dev/null) "
212+ resolved_dir=" $( realpath -- " $dirpart_lookup " 2> /dev/null) "
106213 if [[ -d " $resolved_dir " ]]; then
107214 cd -- " $resolved_dir " 2> /dev/null || return
108215 else
0 commit comments