@optique/core 1.0.0-dev.1163 → 1.0.0-dev.1172
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 +375 -41
- package/dist/completion.js +375 -41
- package/dist/valueparser.cjs +19 -1
- package/dist/valueparser.d.cts +2 -0
- package/dist/valueparser.d.ts +2 -0
- package/dist/valueparser.js +19 -1
- package/package.json +1 -1
package/dist/completion.cjs
CHANGED
|
@@ -99,6 +99,50 @@ function _${programName} () {
|
|
|
99
99
|
fi
|
|
100
100
|
fi
|
|
101
101
|
|
|
102
|
+
# When a pattern is specified, use it as the glob base instead of the
|
|
103
|
+
# current word so that completions enumerate the pattern's directory.
|
|
104
|
+
# However, if the user has already typed beyond the pattern (e.g.,
|
|
105
|
+
# pattern="src/" and current="src/ma"), preserve the typed suffix
|
|
106
|
+
# for incremental filtering.
|
|
107
|
+
local __from_pattern=0
|
|
108
|
+
if [[ -n "$pattern" ]]; then
|
|
109
|
+
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
110
|
+
local __norm_current="\${current#./}"
|
|
111
|
+
local __norm_pattern="\${pattern#./}"
|
|
112
|
+
# For wildcard patterns, compare only the directory prefix before
|
|
113
|
+
# any wildcards so that typing src/ma narrows src/*.ts correctly
|
|
114
|
+
local __compare_pattern="$__norm_pattern"
|
|
115
|
+
if [[ "$__compare_pattern" == *[\*\?]* ]]; then
|
|
116
|
+
__compare_pattern="\${__compare_pattern%%[\*\?]*}"
|
|
117
|
+
[[ "$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
118
|
+
fi
|
|
119
|
+
if [[ ( -n "$__compare_pattern" || -n "$__norm_current" ) && \${#__norm_current} -ge \${#__compare_pattern} && "\${__norm_current:0:\${#__compare_pattern}}" == "$__compare_pattern" && "$current" != "$pattern" ]]; then
|
|
120
|
+
# User has typed beyond or an equivalent form of the pattern
|
|
121
|
+
true
|
|
122
|
+
else
|
|
123
|
+
__from_pattern=1
|
|
124
|
+
# Reset tilde state from the current-word expansion so that a
|
|
125
|
+
# non-tilde pattern is not rewritten through stale tilde state
|
|
126
|
+
__tilde_prefix=""
|
|
127
|
+
__tilde_expanded=""
|
|
128
|
+
__glob_current="$pattern"
|
|
129
|
+
if [[ "$pattern" =~ ^(~[a-zA-Z0-9_.+-]*)(/.*)?$ ]]; then
|
|
130
|
+
__tilde_prefix="\${BASH_REMATCH[1]}"
|
|
131
|
+
eval "__tilde_expanded=\$__tilde_prefix" 2>/dev/null || true
|
|
132
|
+
if [[ -n "$__tilde_expanded" && "$__tilde_expanded" != "$__tilde_prefix" ]]; then
|
|
133
|
+
__glob_current="\${__tilde_expanded}\${pattern#\$__tilde_prefix}"
|
|
134
|
+
else
|
|
135
|
+
__tilde_prefix=""
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
# If the glob base is a directory without a trailing slash,
|
|
139
|
+
# append one so that the glob enumerates its contents
|
|
140
|
+
if [[ -d "$__glob_current" && "$__glob_current" != */ ]]; then
|
|
141
|
+
__glob_current="$__glob_current/"
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
|
|
102
146
|
# Enable dotglob when hidden files are requested, or when the user
|
|
103
147
|
# is already navigating inside a hidden directory (e.g., ~/.config/nvim/)
|
|
104
148
|
# This runs after tilde expansion so that paths like ~/.config/ are
|
|
@@ -114,6 +158,36 @@ function _${programName} () {
|
|
|
114
158
|
[[ "$__prefix_base" == .* ]] && __prefix_targets_hidden=1
|
|
115
159
|
if [[ "$hidden" == "1" || "$__inside_hidden_path" == "1" || "$__prefix_targets_hidden" == "1" ]]; then shopt -s dotglob; fi
|
|
116
160
|
|
|
161
|
+
# Pre-expand file candidates. When the glob base came from the
|
|
162
|
+
# program's pattern and contains wildcard characters (* or ?),
|
|
163
|
+
# use it as-is via compgen -G (safe — no command substitution).
|
|
164
|
+
# Otherwise append * to treat it as a prefix.
|
|
165
|
+
# Note: [ is NOT treated as a glob indicator because it commonly
|
|
166
|
+
# appears in literal filenames like [draft] or foo[1].txt.
|
|
167
|
+
local -a __candidates=()
|
|
168
|
+
if [[ "$__from_pattern" == "1" && "$__glob_current" == *[\*\?]* ]]; then
|
|
169
|
+
mapfile -t __candidates < <(compgen -G "$__glob_current" 2>/dev/null)
|
|
170
|
+
# For file/any modes, also include directories from the base
|
|
171
|
+
# directory for navigation even when the glob itself does not
|
|
172
|
+
# match directory names. Skip this for directory mode so that
|
|
173
|
+
# the pattern's basename filter is respected.
|
|
174
|
+
if [[ "$type" != "directory" ]]; then
|
|
175
|
+
local __glob_dir="\${__glob_current%/*}"
|
|
176
|
+
[[ "$__glob_dir" == "$__glob_current" ]] && __glob_dir="."
|
|
177
|
+
local __d
|
|
178
|
+
for __d in "$__glob_dir"/*/; do
|
|
179
|
+
[[ -d "$__d" ]] && __candidates+=("\${__d%/}")
|
|
180
|
+
done
|
|
181
|
+
fi
|
|
182
|
+
else
|
|
183
|
+
__candidates=("$__glob_current"*)
|
|
184
|
+
# Remove no-match sentinel (bash returns the literal pattern).
|
|
185
|
+
# Also check -L for dangling symlinks which are valid candidates.
|
|
186
|
+
if [[ \${#__candidates[@]} -eq 1 && ! -e "\${__candidates[0]}" && ! -L "\${__candidates[0]}" ]]; then
|
|
187
|
+
__candidates=()
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
|
|
117
191
|
# Generate file completions based on type
|
|
118
192
|
case "$type" in
|
|
119
193
|
file)
|
|
@@ -121,7 +195,7 @@ function _${programName} () {
|
|
|
121
195
|
if [[ -n "$extensions" ]]; then
|
|
122
196
|
# Files with extension filtering + directories
|
|
123
197
|
local ext_pattern="\${extensions//,/|}"
|
|
124
|
-
for item in "
|
|
198
|
+
for item in "\${__candidates[@]}"; do
|
|
125
199
|
if [[ -d "$item" ]]; then
|
|
126
200
|
COMPREPLY+=("$item/")
|
|
127
201
|
elif [[ -f "$item" && "$item" =~ \\.($ext_pattern)$ ]]; then
|
|
@@ -130,7 +204,7 @@ function _${programName} () {
|
|
|
130
204
|
done
|
|
131
205
|
else
|
|
132
206
|
# Complete files and directories for navigation
|
|
133
|
-
for item in "
|
|
207
|
+
for item in "\${__candidates[@]}"; do
|
|
134
208
|
if [[ -d "$item" ]]; then
|
|
135
209
|
COMPREPLY+=("$item/")
|
|
136
210
|
elif [[ -f "$item" ]]; then
|
|
@@ -141,7 +215,7 @@ function _${programName} () {
|
|
|
141
215
|
;;
|
|
142
216
|
directory)
|
|
143
217
|
# Complete directories only
|
|
144
|
-
for dir in "
|
|
218
|
+
for dir in "\${__candidates[@]}"; do
|
|
145
219
|
[[ -d "$dir" ]] && COMPREPLY+=("$dir/")
|
|
146
220
|
done
|
|
147
221
|
;;
|
|
@@ -151,7 +225,7 @@ function _${programName} () {
|
|
|
151
225
|
# Files with extension filtering + directories
|
|
152
226
|
# Files with extension filtering
|
|
153
227
|
local ext_pattern="\${extensions//,/|}"
|
|
154
|
-
for item in "
|
|
228
|
+
for item in "\${__candidates[@]}"; do
|
|
155
229
|
if [[ -d "$item" ]]; then
|
|
156
230
|
COMPREPLY+=("$item/")
|
|
157
231
|
elif [[ ( -e "$item" || -L "$item" ) && "$item" =~ \\.($ext_pattern)$ ]]; then
|
|
@@ -160,7 +234,7 @@ function _${programName} () {
|
|
|
160
234
|
done
|
|
161
235
|
else
|
|
162
236
|
# Complete files and directories, add slash to directories
|
|
163
|
-
for item in "
|
|
237
|
+
for item in "\${__candidates[@]}"; do
|
|
164
238
|
if [[ -d "$item" ]]; then
|
|
165
239
|
COMPREPLY+=("$item/")
|
|
166
240
|
# Use -e || -L to include non-regular files (sockets, FIFOs, dangling symlinks)
|
|
@@ -278,6 +352,28 @@ function _${programName.replace(/[^a-zA-Z0-9]/g, "_")} () {
|
|
|
278
352
|
[[ -o glob_dots ]] && __was_glob_dots=1
|
|
279
353
|
if [[ "\$hidden" == "1" ]]; then setopt glob_dots; fi
|
|
280
354
|
|
|
355
|
+
# When a pattern is specified, override PREFIX so that _files and
|
|
356
|
+
# _directories enumerate the pattern's directory instead of the
|
|
357
|
+
# current word. If the user has already typed beyond the pattern,
|
|
358
|
+
# keep PREFIX unchanged for incremental narrowing.
|
|
359
|
+
local __saved_prefix="\$PREFIX"
|
|
360
|
+
if [[ -n "\$pattern" ]]; then
|
|
361
|
+
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
362
|
+
local __norm_prefix="\${PREFIX#./}"
|
|
363
|
+
local __norm_pattern="\${pattern#./}"
|
|
364
|
+
local __compare_pattern="\$__norm_pattern"
|
|
365
|
+
if [[ "\$__compare_pattern" == *[\*\?]* ]]; then
|
|
366
|
+
__compare_pattern="\${__compare_pattern%%[\*\?]*}"
|
|
367
|
+
[[ "\$__compare_pattern" == */* ]] && __compare_pattern="\${__compare_pattern%/*}/" || __compare_pattern=""
|
|
368
|
+
fi
|
|
369
|
+
if [[ ( -n "\$__compare_pattern" || -n "\$__norm_prefix" ) && \${#__norm_prefix} -ge \${#__compare_pattern} && "\${__norm_prefix[1,\${#__compare_pattern}]}" == "\$__compare_pattern" && "\$PREFIX" != "\$pattern" ]]; then
|
|
370
|
+
# User typed an equivalent or extended form — keep PREFIX
|
|
371
|
+
true
|
|
372
|
+
else
|
|
373
|
+
PREFIX="\$pattern"
|
|
374
|
+
fi
|
|
375
|
+
fi
|
|
376
|
+
|
|
281
377
|
# Use zsh's native file completion
|
|
282
378
|
case "\$type" in
|
|
283
379
|
file)
|
|
@@ -303,7 +399,8 @@ function _${programName.replace(/[^a-zA-Z0-9]/g, "_")} () {
|
|
|
303
399
|
;;
|
|
304
400
|
esac
|
|
305
401
|
|
|
306
|
-
# Restore glob_dots to
|
|
402
|
+
# Restore PREFIX and glob_dots to their previous state
|
|
403
|
+
PREFIX="\$__saved_prefix"
|
|
307
404
|
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
308
405
|
else
|
|
309
406
|
# Regular literal completion
|
|
@@ -382,12 +479,118 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
382
479
|
set -l pattern (string replace -a '%25' '%' -- (string replace -a '%3A' ':' -- $parts[4]))
|
|
383
480
|
set -l hidden $parts[5]
|
|
384
481
|
|
|
482
|
+
# When a pattern is specified, use it as the glob base instead
|
|
483
|
+
# of the current word. If the user has already typed beyond the
|
|
484
|
+
# pattern (e.g., pattern="src/" and current="src/ma"), keep the
|
|
485
|
+
# current word for incremental narrowing.
|
|
486
|
+
set -l glob_base $current
|
|
487
|
+
set -l __tilde_prefix ""
|
|
488
|
+
set -l __from_pattern 0
|
|
489
|
+
if test -n "$pattern"
|
|
490
|
+
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
491
|
+
set -l __norm_current (string replace -r '^\\./' '' -- "$current")
|
|
492
|
+
set -l __norm_pattern (string replace -r '^\\./' '' -- "$pattern")
|
|
493
|
+
# For wildcard patterns, compare only the directory prefix
|
|
494
|
+
set -l __compare_pattern "$__norm_pattern"
|
|
495
|
+
if string match -q '*[*?]*' -- "$__compare_pattern"
|
|
496
|
+
set __compare_pattern (string replace -r '[*?].*' '' -- "$__compare_pattern")
|
|
497
|
+
if string match -q '*/*' -- "$__compare_pattern"
|
|
498
|
+
set __compare_pattern (string replace -r '/[^/]*$' '/' -- "$__compare_pattern")
|
|
499
|
+
else
|
|
500
|
+
set __compare_pattern ""
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
set -l __cp_len (string length -- "$__compare_pattern")
|
|
504
|
+
set -l __nc_len (string length -- "$__norm_current")
|
|
505
|
+
if begin; test -n "$__compare_pattern"; or test -n "$__norm_current"; end
|
|
506
|
+
and test $__nc_len -ge $__cp_len
|
|
507
|
+
and test (string sub -l $__cp_len -- "$__norm_current") = "$__compare_pattern"
|
|
508
|
+
and test "$current" != "$pattern"
|
|
509
|
+
set glob_base $current
|
|
510
|
+
else
|
|
511
|
+
set glob_base $pattern
|
|
512
|
+
set __from_pattern 1
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Expand tilde prefix for globbing — fish does not expand ~
|
|
517
|
+
# inside variable substitutions, so replace it with $HOME
|
|
518
|
+
if string match -q '~/*' -- "$glob_base"
|
|
519
|
+
set __tilde_prefix "~"
|
|
520
|
+
set glob_base (string replace -r '^~' "$HOME" -- "$glob_base")
|
|
521
|
+
else if string match -q '~' -- "$glob_base"
|
|
522
|
+
set __tilde_prefix "~"
|
|
523
|
+
set glob_base "$HOME/"
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# If the glob base is a directory without a trailing slash,
|
|
527
|
+
# append one so that the glob enumerates its contents
|
|
528
|
+
if test -d "$glob_base"; and not string match -q '*/' -- "$glob_base"
|
|
529
|
+
set glob_base "$glob_base/"
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Pre-expand file candidates. When the glob base came from
|
|
533
|
+
# the program's pattern and contains * or ?, use it as the
|
|
534
|
+
# complete glob expression. Otherwise append * as a prefix.
|
|
535
|
+
# Note: [ is NOT treated as a glob because it commonly
|
|
536
|
+
# appears in literal filenames like [draft] or foo[1].txt.
|
|
537
|
+
set -l __has_glob 0
|
|
538
|
+
if test $__from_pattern -eq 1
|
|
539
|
+
and string match -q '*[*?]*' -- "$glob_base"
|
|
540
|
+
set __has_glob 1
|
|
541
|
+
end
|
|
542
|
+
set -l __candidates
|
|
543
|
+
if test $__has_glob -eq 1
|
|
544
|
+
# Safe glob expansion without eval: split the pattern into
|
|
545
|
+
# directory and filter parts, list the directory, then use
|
|
546
|
+
# string match for wildcard filtering
|
|
547
|
+
set -l __glob_dir (string replace -r '/[^/]*$' '' -- "$glob_base")
|
|
548
|
+
set -l __glob_filter (string replace -r '.*/' '' -- "$glob_base")
|
|
549
|
+
if test -z "$__glob_dir"; or test "$__glob_dir" = "$glob_base"
|
|
550
|
+
set __glob_dir "."
|
|
551
|
+
end
|
|
552
|
+
# Match files by the glob filter. Fish's * does not match
|
|
553
|
+
# dotfiles, so also scan .* when the filter targets them.
|
|
554
|
+
for __item in $__glob_dir/*
|
|
555
|
+
if test -e "$__item"
|
|
556
|
+
set -l __bn (basename "$__item")
|
|
557
|
+
if string match -q "$__glob_filter" -- "$__bn"
|
|
558
|
+
set -a __candidates "$__item"
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
if string match -q '.*' -- "$__glob_filter"
|
|
563
|
+
for __item in $__glob_dir/.*
|
|
564
|
+
if test -e "$__item"
|
|
565
|
+
set -l __bn (basename "$__item")
|
|
566
|
+
if test "$__bn" = "." -o "$__bn" = ".."
|
|
567
|
+
continue
|
|
568
|
+
end
|
|
569
|
+
if string match -q "$__glob_filter" -- "$__bn"
|
|
570
|
+
set -a __candidates "$__item"
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
# For file/any modes, also include directories for navigation.
|
|
576
|
+
# Skip for directory mode so the pattern filter is respected.
|
|
577
|
+
if test "$type" != "directory"
|
|
578
|
+
for __item in $__glob_dir/*/
|
|
579
|
+
if test -d "$__item"
|
|
580
|
+
set -a __candidates (string replace -r '/$' '' -- "$__item")
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
else
|
|
585
|
+
set __candidates $glob_base*
|
|
586
|
+
end
|
|
587
|
+
|
|
385
588
|
# Generate file completions based on type
|
|
386
589
|
set -l items
|
|
387
590
|
switch $type
|
|
388
591
|
case file
|
|
389
592
|
# Complete files and directories (directories for navigation)
|
|
390
|
-
for item in $
|
|
593
|
+
for item in $__candidates
|
|
391
594
|
if test -d $item
|
|
392
595
|
set -a items $item/
|
|
393
596
|
else if test -f $item
|
|
@@ -395,14 +598,14 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
395
598
|
end
|
|
396
599
|
end
|
|
397
600
|
# Fish's * glob does not match dotfiles; add them
|
|
398
|
-
# explicitly when the basename is empty (i.e., $
|
|
601
|
+
# explicitly when the basename is empty (i.e., $glob_base
|
|
399
602
|
# is "" or ends with "/"), because only then are * and
|
|
400
603
|
# .* complementary. When a non-empty basename is present
|
|
401
604
|
# (e.g., "foo"), foo* already covers foo.txt, so foo.*
|
|
402
605
|
# would just produce duplicates.
|
|
403
|
-
if test "$hidden" = "1"
|
|
404
|
-
if test -z "$
|
|
405
|
-
for item in $
|
|
606
|
+
if test "$hidden" = "1" -a $__has_glob -eq 0
|
|
607
|
+
if test -z "$glob_base"; or string match -q '*/' -- "$glob_base"
|
|
608
|
+
for item in $glob_base.*
|
|
406
609
|
if test -d $item
|
|
407
610
|
set -a items $item/
|
|
408
611
|
else if test -f $item
|
|
@@ -413,14 +616,14 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
413
616
|
end
|
|
414
617
|
case directory
|
|
415
618
|
# Complete directories only
|
|
416
|
-
for item in $
|
|
619
|
+
for item in $__candidates
|
|
417
620
|
if test -d $item
|
|
418
621
|
set -a items $item/
|
|
419
622
|
end
|
|
420
623
|
end
|
|
421
|
-
if test "$hidden" = "1"
|
|
422
|
-
if test -z "$
|
|
423
|
-
for item in $
|
|
624
|
+
if test "$hidden" = "1" -a $__has_glob -eq 0
|
|
625
|
+
if test -z "$glob_base"; or string match -q '*/' -- "$glob_base"
|
|
626
|
+
for item in $glob_base.*
|
|
424
627
|
if test -d $item
|
|
425
628
|
set -a items $item/
|
|
426
629
|
end
|
|
@@ -429,16 +632,16 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
429
632
|
end
|
|
430
633
|
case any
|
|
431
634
|
# Complete both files and directories
|
|
432
|
-
for item in $
|
|
635
|
+
for item in $__candidates
|
|
433
636
|
if test -d $item
|
|
434
637
|
set -a items $item/
|
|
435
638
|
else if test -f $item
|
|
436
639
|
set -a items $item
|
|
437
640
|
end
|
|
438
641
|
end
|
|
439
|
-
if test "$hidden" = "1"
|
|
440
|
-
if test -z "$
|
|
441
|
-
for item in $
|
|
642
|
+
if test "$hidden" = "1" -a $__has_glob -eq 0
|
|
643
|
+
if test -z "$glob_base"; or string match -q '*/' -- "$glob_base"
|
|
644
|
+
for item in $glob_base.*
|
|
442
645
|
if test -d $item
|
|
443
646
|
set -a items $item/
|
|
444
647
|
else if test -f $item
|
|
@@ -470,8 +673,11 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
470
673
|
set items $filtered
|
|
471
674
|
end
|
|
472
675
|
|
|
473
|
-
# Filter out hidden files unless requested
|
|
474
|
-
|
|
676
|
+
# Filter out hidden files unless requested.
|
|
677
|
+
# Check the basename of glob_base so that patterns like
|
|
678
|
+
# "src/.e" correctly target hidden entries.
|
|
679
|
+
set -l __glob_basename (string replace -r '.*/(.*)' '$1' -- $glob_base)
|
|
680
|
+
if test "$hidden" != "1" -a (string sub -l 1 -- $__glob_basename) != "."
|
|
475
681
|
set -l filtered
|
|
476
682
|
for item in $items
|
|
477
683
|
set -l basename (basename $item)
|
|
@@ -482,6 +688,15 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
482
688
|
set items $filtered
|
|
483
689
|
end
|
|
484
690
|
|
|
691
|
+
# Restore tilde prefix in completion results
|
|
692
|
+
if test -n "$__tilde_prefix"
|
|
693
|
+
set -l restored
|
|
694
|
+
for item in $items
|
|
695
|
+
set -a restored (string replace "$HOME" "~" -- $item)
|
|
696
|
+
end
|
|
697
|
+
set items $restored
|
|
698
|
+
end
|
|
699
|
+
|
|
485
700
|
# Output file completions
|
|
486
701
|
for item in $items
|
|
487
702
|
echo $item
|
|
@@ -635,10 +850,67 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
635
850
|
""
|
|
636
851
|
}
|
|
637
852
|
|
|
638
|
-
#
|
|
639
|
-
#
|
|
853
|
+
# When a pattern is specified, use it as the glob base instead of
|
|
854
|
+
# the user-typed prefix. If the user has already typed beyond the
|
|
855
|
+
# pattern (e.g., pattern="src/" and prefix="src/ma"), keep the
|
|
856
|
+
# prefix for incremental narrowing. Normalize path separators
|
|
857
|
+
# before comparing so that Windows backslashes match forward slashes
|
|
858
|
+
# in the transported pattern.
|
|
859
|
+
let glob_base = if ($pattern | is-not-empty) {
|
|
860
|
+
# Normalize separators and leading ./; downcase only on Windows
|
|
861
|
+
# where filesystems are typically case-insensitive
|
|
862
|
+
let norm_prefix_raw = ($prefix | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
863
|
+
let norm_pattern_raw = ($pattern | str replace -a '\\' '/' | str replace -r '^\\./' '')
|
|
864
|
+
let is_win = (($nu.os-info.name | str downcase) == "windows")
|
|
865
|
+
let norm_prefix = (if $is_win { $norm_prefix_raw | str downcase } else { $norm_prefix_raw })
|
|
866
|
+
let norm_pattern = (if $is_win { $norm_pattern_raw | str downcase } else { $norm_pattern_raw })
|
|
867
|
+
# For wildcard patterns, compare only the directory prefix
|
|
868
|
+
let compare_pattern = if ($norm_pattern =~ '[*?]') {
|
|
869
|
+
let before_wild = ($norm_pattern | str replace -r '[*?].*' '')
|
|
870
|
+
if ($before_wild =~ '/') { $before_wild | str replace -r '/[^/]*$' '/' } else { "" }
|
|
871
|
+
} else {
|
|
872
|
+
$norm_pattern
|
|
873
|
+
}
|
|
874
|
+
if (($compare_pattern | is-not-empty) or ($norm_prefix | is-not-empty)) and ($norm_prefix | str starts-with $compare_pattern) and (($norm_prefix | str length) >= ($compare_pattern | str length)) and ($prefix != $pattern) {
|
|
875
|
+
$prefix
|
|
876
|
+
} else {
|
|
877
|
+
$pattern
|
|
878
|
+
}
|
|
879
|
+
} else {
|
|
880
|
+
$prefix
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
# If the glob base is a directory without a trailing slash (e.g., "src"),
|
|
884
|
+
# append one so that ls enumerates its contents instead of matching
|
|
885
|
+
# siblings like "src-old"
|
|
886
|
+
let glob_base = if ($glob_base | is-not-empty) and ($glob_base | path type) == "dir" and (not ($glob_base | str ends-with "/")) {
|
|
887
|
+
$glob_base + "/"
|
|
888
|
+
} else {
|
|
889
|
+
$glob_base
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
# Generate file completions based on type.
|
|
893
|
+
# When the glob base contains wildcard characters, use it as-is;
|
|
894
|
+
# otherwise append * to treat it as a prefix.
|
|
640
895
|
# Note: into glob is required so that ls expands wildcards from a variable
|
|
641
|
-
let
|
|
896
|
+
let has_glob = ($glob_base =~ '[*?]')
|
|
897
|
+
let ls_pattern = if ($glob_base | is-empty) {
|
|
898
|
+
"."
|
|
899
|
+
} else if $has_glob {
|
|
900
|
+
($glob_base | into glob)
|
|
901
|
+
} else {
|
|
902
|
+
($glob_base + "*" | into glob)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
# When using a glob pattern, also compute a directory listing pattern
|
|
906
|
+
# so that file/any modes can include directories for navigation
|
|
907
|
+
let glob_dir_pattern = if $has_glob {
|
|
908
|
+
let dir_part = ($glob_base | path dirname)
|
|
909
|
+
let dir = if ($dir_part | is-empty) or ($dir_part == $glob_base) { "." } else { $dir_part }
|
|
910
|
+
($dir + "/*" | into glob)
|
|
911
|
+
} else {
|
|
912
|
+
null
|
|
913
|
+
}
|
|
642
914
|
|
|
643
915
|
# Use ls -a to include hidden files when requested
|
|
644
916
|
let items = try {
|
|
@@ -679,8 +951,25 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
679
951
|
[]
|
|
680
952
|
}
|
|
681
953
|
|
|
682
|
-
#
|
|
683
|
-
|
|
954
|
+
# When using a glob pattern, also add directories from the base
|
|
955
|
+
# directory for navigation (file/any modes should list directories
|
|
956
|
+
# even when the glob itself does not match directory names)
|
|
957
|
+
let items = if ($glob_dir_pattern != null) and ($type != "directory") {
|
|
958
|
+
let extra_dirs = try {
|
|
959
|
+
(if $hidden { ls -a $glob_dir_pattern } else { ls $glob_dir_pattern }) | where type == dir
|
|
960
|
+
} catch {
|
|
961
|
+
[]
|
|
962
|
+
}
|
|
963
|
+
$items | append $extra_dirs | uniq-by name
|
|
964
|
+
} else {
|
|
965
|
+
$items
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
# Filter out hidden files unless requested.
|
|
969
|
+
# Check the basename of glob_base so that patterns like "src/.e"
|
|
970
|
+
# correctly target hidden entries.
|
|
971
|
+
let glob_basename = ($glob_base | path basename)
|
|
972
|
+
let filtered = if $hidden or ($glob_basename | str starts-with '.') {
|
|
684
973
|
$items
|
|
685
974
|
} else {
|
|
686
975
|
$items | where {|item|
|
|
@@ -690,12 +979,12 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
690
979
|
}
|
|
691
980
|
|
|
692
981
|
# Extract directory prefix to preserve in completion text
|
|
693
|
-
let dir_prefix = if ($
|
|
982
|
+
let dir_prefix = if ($glob_base | is-empty) {
|
|
694
983
|
""
|
|
695
|
-
} else if ($
|
|
696
|
-
$
|
|
984
|
+
} else if ($glob_base | str ends-with "/") {
|
|
985
|
+
$glob_base
|
|
697
986
|
} else {
|
|
698
|
-
let parsed = ($
|
|
987
|
+
let parsed = ($glob_base | path parse)
|
|
699
988
|
if ($parsed.parent | is-empty) { "" } else if ($parsed.parent | str ends-with "/") { $parsed.parent } else { $parsed.parent + "/" }
|
|
700
989
|
}
|
|
701
990
|
|
|
@@ -836,11 +1125,47 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
836
1125
|
\$pattern = \$parts[3] -replace '%3A', ':' -replace '%25', '%'
|
|
837
1126
|
\$hidden = \$parts[4] -eq '1'
|
|
838
1127
|
|
|
839
|
-
#
|
|
840
|
-
|
|
1128
|
+
# When a pattern is specified, use it as the file matching
|
|
1129
|
+
# prefix instead of the current word. If the user has
|
|
1130
|
+
# already typed beyond the pattern, keep their input for
|
|
1131
|
+
# incremental narrowing. Normalize path separators before
|
|
1132
|
+
# comparing so that Windows backslashes match forward slashes
|
|
1133
|
+
# in the transported pattern.
|
|
1134
|
+
\$normalizedPattern = if (\$pattern) { \$pattern.Replace('\\', '/') -replace '^\\./','' } else { '' }
|
|
1135
|
+
\$normalizedWord = if (\$wordToComplete) { \$wordToComplete.Replace('\\', '/') -replace '^\\./','' } else { '' }
|
|
1136
|
+
\$comparison = if (\$IsWindows) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
|
|
1137
|
+
# For wildcard patterns, compare only the directory prefix
|
|
1138
|
+
\$comparePattern = \$normalizedPattern
|
|
1139
|
+
if (\$comparePattern -match '[\*\?]') {
|
|
1140
|
+
\$beforeWild = \$comparePattern -replace '[\*\?].*', ''
|
|
1141
|
+
if (\$beforeWild.Contains('/')) {
|
|
1142
|
+
\$comparePattern = \$beforeWild.Substring(0, \$beforeWild.LastIndexOf('/') + 1)
|
|
1143
|
+
} else {
|
|
1144
|
+
\$comparePattern = ''
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
\$prefix = if ((\$comparePattern -or \$normalizedWord) -and \$normalizedWord -and \$normalizedWord.StartsWith(\$comparePattern, \$comparison) -and \$normalizedWord.Length -ge \$comparePattern.Length -and \$wordToComplete -ne \$pattern) {
|
|
1148
|
+
\$wordToComplete
|
|
1149
|
+
} elseif (\$pattern) {
|
|
1150
|
+
\$pattern
|
|
1151
|
+
} elseif (\$wordToComplete) {
|
|
1152
|
+
\$wordToComplete
|
|
1153
|
+
} else { '' }
|
|
1154
|
+
|
|
1155
|
+
# Use -Force to include hidden files when requested, or when
|
|
1156
|
+
# the prefix basename targets dotfiles (e.g., src/.e)
|
|
1157
|
+
\$prefixBasename = Split-Path -Leaf \$prefix 2>\$null
|
|
1158
|
+
\$forceParam = if (\$hidden -or (\$prefixBasename -and \$prefixBasename.StartsWith('.'))) { @{Force = \$true} } else { @{} }
|
|
1159
|
+
|
|
1160
|
+
# If prefix is a directory without trailing slash, append
|
|
1161
|
+
# one so Get-ChildItem lists its contents
|
|
1162
|
+
if (\$prefix -and (Test-Path -Path \$prefix -PathType Container) -and -not \$prefix.EndsWith('/') -and -not \$prefix.EndsWith('\\')) {
|
|
1163
|
+
\$prefix = \$prefix + '/'
|
|
1164
|
+
}
|
|
841
1165
|
|
|
842
|
-
#
|
|
843
|
-
|
|
1166
|
+
# Build the glob path — when the prefix contains wildcard
|
|
1167
|
+
# characters, use it as-is; otherwise append *
|
|
1168
|
+
\$globPath = if (\$prefix -match '[\*\?]') { \$prefix } else { "\${prefix}*" }
|
|
844
1169
|
|
|
845
1170
|
# Get file system items based on type
|
|
846
1171
|
\$items = @()
|
|
@@ -849,37 +1174,46 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
849
1174
|
if (\$extensions) {
|
|
850
1175
|
# Filter by extensions, always include directories
|
|
851
1176
|
\$extList = \$extensions -split ','
|
|
852
|
-
\$items = Get-ChildItem @forceParam -Path
|
|
1177
|
+
\$items = Get-ChildItem @forceParam -Path \$globPath -ErrorAction SilentlyContinue |
|
|
853
1178
|
Where-Object {
|
|
854
1179
|
if (\$_.PSIsContainer) { return \$true }
|
|
855
1180
|
\$ext = \$_.Extension
|
|
856
1181
|
\$extList | ForEach-Object { if (\$ext -eq ".\$_") { return \$true } }
|
|
857
1182
|
}
|
|
858
1183
|
} else {
|
|
859
|
-
\$items = Get-ChildItem @forceParam -Path
|
|
1184
|
+
\$items = Get-ChildItem @forceParam -Path \$globPath -ErrorAction SilentlyContinue
|
|
860
1185
|
}
|
|
861
1186
|
}
|
|
862
1187
|
'directory' {
|
|
863
|
-
\$items = Get-ChildItem @forceParam -Directory -Path
|
|
1188
|
+
\$items = Get-ChildItem @forceParam -Directory -Path \$globPath -ErrorAction SilentlyContinue
|
|
864
1189
|
}
|
|
865
1190
|
'any' {
|
|
866
1191
|
if (\$extensions) {
|
|
867
1192
|
# Filter by extensions, always include directories
|
|
868
1193
|
\$extList = \$extensions -split ','
|
|
869
|
-
\$items = Get-ChildItem @forceParam -Path
|
|
1194
|
+
\$items = Get-ChildItem @forceParam -Path \$globPath -ErrorAction SilentlyContinue |
|
|
870
1195
|
Where-Object {
|
|
871
1196
|
if (\$_.PSIsContainer) { return \$true }
|
|
872
1197
|
\$ext = \$_.Extension
|
|
873
1198
|
\$extList | ForEach-Object { if (\$ext -eq ".\$_") { return \$true } }
|
|
874
1199
|
}
|
|
875
1200
|
} else {
|
|
876
|
-
\$items = Get-ChildItem @forceParam -Path
|
|
1201
|
+
\$items = Get-ChildItem @forceParam -Path \$globPath -ErrorAction SilentlyContinue
|
|
877
1202
|
}
|
|
878
1203
|
}
|
|
879
1204
|
}
|
|
880
1205
|
|
|
881
|
-
#
|
|
882
|
-
|
|
1206
|
+
# For file/any modes with glob patterns, also add directories
|
|
1207
|
+
# from the base directory for navigation
|
|
1208
|
+
if (\$prefix -match '[\*\?]' -and \$type -ne 'directory') {
|
|
1209
|
+
\$globDir = Split-Path -Parent \$prefix
|
|
1210
|
+
if (-not \$globDir) { \$globDir = '.' }
|
|
1211
|
+
\$extraDirs = Get-ChildItem @forceParam -Directory -Path "\$globDir/*" -ErrorAction SilentlyContinue
|
|
1212
|
+
if (\$extraDirs) { \$items = @(\$items) + @(\$extraDirs) | Select-Object -Unique }
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
# Filter hidden files unless requested or the prefix targets dotfiles
|
|
1216
|
+
if (-not \$hidden -and -not (\$prefixBasename -and \$prefixBasename.StartsWith('.'))) {
|
|
883
1217
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
884
1218
|
}
|
|
885
1219
|
|