Skip to content

Commit

Permalink
REPLCompletions: async cache PATH scan
Browse files Browse the repository at this point in the history
  • Loading branch information
IanButterworth committed Jan 10, 2024
1 parent b4d857b commit 8b91117
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 31 deletions.
1 change: 1 addition & 0 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module REPL
const PRECOMPILE_STATEMENTS = Vector{String}()

function __init__()
Base.generating_output() || Threads.@spawn REPLCompletions.cache_path()
Base.REPL_MODULE_REF[] = REPL
# We can encounter the situation where the sub-ordinate process used
# during precompilation of REPL, can load a valid cache-file.
Expand Down
72 changes: 41 additions & 31 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,42 @@ function do_string_escape(s)
return escape_string(s, ('\"','$'))
end

const path_cache_lock = Base.ReentrantLock()
const path_cache = Dict{String,Vector{String}}()

function cache_path()
pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":")

for pathdir in pathdirs
actualpath = try
realpath(pathdir)
catch ex
ex isa Base.IOError || rethrow()
# Bash doesn't expect every folder in PATH to exist, so neither shall we
continue
end

if actualpath != pathdir && in(actualpath, pathdirs)
# Remove paths which (after resolving links) are in the env path twice.
# Many distros eg. point /bin to /usr/bin but have both in the env path.
continue
end

try
@lock path_cache_lock get!(path_cache, pathdir, readdir(pathdir))
catch e
# Bash allows dirs in PATH that can't be read, so we should as well.
if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
continue
else
# We only handle IOError and ArgumentError here
rethrow()
end
end
end
return path_cache
end

function complete_path(path::AbstractString;
use_envpath=false,
shell_escape=false,
Expand Down Expand Up @@ -307,37 +343,11 @@ function complete_path(path::AbstractString;
end
end

if use_envpath && isempty(dir)
# Look for files in PATH as well
pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":")

for pathdir in pathdirs
actualpath = try
realpath(pathdir)
catch ex
ex isa Base.IOError || rethrow()
# Bash doesn't expect every folder in PATH to exist, so neither shall we
continue
end

if actualpath != pathdir && in(actualpath, pathdirs)
# Remove paths which (after resolving links) are in the env path twice.
# Many distros eg. point /bin to /usr/bin but have both in the env path.
continue
end

filesinpath = try
readdir(pathdir)
catch e
# Bash allows dirs in PATH that can't be read, so we should as well.
if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
continue
else
# We only handle IOError and ArgumentError here
rethrow()
end
end

if use_envpath && isempty(dir) && trylock(path_cache_lock)
# Look for files in PATH as well.
# these are cached in `cache_path` in a separate task at first shell mode switch.
# If we cannot get lock because its still caching just pass over this.
for (pathdir, filesinpath) in path_cache
for file in filesinpath
# In a perfect world, we would filter on whether the file is executable
# here, or even on whether the current user can execute the file in question.
Expand Down

0 comments on commit 8b91117

Please sign in to comment.