@@ -367,7 +367,8 @@ function complete_path(path::AbstractString;
367367 use_envpath= false ,
368368 shell_escape= false ,
369369 raw_escape= false ,
370- string_escape= false )
370+ string_escape= false ,
371+ contract_user= false )
371372 @assert ! (shell_escape && string_escape)
372373 if Base. Sys. isunix () && occursin (r" ^~(?:/|$)" , path)
373374 # if the path is just "~", don't consider the expanded username as a prefix
@@ -413,15 +414,16 @@ function complete_path(path::AbstractString;
413414
414415 matches = ((shell_escape ? do_shell_escape (s) : string_escape ? do_string_escape (s) : s) for s in matches)
415416 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]
417418 return matches, dir, ! isempty (matches)
418419end
419420
420421function complete_path (path:: AbstractString ,
421422 pos:: Int ;
422423 use_envpath= false ,
423424 shell_escape= false ,
424- string_escape= false )
425+ string_escape= false ,
426+ contract_user= false )
425427 # # TODO : enable this depwarn once Pkg is fixed
426428 # Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
427429 paths, dir, success = complete_path (path; use_envpath, shell_escape, string_escape)
@@ -909,7 +911,7 @@ function close_path_completion(dir, paths, str, pos)
909911 return lastindex (str) <= pos || str[nextind (str, pos)] != ' "'
910912end
911913
912- function bslash_completions (string:: String , pos:: Int )
914+ function bslash_completions (string:: String , pos:: Int , hint :: Bool = false )
913915 slashpos = something (findprev (isequal (' \\ ' ), string, pos), 0 )
914916 if (something (findprev (in (bslash_separators), string, pos), 0 ) < slashpos &&
915917 ! (1 < slashpos && (string[prevind (string, slashpos)]== ' \\ ' )))
@@ -1166,7 +1168,7 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff
11661168 return sort! (unique (suggestions), by= completion_text), (dotpos+ 1 ): pos, true
11671169end
11681170
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 )
11701172 # First parse everything up to the current position
11711173 partial = string[1 : pos]
11721174 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
12191221 # its invocation.
12201222 varrange = findprev (" var\" " , string, pos)
12211223
1224+ expanded = nothing
1225+ was_expanded = false
1226+
12221227 if varrange != = nothing
12231228 ok, ret = bslash_completions (string, pos)
12241229 ok && return ret
@@ -1235,7 +1240,13 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12351240 scs:: String = string[r]
12361241
12371242 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
12391250
12401251 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
12411252 # This expansion with "\\ "=>' ' replacement and shell_escape=true
@@ -1253,12 +1264,19 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12531264 r = nextind (string, startpos + sizeof (dir)): pos
12541265 else
12551266 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)
12571270 end
12581271 end
12591272 end
12601273 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
12621280 end
12631281 elseif inc_tag === :string
12641282 # Find first non-escaped quote
@@ -1268,7 +1286,13 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12681286 scs:: String = string[r]
12691287
12701288 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
12721296
12731297 path = try
12741298 unescape_string (replace (scs, " \\\$ " => " \$ " ))
@@ -1280,7 +1304,9 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12801304 paths, dir, success = complete_path (path:: String , string_escape= true )
12811305
12821306 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)
12841310 end
12851311
12861312 if success && ! isempty (dir)
@@ -1289,21 +1315,31 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12891315 # otherwise make it the whole completion
12901316 if endswith (dir, " /" ) && startswith (scs, dir)
12911317 r = (startpos + sizeof (dir)): pos
1292- elseif startswith (scs, dir * " /" )
1318+ elseif startswith (scs, dir * " /" ) && dir != dirname (homedir ())
1319+ was_expanded && (dir = contractuser (dir))
12931320 r = nextind (string, startpos + sizeof (dir)): pos
12941321 else
12951322 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)
12971326 end
12981327 end
12991328 end
13001329 end
13011330
13021331 # 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
13041338 end
13051339 end
13061340 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
13071343
13081344 ok, ret = bslash_completions (string, pos)
13091345 ok && return ret
@@ -1389,7 +1425,7 @@ end
13891425module_filter (mod:: Module , x:: Symbol ) =
13901426 Base. isbindingresolved (mod, x) && isdefined (mod, x) && isa (getglobal (mod, x), Module)
13911427
1392- function shell_completions (string, pos)
1428+ function shell_completions (string, pos, hint :: Bool = false )
13931429 # First parse everything up to the current position
13941430 scs = string[1 : pos]
13951431 args, last_arg_start = try
@@ -1407,7 +1443,7 @@ function shell_completions(string, pos)
14071443 # If the last char was a space, but shell_parse ignored it search on "".
14081444 if isexpr (lastarg, :incomplete ) || isexpr (lastarg, :error )
14091445 partial = string[last_arg_start: pos]
1410- ret, range = completions (partial, lastindex (partial))
1446+ ret, range = completions (partial, lastindex (partial), Main, true , hint )
14111447 range = range .+ (last_arg_start - 1 )
14121448 return ret, range, true
14131449 elseif endswith (scs, ' ' ) && ! endswith (scs, " \\ " )
@@ -1422,9 +1458,16 @@ function shell_completions(string, pos)
14221458 # Also try looking into the env path if the user wants to complete the first argument
14231459 use_envpath = length (args. args) < 2
14241460
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
14261469
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 )
14281471
14291472 if success && ! isempty (dir)
14301473 let dir = do_shell_escape (dir)
@@ -1442,7 +1485,14 @@ function shell_completions(string, pos)
14421485 end
14431486 end
14441487 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
14461496 return paths, r, success
14471497 end
14481498 return Completion[], 0 : - 1 , false
0 commit comments