From b215082f80dff6aa1ccf1ba4784ca524a724827c Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 25 Jul 2021 11:45:38 -0500 Subject: [PATCH 1/5] ?(x, y)TAB completes methods accepting x, y Closes #30052 xref #38704 xref #37993 --- NEWS.md | 5 ++ stdlib/REPL/docs/src/index.md | 110 +++++++++++++++++++++------- stdlib/REPL/src/LineEdit.jl | 11 +++ stdlib/REPL/src/REPL.jl | 17 ++++- stdlib/REPL/src/REPLCompletions.jl | 94 +++++++++++++++++++++--- stdlib/REPL/test/replcompletions.jl | 30 ++++++++ 6 files changed, 229 insertions(+), 38 deletions(-) diff --git a/NEWS.md b/NEWS.md index 445f3b84ad178..ab719715c02a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -57,6 +57,11 @@ Standard library changes #### REPL +* ` ?(x, y` followed by tab returns all methods that can be called + with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.) + `MyModule.?(x, y` limits the search to `MyModule`. Using SHIFT-TAB instead of TAB + removes methods that have no constaints on their argument types. + #### SparseArrays #### Dates diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index 168d3e963b589..fcf684abdbce8 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -307,6 +307,27 @@ Users should refer to `LineEdit.jl` to discover the available actions on key inp In both the Julian and help modes of the REPL, one can enter the first few characters of a function or type and then press the tab key to get a list all matches: +```julia-repl +julia> x[TAB] +julia> xor +``` + +In some cases it only completes part of the name, up to the next ambiguity: + +```julia-repl +julia> mapf[TAB] +julia> mapfold +``` + +If you hit tab again, then you get the list of things that might complete this: + +```julia-repl +julia> mapfold[TAB] +mapfoldl mapfoldr +``` + +Like other components of the REPL, the search is case-sensitive: + ```julia-repl julia> stri[TAB] stride strides string strip @@ -365,6 +386,46 @@ shell> /[TAB] .dockerinit bin/ dev/ home/ lib64/ mnt/ proc/ run/ srv/ tmp/ var/ ``` +Dictionary keys can also be tab completed: + +```julia-repl +julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3) +Dict{String,Int64} with 3 entries: + "qwer2" => 2 + "asdf" => 3 + "qwer1" => 1 + +julia> foo["q[TAB] + +"qwer1" "qwer2" +julia> foo["qwer +``` + +Tab completion can also help completing fields: + +```julia-repl +julia> x = 3 + 4im; + +julia> julia> x.[TAB][TAB] +im re + +julia> import UUIDs + +julia> UUIDs.uuid[TAB][TAB] +uuid1 uuid4 uuid5 uuid_version +``` + +Fields for output from functions can also be completed: + +```julia-repl +julia> split("","")[1].[TAB] +lastindex offset string +``` + +The completion of fields for output from functions uses type inference, and it can only suggest +fields if the function is type stable. + + Tab completion can help with investigation of the available methods matching the input arguments: ```julia-repl @@ -392,38 +453,31 @@ The completion of the methods uses type inference and can therefore see if the a even if the arguments are output from functions. The function needs to be type stable for the completion to be able to remove non-matching methods. -Tab completion can also help completing fields: - -```julia-repl -julia> import UUIDs - -julia> UUIDs.uuid[TAB] -uuid1 uuid4 uuid_version -``` - -Fields for output from functions can also be completed: +If you wonder which methods can be used with particular argument types, use `?` as the function name: ```julia-repl -julia> split("","")[1].[TAB] -lastindex offset string -``` - -The completion of fields for output from functions uses type inference, and it can only suggest -fields if the function is type stable. - -Dictionary keys can also be tab completed: +julia> InteractiveUtils.?("somefile")[TAB] +apropos(string) in REPL at REPL/src/docview.jl:727 +clipboard(x) in InteractiveUtils at InteractiveUtils/src/clipboard.jl:60 +code_llvm(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:178 +code_native(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:199 +edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:195 +edit(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:223 +eval(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3 +include(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3 +less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:256 +less(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:264 +report_bug(kind) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:385 +separate_kwargs(args...; kwargs...) in InteractiveUtils at InteractiveUtils/src/macros.jl:7 +``` + +This listed all methods in the `InteractiveUtils` module that can be called on a string. +If you use SHIFT-TAB instead of TAB, you exclude methods that have all arguments typed as `Any`: ```julia-repl -julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3) -Dict{String,Int64} with 3 entries: - "qwer2" => 2 - "asdf" => 3 - "qwer1" => 1 - -julia> foo["q[TAB] - -"qwer1" "qwer2" -julia> foo["qwer +julia> InteractiveUtils.?("hi")[SHIFT-TAB] +edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:195 +less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:256 ``` ## Customizing Colors diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index 9a6160c960fe3..531fbd4a8566c 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -1907,6 +1907,10 @@ mode(s::PromptState) = s.p # ::Prompt mode(s::SearchState) = @assert false mode(s::PrefixSearchState) = s.histprompt.parent_prompt # ::Prompt +setmodifier!(s::MIState, val::Symbol) = setmodifier!(mode(s), val) +setmodifier!(p::Prompt, val::Symbol) = setmodifier!(p.complete, val) +setmodifier!(c, val::Symbol) = nothing + # Search Mode completions function complete_line(s::SearchState, repeats) completions, partial, should_complete = complete_line(s.histprompt.complete, s) @@ -2174,6 +2178,11 @@ function edit_tab(s::MIState, jump_spaces::Bool=false, delete_trailing::Bool=jum return refresh_line(s) end +function shift_tab_completion(s::MIState) + setmodifier!(s, :shift) + return complete_line(s) +end + # return true iff the content of the buffer is modified # return false when only the position changed function edit_insert_tab(buf::IOBuffer, jump_spaces::Bool=false, delete_trailing::Bool=jump_spaces) @@ -2209,6 +2218,8 @@ const default_keymap = AnyDict( # Tab '\t' => (s::MIState,o...)->edit_tab(s, true), + # Shift-tab + "\e[Z" => (s::MIState,o...)->shift_tab_completion(s), # Enter '\r' => (s::MIState,o...)->begin if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index a661ffa218e97..6478b03a98273 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -55,6 +55,7 @@ import ..LineEdit: history_last, history_search, accept_result, + setmodifier!, terminal, MIState, PromptState, @@ -470,16 +471,30 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) = false, false, false, envcolors ) -mutable struct REPLCompletionProvider <: CompletionProvider end +mutable struct REPLCompletionProvider <: CompletionProvider + modifier::Symbol +end +REPLCompletionProvider() = REPLCompletionProvider(:none) mutable struct ShellCompletionProvider <: CompletionProvider end struct LatexCompletions <: CompletionProvider end +setmodifier!(c::REPLCompletionProvider, val::Symbol) = c.modifier = val + beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1]) function complete_line(c::REPLCompletionProvider, s::PromptState) partial = beforecursor(s.input_buffer) full = LineEdit.input_string(s) ret, range, should_complete = completions(full, lastindex(partial)) + if c.modifier === :shift + c.modifier = :none + # Filter out methods where all arguments are `Any` + filter!(ret) do c + isa(c, REPLCompletions.MethodCompletion) || return true + sig = Base.unwrap_unionall(c.method.sig)::DataType + return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end]) + end + end return unique!(map(completion_text, ret)), partial[range], should_complete end diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 44b3e6a3a4158..1a945f482ccec 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -478,17 +478,59 @@ function get_type(sym, fn::Module) return found ? Core.Typeof(val) : Any, found end +function get_type(T, found::Bool, default_any::Bool) + return found ? T : + default_any ? Any : throw(ArgumentError("argument not found")) +end + # Method completion on function call expression that look like :(max(1)) function complete_methods(ex_org::Expr, context_module::Module=Main) func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool} !found && return Completion[] - funargs = ex_org.args[2:end] - # handle broadcasting, but only handle number of arguments instead of - # argument types + args_ex, kwargs_ex = complete_methods_args(ex_org.args[2:end], ex_org, context_module, true, true) + + out = Completion[] + complete_methods!(out, func, args_ex, kwargs_ex) + return out +end + +function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool) + out = Completion[] + args_ex, kwargs_ex = try + complete_methods_args(ex_org.args[2:end], ex_org, context_module, false, false) + catch + return out + end + + for name in names(callee_module; all=true) + if isdefined(callee_module, name) + func = getfield(callee_module, name) + if isa(func, Base.Callable) && func !== Vararg + complete_methods!(out, func, args_ex, kwargs_ex, moreargs) + elseif callee_module === Main::Module && isa(func, Module) + callee_module2 = func + for name in names(callee_module2) + if isdefined(callee_module2, name) + func = getfield(callee_module, name) + if isa(func, Base.Callable) && func !== Vararg + complete_methods!(out, func, args_ex, kwargs_ex, moreargs) + end + end + end + end + end + end + + return out +end + +function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool) args_ex = Any[] kwargs_ex = Pair{Symbol,Any}[] - if ex_org.head === :. && ex_org.args[2] isa Expr + if allow_broadcasting && ex_org.head === :. && ex_org.args[2] isa Expr + # handle broadcasting, but only handle number of arguments instead of + # argument types for _ in (ex_org.args[2]::Expr).args push!(args_ex, Any) end @@ -497,18 +539,20 @@ function complete_methods(ex_org::Expr, context_module::Module=Main) if isexpr(ex, :parameters) for x in ex.args n, v = isexpr(x, :kw) ? (x.args...,) : (x, x) - push!(kwargs_ex, n => first(get_type(v, context_module))) + push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any)) end elseif isexpr(ex, :kw) n, v = (ex.args...,) - push!(kwargs_ex, n => first(get_type(v, context_module))) + push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any)) else - push!(args_ex, first(get_type(ex, context_module))) + push!(args_ex, get_type(get_type(ex, context_module)..., default_any)) end end end + return args_ex, kwargs_ex +end - out = Completion[] +function complete_methods!(out::Vector{Completion}, @nospecialize(func::Base.Callable), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true) ml = methods(func) # Input types and number of arguments if isempty(kwargs_ex) @@ -525,6 +569,9 @@ function complete_methods(ex_org::Expr, context_module::Module=Main) ml = methods(kwfunc) func = kwfunc end + if !moreargs + na = typemax(Int) + end for (method::Method, orig_method) in zip(ml, orig_ml) ms = method.sig @@ -534,7 +581,6 @@ function complete_methods(ex_org::Expr, context_module::Module=Main) push!(out, MethodCompletion(func, t_in, method, orig_method)) end end - return out end include("latex_symbols.jl") @@ -652,6 +698,36 @@ function completions(string::String, pos::Int, context_module::Module=Main) partial = string[1:pos] inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false)) + # _(x, y)TAB lists methods you can call with these objects + # _(x, y TAB lists methods that take these objects as the first two arguments + # MyModule._(x, y)TAB restricts the search to names in MyModule + rexm = match(r"(\w+\.|)\?\((.*)$", partial) + if rexm !== nothing + # Get the module scope + if isempty(rexm.captures[1]) + callee_module = context_module + else + modname = Symbol(rexm.captures[1][1:end-1]) + if isdefined(context_module, modname) + callee_module = getfield(context_module, modname) + if !isa(callee_module, Module) + callee_module = context_module + end + else + callee_module = context_module + end + end + moreargs = !endswith(rexm.captures[2], ')') + callstr = "_(" * rexm.captures[2] + if moreargs + callstr *= ')' + end + ex_org = Meta.parse(callstr, raise=false, depwarn=false) + if isa(ex_org, Expr) + return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs), (0:length(rexm.captures[1])+1) .+ rexm.offset, false + end + end + # if completing a key in a Dict identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module) if identifier !== nothing diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 545e81a27968d..1bc97786c7d38 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -64,6 +64,8 @@ let ex = quote test6()=[a, a] test7() = rand(Bool) ? 1 : 1.0 test8() = Any[1][1] + test9(x::Char) = pass + test9(x::Char, i::Int) = pass kwtest(; x=1, y=2, w...) = pass kwtest2(a; x=1, y=2, w...) = pass @@ -516,6 +518,34 @@ for s in ("CompletionFoo.kwtest2(1; x=1,", @test occursin("a; x, y, w...", c[1]) end +################################################################# + +# method completion with `?` (arbitrary method with given argument types) +let s = "CompletionFoo.?([1,2,3], 2.0)" + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test(x::AbstractArray{T, N} where N, y) where T<:Real", str), c) + @test any(str->occursin("test(args...)", str), c) + @test !any(str->occursin("test3(x::AbstractArray{Int", str), c) + @test !any(str->occursin("test4", str), c) +end + +let s = "CompletionFoo.?('c')" + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test9(x::Char)", str), c) + @test !any(str->occursin("test9(x::Char, i::Int", str), c) +end + +let s = "CompletionFoo.?('c'" + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test9(x::Char)", str), c) + @test any(str->occursin("test9(x::Char, i::Int", str), c) +end + +################################################################# + # Test of inference based getfield completion let s = "(1+2im)." c,r = test_complete(s) From 0aab0dc8ee49a893a14f6ea8f7f555c0a6816c7a Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 25 Jul 2021 11:43:20 -0500 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Jameson Nash --- stdlib/REPL/src/REPL.jl | 2 +- stdlib/REPL/src/REPLCompletions.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 6478b03a98273..8f67384d698d1 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -472,7 +472,7 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) = ) mutable struct REPLCompletionProvider <: CompletionProvider - modifier::Symbol + shift::Bool end REPLCompletionProvider() = REPLCompletionProvider(:none) mutable struct ShellCompletionProvider <: CompletionProvider end diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 1a945f482ccec..dd765f0a2da72 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -504,16 +504,16 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul end for name in names(callee_module; all=true) - if isdefined(callee_module, name) + if !isdeprecated(callee_module, name) && isdefined(callee_module, name) func = getfield(callee_module, name) - if isa(func, Base.Callable) && func !== Vararg + if !isa(func, Module) complete_methods!(out, func, args_ex, kwargs_ex, moreargs) elseif callee_module === Main::Module && isa(func, Module) callee_module2 = func for name in names(callee_module2) if isdefined(callee_module2, name) func = getfield(callee_module, name) - if isa(func, Base.Callable) && func !== Vararg + if isa(func, Base.Callable) complete_methods!(out, func, args_ex, kwargs_ex, moreargs) end end From 594c9f8dc0f189f903c88e3afd0d5b301ddb4372 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 25 Jul 2021 13:37:01 -0500 Subject: [PATCH 3/5] Support future generalization with Modifiers type --- stdlib/REPL/src/LineEdit.jl | 13 +++++++++---- stdlib/REPL/src/REPL.jl | 12 ++++++------ stdlib/REPL/src/REPLCompletions.jl | 10 +++++----- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index 531fbd4a8566c..89f9a4cb99208 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -106,6 +106,11 @@ mutable struct PromptState <: ModeState refresh_wait::Union{Timer,Nothing} end +struct Modifiers + shift::Bool +end +Modifiers() = Modifiers(false) + options(s::PromptState) = if isdefined(s.p, :repl) && isdefined(s.p.repl, :options) # we can't test isa(s.p.repl, LineEditREPL) as LineEditREPL is defined @@ -1907,9 +1912,9 @@ mode(s::PromptState) = s.p # ::Prompt mode(s::SearchState) = @assert false mode(s::PrefixSearchState) = s.histprompt.parent_prompt # ::Prompt -setmodifier!(s::MIState, val::Symbol) = setmodifier!(mode(s), val) -setmodifier!(p::Prompt, val::Symbol) = setmodifier!(p.complete, val) -setmodifier!(c, val::Symbol) = nothing +setmodifiers!(s::MIState, m::Modifiers) = setmodifiers!(mode(s), m) +setmodifiers!(p::Prompt, m::Modifiers) = setmodifiers!(p.complete, m) +setmodifiers!(c) = nothing # Search Mode completions function complete_line(s::SearchState, repeats) @@ -2179,7 +2184,7 @@ function edit_tab(s::MIState, jump_spaces::Bool=false, delete_trailing::Bool=jum end function shift_tab_completion(s::MIState) - setmodifier!(s, :shift) + setmodifiers!(s, Modifiers(true)) return complete_line(s) end diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 8f67384d698d1..a815678b7ba52 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -55,7 +55,7 @@ import ..LineEdit: history_last, history_search, accept_result, - setmodifier!, + setmodifiers!, terminal, MIState, PromptState, @@ -472,13 +472,13 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) = ) mutable struct REPLCompletionProvider <: CompletionProvider - shift::Bool + modifiers::LineEdit.Modifiers end -REPLCompletionProvider() = REPLCompletionProvider(:none) +REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers()) mutable struct ShellCompletionProvider <: CompletionProvider end struct LatexCompletions <: CompletionProvider end -setmodifier!(c::REPLCompletionProvider, val::Symbol) = c.modifier = val +setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1]) @@ -486,8 +486,7 @@ function complete_line(c::REPLCompletionProvider, s::PromptState) partial = beforecursor(s.input_buffer) full = LineEdit.input_string(s) ret, range, should_complete = completions(full, lastindex(partial)) - if c.modifier === :shift - c.modifier = :none + if !c.modifiers.shift # Filter out methods where all arguments are `Any` filter!(ret) do c isa(c, REPLCompletions.MethodCompletion) || return true @@ -495,6 +494,7 @@ function complete_line(c::REPLCompletionProvider, s::PromptState) return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end]) end end + c.modifiers = LineEdit.Modifiers() return unique!(map(completion_text, ret)), partial[range], should_complete end diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index dd765f0a2da72..d1d4daa413111 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -504,7 +504,7 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul end for name in names(callee_module; all=true) - if !isdeprecated(callee_module, name) && isdefined(callee_module, name) + if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) func = getfield(callee_module, name) if !isa(func, Module) complete_methods!(out, func, args_ex, kwargs_ex, moreargs) @@ -513,7 +513,7 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul for name in names(callee_module2) if isdefined(callee_module2, name) func = getfield(callee_module, name) - if isa(func, Base.Callable) + if !isa(func, Module) complete_methods!(out, func, args_ex, kwargs_ex, moreargs) end end @@ -552,7 +552,7 @@ function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_modul return args_ex, kwargs_ex end -function complete_methods!(out::Vector{Completion}, @nospecialize(func::Base.Callable), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true) +function complete_methods!(out::Vector{Completion}, @nospecialize(func), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true) ml = methods(func) # Input types and number of arguments if isempty(kwargs_ex) @@ -698,8 +698,8 @@ function completions(string::String, pos::Int, context_module::Module=Main) partial = string[1:pos] inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false)) - # _(x, y)TAB lists methods you can call with these objects - # _(x, y TAB lists methods that take these objects as the first two arguments + # ?(x, y)TAB lists methods you can call with these objects + # ?(x, y TAB lists methods that take these objects as the first two arguments # MyModule._(x, y)TAB restricts the search to names in MyModule rexm = match(r"(\w+\.|)\?\((.*)$", partial) if rexm !== nothing From ed2f00e6078be79ddf1386455eabf9a1d38589fb Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 25 Jul 2021 14:57:04 -0500 Subject: [PATCH 4/5] Fix docs & tests --- stdlib/REPL/docs/src/index.md | 53 +++++++++++++++++++++-------- stdlib/REPL/test/replcompletions.jl | 2 +- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index fcf684abdbce8..ab4c4df19df5b 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -453,31 +453,54 @@ The completion of the methods uses type inference and can therefore see if the a even if the arguments are output from functions. The function needs to be type stable for the completion to be able to remove non-matching methods. -If you wonder which methods can be used with particular argument types, use `?` as the function name: +If you wonder which methods can be used with particular argument types, use `?` as the function name. +This shows an example of looking for functions in InteractiveUtils that accept a single string: ```julia-repl julia> InteractiveUtils.?("somefile")[TAB] -apropos(string) in REPL at REPL/src/docview.jl:727 -clipboard(x) in InteractiveUtils at InteractiveUtils/src/clipboard.jl:60 -code_llvm(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:178 -code_native(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:199 -edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:195 -edit(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:223 +edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:197 +less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:266 +``` + +This listed methods in the `InteractiveUtils` module that can be called on a string. +By default, this excludes methods where all arguments are typed as `Any`, +but you can see those too by holding down SHIFT-TAB instead of TAB: + +```julia-repl +julia> InteractiveUtils.?("somefile")[SHIFT-TAB] +apropos(string) in REPL at REPL/src/docview.jl:796 +clipboard(x) in InteractiveUtils at InteractiveUtils/src/clipboard.jl:64 +code_llvm(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:221 +code_native(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:243 +edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:197 +edit(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:225 eval(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3 include(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3 -less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:256 -less(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:264 -report_bug(kind) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:385 +less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:266 +less(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:274 +report_bug(kind) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:391 separate_kwargs(args...; kwargs...) in InteractiveUtils at InteractiveUtils/src/macros.jl:7 ``` -This listed all methods in the `InteractiveUtils` module that can be called on a string. -If you use SHIFT-TAB instead of TAB, you exclude methods that have all arguments typed as `Any`: +You can also use ` ?("somefile")[TAB]` and look across all modules, but the method lists can be long. + +By omitting the closing parenthesis, you can include functions that might require additional arguments: ```julia-repl -julia> InteractiveUtils.?("hi")[SHIFT-TAB] -edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:195 -less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:256 +julia> using Mmap + +help?> Mmap.?("file",[TAB] +Mmap.Anonymous(name::String, readonly::Bool, create::Bool) in Mmap at Mmap/src/Mmap.jl:16 +mmap(file::AbstractString) in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}) where T<:Array in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}) where {T<:Array, N} in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}, offset::Integer; grow, shared) where {T<:Array, N} in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}, len::Integer) where T<:Array in Mmap at Mmap/src/Mmap.jl:251 +mmap(file::AbstractString, ::Type{T}, len::Integer, offset::Integer; grow, shared) where T<:Array in Mmap at Mmap/src/Mmap.jl:251 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}) where {T<:BitArray, N} in Mmap at Mmap/src/Mmap.jl:316 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}, offset::Integer; grow, shared) where {T<:BitArray, N} in Mmap at Mmap/src/Mmap.jl:316 +mmap(file::AbstractString, ::Type{T}, len::Integer) where T<:BitArray in Mmap at Mmap/src/Mmap.jl:322 +mmap(file::AbstractString, ::Type{T}, len::Integer, offset::Integer; grow, shared) where T<:BitArray in Mmap at Mmap/src/Mmap.jl:322 ``` ## Customizing Colors diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 1bc97786c7d38..14f6de445c083 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -524,7 +524,7 @@ end let s = "CompletionFoo.?([1,2,3], 2.0)" c, r, res = test_complete(s) @test !res - @test any(str->occursin("test(x::AbstractArray{T, N} where N, y) where T<:Real", str), c) + @test any(str->occursin("test(x::AbstractArray{T}, y) where T<:Real", str), c) @test any(str->occursin("test(args...)", str), c) @test !any(str->occursin("test3(x::AbstractArray{Int", str), c) @test !any(str->occursin("test4", str), c) From c3c9540ff87f49347009bb14430606102b308460 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 25 Jul 2021 15:12:41 -0500 Subject: [PATCH 5/5] Fix NEWS and comment --- NEWS.md | 7 ++++--- stdlib/REPL/src/REPLCompletions.jl | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index ab719715c02a3..f1f88a84d4cef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -57,10 +57,11 @@ Standard library changes #### REPL -* ` ?(x, y` followed by tab returns all methods that can be called +* ` ?(x, y` followed by TAB displays all methods that can be called with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.) - `MyModule.?(x, y` limits the search to `MyModule`. Using SHIFT-TAB instead of TAB - removes methods that have no constaints on their argument types. + `MyModule.?(x, y` limits the search to `MyModule`. TAB requires that at least one + argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB + to allow any compatible methods. #### SparseArrays diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index d1d4daa413111..152f67bdb34e5 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -700,7 +700,7 @@ function completions(string::String, pos::Int, context_module::Module=Main) # ?(x, y)TAB lists methods you can call with these objects # ?(x, y TAB lists methods that take these objects as the first two arguments - # MyModule._(x, y)TAB restricts the search to names in MyModule + # MyModule.?(x, y)TAB restricts the search to names in MyModule rexm = match(r"(\w+\.|)\?\((.*)$", partial) if rexm !== nothing # Get the module scope