@@ -367,7 +367,8 @@ function complete_path(path::AbstractString;
367
367
use_envpath= false ,
368
368
shell_escape= false ,
369
369
raw_escape= false ,
370
- string_escape= false )
370
+ string_escape= false ,
371
+ contract_user= false )
371
372
@assert ! (shell_escape && string_escape)
372
373
if Base. Sys. isunix () && occursin (r" ^~(?:/|$)" , path)
373
374
# if the path is just "~", don't consider the expanded username as a prefix
@@ -413,15 +414,16 @@ function complete_path(path::AbstractString;
413
414
414
415
matches = ((shell_escape ? do_shell_escape (s) : string_escape ? do_string_escape (s) : s) for s in matches)
415
416
matches = ((raw_escape ? do_raw_escape (s) : s) for s in matches)
416
- matches = Completion[PathCompletion (s) for s in matches]
417
+ matches = Completion[PathCompletion (contract_user ? contractuser (s) : s) for s in matches]
417
418
return matches, dir, ! isempty (matches)
418
419
end
419
420
420
421
function complete_path (path:: AbstractString ,
421
422
pos:: Int ;
422
423
use_envpath= false ,
423
424
shell_escape= false ,
424
- string_escape= false )
425
+ string_escape= false ,
426
+ contract_user= false )
425
427
# # TODO : enable this depwarn once Pkg is fixed
426
428
# Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
427
429
paths, dir, success = complete_path (path; use_envpath, shell_escape, string_escape)
@@ -909,7 +911,7 @@ function close_path_completion(dir, paths, str, pos)
909
911
return lastindex (str) <= pos || str[nextind (str, pos)] != ' "'
910
912
end
911
913
912
- function bslash_completions (string:: String , pos:: Int )
914
+ function bslash_completions (string:: String , pos:: Int , hint :: Bool = false )
913
915
slashpos = something (findprev (isequal (' \\ ' ), string, pos), 0 )
914
916
if (something (findprev (in (bslash_separators), string, pos), 0 ) < slashpos &&
915
917
! (1 < slashpos && (string[prevind (string, slashpos)]== ' \\ ' )))
@@ -1166,7 +1168,7 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff
1166
1168
return sort! (unique (suggestions), by= completion_text), (dotpos+ 1 ): pos, true
1167
1169
end
1168
1170
1169
- function completions (string:: String , pos:: Int , context_module:: Module = Main, shift:: Bool = true )
1171
+ function completions (string:: String , pos:: Int , context_module:: Module = Main, shift:: Bool = true , hint :: Bool = false )
1170
1172
# First parse everything up to the current position
1171
1173
partial = string[1 : pos]
1172
1174
inc_tag = Base. incomplete_tag (Meta. parse (partial, raise= false , depwarn= false ))
@@ -1219,6 +1221,9 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
1219
1221
# its invocation.
1220
1222
varrange = findprev (" var\" " , string, pos)
1221
1223
1224
+ expanded = nothing
1225
+ was_expanded = false
1226
+
1222
1227
if varrange != = nothing
1223
1228
ok, ret = bslash_completions (string, pos)
1224
1229
ok && return ret
@@ -1235,7 +1240,13 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
1235
1240
scs:: String = string[r]
1236
1241
1237
1242
expanded = complete_expanduser (scs, r)
1238
- expanded[3 ] && return expanded # If user expansion available, return it
1243
+ was_expanded = expanded[3 ]
1244
+ if was_expanded
1245
+ scs = (only (expanded[1 ]):: PathCompletion ). path
1246
+ # If tab press, ispath and user expansion available, return it now
1247
+ # otherwise see if we can complete the path further before returning with expanded ~
1248
+ ! hint && ispath (scs) && return expanded:: Completions
1249
+ end
1239
1250
1240
1251
path:: String = replace (scs, r" (\\ +)\g 1(\\ ?)`" => " \1\2 `" ) # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many
1241
1252
# This expansion with "\\ "=>' ' replacement and shell_escape=true
@@ -1253,12 +1264,19 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
1253
1264
r = nextind (string, startpos + sizeof (dir)): pos
1254
1265
else
1255
1266
map! (paths, paths) do c:: PathCompletion
1256
- return PathCompletion (dir * " /" * c. path)
1267
+ p = dir * " /" * c. path
1268
+ was_expanded && (p = contractuser (p))
1269
+ return PathCompletion (p)
1257
1270
end
1258
1271
end
1259
1272
end
1260
1273
end
1261
- return sort! (paths, by= p-> p. path), r, success
1274
+ if isempty (paths) && ! hint && was_expanded
1275
+ # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1276
+ return expanded:: Completions
1277
+ else
1278
+ return sort! (paths, by= p-> p. path), r:: UnitRange{Int} , success
1279
+ end
1262
1280
end
1263
1281
elseif inc_tag === :string
1264
1282
# Find first non-escaped quote
@@ -1268,7 +1286,13 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
1268
1286
scs:: String = string[r]
1269
1287
1270
1288
expanded = complete_expanduser (scs, r)
1271
- expanded[3 ] && return expanded # If user expansion available, return it
1289
+ was_expanded = expanded[3 ]
1290
+ if was_expanded
1291
+ scs = (only (expanded[1 ]):: PathCompletion ). path
1292
+ # If tab press, ispath and user expansion available, return it now
1293
+ # otherwise see if we can complete the path further before returning with expanded ~
1294
+ ! hint && ispath (scs) && return expanded:: Completions
1295
+ end
1272
1296
1273
1297
path = try
1274
1298
unescape_string (replace (scs, " \\\$ " => " \$ " ))
@@ -1280,7 +1304,9 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
1280
1304
paths, dir, success = complete_path (path:: String , string_escape= true )
1281
1305
1282
1306
if close_path_completion (dir, paths, path, pos)
1283
- paths[1 ] = PathCompletion ((paths[1 ]:: PathCompletion ). path * " \" " )
1307
+ p = (paths[1 ]:: PathCompletion ). path * " \" "
1308
+ hint && was_expanded && (p = contractuser (p))
1309
+ paths[1 ] = PathCompletion (p)
1284
1310
end
1285
1311
1286
1312
if success && ! isempty (dir)
@@ -1289,21 +1315,31 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
1289
1315
# otherwise make it the whole completion
1290
1316
if endswith (dir, " /" ) && startswith (scs, dir)
1291
1317
r = (startpos + sizeof (dir)): pos
1292
- elseif startswith (scs, dir * " /" )
1318
+ elseif startswith (scs, dir * " /" ) && dir != dirname (homedir ())
1319
+ was_expanded && (dir = contractuser (dir))
1293
1320
r = nextind (string, startpos + sizeof (dir)): pos
1294
1321
else
1295
1322
map! (paths, paths) do c:: PathCompletion
1296
- return PathCompletion (dir * " /" * c. path)
1323
+ p = dir * " /" * c. path
1324
+ hint && was_expanded && (p = contractuser (p))
1325
+ return PathCompletion (p)
1297
1326
end
1298
1327
end
1299
1328
end
1300
1329
end
1301
1330
1302
1331
# Fallthrough allowed so that Latex symbols can be completed in strings
1303
- success && return sort! (paths, by= p-> p. path), r, success
1332
+ if success
1333
+ return sort! (paths, by= p-> p. path), r:: UnitRange{Int} , success
1334
+ elseif ! hint && was_expanded
1335
+ # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1336
+ return expanded:: Completions
1337
+ end
1304
1338
end
1305
1339
end
1306
1340
end
1341
+ # if path has ~ and we didn't find any paths to complete just return the expanded path
1342
+ was_expanded && return expanded:: Completions
1307
1343
1308
1344
ok, ret = bslash_completions (string, pos)
1309
1345
ok && return ret
@@ -1389,7 +1425,7 @@ end
1389
1425
module_filter (mod:: Module , x:: Symbol ) =
1390
1426
Base. isbindingresolved (mod, x) && isdefined (mod, x) && isa (getglobal (mod, x), Module)
1391
1427
1392
- function shell_completions (string, pos)
1428
+ function shell_completions (string, pos, hint :: Bool = false )
1393
1429
# First parse everything up to the current position
1394
1430
scs = string[1 : pos]
1395
1431
args, last_arg_start = try
@@ -1407,7 +1443,7 @@ function shell_completions(string, pos)
1407
1443
# If the last char was a space, but shell_parse ignored it search on "".
1408
1444
if isexpr (lastarg, :incomplete ) || isexpr (lastarg, :error )
1409
1445
partial = string[last_arg_start: pos]
1410
- ret, range = completions (partial, lastindex (partial))
1446
+ ret, range = completions (partial, lastindex (partial), Main, true , hint )
1411
1447
range = range .+ (last_arg_start - 1 )
1412
1448
return ret, range, true
1413
1449
elseif endswith (scs, ' ' ) && ! endswith (scs, " \\ " )
@@ -1422,9 +1458,16 @@ function shell_completions(string, pos)
1422
1458
# Also try looking into the env path if the user wants to complete the first argument
1423
1459
use_envpath = length (args. args) < 2
1424
1460
1425
- # TODO : call complete_expanduser here?
1461
+ expanded = complete_expanduser (path, r)
1462
+ was_expanded = expanded[3 ]
1463
+ if was_expanded
1464
+ path = (only (expanded[1 ]):: PathCompletion ). path
1465
+ # If tab press, ispath and user expansion available, return it now
1466
+ # otherwise see if we can complete the path further before returning with expanded ~
1467
+ ! hint && ispath (path) && return expanded:: Completions
1468
+ end
1426
1469
1427
- paths, dir, success = complete_path (path, use_envpath= use_envpath, shell_escape= true )
1470
+ paths, dir, success = complete_path (path, use_envpath= use_envpath, shell_escape= true , contract_user = was_expanded )
1428
1471
1429
1472
if success && ! isempty (dir)
1430
1473
let dir = do_shell_escape (dir)
@@ -1442,7 +1485,14 @@ function shell_completions(string, pos)
1442
1485
end
1443
1486
end
1444
1487
end
1445
-
1488
+ # if ~ was expanded earlier and the incomplete string isn't a path
1489
+ # return the path with contracted user to match what the hint shows. Otherwise expand ~
1490
+ # i.e. require two tab presses to expand user
1491
+ if was_expanded && ! ispath (path)
1492
+ map! (paths, paths) do c:: PathCompletion
1493
+ PathCompletion (contractuser (c. path))
1494
+ end
1495
+ end
1446
1496
return paths, r, success
1447
1497
end
1448
1498
return Completion[], 0 : - 1 , false
0 commit comments