@optique/core 1.0.15-dev.2816 → 1.0.15-dev.2824
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 +192 -82
- package/dist/completion.js +192 -82
- package/package.json +1 -1
package/dist/completion.cjs
CHANGED
|
@@ -902,15 +902,13 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
902
902
|
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
903
903
|
set -l __norm_current (string replace -r '^\\./' '' -- "$current")
|
|
904
904
|
set -l __norm_pattern (string replace -r '^\\./' '' -- "$pattern")
|
|
905
|
-
# For wildcard patterns, compare only the directory prefix
|
|
905
|
+
# For wildcard patterns, compare only the directory prefix.
|
|
906
|
+
# Use a regex: fish wildcards have no character classes.
|
|
907
|
+
# Only the basename can hold wildcards; a ? or * in a
|
|
908
|
+
# directory name such as What?/ is literal.
|
|
906
909
|
set -l __compare_pattern "$__norm_pattern"
|
|
907
|
-
if string match -
|
|
908
|
-
set __compare_pattern (string replace -r '[
|
|
909
|
-
if string match -q '*/*' -- "$__compare_pattern"
|
|
910
|
-
set __compare_pattern (string replace -r '/[^/]*$' '/' -- "$__compare_pattern")
|
|
911
|
-
else
|
|
912
|
-
set __compare_pattern ""
|
|
913
|
-
end
|
|
910
|
+
if string match -qr '[*?][^/]*$' -- "$__compare_pattern"
|
|
911
|
+
set __compare_pattern (string replace -r '[^/]*$' '' -- "$__compare_pattern")
|
|
914
912
|
end
|
|
915
913
|
set -l __cp_len (string length -- "$__compare_pattern")
|
|
916
914
|
set -l __nc_len (string length -- "$__norm_current")
|
|
@@ -945,54 +943,82 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
945
943
|
end
|
|
946
944
|
|
|
947
945
|
# Pre-expand file candidates. When the glob base came from
|
|
948
|
-
# the program's pattern and contains * or ?, use
|
|
949
|
-
# complete glob expression. Otherwise append * as a
|
|
946
|
+
# the program's pattern and its basename contains * or ?, use
|
|
947
|
+
# it as the complete glob expression. Otherwise append * as a
|
|
948
|
+
# prefix, so a literal ? in a directory name such as What?/
|
|
949
|
+
# still lists that directory. A pattern equal to the current
|
|
950
|
+
# word is the typed text echoed back (as path() does), so its
|
|
951
|
+
# ? and * are literal characters, not wildcards.
|
|
950
952
|
# Note: [ is NOT treated as a glob because it commonly
|
|
951
953
|
# appears in literal filenames like [draft] or foo[1].txt.
|
|
952
954
|
set -l __has_glob 0
|
|
953
955
|
if test $__from_pattern -eq 1
|
|
954
|
-
and string match -
|
|
956
|
+
and string match -qr '[*?][^/]*$' -- "$glob_base"
|
|
957
|
+
and test "$pattern" != "$current"
|
|
955
958
|
set __has_glob 1
|
|
956
959
|
end
|
|
957
960
|
set -l __candidates
|
|
958
961
|
if test $__has_glob -eq 1
|
|
959
962
|
# Safe glob expansion without eval: split the pattern into
|
|
960
|
-
# directory and filter
|
|
961
|
-
#
|
|
962
|
-
|
|
963
|
+
# a directory prefix and a basename filter, list the
|
|
964
|
+
# directory, then match basenames against a regex built
|
|
965
|
+
# from the filter. The prefix keeps one empty element for
|
|
966
|
+
# bare patterns so that $__glob_prefix* still expands.
|
|
967
|
+
set -l __glob_prefix ""
|
|
968
|
+
if string match -q '*/*' -- "$glob_base"
|
|
969
|
+
set __glob_prefix (string replace -r '[^/]*$' '' -- "$glob_base")
|
|
970
|
+
end
|
|
963
971
|
set -l __glob_filter (string replace -r '.*/' '' -- "$glob_base")
|
|
964
|
-
|
|
965
|
-
|
|
972
|
+
# Translate the filter into an anchored regex: * and ? are
|
|
973
|
+
# wildcards and every other character is literal. Fish's
|
|
974
|
+
# own wildcards cannot be used here because ? is literal
|
|
975
|
+
# when the qmark-noglob feature is enabled (fish 4 default).
|
|
976
|
+
set -l __glob_regex '^'
|
|
977
|
+
for __ch in (string split '' -- "$__glob_filter")
|
|
978
|
+
if test "$__ch" = '*'
|
|
979
|
+
set __glob_regex "$__glob_regex.*"
|
|
980
|
+
else if test "$__ch" = '?'
|
|
981
|
+
set __glob_regex "$__glob_regex."
|
|
982
|
+
else
|
|
983
|
+
set __glob_regex "$__glob_regex"(string escape --style=regex -- "$__ch")
|
|
984
|
+
end
|
|
966
985
|
end
|
|
986
|
+
set __glob_regex "$__glob_regex"'$'
|
|
967
987
|
# Match files by the glob filter. Fish's * does not match
|
|
968
|
-
# dotfiles, so also scan .* when the filter targets them
|
|
969
|
-
|
|
988
|
+
# dotfiles, so also scan .* when the filter targets them or
|
|
989
|
+
# hidden files are requested.
|
|
990
|
+
set -l __entries $__glob_prefix*
|
|
991
|
+
if test "$hidden" = "1"; or string match -q '.*' -- "$__glob_filter"
|
|
992
|
+
set -a __entries $__glob_prefix.*
|
|
993
|
+
end
|
|
994
|
+
for __item in $__entries
|
|
970
995
|
if test -e "$__item"
|
|
971
|
-
set -l __bn (
|
|
972
|
-
if
|
|
973
|
-
|
|
996
|
+
set -l __bn (string replace -r '.*/' '' -- "$__item")
|
|
997
|
+
if contains -- "$__bn" . ..
|
|
998
|
+
continue
|
|
974
999
|
end
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
if string match -q '.*' -- "$__glob_filter"
|
|
978
|
-
for __item in $__glob_dir/.*
|
|
979
|
-
if test -e "$__item"
|
|
980
|
-
set -l __bn (basename -- "$__item")
|
|
981
|
-
if test "$__bn" = "." -o "$__bn" = ".."
|
|
982
|
-
continue
|
|
983
|
-
end
|
|
984
|
-
if string match -q -- "$__glob_filter" "$__bn"
|
|
985
|
-
set -a __candidates "$__item"
|
|
986
|
-
end
|
|
1000
|
+
if string match -qr -- "$__glob_regex" "$__bn"
|
|
1001
|
+
set -a __candidates "$__item"
|
|
987
1002
|
end
|
|
988
1003
|
end
|
|
989
1004
|
end
|
|
990
1005
|
# For file/any modes, also include directories for navigation.
|
|
991
1006
|
# Skip for directory mode so the pattern filter is respected.
|
|
1007
|
+
# Directories the filter already matched are not repeated.
|
|
992
1008
|
if test "$type" != "directory"
|
|
993
|
-
|
|
1009
|
+
set -l __nav_dirs $__glob_prefix*/
|
|
1010
|
+
if test "$hidden" = "1"
|
|
1011
|
+
set -a __nav_dirs $__glob_prefix.*/
|
|
1012
|
+
end
|
|
1013
|
+
for __item in $__nav_dirs
|
|
1014
|
+
set __item (string replace -r '/$' '' -- "$__item")
|
|
1015
|
+
set -l __bn (string replace -r '.*/' '' -- "$__item")
|
|
1016
|
+
if contains -- "$__bn" . ..
|
|
1017
|
+
continue
|
|
1018
|
+
end
|
|
994
1019
|
if test -d "$__item"
|
|
995
|
-
|
|
1020
|
+
and not string match -qr -- "$__glob_regex" "$__bn"
|
|
1021
|
+
set -a __candidates "$__item"
|
|
996
1022
|
end
|
|
997
1023
|
end
|
|
998
1024
|
end
|
|
@@ -1174,66 +1200,149 @@ const nu = {
|
|
|
1174
1200
|
const safeName = programName.replace(/[^a-zA-Z0-9]+/g, "-");
|
|
1175
1201
|
const functionName = `nu-complete-${safeName}`;
|
|
1176
1202
|
return `
|
|
1177
|
-
# Helper to split
|
|
1203
|
+
# Helper to split a command line into the arguments Nushell would pass to
|
|
1204
|
+
# an external command. Words are found the way Nushell's lexer finds them
|
|
1205
|
+
# (quotes and raw strings may contain whitespace), then each word is decoded
|
|
1206
|
+
# like an external argument. A final empty argument is added when the line
|
|
1207
|
+
# ends in unquoted whitespace, for the word about to be completed.
|
|
1178
1208
|
def args-split []: string -> list<string> {
|
|
1179
|
-
let STATE_NORMAL = 0
|
|
1180
|
-
let STATE_IN_SINGLE_QUOTE = 1
|
|
1181
|
-
let STATE_IN_DOUBLE_QUOTE = 2
|
|
1182
|
-
let STATE_ESCAPE = 3
|
|
1183
1209
|
let WHITESPACES = [" " "\\t" "\\n" "\\r"]
|
|
1210
|
+
let QUOTES = ["'" '"' "\`"]
|
|
1184
1211
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1212
|
+
# Decodes one word. An unterminated word is the one still being typed,
|
|
1213
|
+
# so its open quote is taken as if it were closed.
|
|
1214
|
+
let decode = {|word: string, unterminated: bool|
|
|
1215
|
+
let hashes = if ($word | str starts-with "r#") {
|
|
1216
|
+
$word | str replace -r '(?s)^r(#+).*$' '$1'
|
|
1217
|
+
} else {
|
|
1218
|
+
""
|
|
1219
|
+
}
|
|
1220
|
+
if ($hashes | is-not-empty) {
|
|
1221
|
+
let prefix = "r" + $hashes + "'"
|
|
1222
|
+
let suffix = "'" + $hashes
|
|
1223
|
+
let size = ($hashes | str length)
|
|
1224
|
+
if ($word | str starts-with $prefix) and ($word | str ends-with $suffix) and (($word | str length) >= ($size * 2 + 3)) {
|
|
1225
|
+
$word | str replace $prefix "" | str replace -r ($suffix + '$') ""
|
|
1226
|
+
} else if $unterminated and ($word | str starts-with $prefix) {
|
|
1227
|
+
$word | str replace $prefix ""
|
|
1228
|
+
} else {
|
|
1229
|
+
$word
|
|
1230
|
+
}
|
|
1231
|
+
} else {
|
|
1232
|
+
mut result = ""
|
|
1233
|
+
mut quote = ""
|
|
1234
|
+
mut quoted = ""
|
|
1235
|
+
mut escaped = false
|
|
1236
|
+
for char in ($word | split chars) {
|
|
1237
|
+
if $quote == "" {
|
|
1238
|
+
if ($char in $QUOTES) {
|
|
1239
|
+
$quote = $char
|
|
1240
|
+
$quoted = ""
|
|
1241
|
+
} else {
|
|
1242
|
+
$result = $result + $char
|
|
1243
|
+
}
|
|
1244
|
+
} else if $escaped {
|
|
1245
|
+
$quoted = $quoted + $char
|
|
1246
|
+
$escaped = false
|
|
1247
|
+
} else if $char == $quote {
|
|
1248
|
+
let body = $quoted
|
|
1249
|
+
$result = $result + (if $quote == '"' {
|
|
1250
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1251
|
+
} else {
|
|
1252
|
+
$body
|
|
1253
|
+
})
|
|
1254
|
+
$quote = ""
|
|
1255
|
+
} else {
|
|
1256
|
+
$quoted = $quoted + $char
|
|
1257
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if $quote != "" {
|
|
1261
|
+
let body = $quoted
|
|
1262
|
+
$result = $result + (if not $unterminated {
|
|
1263
|
+
$quote + $body
|
|
1264
|
+
} else if $quote == '"' {
|
|
1265
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1266
|
+
} else {
|
|
1267
|
+
$body
|
|
1268
|
+
})
|
|
1269
|
+
}
|
|
1270
|
+
$result
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
mut words: list<string> = []
|
|
1275
|
+
mut word = ""
|
|
1276
|
+
mut in_word = false
|
|
1277
|
+
mut quote = ""
|
|
1278
|
+
mut escaped = false
|
|
1279
|
+
mut raw_hashes = ""
|
|
1280
|
+
mut raw_pending = ""
|
|
1281
|
+
mut prev = ""
|
|
1282
|
+
mut trailing_space = false
|
|
1190
1283
|
|
|
1191
1284
|
for char in ($in | split chars) {
|
|
1192
|
-
$
|
|
1193
|
-
if $
|
|
1194
|
-
|
|
1195
|
-
$
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
$state = $STATE_IN_DOUBLE_QUOTE
|
|
1204
|
-
} else if ($char in $WHITESPACES) {
|
|
1205
|
-
$ends_with_delimiter = true
|
|
1206
|
-
if ($current_token | is-not-empty) {
|
|
1207
|
-
$result = $result | append $current_token
|
|
1208
|
-
$current_token = ""
|
|
1209
|
-
}
|
|
1285
|
+
$trailing_space = false
|
|
1286
|
+
if $quote == "#" and $char != "#" and $char != "'" {
|
|
1287
|
+
# Not a raw string after all; lex this character normally
|
|
1288
|
+
$quote = ""
|
|
1289
|
+
}
|
|
1290
|
+
if $quote == "r" {
|
|
1291
|
+
# Inside a raw string, which ends at ' followed by the same number of #
|
|
1292
|
+
if ($raw_pending | is-not-empty) and $char == "#" {
|
|
1293
|
+
$raw_pending = $raw_pending + $char
|
|
1294
|
+
} else if $char == "'" {
|
|
1295
|
+
$raw_pending = $char
|
|
1210
1296
|
} else {
|
|
1211
|
-
$
|
|
1297
|
+
$raw_pending = ""
|
|
1298
|
+
}
|
|
1299
|
+
if $raw_pending == ("'" + $raw_hashes) {
|
|
1300
|
+
$quote = ""
|
|
1301
|
+
$raw_pending = ""
|
|
1212
1302
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1303
|
+
$word = $word + $char
|
|
1304
|
+
} else if $quote == "#" {
|
|
1305
|
+
# Between r# and the opening ' of a raw string
|
|
1306
|
+
$word = $word + $char
|
|
1307
|
+
if $char == "#" {
|
|
1308
|
+
$raw_hashes = $raw_hashes + $char
|
|
1216
1309
|
} else {
|
|
1217
|
-
$
|
|
1310
|
+
$quote = "r"
|
|
1218
1311
|
}
|
|
1219
|
-
} else if $
|
|
1220
|
-
|
|
1221
|
-
|
|
1312
|
+
} else if $quote != "" {
|
|
1313
|
+
$word = $word + $char
|
|
1314
|
+
if $escaped {
|
|
1315
|
+
$escaped = false
|
|
1316
|
+
} else if $char == $quote {
|
|
1317
|
+
$quote = ""
|
|
1222
1318
|
} else {
|
|
1223
|
-
$
|
|
1319
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1224
1320
|
}
|
|
1321
|
+
} else if ($char in $WHITESPACES) {
|
|
1322
|
+
if $in_word {
|
|
1323
|
+
$words = $words | append (do $decode $word false)
|
|
1324
|
+
$word = ""
|
|
1325
|
+
$in_word = false
|
|
1326
|
+
}
|
|
1327
|
+
$trailing_space = true
|
|
1328
|
+
} else {
|
|
1329
|
+
if ($char in $QUOTES) {
|
|
1330
|
+
$quote = $char
|
|
1331
|
+
} else if $char == "#" and $prev == "r" {
|
|
1332
|
+
$quote = "#"
|
|
1333
|
+
$raw_hashes = "#"
|
|
1334
|
+
}
|
|
1335
|
+
$word = $word + $char
|
|
1336
|
+
$in_word = true
|
|
1225
1337
|
}
|
|
1338
|
+
$prev = $char
|
|
1226
1339
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
$
|
|
1231
|
-
}
|
|
1232
|
-
# Whitespace outside quotes at the end starts a new, still empty argument
|
|
1233
|
-
if $ends_with_delimiter {
|
|
1234
|
-
$result = $result | append ''
|
|
1340
|
+
if $in_word {
|
|
1341
|
+
$words = $words | append (do $decode $word ($quote != ""))
|
|
1342
|
+
} else if $trailing_space {
|
|
1343
|
+
$words = $words | append ""
|
|
1235
1344
|
}
|
|
1236
|
-
$
|
|
1345
|
+
$words
|
|
1237
1346
|
}
|
|
1238
1347
|
|
|
1239
1348
|
# Helper to tell whether Nushell would not read a value back verbatim as a
|
|
@@ -1272,7 +1381,8 @@ def quote-arg []: string -> string {
|
|
|
1272
1381
|
|
|
1273
1382
|
# Completion command that calls back to the CLI
|
|
1274
1383
|
export def "${functionName}" [context: string] {
|
|
1275
|
-
# Split context into
|
|
1384
|
+
# Split context into the arguments Nushell would pass, including an empty
|
|
1385
|
+
# one for a new word when the context ends in unquoted whitespace
|
|
1276
1386
|
let tokens = $context | args-split
|
|
1277
1387
|
|
|
1278
1388
|
# Remove the command name (first token) to get arguments
|
package/dist/completion.js
CHANGED
|
@@ -902,15 +902,13 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
902
902
|
# Normalize leading ./ so that ./src/ma matches pattern src/
|
|
903
903
|
set -l __norm_current (string replace -r '^\\./' '' -- "$current")
|
|
904
904
|
set -l __norm_pattern (string replace -r '^\\./' '' -- "$pattern")
|
|
905
|
-
# For wildcard patterns, compare only the directory prefix
|
|
905
|
+
# For wildcard patterns, compare only the directory prefix.
|
|
906
|
+
# Use a regex: fish wildcards have no character classes.
|
|
907
|
+
# Only the basename can hold wildcards; a ? or * in a
|
|
908
|
+
# directory name such as What?/ is literal.
|
|
906
909
|
set -l __compare_pattern "$__norm_pattern"
|
|
907
|
-
if string match -
|
|
908
|
-
set __compare_pattern (string replace -r '[
|
|
909
|
-
if string match -q '*/*' -- "$__compare_pattern"
|
|
910
|
-
set __compare_pattern (string replace -r '/[^/]*$' '/' -- "$__compare_pattern")
|
|
911
|
-
else
|
|
912
|
-
set __compare_pattern ""
|
|
913
|
-
end
|
|
910
|
+
if string match -qr '[*?][^/]*$' -- "$__compare_pattern"
|
|
911
|
+
set __compare_pattern (string replace -r '[^/]*$' '' -- "$__compare_pattern")
|
|
914
912
|
end
|
|
915
913
|
set -l __cp_len (string length -- "$__compare_pattern")
|
|
916
914
|
set -l __nc_len (string length -- "$__norm_current")
|
|
@@ -945,54 +943,82 @@ ${escapedArgs ? ` set -l output (${programName} ${escapedArgs} $prev $current
|
|
|
945
943
|
end
|
|
946
944
|
|
|
947
945
|
# Pre-expand file candidates. When the glob base came from
|
|
948
|
-
# the program's pattern and contains * or ?, use
|
|
949
|
-
# complete glob expression. Otherwise append * as a
|
|
946
|
+
# the program's pattern and its basename contains * or ?, use
|
|
947
|
+
# it as the complete glob expression. Otherwise append * as a
|
|
948
|
+
# prefix, so a literal ? in a directory name such as What?/
|
|
949
|
+
# still lists that directory. A pattern equal to the current
|
|
950
|
+
# word is the typed text echoed back (as path() does), so its
|
|
951
|
+
# ? and * are literal characters, not wildcards.
|
|
950
952
|
# Note: [ is NOT treated as a glob because it commonly
|
|
951
953
|
# appears in literal filenames like [draft] or foo[1].txt.
|
|
952
954
|
set -l __has_glob 0
|
|
953
955
|
if test $__from_pattern -eq 1
|
|
954
|
-
and string match -
|
|
956
|
+
and string match -qr '[*?][^/]*$' -- "$glob_base"
|
|
957
|
+
and test "$pattern" != "$current"
|
|
955
958
|
set __has_glob 1
|
|
956
959
|
end
|
|
957
960
|
set -l __candidates
|
|
958
961
|
if test $__has_glob -eq 1
|
|
959
962
|
# Safe glob expansion without eval: split the pattern into
|
|
960
|
-
# directory and filter
|
|
961
|
-
#
|
|
962
|
-
|
|
963
|
+
# a directory prefix and a basename filter, list the
|
|
964
|
+
# directory, then match basenames against a regex built
|
|
965
|
+
# from the filter. The prefix keeps one empty element for
|
|
966
|
+
# bare patterns so that $__glob_prefix* still expands.
|
|
967
|
+
set -l __glob_prefix ""
|
|
968
|
+
if string match -q '*/*' -- "$glob_base"
|
|
969
|
+
set __glob_prefix (string replace -r '[^/]*$' '' -- "$glob_base")
|
|
970
|
+
end
|
|
963
971
|
set -l __glob_filter (string replace -r '.*/' '' -- "$glob_base")
|
|
964
|
-
|
|
965
|
-
|
|
972
|
+
# Translate the filter into an anchored regex: * and ? are
|
|
973
|
+
# wildcards and every other character is literal. Fish's
|
|
974
|
+
# own wildcards cannot be used here because ? is literal
|
|
975
|
+
# when the qmark-noglob feature is enabled (fish 4 default).
|
|
976
|
+
set -l __glob_regex '^'
|
|
977
|
+
for __ch in (string split '' -- "$__glob_filter")
|
|
978
|
+
if test "$__ch" = '*'
|
|
979
|
+
set __glob_regex "$__glob_regex.*"
|
|
980
|
+
else if test "$__ch" = '?'
|
|
981
|
+
set __glob_regex "$__glob_regex."
|
|
982
|
+
else
|
|
983
|
+
set __glob_regex "$__glob_regex"(string escape --style=regex -- "$__ch")
|
|
984
|
+
end
|
|
966
985
|
end
|
|
986
|
+
set __glob_regex "$__glob_regex"'$'
|
|
967
987
|
# Match files by the glob filter. Fish's * does not match
|
|
968
|
-
# dotfiles, so also scan .* when the filter targets them
|
|
969
|
-
|
|
988
|
+
# dotfiles, so also scan .* when the filter targets them or
|
|
989
|
+
# hidden files are requested.
|
|
990
|
+
set -l __entries $__glob_prefix*
|
|
991
|
+
if test "$hidden" = "1"; or string match -q '.*' -- "$__glob_filter"
|
|
992
|
+
set -a __entries $__glob_prefix.*
|
|
993
|
+
end
|
|
994
|
+
for __item in $__entries
|
|
970
995
|
if test -e "$__item"
|
|
971
|
-
set -l __bn (
|
|
972
|
-
if
|
|
973
|
-
|
|
996
|
+
set -l __bn (string replace -r '.*/' '' -- "$__item")
|
|
997
|
+
if contains -- "$__bn" . ..
|
|
998
|
+
continue
|
|
974
999
|
end
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
if string match -q '.*' -- "$__glob_filter"
|
|
978
|
-
for __item in $__glob_dir/.*
|
|
979
|
-
if test -e "$__item"
|
|
980
|
-
set -l __bn (basename -- "$__item")
|
|
981
|
-
if test "$__bn" = "." -o "$__bn" = ".."
|
|
982
|
-
continue
|
|
983
|
-
end
|
|
984
|
-
if string match -q -- "$__glob_filter" "$__bn"
|
|
985
|
-
set -a __candidates "$__item"
|
|
986
|
-
end
|
|
1000
|
+
if string match -qr -- "$__glob_regex" "$__bn"
|
|
1001
|
+
set -a __candidates "$__item"
|
|
987
1002
|
end
|
|
988
1003
|
end
|
|
989
1004
|
end
|
|
990
1005
|
# For file/any modes, also include directories for navigation.
|
|
991
1006
|
# Skip for directory mode so the pattern filter is respected.
|
|
1007
|
+
# Directories the filter already matched are not repeated.
|
|
992
1008
|
if test "$type" != "directory"
|
|
993
|
-
|
|
1009
|
+
set -l __nav_dirs $__glob_prefix*/
|
|
1010
|
+
if test "$hidden" = "1"
|
|
1011
|
+
set -a __nav_dirs $__glob_prefix.*/
|
|
1012
|
+
end
|
|
1013
|
+
for __item in $__nav_dirs
|
|
1014
|
+
set __item (string replace -r '/$' '' -- "$__item")
|
|
1015
|
+
set -l __bn (string replace -r '.*/' '' -- "$__item")
|
|
1016
|
+
if contains -- "$__bn" . ..
|
|
1017
|
+
continue
|
|
1018
|
+
end
|
|
994
1019
|
if test -d "$__item"
|
|
995
|
-
|
|
1020
|
+
and not string match -qr -- "$__glob_regex" "$__bn"
|
|
1021
|
+
set -a __candidates "$__item"
|
|
996
1022
|
end
|
|
997
1023
|
end
|
|
998
1024
|
end
|
|
@@ -1174,66 +1200,149 @@ const nu = {
|
|
|
1174
1200
|
const safeName = programName.replace(/[^a-zA-Z0-9]+/g, "-");
|
|
1175
1201
|
const functionName = `nu-complete-${safeName}`;
|
|
1176
1202
|
return `
|
|
1177
|
-
# Helper to split
|
|
1203
|
+
# Helper to split a command line into the arguments Nushell would pass to
|
|
1204
|
+
# an external command. Words are found the way Nushell's lexer finds them
|
|
1205
|
+
# (quotes and raw strings may contain whitespace), then each word is decoded
|
|
1206
|
+
# like an external argument. A final empty argument is added when the line
|
|
1207
|
+
# ends in unquoted whitespace, for the word about to be completed.
|
|
1178
1208
|
def args-split []: string -> list<string> {
|
|
1179
|
-
let STATE_NORMAL = 0
|
|
1180
|
-
let STATE_IN_SINGLE_QUOTE = 1
|
|
1181
|
-
let STATE_IN_DOUBLE_QUOTE = 2
|
|
1182
|
-
let STATE_ESCAPE = 3
|
|
1183
1209
|
let WHITESPACES = [" " "\\t" "\\n" "\\r"]
|
|
1210
|
+
let QUOTES = ["'" '"' "\`"]
|
|
1184
1211
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1212
|
+
# Decodes one word. An unterminated word is the one still being typed,
|
|
1213
|
+
# so its open quote is taken as if it were closed.
|
|
1214
|
+
let decode = {|word: string, unterminated: bool|
|
|
1215
|
+
let hashes = if ($word | str starts-with "r#") {
|
|
1216
|
+
$word | str replace -r '(?s)^r(#+).*$' '$1'
|
|
1217
|
+
} else {
|
|
1218
|
+
""
|
|
1219
|
+
}
|
|
1220
|
+
if ($hashes | is-not-empty) {
|
|
1221
|
+
let prefix = "r" + $hashes + "'"
|
|
1222
|
+
let suffix = "'" + $hashes
|
|
1223
|
+
let size = ($hashes | str length)
|
|
1224
|
+
if ($word | str starts-with $prefix) and ($word | str ends-with $suffix) and (($word | str length) >= ($size * 2 + 3)) {
|
|
1225
|
+
$word | str replace $prefix "" | str replace -r ($suffix + '$') ""
|
|
1226
|
+
} else if $unterminated and ($word | str starts-with $prefix) {
|
|
1227
|
+
$word | str replace $prefix ""
|
|
1228
|
+
} else {
|
|
1229
|
+
$word
|
|
1230
|
+
}
|
|
1231
|
+
} else {
|
|
1232
|
+
mut result = ""
|
|
1233
|
+
mut quote = ""
|
|
1234
|
+
mut quoted = ""
|
|
1235
|
+
mut escaped = false
|
|
1236
|
+
for char in ($word | split chars) {
|
|
1237
|
+
if $quote == "" {
|
|
1238
|
+
if ($char in $QUOTES) {
|
|
1239
|
+
$quote = $char
|
|
1240
|
+
$quoted = ""
|
|
1241
|
+
} else {
|
|
1242
|
+
$result = $result + $char
|
|
1243
|
+
}
|
|
1244
|
+
} else if $escaped {
|
|
1245
|
+
$quoted = $quoted + $char
|
|
1246
|
+
$escaped = false
|
|
1247
|
+
} else if $char == $quote {
|
|
1248
|
+
let body = $quoted
|
|
1249
|
+
$result = $result + (if $quote == '"' {
|
|
1250
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1251
|
+
} else {
|
|
1252
|
+
$body
|
|
1253
|
+
})
|
|
1254
|
+
$quote = ""
|
|
1255
|
+
} else {
|
|
1256
|
+
$quoted = $quoted + $char
|
|
1257
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if $quote != "" {
|
|
1261
|
+
let body = $quoted
|
|
1262
|
+
$result = $result + (if not $unterminated {
|
|
1263
|
+
$quote + $body
|
|
1264
|
+
} else if $quote == '"' {
|
|
1265
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1266
|
+
} else {
|
|
1267
|
+
$body
|
|
1268
|
+
})
|
|
1269
|
+
}
|
|
1270
|
+
$result
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
mut words: list<string> = []
|
|
1275
|
+
mut word = ""
|
|
1276
|
+
mut in_word = false
|
|
1277
|
+
mut quote = ""
|
|
1278
|
+
mut escaped = false
|
|
1279
|
+
mut raw_hashes = ""
|
|
1280
|
+
mut raw_pending = ""
|
|
1281
|
+
mut prev = ""
|
|
1282
|
+
mut trailing_space = false
|
|
1190
1283
|
|
|
1191
1284
|
for char in ($in | split chars) {
|
|
1192
|
-
$
|
|
1193
|
-
if $
|
|
1194
|
-
|
|
1195
|
-
$
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
$state = $STATE_IN_DOUBLE_QUOTE
|
|
1204
|
-
} else if ($char in $WHITESPACES) {
|
|
1205
|
-
$ends_with_delimiter = true
|
|
1206
|
-
if ($current_token | is-not-empty) {
|
|
1207
|
-
$result = $result | append $current_token
|
|
1208
|
-
$current_token = ""
|
|
1209
|
-
}
|
|
1285
|
+
$trailing_space = false
|
|
1286
|
+
if $quote == "#" and $char != "#" and $char != "'" {
|
|
1287
|
+
# Not a raw string after all; lex this character normally
|
|
1288
|
+
$quote = ""
|
|
1289
|
+
}
|
|
1290
|
+
if $quote == "r" {
|
|
1291
|
+
# Inside a raw string, which ends at ' followed by the same number of #
|
|
1292
|
+
if ($raw_pending | is-not-empty) and $char == "#" {
|
|
1293
|
+
$raw_pending = $raw_pending + $char
|
|
1294
|
+
} else if $char == "'" {
|
|
1295
|
+
$raw_pending = $char
|
|
1210
1296
|
} else {
|
|
1211
|
-
$
|
|
1297
|
+
$raw_pending = ""
|
|
1298
|
+
}
|
|
1299
|
+
if $raw_pending == ("'" + $raw_hashes) {
|
|
1300
|
+
$quote = ""
|
|
1301
|
+
$raw_pending = ""
|
|
1212
1302
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1303
|
+
$word = $word + $char
|
|
1304
|
+
} else if $quote == "#" {
|
|
1305
|
+
# Between r# and the opening ' of a raw string
|
|
1306
|
+
$word = $word + $char
|
|
1307
|
+
if $char == "#" {
|
|
1308
|
+
$raw_hashes = $raw_hashes + $char
|
|
1216
1309
|
} else {
|
|
1217
|
-
$
|
|
1310
|
+
$quote = "r"
|
|
1218
1311
|
}
|
|
1219
|
-
} else if $
|
|
1220
|
-
|
|
1221
|
-
|
|
1312
|
+
} else if $quote != "" {
|
|
1313
|
+
$word = $word + $char
|
|
1314
|
+
if $escaped {
|
|
1315
|
+
$escaped = false
|
|
1316
|
+
} else if $char == $quote {
|
|
1317
|
+
$quote = ""
|
|
1222
1318
|
} else {
|
|
1223
|
-
$
|
|
1319
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1224
1320
|
}
|
|
1321
|
+
} else if ($char in $WHITESPACES) {
|
|
1322
|
+
if $in_word {
|
|
1323
|
+
$words = $words | append (do $decode $word false)
|
|
1324
|
+
$word = ""
|
|
1325
|
+
$in_word = false
|
|
1326
|
+
}
|
|
1327
|
+
$trailing_space = true
|
|
1328
|
+
} else {
|
|
1329
|
+
if ($char in $QUOTES) {
|
|
1330
|
+
$quote = $char
|
|
1331
|
+
} else if $char == "#" and $prev == "r" {
|
|
1332
|
+
$quote = "#"
|
|
1333
|
+
$raw_hashes = "#"
|
|
1334
|
+
}
|
|
1335
|
+
$word = $word + $char
|
|
1336
|
+
$in_word = true
|
|
1225
1337
|
}
|
|
1338
|
+
$prev = $char
|
|
1226
1339
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
$
|
|
1231
|
-
}
|
|
1232
|
-
# Whitespace outside quotes at the end starts a new, still empty argument
|
|
1233
|
-
if $ends_with_delimiter {
|
|
1234
|
-
$result = $result | append ''
|
|
1340
|
+
if $in_word {
|
|
1341
|
+
$words = $words | append (do $decode $word ($quote != ""))
|
|
1342
|
+
} else if $trailing_space {
|
|
1343
|
+
$words = $words | append ""
|
|
1235
1344
|
}
|
|
1236
|
-
$
|
|
1345
|
+
$words
|
|
1237
1346
|
}
|
|
1238
1347
|
|
|
1239
1348
|
# Helper to tell whether Nushell would not read a value back verbatim as a
|
|
@@ -1272,7 +1381,8 @@ def quote-arg []: string -> string {
|
|
|
1272
1381
|
|
|
1273
1382
|
# Completion command that calls back to the CLI
|
|
1274
1383
|
export def "${functionName}" [context: string] {
|
|
1275
|
-
# Split context into
|
|
1384
|
+
# Split context into the arguments Nushell would pass, including an empty
|
|
1385
|
+
# one for a new word when the context ends in unquoted whitespace
|
|
1276
1386
|
let tokens = $context | args-split
|
|
1277
1387
|
|
|
1278
1388
|
# Remove the command name (first token) to get arguments
|