Skip to content

Commit f105b21

Browse files
committed
?(x, y)TAB completes methods accepting x, y
Closes #30052 xref #38704 xref #37993
1 parent a813a6e commit f105b21

File tree

6 files changed

+228
-38
lines changed

6 files changed

+228
-38
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ Standard library changes
5454

5555
#### REPL
5656

57+
* ` ?(x, y` followed by tab returns all methods that can be called
58+
with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.)
59+
`MyModule.?(x, y` limits the search to `MyModule`. Using SHIFT-TAB instead of TAB
60+
removes methods that have no constaints on their argument types.
5761

5862
#### SparseArrays
5963

stdlib/REPL/docs/src/index.md

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,27 @@ Users should refer to `LineEdit.jl` to discover the available actions on key inp
299299
In both the Julian and help modes of the REPL, one can enter the first few characters of a function
300300
or type and then press the tab key to get a list all matches:
301301

302+
```julia-repl
303+
julia> x[TAB]
304+
julia> xor
305+
```
306+
307+
In some cases it only completes part of the name, up to the next ambiguity:
308+
309+
```julia-repl
310+
julia> mapf[TAB]
311+
julia> mapfold
312+
```
313+
314+
If you hit tab again, then you get the list of things that might complete this:
315+
316+
```julia-repl
317+
julia> mapfold[TAB]
318+
mapfoldl mapfoldr
319+
```
320+
321+
Like other components of the REPL, the search is case-sensitive:
322+
302323
```julia-repl
303324
julia> stri[TAB]
304325
stride strides string strip
@@ -357,6 +378,46 @@ shell> /[TAB]
357378
.dockerinit bin/ dev/ home/ lib64/ mnt/ proc/ run/ srv/ tmp/ var/
358379
```
359380

381+
Dictionary keys can also be tab completed:
382+
383+
```julia-repl
384+
julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3)
385+
Dict{String,Int64} with 3 entries:
386+
"qwer2" => 2
387+
"asdf" => 3
388+
"qwer1" => 1
389+
390+
julia> foo["q[TAB]
391+
392+
"qwer1" "qwer2"
393+
julia> foo["qwer
394+
```
395+
396+
Tab completion can also help completing fields:
397+
398+
```julia-repl
399+
julia> x = 3 + 4im;
400+
401+
julia> julia> x.[TAB][TAB]
402+
im re
403+
404+
julia> import UUIDs
405+
406+
julia> UUIDs.uuid[TAB][TAB]
407+
uuid1 uuid4 uuid5 uuid_version
408+
```
409+
410+
Fields for output from functions can also be completed:
411+
412+
```julia-repl
413+
julia> split("","")[1].[TAB]
414+
lastindex offset string
415+
```
416+
417+
The completion of fields for output from functions uses type inference, and it can only suggest
418+
fields if the function is type stable.
419+
420+
360421
Tab completion can help with investigation of the available methods matching the input arguments:
361422

362423
```julia-repl
@@ -384,38 +445,31 @@ The completion of the methods uses type inference and can therefore see if the a
384445
even if the arguments are output from functions. The function needs to be type stable for the
385446
completion to be able to remove non-matching methods.
386447

387-
Tab completion can also help completing fields:
388-
389-
```julia-repl
390-
julia> import UUIDs
391-
392-
julia> UUIDs.uuid[TAB]
393-
uuid1 uuid4 uuid_version
394-
```
395-
396-
Fields for output from functions can also be completed:
448+
If you wonder which methods can be used with particular argument types, use `?` as the function name:
397449

398450
```julia-repl
399-
julia> split("","")[1].[TAB]
400-
lastindex offset string
401-
```
402-
403-
The completion of fields for output from functions uses type inference, and it can only suggest
404-
fields if the function is type stable.
405-
406-
Dictionary keys can also be tab completed:
451+
julia> InteractiveUtils.?("somefile")[TAB]
452+
apropos(string) in REPL at REPL/src/docview.jl:727
453+
clipboard(x) in InteractiveUtils at InteractiveUtils/src/clipboard.jl:60
454+
code_llvm(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:178
455+
code_native(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:199
456+
edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:195
457+
edit(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:223
458+
eval(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3
459+
include(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3
460+
less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:256
461+
less(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:264
462+
report_bug(kind) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:385
463+
separate_kwargs(args...; kwargs...) in InteractiveUtils at InteractiveUtils/src/macros.jl:7
464+
```
465+
466+
This listed all methods in the `InteractiveUtils` module that can be called on a string.
467+
If you use SHIFT-TAB instead of TAB, you exclude methods that have all arguments typed as `Any`:
407468

