diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 06341361819c1..ab1cb32f8fb9b 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -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)) @@ -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. @@ -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 @@ -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] diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 1de42f86d2283..280b23a68c4e2 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -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