@optique/core 1.0.15-dev.2880 → 1.0.15-dev.2887
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/completion.cjs +432 -18
- package/dist/completion.js +432 -18
- package/package.json +1 -1
package/dist/completion.cjs
CHANGED
|
@@ -805,12 +805,27 @@ const zsh = {
|
|
|
805
805
|
validateProgramName(programName);
|
|
806
806
|
const escapedArgs = args.map((arg) => `'${arg.replace(/'/g, "'\\''")}'`).join(" ");
|
|
807
807
|
const functionName = /[.-]/.test(programName) ? `_optique:${programName}` : `_${programName}`;
|
|
808
|
+
const fixedGlobFunction = "__optique-fixed-glob-" + Array.from(programName, (character) => character.charCodeAt(0).toString(16).padStart(2, "0")).join("");
|
|
808
809
|
return `#compdef ${programName}
|
|
809
810
|
function ${functionName} () {
|
|
810
|
-
|
|
811
|
+
# Completion fields omit surrounding quotes, unlike words[CURRENT].
|
|
812
|
+
# Single-quoted fields are already literal; other fields retain escapes.
|
|
813
|
+
local current="\$IPREFIX\$PREFIX\$SUFFIX\$ISUFFIX"
|
|
814
|
+
local __logical_prefix="\$IPREFIX\$PREFIX"
|
|
811
815
|
# Let zsh match the suffix when completing in the middle of a word.
|
|
812
816
|
if [[ -o complete_in_word ]]; then
|
|
813
|
-
current="\$
|
|
817
|
+
current="\$__logical_prefix"
|
|
818
|
+
fi
|
|
819
|
+
if [[ "\$compstate[quote]" == '\"' ]]; then
|
|
820
|
+
# Restore the omitted quoting context before removing shell escapes.
|
|
821
|
+
# In double quotes, a backslash before an ordinary character is literal.
|
|
822
|
+
current="\$QIPREFIX\$current\${QISUFFIX:-\$compstate[quote]}"
|
|
823
|
+
__logical_prefix="\$QIPREFIX\$__logical_prefix\$compstate[quote]"
|
|
824
|
+
current="\${(Q)current}"
|
|
825
|
+
__logical_prefix="\${(Q)__logical_prefix}"
|
|
826
|
+
elif [[ "\$compstate[quote]" != "'" ]]; then
|
|
827
|
+
current="\${(Q)current}"
|
|
828
|
+
__logical_prefix="\${(Q)__logical_prefix}"
|
|
814
829
|
fi
|
|
815
830
|
local -a prev
|
|
816
831
|
# Extract previous arguments, skipping empty ones
|
|
@@ -818,7 +833,7 @@ function ${functionName} () {
|
|
|
818
833
|
local i
|
|
819
834
|
for (( i=2; i < CURRENT; i++ )); do
|
|
820
835
|
if [[ -n "\$words[i]" ]]; then
|
|
821
|
-
prev+=("\$words[i]")
|
|
836
|
+
prev+=("\${(Q)words[i]}")
|
|
822
837
|
fi
|
|
823
838
|
done
|
|
824
839
|
|
|
@@ -861,6 +876,9 @@ function ${functionName} () {
|
|
|
861
876
|
unsetopt sh_glob
|
|
862
877
|
local __was_extended_glob=0
|
|
863
878
|
[[ -o extended_glob ]] && __was_extended_glob=1
|
|
879
|
+
local __was_sh_word_split=0
|
|
880
|
+
[[ -o sh_word_split ]] && __was_sh_word_split=1
|
|
881
|
+
unsetopt sh_word_split
|
|
864
882
|
|
|
865
883
|
# Enable glob_dots when hidden files are requested so that
|
|
866
884
|
# _files and _directories include dot-prefixed entries
|
|
@@ -873,6 +891,8 @@ function ${functionName} () {
|
|
|
873
891
|
# current word. If the user has already typed beyond the pattern,
|
|
874
892
|
# keep PREFIX unchanged for incremental narrowing.
|
|
875
893
|
local __saved_prefix="\$PREFIX"
|
|
894
|
+
local __fixed_glob=0
|
|
895
|
+
local -a __fixed_matches
|
|
876
896
|
local __initial_file_pattern="" __initial_directory="" __initial_leaf=""
|
|
877
897
|
if [[ ( "\$type" == "file" || "\$type" == "any" || "\$type" == "directory" ) &&
|
|
878
898
|
-z "\$current\$PREFIX\$SUFFIX" &&
|
|
@@ -900,20 +920,322 @@ function ${functionName} () {
|
|
|
900
920
|
fi
|
|
901
921
|
fi
|
|
902
922
|
fi
|
|
903
|
-
|
|
923
|
+
local __saved_pattern_match="\$compstate[pattern_match]"
|
|
924
|
+
local __pattern_match_changed=0
|
|
925
|
+
# path() echoes current as its pattern. Keep zsh's escaped PREFIX
|
|
926
|
+
# in that case instead of reinterpreting logical text as shell syntax.
|
|
927
|
+
if [[ -n "\$pattern" && "\$pattern" != "\$current" && -z "\$__initial_file_pattern" ]]; then
|
|
904
928
|
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
905
|
-
local __norm_prefix="\${
|
|
929
|
+
local __norm_prefix="\${__logical_prefix#./}"
|
|
906
930
|
local __norm_pattern="\${pattern#./}"
|
|
907
931
|
local __compare_pattern="\$__norm_pattern"
|
|
908
932
|
if [[ "\$__compare_pattern" == *[\*\?]* ]]; then
|
|
909
933
|
__compare_pattern="\${__compare_pattern%%[\*\?]*}"
|
|
910
934
|
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
911
935
|
fi
|
|
912
|
-
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$
|
|
913
|
-
#
|
|
914
|
-
|
|
936
|
+
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$__logical_prefix" != "\$pattern" ]]; then
|
|
937
|
+
# Keep the typed prefix, but retain a fixed file glob separately.
|
|
938
|
+
if [[ "\$type" != "directory" && "\$__norm_pattern" == *[\*\?]* ]]; then
|
|
939
|
+
__fixed_glob=1
|
|
940
|
+
local __fixed_was_glob_dots=0
|
|
941
|
+
[[ -o glob_dots ]] && __fixed_was_glob_dots=1
|
|
942
|
+
if [[ "\$PREFIX" == .* || "\$PREFIX" == */.* ]]; then setopt glob_dots; fi
|
|
943
|
+
# Keep qualifiers in a string so sourcing also works with SH_GLOB.
|
|
944
|
+
local __fixed_pattern="" __glob_char="" __glob_previous=""
|
|
945
|
+
local __glob_escaped=0
|
|
946
|
+
local __glob_class=0 __glob_class_start=0 __glob_class_nested=""
|
|
947
|
+
local -i __glob_index
|
|
948
|
+
for (( __glob_index=1; __glob_index <= \${#pattern}; __glob_index++ )); do
|
|
949
|
+
__glob_char="\${pattern[__glob_index]}"
|
|
950
|
+
if [[ "\$__glob_class" == "1" ]]; then
|
|
951
|
+
# Class members are already literal. Preserve leading ]
|
|
952
|
+
# and nested POSIX classes without adding backslashes.
|
|
953
|
+
__fixed_pattern+="\$__glob_char"
|
|
954
|
+
if [[ -n "\$__glob_class_nested" ]]; then
|
|
955
|
+
if [[ "\$__glob_char" == "]" && "\$__glob_previous" == "\$__glob_class_nested" ]]; then
|
|
956
|
+
__glob_class_nested=""
|
|
957
|
+
fi
|
|
958
|
+
elif [[ "\$__glob_char" == "[" && "\${pattern[__glob_index+1]}" == ":" ]]; then
|
|
959
|
+
__glob_class_nested="\${pattern[__glob_index+1]}"
|
|
960
|
+
__glob_class_start=0
|
|
961
|
+
elif [[ "\$__glob_char" == "]" && "\$__glob_class_start" == "0" ]]; then
|
|
962
|
+
__glob_class=0
|
|
963
|
+
elif [[ "\$__glob_class_start" == "1" && ( "\$__glob_char" == "!" || "\$__glob_char" == "^" ) &&
|
|
964
|
+
( "\${pattern[__glob_index+1]}" != "]" || "\${pattern[__glob_index+2,-1]}" == *']'* ) ]]; then
|
|
965
|
+
__glob_class_start=2
|
|
966
|
+
else
|
|
967
|
+
__glob_class_start=0
|
|
968
|
+
fi
|
|
969
|
+
__glob_previous="\$__glob_char"
|
|
970
|
+
continue
|
|
971
|
+
fi
|
|
972
|
+
if [[ "\$__glob_escaped" == "1" ]]; then
|
|
973
|
+
__fixed_pattern+="\$__glob_char"
|
|
974
|
+
__glob_escaped=0
|
|
975
|
+
__glob_previous=""
|
|
976
|
+
continue
|
|
977
|
+
fi
|
|
978
|
+
if [[ "\$__glob_char" == "\\\\" ]]; then
|
|
979
|
+
__fixed_pattern+="\$__glob_char"
|
|
980
|
+
__glob_escaped=1
|
|
981
|
+
elif [[ "\$__glob_char" == "[" ]]; then
|
|
982
|
+
__fixed_pattern+="\$__glob_char"
|
|
983
|
+
__glob_class=1
|
|
984
|
+
__glob_class_start=1
|
|
985
|
+
elif [[ "\$__glob_char" == [#^~\\(\\)\\|\\<\\>] &&
|
|
986
|
+
! ( "\$__glob_char" == "~" && "\$__glob_index" == "1" ) ]]; then
|
|
987
|
+
# Keep class negation and leading home expansion; escape
|
|
988
|
+
# extended-glob operators in literal pathname components.
|
|
989
|
+
__fixed_pattern+="\\\\\$__glob_char"
|
|
990
|
+
else
|
|
991
|
+
__fixed_pattern+="\$__glob_char"
|
|
992
|
+
fi
|
|
993
|
+
__glob_previous="\$__glob_char"
|
|
994
|
+
done
|
|
995
|
+
__fixed_pattern+="(#qN)"
|
|
996
|
+
__fixed_matches=( \${~__fixed_pattern} )
|
|
997
|
+
__fixed_matches=( "\${(@)__fixed_matches:a}" )
|
|
998
|
+
# Hidden parents widen fixed matches, not native child suggestions.
|
|
999
|
+
if [[ "\$__fixed_was_glob_dots" == "0" && "\${PREFIX##*/}" != .* ]]; then unsetopt glob_dots; fi
|
|
1000
|
+
fi
|
|
915
1001
|
else
|
|
916
|
-
|
|
1002
|
+
local __native_pattern="\$pattern"
|
|
1003
|
+
# Initial globs use the conservative native filters above.
|
|
1004
|
+
# Retained input and protected markers still need pattern matching.
|
|
1005
|
+
if [[ -n "\$compstate[context]" && "\$pattern" == *[\\*\\?\\[]* &&
|
|
1006
|
+
( -n "\$current" || "\$pattern" == *'$'* || "\$pattern" == *'\`'* ) ]]; then
|
|
1007
|
+
compstate[pattern_match]='*'
|
|
1008
|
+
__pattern_match_changed=1
|
|
1009
|
+
fi
|
|
1010
|
+
# Native helpers expand substitutions, tilde names and executable
|
|
1011
|
+
# glob qualifiers. Input-derived patterns must keep these literal.
|
|
1012
|
+
# Ordinary fixed glob/tilde patterns retain their native behavior.
|
|
1013
|
+
local __tilde_name="\${pattern#\\~}"
|
|
1014
|
+
__tilde_name="\${__tilde_name%%/*}"
|
|
1015
|
+
local -a match mbegin mend
|
|
1016
|
+
local __code_qualifier=0
|
|
1017
|
+
local __fixed_qualifier_base=""
|
|
1018
|
+
local __safe_qualifiers='([-/F.@=p*rwxAIERWXsStUG^MTNDn,]|[ugdl]<->|[amc]([Mwhmsd]|)([-+]|)<->|L([kKmMgGtTpP]|)([-+]|)<->|[oO][nLlamcdN])##'
|
|
1019
|
+
local __qualifier_pattern="\$pattern" __qualifier
|
|
1020
|
+
while _have_glob_qual "\$__qualifier_pattern" complete; do
|
|
1021
|
+
__qualifier="\${match[5]#\\#q}"
|
|
1022
|
+
__qualifier_pattern="\$match[2]"
|
|
1023
|
+
# A numeric sense (+1) is not a function qualifier (+name).
|
|
1024
|
+
# Accept only bounded attribute/sort syntax in this exception.
|
|
1025
|
+
# Expand the pattern at runtime after unsetting SH_GLOB.
|
|
1026
|
+
if [[ "\$__qualifier" == \${~__safe_qualifiers} ]]; then
|
|
1027
|
+
__fixed_qualifier_base="\$__qualifier_pattern"
|
|
1028
|
+
elif [[ "\$__qualifier" == *[e+]* ]]; then
|
|
1029
|
+
__code_qualifier=1
|
|
1030
|
+
fi
|
|
1031
|
+
done
|
|
1032
|
+
# Partial overlaps with complete backend classes/groups remain
|
|
1033
|
+
# syntax. A complete input-provided expression can be literal.
|
|
1034
|
+
local -a __syntax_starts=() __syntax_ends=() __group_stack=()
|
|
1035
|
+
local -i __scan_index __class_begin=0 __member_begin=0
|
|
1036
|
+
local __scan_char __nested_class=""
|
|
1037
|
+
for (( __scan_index=1; __scan_index <= \${#pattern}; __scan_index++ )); do
|
|
1038
|
+
__scan_char="\${pattern[__scan_index]}"
|
|
1039
|
+
if [[ "\$__scan_char" == '\\' ]]; then
|
|
1040
|
+
(( __scan_index++ ))
|
|
1041
|
+
elif (( __class_begin )); then
|
|
1042
|
+
if [[ "\$__scan_char" == '[' && "\${pattern[__scan_index+1]}" == [:.=] ]]; then
|
|
1043
|
+
__nested_class="\${pattern[__scan_index+1]}"
|
|
1044
|
+
elif [[ -n "\$__nested_class" && "\$__scan_char" == "\$__nested_class" && "\${pattern[__scan_index+1]}" == ']' ]]; then
|
|
1045
|
+
(( __scan_index++ ))
|
|
1046
|
+
__nested_class=""
|
|
1047
|
+
elif [[ -z "\$__nested_class" && "\$__scan_char" == ']' ]] && (( __scan_index > __member_begin )); then
|
|
1048
|
+
__syntax_starts+=("\$__class_begin")
|
|
1049
|
+
__syntax_ends+=("\$__scan_index")
|
|
1050
|
+
__class_begin=0
|
|
1051
|
+
elif (( __scan_index == __member_begin )) &&
|
|
1052
|
+
[[ "\$__scan_char" == '!' || "\$__scan_char" == '^' ]] &&
|
|
1053
|
+
[[ "\${pattern[__scan_index+1]}" != ']' || "\${pattern[__scan_index+2,-1]}" == *']'* ]]; then
|
|
1054
|
+
(( __member_begin++ ))
|
|
1055
|
+
fi
|
|
1056
|
+
elif [[ "\$__scan_char" == '[' ]]; then
|
|
1057
|
+
__class_begin=\$__scan_index
|
|
1058
|
+
__member_begin=\$(( __scan_index + 1 ))
|
|
1059
|
+
elif [[ "\$__scan_char" == '(' ]]; then
|
|
1060
|
+
__group_stack+=("\$__scan_index")
|
|
1061
|
+
elif [[ "\$__scan_char" == ')' ]] && (( \${#__group_stack} )); then
|
|
1062
|
+
__syntax_starts+=("\${__group_stack[-1]}")
|
|
1063
|
+
__syntax_ends+=("\$__scan_index")
|
|
1064
|
+
__group_stack[-1]=()
|
|
1065
|
+
fi
|
|
1066
|
+
done
|
|
1067
|
+
# Search retained input only where it cannot split fixed syntax.
|
|
1068
|
+
local __input_part="\$current"
|
|
1069
|
+
local __backend_prefix="\$pattern" __backend_suffix=""
|
|
1070
|
+
local __search_pattern __candidate_prefix
|
|
1071
|
+
local -i __search_offset __overlap_start __overlap_end __range_index __syntax_overlap
|
|
1072
|
+
local -i __fixed_tilde_end=0
|
|
1073
|
+
local __tilde_expression='([+-]|[+-]<->|[[:alnum:]_.-]#)'
|
|
1074
|
+
if [[ "\$pattern" == '~'* &&
|
|
1075
|
+
"\$__tilde_name" == \${~__tilde_expression} ]]; then
|
|
1076
|
+
__fixed_tilde_end=\$(( \${#__tilde_name} + 1 ))
|
|
1077
|
+
fi
|
|
1078
|
+
while [[ -n "\$__input_part" ]]; do
|
|
1079
|
+
__search_pattern="\$pattern"
|
|
1080
|
+
__search_offset=0
|
|
1081
|
+
while [[ "\$__search_pattern" == *"\$__input_part"* ]]; do
|
|
1082
|
+
__candidate_prefix="\${__search_pattern%%"\$__input_part"*}"
|
|
1083
|
+
__overlap_start=\$(( __search_offset + \${#__candidate_prefix} + 1 ))
|
|
1084
|
+
__overlap_end=\$(( __overlap_start + \${#__input_part} - 1 ))
|
|
1085
|
+
__syntax_overlap=0
|
|
1086
|
+
# Leading home/directory-stack/named-directory syntax belongs
|
|
1087
|
+
# to the fixed pattern, even when the input overlaps it.
|
|
1088
|
+
if (( __fixed_tilde_end && __overlap_start <= __fixed_tilde_end )); then
|
|
1089
|
+
__syntax_overlap=1
|
|
1090
|
+
fi
|
|
1091
|
+
for (( __range_index=1; __range_index <= \${#__syntax_starts}; __range_index++ )); do
|
|
1092
|
+
if (( __overlap_start <= __syntax_ends[__range_index] &&
|
|
1093
|
+
__overlap_end >= __syntax_starts[__range_index] &&
|
|
1094
|
+
( __overlap_start > __syntax_starts[__range_index] ||
|
|
1095
|
+
__overlap_end < __syntax_ends[__range_index] ) )); then
|
|
1096
|
+
__syntax_overlap=1
|
|
1097
|
+
break
|
|
1098
|
+
fi
|
|
1099
|
+
done
|
|
1100
|
+
# Every position in a recognized fixed attribute qualifier
|
|
1101
|
+
# is backend syntax, including its closing parenthesis.
|
|
1102
|
+
if [[ "\$__fixed_qualifier_base" == *[\\*\\?\\]] ]] &&
|
|
1103
|
+
(( __overlap_end > \${#__fixed_qualifier_base} )); then
|
|
1104
|
+
__syntax_overlap=1
|
|
1105
|
+
fi
|
|
1106
|
+
if (( ! __syntax_overlap )); then
|
|
1107
|
+
if (( __overlap_start == 1 )); then
|
|
1108
|
+
__backend_prefix=""
|
|
1109
|
+
else
|
|
1110
|
+
__backend_prefix="\${pattern[1,__overlap_start-1]}"
|
|
1111
|
+
fi
|
|
1112
|
+
__backend_suffix="\${pattern[__overlap_end+1,-1]}"
|
|
1113
|
+
break 2
|
|
1114
|
+
fi
|
|
1115
|
+
__search_offset=\$__overlap_start
|
|
1116
|
+
__search_pattern="\${pattern[__overlap_start+1,-1]}"
|
|
1117
|
+
done
|
|
1118
|
+
__input_part="\${__input_part[2,-1]}"
|
|
1119
|
+
done
|
|
1120
|
+
local __quoted_input="\${(q)__input_part}"
|
|
1121
|
+
# Pattern matching removes quoting differently from normal
|
|
1122
|
+
# completion. Character classes keep these characters literal.
|
|
1123
|
+
if [[ -n "\$compstate[pattern_match]" ]]; then
|
|
1124
|
+
__quoted_input="\${(b)__input_part}"
|
|
1125
|
+
__quoted_input="\${__quoted_input//\\\$/[\\\$]}"
|
|
1126
|
+
__quoted_input="\${__quoted_input//\\\`/[\\\`]}"
|
|
1127
|
+
fi
|
|
1128
|
+
if [[ "\$pattern" == *'$'* || "\$pattern" == *'\`'* ||
|
|
1129
|
+
( "\$pattern" == '~'* && "\$__tilde_name" == *[^[:alnum:]_.+-]* ) ]] ||
|
|
1130
|
+
(( __code_qualifier )); then
|
|
1131
|
+
__native_pattern="\${(q)pattern}"
|
|
1132
|
+
local __backend_pattern="\$__backend_prefix\$__backend_suffix"
|
|
1133
|
+
local __backend_tilde="\${__backend_pattern#\\~}"
|
|
1134
|
+
__backend_tilde="\${__backend_tilde%%/*}"
|
|
1135
|
+
local __unsafe_backend_qualifier=0
|
|
1136
|
+
__qualifier_pattern="\$__backend_pattern"
|
|
1137
|
+
while _have_glob_qual "\$__qualifier_pattern" complete; do
|
|
1138
|
+
__qualifier="\${match[5]#\\#q}"
|
|
1139
|
+
__qualifier_pattern="\$match[2]"
|
|
1140
|
+
if [[ "\$__qualifier" != \${~__safe_qualifiers} ]]; then
|
|
1141
|
+
__unsafe_backend_qualifier=1
|
|
1142
|
+
fi
|
|
1143
|
+
done
|
|
1144
|
+
# Retain trusted wildcards and safe attribute qualifiers when
|
|
1145
|
+
# all executable syntax belongs to the quoted input.
|
|
1146
|
+
if [[ -n "\$__input_part" &&
|
|
1147
|
+
"\$__backend_pattern" != *'$'* &&
|
|
1148
|
+
"\$__backend_pattern" != *'\`'* &&
|
|
1149
|
+
( "\$__backend_pattern" != '~'* ||
|
|
1150
|
+
"\$__backend_tilde" != *[^[:alnum:]_.+-]* ) ]] &&
|
|
1151
|
+
(( ! __unsafe_backend_qualifier )); then
|
|
1152
|
+
__native_pattern="\$__backend_prefix\$__quoted_input\$__backend_suffix"
|
|
1153
|
+
elif [[ "\$__backend_pattern" != *'$('* &&
|
|
1154
|
+
"\$__backend_pattern" != *'\${'* &&
|
|
1155
|
+
"\$__backend_pattern" != *'$['* &&
|
|
1156
|
+
( "\$pattern" != '~'* ||
|
|
1157
|
+
"\$__tilde_name" != *[^[:alnum:]_.+-]* ) ]] &&
|
|
1158
|
+
(( ! __unsafe_backend_qualifier )); then
|
|
1159
|
+
# Fixed patterns also need literal substitution markers,
|
|
1160
|
+
# without quoting their wildcards or safe qualifiers.
|
|
1161
|
+
local __dollar='\\\$'
|
|
1162
|
+
local __backtick='\\\`'
|
|
1163
|
+
if [[ -n "\$compstate[pattern_match]" ]]; then
|
|
1164
|
+
__dollar='[\$]'
|
|
1165
|
+
__backtick='[\`]'
|
|
1166
|
+
fi
|
|
1167
|
+
# Existing glob classes already protect their markers.
|
|
1168
|
+
local -a __backend_parts=()
|
|
1169
|
+
local __part __escaped __char __class_kind
|
|
1170
|
+
local -i __index __in_class __class_start
|
|
1171
|
+
for __part in "\$__backend_prefix" "\$__backend_suffix"; do
|
|
1172
|
+
__escaped=""
|
|
1173
|
+
__in_class=0
|
|
1174
|
+
__class_kind=""
|
|
1175
|
+
for (( __index=1; __index <= \${#__part}; __index++ )); do
|
|
1176
|
+
__char="\${__part[__index]}"
|
|
1177
|
+
if [[ "\$__char" == '\\' ]]; then
|
|
1178
|
+
__escaped+="\$__char\${__part[__index+1]}"
|
|
1179
|
+
(( __index++ ))
|
|
1180
|
+
continue
|
|
1181
|
+
elif (( __in_class )); then
|
|
1182
|
+
if [[ "\$__char" == '[' && "\${__part[__index+1]}" == [:.=] ]]; then
|
|
1183
|
+
__class_kind="\${__part[__index+1]}"
|
|
1184
|
+
elif [[ -n "\$__class_kind" && "\$__char" == "\$__class_kind" && "\${__part[__index+1]}" == ']' ]]; then
|
|
1185
|
+
__escaped+="\$__char]"
|
|
1186
|
+
(( __index++ ))
|
|
1187
|
+
__class_kind=""
|
|
1188
|
+
continue
|
|
1189
|
+
elif [[ -z "\$__class_kind" && "\$__char" == ']' ]] && (( __index > __class_start )); then
|
|
1190
|
+
__in_class=0
|
|
1191
|
+
elif (( __index == __class_start )) && [[ "\$__char" == '!' || "\$__char" == '^' ]]; then
|
|
1192
|
+
(( __class_start++ ))
|
|
1193
|
+
fi
|
|
1194
|
+
elif [[ "\$__char" == '[' ]]; then
|
|
1195
|
+
__in_class=1
|
|
1196
|
+
__class_start=$(( __index + 1 ))
|
|
1197
|
+
elif [[ "\$__char" == '$' ]]; then
|
|
1198
|
+
__char="\$__dollar"
|
|
1199
|
+
elif [[ "\$__char" == '\`' ]]; then
|
|
1200
|
+
__char="\$__backtick"
|
|
1201
|
+
fi
|
|
1202
|
+
__escaped+="\$__char"
|
|
1203
|
+
done
|
|
1204
|
+
__backend_parts+=("\$__escaped")
|
|
1205
|
+
done
|
|
1206
|
+
__backend_prefix="\${__backend_parts[1]}"
|
|
1207
|
+
__backend_suffix="\${__backend_parts[2]}"
|
|
1208
|
+
__native_pattern="\$__backend_prefix\$__quoted_input\$__backend_suffix"
|
|
1209
|
+
fi
|
|
1210
|
+
else
|
|
1211
|
+
# Quote retained metacharacters, including segments made only
|
|
1212
|
+
# of them. Wildcard-only overlap remains backend syntax.
|
|
1213
|
+
if [[ "\$__input_part" == *[\\\\\\(\\)\\[\\#\\^\\|\\~\\<\\>\\*\\?]* &&
|
|
1214
|
+
"\$__input_part" != [\\*\\?]## ]]; then
|
|
1215
|
+
__native_pattern="\$__backend_prefix\$__quoted_input\$__backend_suffix"
|
|
1216
|
+
fi
|
|
1217
|
+
fi
|
|
1218
|
+
PREFIX="\$__native_pattern"
|
|
1219
|
+
# Match simple fixed tilde globs through native -g filters. The
|
|
1220
|
+
# pattern matcher otherwise treats the leading ~ as glob syntax,
|
|
1221
|
+
# even after _path_files has expanded it for filesystem lookup.
|
|
1222
|
+
if (( __fixed_tilde_end )) && [[ -z "\$__input_part" &&
|
|
1223
|
+
"\$__native_pattern" == "\$pattern" && "\$pattern" == */* ]]; then
|
|
1224
|
+
__initial_directory="\${pattern%/*}/"
|
|
1225
|
+
__initial_leaf="\${pattern##*/}"
|
|
1226
|
+
if [[ "\$__initial_leaf" == *[\*\?]* &&
|
|
1227
|
+
"\$__initial_directory" != *[\*\?]* &&
|
|
1228
|
+
"\$__initial_directory" != *["[]()"]* &&
|
|
1229
|
+
"\$__initial_leaf" != *["[]()#~^|<>;&"]* &&
|
|
1230
|
+
"\$__initial_leaf" != *'\\'* &&
|
|
1231
|
+
"\$__initial_leaf" != *[[:space:]]* &&
|
|
1232
|
+
"\$__initial_leaf" != *"'"* && "\$__initial_leaf" != *'"'* ]]; then
|
|
1233
|
+
__initial_file_pattern="\$__initial_leaf"
|
|
1234
|
+
PREFIX="\$__initial_directory"
|
|
1235
|
+
compstate[pattern_match]=""
|
|
1236
|
+
__pattern_match_changed=1
|
|
1237
|
+
fi
|
|
1238
|
+
fi
|
|
917
1239
|
fi
|
|
918
1240
|
fi
|
|
919
1241
|
|
|
@@ -935,6 +1257,9 @@ function ${functionName} () {
|
|
|
935
1257
|
# Regular-file suggestions retain symlinked files; initial
|
|
936
1258
|
# any globs use a non-directory qualifier instead.
|
|
937
1259
|
local file_pattern="\${ext_pattern:-*}(#q-.)"
|
|
1260
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1261
|
+
file_pattern="\${ext_pattern:-*}(#q-.e:${fixedGlobFunction}:)"
|
|
1262
|
+
fi
|
|
938
1263
|
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
939
1264
|
local __file_qualifier="-."
|
|
940
1265
|
[[ "\$type" == "any" ]] && __file_qualifier="^-/"
|
|
@@ -950,6 +1275,21 @@ function ${functionName} () {
|
|
|
950
1275
|
fi
|
|
951
1276
|
file_pattern="\${(j: :)__initial_file_patterns}"
|
|
952
1277
|
fi
|
|
1278
|
+
local file_pattern_prefix="\$PREFIX"
|
|
1279
|
+
local -a match mbegin mend
|
|
1280
|
+
# Native qualifier copying concatenates flags with the explicit
|
|
1281
|
+
# regular-file group. Keep backend attributes in their own group.
|
|
1282
|
+
if [[ -n "\$compstate[pattern_match]" && -z "\$SUFFIX" ]]; then
|
|
1283
|
+
local file_qualifier file_qualifiers=""
|
|
1284
|
+
while _have_glob_qual "\$file_pattern_prefix" complete; do
|
|
1285
|
+
file_qualifier="\${match[5]#\\#q}"
|
|
1286
|
+
file_pattern_prefix="\$match[2]"
|
|
1287
|
+
file_qualifiers="(#q\$file_qualifier)\$file_qualifiers"
|
|
1288
|
+
done
|
|
1289
|
+
if [[ -n "\$file_qualifiers" ]]; then
|
|
1290
|
+
file_pattern+="\$file_qualifiers"
|
|
1291
|
+
fi
|
|
1292
|
+
fi
|
|
953
1293
|
local file_subtree_pattern="**/*(#q/N)"
|
|
954
1294
|
local -a file_subtree=() file_recursive_patterns=()
|
|
955
1295
|
local -a file_tags=(files directories)
|
|
@@ -963,15 +1303,22 @@ function ${functionName} () {
|
|
|
963
1303
|
local file_matched=0
|
|
964
1304
|
for file_tag in "\${file_tags[@]}"; do
|
|
965
1305
|
if [[ "\$file_tag" == directories ]]; then
|
|
1306
|
+
local file_tag_prefix="\$PREFIX"
|
|
1307
|
+
PREFIX="\$file_pattern_prefix"
|
|
966
1308
|
while _next_label directories expl directory; do
|
|
1309
|
+
# File qualifiers constrain final results, not the
|
|
1310
|
+
# directories needed to navigate to those results.
|
|
967
1311
|
_path_files -/ "\${expl[@]}" && file_matched=1
|
|
968
1312
|
done
|
|
1313
|
+
PREFIX="\$file_tag_prefix"
|
|
969
1314
|
else
|
|
1315
|
+
local file_tag_prefix="\$PREFIX"
|
|
1316
|
+
PREFIX="\$file_pattern_prefix"
|
|
970
1317
|
while _next_label files expl file; do
|
|
971
1318
|
local -a file_ignore=() file_ignore_args=()
|
|
972
1319
|
# Explicit globs bypass automatic fignore. Apply it only
|
|
973
1320
|
# when the tag has not supplied its own ignore patterns.
|
|
974
|
-
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
1321
|
+
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && ( "\$__fixed_glob" != "1" || "\$pattern" == "*" ) && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
975
1322
|
file_ignore=("?*\${^fignore[@]}")
|
|
976
1323
|
file_ignore_args=(-F file_ignore)
|
|
977
1324
|
fi
|
|
@@ -997,12 +1344,17 @@ function ${functionName} () {
|
|
|
997
1344
|
done
|
|
998
1345
|
fi
|
|
999
1346
|
done
|
|
1347
|
+
PREFIX="\$file_tag_prefix"
|
|
1000
1348
|
fi
|
|
1001
1349
|
done
|
|
1002
1350
|
(( file_matched )) && break
|
|
1003
1351
|
done
|
|
1004
|
-
elif [[ -n "\$ext_pattern" ]]; then
|
|
1352
|
+
elif [[ -n "\$ext_pattern" || "\$__fixed_glob" == "1" ]]; then
|
|
1005
1353
|
local file_pattern="\${ext_pattern}(#q^-/)"
|
|
1354
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1355
|
+
# Put the predicate before ^, which negates later qualifiers.
|
|
1356
|
+
file_pattern="\${ext_pattern:-*}(#qe:${fixedGlobFunction}:^-/)"
|
|
1357
|
+
fi
|
|
1006
1358
|
_wanted files expl file _path_files -g "\${file_pattern}"
|
|
1007
1359
|
_wanted directories expl directory _path_files -/
|
|
1008
1360
|
else
|
|
@@ -1021,7 +1373,11 @@ function ${functionName} () {
|
|
|
1021
1373
|
# Restore PREFIX and glob_dots to their previous state
|
|
1022
1374
|
PREFIX="\$__saved_prefix"
|
|
1023
1375
|
if [[ "\$__was_extended_glob" == "1" ]]; then setopt extended_glob; else unsetopt extended_glob; fi
|
|
1376
|
+
if (( __pattern_match_changed )); then
|
|
1377
|
+
compstate[pattern_match]="\$__saved_pattern_match"
|
|
1378
|
+
fi
|
|
1024
1379
|
if [[ "\$__was_sh_glob" == "1" ]]; then setopt sh_glob; else unsetopt sh_glob; fi
|
|
1380
|
+
if [[ "\$__was_sh_word_split" == "1" ]]; then setopt sh_word_split; else unsetopt sh_word_split; fi
|
|
1025
1381
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
1026
1382
|
else
|
|
1027
1383
|
# Regular literal completion
|
|
@@ -1038,20 +1394,31 @@ function ${functionName} () {
|
|
|
1038
1394
|
# Prepare completion with descriptions for _describe
|
|
1039
1395
|
local -a matches
|
|
1040
1396
|
local -i i
|
|
1397
|
+
local __match
|
|
1041
1398
|
for (( i=1; i <= \${#completions[@]}; i++ )); do
|
|
1042
|
-
# _describe
|
|
1043
|
-
|
|
1044
|
-
|
|
1399
|
+
# _describe uses backslashes to escape its colon separator.
|
|
1400
|
+
__match="\${completions[i]//\\\\/\\\\\\\\}"
|
|
1401
|
+
__match="\${__match//:/\\\\:}"
|
|
1045
1402
|
if [[ -n "\${descriptions[i]}" ]]; then
|
|
1046
|
-
matches+=("\$
|
|
1403
|
+
matches+=("\$__match:\${descriptions[i]}")
|
|
1047
1404
|
else
|
|
1048
|
-
matches+=("\$
|
|
1405
|
+
matches+=("\$__match")
|
|
1049
1406
|
fi
|
|
1050
1407
|
done
|
|
1051
|
-
|
|
1408
|
+
if [[ -n "\$QISUFFIX" ]]; then
|
|
1409
|
+
# A closing quote already exists; do not append a space inside it.
|
|
1410
|
+
_describe 'commands' matches -S ''
|
|
1411
|
+
else
|
|
1412
|
+
_describe 'commands' matches
|
|
1413
|
+
fi
|
|
1052
1414
|
fi
|
|
1053
1415
|
}
|
|
1054
1416
|
|
|
1417
|
+
function ${fixedGlobFunction} () {
|
|
1418
|
+
local __fixed_candidate="\${REPLY:a}"
|
|
1419
|
+
(( \${__fixed_matches[(Ie)\$__fixed_candidate]} ))
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1055
1422
|
if (( $+functions[compdef] )); then
|
|
1056
1423
|
compdef ${functionName} ${programName}
|
|
1057
1424
|
fi
|
|
@@ -1685,12 +2052,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1685
2052
|
# prefix for incremental narrowing. Normalize path separators
|
|
1686
2053
|
# before comparing so that Windows backslashes match forward slashes
|
|
1687
2054
|
# in the transported pattern.
|
|
2055
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1688
2056
|
let glob_base = if ($pattern | is-not-empty) {
|
|
1689
2057
|
# Normalize separators and leading ./; downcase only on Windows
|
|
1690
2058
|
# where filesystems are typically case-insensitive
|
|
1691
2059
|
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1692
2060
|
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1693
|
-
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1694
2061
|
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
1695
2062
|
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
1696
2063
|
# For wildcard patterns, compare only the directory prefix
|
|
@@ -1709,6 +2076,21 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1709
2076
|
$prefix
|
|
1710
2077
|
}
|
|
1711
2078
|
|
|
2079
|
+
# Intersect fixed glob matches with the narrowed prefix candidates.
|
|
2080
|
+
# Directory-only suggestions retain their existing navigation behavior.
|
|
2081
|
+
let narrow_fixed_glob = ($type != "directory") and ($pattern =~ '[*?]') and ($prefix != $pattern) and ($glob_base == $prefix)
|
|
2082
|
+
let fixed_matches = if $narrow_fixed_glob {
|
|
2083
|
+
try {
|
|
2084
|
+
let fixed_pattern = ($pattern | into glob)
|
|
2085
|
+
let include_hidden = $hidden or ($prefix | path basename | str starts-with '.')
|
|
2086
|
+
let matches = if $include_hidden { ls -a --directory $fixed_pattern } else { ls --directory $fixed_pattern }
|
|
2087
|
+
$matches | each {|item|
|
|
2088
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
2089
|
+
if $is_win { $name | str downcase } else { $name }
|
|
2090
|
+
}
|
|
2091
|
+
} catch { [] }
|
|
2092
|
+
} else { [] }
|
|
2093
|
+
|
|
1712
2094
|
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
1713
2095
|
# append one so that ls enumerates its contents instead of matching
|
|
1714
2096
|
# siblings like "src-old". path expand follows symlinks, so that
|
|
@@ -1831,6 +2213,17 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1831
2213
|
}
|
|
1832
2214
|
}
|
|
1833
2215
|
|
|
2216
|
+
let filtered = if $narrow_fixed_glob {
|
|
2217
|
+
$filtered | where {|item|
|
|
2218
|
+
# Any-mode listings already include directory symlinks; keep them
|
|
2219
|
+
# navigable without changing their existing completion rendering.
|
|
2220
|
+
let navigation_dir = ($item.type == dir) or ($item.type == symlink and (try { ($item.name | path expand | path type) == "dir" } catch { false }))
|
|
2221
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
2222
|
+
let normalized_name = if $is_win { $name | str downcase } else { $name }
|
|
2223
|
+
$navigation_dir or ($normalized_name in $fixed_matches)
|
|
2224
|
+
}
|
|
2225
|
+
} else { $filtered }
|
|
2226
|
+
|
|
1834
2227
|
# Extract directory prefix to preserve in completion text
|
|
1835
2228
|
let dir_prefix = if ($glob_base | is-empty) {
|
|
1836
2229
|
""
|
|
@@ -2106,6 +2499,9 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2106
2499
|
\$prefixFromInput = \$valuePrefix -and \$prefix -ceq \$valuePrefix
|
|
2107
2500
|
\$lookupPrefix = if (\$prefixFromInput -and \$null -ne \$quoteType -and \$prefix.StartsWith('~')) { './' + \$prefix } else { \$prefix }
|
|
2108
2501
|
|
|
2502
|
+
# Detect narrowing before directory normalization changes prefix.
|
|
2503
|
+
\$narrowFixedGlob = \$type -ne 'directory' -and \$pattern -match '[\*\?]' -and \$prefix -ceq \$valuePrefix -and \$pattern -cne \$valuePrefix
|
|
2504
|
+
|
|
2109
2505
|
# If prefix is a directory without trailing slash, append
|
|
2110
2506
|
# one so Get-ChildItem lists its contents
|
|
2111
2507
|
if (\$prefix -and (Test-Path -Path \$lookupPrefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
@@ -2117,6 +2513,16 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2117
2513
|
# characters, use it as-is; otherwise append *
|
|
2118
2514
|
\$globPath = if (\$lookupPrefix -match '[\*\?]') { \$lookupPrefix } else { "\${lookupPrefix}*" }
|
|
2119
2515
|
|
|
2516
|
+
# Keep the original native glob as an independent file filter
|
|
2517
|
+
# when the current word narrows its candidates.
|
|
2518
|
+
\$fixedMatches = @()
|
|
2519
|
+
if (\$narrowFixedGlob) {
|
|
2520
|
+
# Include hidden parents here; candidate enumeration still
|
|
2521
|
+
# controls which hidden children can be suggested.
|
|
2522
|
+
\$fixedMatches = @(Get-Item -Force -Path \$pattern -ErrorAction SilentlyContinue |
|
|
2523
|
+
Select-Object -ExpandProperty FullName)
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2120
2526
|
# Get file system items based on type
|
|
2121
2527
|
\$items = @()
|
|
2122
2528
|
switch (\$type) {
|
|
@@ -2169,6 +2575,14 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2169
2575
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
2170
2576
|
}
|
|
2171
2577
|
|
|
2578
|
+
if (\$narrowFixedGlob) {
|
|
2579
|
+
\$items = \$items | Where-Object {
|
|
2580
|
+
if (\$_.PSIsContainer) { \$true }
|
|
2581
|
+
elseif (\$IsWindows) { \$fixedMatches -contains \$_.FullName }
|
|
2582
|
+
else { \$fixedMatches -ccontains \$_.FullName }
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2172
2586
|
# Extract directory prefix to preserve in completion text
|
|
2173
2587
|
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
2174
2588
|
\$slashIdx = [Math]::Max(\$prefix.LastIndexOf('/'), \$prefix.LastIndexOf('\\'))
|
package/dist/completion.js
CHANGED
|
@@ -805,12 +805,27 @@ const zsh = {
|
|
|
805
805
|
validateProgramName(programName);
|
|
806
806
|
const escapedArgs = args.map((arg) => `'${arg.replace(/'/g, "'\\''")}'`).join(" ");
|
|
807
807
|
const functionName = /[.-]/.test(programName) ? `_optique:${programName}` : `_${programName}`;
|
|
808
|
+
const fixedGlobFunction = "__optique-fixed-glob-" + Array.from(programName, (character) => character.charCodeAt(0).toString(16).padStart(2, "0")).join("");
|
|
808
809
|
return `#compdef ${programName}
|
|
809
810
|
function ${functionName} () {
|
|
810
|
-
|
|
811
|
+
# Completion fields omit surrounding quotes, unlike words[CURRENT].
|
|
812
|
+
# Single-quoted fields are already literal; other fields retain escapes.
|
|
813
|
+
local current="\$IPREFIX\$PREFIX\$SUFFIX\$ISUFFIX"
|
|
814
|
+
local __logical_prefix="\$IPREFIX\$PREFIX"
|
|
811
815
|
# Let zsh match the suffix when completing in the middle of a word.
|
|
812
816
|
if [[ -o complete_in_word ]]; then
|
|
813
|
-
current="\$
|
|
817
|
+
current="\$__logical_prefix"
|
|
818
|
+
fi
|
|
819
|
+
if [[ "\$compstate[quote]" == '\"' ]]; then
|
|
820
|
+
# Restore the omitted quoting context before removing shell escapes.
|
|
821
|
+
# In double quotes, a backslash before an ordinary character is literal.
|
|
822
|
+
current="\$QIPREFIX\$current\${QISUFFIX:-\$compstate[quote]}"
|
|
823
|
+
__logical_prefix="\$QIPREFIX\$__logical_prefix\$compstate[quote]"
|
|
824
|
+
current="\${(Q)current}"
|
|
825
|
+
__logical_prefix="\${(Q)__logical_prefix}"
|
|
826
|
+
elif [[ "\$compstate[quote]" != "'" ]]; then
|
|
827
|
+
current="\${(Q)current}"
|
|
828
|
+
__logical_prefix="\${(Q)__logical_prefix}"
|
|
814
829
|
fi
|
|
815
830
|
local -a prev
|
|
816
831
|
# Extract previous arguments, skipping empty ones
|
|
@@ -818,7 +833,7 @@ function ${functionName} () {
|
|
|
818
833
|
local i
|
|
819
834
|
for (( i=2; i < CURRENT; i++ )); do
|
|
820
835
|
if [[ -n "\$words[i]" ]]; then
|
|
821
|
-
prev+=("\$words[i]")
|
|
836
|
+
prev+=("\${(Q)words[i]}")
|
|
822
837
|
fi
|
|
823
838
|
done
|
|
824
839
|
|
|
@@ -861,6 +876,9 @@ function ${functionName} () {
|
|
|
861
876
|
unsetopt sh_glob
|
|
862
877
|
local __was_extended_glob=0
|
|
863
878
|
[[ -o extended_glob ]] && __was_extended_glob=1
|
|
879
|
+
local __was_sh_word_split=0
|
|
880
|
+
[[ -o sh_word_split ]] && __was_sh_word_split=1
|
|
881
|
+
unsetopt sh_word_split
|
|
864
882
|
|
|
865
883
|
# Enable glob_dots when hidden files are requested so that
|
|
866
884
|
# _files and _directories include dot-prefixed entries
|
|
@@ -873,6 +891,8 @@ function ${functionName} () {
|
|
|
873
891
|
# current word. If the user has already typed beyond the pattern,
|
|
874
892
|
# keep PREFIX unchanged for incremental narrowing.
|
|
875
893
|
local __saved_prefix="\$PREFIX"
|
|
894
|
+
local __fixed_glob=0
|
|
895
|
+
local -a __fixed_matches
|
|
876
896
|
local __initial_file_pattern="" __initial_directory="" __initial_leaf=""
|
|
877
897
|
if [[ ( "\$type" == "file" || "\$type" == "any" || "\$type" == "directory" ) &&
|
|
878
898
|
-z "\$current\$PREFIX\$SUFFIX" &&
|
|
@@ -900,20 +920,322 @@ function ${functionName} () {
|
|
|
900
920
|
fi
|
|
901
921
|
fi
|
|
902
922
|
fi
|
|
903
|
-
|
|
923
|
+
local __saved_pattern_match="\$compstate[pattern_match]"
|
|
924
|
+
local __pattern_match_changed=0
|
|
925
|
+
# path() echoes current as its pattern. Keep zsh's escaped PREFIX
|
|
926
|
+
# in that case instead of reinterpreting logical text as shell syntax.
|
|
927
|
+
if [[ -n "\$pattern" && "\$pattern" != "\$current" && -z "\$__initial_file_pattern" ]]; then
|
|
904
928
|
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
905
|
-
local __norm_prefix="\${
|
|
929
|
+
local __norm_prefix="\${__logical_prefix#./}"
|
|
906
930
|
local __norm_pattern="\${pattern#./}"
|
|
907
931
|
local __compare_pattern="\$__norm_pattern"
|
|
908
932
|
if [[ "\$__compare_pattern" == *[\*\?]* ]]; then
|
|
909
933
|
__compare_pattern="\${__compare_pattern%%[\*\?]*}"
|
|
910
934
|
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
911
935
|
fi
|
|
912
|
-
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$
|
|
913
|
-
#
|
|
914
|
-
|
|
936
|
+
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$__logical_prefix" != "\$pattern" ]]; then
|
|
937
|
+
# Keep the typed prefix, but retain a fixed file glob separately.
|
|
938
|
+
if [[ "\$type" != "directory" && "\$__norm_pattern" == *[\*\?]* ]]; then
|
|
939
|
+
__fixed_glob=1
|
|
940
|
+
local __fixed_was_glob_dots=0
|
|
941
|
+
[[ -o glob_dots ]] && __fixed_was_glob_dots=1
|
|
942
|
+
if [[ "\$PREFIX" == .* || "\$PREFIX" == */.* ]]; then setopt glob_dots; fi
|
|
943
|
+
# Keep qualifiers in a string so sourcing also works with SH_GLOB.
|
|
944
|
+
local __fixed_pattern="" __glob_char="" __glob_previous=""
|
|
945
|
+
local __glob_escaped=0
|
|
946
|
+
local __glob_class=0 __glob_class_start=0 __glob_class_nested=""
|
|
947
|
+
local -i __glob_index
|
|
948
|
+
for (( __glob_index=1; __glob_index <= \${#pattern}; __glob_index++ )); do
|
|
949
|
+
__glob_char="\${pattern[__glob_index]}"
|
|
950
|
+
if [[ "\$__glob_class" == "1" ]]; then
|
|
951
|
+
# Class members are already literal. Preserve leading ]
|
|
952
|
+
# and nested POSIX classes without adding backslashes.
|
|
953
|
+
__fixed_pattern+="\$__glob_char"
|
|
954
|
+
if [[ -n "\$__glob_class_nested" ]]; then
|
|
955
|
+
if [[ "\$__glob_char" == "]" && "\$__glob_previous" == "\$__glob_class_nested" ]]; then
|
|
956
|
+
__glob_class_nested=""
|
|
957
|
+
fi
|
|
958
|
+
elif [[ "\$__glob_char" == "[" && "\${pattern[__glob_index+1]}" == ":" ]]; then
|
|
959
|
+
__glob_class_nested="\${pattern[__glob_index+1]}"
|
|
960
|
+
__glob_class_start=0
|
|
961
|
+
elif [[ "\$__glob_char" == "]" && "\$__glob_class_start" == "0" ]]; then
|
|
962
|
+
__glob_class=0
|
|
963
|
+
elif [[ "\$__glob_class_start" == "1" && ( "\$__glob_char" == "!" || "\$__glob_char" == "^" ) &&
|
|
964
|
+
( "\${pattern[__glob_index+1]}" != "]" || "\${pattern[__glob_index+2,-1]}" == *']'* ) ]]; then
|
|
965
|
+
__glob_class_start=2
|
|
966
|
+
else
|
|
967
|
+
__glob_class_start=0
|
|
968
|
+
fi
|
|
969
|
+
__glob_previous="\$__glob_char"
|
|
970
|
+
continue
|
|
971
|
+
fi
|
|
972
|
+
if [[ "\$__glob_escaped" == "1" ]]; then
|
|
973
|
+
__fixed_pattern+="\$__glob_char"
|
|
974
|
+
__glob_escaped=0
|
|
975
|
+
__glob_previous=""
|
|
976
|
+
continue
|
|
977
|
+
fi
|
|
978
|
+
if [[ "\$__glob_char" == "\\\\" ]]; then
|
|
979
|
+
__fixed_pattern+="\$__glob_char"
|
|
980
|
+
__glob_escaped=1
|
|
981
|
+
elif [[ "\$__glob_char" == "[" ]]; then
|
|
982
|
+
__fixed_pattern+="\$__glob_char"
|
|
983
|
+
__glob_class=1
|
|
984
|
+
__glob_class_start=1
|
|
985
|
+
elif [[ "\$__glob_char" == [#^~\\(\\)\\|\\<\\>] &&
|
|
986
|
+
! ( "\$__glob_char" == "~" && "\$__glob_index" == "1" ) ]]; then
|
|
987
|
+
# Keep class negation and leading home expansion; escape
|
|
988
|
+
# extended-glob operators in literal pathname components.
|
|
989
|
+
__fixed_pattern+="\\\\\$__glob_char"
|
|
990
|
+
else
|
|
991
|
+
__fixed_pattern+="\$__glob_char"
|
|
992
|
+
fi
|
|
993
|
+
__glob_previous="\$__glob_char"
|
|
994
|
+
done
|
|
995
|
+
__fixed_pattern+="(#qN)"
|
|
996
|
+
__fixed_matches=( \${~__fixed_pattern} )
|
|
997
|
+
__fixed_matches=( "\${(@)__fixed_matches:a}" )
|
|
998
|
+
# Hidden parents widen fixed matches, not native child suggestions.
|
|
999
|
+
if [[ "\$__fixed_was_glob_dots" == "0" && "\${PREFIX##*/}" != .* ]]; then unsetopt glob_dots; fi
|
|
1000
|
+
fi
|
|
915
1001
|
else
|
|
916
|
-
|
|
1002
|
+
local __native_pattern="\$pattern"
|
|
1003
|
+
# Initial globs use the conservative native filters above.
|
|
1004
|
+
# Retained input and protected markers still need pattern matching.
|
|
1005
|
+
if [[ -n "\$compstate[context]" && "\$pattern" == *[\\*\\?\\[]* &&
|
|
1006
|
+
( -n "\$current" || "\$pattern" == *'$'* || "\$pattern" == *'\`'* ) ]]; then
|
|
1007
|
+
compstate[pattern_match]='*'
|
|
1008
|
+
__pattern_match_changed=1
|
|
1009
|
+
fi
|
|
1010
|
+
# Native helpers expand substitutions, tilde names and executable
|
|
1011
|
+
# glob qualifiers. Input-derived patterns must keep these literal.
|
|
1012
|
+
# Ordinary fixed glob/tilde patterns retain their native behavior.
|
|
1013
|
+
local __tilde_name="\${pattern#\\~}"
|
|
1014
|
+
__tilde_name="\${__tilde_name%%/*}"
|
|
1015
|
+
local -a match mbegin mend
|
|
1016
|
+
local __code_qualifier=0
|
|
1017
|
+
local __fixed_qualifier_base=""
|
|
1018
|
+
local __safe_qualifiers='([-/F.@=p*rwxAIERWXsStUG^MTNDn,]|[ugdl]<->|[amc]([Mwhmsd]|)([-+]|)<->|L([kKmMgGtTpP]|)([-+]|)<->|[oO][nLlamcdN])##'
|
|
1019
|
+
local __qualifier_pattern="\$pattern" __qualifier
|
|
1020
|
+
while _have_glob_qual "\$__qualifier_pattern" complete; do
|
|
1021
|
+
__qualifier="\${match[5]#\\#q}"
|
|
1022
|
+
__qualifier_pattern="\$match[2]"
|
|
1023
|
+
# A numeric sense (+1) is not a function qualifier (+name).
|
|
1024
|
+
# Accept only bounded attribute/sort syntax in this exception.
|
|
1025
|
+
# Expand the pattern at runtime after unsetting SH_GLOB.
|
|
1026
|
+
if [[ "\$__qualifier" == \${~__safe_qualifiers} ]]; then
|
|
1027
|
+
__fixed_qualifier_base="\$__qualifier_pattern"
|
|
1028
|
+
elif [[ "\$__qualifier" == *[e+]* ]]; then
|
|
1029
|
+
__code_qualifier=1
|
|
1030
|
+
fi
|
|
1031
|
+
done
|
|
1032
|
+
# Partial overlaps with complete backend classes/groups remain
|
|
1033
|
+
# syntax. A complete input-provided expression can be literal.
|
|
1034
|
+
local -a __syntax_starts=() __syntax_ends=() __group_stack=()
|
|
1035
|
+
local -i __scan_index __class_begin=0 __member_begin=0
|
|
1036
|
+
local __scan_char __nested_class=""
|
|
1037
|
+
for (( __scan_index=1; __scan_index <= \${#pattern}; __scan_index++ )); do
|
|
1038
|
+
__scan_char="\${pattern[__scan_index]}"
|
|
1039
|
+
if [[ "\$__scan_char" == '\\' ]]; then
|
|
1040
|
+
(( __scan_index++ ))
|
|
1041
|
+
elif (( __class_begin )); then
|
|
1042
|
+
if [[ "\$__scan_char" == '[' && "\${pattern[__scan_index+1]}" == [:.=] ]]; then
|
|
1043
|
+
__nested_class="\${pattern[__scan_index+1]}"
|
|
1044
|
+
elif [[ -n "\$__nested_class" && "\$__scan_char" == "\$__nested_class" && "\${pattern[__scan_index+1]}" == ']' ]]; then
|
|
1045
|
+
(( __scan_index++ ))
|
|
1046
|
+
__nested_class=""
|
|
1047
|
+
elif [[ -z "\$__nested_class" && "\$__scan_char" == ']' ]] && (( __scan_index > __member_begin )); then
|
|
1048
|
+
__syntax_starts+=("\$__class_begin")
|
|
1049
|
+
__syntax_ends+=("\$__scan_index")
|
|
1050
|
+
__class_begin=0
|
|
1051
|
+
elif (( __scan_index == __member_begin )) &&
|
|
1052
|
+
[[ "\$__scan_char" == '!' || "\$__scan_char" == '^' ]] &&
|
|
1053
|
+
[[ "\${pattern[__scan_index+1]}" != ']' || "\${pattern[__scan_index+2,-1]}" == *']'* ]]; then
|
|
1054
|
+
(( __member_begin++ ))
|
|
1055
|
+
fi
|
|
1056
|
+
elif [[ "\$__scan_char" == '[' ]]; then
|
|
1057
|
+
__class_begin=\$__scan_index
|
|
1058
|
+
__member_begin=\$(( __scan_index + 1 ))
|
|
1059
|
+
elif [[ "\$__scan_char" == '(' ]]; then
|
|
1060
|
+
__group_stack+=("\$__scan_index")
|
|
1061
|
+
elif [[ "\$__scan_char" == ')' ]] && (( \${#__group_stack} )); then
|
|
1062
|
+
__syntax_starts+=("\${__group_stack[-1]}")
|
|
1063
|
+
__syntax_ends+=("\$__scan_index")
|
|
1064
|
+
__group_stack[-1]=()
|
|
1065
|
+
fi
|
|
1066
|
+
done
|
|
1067
|
+
# Search retained input only where it cannot split fixed syntax.
|
|
1068
|
+
local __input_part="\$current"
|
|
1069
|
+
local __backend_prefix="\$pattern" __backend_suffix=""
|
|
1070
|
+
local __search_pattern __candidate_prefix
|
|
1071
|
+
local -i __search_offset __overlap_start __overlap_end __range_index __syntax_overlap
|
|
1072
|
+
local -i __fixed_tilde_end=0
|
|
1073
|
+
local __tilde_expression='([+-]|[+-]<->|[[:alnum:]_.-]#)'
|
|
1074
|
+
if [[ "\$pattern" == '~'* &&
|
|
1075
|
+
"\$__tilde_name" == \${~__tilde_expression} ]]; then
|
|
1076
|
+
__fixed_tilde_end=\$(( \${#__tilde_name} + 1 ))
|
|
1077
|
+
fi
|
|
1078
|
+
while [[ -n "\$__input_part" ]]; do
|
|
1079
|
+
__search_pattern="\$pattern"
|
|
1080
|
+
__search_offset=0
|
|
1081
|
+
while [[ "\$__search_pattern" == *"\$__input_part"* ]]; do
|
|
1082
|
+
__candidate_prefix="\${__search_pattern%%"\$__input_part"*}"
|
|
1083
|
+
__overlap_start=\$(( __search_offset + \${#__candidate_prefix} + 1 ))
|
|
1084
|
+
__overlap_end=\$(( __overlap_start + \${#__input_part} - 1 ))
|
|
1085
|
+
__syntax_overlap=0
|
|
1086
|
+
# Leading home/directory-stack/named-directory syntax belongs
|
|
1087
|
+
# to the fixed pattern, even when the input overlaps it.
|
|
1088
|
+
if (( __fixed_tilde_end && __overlap_start <= __fixed_tilde_end )); then
|
|
1089
|
+
__syntax_overlap=1
|
|
1090
|
+
fi
|
|
1091
|
+
for (( __range_index=1; __range_index <= \${#__syntax_starts}; __range_index++ )); do
|
|
1092
|
+
if (( __overlap_start <= __syntax_ends[__range_index] &&
|
|
1093
|
+
__overlap_end >= __syntax_starts[__range_index] &&
|
|
1094
|
+
( __overlap_start > __syntax_starts[__range_index] ||
|
|
1095
|
+
__overlap_end < __syntax_ends[__range_index] ) )); then
|
|
1096
|
+
__syntax_overlap=1
|
|
1097
|
+
break
|
|
1098
|
+
fi
|
|
1099
|
+
done
|
|
1100
|
+
# Every position in a recognized fixed attribute qualifier
|
|
1101
|
+
# is backend syntax, including its closing parenthesis.
|
|
1102
|
+
if [[ "\$__fixed_qualifier_base" == *[\\*\\?\\]] ]] &&
|
|
1103
|
+
(( __overlap_end > \${#__fixed_qualifier_base} )); then
|
|
1104
|
+
__syntax_overlap=1
|
|
1105
|
+
fi
|
|
1106
|
+
if (( ! __syntax_overlap )); then
|
|
1107
|
+
if (( __overlap_start == 1 )); then
|
|
1108
|
+
__backend_prefix=""
|
|
1109
|
+
else
|
|
1110
|
+
__backend_prefix="\${pattern[1,__overlap_start-1]}"
|
|
1111
|
+
fi
|
|
1112
|
+
__backend_suffix="\${pattern[__overlap_end+1,-1]}"
|
|
1113
|
+
break 2
|
|
1114
|
+
fi
|
|
1115
|
+
__search_offset=\$__overlap_start
|
|
1116
|
+
__search_pattern="\${pattern[__overlap_start+1,-1]}"
|
|
1117
|
+
done
|
|
1118
|
+
__input_part="\${__input_part[2,-1]}"
|
|
1119
|
+
done
|
|
1120
|
+
local __quoted_input="\${(q)__input_part}"
|
|
1121
|
+
# Pattern matching removes quoting differently from normal
|
|
1122
|
+
# completion. Character classes keep these characters literal.
|
|
1123
|
+
if [[ -n "\$compstate[pattern_match]" ]]; then
|
|
1124
|
+
__quoted_input="\${(b)__input_part}"
|
|
1125
|
+
__quoted_input="\${__quoted_input//\\\$/[\\\$]}"
|
|
1126
|
+
__quoted_input="\${__quoted_input//\\\`/[\\\`]}"
|
|
1127
|
+
fi
|
|
1128
|
+
if [[ "\$pattern" == *'$'* || "\$pattern" == *'\`'* ||
|
|
1129
|
+
( "\$pattern" == '~'* && "\$__tilde_name" == *[^[:alnum:]_.+-]* ) ]] ||
|
|
1130
|
+
(( __code_qualifier )); then
|
|
1131
|
+
__native_pattern="\${(q)pattern}"
|
|
1132
|
+
local __backend_pattern="\$__backend_prefix\$__backend_suffix"
|
|
1133
|
+
local __backend_tilde="\${__backend_pattern#\\~}"
|
|
1134
|
+
__backend_tilde="\${__backend_tilde%%/*}"
|
|
1135
|
+
local __unsafe_backend_qualifier=0
|
|
1136
|
+
__qualifier_pattern="\$__backend_pattern"
|
|
1137
|
+
while _have_glob_qual "\$__qualifier_pattern" complete; do
|
|
1138
|
+
__qualifier="\${match[5]#\\#q}"
|
|
1139
|
+
__qualifier_pattern="\$match[2]"
|
|
1140
|
+
if [[ "\$__qualifier" != \${~__safe_qualifiers} ]]; then
|
|
1141
|
+
__unsafe_backend_qualifier=1
|
|
1142
|
+
fi
|
|
1143
|
+
done
|
|
1144
|
+
# Retain trusted wildcards and safe attribute qualifiers when
|
|
1145
|
+
# all executable syntax belongs to the quoted input.
|
|
1146
|
+
if [[ -n "\$__input_part" &&
|
|
1147
|
+
"\$__backend_pattern" != *'$'* &&
|
|
1148
|
+
"\$__backend_pattern" != *'\`'* &&
|
|
1149
|
+
( "\$__backend_pattern" != '~'* ||
|
|
1150
|
+
"\$__backend_tilde" != *[^[:alnum:]_.+-]* ) ]] &&
|
|
1151
|
+
(( ! __unsafe_backend_qualifier )); then
|
|
1152
|
+
__native_pattern="\$__backend_prefix\$__quoted_input\$__backend_suffix"
|
|
1153
|
+
elif [[ "\$__backend_pattern" != *'$('* &&
|
|
1154
|
+
"\$__backend_pattern" != *'\${'* &&
|
|
1155
|
+
"\$__backend_pattern" != *'$['* &&
|
|
1156
|
+
( "\$pattern" != '~'* ||
|
|
1157
|
+
"\$__tilde_name" != *[^[:alnum:]_.+-]* ) ]] &&
|
|
1158
|
+
(( ! __unsafe_backend_qualifier )); then
|
|
1159
|
+
# Fixed patterns also need literal substitution markers,
|
|
1160
|
+
# without quoting their wildcards or safe qualifiers.
|
|
1161
|
+
local __dollar='\\\$'
|
|
1162
|
+
local __backtick='\\\`'
|
|
1163
|
+
if [[ -n "\$compstate[pattern_match]" ]]; then
|
|
1164
|
+
__dollar='[\$]'
|
|
1165
|
+
__backtick='[\`]'
|
|
1166
|
+
fi
|
|
1167
|
+
# Existing glob classes already protect their markers.
|
|
1168
|
+
local -a __backend_parts=()
|
|
1169
|
+
local __part __escaped __char __class_kind
|
|
1170
|
+
local -i __index __in_class __class_start
|
|
1171
|
+
for __part in "\$__backend_prefix" "\$__backend_suffix"; do
|
|
1172
|
+
__escaped=""
|
|
1173
|
+
__in_class=0
|
|
1174
|
+
__class_kind=""
|
|
1175
|
+
for (( __index=1; __index <= \${#__part}; __index++ )); do
|
|
1176
|
+
__char="\${__part[__index]}"
|
|
1177
|
+
if [[ "\$__char" == '\\' ]]; then
|
|
1178
|
+
__escaped+="\$__char\${__part[__index+1]}"
|
|
1179
|
+
(( __index++ ))
|
|
1180
|
+
continue
|
|
1181
|
+
elif (( __in_class )); then
|
|
1182
|
+
if [[ "\$__char" == '[' && "\${__part[__index+1]}" == [:.=] ]]; then
|
|
1183
|
+
__class_kind="\${__part[__index+1]}"
|
|
1184
|
+
elif [[ -n "\$__class_kind" && "\$__char" == "\$__class_kind" && "\${__part[__index+1]}" == ']' ]]; then
|
|
1185
|
+
__escaped+="\$__char]"
|
|
1186
|
+
(( __index++ ))
|
|
1187
|
+
__class_kind=""
|
|
1188
|
+
continue
|
|
1189
|
+
elif [[ -z "\$__class_kind" && "\$__char" == ']' ]] && (( __index > __class_start )); then
|
|
1190
|
+
__in_class=0
|
|
1191
|
+
elif (( __index == __class_start )) && [[ "\$__char" == '!' || "\$__char" == '^' ]]; then
|
|
1192
|
+
(( __class_start++ ))
|
|
1193
|
+
fi
|
|
1194
|
+
elif [[ "\$__char" == '[' ]]; then
|
|
1195
|
+
__in_class=1
|
|
1196
|
+
__class_start=$(( __index + 1 ))
|
|
1197
|
+
elif [[ "\$__char" == '$' ]]; then
|
|
1198
|
+
__char="\$__dollar"
|
|
1199
|
+
elif [[ "\$__char" == '\`' ]]; then
|
|
1200
|
+
__char="\$__backtick"
|
|
1201
|
+
fi
|
|
1202
|
+
__escaped+="\$__char"
|
|
1203
|
+
done
|
|
1204
|
+
__backend_parts+=("\$__escaped")
|
|
1205
|
+
done
|
|
1206
|
+
__backend_prefix="\${__backend_parts[1]}"
|
|
1207
|
+
__backend_suffix="\${__backend_parts[2]}"
|
|
1208
|
+
__native_pattern="\$__backend_prefix\$__quoted_input\$__backend_suffix"
|
|
1209
|
+
fi
|
|
1210
|
+
else
|
|
1211
|
+
# Quote retained metacharacters, including segments made only
|
|
1212
|
+
# of them. Wildcard-only overlap remains backend syntax.
|
|
1213
|
+
if [[ "\$__input_part" == *[\\\\\\(\\)\\[\\#\\^\\|\\~\\<\\>\\*\\?]* &&
|
|
1214
|
+
"\$__input_part" != [\\*\\?]## ]]; then
|
|
1215
|
+
__native_pattern="\$__backend_prefix\$__quoted_input\$__backend_suffix"
|
|
1216
|
+
fi
|
|
1217
|
+
fi
|
|
1218
|
+
PREFIX="\$__native_pattern"
|
|
1219
|
+
# Match simple fixed tilde globs through native -g filters. The
|
|
1220
|
+
# pattern matcher otherwise treats the leading ~ as glob syntax,
|
|
1221
|
+
# even after _path_files has expanded it for filesystem lookup.
|
|
1222
|
+
if (( __fixed_tilde_end )) && [[ -z "\$__input_part" &&
|
|
1223
|
+
"\$__native_pattern" == "\$pattern" && "\$pattern" == */* ]]; then
|
|
1224
|
+
__initial_directory="\${pattern%/*}/"
|
|
1225
|
+
__initial_leaf="\${pattern##*/}"
|
|
1226
|
+
if [[ "\$__initial_leaf" == *[\*\?]* &&
|
|
1227
|
+
"\$__initial_directory" != *[\*\?]* &&
|
|
1228
|
+
"\$__initial_directory" != *["[]()"]* &&
|
|
1229
|
+
"\$__initial_leaf" != *["[]()#~^|<>;&"]* &&
|
|
1230
|
+
"\$__initial_leaf" != *'\\'* &&
|
|
1231
|
+
"\$__initial_leaf" != *[[:space:]]* &&
|
|
1232
|
+
"\$__initial_leaf" != *"'"* && "\$__initial_leaf" != *'"'* ]]; then
|
|
1233
|
+
__initial_file_pattern="\$__initial_leaf"
|
|
1234
|
+
PREFIX="\$__initial_directory"
|
|
1235
|
+
compstate[pattern_match]=""
|
|
1236
|
+
__pattern_match_changed=1
|
|
1237
|
+
fi
|
|
1238
|
+
fi
|
|
917
1239
|
fi
|
|
918
1240
|
fi
|
|
919
1241
|
|
|
@@ -935,6 +1257,9 @@ function ${functionName} () {
|
|
|
935
1257
|
# Regular-file suggestions retain symlinked files; initial
|
|
936
1258
|
# any globs use a non-directory qualifier instead.
|
|
937
1259
|
local file_pattern="\${ext_pattern:-*}(#q-.)"
|
|
1260
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1261
|
+
file_pattern="\${ext_pattern:-*}(#q-.e:${fixedGlobFunction}:)"
|
|
1262
|
+
fi
|
|
938
1263
|
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
939
1264
|
local __file_qualifier="-."
|
|
940
1265
|
[[ "\$type" == "any" ]] && __file_qualifier="^-/"
|
|
@@ -950,6 +1275,21 @@ function ${functionName} () {
|
|
|
950
1275
|
fi
|
|
951
1276
|
file_pattern="\${(j: :)__initial_file_patterns}"
|
|
952
1277
|
fi
|
|
1278
|
+
local file_pattern_prefix="\$PREFIX"
|
|
1279
|
+
local -a match mbegin mend
|
|
1280
|
+
# Native qualifier copying concatenates flags with the explicit
|
|
1281
|
+
# regular-file group. Keep backend attributes in their own group.
|
|
1282
|
+
if [[ -n "\$compstate[pattern_match]" && -z "\$SUFFIX" ]]; then
|
|
1283
|
+
local file_qualifier file_qualifiers=""
|
|
1284
|
+
while _have_glob_qual "\$file_pattern_prefix" complete; do
|
|
1285
|
+
file_qualifier="\${match[5]#\\#q}"
|
|
1286
|
+
file_pattern_prefix="\$match[2]"
|
|
1287
|
+
file_qualifiers="(#q\$file_qualifier)\$file_qualifiers"
|
|
1288
|
+
done
|
|
1289
|
+
if [[ -n "\$file_qualifiers" ]]; then
|
|
1290
|
+
file_pattern+="\$file_qualifiers"
|
|
1291
|
+
fi
|
|
1292
|
+
fi
|
|
953
1293
|
local file_subtree_pattern="**/*(#q/N)"
|
|
954
1294
|
local -a file_subtree=() file_recursive_patterns=()
|
|
955
1295
|
local -a file_tags=(files directories)
|
|
@@ -963,15 +1303,22 @@ function ${functionName} () {
|
|
|
963
1303
|
local file_matched=0
|
|
964
1304
|
for file_tag in "\${file_tags[@]}"; do
|
|
965
1305
|
if [[ "\$file_tag" == directories ]]; then
|
|
1306
|
+
local file_tag_prefix="\$PREFIX"
|
|
1307
|
+
PREFIX="\$file_pattern_prefix"
|
|
966
1308
|
while _next_label directories expl directory; do
|
|
1309
|
+
# File qualifiers constrain final results, not the
|
|
1310
|
+
# directories needed to navigate to those results.
|
|
967
1311
|
_path_files -/ "\${expl[@]}" && file_matched=1
|
|
968
1312
|
done
|
|
1313
|
+
PREFIX="\$file_tag_prefix"
|
|
969
1314
|
else
|
|
1315
|
+
local file_tag_prefix="\$PREFIX"
|
|
1316
|
+
PREFIX="\$file_pattern_prefix"
|
|
970
1317
|
while _next_label files expl file; do
|
|
971
1318
|
local -a file_ignore=() file_ignore_args=()
|
|
972
1319
|
# Explicit globs bypass automatic fignore. Apply it only
|
|
973
1320
|
# when the tag has not supplied its own ignore patterns.
|
|
974
|
-
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
1321
|
+
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && ( "\$__fixed_glob" != "1" || "\$pattern" == "*" ) && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
975
1322
|
file_ignore=("?*\${^fignore[@]}")
|
|
976
1323
|
file_ignore_args=(-F file_ignore)
|
|
977
1324
|
fi
|
|
@@ -997,12 +1344,17 @@ function ${functionName} () {
|
|
|
997
1344
|
done
|
|
998
1345
|
fi
|
|
999
1346
|
done
|
|
1347
|
+
PREFIX="\$file_tag_prefix"
|
|
1000
1348
|
fi
|
|
1001
1349
|
done
|
|
1002
1350
|
(( file_matched )) && break
|
|
1003
1351
|
done
|
|
1004
|
-
elif [[ -n "\$ext_pattern" ]]; then
|
|
1352
|
+
elif [[ -n "\$ext_pattern" || "\$__fixed_glob" == "1" ]]; then
|
|
1005
1353
|
local file_pattern="\${ext_pattern}(#q^-/)"
|
|
1354
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1355
|
+
# Put the predicate before ^, which negates later qualifiers.
|
|
1356
|
+
file_pattern="\${ext_pattern:-*}(#qe:${fixedGlobFunction}:^-/)"
|
|
1357
|
+
fi
|
|
1006
1358
|
_wanted files expl file _path_files -g "\${file_pattern}"
|
|
1007
1359
|
_wanted directories expl directory _path_files -/
|
|
1008
1360
|
else
|
|
@@ -1021,7 +1373,11 @@ function ${functionName} () {
|
|
|
1021
1373
|
# Restore PREFIX and glob_dots to their previous state
|
|
1022
1374
|
PREFIX="\$__saved_prefix"
|
|
1023
1375
|
if [[ "\$__was_extended_glob" == "1" ]]; then setopt extended_glob; else unsetopt extended_glob; fi
|
|
1376
|
+
if (( __pattern_match_changed )); then
|
|
1377
|
+
compstate[pattern_match]="\$__saved_pattern_match"
|
|
1378
|
+
fi
|
|
1024
1379
|
if [[ "\$__was_sh_glob" == "1" ]]; then setopt sh_glob; else unsetopt sh_glob; fi
|
|
1380
|
+
if [[ "\$__was_sh_word_split" == "1" ]]; then setopt sh_word_split; else unsetopt sh_word_split; fi
|
|
1025
1381
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
1026
1382
|
else
|
|
1027
1383
|
# Regular literal completion
|
|
@@ -1038,20 +1394,31 @@ function ${functionName} () {
|
|
|
1038
1394
|
# Prepare completion with descriptions for _describe
|
|
1039
1395
|
local -a matches
|
|
1040
1396
|
local -i i
|
|
1397
|
+
local __match
|
|
1041
1398
|
for (( i=1; i <= \${#completions[@]}; i++ )); do
|
|
1042
|
-
# _describe
|
|
1043
|
-
|
|
1044
|
-
|
|
1399
|
+
# _describe uses backslashes to escape its colon separator.
|
|
1400
|
+
__match="\${completions[i]//\\\\/\\\\\\\\}"
|
|
1401
|
+
__match="\${__match//:/\\\\:}"
|
|
1045
1402
|
if [[ -n "\${descriptions[i]}" ]]; then
|
|
1046
|
-
matches+=("\$
|
|
1403
|
+
matches+=("\$__match:\${descriptions[i]}")
|
|
1047
1404
|
else
|
|
1048
|
-
matches+=("\$
|
|
1405
|
+
matches+=("\$__match")
|
|
1049
1406
|
fi
|
|
1050
1407
|
done
|
|
1051
|
-
|
|
1408
|
+
if [[ -n "\$QISUFFIX" ]]; then
|
|
1409
|
+
# A closing quote already exists; do not append a space inside it.
|
|
1410
|
+
_describe 'commands' matches -S ''
|
|
1411
|
+
else
|
|
1412
|
+
_describe 'commands' matches
|
|
1413
|
+
fi
|
|
1052
1414
|
fi
|
|
1053
1415
|
}
|
|
1054
1416
|
|
|
1417
|
+
function ${fixedGlobFunction} () {
|
|
1418
|
+
local __fixed_candidate="\${REPLY:a}"
|
|
1419
|
+
(( \${__fixed_matches[(Ie)\$__fixed_candidate]} ))
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1055
1422
|
if (( $+functions[compdef] )); then
|
|
1056
1423
|
compdef ${functionName} ${programName}
|
|
1057
1424
|
fi
|
|
@@ -1685,12 +2052,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1685
2052
|
# prefix for incremental narrowing. Normalize path separators
|
|
1686
2053
|
# before comparing so that Windows backslashes match forward slashes
|
|
1687
2054
|
# in the transported pattern.
|
|
2055
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1688
2056
|
let glob_base = if ($pattern | is-not-empty) {
|
|
1689
2057
|
# Normalize separators and leading ./; downcase only on Windows
|
|
1690
2058
|
# where filesystems are typically case-insensitive
|
|
1691
2059
|
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1692
2060
|
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1693
|
-
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1694
2061
|
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
1695
2062
|
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
1696
2063
|
# For wildcard patterns, compare only the directory prefix
|
|
@@ -1709,6 +2076,21 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1709
2076
|
$prefix
|
|
1710
2077
|
}
|
|
1711
2078
|
|
|
2079
|
+
# Intersect fixed glob matches with the narrowed prefix candidates.
|
|
2080
|
+
# Directory-only suggestions retain their existing navigation behavior.
|
|
2081
|
+
let narrow_fixed_glob = ($type != "directory") and ($pattern =~ '[*?]') and ($prefix != $pattern) and ($glob_base == $prefix)
|
|
2082
|
+
let fixed_matches = if $narrow_fixed_glob {
|
|
2083
|
+
try {
|
|
2084
|
+
let fixed_pattern = ($pattern | into glob)
|
|
2085
|
+
let include_hidden = $hidden or ($prefix | path basename | str starts-with '.')
|
|
2086
|
+
let matches = if $include_hidden { ls -a --directory $fixed_pattern } else { ls --directory $fixed_pattern }
|
|
2087
|
+
$matches | each {|item|
|
|
2088
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
2089
|
+
if $is_win { $name | str downcase } else { $name }
|
|
2090
|
+
}
|
|
2091
|
+
} catch { [] }
|
|
2092
|
+
} else { [] }
|
|
2093
|
+
|
|
1712
2094
|
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
1713
2095
|
# append one so that ls enumerates its contents instead of matching
|
|
1714
2096
|
# siblings like "src-old". path expand follows symlinks, so that
|
|
@@ -1831,6 +2213,17 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1831
2213
|
}
|
|
1832
2214
|
}
|
|
1833
2215
|
|
|
2216
|
+
let filtered = if $narrow_fixed_glob {
|
|
2217
|
+
$filtered | where {|item|
|
|
2218
|
+
# Any-mode listings already include directory symlinks; keep them
|
|
2219
|
+
# navigable without changing their existing completion rendering.
|
|
2220
|
+
let navigation_dir = ($item.type == dir) or ($item.type == symlink and (try { ($item.name | path expand | path type) == "dir" } catch { false }))
|
|
2221
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
2222
|
+
let normalized_name = if $is_win { $name | str downcase } else { $name }
|
|
2223
|
+
$navigation_dir or ($normalized_name in $fixed_matches)
|
|
2224
|
+
}
|
|
2225
|
+
} else { $filtered }
|
|
2226
|
+
|
|
1834
2227
|
# Extract directory prefix to preserve in completion text
|
|
1835
2228
|
let dir_prefix = if ($glob_base | is-empty) {
|
|
1836
2229
|
""
|
|
@@ -2106,6 +2499,9 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2106
2499
|
\$prefixFromInput = \$valuePrefix -and \$prefix -ceq \$valuePrefix
|
|
2107
2500
|
\$lookupPrefix = if (\$prefixFromInput -and \$null -ne \$quoteType -and \$prefix.StartsWith('~')) { './' + \$prefix } else { \$prefix }
|
|
2108
2501
|
|
|
2502
|
+
# Detect narrowing before directory normalization changes prefix.
|
|
2503
|
+
\$narrowFixedGlob = \$type -ne 'directory' -and \$pattern -match '[\*\?]' -and \$prefix -ceq \$valuePrefix -and \$pattern -cne \$valuePrefix
|
|
2504
|
+
|
|
2109
2505
|
# If prefix is a directory without trailing slash, append
|
|
2110
2506
|
# one so Get-ChildItem lists its contents
|
|
2111
2507
|
if (\$prefix -and (Test-Path -Path \$lookupPrefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
@@ -2117,6 +2513,16 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2117
2513
|
# characters, use it as-is; otherwise append *
|
|
2118
2514
|
\$globPath = if (\$lookupPrefix -match '[\*\?]') { \$lookupPrefix } else { "\${lookupPrefix}*" }
|
|
2119
2515
|
|
|
2516
|
+
# Keep the original native glob as an independent file filter
|
|
2517
|
+
# when the current word narrows its candidates.
|
|
2518
|
+
\$fixedMatches = @()
|
|
2519
|
+
if (\$narrowFixedGlob) {
|
|
2520
|
+
# Include hidden parents here; candidate enumeration still
|
|
2521
|
+
# controls which hidden children can be suggested.
|
|
2522
|
+
\$fixedMatches = @(Get-Item -Force -Path \$pattern -ErrorAction SilentlyContinue |
|
|
2523
|
+
Select-Object -ExpandProperty FullName)
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2120
2526
|
# Get file system items based on type
|
|
2121
2527
|
\$items = @()
|
|
2122
2528
|
switch (\$type) {
|
|
@@ -2169,6 +2575,14 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2169
2575
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
2170
2576
|
}
|
|
2171
2577
|
|
|
2578
|
+
if (\$narrowFixedGlob) {
|
|
2579
|
+
\$items = \$items | Where-Object {
|
|
2580
|
+
if (\$_.PSIsContainer) { \$true }
|
|
2581
|
+
elseif (\$IsWindows) { \$fixedMatches -contains \$_.FullName }
|
|
2582
|
+
else { \$fixedMatches -ccontains \$_.FullName }
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2172
2586
|
# Extract directory prefix to preserve in completion text
|
|
2173
2587
|
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
2174
2588
|
\$slashIdx = [Math]::Max(\$prefix.LastIndexOf('/'), \$prefix.LastIndexOf('\\'))
|