408469
```julia-repl
409-
julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3)
410-
Dict{String,Int64} with 3 entries:
411-
"qwer2" => 2
412-
"asdf" => 3
413-
"qwer1" => 1
414-
415-
julia> foo["q[TAB]
416-
417-
"qwer1" "qwer2"
418-
julia> foo["qwer
470+
julia> InteractiveUtils.?("hi")[SHIFT-TAB]
471+
edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:195
472+
less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:256
419473
```
420474

421475
## Customizing Colors

stdlib/REPL/src/LineEdit.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,10 @@ mode(s::PromptState) = s.p # ::Prompt
18551855
mode(s::SearchState) = @assert false
18561856
mode(s::PrefixSearchState) = s.histprompt.parent_prompt # ::Prompt
18571857

1858+
setmodifier!(s::MIState, val::Symbol) = setmodifier!(mode(s), val)
1859+
setmodifier!(p::Prompt, val::Symbol) = setmodifier!(p.complete, val)
1860+
setmodifier!(c, val::Symbol) = nothing
1861+
18581862
# Search Mode completions
18591863
function complete_line(s::SearchState, repeats)
18601864
completions, partial, should_complete = complete_line(s.histprompt.complete, s)
@@ -2122,6 +2126,11 @@ function edit_tab(s::MIState, jump_spaces::Bool=false, delete_trailing::Bool=jum
21222126
return refresh_line(s)
21232127
end
21242128

2129+
function shift_tab_completion(s::MIState)
2130+
setmodifier!(s, :shift)
2131+
return complete_line(s)
2132+
end
2133+
21252134
# return true iff the content of the buffer is modified
21262135
# return false when only the position changed
21272136
function edit_insert_tab(buf::IOBuffer, jump_spaces::Bool=false, delete_trailing::Bool=jump_spaces)
@@ -2157,6 +2166,8 @@ const default_keymap =
21572166
AnyDict(
21582167
# Tab
21592168
'\t' => (s::MIState,o...)->edit_tab(s, true),
2169+
# Shift-tab
2170+
"\e[Z" => (s::MIState,o...)->shift_tab_completion(s),
21602171
# Enter
21612172
'\r' => (s::MIState,o...)->begin
21622173
if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1)

stdlib/REPL/src/REPL.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import ..LineEdit:
5656
history_last,
5757
history_search,
5858
accept_result,
59+
setmodifier!,
5960
terminal,
6061
MIState,
6162
PromptState,
@@ -427,16 +428,30 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
427428
false, false, false, envcolors
428429
)
429430

430-
mutable struct REPLCompletionProvider <: CompletionProvider end
431+
mutable struct REPLCompletionProvider <: CompletionProvider
432+
modifier::Symbol
433+
end
434+
REPLCompletionProvider() = REPLCompletionProvider(:none)
431435
mutable struct ShellCompletionProvider <: CompletionProvider end
432436
struct LatexCompletions <: CompletionProvider end
433437

438+
setmodifier!(c::REPLCompletionProvider, val::Symbol) = c.modifier = val
439+
434440
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
435441

436442
function complete_line(c::REPLCompletionProvider, s::PromptState)
437443
partial = beforecursor(s.input_buffer)
438444
full = LineEdit.input_string(s)
439445
ret, range, should_complete = completions(full, lastindex(partial))
446+
if c.modifier === :shift
447+
c.modifier = :none
448+
# Filter out methods where all arguments are `Any`
449+
filter!(ret) do c
450+
isa(c, REPLCompletions.MethodCompletion) || return true
451+
sig = Base.unwrap_unionall(c.method.sig)::DataType
452+
return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
453+
end
454+
end
440455
return unique!(map(completion_text, ret)), partial[range], should_complete
441456
end
442457

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -464,17 +464,59 @@ function get_type(sym, fn::Module)
464464
return found ? Core.Typeof(val) : Any, found
465465
end
466466

467+
function get_type(T, found::Bool, default_any::Bool)
468+
return found ? T :
469+
default_any ? Any : throw(ArgumentError("argument not found"))
470+
end
471+
467472
# Method completion on function call expression that look like :(max(1))
468473
function complete_methods(ex_org::Expr, context_module::Module=Main)
469474
func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool}
470475
!found && return Completion[]
471476

