Skip to content

Commit

Permalink
REPLCompletions: allow completions for explicitly using-ed names (J…
Browse files Browse the repository at this point in the history
…uliaLang#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 JuliaLang#29275
- fixes JuliaLang#40356
- fixes JuliaLang#49109
- fixes JuliaLang#53524
  • Loading branch information
aviatesk authored and lazarusA committed Jul 12, 2024
1 parent d11752b commit 465e0df
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 23 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 18 additions & 20 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
54 changes: 51 additions & 3 deletions stdlib/REPL/test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"),
Expand All @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 465e0df

Please sign in to comment.