Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REPLCompletions] enable completions for using Module.Inner| #52952

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,10 @@ function project_deps_get_completion_candidates(pkgstarts::String, project_file:
return Completion[PackageCompletion(name) for name in loading_candidates]
end

function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc::Function), context_module::Module, string::String, name::String, pos::Int, dotpos::Int, startpos::Int, comp_keywords=false)
function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc),
context_module::Module, string::String, name::String,
pos::Int, dotpos::Int, startpos::Int;
comp_keywords=false)
ex = nothing
if comp_keywords
append!(suggestions, complete_keyword(name))
Expand Down Expand Up @@ -1132,10 +1135,41 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff
if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
lookup_name, name = rsplit(s, ".", limit=2)
name = String(name)

ex = Meta.parse(lookup_name, raise=false, depwarn=false)
end
isexpr(ex, :incomplete) && (ex = nothing)
elseif isexpr(ex, (:using, :import))
arg1 = ex.args[1]
if isexpr(arg1, :.)
# We come here for cases like:
# - `string`: "using Mod1.Mod2.M"
# - `ex`: :(using Mod1.Mod2)
# - `name`: "M"
# Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol` to
# complete for inner modules whose name starts with `M`.
# Note that `ffunc` is set to `module_filter` within `completions`
ex = nothing
firstdot = true
for arg = arg1.args
if arg === :.
# override `context_module` if multiple `.` accessors are used
if firstdot
firstdot = false
else
context_module = parentmodule(context_module)
end
elseif arg isa Symbol
if ex === nothing
ex = arg
else
ex = Expr(:., out, QuoteNode(arg))
end
else # invalid expression
ex = nothing
break
end
end
end
elseif isexpr(ex, :call) && length(ex.args) > 1
isinfix = s[end] != ')'
# A complete call expression that does not finish with ')' is an infix call.
Expand Down Expand Up @@ -1216,8 +1250,9 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
ok && return ret
startpos = first(varrange) + 4
dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
return complete_identifiers!(Completion[], ffunc, context_module, string,
string[startpos:pos], pos, dotpos, startpos)
name = string[startpos:pos]
return complete_identifiers!(Completion[], ffunc, context_module, string, name, pos,
dotpos, startpos)
elseif inc_tag === :cmd
# TODO: should this call shell_completions instead of partially reimplementing it?
let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
Expand Down Expand Up @@ -1365,16 +1400,20 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
end
end
end
ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
ffunc = module_filter
comp_keywords = false
end

startpos == 0 && (pos = -1)
dotpos < startpos && (dotpos = startpos - 1)
return complete_identifiers!(suggestions, ffunc, context_module, string,
name, pos, dotpos, startpos, comp_keywords)
return complete_identifiers!(suggestions, ffunc, context_module, string, name, pos,
dotpos, startpos;
comp_keywords)
end

module_filter(mod::Module, x::Symbol) =
Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)

function shell_completions(string, pos)
# First parse everything up to the current position
scs = string[1:pos]
Expand Down
44 changes: 44 additions & 0 deletions stdlib/REPL/test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2203,3 +2203,47 @@ replinterp_invalidation_caller() = replinterp_invalidation_callee().value
@test REPLCompletions.repl_eval_ex(:(replinterp_invalidation_caller()), @__MODULE__) == Regex
replinterp_invalidation_callee(c::Bool=rand(Bool)) = Some(c ? "foo" : "bar")
@test REPLCompletions.repl_eval_ex(:(replinterp_invalidation_caller()), @__MODULE__) == String

# JuliaLang/julia#52922
let s = "using Base.Th"
c, r, res = test_complete_context(s)
@test res
@test "Threads" in c
end
let s = "using Base."
c, r, res = test_complete_context(s)
@test res
@test "BinaryPlatforms" in c
end
# test cases with the `.` accessor
module Issue52922
module Inner1
module Inner12 end
end
module Inner2 end
end
let s = "using .Iss"
c, r, res = test_complete_context(s)
@test res
@test "Issue52922" in c
end
let s = "using .Issue52922.Inn"
c, r, res = test_complete_context(s)
@test res
@test "Inner1" in c
end
let s = "using .Inner1.Inn"
c, r, res = test_complete_context(s, Issue52922)
@test res
@test "Inner12" in c
end
let s = "using ..Issue52922.Inn"
c, r, res = test_complete_context(s, Issue52922.Inner1)
@test res
@test "Inner2" in c
end
let s = "using ...Issue52922.Inn"
c, r, res = test_complete_context(s, Issue52922.Inner1.Inner12)
@test res
@test "Inner2" in c
end