Skip to content

Commit a1ad1ba

Browse files
aviateskKristofferC
authored andcommitted
[REPLCompletions] enable completions for using Module.Inner| (#52952)
(cherry picked from commit a0d55cd)
1 parent 93a596e commit a1ad1ba

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
@@ -980,7 +980,10 @@ function project_deps_get_completion_candidates(pkgstarts::String, project_file:
980980
return Completion[PackageCompletion(name) for name in loading_candidates]
981981
end
982982

983-
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)
983+
function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc),
984+
context_module::Module, string::String, name::String,
985+
pos::Int, dotpos::Int, startpos::Int;
986+
comp_keywords=false)
984987
ex = nothing
985988
if comp_keywords
986989
append!(suggestions, complete_keyword(name))
@@ -1022,10 +1025,41 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff
10221025
if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
10231026
lookup_name, name = rsplit(s, ".", limit=2)
10241027
name = String(name)
1025-
10261028
ex = Meta.parse(lookup_name, raise=false, depwarn=false)
10271029
end
10281030
isexpr(ex, :incomplete) && (ex = nothing)
1031+
elseif isexpr(ex, (:using, :import))
1032+
arg1 = ex.args[1]
1033+
if isexpr(arg1, :.)
1034+
# We come here for cases like:
1035+
# - `string`: "using Mod1.Mod2.M"
1036+
# - `ex`: :(using Mod1.Mod2)
1037+
# - `name`: "M"
1038+
# Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol` to
1039+
# complete for inner modules whose name starts with `M`.
1040+
# Note that `ffunc` is set to `module_filter` within `completions`
1041+
ex = nothing
1042+
firstdot = true
1043+
for arg = arg1.args
1044+
if arg === :.
1045+
# override `context_module` if multiple `.` accessors are used
1046+
if firstdot
1047+
firstdot = false
1048+
else
1049+
context_module = parentmodule(context_module)
1050+
end
1051+
elseif arg isa Symbol
1052+
if ex === nothing
1053+
ex = arg
1054+
else
1055+
ex = Expr(:., out, QuoteNode(arg))
1056+
end
1057+
else # invalid expression
1058+
ex = nothing
1059+
break
1060+
end
1061+
end
1062+
end
10291063
elseif isexpr(ex, :call) && length(ex.args) > 1
10301064
isinfix = s[end] != ')'
10311065
# A complete call expression that does not finish with ')' is an infix call.
@@ -1106,8 +1140,9 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
11061140
ok && return ret
11071141
startpos = first(varrange) + 4
11081142
dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
1109-
return complete_identifiers!(Completion[], ffunc, context_module, string,
1110-
string[startpos:pos], pos, dotpos, startpos)
1143+
name = string[startpos:pos]
1144+
return complete_identifiers!(Completion[], ffunc, context_module, string, name, pos,
1145+
dotpos, startpos)
11111146
elseif inc_tag === :cmd
11121147
# TODO: should this call shell_completions instead of partially reimplementing it?
11131148
let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
@@ -1255,16 +1290,20 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12551290
end
12561291
end
12571292
end
1258-
ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
1293+
ffunc = module_filter
12591294
comp_keywords = false
12601295
end
12611296

12621297
startpos == 0 && (pos = -1)
12631298
dotpos < startpos && (dotpos = startpos - 1)
1264-
return complete_identifiers!(suggestions, ffunc, context_module, string,
1265-
name, pos, dotpos, startpos, comp_keywords)
1299+
return complete_identifiers!(suggestions, ffunc, context_module, string, name, pos,
1300+
dotpos, startpos;
1301+
comp_keywords)
12661302
end
12671303

1304+
module_filter(mod::Module, x::Symbol) =
1305+
Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
1306+
12681307
function shell_completions(string, pos)
12691308
# First parse everything up to the current position
12701309
scs = string[1:pos]

stdlib/REPL/test/replcompletions.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,3 +1939,47 @@ let s = "@issue51827 Base.ac"
19391939
@test res
19401940
@test "acquire" in c
19411941
end
1942+
1943+
# JuliaLang/julia#52922
1944+
let s = "using Base.Th"
1945+
c, r, res = test_complete_context(s)
1946+
@test res
1947+
@test "Threads" in c
1948+
end
1949+
let s = "using Base."
1950+
c, r, res = test_complete_context(s)
1951+
@test res
1952+
@test "BinaryPlatforms" in c
1953+
end
1954+
# test cases with the `.` accessor
1955+
module Issue52922
1956+
module Inner1
1957+
module Inner12 end
1958+
end
1959+
module Inner2 end
1960+
end
1961+
let s = "using .Iss"
1962+
c, r, res = test_complete_context(s)
1963+
@test res
1964+
@test "Issue52922" in c
1965+
end
1966+
let s = "using .Issue52922.Inn"
1967+
c, r, res = test_complete_context(s)
1968+
@test res
1969+
@test "Inner1" in c
1970+
end
1971+
let s = "using .Inner1.Inn"
1972+
c, r, res = test_complete_context(s, Issue52922)
1973+
@test res
1974+
@test "Inner12" in c
1975+
end
1976+
let s = "using ..Issue52922.Inn"
1977+
c, r, res = test_complete_context(s, Issue52922.Inner1)
1978+
@test res
1979+
@test "Inner2" in c
1980+
end
1981+
let s = "using ...Issue52922.Inn"
1982+
c, r, res = test_complete_context(s, Issue52922.Inner1.Inner12)
1983+
@test res
1984+
@test "Inner2" in c
1985+
end

0 commit comments

Comments
 (0)