@optique/core 1.0.15-dev.2813 → 1.0.15-dev.2822
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 +211 -53
- package/dist/completion.js +211 -53
- package/package.json +1 -1
package/dist/completion.cjs
CHANGED
|
@@ -1174,62 +1174,189 @@ const nu = {
|
|
|
1174
1174
|
const safeName = programName.replace(/[^a-zA-Z0-9]+/g, "-");
|
|
1175
1175
|
const functionName = `nu-complete-${safeName}`;
|
|
1176
1176
|
return `
|
|
1177
|
-
# Helper to split
|
|
1177
|
+
# Helper to split a command line into the arguments Nushell would pass to
|
|
1178
|
+
# an external command. Words are found the way Nushell's lexer finds them
|
|
1179
|
+
# (quotes and raw strings may contain whitespace), then each word is decoded
|
|
1180
|
+
# like an external argument. A final empty argument is added when the line
|
|
1181
|
+
# ends in unquoted whitespace, for the word about to be completed.
|
|
1178
1182
|
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
1183
|
let WHITESPACES = [" " "\\t" "\\n" "\\r"]
|
|
1184
|
+
let QUOTES = ["'" '"' "\`"]
|
|
1184
1185
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1186
|
+
# Decodes one word. An unterminated word is the one still being typed,
|
|
1187
|
+
# so its open quote is taken as if it were closed.
|
|
1188
|
+
let decode = {|word: string, unterminated: bool|
|
|
1189
|
+
let hashes = if ($word | str starts-with "r#") {
|
|
1190
|
+
$word | str replace -r '(?s)^r(#+).*$' '$1'
|
|
1191
|
+
} else {
|
|
1192
|
+
""
|
|
1193
|
+
}
|
|
1194
|
+
if ($hashes | is-not-empty) {
|
|
1195
|
+
let prefix = "r" + $hashes + "'"
|
|
1196
|
+
let suffix = "'" + $hashes
|
|
1197
|
+
let size = ($hashes | str length)
|
|
1198
|
+
if ($word | str starts-with $prefix) and ($word | str ends-with $suffix) and (($word | str length) >= ($size * 2 + 3)) {
|
|
1199
|
+
$word | str replace $prefix "" | str replace -r ($suffix + '$') ""
|
|
1200
|
+
} else if $unterminated and ($word | str starts-with $prefix) {
|
|
1201
|
+
$word | str replace $prefix ""
|
|
1202
|
+
} else {
|
|
1203
|
+
$word
|
|
1204
|
+
}
|
|
1205
|
+
} else {
|
|
1206
|
+
mut result = ""
|
|
1207
|
+
mut quote = ""
|
|
1208
|
+
mut quoted = ""
|
|
1209
|
+
mut escaped = false
|
|
1210
|
+
for char in ($word | split chars) {
|
|
1211
|
+
if $quote == "" {
|
|
1212
|
+
if ($char in $QUOTES) {
|
|
1213
|
+
$quote = $char
|
|
1214
|
+
$quoted = ""
|
|
1215
|
+
} else {
|
|
1216
|
+
$result = $result + $char
|
|
1217
|
+
}
|
|
1218
|
+
} else if $escaped {
|
|
1219
|
+
$quoted = $quoted + $char
|
|
1220
|
+
$escaped = false
|
|
1221
|
+
} else if $char == $quote {
|
|
1222
|
+
let body = $quoted
|
|
1223
|
+
$result = $result + (if $quote == '"' {
|
|
1224
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1225
|
+
} else {
|
|
1226
|
+
$body
|
|
1227
|
+
})
|
|
1228
|
+
$quote = ""
|
|
1229
|
+
} else {
|
|
1230
|
+
$quoted = $quoted + $char
|
|
1231
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if $quote != "" {
|
|
1235
|
+
let body = $quoted
|
|
1236
|
+
$result = $result + (if not $unterminated {
|
|
1237
|
+
$quote + $body
|
|
1238
|
+
} else if $quote == '"' {
|
|
1239
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1240
|
+
} else {
|
|
1241
|
+
$body
|
|
1242
|
+
})
|
|
1243
|
+
}
|
|
1244
|
+
$result
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
mut words: list<string> = []
|
|
1249
|
+
mut word = ""
|
|
1250
|
+
mut in_word = false
|
|
1251
|
+
mut quote = ""
|
|
1252
|
+
mut escaped = false
|
|
1253
|
+
mut raw_hashes = ""
|
|
1254
|
+
mut raw_pending = ""
|
|
1255
|
+
mut prev = ""
|
|
1256
|
+
mut trailing_space = false
|
|
1189
1257
|
|
|
1190
1258
|
for char in ($in | split chars) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if $char == "
|
|
1199
|
-
$
|
|
1200
|
-
} else if $char == '"
|
|
1201
|
-
$
|
|
1202
|
-
} else if ($char in $WHITESPACES) {
|
|
1203
|
-
if ($current_token | is-not-empty) {
|
|
1204
|
-
$result = $result | append $current_token
|
|
1205
|
-
$current_token = ""
|
|
1206
|
-
}
|
|
1259
|
+
$trailing_space = false
|
|
1260
|
+
if $quote == "#" and $char != "#" and $char != "'" {
|
|
1261
|
+
# Not a raw string after all; lex this character normally
|
|
1262
|
+
$quote = ""
|
|
1263
|
+
}
|
|
1264
|
+
if $quote == "r" {
|
|
1265
|
+
# Inside a raw string, which ends at ' followed by the same number of #
|
|
1266
|
+
if ($raw_pending | is-not-empty) and $char == "#" {
|
|
1267
|
+
$raw_pending = $raw_pending + $char
|
|
1268
|
+
} else if $char == "'" {
|
|
1269
|
+
$raw_pending = $char
|
|
1207
1270
|
} else {
|
|
1208
|
-
$
|
|
1271
|
+
$raw_pending = ""
|
|
1272
|
+
}
|
|
1273
|
+
if $raw_pending == ("'" + $raw_hashes) {
|
|
1274
|
+
$quote = ""
|
|
1275
|
+
$raw_pending = ""
|
|
1209
1276
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1277
|
+
$word = $word + $char
|
|
1278
|
+
} else if $quote == "#" {
|
|
1279
|
+
# Between r# and the opening ' of a raw string
|
|
1280
|
+
$word = $word + $char
|
|
1281
|
+
if $char == "#" {
|
|
1282
|
+
$raw_hashes = $raw_hashes + $char
|
|
1213
1283
|
} else {
|
|
1214
|
-
$
|
|
1284
|
+
$quote = "r"
|
|
1215
1285
|
}
|
|
1216
|
-
} else if $
|
|
1217
|
-
|
|
1218
|
-
|
|
1286
|
+
} else if $quote != "" {
|
|
1287
|
+
$word = $word + $char
|
|
1288
|
+
if $escaped {
|
|
1289
|
+
$escaped = false
|
|
1290
|
+
} else if $char == $quote {
|
|
1291
|
+
$quote = ""
|
|
1219
1292
|
} else {
|
|
1220
|
-
$
|
|
1293
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1294
|
+
}
|
|
1295
|
+
} else if ($char in $WHITESPACES) {
|
|
1296
|
+
if $in_word {
|
|
1297
|
+
$words = $words | append (do $decode $word false)
|
|
1298
|
+
$word = ""
|
|
1299
|
+
$in_word = false
|
|
1300
|
+
}
|
|
1301
|
+
$trailing_space = true
|
|
1302
|
+
} else {
|
|
1303
|
+
if ($char in $QUOTES) {
|
|
1304
|
+
$quote = $char
|
|
1305
|
+
} else if $char == "#" and $prev == "r" {
|
|
1306
|
+
$quote = "#"
|
|
1307
|
+
$raw_hashes = "#"
|
|
1221
1308
|
}
|
|
1309
|
+
$word = $word + $char
|
|
1310
|
+
$in_word = true
|
|
1222
1311
|
}
|
|
1312
|
+
$prev = $char
|
|
1223
1313
|
}
|
|
1224
|
-
if
|
|
1225
|
-
$
|
|
1314
|
+
if $in_word {
|
|
1315
|
+
$words = $words | append (do $decode $word ($quote != ""))
|
|
1316
|
+
} else if $trailing_space {
|
|
1317
|
+
$words = $words | append ""
|
|
1318
|
+
}
|
|
1319
|
+
$words
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
# Helper to tell whether Nushell would not read a value back verbatim as a
|
|
1323
|
+
# single argument. A leading ! is included because the line editor expands
|
|
1324
|
+
# history (e.g., !!) there. With --allow-expansion, a leading tilde and n-dot
|
|
1325
|
+
# path segments (e.g., ...) are allowed to expand.
|
|
1326
|
+
def needs-quote [--allow-expansion]: string -> bool {
|
|
1327
|
+
let value = $in
|
|
1328
|
+
let special = ["'" '"' '\`' "#" "(" ")" "[" "]" "{" "}" "|" ";" "&" "$" "*" "?" "<" ">"]
|
|
1329
|
+
(
|
|
1330
|
+
($value =~ '\\s')
|
|
1331
|
+
or ($special | any {|c| $value | str contains $c })
|
|
1332
|
+
or ($value =~ '^(\\+\\+|[-+*/])?=$')
|
|
1333
|
+
or ($value | str starts-with '!')
|
|
1334
|
+
or ((not $allow_expansion) and (
|
|
1335
|
+
($value | str starts-with '~')
|
|
1336
|
+
or ($value | split row -r '[/\\\\]' | any {|s| $s =~ '^\\.{3,}$' })
|
|
1337
|
+
))
|
|
1338
|
+
)
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
# Helper to quote a value so that Nushell reads it back verbatim as a
|
|
1342
|
+
# single argument. The line editor expands history at a ! that follows
|
|
1343
|
+
# whitespace even inside quotes, so such a ! is put between two quoted parts,
|
|
1344
|
+
# right after a quote, where it is left alone.
|
|
1345
|
+
def quote-arg []: string -> string {
|
|
1346
|
+
let value = $in
|
|
1347
|
+
if ($value | is-empty) or not ($value | needs-quote) {
|
|
1348
|
+
$value
|
|
1349
|
+
} else if ($value | str contains "'") {
|
|
1350
|
+
'"' + ($value | str replace -a '\\' '\\\\' | str replace -a '"' '\\"' | str replace -ar '(\\s)!' '\${1}"!"') + '"'
|
|
1351
|
+
} else {
|
|
1352
|
+
"'" + ($value | str replace -ar '(\\s)!' "\${1}'!'") + "'"
|
|
1226
1353
|
}
|
|
1227
|
-
$result
|
|
1228
1354
|
}
|
|
1229
1355
|
|
|
1230
1356
|
# Completion command that calls back to the CLI
|
|
1231
1357
|
export def "${functionName}" [context: string] {
|
|
1232
|
-
# Split context into
|
|
1358
|
+
# Split context into the arguments Nushell would pass, including an empty
|
|
1359
|
+
# one for a new word when the context ends in unquoted whitespace
|
|
1233
1360
|
let tokens = $context | args-split
|
|
1234
1361
|
|
|
1235
1362
|
# Remove the command name (first token) to get arguments
|
|
@@ -1239,18 +1366,11 @@ export def "${functionName}" [context: string] {
|
|
|
1239
1366
|
[]
|
|
1240
1367
|
}
|
|
1241
1368
|
|
|
1242
|
-
# If context ends with whitespace, add empty string to represent the new argument
|
|
1243
|
-
let args_with_empty = if ($context | str ends-with ' ') {
|
|
1244
|
-
$args | append ''
|
|
1245
|
-
} else {
|
|
1246
|
-
$args
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
1369
|
# Ensure at least one argument (empty string) to get completions not script
|
|
1250
|
-
let final_args = if ($
|
|
1370
|
+
let final_args = if ($args | is-empty) {
|
|
1251
1371
|
['']
|
|
1252
1372
|
} else {
|
|
1253
|
-
$
|
|
1373
|
+
$args
|
|
1254
1374
|
}
|
|
1255
1375
|
|
|
1256
1376
|
let output = try {
|
|
@@ -1432,19 +1552,45 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1432
1552
|
if ($parsed.parent | is-empty) { "" } else if ($parsed.parent | str ends-with "/") { $parsed.parent } else { $parsed.parent + "/" }
|
|
1433
1553
|
}
|
|
1434
1554
|
|
|
1435
|
-
# Format file completions
|
|
1555
|
+
# Format file completions, quoting names that Nushell would not read
|
|
1556
|
+
# back as a single argument
|
|
1436
1557
|
$filtered | each {|item|
|
|
1437
|
-
let
|
|
1438
|
-
|
|
1558
|
+
let base = if $item.type == dir {
|
|
1559
|
+
($item.name | path basename) + "/"
|
|
1439
1560
|
} else {
|
|
1440
|
-
$
|
|
1561
|
+
$item.name | path basename
|
|
1562
|
+
}
|
|
1563
|
+
let name = $dir_prefix + $base
|
|
1564
|
+
# Nushell expands a leading tilde even in a file name, but not after ./
|
|
1565
|
+
let spelled = if ($dir_prefix | is-empty) and ($base | str starts-with '~') {
|
|
1566
|
+
'./' + $base
|
|
1567
|
+
} else {
|
|
1568
|
+
$name
|
|
1569
|
+
}
|
|
1570
|
+
# A tilde or n-dot prefix typed by the user is meant to be expanded
|
|
1571
|
+
# by Nushell, which quoting would prevent, so expand such a prefix
|
|
1572
|
+
# here when the value has to be quoted
|
|
1573
|
+
let literal_ndots = ($base | str replace -r '[/\\\\]$' '') =~ '^\\.{3,}$'
|
|
1574
|
+
let expandable = ($dir_prefix | str starts-with '~') or ($dir_prefix | split row -r '[/\\\\]' | any {|s| $s =~ '^\\.{3,}$' })
|
|
1575
|
+
let value = if not ($literal_ndots or ($spelled | needs-quote --allow-expansion)) {
|
|
1576
|
+
$spelled
|
|
1577
|
+
} else if $expandable {
|
|
1578
|
+
let expanded_prefix = ($dir_prefix | path expand --no-symlink)
|
|
1579
|
+
let expanded_prefix = if ($expanded_prefix | str ends-with "/") { $expanded_prefix } else { $expanded_prefix + "/" }
|
|
1580
|
+
($expanded_prefix + $base) | quote-arg
|
|
1581
|
+
} else {
|
|
1582
|
+
$spelled | quote-arg
|
|
1583
|
+
}
|
|
1584
|
+
if $value == $name {
|
|
1585
|
+
{ value: $value }
|
|
1586
|
+
} else {
|
|
1587
|
+
{ value: $value, display_override: $name }
|
|
1441
1588
|
}
|
|
1442
|
-
{ value: $name }
|
|
1443
1589
|
}
|
|
1444
1590
|
} else {
|
|
1445
1591
|
# Regular literal completion - split by tab
|
|
1446
1592
|
let parts = ($line | split row "\t")
|
|
1447
|
-
if ($parts | length) >= 2 {
|
|
1593
|
+
let record = if ($parts | length) >= 2 {
|
|
1448
1594
|
# value\\tdescription format
|
|
1449
1595
|
{ value: ($parts | get 0), description: ($parts | get 1) }
|
|
1450
1596
|
} else if ($parts | length) == 1 and ($parts | get 0 | is-not-empty) {
|
|
@@ -1453,6 +1599,18 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1453
1599
|
} else {
|
|
1454
1600
|
null
|
|
1455
1601
|
}
|
|
1602
|
+
# Quote values that Nushell would not read back as a single argument,
|
|
1603
|
+
# while the menu keeps showing them as they are
|
|
1604
|
+
if $record == null {
|
|
1605
|
+
null
|
|
1606
|
+
} else {
|
|
1607
|
+
let value = ($record.value | quote-arg)
|
|
1608
|
+
if $value == $record.value {
|
|
1609
|
+
$record
|
|
1610
|
+
} else {
|
|
1611
|
+
$record | upsert value $value | insert display_override $record.value
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1456
1614
|
}
|
|
1457
1615
|
} | flatten | compact
|
|
1458
1616
|
}
|
package/dist/completion.js
CHANGED
|
@@ -1174,62 +1174,189 @@ const nu = {
|
|
|
1174
1174
|
const safeName = programName.replace(/[^a-zA-Z0-9]+/g, "-");
|
|
1175
1175
|
const functionName = `nu-complete-${safeName}`;
|
|
1176
1176
|
return `
|
|
1177
|
-
# Helper to split
|
|
1177
|
+
# Helper to split a command line into the arguments Nushell would pass to
|
|
1178
|
+
# an external command. Words are found the way Nushell's lexer finds them
|
|
1179
|
+
# (quotes and raw strings may contain whitespace), then each word is decoded
|
|
1180
|
+
# like an external argument. A final empty argument is added when the line
|
|
1181
|
+
# ends in unquoted whitespace, for the word about to be completed.
|
|
1178
1182
|
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
1183
|
let WHITESPACES = [" " "\\t" "\\n" "\\r"]
|
|
1184
|
+
let QUOTES = ["'" '"' "\`"]
|
|
1184
1185
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1186
|
+
# Decodes one word. An unterminated word is the one still being typed,
|
|
1187
|
+
# so its open quote is taken as if it were closed.
|
|
1188
|
+
let decode = {|word: string, unterminated: bool|
|
|
1189
|
+
let hashes = if ($word | str starts-with "r#") {
|
|
1190
|
+
$word | str replace -r '(?s)^r(#+).*$' '$1'
|
|
1191
|
+
} else {
|
|
1192
|
+
""
|
|
1193
|
+
}
|
|
1194
|
+
if ($hashes | is-not-empty) {
|
|
1195
|
+
let prefix = "r" + $hashes + "'"
|
|
1196
|
+
let suffix = "'" + $hashes
|
|
1197
|
+
let size = ($hashes | str length)
|
|
1198
|
+
if ($word | str starts-with $prefix) and ($word | str ends-with $suffix) and (($word | str length) >= ($size * 2 + 3)) {
|
|
1199
|
+
$word | str replace $prefix "" | str replace -r ($suffix + '$') ""
|
|
1200
|
+
} else if $unterminated and ($word | str starts-with $prefix) {
|
|
1201
|
+
$word | str replace $prefix ""
|
|
1202
|
+
} else {
|
|
1203
|
+
$word
|
|
1204
|
+
}
|
|
1205
|
+
} else {
|
|
1206
|
+
mut result = ""
|
|
1207
|
+
mut quote = ""
|
|
1208
|
+
mut quoted = ""
|
|
1209
|
+
mut escaped = false
|
|
1210
|
+
for char in ($word | split chars) {
|
|
1211
|
+
if $quote == "" {
|
|
1212
|
+
if ($char in $QUOTES) {
|
|
1213
|
+
$quote = $char
|
|
1214
|
+
$quoted = ""
|
|
1215
|
+
} else {
|
|
1216
|
+
$result = $result + $char
|
|
1217
|
+
}
|
|
1218
|
+
} else if $escaped {
|
|
1219
|
+
$quoted = $quoted + $char
|
|
1220
|
+
$escaped = false
|
|
1221
|
+
} else if $char == $quote {
|
|
1222
|
+
let body = $quoted
|
|
1223
|
+
$result = $result + (if $quote == '"' {
|
|
1224
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1225
|
+
} else {
|
|
1226
|
+
$body
|
|
1227
|
+
})
|
|
1228
|
+
$quote = ""
|
|
1229
|
+
} else {
|
|
1230
|
+
$quoted = $quoted + $char
|
|
1231
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if $quote != "" {
|
|
1235
|
+
let body = $quoted
|
|
1236
|
+
$result = $result + (if not $unterminated {
|
|
1237
|
+
$quote + $body
|
|
1238
|
+
} else if $quote == '"' {
|
|
1239
|
+
try { '"' + $body + '"' | from nuon } catch { $body }
|
|
1240
|
+
} else {
|
|
1241
|
+
$body
|
|
1242
|
+
})
|
|
1243
|
+
}
|
|
1244
|
+
$result
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
mut words: list<string> = []
|
|
1249
|
+
mut word = ""
|
|
1250
|
+
mut in_word = false
|
|
1251
|
+
mut quote = ""
|
|
1252
|
+
mut escaped = false
|
|
1253
|
+
mut raw_hashes = ""
|
|
1254
|
+
mut raw_pending = ""
|
|
1255
|
+
mut prev = ""
|
|
1256
|
+
mut trailing_space = false
|
|
1189
1257
|
|
|
1190
1258
|
for char in ($in | split chars) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if $char == "
|
|
1199
|
-
$
|
|
1200
|
-
} else if $char == '"
|
|
1201
|
-
$
|
|
1202
|
-
} else if ($char in $WHITESPACES) {
|
|
1203
|
-
if ($current_token | is-not-empty) {
|
|
1204
|
-
$result = $result | append $current_token
|
|
1205
|
-
$current_token = ""
|
|
1206
|
-
}
|
|
1259
|
+
$trailing_space = false
|
|
1260
|
+
if $quote == "#" and $char != "#" and $char != "'" {
|
|
1261
|
+
# Not a raw string after all; lex this character normally
|
|
1262
|
+
$quote = ""
|
|
1263
|
+
}
|
|
1264
|
+
if $quote == "r" {
|
|
1265
|
+
# Inside a raw string, which ends at ' followed by the same number of #
|
|
1266
|
+
if ($raw_pending | is-not-empty) and $char == "#" {
|
|
1267
|
+
$raw_pending = $raw_pending + $char
|
|
1268
|
+
} else if $char == "'" {
|
|
1269
|
+
$raw_pending = $char
|
|
1207
1270
|
} else {
|
|
1208
|
-
$
|
|
1271
|
+
$raw_pending = ""
|
|
1272
|
+
}
|
|
1273
|
+
if $raw_pending == ("'" + $raw_hashes) {
|
|
1274
|
+
$quote = ""
|
|
1275
|
+
$raw_pending = ""
|
|
1209
1276
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1277
|
+
$word = $word + $char
|
|
1278
|
+
} else if $quote == "#" {
|
|
1279
|
+
# Between r# and the opening ' of a raw string
|
|
1280
|
+
$word = $word + $char
|
|
1281
|
+
if $char == "#" {
|
|
1282
|
+
$raw_hashes = $raw_hashes + $char
|
|
1213
1283
|
} else {
|
|
1214
|
-
$
|
|
1284
|
+
$quote = "r"
|
|
1215
1285
|
}
|
|
1216
|
-
} else if $
|
|
1217
|
-
|
|
1218
|
-
|
|
1286
|
+
} else if $quote != "" {
|
|
1287
|
+
$word = $word + $char
|
|
1288
|
+
if $escaped {
|
|
1289
|
+
$escaped = false
|
|
1290
|
+
} else if $char == $quote {
|
|
1291
|
+
$quote = ""
|
|
1219
1292
|
} else {
|
|
1220
|
-
$
|
|
1293
|
+
$escaped = ($quote == '"' and $char == '\\')
|
|
1294
|
+
}
|
|
1295
|
+
} else if ($char in $WHITESPACES) {
|
|
1296
|
+
if $in_word {
|
|
1297
|
+
$words = $words | append (do $decode $word false)
|
|
1298
|
+
$word = ""
|
|
1299
|
+
$in_word = false
|
|
1300
|
+
}
|
|
1301
|
+
$trailing_space = true
|
|
1302
|
+
} else {
|
|
1303
|
+
if ($char in $QUOTES) {
|
|
1304
|
+
$quote = $char
|
|
1305
|
+
} else if $char == "#" and $prev == "r" {
|
|
1306
|
+
$quote = "#"
|
|
1307
|
+
$raw_hashes = "#"
|
|
1221
1308
|
}
|
|
1309
|
+
$word = $word + $char
|
|
1310
|
+
$in_word = true
|
|
1222
1311
|
}
|
|
1312
|
+
$prev = $char
|
|
1223
1313
|
}
|
|
1224
|
-
if
|
|
1225
|
-
$
|
|
1314
|
+
if $in_word {
|
|
1315
|
+
$words = $words | append (do $decode $word ($quote != ""))
|
|
1316
|
+
} else if $trailing_space {
|
|
1317
|
+
$words = $words | append ""
|
|
1318
|
+
}
|
|
1319
|
+
$words
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
# Helper to tell whether Nushell would not read a value back verbatim as a
|
|
1323
|
+
# single argument. A leading ! is included because the line editor expands
|
|
1324
|
+
# history (e.g., !!) there. With --allow-expansion, a leading tilde and n-dot
|
|
1325
|
+
# path segments (e.g., ...) are allowed to expand.
|
|
1326
|
+
def needs-quote [--allow-expansion]: string -> bool {
|
|
1327
|
+
let value = $in
|
|
1328
|
+
let special = ["'" '"' '\`' "#" "(" ")" "[" "]" "{" "}" "|" ";" "&" "$" "*" "?" "<" ">"]
|
|
1329
|
+
(
|
|
1330
|
+
($value =~ '\\s')
|
|
1331
|
+
or ($special | any {|c| $value | str contains $c })
|
|
1332
|
+
or ($value =~ '^(\\+\\+|[-+*/])?=$')
|
|
1333
|
+
or ($value | str starts-with '!')
|
|
1334
|
+
or ((not $allow_expansion) and (
|
|
1335
|
+
($value | str starts-with '~')
|
|
1336
|
+
or ($value | split row -r '[/\\\\]' | any {|s| $s =~ '^\\.{3,}$' })
|
|
1337
|
+
))
|
|
1338
|
+
)
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
# Helper to quote a value so that Nushell reads it back verbatim as a
|
|
1342
|
+
# single argument. The line editor expands history at a ! that follows
|
|
1343
|
+
# whitespace even inside quotes, so such a ! is put between two quoted parts,
|
|
1344
|
+
# right after a quote, where it is left alone.
|
|
1345
|
+
def quote-arg []: string -> string {
|
|
1346
|
+
let value = $in
|
|
1347
|
+
if ($value | is-empty) or not ($value | needs-quote) {
|
|
1348
|
+
$value
|
|
1349
|
+
} else if ($value | str contains "'") {
|
|
1350
|
+
'"' + ($value | str replace -a '\\' '\\\\' | str replace -a '"' '\\"' | str replace -ar '(\\s)!' '\${1}"!"') + '"'
|
|
1351
|
+
} else {
|
|
1352
|
+
"'" + ($value | str replace -ar '(\\s)!' "\${1}'!'") + "'"
|
|
1226
1353
|
}
|
|
1227
|
-
$result
|
|
1228
1354
|
}
|
|
1229
1355
|
|
|
1230
1356
|
# Completion command that calls back to the CLI
|
|
1231
1357
|
export def "${functionName}" [context: string] {
|
|
1232
|
-
# Split context into
|
|
1358
|
+
# Split context into the arguments Nushell would pass, including an empty
|
|
1359
|
+
# one for a new word when the context ends in unquoted whitespace
|
|
1233
1360
|
let tokens = $context | args-split
|
|
1234
1361
|
|
|
1235
1362
|
# Remove the command name (first token) to get arguments
|
|
@@ -1239,18 +1366,11 @@ export def "${functionName}" [context: string] {
|
|
|
1239
1366
|
[]
|
|
1240
1367
|
}
|
|
1241
1368
|
|
|
1242
|
-
# If context ends with whitespace, add empty string to represent the new argument
|
|
1243
|
-
let args_with_empty = if ($context | str ends-with ' ') {
|
|
1244
|
-
$args | append ''
|
|
1245
|
-
} else {
|
|
1246
|
-
$args
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
1369
|
# Ensure at least one argument (empty string) to get completions not script
|
|
1250
|
-
let final_args = if ($
|
|
1370
|
+
let final_args = if ($args | is-empty) {
|
|
1251
1371
|
['']
|
|
1252
1372
|
} else {
|
|
1253
|
-
$
|
|
1373
|
+
$args
|
|
1254
1374
|
}
|
|
1255
1375
|
|
|
1256
1376
|
let output = try {
|
|
@@ -1432,19 +1552,45 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1432
1552
|
if ($parsed.parent | is-empty) { "" } else if ($parsed.parent | str ends-with "/") { $parsed.parent } else { $parsed.parent + "/" }
|
|
1433
1553
|
}
|
|
1434
1554
|
|
|
1435
|
-
# Format file completions
|
|
1555
|
+
# Format file completions, quoting names that Nushell would not read
|
|
1556
|
+
# back as a single argument
|
|
1436
1557
|
$filtered | each {|item|
|
|
1437
|
-
let
|
|
1438
|
-
|
|
1558
|
+
let base = if $item.type == dir {
|
|
1559
|
+
($item.name | path basename) + "/"
|
|
1439
1560
|
} else {
|
|
1440
|
-
$
|
|
1561
|
+
$item.name | path basename
|
|
1562
|
+
}
|
|
1563
|
+
let name = $dir_prefix + $base
|
|
1564
|
+
# Nushell expands a leading tilde even in a file name, but not after ./
|
|
1565
|
+
let spelled = if ($dir_prefix | is-empty) and ($base | str starts-with '~') {
|
|
1566
|
+
'./' + $base
|
|
1567
|
+
} else {
|
|
1568
|
+
$name
|
|
1569
|
+
}
|
|
1570
|
+
# A tilde or n-dot prefix typed by the user is meant to be expanded
|
|
1571
|
+
# by Nushell, which quoting would prevent, so expand such a prefix
|
|
1572
|
+
# here when the value has to be quoted
|
|
1573
|
+
let literal_ndots = ($base | str replace -r '[/\\\\]$' '') =~ '^\\.{3,}$'
|
|
1574
|
+
let expandable = ($dir_prefix | str starts-with '~') or ($dir_prefix | split row -r '[/\\\\]' | any {|s| $s =~ '^\\.{3,}$' })
|
|
1575
|
+
let value = if not ($literal_ndots or ($spelled | needs-quote --allow-expansion)) {
|
|
1576
|
+
$spelled
|
|
1577
|
+
} else if $expandable {
|
|
1578
|
+
let expanded_prefix = ($dir_prefix | path expand --no-symlink)
|
|
1579
|
+
let expanded_prefix = if ($expanded_prefix | str ends-with "/") { $expanded_prefix } else { $expanded_prefix + "/" }
|
|
1580
|
+
($expanded_prefix + $base) | quote-arg
|
|
1581
|
+
} else {
|
|
1582
|
+
$spelled | quote-arg
|
|
1583
|
+
}
|
|
1584
|
+
if $value == $name {
|
|
1585
|
+
{ value: $value }
|
|
1586
|
+
} else {
|
|
1587
|
+
{ value: $value, display_override: $name }
|
|
1441
1588
|
}
|
|
1442
|
-
{ value: $name }
|
|
1443
1589
|
}
|
|
1444
1590
|
} else {
|
|
1445
1591
|
# Regular literal completion - split by tab
|
|
1446
1592
|
let parts = ($line | split row "\t")
|
|
1447
|
-
if ($parts | length) >= 2 {
|
|
1593
|
+
let record = if ($parts | length) >= 2 {
|
|
1448
1594
|
# value\\tdescription format
|
|
1449
1595
|
{ value: ($parts | get 0), description: ($parts | get 1) }
|
|
1450
1596
|
} else if ($parts | length) == 1 and ($parts | get 0 | is-not-empty) {
|
|
@@ -1453,6 +1599,18 @@ ${escapedArgs ? ` ^${programName} ${escapedArgs} ...$final_args | complete |
|
|
|
1453
1599
|
} else {
|
|
1454
1600
|
null
|
|
1455
1601
|
}
|
|
1602
|
+
# Quote values that Nushell would not read back as a single argument,
|
|
1603
|
+
# while the menu keeps showing them as they are
|
|
1604
|
+
if $record == null {
|
|
1605
|
+
null
|
|
1606
|
+
} else {
|
|
1607
|
+
let value = ($record.value | quote-arg)
|
|
1608
|
+
if $value == $record.value {
|
|
1609
|
+
$record
|
|
1610
|
+
} else {
|
|
1611
|
+
$record | upsert value $value | insert display_override $record.value
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1456
1614
|
}
|
|
1457
1615
|
} | flatten | compact
|
|
1458
1616
|
}
|