Skip to content

Commit a0d55cd

Browse files
authored
[REPLCompletions] enable completions for using Module.Inner| (JuliaLang#52952)
1 parent 3a63f25 commit a0d55cd

File tree

2 files changed

+90
-7
lines changed

2 files changed

+90
-7
lines changed

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,10 @@ function project_deps_get_completion_candidates(pkgstarts::String, project_file:
10901090
return Completion[PackageCompletion(name) for name in loading_candidates]
10911091
end
10921092

1093-
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)
1093+
function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc),
1094+
context_module::Module, string::String, name::String,
1095+
pos::Int, dotpos::Int, startpos::Int;
1096+
comp_keywords=false)
10941097
ex = nothing
10951098
if comp_keywords
10961099
append!(suggestions, complete_keyword(name))
@@ -1132,10 +1135,41 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff
11321135
if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
11331136
lookup_name, name = rsplit(s, ".", limit=2)
11341137
name = String(name)
1135-
11361138
ex = Meta.parse(lookup_name, raise=false, depwarn=false)
11371139
end
11381140
isexpr(ex, :incomplete) && (ex = nothing)
1141+
elseif isexpr(ex, (:using, :import))
1142+
arg1 = ex.args[1]
1143+
if isexpr(arg1, :.)
1144+
# We come here for cases like:
1145+
# - `string`: "using Mod1.Mod2.M"
1146+
# - `ex`: :(using Mod1.Mod2)
1147+
# - `name`: "M"
1148+
# Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol` to
1149+
# complete for inner modules whose name starts with `M`.
1150+
# Note that `ffunc` is set to `module_filter` within `completions`
1151+
ex = nothing
1152+
firstdot = true
1153+
for arg = arg1.args
1154+
if arg === :.
1155+
# override `context_module` if multiple `.` accessors are used
1156+
if firstdot
1157+
firstdot = false
1158+
else
1159+
context_module = parentmodule(context_module)
1160+
end
1161+
elseif arg isa Symbol
1162+
if ex === nothing
1163+
ex = arg
1164+
else
1165+
ex = Expr(:., out, QuoteNode(arg))
1166+
end
1167+
else # invalid expression
1168+
ex = nothing
1169+
break
1170+
end
1171+
end
1172+
end
11391173
elseif isexpr(ex, :call) && length(ex.args) > 1
11401174
isinfix = s[end] != ')'
11411175
# A complete call expression that does not finish with ')' is an infix call.
@@ -1216,8 +1250,9 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12161250
ok && return ret
12171251
startpos = first(varrange) + 4
12181252
dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
1219-
return complete_identifiers!(Completion[], ffunc, context_module, string,
1220-
string[startpos:pos], pos, dotpos, startpos)
1253+
name = string[startpos:pos]
1254+
return complete_identifiers!(Completion[], ffunc, context_module, string, name, pos,
1255+
dotpos, startpos)
12211256
elseif inc_tag === :cmd
12221257
# TODO: should this call shell_completions instead of partially reimplementing it?
12231258
let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
@@ -1365,16 +1400,20 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
13651400
end
13661401
end
13671402
end
1368-
ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
1403+
ffunc = module_filter
13691404
comp_keywords = false
13701405
end
13711406

13721407
startpos == 0 && (pos = -1)
13731408
dotpos < startpos && (dotpos = startpos - 1)
1374-
return complete_identifiers!(suggestions, ffunc, context_module, string,
1375-
name, pos, dotpos, startpos, comp_keywords)
1409+
return complete_identifiers!(suggestions, ffunc, context_module, string, name, pos,
1410+
dotpos, startpos;
1411+
comp_keywords)
13761412
end
13771413

1414+
module_filter(mod::Module, x::Symbol) =
1415+
Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
1416+
13781417
function shell_completions(string, pos)
13791418
# First parse everything up to the current position
13801419
scs = string[1:pos]

stdlib/REPL/test/replcompletions.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,3 +2203,47 @@ replinterp_invalidation_caller() = replinterp_invalidation_callee().value
22032203
@test REPLCompletions.repl_eval_ex(:(replinterp_invalidation_caller()), @__MODULE__) == Regex
22042204
replinterp_invalidation_callee(c::Bool=rand(Bool)) = Some(c ? "foo" : "bar")
22052205
@test REPLCompletions.repl_eval_ex(:(replinterp_invalidation_caller()), @__MODULE__) == String
2206+
2207+
# JuliaLang/julia#52922
2208+
let s = "using Base.Th"
2209+
c, r, res = test_complete_context(s)
2210+
@test res
2211+
@test "Threads" in c
2212+
end
2213+
let s = "using Base."
2214+
c, r, res = test_complete_context(s)
2215+
@test res
2216+
@test "BinaryPlatforms" in c
2217+
end
2218+
# test cases with the `.` accessor
2219+
module Issue52922
2220+
module Inner1
2221+
module Inner12 end
2222+
end
2223+
module Inner2 end
2224+
end
2225+
let s = "using .Iss"
2226+
c, r, res = test_complete_context(s)
2227+
@test res
2228+
@test "Issue52922" in c
2229+
end
2230+
let s = "using .Issue52922.Inn"
2231+
c, r, res = test_complete_context(s)
2232+
@test res
2233+
@test "Inner1" in c
2234+
end
2235+
let s = "using .Inner1.Inn"
2236+
c, r, res = test_complete_context(s, Issue52922)
2237+
@test res
2238+
@test "Inner12" in c
2239+
end
2240+
let s = "using ..Issue52922.Inn"
2241+
c, r, res = test_complete_context(s, Issue52922.Inner1)
2242+
@test res
2243+
@test "Inner2" in c
2244+
end
2245+
let s = "using ...Issue52922.Inn"
2246+
c, r, res = test_complete_context(s, Issue52922.Inner1.Inner12)
2247+
@test res
2248+
@test "Inner2" in c
2249+
end

0 commit comments

Comments
 (0)