@optique/core 1.0.15-dev.2873 → 1.0.15-dev.2883
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 +241 -62
- package/dist/completion.js +241 -62
- package/package.json +1 -1
package/dist/completion.cjs
CHANGED
|
@@ -805,6 +805,7 @@ 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
|
local current="\$words[CURRENT]"
|
|
@@ -859,6 +860,11 @@ function ${functionName} () {
|
|
|
859
860
|
local __was_sh_glob=0
|
|
860
861
|
[[ -o sh_glob ]] && __was_sh_glob=1
|
|
861
862
|
unsetopt sh_glob
|
|
863
|
+
local __was_extended_glob=0
|
|
864
|
+
[[ -o extended_glob ]] && __was_extended_glob=1
|
|
865
|
+
local __was_sh_word_split=0
|
|
866
|
+
[[ -o sh_word_split ]] && __was_sh_word_split=1
|
|
867
|
+
unsetopt sh_word_split
|
|
862
868
|
|
|
863
869
|
# Enable glob_dots when hidden files are requested so that
|
|
864
870
|
# _files and _directories include dot-prefixed entries
|
|
@@ -871,7 +877,36 @@ function ${functionName} () {
|
|
|
871
877
|
# current word. If the user has already typed beyond the pattern,
|
|
872
878
|
# keep PREFIX unchanged for incremental narrowing.
|
|
873
879
|
local __saved_prefix="\$PREFIX"
|
|
874
|
-
|
|
880
|
+
local __fixed_glob=0
|
|
881
|
+
local -a __fixed_matches
|
|
882
|
+
local __initial_file_pattern="" __initial_directory="" __initial_leaf=""
|
|
883
|
+
if [[ ( "\$type" == "file" || "\$type" == "any" || "\$type" == "directory" ) &&
|
|
884
|
+
-z "\$current\$PREFIX\$SUFFIX" &&
|
|
885
|
+
-z "\${compstate[pattern_match]}" &&
|
|
886
|
+
"\$pattern" != /* && "\$pattern" != "~"* ]]; then
|
|
887
|
+
if [[ "\$pattern" == */* ]]; then
|
|
888
|
+
__initial_directory="\${pattern%/*}/"
|
|
889
|
+
fi
|
|
890
|
+
__initial_leaf="\${pattern##*/}"
|
|
891
|
+
# Native -g filters are split as shell words and use extended glob
|
|
892
|
+
# syntax. Keep unsupported filename forms on the existing path.
|
|
893
|
+
if [[ "\$__initial_leaf" == *[\*\?]* &&
|
|
894
|
+
"\$__initial_directory" != *[\*\?]* &&
|
|
895
|
+
"\$__initial_directory" != *["[]()"]* &&
|
|
896
|
+
"\$__initial_leaf" != *["[]"]* &&
|
|
897
|
+
"\$__initial_leaf" != *'\\'* &&
|
|
898
|
+
"\$__initial_leaf" != *[[:space:]]* &&
|
|
899
|
+
"\$__initial_leaf" != *["#~^()|<>;&"]* &&
|
|
900
|
+
"\$__initial_leaf" != *"'"* && "\$__initial_leaf" != *'"'* ]]; then
|
|
901
|
+
__initial_file_pattern="\$__initial_leaf"
|
|
902
|
+
# Only the literal directory belongs in PREFIX. Quote it so
|
|
903
|
+
# spaces and shell metacharacters remain literal path text.
|
|
904
|
+
if [[ -n "\$__initial_directory" ]]; then
|
|
905
|
+
PREFIX="\${(q)__initial_directory}"
|
|
906
|
+
fi
|
|
907
|
+
fi
|
|
908
|
+
fi
|
|
909
|
+
if [[ -n "\$pattern" && -z "\$__initial_file_pattern" ]]; then
|
|
875
910
|
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
876
911
|
local __norm_prefix="\${PREFIX#./}"
|
|
877
912
|
local __norm_pattern="\${pattern#./}"
|
|
@@ -881,8 +916,70 @@ function ${functionName} () {
|
|
|
881
916
|
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
882
917
|
fi
|
|
883
918
|
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
|
|
884
|
-
#
|
|
885
|
-
|
|
919
|
+
# Keep the typed prefix, but retain a fixed file glob separately.
|
|
920
|
+
if [[ "\$type" != "directory" && "\$__norm_pattern" == *[\*\?]* ]]; then
|
|
921
|
+
__fixed_glob=1
|
|
922
|
+
local __fixed_was_glob_dots=0
|
|
923
|
+
[[ -o glob_dots ]] && __fixed_was_glob_dots=1
|
|
924
|
+
if [[ "\$PREFIX" == .* || "\$PREFIX" == */.* ]]; then setopt glob_dots; fi
|
|
925
|
+
# Keep qualifiers in a string so sourcing also works with SH_GLOB.
|
|
926
|
+
local __fixed_pattern="" __glob_char="" __glob_previous=""
|
|
927
|
+
local __glob_escaped=0
|
|
928
|
+
local __glob_class=0 __glob_class_start=0 __glob_class_nested=""
|
|
929
|
+
local -i __glob_index
|
|
930
|
+
for (( __glob_index=1; __glob_index <= \${#pattern}; __glob_index++ )); do
|
|
931
|
+
__glob_char="\${pattern[__glob_index]}"
|
|
932
|
+
if [[ "\$__glob_class" == "1" ]]; then
|
|
933
|
+
# Class members are already literal. Preserve leading ]
|
|
934
|
+
# and nested POSIX classes without adding backslashes.
|
|
935
|
+
__fixed_pattern+="\$__glob_char"
|
|
936
|
+
if [[ -n "\$__glob_class_nested" ]]; then
|
|
937
|
+
if [[ "\$__glob_char" == "]" && "\$__glob_previous" == "\$__glob_class_nested" ]]; then
|
|
938
|
+
__glob_class_nested=""
|
|
939
|
+
fi
|
|
940
|
+
elif [[ "\$__glob_char" == "[" && "\${pattern[__glob_index+1]}" == ":" ]]; then
|
|
941
|
+
__glob_class_nested="\${pattern[__glob_index+1]}"
|
|
942
|
+
__glob_class_start=0
|
|
943
|
+
elif [[ "\$__glob_char" == "]" && "\$__glob_class_start" == "0" ]]; then
|
|
944
|
+
__glob_class=0
|
|
945
|
+
elif [[ "\$__glob_class_start" == "1" && ( "\$__glob_char" == "!" || "\$__glob_char" == "^" ) &&
|
|
946
|
+
( "\${pattern[__glob_index+1]}" != "]" || "\${pattern[__glob_index+2,-1]}" == *']'* ) ]]; then
|
|
947
|
+
__glob_class_start=2
|
|
948
|
+
else
|
|
949
|
+
__glob_class_start=0
|
|
950
|
+
fi
|
|
951
|
+
__glob_previous="\$__glob_char"
|
|
952
|
+
continue
|
|
953
|
+
fi
|
|
954
|
+
if [[ "\$__glob_escaped" == "1" ]]; then
|
|
955
|
+
__fixed_pattern+="\$__glob_char"
|
|
956
|
+
__glob_escaped=0
|
|
957
|
+
__glob_previous=""
|
|
958
|
+
continue
|
|
959
|
+
fi
|
|
960
|
+
if [[ "\$__glob_char" == "\\\\" ]]; then
|
|
961
|
+
__fixed_pattern+="\$__glob_char"
|
|
962
|
+
__glob_escaped=1
|
|
963
|
+
elif [[ "\$__glob_char" == "[" ]]; then
|
|
964
|
+
__fixed_pattern+="\$__glob_char"
|
|
965
|
+
__glob_class=1
|
|
966
|
+
__glob_class_start=1
|
|
967
|
+
elif [[ "\$__glob_char" == [#^~\\(\\)\\|\\<\\>] &&
|
|
968
|
+
! ( "\$__glob_char" == "~" && "\$__glob_index" == "1" ) ]]; then
|
|
969
|
+
# Keep class negation and leading home expansion; escape
|
|
970
|
+
# extended-glob operators in literal pathname components.
|
|
971
|
+
__fixed_pattern+="\\\\\$__glob_char"
|
|
972
|
+
else
|
|
973
|
+
__fixed_pattern+="\$__glob_char"
|
|
974
|
+
fi
|
|
975
|
+
__glob_previous="\$__glob_char"
|
|
976
|
+
done
|
|
977
|
+
__fixed_pattern+="(#qN)"
|
|
978
|
+
__fixed_matches=( \${~__fixed_pattern} )
|
|
979
|
+
__fixed_matches=( "\${(@)__fixed_matches:a}" )
|
|
980
|
+
# Hidden parents widen fixed matches, not native child suggestions.
|
|
981
|
+
if [[ "\$__fixed_was_glob_dots" == "0" && "\${PREFIX##*/}" != .* ]]; then unsetopt glob_dots; fi
|
|
982
|
+
fi
|
|
886
983
|
else
|
|
887
984
|
PREFIX="\$pattern"
|
|
888
985
|
fi
|
|
@@ -896,81 +993,111 @@ function ${functionName} () {
|
|
|
896
993
|
ext_pattern="*.\$extensions"
|
|
897
994
|
fi
|
|
898
995
|
fi
|
|
996
|
+
# _wanted/_next_label write to the named explanation parameter.
|
|
997
|
+
# Keep it local for every file directive, including directory/any.
|
|
998
|
+
local -a expl=()
|
|
899
999
|
# Use zsh's native file completion
|
|
900
1000
|
case "\$type" in
|
|
901
|
-
file)
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
while _next_label directories expl directory; do
|
|
919
|
-
_path_files -/ "\${expl[@]}" && file_matched=1
|
|
1001
|
+
file|any)
|
|
1002
|
+
if [[ "\$type" == "file" || -n "\$__initial_file_pattern" ]]; then
|
|
1003
|
+
# Regular-file suggestions retain symlinked files; initial
|
|
1004
|
+
# any globs use a non-directory qualifier instead.
|
|
1005
|
+
local file_pattern="\${ext_pattern:-*}(#q-.)"
|
|
1006
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1007
|
+
file_pattern="\${ext_pattern:-*}(#q-.e:${fixedGlobFunction}:)"
|
|
1008
|
+
fi
|
|
1009
|
+
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
1010
|
+
local __file_qualifier="-."
|
|
1011
|
+
[[ "\$type" == "any" ]] && __file_qualifier="^-/"
|
|
1012
|
+
local -a __initial_file_patterns=()
|
|
1013
|
+
if [[ -n "\$extensions" ]]; then
|
|
1014
|
+
setopt extended_glob
|
|
1015
|
+
local __initial_extension
|
|
1016
|
+
for __initial_extension in "\${(@s:,:)extensions}"; do
|
|
1017
|
+
__initial_file_patterns+=("(\${__initial_file_pattern}~^*.\${__initial_extension})(#q\${__file_qualifier})")
|
|
920
1018
|
done
|
|
921
1019
|
else
|
|
922
|
-
|
|
923
|
-
local -a file_ignore=() file_ignore_args=()
|
|
924
|
-
# Explicit globs bypass automatic fignore. Apply it only
|
|
925
|
-
# when the tag has not supplied its own ignore patterns.
|
|
926
|
-
if [[ -z "\$ext_pattern" && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
927
|
-
file_ignore=("?*\${^fignore[@]}")
|
|
928
|
-
file_ignore_args=(-F file_ignore)
|
|
929
|
-
fi
|
|
930
|
-
if _path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}"; then
|
|
931
|
-
file_matched=1
|
|
932
|
-
elif [[ -z "\$ext_pattern" && "\$PREFIX\$SUFFIX" != */* ]] &&
|
|
933
|
-
zstyle -a ":completion:\${curcontext}:files" recursive-files file_recursive_patterns; then
|
|
934
|
-
# Match _files' recursive fallback, retaining the same
|
|
935
|
-
# file filter and tag-specific ignore options.
|
|
936
|
-
for file_recursive_pattern in "\${file_recursive_patterns[@]}"; do
|
|
937
|
-
if [[ "\$PWD/" = \${~file_recursive_pattern} ]]; then
|
|
938
|
-
if (( \${#file_subtree[@]} == 0 )); then
|
|
939
|
-
file_subtree=(\${~file_subtree_pattern})
|
|
940
|
-
fi
|
|
941
|
-
for file_directory in "\${file_subtree[@]}"; do
|
|
942
|
-
file_prefix="\$PREFIX"
|
|
943
|
-
PREFIX="\$file_directory/\$PREFIX"
|
|
944
|
-
_path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}" && file_matched=1
|
|
945
|
-
PREFIX="\$file_prefix"
|
|
946
|
-
done
|
|
947
|
-
break
|
|
948
|
-
fi
|
|
949
|
-
done
|
|
950
|
-
fi
|
|
951
|
-
done
|
|
1020
|
+
__initial_file_patterns=("\${__initial_file_pattern}(#q\${__file_qualifier})")
|
|
952
1021
|
fi
|
|
1022
|
+
file_pattern="\${(j: :)__initial_file_patterns}"
|
|
1023
|
+
fi
|
|
1024
|
+
local file_subtree_pattern="**/*(#q/N)"
|
|
1025
|
+
local -a file_subtree=() file_recursive_patterns=()
|
|
1026
|
+
local -a file_tags=(files directories)
|
|
1027
|
+
local file_recursive_pattern file_directory file_prefix file_tag
|
|
1028
|
+
if [[ -n "\$__initial_file_pattern" || -z "\$ext_pattern" ]] &&
|
|
1029
|
+
zstyle -t ":completion:\${curcontext}:" list-dirs-first; then
|
|
1030
|
+
file_tags=(directories files)
|
|
1031
|
+
fi
|
|
1032
|
+
_tags "\${file_tags[@]}"
|
|
1033
|
+
while _tags; do
|
|
1034
|
+
local file_matched=0
|
|
1035
|
+
for file_tag in "\${file_tags[@]}"; do
|
|
1036
|
+
if [[ "\$file_tag" == directories ]]; then
|
|
1037
|
+
while _next_label directories expl directory; do
|
|
1038
|
+
_path_files -/ "\${expl[@]}" && file_matched=1
|
|
1039
|
+
done
|
|
1040
|
+
else
|
|
1041
|
+
while _next_label files expl file; do
|
|
1042
|
+
local -a file_ignore=() file_ignore_args=()
|
|
1043
|
+
# Explicit globs bypass automatic fignore. Apply it only
|
|
1044
|
+
# when the tag has not supplied its own ignore patterns.
|
|
1045
|
+
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && ( "\$__fixed_glob" != "1" || "\$pattern" == "*" ) && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
1046
|
+
file_ignore=("?*\${^fignore[@]}")
|
|
1047
|
+
file_ignore_args=(-F file_ignore)
|
|
1048
|
+
fi
|
|
1049
|
+
if _path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}"; then
|
|
1050
|
+
file_matched=1
|
|
1051
|
+
elif [[ ( -n "\$__initial_file_pattern" || -z "\$ext_pattern" ) && "\$PREFIX\$SUFFIX" != */* ]] &&
|
|
1052
|
+
zstyle -a ":completion:\${curcontext}:files" recursive-files file_recursive_patterns; then
|
|
1053
|
+
# Match _files' recursive fallback, retaining the same
|
|
1054
|
+
# file filter and tag-specific ignore options.
|
|
1055
|
+
for file_recursive_pattern in "\${file_recursive_patterns[@]}"; do
|
|
1056
|
+
if [[ "\$PWD/" = \${~file_recursive_pattern} ]]; then
|
|
1057
|
+
if (( \${#file_subtree[@]} == 0 )); then
|
|
1058
|
+
file_subtree=(\${~file_subtree_pattern})
|
|
1059
|
+
fi
|
|
1060
|
+
for file_directory in "\${file_subtree[@]}"; do
|
|
1061
|
+
file_prefix="\$PREFIX"
|
|
1062
|
+
PREFIX="\$file_directory/\$PREFIX"
|
|
1063
|
+
_path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}" && file_matched=1
|
|
1064
|
+
PREFIX="\$file_prefix"
|
|
1065
|
+
done
|
|
1066
|
+
break
|
|
1067
|
+
fi
|
|
1068
|
+
done
|
|
1069
|
+
fi
|
|
1070
|
+
done
|
|
1071
|
+
fi
|
|
1072
|
+
done
|
|
1073
|
+
(( file_matched )) && break
|
|
953
1074
|
done
|
|
954
|
-
|
|
955
|
-
done
|
|
956
|
-
;;
|
|
957
|
-
directory)
|
|
958
|
-
_directories
|
|
959
|
-
;;
|
|
960
|
-
any)
|
|
961
|
-
if [[ -n "\$ext_pattern" ]]; then
|
|
1075
|
+
elif [[ -n "\$ext_pattern" || "\$__fixed_glob" == "1" ]]; then
|
|
962
1076
|
local file_pattern="\${ext_pattern}(#q^-/)"
|
|
1077
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1078
|
+
# Put the predicate before ^, which negates later qualifiers.
|
|
1079
|
+
file_pattern="\${ext_pattern:-*}(#qe:${fixedGlobFunction}:^-/)"
|
|
1080
|
+
fi
|
|
963
1081
|
_wanted files expl file _path_files -g "\${file_pattern}"
|
|
964
1082
|
_wanted directories expl directory _path_files -/
|
|
965
1083
|
else
|
|
966
1084
|
_files
|
|
967
1085
|
fi
|
|
968
1086
|
;;
|
|
1087
|
+
directory)
|
|
1088
|
+
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
1089
|
+
# -/ would add every directory alongside the filtered glob.
|
|
1090
|
+
_wanted directories expl directory _path_files -g "\${__initial_file_pattern}(#q-/)"
|
|
1091
|
+
else
|
|
1092
|
+
_directories
|
|
1093
|
+
fi
|
|
1094
|
+
;;
|
|
969
1095
|
esac
|
|
970
|
-
|
|
971
1096
|
# Restore PREFIX and glob_dots to their previous state
|
|
972
1097
|
PREFIX="\$__saved_prefix"
|
|
1098
|
+
if [[ "\$__was_extended_glob" == "1" ]]; then setopt extended_glob; else unsetopt extended_glob; fi
|
|
973
1099
|
if [[ "\$__was_sh_glob" == "1" ]]; then setopt sh_glob; else unsetopt sh_glob; fi
|
|
1100
|
+
if [[ "\$__was_sh_word_split" == "1" ]]; then setopt sh_word_split; else unsetopt sh_word_split; fi
|
|
974
1101
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
975
1102
|
else
|
|
976
1103
|
# Regular literal completion
|
|
@@ -1001,6 +1128,11 @@ function ${functionName} () {
|
|
|
1001
1128
|
fi
|
|
1002
1129
|
}
|
|
1003
1130
|
|
|
1131
|
+
function ${fixedGlobFunction} () {
|
|
1132
|
+
local __fixed_candidate="\${REPLY:a}"
|
|
1133
|
+
(( \${__fixed_matches[(Ie)\$__fixed_candidate]} ))
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1004
1136
|
if (( $+functions[compdef] )); then
|
|
1005
1137
|
compdef ${functionName} ${programName}
|
|
1006
1138
|
fi
|
|
@@ -1634,12 +1766,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1634
1766
|
# prefix for incremental narrowing. Normalize path separators
|
|
1635
1767
|
# before comparing so that Windows backslashes match forward slashes
|
|
1636
1768
|
# in the transported pattern.
|
|
1769
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1637
1770
|
let glob_base = if ($pattern | is-not-empty) {
|
|
1638
1771
|
# Normalize separators and leading ./; downcase only on Windows
|
|
1639
1772
|
# where filesystems are typically case-insensitive
|
|
1640
1773
|
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1641
1774
|
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1642
|
-
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1643
1775
|
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
1644
1776
|
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
1645
1777
|
# For wildcard patterns, compare only the directory prefix
|
|
@@ -1658,6 +1790,21 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1658
1790
|
$prefix
|
|
1659
1791
|
}
|
|
1660
1792
|
|
|
1793
|
+
# Intersect fixed glob matches with the narrowed prefix candidates.
|
|
1794
|
+
# Directory-only suggestions retain their existing navigation behavior.
|
|
1795
|
+
let narrow_fixed_glob = ($type != "directory") and ($pattern =~ '[*?]') and ($prefix != $pattern) and ($glob_base == $prefix)
|
|
1796
|
+
let fixed_matches = if $narrow_fixed_glob {
|
|
1797
|
+
try {
|
|
1798
|
+
let fixed_pattern = ($pattern | into glob)
|
|
1799
|
+
let include_hidden = $hidden or ($prefix | path basename | str starts-with '.')
|
|
1800
|
+
let matches = if $include_hidden { ls -a --directory $fixed_pattern } else { ls --directory $fixed_pattern }
|
|
1801
|
+
$matches | each {|item|
|
|
1802
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
1803
|
+
if $is_win { $name | str downcase } else { $name }
|
|
1804
|
+
}
|
|
1805
|
+
} catch { [] }
|
|
1806
|
+
} else { [] }
|
|
1807
|
+
|
|
1661
1808
|
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
1662
1809
|
# append one so that ls enumerates its contents instead of matching
|
|
1663
1810
|
# siblings like "src-old". path expand follows symlinks, so that
|
|
@@ -1780,6 +1927,17 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1780
1927
|
}
|
|
1781
1928
|
}
|
|
1782
1929
|
|
|
1930
|
+
let filtered = if $narrow_fixed_glob {
|
|
1931
|
+
$filtered | where {|item|
|
|
1932
|
+
# Any-mode listings already include directory symlinks; keep them
|
|
1933
|
+
# navigable without changing their existing completion rendering.
|
|
1934
|
+
let navigation_dir = ($item.type == dir) or ($item.type == symlink and (try { ($item.name | path expand | path type) == "dir" } catch { false }))
|
|
1935
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
1936
|
+
let normalized_name = if $is_win { $name | str downcase } else { $name }
|
|
1937
|
+
$navigation_dir or ($normalized_name in $fixed_matches)
|
|
1938
|
+
}
|
|
1939
|
+
} else { $filtered }
|
|
1940
|
+
|
|
1783
1941
|
# Extract directory prefix to preserve in completion text
|
|
1784
1942
|
let dir_prefix = if ($glob_base | is-empty) {
|
|
1785
1943
|
""
|
|
@@ -2055,6 +2213,9 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2055
2213
|
\$prefixFromInput = \$valuePrefix -and \$prefix -ceq \$valuePrefix
|
|
2056
2214
|
\$lookupPrefix = if (\$prefixFromInput -and \$null -ne \$quoteType -and \$prefix.StartsWith('~')) { './' + \$prefix } else { \$prefix }
|
|
2057
2215
|
|
|
2216
|
+
# Detect narrowing before directory normalization changes prefix.
|
|
2217
|
+
\$narrowFixedGlob = \$type -ne 'directory' -and \$pattern -match '[\*\?]' -and \$prefix -ceq \$valuePrefix -and \$pattern -cne \$valuePrefix
|
|
2218
|
+
|
|
2058
2219
|
# If prefix is a directory without trailing slash, append
|
|
2059
2220
|
# one so Get-ChildItem lists its contents
|
|
2060
2221
|
if (\$prefix -and (Test-Path -Path \$lookupPrefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
@@ -2066,6 +2227,16 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2066
2227
|
# characters, use it as-is; otherwise append *
|
|
2067
2228
|
\$globPath = if (\$lookupPrefix -match '[\*\?]') { \$lookupPrefix } else { "\${lookupPrefix}*" }
|
|
2068
2229
|
|
|
2230
|
+
# Keep the original native glob as an independent file filter
|
|
2231
|
+
# when the current word narrows its candidates.
|
|
2232
|
+
\$fixedMatches = @()
|
|
2233
|
+
if (\$narrowFixedGlob) {
|
|
2234
|
+
# Include hidden parents here; candidate enumeration still
|
|
2235
|
+
# controls which hidden children can be suggested.
|
|
2236
|
+
\$fixedMatches = @(Get-Item -Force -Path \$pattern -ErrorAction SilentlyContinue |
|
|
2237
|
+
Select-Object -ExpandProperty FullName)
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2069
2240
|
# Get file system items based on type
|
|
2070
2241
|
\$items = @()
|
|
2071
2242
|
switch (\$type) {
|
|
@@ -2118,6 +2289,14 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2118
2289
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
2119
2290
|
}
|
|
2120
2291
|
|
|
2292
|
+
if (\$narrowFixedGlob) {
|
|
2293
|
+
\$items = \$items | Where-Object {
|
|
2294
|
+
if (\$_.PSIsContainer) { \$true }
|
|
2295
|
+
elseif (\$IsWindows) { \$fixedMatches -contains \$_.FullName }
|
|
2296
|
+
else { \$fixedMatches -ccontains \$_.FullName }
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2121
2300
|
# Extract directory prefix to preserve in completion text
|
|
2122
2301
|
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
2123
2302
|
\$slashIdx = [Math]::Max(\$prefix.LastIndexOf('/'), \$prefix.LastIndexOf('\\'))
|
package/dist/completion.js
CHANGED
|
@@ -805,6 +805,7 @@ 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
|
local current="\$words[CURRENT]"
|
|
@@ -859,6 +860,11 @@ function ${functionName} () {
|
|
|
859
860
|
local __was_sh_glob=0
|
|
860
861
|
[[ -o sh_glob ]] && __was_sh_glob=1
|
|
861
862
|
unsetopt sh_glob
|
|
863
|
+
local __was_extended_glob=0
|
|
864
|
+
[[ -o extended_glob ]] && __was_extended_glob=1
|
|
865
|
+
local __was_sh_word_split=0
|
|
866
|
+
[[ -o sh_word_split ]] && __was_sh_word_split=1
|
|
867
|
+
unsetopt sh_word_split
|
|
862
868
|
|
|
863
869
|
# Enable glob_dots when hidden files are requested so that
|
|
864
870
|
# _files and _directories include dot-prefixed entries
|
|
@@ -871,7 +877,36 @@ function ${functionName} () {
|
|
|
871
877
|
# current word. If the user has already typed beyond the pattern,
|
|
872
878
|
# keep PREFIX unchanged for incremental narrowing.
|
|
873
879
|
local __saved_prefix="\$PREFIX"
|
|
874
|
-
|
|
880
|
+
local __fixed_glob=0
|
|
881
|
+
local -a __fixed_matches
|
|
882
|
+
local __initial_file_pattern="" __initial_directory="" __initial_leaf=""
|
|
883
|
+
if [[ ( "\$type" == "file" || "\$type" == "any" || "\$type" == "directory" ) &&
|
|
884
|
+
-z "\$current\$PREFIX\$SUFFIX" &&
|
|
885
|
+
-z "\${compstate[pattern_match]}" &&
|
|
886
|
+
"\$pattern" != /* && "\$pattern" != "~"* ]]; then
|
|
887
|
+
if [[ "\$pattern" == */* ]]; then
|
|
888
|
+
__initial_directory="\${pattern%/*}/"
|
|
889
|
+
fi
|
|
890
|
+
__initial_leaf="\${pattern##*/}"
|
|
891
|
+
# Native -g filters are split as shell words and use extended glob
|
|
892
|
+
# syntax. Keep unsupported filename forms on the existing path.
|
|
893
|
+
if [[ "\$__initial_leaf" == *[\*\?]* &&
|
|
894
|
+
"\$__initial_directory" != *[\*\?]* &&
|
|
895
|
+
"\$__initial_directory" != *["[]()"]* &&
|
|
896
|
+
"\$__initial_leaf" != *["[]"]* &&
|
|
897
|
+
"\$__initial_leaf" != *'\\'* &&
|
|
898
|
+
"\$__initial_leaf" != *[[:space:]]* &&
|
|
899
|
+
"\$__initial_leaf" != *["#~^()|<>;&"]* &&
|
|
900
|
+
"\$__initial_leaf" != *"'"* && "\$__initial_leaf" != *'"'* ]]; then
|
|
901
|
+
__initial_file_pattern="\$__initial_leaf"
|
|
902
|
+
# Only the literal directory belongs in PREFIX. Quote it so
|
|
903
|
+
# spaces and shell metacharacters remain literal path text.
|
|
904
|
+
if [[ -n "\$__initial_directory" ]]; then
|
|
905
|
+
PREFIX="\${(q)__initial_directory}"
|
|
906
|
+
fi
|
|
907
|
+
fi
|
|
908
|
+
fi
|
|
909
|
+
if [[ -n "\$pattern" && -z "\$__initial_file_pattern" ]]; then
|
|
875
910
|
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
876
911
|
local __norm_prefix="\${PREFIX#./}"
|
|
877
912
|
local __norm_pattern="\${pattern#./}"
|
|
@@ -881,8 +916,70 @@ function ${functionName} () {
|
|
|
881
916
|
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
882
917
|
fi
|
|
883
918
|
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
|
|
884
|
-
#
|
|
885
|
-
|
|
919
|
+
# Keep the typed prefix, but retain a fixed file glob separately.
|
|
920
|
+
if [[ "\$type" != "directory" && "\$__norm_pattern" == *[\*\?]* ]]; then
|
|
921
|
+
__fixed_glob=1
|
|
922
|
+
local __fixed_was_glob_dots=0
|
|
923
|
+
[[ -o glob_dots ]] && __fixed_was_glob_dots=1
|
|
924
|
+
if [[ "\$PREFIX" == .* || "\$PREFIX" == */.* ]]; then setopt glob_dots; fi
|
|
925
|
+
# Keep qualifiers in a string so sourcing also works with SH_GLOB.
|
|
926
|
+
local __fixed_pattern="" __glob_char="" __glob_previous=""
|
|
927
|
+
local __glob_escaped=0
|
|
928
|
+
local __glob_class=0 __glob_class_start=0 __glob_class_nested=""
|
|
929
|
+
local -i __glob_index
|
|
930
|
+
for (( __glob_index=1; __glob_index <= \${#pattern}; __glob_index++ )); do
|
|
931
|
+
__glob_char="\${pattern[__glob_index]}"
|
|
932
|
+
if [[ "\$__glob_class" == "1" ]]; then
|
|
933
|
+
# Class members are already literal. Preserve leading ]
|
|
934
|
+
# and nested POSIX classes without adding backslashes.
|
|
935
|
+
__fixed_pattern+="\$__glob_char"
|
|
936
|
+
if [[ -n "\$__glob_class_nested" ]]; then
|
|
937
|
+
if [[ "\$__glob_char" == "]" && "\$__glob_previous" == "\$__glob_class_nested" ]]; then
|
|
938
|
+
__glob_class_nested=""
|
|
939
|
+
fi
|
|
940
|
+
elif [[ "\$__glob_char" == "[" && "\${pattern[__glob_index+1]}" == ":" ]]; then
|
|
941
|
+
__glob_class_nested="\${pattern[__glob_index+1]}"
|
|
942
|
+
__glob_class_start=0
|
|
943
|
+
elif [[ "\$__glob_char" == "]" && "\$__glob_class_start" == "0" ]]; then
|
|
944
|
+
__glob_class=0
|
|
945
|
+
elif [[ "\$__glob_class_start" == "1" && ( "\$__glob_char" == "!" || "\$__glob_char" == "^" ) &&
|
|
946
|
+
( "\${pattern[__glob_index+1]}" != "]" || "\${pattern[__glob_index+2,-1]}" == *']'* ) ]]; then
|
|
947
|
+
__glob_class_start=2
|
|
948
|
+
else
|
|
949
|
+
__glob_class_start=0
|
|
950
|
+
fi
|
|
951
|
+
__glob_previous="\$__glob_char"
|
|
952
|
+
continue
|
|
953
|
+
fi
|
|
954
|
+
if [[ "\$__glob_escaped" == "1" ]]; then
|
|
955
|
+
__fixed_pattern+="\$__glob_char"
|
|
956
|
+
__glob_escaped=0
|
|
957
|
+
__glob_previous=""
|
|
958
|
+
continue
|
|
959
|
+
fi
|
|
960
|
+
if [[ "\$__glob_char" == "\\\\" ]]; then
|
|
961
|
+
__fixed_pattern+="\$__glob_char"
|
|
962
|
+
__glob_escaped=1
|
|
963
|
+
elif [[ "\$__glob_char" == "[" ]]; then
|
|
964
|
+
__fixed_pattern+="\$__glob_char"
|
|
965
|
+
__glob_class=1
|
|
966
|
+
__glob_class_start=1
|
|
967
|
+
elif [[ "\$__glob_char" == [#^~\\(\\)\\|\\<\\>] &&
|
|
968
|
+
! ( "\$__glob_char" == "~" && "\$__glob_index" == "1" ) ]]; then
|
|
969
|
+
# Keep class negation and leading home expansion; escape
|
|
970
|
+
# extended-glob operators in literal pathname components.
|
|
971
|
+
__fixed_pattern+="\\\\\$__glob_char"
|
|
972
|
+
else
|
|
973
|
+
__fixed_pattern+="\$__glob_char"
|
|
974
|
+
fi
|
|
975
|
+
__glob_previous="\$__glob_char"
|
|
976
|
+
done
|
|
977
|
+
__fixed_pattern+="(#qN)"
|
|
978
|
+
__fixed_matches=( \${~__fixed_pattern} )
|
|
979
|
+
__fixed_matches=( "\${(@)__fixed_matches:a}" )
|
|
980
|
+
# Hidden parents widen fixed matches, not native child suggestions.
|
|
981
|
+
if [[ "\$__fixed_was_glob_dots" == "0" && "\${PREFIX##*/}" != .* ]]; then unsetopt glob_dots; fi
|
|
982
|
+
fi
|
|
886
983
|
else
|
|
887
984
|
PREFIX="\$pattern"
|
|
888
985
|
fi
|
|
@@ -896,81 +993,111 @@ function ${functionName} () {
|
|
|
896
993
|
ext_pattern="*.\$extensions"
|
|
897
994
|
fi
|
|
898
995
|
fi
|
|
996
|
+
# _wanted/_next_label write to the named explanation parameter.
|
|
997
|
+
# Keep it local for every file directive, including directory/any.
|
|
998
|
+
local -a expl=()
|
|
899
999
|
# Use zsh's native file completion
|
|
900
1000
|
case "\$type" in
|
|
901
|
-
file)
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
while _next_label directories expl directory; do
|
|
919
|
-
_path_files -/ "\${expl[@]}" && file_matched=1
|
|
1001
|
+
file|any)
|
|
1002
|
+
if [[ "\$type" == "file" || -n "\$__initial_file_pattern" ]]; then
|
|
1003
|
+
# Regular-file suggestions retain symlinked files; initial
|
|
1004
|
+
# any globs use a non-directory qualifier instead.
|
|
1005
|
+
local file_pattern="\${ext_pattern:-*}(#q-.)"
|
|
1006
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1007
|
+
file_pattern="\${ext_pattern:-*}(#q-.e:${fixedGlobFunction}:)"
|
|
1008
|
+
fi
|
|
1009
|
+
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
1010
|
+
local __file_qualifier="-."
|
|
1011
|
+
[[ "\$type" == "any" ]] && __file_qualifier="^-/"
|
|
1012
|
+
local -a __initial_file_patterns=()
|
|
1013
|
+
if [[ -n "\$extensions" ]]; then
|
|
1014
|
+
setopt extended_glob
|
|
1015
|
+
local __initial_extension
|
|
1016
|
+
for __initial_extension in "\${(@s:,:)extensions}"; do
|
|
1017
|
+
__initial_file_patterns+=("(\${__initial_file_pattern}~^*.\${__initial_extension})(#q\${__file_qualifier})")
|
|
920
1018
|
done
|
|
921
1019
|
else
|
|
922
|
-
|
|
923
|
-
local -a file_ignore=() file_ignore_args=()
|
|
924
|
-
# Explicit globs bypass automatic fignore. Apply it only
|
|
925
|
-
# when the tag has not supplied its own ignore patterns.
|
|
926
|
-
if [[ -z "\$ext_pattern" && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
927
|
-
file_ignore=("?*\${^fignore[@]}")
|
|
928
|
-
file_ignore_args=(-F file_ignore)
|
|
929
|
-
fi
|
|
930
|
-
if _path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}"; then
|
|
931
|
-
file_matched=1
|
|
932
|
-
elif [[ -z "\$ext_pattern" && "\$PREFIX\$SUFFIX" != */* ]] &&
|
|
933
|
-
zstyle -a ":completion:\${curcontext}:files" recursive-files file_recursive_patterns; then
|
|
934
|
-
# Match _files' recursive fallback, retaining the same
|
|
935
|
-
# file filter and tag-specific ignore options.
|
|
936
|
-
for file_recursive_pattern in "\${file_recursive_patterns[@]}"; do
|
|
937
|
-
if [[ "\$PWD/" = \${~file_recursive_pattern} ]]; then
|
|
938
|
-
if (( \${#file_subtree[@]} == 0 )); then
|
|
939
|
-
file_subtree=(\${~file_subtree_pattern})
|
|
940
|
-
fi
|
|
941
|
-
for file_directory in "\${file_subtree[@]}"; do
|
|
942
|
-
file_prefix="\$PREFIX"
|
|
943
|
-
PREFIX="\$file_directory/\$PREFIX"
|
|
944
|
-
_path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}" && file_matched=1
|
|
945
|
-
PREFIX="\$file_prefix"
|
|
946
|
-
done
|
|
947
|
-
break
|
|
948
|
-
fi
|
|
949
|
-
done
|
|
950
|
-
fi
|
|
951
|
-
done
|
|
1020
|
+
__initial_file_patterns=("\${__initial_file_pattern}(#q\${__file_qualifier})")
|
|
952
1021
|
fi
|
|
1022
|
+
file_pattern="\${(j: :)__initial_file_patterns}"
|
|
1023
|
+
fi
|
|
1024
|
+
local file_subtree_pattern="**/*(#q/N)"
|
|
1025
|
+
local -a file_subtree=() file_recursive_patterns=()
|
|
1026
|
+
local -a file_tags=(files directories)
|
|
1027
|
+
local file_recursive_pattern file_directory file_prefix file_tag
|
|
1028
|
+
if [[ -n "\$__initial_file_pattern" || -z "\$ext_pattern" ]] &&
|
|
1029
|
+
zstyle -t ":completion:\${curcontext}:" list-dirs-first; then
|
|
1030
|
+
file_tags=(directories files)
|
|
1031
|
+
fi
|
|
1032
|
+
_tags "\${file_tags[@]}"
|
|
1033
|
+
while _tags; do
|
|
1034
|
+
local file_matched=0
|
|
1035
|
+
for file_tag in "\${file_tags[@]}"; do
|
|
1036
|
+
if [[ "\$file_tag" == directories ]]; then
|
|
1037
|
+
while _next_label directories expl directory; do
|
|
1038
|
+
_path_files -/ "\${expl[@]}" && file_matched=1
|
|
1039
|
+
done
|
|
1040
|
+
else
|
|
1041
|
+
while _next_label files expl file; do
|
|
1042
|
+
local -a file_ignore=() file_ignore_args=()
|
|
1043
|
+
# Explicit globs bypass automatic fignore. Apply it only
|
|
1044
|
+
# when the tag has not supplied its own ignore patterns.
|
|
1045
|
+
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && ( "\$__fixed_glob" != "1" || "\$pattern" == "*" ) && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
1046
|
+
file_ignore=("?*\${^fignore[@]}")
|
|
1047
|
+
file_ignore_args=(-F file_ignore)
|
|
1048
|
+
fi
|
|
1049
|
+
if _path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}"; then
|
|
1050
|
+
file_matched=1
|
|
1051
|
+
elif [[ ( -n "\$__initial_file_pattern" || -z "\$ext_pattern" ) && "\$PREFIX\$SUFFIX" != */* ]] &&
|
|
1052
|
+
zstyle -a ":completion:\${curcontext}:files" recursive-files file_recursive_patterns; then
|
|
1053
|
+
# Match _files' recursive fallback, retaining the same
|
|
1054
|
+
# file filter and tag-specific ignore options.
|
|
1055
|
+
for file_recursive_pattern in "\${file_recursive_patterns[@]}"; do
|
|
1056
|
+
if [[ "\$PWD/" = \${~file_recursive_pattern} ]]; then
|
|
1057
|
+
if (( \${#file_subtree[@]} == 0 )); then
|
|
1058
|
+
file_subtree=(\${~file_subtree_pattern})
|
|
1059
|
+
fi
|
|
1060
|
+
for file_directory in "\${file_subtree[@]}"; do
|
|
1061
|
+
file_prefix="\$PREFIX"
|
|
1062
|
+
PREFIX="\$file_directory/\$PREFIX"
|
|
1063
|
+
_path_files -g "\${file_pattern}" "\${expl[@]}" "\${file_ignore_args[@]}" && file_matched=1
|
|
1064
|
+
PREFIX="\$file_prefix"
|
|
1065
|
+
done
|
|
1066
|
+
break
|
|
1067
|
+
fi
|
|
1068
|
+
done
|
|
1069
|
+
fi
|
|
1070
|
+
done
|
|
1071
|
+
fi
|
|
1072
|
+
done
|
|
1073
|
+
(( file_matched )) && break
|
|
953
1074
|
done
|
|
954
|
-
|
|
955
|
-
done
|
|
956
|
-
;;
|
|
957
|
-
directory)
|
|
958
|
-
_directories
|
|
959
|
-
;;
|
|
960
|
-
any)
|
|
961
|
-
if [[ -n "\$ext_pattern" ]]; then
|
|
1075
|
+
elif [[ -n "\$ext_pattern" || "\$__fixed_glob" == "1" ]]; then
|
|
962
1076
|
local file_pattern="\${ext_pattern}(#q^-/)"
|
|
1077
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1078
|
+
# Put the predicate before ^, which negates later qualifiers.
|
|
1079
|
+
file_pattern="\${ext_pattern:-*}(#qe:${fixedGlobFunction}:^-/)"
|
|
1080
|
+
fi
|
|
963
1081
|
_wanted files expl file _path_files -g "\${file_pattern}"
|
|
964
1082
|
_wanted directories expl directory _path_files -/
|
|
965
1083
|
else
|
|
966
1084
|
_files
|
|
967
1085
|
fi
|
|
968
1086
|
;;
|
|
1087
|
+
directory)
|
|
1088
|
+
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
1089
|
+
# -/ would add every directory alongside the filtered glob.
|
|
1090
|
+
_wanted directories expl directory _path_files -g "\${__initial_file_pattern}(#q-/)"
|
|
1091
|
+
else
|
|
1092
|
+
_directories
|
|
1093
|
+
fi
|
|
1094
|
+
;;
|
|
969
1095
|
esac
|
|
970
|
-
|
|
971
1096
|
# Restore PREFIX and glob_dots to their previous state
|
|
972
1097
|
PREFIX="\$__saved_prefix"
|
|
1098
|
+
if [[ "\$__was_extended_glob" == "1" ]]; then setopt extended_glob; else unsetopt extended_glob; fi
|
|
973
1099
|
if [[ "\$__was_sh_glob" == "1" ]]; then setopt sh_glob; else unsetopt sh_glob; fi
|
|
1100
|
+
if [[ "\$__was_sh_word_split" == "1" ]]; then setopt sh_word_split; else unsetopt sh_word_split; fi
|
|
974
1101
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
975
1102
|
else
|
|
976
1103
|
# Regular literal completion
|
|
@@ -1001,6 +1128,11 @@ function ${functionName} () {
|
|
|
1001
1128
|
fi
|
|
1002
1129
|
}
|
|
1003
1130
|
|
|
1131
|
+
function ${fixedGlobFunction} () {
|
|
1132
|
+
local __fixed_candidate="\${REPLY:a}"
|
|
1133
|
+
(( \${__fixed_matches[(Ie)\$__fixed_candidate]} ))
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1004
1136
|
if (( $+functions[compdef] )); then
|
|
1005
1137
|
compdef ${functionName} ${programName}
|
|
1006
1138
|
fi
|
|
@@ -1634,12 +1766,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1634
1766
|
# prefix for incremental narrowing. Normalize path separators
|
|
1635
1767
|
# before comparing so that Windows backslashes match forward slashes
|
|
1636
1768
|
# in the transported pattern.
|
|
1769
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1637
1770
|
let glob_base = if ($pattern | is-not-empty) {
|
|
1638
1771
|
# Normalize separators and leading ./; downcase only on Windows
|
|
1639
1772
|
# where filesystems are typically case-insensitive
|
|
1640
1773
|
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1641
1774
|
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1642
|
-
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1643
1775
|
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
1644
1776
|
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
1645
1777
|
# For wildcard patterns, compare only the directory prefix
|
|
@@ -1658,6 +1790,21 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1658
1790
|
$prefix
|
|
1659
1791
|
}
|
|
1660
1792
|
|
|
1793
|
+
# Intersect fixed glob matches with the narrowed prefix candidates.
|
|
1794
|
+
# Directory-only suggestions retain their existing navigation behavior.
|
|
1795
|
+
let narrow_fixed_glob = ($type != "directory") and ($pattern =~ '[*?]') and ($prefix != $pattern) and ($glob_base == $prefix)
|
|
1796
|
+
let fixed_matches = if $narrow_fixed_glob {
|
|
1797
|
+
try {
|
|
1798
|
+
let fixed_pattern = ($pattern | into glob)
|
|
1799
|
+
let include_hidden = $hidden or ($prefix | path basename | str starts-with '.')
|
|
1800
|
+
let matches = if $include_hidden { ls -a --directory $fixed_pattern } else { ls --directory $fixed_pattern }
|
|
1801
|
+
$matches | each {|item|
|
|
1802
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
1803
|
+
if $is_win { $name | str downcase } else { $name }
|
|
1804
|
+
}
|
|
1805
|
+
} catch { [] }
|
|
1806
|
+
} else { [] }
|
|
1807
|
+
|
|
1661
1808
|
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
1662
1809
|
# append one so that ls enumerates its contents instead of matching
|
|
1663
1810
|
# siblings like "src-old". path expand follows symlinks, so that
|
|
@@ -1780,6 +1927,17 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1780
1927
|
}
|
|
1781
1928
|
}
|
|
1782
1929
|
|
|
1930
|
+
let filtered = if $narrow_fixed_glob {
|
|
1931
|
+
$filtered | where {|item|
|
|
1932
|
+
# Any-mode listings already include directory symlinks; keep them
|
|
1933
|
+
# navigable without changing their existing completion rendering.
|
|
1934
|
+
let navigation_dir = ($item.type == dir) or ($item.type == symlink and (try { ($item.name | path expand | path type) == "dir" } catch { false }))
|
|
1935
|
+
let name = ($env.PWD | path join $item.name | path expand --no-symlink)
|
|
1936
|
+
let normalized_name = if $is_win { $name | str downcase } else { $name }
|
|
1937
|
+
$navigation_dir or ($normalized_name in $fixed_matches)
|
|
1938
|
+
}
|
|
1939
|
+
} else { $filtered }
|
|
1940
|
+
|
|
1783
1941
|
# Extract directory prefix to preserve in completion text
|
|
1784
1942
|
let dir_prefix = if ($glob_base | is-empty) {
|
|
1785
1943
|
""
|
|
@@ -2055,6 +2213,9 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2055
2213
|
\$prefixFromInput = \$valuePrefix -and \$prefix -ceq \$valuePrefix
|
|
2056
2214
|
\$lookupPrefix = if (\$prefixFromInput -and \$null -ne \$quoteType -and \$prefix.StartsWith('~')) { './' + \$prefix } else { \$prefix }
|
|
2057
2215
|
|
|
2216
|
+
# Detect narrowing before directory normalization changes prefix.
|
|
2217
|
+
\$narrowFixedGlob = \$type -ne 'directory' -and \$pattern -match '[\*\?]' -and \$prefix -ceq \$valuePrefix -and \$pattern -cne \$valuePrefix
|
|
2218
|
+
|
|
2058
2219
|
# If prefix is a directory without trailing slash, append
|
|
2059
2220
|
# one so Get-ChildItem lists its contents
|
|
2060
2221
|
if (\$prefix -and (Test-Path -Path \$lookupPrefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
@@ -2066,6 +2227,16 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2066
2227
|
# characters, use it as-is; otherwise append *
|
|
2067
2228
|
\$globPath = if (\$lookupPrefix -match '[\*\?]') { \$lookupPrefix } else { "\${lookupPrefix}*" }
|
|
2068
2229
|
|
|
2230
|
+
# Keep the original native glob as an independent file filter
|
|
2231
|
+
# when the current word narrows its candidates.
|
|
2232
|
+
\$fixedMatches = @()
|
|
2233
|
+
if (\$narrowFixedGlob) {
|
|
2234
|
+
# Include hidden parents here; candidate enumeration still
|
|
2235
|
+
# controls which hidden children can be suggested.
|
|
2236
|
+
\$fixedMatches = @(Get-Item -Force -Path \$pattern -ErrorAction SilentlyContinue |
|
|
2237
|
+
Select-Object -ExpandProperty FullName)
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2069
2240
|
# Get file system items based on type
|
|
2070
2241
|
\$items = @()
|
|
2071
2242
|
switch (\$type) {
|
|
@@ -2118,6 +2289,14 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2118
2289
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
2119
2290
|
}
|
|
2120
2291
|
|
|
2292
|
+
if (\$narrowFixedGlob) {
|
|
2293
|
+
\$items = \$items | Where-Object {
|
|
2294
|
+
if (\$_.PSIsContainer) { \$true }
|
|
2295
|
+
elseif (\$IsWindows) { \$fixedMatches -contains \$_.FullName }
|
|
2296
|
+
else { \$fixedMatches -ccontains \$_.FullName }
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2121
2300
|
# Extract directory prefix to preserve in completion text
|
|
2122
2301
|
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
2123
2302
|
\$slashIdx = [Math]::Max(\$prefix.LastIndexOf('/'), \$prefix.LastIndexOf('\\'))
|