472-
funargs = ex_org.args[2:end]
473-
# handle broadcasting, but only handle number of arguments instead of
474-
# argument types
477+
args_ex, kwargs_ex = complete_methods_args(ex_org.args[2:end], ex_org, context_module, true, true)
478+
479+
out = Completion[]
480+
complete_methods!(out, func, args_ex, kwargs_ex)
481+
return out
482+
end
483+
484+
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool)
485+
out = Completion[]
486+
args_ex, kwargs_ex = try
487+
complete_methods_args(ex_org.args[2:end], ex_org, context_module, false, false)
488+
catch
489+
return out
490+
end
491+
492+
for name in names(callee_module; all=true)
493+
if isdefined(callee_module, name)
494+
func = getfield(callee_module, name)
495+
if isa(func, Base.Callable) && func !== Vararg
496+
complete_methods!(out, func, args_ex, kwargs_ex, moreargs)
497+
elseif callee_module === Main::Module && isa(func, Module)
498+
callee_module2 = func
499+
for name in names(callee_module2)
500+
if isdefined(callee_module2, name)
501+
func = getfield(callee_module, name)
502+
if isa(func, Base.Callable) && func !== Vararg
503+
complete_methods!(out, func, args_ex, kwargs_ex, moreargs)
504+
end
505+
end
506+
end
507+
end
508+
end
509+
end
510+
511+
return out
512+
end
513+
514+
function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
475515
args_ex = Any[]
476516
kwargs_ex = Pair{Symbol,Any}[]
477-
if ex_org.head === :. && ex_org.args[2] isa Expr
517+
if allow_broadcasting && ex_org.head === :. && ex_org.args[2] isa Expr
518+
# handle broadcasting, but only handle number of arguments instead of
519+
# argument types
478520
for _ in (ex_org.args[2]::Expr).args
479521
push!(args_ex, Any)
480522
end
@@ -483,18 +525,20 @@ function complete_methods(ex_org::Expr, context_module::Module=Main)
483525
if isexpr(ex, :parameters)
484526
for x in ex.args
485527
n, v = isexpr(x, :kw) ? (x.args...,) : (x, x)
486-
push!(kwargs_ex, n => first(get_type(v, context_module)))
528+
push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any))
487529
end
488530
elseif isexpr(ex, :kw)
489531
n, v = (ex.args...,)
490-
push!(kwargs_ex, n => first(get_type(v, context_module)))
532+
push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any))
491533
else
492-
push!(args_ex, first(get_type(ex, context_module)))
534+
push!(args_ex, get_type(get_type(ex, context_module)..., default_any))
493535
end
494536
end
495537
end
538+
return args_ex, kwargs_ex
539+
end
496540

497-
out = Completion[]
541+
function complete_methods!(out::Vector{Completion}, @nospecialize(func::Base.Callable), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true)
498542
ml = methods(func)
499543
# Input types and number of arguments
500544
if isempty(kwargs_ex)
@@ -511,6 +555,9 @@ function complete_methods(ex_org::Expr, context_module::Module=Main)
511555
ml = methods(kwfunc)
512556
func = kwfunc
513557
end
558+
if !moreargs
559+
na = typemax(Int)
560+
end
514561

515562
for (method::Method, orig_method) in zip(ml, orig_ml)
516563
ms = method.sig
@@ -520,7 +567,6 @@ function complete_methods(ex_org::Expr, context_module::Module=Main)
520567
push!(out, MethodCompletion(func, t_in, method, orig_method))
521568
end
522569
end
523-
return out
524570
end
525571

526572
include("latex_symbols.jl")
@@ -638,6 +684,36 @@ function completions(string::String, pos::Int, context_module::Module=Main)
638684
partial = string[1:pos]
639685
inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
640686

687+
# _(x, y)TAB lists methods you can call with these objects
688+
# _(x, y TAB lists methods that take these objects as the first two arguments
689+
# MyModule._(x, y)TAB restricts the search to names in MyModule
690+
rexm = match(r"(\w+\.|)\?\((.*)$", partial)
691+
if rexm !== nothing
692+
# Get the module scope
693+
if isempty(rexm.captures[1])
694+
callee_module = context_module
695+
else
696+
modname = Symbol(rexm.captures[1][1:end-1])
697+
if isdefined(context_module, modname)
698+
callee_module = getfield(context_module, modname)
699+
if !isa(callee_module, Module)
700+
callee_module = context_module
701+
end
702+
else
703+
callee_module = context_module
704+
end
705+
end
706+
moreargs = !endswith(rexm.captures[2], ')')
707+
callstr = "_(" * rexm.captures[2]
708+
if moreargs
709+
callstr *= ')'
710+
end
711+
ex_org = Meta.parse(callstr, raise=false, depwarn=false)
712+
if isa(ex_org, Expr)
713+
return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
714+
end
715+
end
716+
641717
# if completing a key in a Dict
642718
identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
643719
if identifier !== nothing

0 commit comments

Comments
 (0)