From 465e0df4d80d04d4a51e523fbec2805aadb7e356 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 31 May 2024 15:55:14 +0900 Subject: [PATCH] REPLCompletions: allow completions for explicitly `using`-ed names (#54610) The new feature `usings=true` added to `names` enhances REPL completions by allowing explicitly `using`-ed names to be found. ```julia julia> using Base: @assume_effects julia> @assu| # completes to `@assume_effects` ``` As a result, the implementation of REPL completions has been simplified. Additionally, it allows completion for names that are implicitly or explicitly `using`-ed in code specifying a module explicitly, such as: ```julia julia> module A end julia> A.signi| # completes to `A.significand` ``` - fixes #29275 - fixes #40356 - fixes #49109 - fixes #53524 --- NEWS.md | 3 ++ stdlib/REPL/src/REPLCompletions.jl | 38 ++++++++++---------- stdlib/REPL/test/replcompletions.jl | 54 +++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6abfbfff18175..9b265f1f82c24 100644 --- a/NEWS.md +++ b/NEWS.md @@ -108,6 +108,9 @@ Standard library changes #### REPL +- Using the new `usings=true` feature of the `names()` function, REPL completions can now + complete names that have been explicitly `using`-ed. ([#54610]) + #### SuiteSparse #### SparseArrays diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index fc727a294e448..e894d9b918072 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -135,9 +135,8 @@ function appendmacro!(syms, macros, needle, endchar) end end -function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, all::Bool = false, imported::Bool = false) - ssyms = names(mod, all = all, imported = imported) - all || filter!(Base.Fix1(Base.isexported, mod), ssyms) +function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString; kwargs...) + ssyms = names(mod; kwargs...) filter!(ffunc, ssyms) macros = filter(x -> startswith(String(x), "@" * name), ssyms) syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)] @@ -147,7 +146,7 @@ function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, end # REPL Symbol Completions -function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), context_module::Module=Main) +function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), context_module::Module) mod = context_module lookup_module = true @@ -173,23 +172,22 @@ function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), suggestions = Completion[] if lookup_module - # We will exclude the results that the user does not want, as well - # as excluding Main.Main.Main, etc., because that's most likely not what - # the user wants - p = let mod=mod, modname=nameof(mod) - (s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool && !(mod === Main && s === :MainInclude) - end - # Looking for a binding in a module - if mod == context_module - # Also look in modules we got through `using` - mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector - for m in mods - append!(suggestions, filtered_mod_names(p, m::Module, name)) + fmnames = let modname = nameof(mod), + is_main = mod===Main + filtered_mod_names(mod, name; all=true, imported=true, usings=true) do s::Symbol + if Base.isdeprecated(mod, s) + return false + elseif s === modname + return false # exclude `Main.Main.Main`, etc. + elseif !ffunc(mod, s)::Bool + return false + elseif is_main && s === :MainInclude + return false + end + return true end - append!(suggestions, filtered_mod_names(p, mod, name, true, true)) - else - append!(suggestions, filtered_mod_names(p, mod, name, true, false)) end + append!(suggestions, fmnames) elseif val !== nothing # looking for a property of an instance try for property in propertynames(val, false) @@ -1065,7 +1063,7 @@ end function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc), context_module::Module, string::String, name::String, pos::Int, dotpos::Int, startpos::Int; - comp_keywords=false) + comp_keywords::Bool=false) ex = nothing if comp_keywords append!(suggestions, complete_keyword(name)) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 0a73a944ec8ea..4041ef186555a 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -120,6 +120,7 @@ let ex = quote kwtest5(a::Char, b; xyz) = pass const named = (; len2=3) + const fmsoebelkv = (; len2=3) array = [1, 1] varfloat = 0.1 @@ -2129,7 +2130,7 @@ end end # issue #51194 -for (s, compl) in (("2*CompletionFoo.nam", "named"), +for (s, compl) in (("2*CompletionFoo.fmsoe", "fmsoebelkv"), (":a isa CompletionFoo.test!1", "test!12"), ("-CompletionFoo.Test_y(3).", "yy"), ("99 ⨷⁻ᵨ⁷ CompletionFoo.type_test.", "xx"), @@ -2138,8 +2139,11 @@ for (s, compl) in (("2*CompletionFoo.nam", "named"), ("CompletionFoo.type_test + CompletionFoo.unicode_αβγ.", "yy"), ("(CompletionFoo.type_test + CompletionFoo.unicode_αβγ).", "xx"), ("foo'CompletionFoo.test!1", "test!12")) - c, r = test_complete(s) - @test only(c) == compl + @testset let s=s, compl=compl + c, r = test_complete_noshift(s) + @test length(c) == 1 + @test only(c) == compl + end end # allows symbol completion within incomplete :macrocall @@ -2260,3 +2264,47 @@ let s = "Issue53126()." @test res @test isempty(c) end + +# complete explicitly `using`ed names +baremodule TestExplicitUsing +using Base: @assume_effects +end # baremodule TestExplicitUsing +let s = "@assu" + c, r, res = test_complete_context(s, TestExplicitUsing) + @test res + @test "@assume_effects" in c +end +let s = "TestExplicitUsing.@assu" + c, r, res = test_complete_context(s) + @test res + @test "@assume_effects" in c +end +baremodule TestExplicitUsingNegative end +let s = "@assu" + c, r, res = test_complete_context(s, TestExplicitUsingNegative) + @test res + @test "@assume_effects" ∉ c +end +let s = "TestExplicitUsingNegative.@assu" + c, r, res = test_complete_context(s) + @test res + @test "@assume_effects" ∉ c +end +# should complete implicitly `using`ed names +module TestImplicitUsing end +let s = "@asse" + c, r, res = test_complete_context(s, TestImplicitUsing) + @test res + @test "@assert" in c +end +let s = "TestImplicitUsing.@asse" + c, r, res = test_complete_context(s) + @test res + @test "@assert" in c +end +# JuliaLang/julia#53999 +let s = "using Base.Thre" + c, r, res = test_complete_context(s) + @test res + @test "Threads" in c +end