@optique/core 1.0.15-dev.2880 → 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 +133 -5
- package/dist/completion.js +133 -5
- 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]"
|
|
@@ -861,6 +862,9 @@ function ${functionName} () {
|
|
|
861
862
|
unsetopt sh_glob
|
|
862
863
|
local __was_extended_glob=0
|
|
863
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
|
|
864
868
|
|
|
865
869
|
# Enable glob_dots when hidden files are requested so that
|
|
866
870
|
# _files and _directories include dot-prefixed entries
|
|
@@ -873,6 +877,8 @@ function ${functionName} () {
|
|
|
873
877
|
# current word. If the user has already typed beyond the pattern,
|
|
874
878
|
# keep PREFIX unchanged for incremental narrowing.
|
|
875
879
|
local __saved_prefix="\$PREFIX"
|
|
880
|
+
local __fixed_glob=0
|
|
881
|
+
local -a __fixed_matches
|
|
876
882
|
local __initial_file_pattern="" __initial_directory="" __initial_leaf=""
|
|
877
883
|
if [[ ( "\$type" == "file" || "\$type" == "any" || "\$type" == "directory" ) &&
|
|
878
884
|
-z "\$current\$PREFIX\$SUFFIX" &&
|
|
@@ -910,8 +916,70 @@ function ${functionName} () {
|
|
|
910
916
|
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
911
917
|
fi
|
|
912
918
|
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
|
|
913
|
-
#
|
|
914
|
-
|
|
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
|
|
915
983
|
else
|
|
916
984
|
PREFIX="\$pattern"
|
|
917
985
|
fi
|
|
@@ -935,6 +1003,9 @@ function ${functionName} () {
|
|
|
935
1003
|
# Regular-file suggestions retain symlinked files; initial
|
|
936
1004
|
# any globs use a non-directory qualifier instead.
|
|
937
1005
|
local file_pattern="\${ext_pattern:-*}(#q-.)"
|
|
1006
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1007
|
+
file_pattern="\${ext_pattern:-*}(#q-.e:${fixedGlobFunction}:)"
|
|
1008
|
+
fi
|
|
938
1009
|
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
939
1010
|
local __file_qualifier="-."
|
|
940
1011
|
[[ "\$type" == "any" ]] && __file_qualifier="^-/"
|
|
@@ -971,7 +1042,7 @@ function ${functionName} () {
|
|
|
971
1042
|
local -a file_ignore=() file_ignore_args=()
|
|
972
1043
|
# Explicit globs bypass automatic fignore. Apply it only
|
|
973
1044
|
# 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
|
|
1045
|
+
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && ( "\$__fixed_glob" != "1" || "\$pattern" == "*" ) && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
975
1046
|
file_ignore=("?*\${^fignore[@]}")
|
|
976
1047
|
file_ignore_args=(-F file_ignore)
|
|
977
1048
|
fi
|
|
@@ -1001,8 +1072,12 @@ function ${functionName} () {
|
|
|
1001
1072
|
done
|
|
1002
1073
|
(( file_matched )) && break
|
|
1003
1074
|
done
|
|
1004
|
-
elif [[ -n "\$ext_pattern" ]]; then
|
|
1075
|
+
elif [[ -n "\$ext_pattern" || "\$__fixed_glob" == "1" ]]; then
|
|
1005
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
|
|
1006
1081
|
_wanted files expl file _path_files -g "\${file_pattern}"
|
|
1007
1082
|
_wanted directories expl directory _path_files -/
|
|
1008
1083
|
else
|
|
@@ -1022,6 +1097,7 @@ function ${functionName} () {
|
|
|
1022
1097
|
PREFIX="\$__saved_prefix"
|
|
1023
1098
|
if [[ "\$__was_extended_glob" == "1" ]]; then setopt extended_glob; else unsetopt extended_glob; fi
|
|
1024
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
|
|
1025
1101
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
1026
1102
|
else
|
|
1027
1103
|
# Regular literal completion
|
|
@@ -1052,6 +1128,11 @@ function ${functionName} () {
|
|
|
1052
1128
|
fi
|
|
1053
1129
|
}
|
|
1054
1130
|
|
|
1131
|
+
function ${fixedGlobFunction} () {
|
|
1132
|
+
local __fixed_candidate="\${REPLY:a}"
|
|
1133
|
+
(( \${__fixed_matches[(Ie)\$__fixed_candidate]} ))
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1055
1136
|
if (( $+functions[compdef] )); then
|
|
1056
1137
|
compdef ${functionName} ${programName}
|
|
1057
1138
|
fi
|
|
@@ -1685,12 +1766,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1685
1766
|
# prefix for incremental narrowing. Normalize path separators
|
|
1686
1767
|
# before comparing so that Windows backslashes match forward slashes
|
|
1687
1768
|
# in the transported pattern.
|
|
1769
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1688
1770
|
let glob_base = if ($pattern | is-not-empty) {
|
|
1689
1771
|
# Normalize separators and leading ./; downcase only on Windows
|
|
1690
1772
|
# where filesystems are typically case-insensitive
|
|
1691
1773
|
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1692
1774
|
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1693
|
-
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1694
1775
|
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
1695
1776
|
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
1696
1777
|
# For wildcard patterns, compare only the directory prefix
|
|
@@ -1709,6 +1790,21 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1709
1790
|
$prefix
|
|
1710
1791
|
}
|
|
1711
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
|
+
|
|
1712
1808
|
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
1713
1809
|
# append one so that ls enumerates its contents instead of matching
|
|
1714
1810
|
# siblings like "src-old". path expand follows symlinks, so that
|
|
@@ -1831,6 +1927,17 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1831
1927
|
}
|
|
1832
1928
|
}
|
|
1833
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
|
+
|
|
1834
1941
|
# Extract directory prefix to preserve in completion text
|
|
1835
1942
|
let dir_prefix = if ($glob_base | is-empty) {
|
|
1836
1943
|
""
|
|
@@ -2106,6 +2213,9 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2106
2213
|
\$prefixFromInput = \$valuePrefix -and \$prefix -ceq \$valuePrefix
|
|
2107
2214
|
\$lookupPrefix = if (\$prefixFromInput -and \$null -ne \$quoteType -and \$prefix.StartsWith('~')) { './' + \$prefix } else { \$prefix }
|
|
2108
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
|
+
|
|
2109
2219
|
# If prefix is a directory without trailing slash, append
|
|
2110
2220
|
# one so Get-ChildItem lists its contents
|
|
2111
2221
|
if (\$prefix -and (Test-Path -Path \$lookupPrefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
@@ -2117,6 +2227,16 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2117
2227
|
# characters, use it as-is; otherwise append *
|
|
2118
2228
|
\$globPath = if (\$lookupPrefix -match '[\*\?]') { \$lookupPrefix } else { "\${lookupPrefix}*" }
|
|
2119
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
|
+
|
|
2120
2240
|
# Get file system items based on type
|
|
2121
2241
|
\$items = @()
|
|
2122
2242
|
switch (\$type) {
|
|
@@ -2169,6 +2289,14 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2169
2289
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
2170
2290
|
}
|
|
2171
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
|
+
|
|
2172
2300
|
# Extract directory prefix to preserve in completion text
|
|
2173
2301
|
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
2174
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]"
|
|
@@ -861,6 +862,9 @@ function ${functionName} () {
|
|
|
861
862
|
unsetopt sh_glob
|
|
862
863
|
local __was_extended_glob=0
|
|
863
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
|
|
864
868
|
|
|
865
869
|
# Enable glob_dots when hidden files are requested so that
|
|
866
870
|
# _files and _directories include dot-prefixed entries
|
|
@@ -873,6 +877,8 @@ function ${functionName} () {
|
|
|
873
877
|
# current word. If the user has already typed beyond the pattern,
|
|
874
878
|
# keep PREFIX unchanged for incremental narrowing.
|
|
875
879
|
local __saved_prefix="\$PREFIX"
|
|
880
|
+
local __fixed_glob=0
|
|
881
|
+
local -a __fixed_matches
|
|
876
882
|
local __initial_file_pattern="" __initial_directory="" __initial_leaf=""
|
|
877
883
|
if [[ ( "\$type" == "file" || "\$type" == "any" || "\$type" == "directory" ) &&
|
|
878
884
|
-z "\$current\$PREFIX\$SUFFIX" &&
|
|
@@ -910,8 +916,70 @@ function ${functionName} () {
|
|
|
910
916
|
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
911
917
|
fi
|
|
912
918
|
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
|
|
913
|
-
#
|
|
914
|
-
|
|
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
|
|
915
983
|
else
|
|
916
984
|
PREFIX="\$pattern"
|
|
917
985
|
fi
|
|
@@ -935,6 +1003,9 @@ function ${functionName} () {
|
|
|
935
1003
|
# Regular-file suggestions retain symlinked files; initial
|
|
936
1004
|
# any globs use a non-directory qualifier instead.
|
|
937
1005
|
local file_pattern="\${ext_pattern:-*}(#q-.)"
|
|
1006
|
+
if [[ "\$__fixed_glob" == "1" ]]; then
|
|
1007
|
+
file_pattern="\${ext_pattern:-*}(#q-.e:${fixedGlobFunction}:)"
|
|
1008
|
+
fi
|
|
938
1009
|
if [[ -n "\$__initial_file_pattern" ]]; then
|
|
939
1010
|
local __file_qualifier="-."
|
|
940
1011
|
[[ "\$type" == "any" ]] && __file_qualifier="^-/"
|
|
@@ -971,7 +1042,7 @@ function ${functionName} () {
|
|
|
971
1042
|
local -a file_ignore=() file_ignore_args=()
|
|
972
1043
|
# Explicit globs bypass automatic fignore. Apply it only
|
|
973
1044
|
# 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
|
|
1045
|
+
if [[ -z "\$__initial_file_pattern" && -z "\$ext_pattern" && ( "\$__fixed_glob" != "1" || "\$pattern" == "*" ) && -z "\$_comp_no_ignore" && -n "\$FIGNORE" ]] && (( \${#_comp_ignore[@]} == 0 )); then
|
|
975
1046
|
file_ignore=("?*\${^fignore[@]}")
|
|
976
1047
|
file_ignore_args=(-F file_ignore)
|
|
977
1048
|
fi
|
|
@@ -1001,8 +1072,12 @@ function ${functionName} () {
|
|
|
1001
1072
|
done
|
|
1002
1073
|
(( file_matched )) && break
|
|
1003
1074
|
done
|
|
1004
|
-
elif [[ -n "\$ext_pattern" ]]; then
|
|
1075
|
+
elif [[ -n "\$ext_pattern" || "\$__fixed_glob" == "1" ]]; then
|
|
1005
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
|
|
1006
1081
|
_wanted files expl file _path_files -g "\${file_pattern}"
|
|
1007
1082
|
_wanted directories expl directory _path_files -/
|
|
1008
1083
|
else
|
|
@@ -1022,6 +1097,7 @@ function ${functionName} () {
|
|
|
1022
1097
|
PREFIX="\$__saved_prefix"
|
|
1023
1098
|
if [[ "\$__was_extended_glob" == "1" ]]; then setopt extended_glob; else unsetopt extended_glob; fi
|
|
1024
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
|
|
1025
1101
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
1026
1102
|
else
|
|
1027
1103
|
# Regular literal completion
|
|
@@ -1052,6 +1128,11 @@ function ${functionName} () {
|
|
|
1052
1128
|
fi
|
|
1053
1129
|
}
|
|
1054
1130
|
|
|
1131
|
+
function ${fixedGlobFunction} () {
|
|
1132
|
+
local __fixed_candidate="\${REPLY:a}"
|
|
1133
|
+
(( \${__fixed_matches[(Ie)\$__fixed_candidate]} ))
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1055
1136
|
if (( $+functions[compdef] )); then
|
|
1056
1137
|
compdef ${functionName} ${programName}
|
|
1057
1138
|
fi
|
|
@@ -1685,12 +1766,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1685
1766
|
# prefix for incremental narrowing. Normalize path separators
|
|
1686
1767
|
# before comparing so that Windows backslashes match forward slashes
|
|
1687
1768
|
# in the transported pattern.
|
|
1769
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1688
1770
|
let glob_base = if ($pattern | is-not-empty) {
|
|
1689
1771
|
# Normalize separators and leading ./; downcase only on Windows
|
|
1690
1772
|
# where filesystems are typically case-insensitive
|
|
1691
1773
|
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1692
1774
|
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
1693
|
-
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
1694
1775
|
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
1695
1776
|
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
1696
1777
|
# For wildcard patterns, compare only the directory prefix
|
|
@@ -1709,6 +1790,21 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1709
1790
|
$prefix
|
|
1710
1791
|
}
|
|
1711
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
|
+
|
|
1712
1808
|
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
1713
1809
|
# append one so that ls enumerates its contents instead of matching
|
|
1714
1810
|
# siblings like "src-old". path expand follows symlinks, so that
|
|
@@ -1831,6 +1927,17 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1831
1927
|
}
|
|
1832
1928
|
}
|
|
1833
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
|
+
|
|
1834
1941
|
# Extract directory prefix to preserve in completion text
|
|
1835
1942
|
let dir_prefix = if ($glob_base | is-empty) {
|
|
1836
1943
|
""
|
|
@@ -2106,6 +2213,9 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2106
2213
|
\$prefixFromInput = \$valuePrefix -and \$prefix -ceq \$valuePrefix
|
|
2107
2214
|
\$lookupPrefix = if (\$prefixFromInput -and \$null -ne \$quoteType -and \$prefix.StartsWith('~')) { './' + \$prefix } else { \$prefix }
|
|
2108
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
|
+
|
|
2109
2219
|
# If prefix is a directory without trailing slash, append
|
|
2110
2220
|
# one so Get-ChildItem lists its contents
|
|
2111
2221
|
if (\$prefix -and (Test-Path -Path \$lookupPrefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
@@ -2117,6 +2227,16 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2117
2227
|
# characters, use it as-is; otherwise append *
|
|
2118
2228
|
\$globPath = if (\$lookupPrefix -match '[\*\?]') { \$lookupPrefix } else { "\${lookupPrefix}*" }
|
|
2119
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
|
+
|
|
2120
2240
|
# Get file system items based on type
|
|
2121
2241
|
\$items = @()
|
|
2122
2242
|
switch (\$type) {
|
|
@@ -2169,6 +2289,14 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
2169
2289
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
2170
2290
|
}
|
|
2171
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
|
+
|
|
2172
2300
|
# Extract directory prefix to preserve in completion text
|
|
2173
2301
|
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
2174
2302
|
\$slashIdx = [Math]::Max(\$prefix.LastIndexOf('/'), \$prefix.LastIndexOf('\\'))
|