@optique/core 1.0.15-dev.2883 → 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.
@@ -808,10 +808,24 @@ const zsh = {
808
808
  const fixedGlobFunction = "__optique-fixed-glob-" + Array.from(programName, (character) => character.charCodeAt(0).toString(16).padStart(2, "0")).join("");
809
809
  return `#compdef ${programName}
810
810
  function ${functionName} () {
811
- local current="\$words[CURRENT]"
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"
812
815
  # Let zsh match the suffix when completing in the middle of a word.
813
816
  if [[ -o complete_in_word ]]; then
814
- current="\$PREFIX"
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}"
815
829
  fi
816
830
  local -a prev
817
831
  # Extract previous arguments, skipping empty ones
@@ -819,7 +833,7 @@ function ${functionName} () {
819
833
  local i
820
834
  for (( i=2; i < CURRENT; i++ )); do
821
835
  if [[ -n "\$words[i]" ]]; then
822
- prev+=("\$words[i]")
836
+ prev+=("\${(Q)words[i]}")
823
837
  fi
824
838
  done
825
839
 
@@ -906,16 +920,20 @@ function ${functionName} () {
906
920
  fi
907
921
  fi
908
922
  fi
909
- if [[ -n "\$pattern" && -z "\$__initial_file_pattern" ]]; then
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
910
928
  # Normalize leading ./ so that ./src/ma matches pattern src/
911
- local __norm_prefix="\${PREFIX#./}"
929
+ local __norm_prefix="\${__logical_prefix#./}"
912
930
  local __norm_pattern="\${pattern#./}"
913
931
  local __compare_pattern="\$__norm_pattern"
914
932
  if [[ "\$__compare_pattern" == *[\*\?]* ]]; then
915
933
  __compare_pattern="\${__compare_pattern%%[\*\?]*}"
916
934
  [[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
917
935
  fi
918
- if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
936
+ if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$__logical_prefix" != "\$pattern" ]]; then
919
937
  # Keep the typed prefix, but retain a fixed file glob separately.
920
938
  if [[ "\$type" != "directory" && "\$__norm_pattern" == *[\*\?]* ]]; then
921
939
  __fixed_glob=1
@@ -981,7 +999,243 @@ function ${functionName} () {
981
999
  if [[ "\$__fixed_was_glob_dots" == "0" && "\${PREFIX##*/}" != .* ]]; then unsetopt glob_dots; fi
982
1000
  fi
983
1001
  else
984
- PREFIX="\$pattern"
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
985
1239
  fi
986
1240
  fi
987
1241
 
@@ -1021,6 +1275,21 @@ function ${functionName} () {
1021
1275
  fi
1022
1276
  file_pattern="\${(j: :)__initial_file_patterns}"
1023
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
1024
1293
  local file_subtree_pattern="**/*(#q/N)"
1025
1294
  local -a file_subtree=() file_recursive_patterns=()
1026
1295
  local -a file_tags=(files directories)
@@ -1034,10 +1303,17 @@ function ${functionName} () {
1034
1303
  local file_matched=0
1035
1304
  for file_tag in "\${file_tags[@]}"; do
1036
1305
  if [[ "\$file_tag" == directories ]]; then
1306
+ local file_tag_prefix="\$PREFIX"
1307
+ PREFIX="\$file_pattern_prefix"
1037
1308
  while _next_label directories expl directory; do
1309
+ # File qualifiers constrain final results, not the
1310
+ # directories needed to navigate to those results.
1038
1311
  _path_files -/ "\${expl[@]}" && file_matched=1
1039
1312
  done
1313
+ PREFIX="\$file_tag_prefix"
1040
1314
  else
1315
+ local file_tag_prefix="\$PREFIX"
1316
+ PREFIX="\$file_pattern_prefix"
1041
1317
  while _next_label files expl file; do
1042
1318
  local -a file_ignore=() file_ignore_args=()
1043
1319
  # Explicit globs bypass automatic fignore. Apply it only
@@ -1068,6 +1344,7 @@ function ${functionName} () {
1068
1344
  done
1069
1345
  fi
1070
1346
  done
1347
+ PREFIX="\$file_tag_prefix"
1071
1348
  fi
1072
1349
  done
1073
1350
  (( file_matched )) && break
@@ -1096,6 +1373,9 @@ function ${functionName} () {
1096
1373
  # Restore PREFIX and glob_dots to their previous state
1097
1374
  PREFIX="\$__saved_prefix"
1098
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
1099
1379
  if [[ "\$__was_sh_glob" == "1" ]]; then setopt sh_glob; else unsetopt sh_glob; fi
1100
1380
  if [[ "\$__was_sh_word_split" == "1" ]]; then setopt sh_word_split; else unsetopt sh_word_split; fi
1101
1381
  if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
@@ -1114,17 +1394,23 @@ function ${functionName} () {
1114
1394
  # Prepare completion with descriptions for _describe
1115
1395
  local -a matches
1116
1396
  local -i i
1397
+ local __match
1117
1398
  for (( i=1; i <= \${#completions[@]}; i++ )); do
1118
- # _describe treats colons as separators and backslashes as escapes.
1119
- value="\${completions[i]//\\\\/\\\\\\\\}"
1120
- value="\${value//:/\\\\:}"
1399
+ # _describe uses backslashes to escape its colon separator.
1400
+ __match="\${completions[i]//\\\\/\\\\\\\\}"
1401
+ __match="\${__match//:/\\\\:}"
1121
1402
  if [[ -n "\${descriptions[i]}" ]]; then
1122
- matches+=("\${value}:\${descriptions[i]}")
1403
+ matches+=("\$__match:\${descriptions[i]}")
1123
1404
  else
1124
- matches+=("\${value}")
1405
+ matches+=("\$__match")
1125
1406
  fi
1126
1407
  done
1127
- _describe 'commands' matches
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
1128
1414
  fi
1129
1415
  }
1130
1416
 
@@ -808,10 +808,24 @@ const zsh = {
808
808
  const fixedGlobFunction = "__optique-fixed-glob-" + Array.from(programName, (character) => character.charCodeAt(0).toString(16).padStart(2, "0")).join("");
809
809
  return `#compdef ${programName}
810
810
  function ${functionName} () {
811
- local current="\$words[CURRENT]"
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"
812
815
  # Let zsh match the suffix when completing in the middle of a word.
813
816
  if [[ -o complete_in_word ]]; then
814
- current="\$PREFIX"
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}"
815
829
  fi
816
830
  local -a prev
817
831
  # Extract previous arguments, skipping empty ones
@@ -819,7 +833,7 @@ function ${functionName} () {
819
833
  local i
820
834
  for (( i=2; i < CURRENT; i++ )); do
821
835
  if [[ -n "\$words[i]" ]]; then
822
- prev+=("\$words[i]")
836
+ prev+=("\${(Q)words[i]}")
823
837
  fi
824
838
  done
825
839
 
@@ -906,16 +920,20 @@ function ${functionName} () {
906
920
  fi
907
921
  fi
908
922
  fi
909
- if [[ -n "\$pattern" && -z "\$__initial_file_pattern" ]]; then
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
910
928
  # Normalize leading ./ so that ./src/ma matches pattern src/
911
- local __norm_prefix="\${PREFIX#./}"
929
+ local __norm_prefix="\${__logical_prefix#./}"
912
930
  local __norm_pattern="\${pattern#./}"
913
931
  local __compare_pattern="\$__norm_pattern"
914
932
  if [[ "\$__compare_pattern" == *[\*\?]* ]]; then
915
933
  __compare_pattern="\${__compare_pattern%%[\*\?]*}"
916
934
  [[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
917
935
  fi
918
- if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
936
+ if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$__logical_prefix" != "\$pattern" ]]; then
919
937
  # Keep the typed prefix, but retain a fixed file glob separately.
920
938
  if [[ "\$type" != "directory" && "\$__norm_pattern" == *[\*\?]* ]]; then
921
939
  __fixed_glob=1
@@ -981,7 +999,243 @@ function ${functionName} () {
981
999
  if [[ "\$__fixed_was_glob_dots" == "0" && "\${PREFIX##*/}" != .* ]]; then unsetopt glob_dots; fi
982
1000
  fi
983
1001
  else
984
- PREFIX="\$pattern"
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
985
1239
  fi
986
1240
  fi
987
1241
 
@@ -1021,6 +1275,21 @@ function ${functionName} () {
1021
1275
  fi
1022
1276
  file_pattern="\${(j: :)__initial_file_patterns}"
1023
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
1024
1293
  local file_subtree_pattern="**/*(#q/N)"
1025
1294
  local -a file_subtree=() file_recursive_patterns=()
1026
1295
  local -a file_tags=(files directories)
@@ -1034,10 +1303,17 @@ function ${functionName} () {
1034
1303
  local file_matched=0
1035
1304
  for file_tag in "\${file_tags[@]}"; do
1036
1305
  if [[ "\$file_tag" == directories ]]; then
1306
+ local file_tag_prefix="\$PREFIX"
1307
+ PREFIX="\$file_pattern_prefix"
1037
1308
  while _next_label directories expl directory; do
1309
+ # File qualifiers constrain final results, not the
1310
+ # directories needed to navigate to those results.
1038
1311
  _path_files -/ "\${expl[@]}" && file_matched=1
1039
1312
  done
1313
+ PREFIX="\$file_tag_prefix"
1040
1314
  else
1315
+ local file_tag_prefix="\$PREFIX"
1316
+ PREFIX="\$file_pattern_prefix"
1041
1317
  while _next_label files expl file; do
1042
1318
  local -a file_ignore=() file_ignore_args=()
1043
1319
  # Explicit globs bypass automatic fignore. Apply it only
@@ -1068,6 +1344,7 @@ function ${functionName} () {
1068
1344
  done
1069
1345
  fi
1070
1346
  done
1347
+ PREFIX="\$file_tag_prefix"
1071
1348
  fi
1072
1349
  done
1073
1350
  (( file_matched )) && break
@@ -1096,6 +1373,9 @@ function ${functionName} () {
1096
1373
  # Restore PREFIX and glob_dots to their previous state
1097
1374
  PREFIX="\$__saved_prefix"
1098
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
1099
1379
  if [[ "\$__was_sh_glob" == "1" ]]; then setopt sh_glob; else unsetopt sh_glob; fi
1100
1380
  if [[ "\$__was_sh_word_split" == "1" ]]; then setopt sh_word_split; else unsetopt sh_word_split; fi
1101
1381
  if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
@@ -1114,17 +1394,23 @@ function ${functionName} () {
1114
1394
  # Prepare completion with descriptions for _describe
1115
1395
  local -a matches
1116
1396
  local -i i
1397
+ local __match
1117
1398
  for (( i=1; i <= \${#completions[@]}; i++ )); do
1118
- # _describe treats colons as separators and backslashes as escapes.
1119
- value="\${completions[i]//\\\\/\\\\\\\\}"
1120
- value="\${value//:/\\\\:}"
1399
+ # _describe uses backslashes to escape its colon separator.
1400
+ __match="\${completions[i]//\\\\/\\\\\\\\}"
1401
+ __match="\${__match//:/\\\\:}"
1121
1402
  if [[ -n "\${descriptions[i]}" ]]; then
1122
- matches+=("\${value}:\${descriptions[i]}")
1403
+ matches+=("\$__match:\${descriptions[i]}")
1123
1404
  else
1124
- matches+=("\${value}")
1405
+ matches+=("\$__match")
1125
1406
  fi
1126
1407
  done
1127
- _describe 'commands' matches
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
1128
1414
  fi
1129
1415
  }
1130
1416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.15-dev.2883",
3
+ "version": "1.0.15-dev.2887",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",