@optique/core 0.10.7 → 1.0.0-dev.1116
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/README.md +4 -6
- package/dist/annotations.cjs +209 -1
- package/dist/annotations.d.cts +78 -1
- package/dist/annotations.d.ts +78 -1
- package/dist/annotations.js +201 -1
- package/dist/completion.cjs +194 -52
- package/dist/completion.js +194 -52
- package/dist/constructs.cjs +310 -78
- package/dist/constructs.d.cts +525 -644
- package/dist/constructs.d.ts +525 -644
- package/dist/constructs.js +311 -79
- package/dist/context.cjs +43 -3
- package/dist/context.d.cts +113 -5
- package/dist/context.d.ts +113 -5
- package/dist/context.js +41 -3
- package/dist/dependency.cjs +172 -66
- package/dist/dependency.d.cts +22 -2
- package/dist/dependency.d.ts +22 -2
- package/dist/dependency.js +172 -66
- package/dist/doc.cjs +46 -1
- package/dist/doc.d.cts +24 -0
- package/dist/doc.d.ts +24 -0
- package/dist/doc.js +46 -1
- package/dist/facade.cjs +702 -322
- package/dist/facade.d.cts +124 -190
- package/dist/facade.d.ts +124 -190
- package/dist/facade.js +703 -323
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/message.cjs +7 -4
- package/dist/message.js +7 -4
- package/dist/mode-dispatch.cjs +23 -1
- package/dist/mode-dispatch.d.cts +55 -0
- package/dist/mode-dispatch.d.ts +55 -0
- package/dist/mode-dispatch.js +21 -1
- package/dist/modifiers.cjs +210 -55
- package/dist/modifiers.js +211 -56
- package/dist/parser.cjs +80 -47
- package/dist/parser.d.cts +18 -3
- package/dist/parser.d.ts +18 -3
- package/dist/parser.js +82 -50
- package/dist/primitives.cjs +102 -37
- package/dist/primitives.d.cts +81 -24
- package/dist/primitives.d.ts +81 -24
- package/dist/primitives.js +103 -39
- package/dist/usage.cjs +88 -6
- package/dist/usage.d.cts +51 -13
- package/dist/usage.d.ts +51 -13
- package/dist/usage.js +85 -7
- package/dist/valueparser.cjs +391 -106
- package/dist/valueparser.d.cts +62 -10
- package/dist/valueparser.d.ts +62 -10
- package/dist/valueparser.js +391 -106
- package/package.json +10 -1
package/dist/completion.js
CHANGED
|
@@ -5,16 +5,17 @@ import { formatMessage } from "./message.js";
|
|
|
5
5
|
* A regular expression pattern for valid program names that can be safely
|
|
6
6
|
* interpolated into shell scripts.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* The first character must be alphanumeric or underscore. Subsequent
|
|
9
|
+
* characters may also include hyphens and dots:
|
|
9
10
|
* - Letters (a-z, A-Z)
|
|
10
11
|
* - Numbers (0-9)
|
|
11
12
|
* - Underscore (_)
|
|
12
|
-
* - Hyphen (-)
|
|
13
|
-
* - Dot (.)
|
|
13
|
+
* - Hyphen (-) — not as the first character
|
|
14
|
+
* - Dot (.) — not as the first character
|
|
14
15
|
*
|
|
15
16
|
* @internal
|
|
16
17
|
*/
|
|
17
|
-
const SAFE_PROGRAM_NAME_PATTERN = /^[a-zA-Z0-9_.-]
|
|
18
|
+
const SAFE_PROGRAM_NAME_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
|
|
18
19
|
/**
|
|
19
20
|
* Validates a program name for safe use in shell scripts.
|
|
20
21
|
*
|
|
@@ -27,7 +28,17 @@ const SAFE_PROGRAM_NAME_PATTERN = /^[a-zA-Z0-9_.-]+$/;
|
|
|
27
28
|
* @internal
|
|
28
29
|
*/
|
|
29
30
|
function validateProgramName(programName) {
|
|
30
|
-
if (!SAFE_PROGRAM_NAME_PATTERN.test(programName)) throw new Error(`Invalid program name for shell completion: "${programName}". Program names must contain only alphanumeric characters, underscores, hyphens, and dots.`);
|
|
31
|
+
if (!SAFE_PROGRAM_NAME_PATTERN.test(programName)) throw new Error(`Invalid program name for shell completion: "${programName}". Program names must start with an alphanumeric character or underscore, and contain only alphanumeric characters, underscores, hyphens, and dots.`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Percent-encodes colons and percent signs in a pattern string so that it
|
|
35
|
+
* can be safely embedded in the colon-delimited `__FILE__` transport format.
|
|
36
|
+
* @param pattern The raw pattern string.
|
|
37
|
+
* @returns The encoded pattern string.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
function encodePattern(pattern) {
|
|
41
|
+
return pattern.replace(/%/g, "%25").replace(/:/g, "%3A");
|
|
31
42
|
}
|
|
32
43
|
/**
|
|
33
44
|
* The Bash shell completion generator.
|
|
@@ -47,6 +58,47 @@ function _${programName} () {
|
|
|
47
58
|
if [[ "$line" == __FILE__:* ]]; then
|
|
48
59
|
# Parse file completion directive: __FILE__:type:extensions:pattern:hidden
|
|
49
60
|
IFS=':' read -r _ type extensions pattern hidden <<< "$line"
|
|
61
|
+
pattern="\${pattern//%3A/:}"; pattern="\${pattern//%25/%}"
|
|
62
|
+
|
|
63
|
+
# Save and adjust glob/shell options for safe file completion
|
|
64
|
+
local __dotglob_was_set=0 __failglob_was_set=0 __noglob_was_set=0
|
|
65
|
+
local __globignore_was_set=0 __saved_globignore="\${GLOBIGNORE-}"
|
|
66
|
+
[[ \${GLOBIGNORE+x} == x ]] && __globignore_was_set=1
|
|
67
|
+
shopt -q dotglob && __dotglob_was_set=1
|
|
68
|
+
shopt -q failglob && __failglob_was_set=1
|
|
69
|
+
[[ $- == *f* ]] && __noglob_was_set=1
|
|
70
|
+
# Unset GLOBIGNORE before enabling dotglob because unsetting
|
|
71
|
+
# GLOBIGNORE implicitly clears dotglob in Bash
|
|
72
|
+
shopt -u failglob 2>/dev/null
|
|
73
|
+
set +f
|
|
74
|
+
unset GLOBIGNORE
|
|
75
|
+
|
|
76
|
+
# Expand tilde prefix for file globbing
|
|
77
|
+
local __glob_current="$current" __tilde_prefix="" __tilde_expanded=""
|
|
78
|
+
if [[ "$current" =~ ^(~[a-zA-Z0-9_.+-]*)(/.*)$ ]]; then
|
|
79
|
+
__tilde_prefix="\${BASH_REMATCH[1]}"
|
|
80
|
+
eval "__tilde_expanded=\$__tilde_prefix" 2>/dev/null || true
|
|
81
|
+
if [[ -n "$__tilde_expanded" && "$__tilde_expanded" != "$__tilde_prefix" ]]; then
|
|
82
|
+
__glob_current="\${__tilde_expanded}\${current#\$__tilde_prefix}"
|
|
83
|
+
else
|
|
84
|
+
__tilde_prefix=""
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Enable dotglob when hidden files are requested, or when the user
|
|
89
|
+
# is already navigating inside a hidden directory (e.g., ~/.config/nvim/)
|
|
90
|
+
# This runs after tilde expansion so that paths like ~/.config/ are
|
|
91
|
+
# checked against the expanded path, not the literal ~ string.
|
|
92
|
+
local __inside_hidden_path=0
|
|
93
|
+
case "/\${__glob_current%/}/" in
|
|
94
|
+
*/.[!.]*/*|*/..?*/*) __inside_hidden_path=1 ;;
|
|
95
|
+
esac
|
|
96
|
+
# Also check if the current prefix explicitly targets hidden entries
|
|
97
|
+
# (e.g., user typed "." or ".e" to complete .env)
|
|
98
|
+
local __prefix_targets_hidden=0
|
|
99
|
+
local __prefix_base="\${__glob_current##*/}"
|
|
100
|
+
[[ "$__prefix_base" == .* ]] && __prefix_targets_hidden=1
|
|
101
|
+
if [[ "$hidden" == "1" || "$__inside_hidden_path" == "1" || "$__prefix_targets_hidden" == "1" ]]; then shopt -s dotglob; fi
|
|
50
102
|
|
|
51
103
|
# Generate file completions based on type
|
|
52
104
|
case "$type" in
|
|
@@ -55,21 +107,21 @@ function _${programName} () {
|
|
|
55
107
|
if [[ -n "$extensions" ]]; then
|
|
56
108
|
# Complete with extension filtering
|
|
57
109
|
local ext_pattern="\${extensions//,/|}"
|
|
58
|
-
for file in "$
|
|
59
|
-
[[ -
|
|
110
|
+
for file in "$__glob_current"*; do
|
|
111
|
+
[[ -f "$file" && "$file" =~ \\.($ext_pattern)$ ]] && COMPREPLY+=("$file")
|
|
60
112
|
done
|
|
61
113
|
else
|
|
62
114
|
# Complete files only, exclude directories
|
|
63
|
-
|
|
115
|
+
for item in "$__glob_current"*; do
|
|
64
116
|
[[ -f "$item" ]] && COMPREPLY+=("$item")
|
|
65
|
-
done
|
|
117
|
+
done
|
|
66
118
|
fi
|
|
67
119
|
;;
|
|
68
120
|
directory)
|
|
69
121
|
# Complete directories only
|
|
70
|
-
|
|
71
|
-
COMPREPLY+=("$dir/")
|
|
72
|
-
done
|
|
122
|
+
for dir in "$__glob_current"*; do
|
|
123
|
+
[[ -d "$dir" ]] && COMPREPLY+=("$dir/")
|
|
124
|
+
done
|
|
73
125
|
;;
|
|
74
126
|
any)
|
|
75
127
|
# Complete both files and directories
|
|
@@ -77,31 +129,50 @@ function _${programName} () {
|
|
|
77
129
|
# Files with extension filtering + directories
|
|
78
130
|
# Files with extension filtering
|
|
79
131
|
local ext_pattern="\${extensions//,/|}"
|
|
80
|
-
for item in "$
|
|
132
|
+
for item in "$__glob_current"*; do
|
|
81
133
|
if [[ -d "$item" ]]; then
|
|
82
134
|
COMPREPLY+=("$item/")
|
|
83
|
-
elif [[ -
|
|
135
|
+
elif [[ ( -e "$item" || -L "$item" ) && "$item" =~ \\.($ext_pattern)$ ]]; then
|
|
84
136
|
COMPREPLY+=("$item")
|
|
85
137
|
fi
|
|
86
138
|
done
|
|
87
139
|
else
|
|
88
140
|
# Complete files and directories, add slash to directories
|
|
89
|
-
|
|
141
|
+
for item in "$__glob_current"*; do
|
|
90
142
|
if [[ -d "$item" ]]; then
|
|
91
143
|
COMPREPLY+=("$item/")
|
|
92
|
-
|
|
144
|
+
# Use -e || -L to include non-regular files (sockets, FIFOs, dangling symlinks)
|
|
145
|
+
elif [[ -e "$item" || -L "$item" ]]; then
|
|
93
146
|
COMPREPLY+=("$item")
|
|
94
147
|
fi
|
|
95
|
-
done
|
|
148
|
+
done
|
|
96
149
|
fi
|
|
97
150
|
;;
|
|
98
151
|
esac
|
|
99
152
|
|
|
153
|
+
# Restore tilde prefix in completion results
|
|
154
|
+
if [[ -n "$__tilde_prefix" ]]; then
|
|
155
|
+
local __i
|
|
156
|
+
for __i in "\${!COMPREPLY[@]}"; do
|
|
157
|
+
COMPREPLY[\$__i]="\${COMPREPLY[\$__i]/#\$__tilde_expanded/\$__tilde_prefix}"
|
|
158
|
+
done
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Restore glob/shell options
|
|
162
|
+
# Restore GLOBIGNORE before dotglob because assigning GLOBIGNORE
|
|
163
|
+
# implicitly enables dotglob in Bash
|
|
164
|
+
if [[ "$__globignore_was_set" == "1" ]]; then GLOBIGNORE="$__saved_globignore"; fi
|
|
165
|
+
if [[ "$__dotglob_was_set" == "0" ]]; then shopt -u dotglob; else shopt -s dotglob; fi
|
|
166
|
+
if [[ "$__failglob_was_set" == "1" ]]; then shopt -s failglob; fi
|
|
167
|
+
if [[ "$__noglob_was_set" == "1" ]]; then set -f; fi
|
|
168
|
+
|
|
100
169
|
# Filter out hidden files unless requested
|
|
101
|
-
if [[ "$hidden" != "1" && "$
|
|
170
|
+
if [[ "$hidden" != "1" && "$__inside_hidden_path" == "0" && "$__prefix_targets_hidden" == "0" ]]; then
|
|
102
171
|
local filtered=()
|
|
172
|
+
local __name
|
|
103
173
|
for item in "\${COMPREPLY[@]}"; do
|
|
104
|
-
|
|
174
|
+
__name="\${item%/}"; __name="\${__name##*/}"
|
|
175
|
+
[[ "$__name" != .* ]] && filtered+=("$item")
|
|
105
176
|
done
|
|
106
177
|
COMPREPLY=("\${filtered[@]}")
|
|
107
178
|
fi
|
|
@@ -123,7 +194,8 @@ complete -F _${programName} ${programName}
|
|
|
123
194
|
else {
|
|
124
195
|
const extensions = suggestion.extensions?.join(",") || "";
|
|
125
196
|
const hidden = suggestion.includeHidden ? "1" : "0";
|
|
126
|
-
|
|
197
|
+
const pattern = encodePattern(suggestion.pattern ?? "");
|
|
198
|
+
yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}`;
|
|
127
199
|
}
|
|
128
200
|
i++;
|
|
129
201
|
}
|
|
@@ -175,15 +247,22 @@ function _${programName.replace(/[^a-zA-Z0-9]/g, "_")} () {
|
|
|
175
247
|
# Parse file completion directive: __FILE__:type:extensions:pattern:hidden
|
|
176
248
|
local type extensions pattern hidden
|
|
177
249
|
IFS=':' read -r _ type extensions pattern hidden <<< "\$value"
|
|
250
|
+
pattern="\${pattern//%3A/:}"; pattern="\${pattern//%25/%}"
|
|
178
251
|
has_file_completion=1
|
|
179
252
|
|
|
253
|
+
# Enable glob_dots when hidden files are requested so that
|
|
254
|
+
# _files and _directories include dot-prefixed entries
|
|
255
|
+
local __was_glob_dots=0
|
|
256
|
+
[[ -o glob_dots ]] && __was_glob_dots=1
|
|
257
|
+
if [[ "\$hidden" == "1" ]]; then setopt glob_dots; fi
|
|
258
|
+
|
|
180
259
|
# Use zsh's native file completion
|
|
181
260
|
case "\$type" in
|
|
182
261
|
file)
|
|
183
262
|
if [[ -n "\$extensions" ]]; then
|
|
184
263
|
# Complete files with extension filtering
|
|
185
|
-
local ext_pattern="*.(
|
|
186
|
-
_files -g "
|
|
264
|
+
local ext_pattern="*.(\$\{extensions//,/|\})"
|
|
265
|
+
_files -g "\$ext_pattern"
|
|
187
266
|
else
|
|
188
267
|
_files -g "*"
|
|
189
268
|
fi
|
|
@@ -194,16 +273,16 @@ function _${programName.replace(/[^a-zA-Z0-9]/g, "_")} () {
|
|
|
194
273
|
any)
|
|
195
274
|
if [[ -n "\$extensions" ]]; then
|
|
196
275
|
# Complete both files and directories, with extension filtering for files
|
|
197
|
-
local ext_pattern="*.(
|
|
198
|
-
_files -g "
|
|
276
|
+
local ext_pattern="*.(\$\{extensions//,/|\})"
|
|
277
|
+
_files -g "\$ext_pattern" && _directories
|
|
199
278
|
else
|
|
200
279
|
_files
|
|
201
280
|
fi
|
|
202
281
|
;;
|
|
203
282
|
esac
|
|
204
283
|
|
|
205
|
-
#
|
|
206
|
-
|
|
284
|
+
# Restore glob_dots to its previous state
|
|
285
|
+
if [[ "\$__was_glob_dots" == "1" ]]; then setopt glob_dots; else unsetopt glob_dots; fi
|
|
207
286
|
else
|
|
208
287
|
# Regular literal completion
|
|
209
288
|
if [[ -n "\$value" ]]; then
|
|
@@ -241,7 +320,8 @@ compdef _${programName.replace(/[^a-zA-Z0-9]/g, "_")} ${programName}
|
|
|
241
320
|
const extensions = suggestion.extensions?.join(",") || "";
|
|
242
321
|
const hidden = suggestion.includeHidden ? "1" : "0";
|
|
243
322
|
const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
|
|
244
|
-
|
|
323
|
+
const pattern = encodePattern(suggestion.pattern ?? "");
|
|
324
|
+
yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\0${description}\0`;
|
|
245
325
|
}
|
|
246
326
|
}
|
|
247
327
|
};
|
|
@@ -273,10 +353,11 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
273
353
|
for line in $output
|
|
274
354
|
if string match -q '__FILE__:*' -- $line
|
|
275
355
|
# Parse file completion directive: __FILE__:type:extensions:pattern:hidden
|
|
276
|
-
set -l
|
|
356
|
+
set -l directive (string split \\t -- $line)[1]
|
|
357
|
+
set -l parts (string split ':' -- $directive)
|
|
277
358
|
set -l type $parts[2]
|
|
278
359
|
set -l extensions $parts[3]
|
|
279
|
-
set -l pattern $parts[4]
|
|
360
|
+
set -l pattern (string replace -a '%25' '%' -- (string replace -a '%3A' ':' -- $parts[4]))
|
|
280
361
|
set -l hidden $parts[5]
|
|
281
362
|
|
|
282
363
|
# Generate file completions based on type
|
|
@@ -289,6 +370,21 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
289
370
|
set -a items $item
|
|
290
371
|
end
|
|
291
372
|
end
|
|
373
|
+
# Fish's * glob does not match dotfiles; add them
|
|
374
|
+
# explicitly when the basename is empty (i.e., $current
|
|
375
|
+
# is "" or ends with "/"), because only then are * and
|
|
376
|
+
# .* complementary. When a non-empty basename is present
|
|
377
|
+
# (e.g., "foo"), foo* already covers foo.txt, so foo.*
|
|
378
|
+
# would just produce duplicates.
|
|
379
|
+
if test "$hidden" = "1"
|
|
380
|
+
if test -z "$current"; or string match -q '*/' -- "$current"
|
|
381
|
+
for item in $current.*
|
|
382
|
+
if test -f $item
|
|
383
|
+
set -a items $item
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
292
388
|
case directory
|
|
293
389
|
# Complete directories only
|
|
294
390
|
for item in $current*
|
|
@@ -296,6 +392,15 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
296
392
|
set -a items $item/
|
|
297
393
|
end
|
|
298
394
|
end
|
|
395
|
+
if test "$hidden" = "1"
|
|
396
|
+
if test -z "$current"; or string match -q '*/' -- "$current"
|
|
397
|
+
for item in $current.*
|
|
398
|
+
if test -d $item
|
|
399
|
+
set -a items $item/
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
299
404
|
case any
|
|
300
405
|
# Complete both files and directories
|
|
301
406
|
for item in $current*
|
|
@@ -305,6 +410,17 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
305
410
|
set -a items $item
|
|
306
411
|
end
|
|
307
412
|
end
|
|
413
|
+
if test "$hidden" = "1"
|
|
414
|
+
if test -z "$current"; or string match -q '*/' -- "$current"
|
|
415
|
+
for item in $current.*
|
|
416
|
+
if test -d $item
|
|
417
|
+
set -a items $item/
|
|
418
|
+
else if test -f $item
|
|
419
|
+
set -a items $item
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
308
424
|
end
|
|
309
425
|
|
|
310
426
|
# Filter by extensions if specified
|
|
@@ -372,7 +488,8 @@ complete -c ${programName} -f -a '(${functionName})'
|
|
|
372
488
|
const extensions = suggestion.extensions?.join(",") || "";
|
|
373
489
|
const hidden = suggestion.includeHidden ? "1" : "0";
|
|
374
490
|
const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
|
|
375
|
-
|
|
491
|
+
const pattern = encodePattern(suggestion.pattern ?? "");
|
|
492
|
+
yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t${description}`;
|
|
376
493
|
}
|
|
377
494
|
i++;
|
|
378
495
|
}
|
|
@@ -478,10 +595,11 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
478
595
|
$output | lines | each {|line|
|
|
479
596
|
if ($line | str starts-with '__FILE__:') {
|
|
480
597
|
# Parse file completion directive: __FILE__:type:extensions:pattern:hidden
|
|
481
|
-
let
|
|
598
|
+
let directive = ($line | split row "\t" | first)
|
|
599
|
+
let parts = ($directive | split row ':')
|
|
482
600
|
let type = ($parts | get 1)
|
|
483
601
|
let extensions = ($parts | get 2)
|
|
484
|
-
let pattern = ($parts | get 3)
|
|
602
|
+
let pattern = ($parts | get 3 | str replace -a '%3A' ':' | str replace -a '%25' '%')
|
|
485
603
|
let hidden = ($parts | get 4) == '1'
|
|
486
604
|
|
|
487
605
|
# Extract prefix from the last argument if it exists
|
|
@@ -493,31 +611,33 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
493
611
|
|
|
494
612
|
# Generate file completions based on type
|
|
495
613
|
# Use current directory if prefix is empty
|
|
496
|
-
|
|
614
|
+
# Note: into glob is required so that ls expands wildcards from a variable
|
|
615
|
+
let ls_pattern = if ($prefix | is-empty) { "." } else { ($prefix + "*" | into glob) }
|
|
497
616
|
|
|
617
|
+
# Use ls -a to include hidden files when requested
|
|
498
618
|
let items = try {
|
|
499
619
|
match $type {
|
|
500
620
|
"file" => {
|
|
501
621
|
if ($extensions | is-empty) {
|
|
502
|
-
ls $ls_pattern | where type == file
|
|
622
|
+
(if $hidden { ls -a $ls_pattern } else { ls $ls_pattern }) | where type == file
|
|
503
623
|
} else {
|
|
504
624
|
let ext_list = ($extensions | split row ',')
|
|
505
|
-
ls $ls_pattern | where type == file | where {|f|
|
|
625
|
+
(if $hidden { ls -a $ls_pattern } else { ls $ls_pattern }) | where type == file | where {|f|
|
|
506
626
|
let ext = ($f.name | path parse | get extension)
|
|
507
627
|
$ext in $ext_list
|
|
508
628
|
}
|
|
509
629
|
}
|
|
510
630
|
},
|
|
511
631
|
"directory" => {
|
|
512
|
-
ls $ls_pattern | where type == dir
|
|
632
|
+
(if $hidden { ls -a $ls_pattern } else { ls $ls_pattern }) | where type == dir
|
|
513
633
|
},
|
|
514
634
|
"any" => {
|
|
515
635
|
if ($extensions | is-empty) {
|
|
516
|
-
ls $ls_pattern
|
|
636
|
+
if $hidden { ls -a $ls_pattern } else { ls $ls_pattern }
|
|
517
637
|
} else {
|
|
518
638
|
let ext_list = ($extensions | split row ',')
|
|
519
|
-
let dirs = ls $ls_pattern | where type == dir
|
|
520
|
-
let files = ls $ls_pattern | where type == file | where {|f|
|
|
639
|
+
let dirs = (if $hidden { ls -a $ls_pattern } else { ls $ls_pattern }) | where type == dir
|
|
640
|
+
let files = (if $hidden { ls -a $ls_pattern } else { ls $ls_pattern }) | where type == file | where {|f|
|
|
521
641
|
let ext = ($f.name | path parse | get extension)
|
|
522
642
|
$ext in $ext_list
|
|
523
643
|
}
|
|
@@ -539,12 +659,22 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
539
659
|
}
|
|
540
660
|
}
|
|
541
661
|
|
|
662
|
+
# Extract directory prefix to preserve in completion text
|
|
663
|
+
let dir_prefix = if ($prefix | is-empty) {
|
|
664
|
+
""
|
|
665
|
+
} else if ($prefix | str ends-with "/") {
|
|
666
|
+
$prefix
|
|
667
|
+
} else {
|
|
668
|
+
let parsed = ($prefix | path parse)
|
|
669
|
+
if ($parsed.parent | is-empty) { "" } else if ($parsed.parent | str ends-with "/") { $parsed.parent } else { $parsed.parent + "/" }
|
|
670
|
+
}
|
|
671
|
+
|
|
542
672
|
# Format file completions
|
|
543
673
|
$filtered | each {|item|
|
|
544
674
|
let name = if $item.type == dir {
|
|
545
|
-
($item.name | path basename) + "/"
|
|
675
|
+
$dir_prefix + ($item.name | path basename) + "/"
|
|
546
676
|
} else {
|
|
547
|
-
$item.name | path basename
|
|
677
|
+
$dir_prefix + ($item.name | path basename)
|
|
548
678
|
}
|
|
549
679
|
{ value: $name }
|
|
550
680
|
}
|
|
@@ -602,7 +732,8 @@ ${functionName}-external
|
|
|
602
732
|
const extensions = suggestion.extensions?.join(",") || "";
|
|
603
733
|
const hidden = suggestion.includeHidden ? "1" : "0";
|
|
604
734
|
const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
|
|
605
|
-
|
|
735
|
+
const pattern = encodePattern(suggestion.pattern ?? "");
|
|
736
|
+
yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t${description}`;
|
|
606
737
|
}
|
|
607
738
|
i++;
|
|
608
739
|
}
|
|
@@ -668,15 +799,19 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
668
799
|
|
|
669
800
|
if (\$line -match '^__FILE__:') {
|
|
670
801
|
# Parse file completion directive: __FILE__:type:extensions:pattern:hidden
|
|
671
|
-
\$
|
|
802
|
+
\$directive = (\$line -split "\`t")[0]
|
|
803
|
+
\$parts = \$directive -split ':', 5
|
|
672
804
|
\$type = \$parts[1]
|
|
673
805
|
\$extensions = \$parts[2]
|
|
674
|
-
\$pattern = \$parts[3]
|
|
806
|
+
\$pattern = \$parts[3] -replace '%3A', ':' -replace '%25', '%'
|
|
675
807
|
\$hidden = \$parts[4] -eq '1'
|
|
676
808
|
|
|
677
809
|
# Determine current prefix for file matching
|
|
678
810
|
\$prefix = if (\$wordToComplete) { \$wordToComplete } else { '' }
|
|
679
811
|
|
|
812
|
+
# Use -Force to include hidden files when requested
|
|
813
|
+
\$forceParam = if (\$hidden) { @{Force = \$true} } else { @{} }
|
|
814
|
+
|
|
680
815
|
# Get file system items based on type
|
|
681
816
|
\$items = @()
|
|
682
817
|
switch (\$type) {
|
|
@@ -684,31 +819,31 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
684
819
|
if (\$extensions) {
|
|
685
820
|
# Filter by extensions
|
|
686
821
|
\$extList = \$extensions -split ','
|
|
687
|
-
\$items = Get-ChildItem -File -Path "\${prefix}*" -ErrorAction SilentlyContinue |
|
|
822
|
+
\$items = Get-ChildItem @forceParam -File -Path "\${prefix}*" -ErrorAction SilentlyContinue |
|
|
688
823
|
Where-Object {
|
|
689
824
|
\$ext = \$_.Extension
|
|
690
825
|
\$extList | ForEach-Object { if (\$ext -eq ".\$_") { return \$true } }
|
|
691
826
|
}
|
|
692
827
|
} else {
|
|
693
|
-
\$items = Get-ChildItem -File -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
828
|
+
\$items = Get-ChildItem @forceParam -File -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
694
829
|
}
|
|
695
830
|
}
|
|
696
831
|
'directory' {
|
|
697
|
-
\$items = Get-ChildItem -Directory -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
832
|
+
\$items = Get-ChildItem @forceParam -Directory -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
698
833
|
}
|
|
699
834
|
'any' {
|
|
700
835
|
if (\$extensions) {
|
|
701
836
|
# Get directories and filtered files
|
|
702
|
-
\$dirs = Get-ChildItem -Directory -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
837
|
+
\$dirs = Get-ChildItem @forceParam -Directory -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
703
838
|
\$extList = \$extensions -split ','
|
|
704
|
-
\$files = Get-ChildItem -File -Path "\${prefix}*" -ErrorAction SilentlyContinue |
|
|
839
|
+
\$files = Get-ChildItem @forceParam -File -Path "\${prefix}*" -ErrorAction SilentlyContinue |
|
|
705
840
|
Where-Object {
|
|
706
841
|
\$ext = \$_.Extension
|
|
707
842
|
\$extList | ForEach-Object { if (\$ext -eq ".\$_") { return \$true } }
|
|
708
843
|
}
|
|
709
844
|
\$items = \$dirs + \$files
|
|
710
845
|
} else {
|
|
711
|
-
\$items = Get-ChildItem -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
846
|
+
\$items = Get-ChildItem @forceParam -Path "\${prefix}*" -ErrorAction SilentlyContinue
|
|
712
847
|
}
|
|
713
848
|
}
|
|
714
849
|
}
|
|
@@ -718,9 +853,15 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
718
853
|
\$items = \$items | Where-Object { -not \$_.Attributes.HasFlag([System.IO.FileAttributes]::Hidden) }
|
|
719
854
|
}
|
|
720
855
|
|
|
856
|
+
# Extract directory prefix to preserve in completion text
|
|
857
|
+
\$dirPrefix = if (\$prefix -and (\$prefix.Contains('/') -or \$prefix.Contains('\\'))) {
|
|
858
|
+
\$slashIdx = [Math]::Max(\$prefix.LastIndexOf('/'), \$prefix.LastIndexOf('\\'))
|
|
859
|
+
\$prefix.Substring(0, \$slashIdx + 1)
|
|
860
|
+
} else { '' }
|
|
861
|
+
|
|
721
862
|
# Create completion results for files
|
|
722
863
|
\$items | ForEach-Object {
|
|
723
|
-
\$completionText = if (\$_.PSIsContainer) { "\$(\$_.Name)/" } else { \$_.Name }
|
|
864
|
+
\$completionText = if (\$_.PSIsContainer) { "\$dirPrefix\$(\$_.Name)/" } else { "\$dirPrefix\$(\$_.Name)" }
|
|
724
865
|
\$itemType = if (\$_.PSIsContainer) { 'Directory' } else { 'File' }
|
|
725
866
|
[System.Management.Automation.CompletionResult]::new(
|
|
726
867
|
\$completionText,
|
|
@@ -761,7 +902,8 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
|
|
|
761
902
|
const extensions = suggestion.extensions?.join(",") || "";
|
|
762
903
|
const hidden = suggestion.includeHidden ? "1" : "0";
|
|
763
904
|
const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
|
|
764
|
-
|
|
905
|
+
const pattern = encodePattern(suggestion.pattern ?? "");
|
|
906
|
+
yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t[file]\t${description}`;
|
|
765
907
|
}
|
|
766
908
|
i++;
|
|
767
909
|
}
|