• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37446

pending completion
#37446

push

local

web-flow
do lazy reallocation after take!(iobuffer) (#48676)

closes #27741. closes #48651

30 of 30 new or added lines in 3 files covered. (100.0%)

69389 of 80609 relevant lines covered (86.08%)

34717409.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.54
/stdlib/REPL/src/REPLCompletions.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
module REPLCompletions
4

5
export completions, shell_completions, bslash_completions, completion_text
6

7
using Base.Meta
8
using Base: propertynames, something
9

10
abstract type Completion end
11

12
struct TextCompletion <: Completion
13
    text::String
×
14
end
15

16
struct KeywordCompletion <: Completion
17
    keyword::String
18
end
19

20
struct PathCompletion <: Completion
21
    path::String
22
end
23

24
struct ModuleCompletion <: Completion
25
    parent::Module
26
    mod::String
27
end
28

29
struct PackageCompletion <: Completion
30
    package::String
31
end
32

33
struct PropertyCompletion <: Completion
34
    value
35
    property::Symbol
36
end
37

38
struct FieldCompletion <: Completion
39
    typ::DataType
40
    field::Symbol
41
end
42

43
struct MethodCompletion <: Completion
44
    tt # may be used by an external consumer to infer return type, etc.
45
    method::Method
46
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
1✔
47
end
48

49
struct BslashCompletion <: Completion
50
    bslash::String
51
end
52

53
struct ShellCompletion <: Completion
54
    text::String
55
end
56

57
struct DictCompletion <: Completion
58
    dict::AbstractDict
59
    key::String
60
end
61

62
struct KeywordArgumentCompletion <: Completion
63
    kwarg::String
64
end
65

66
# interface definition
67
function Base.getproperty(c::Completion, name::Symbol)
1✔
68
    if name === :text
1✔
69
        return getfield(c, :text)::String
×
70
    elseif name === :keyword
1✔
71
        return getfield(c, :keyword)::String
×
72
    elseif name === :path
1✔
73
        return getfield(c, :path)::String
×
74
    elseif name === :parent
1✔
75
        return getfield(c, :parent)::Module
×
76
    elseif name === :mod
1✔
77
        return getfield(c, :mod)::String
×
78
    elseif name === :package
1✔
79
        return getfield(c, :package)::String
×
80
    elseif name === :property
1✔
81
        return getfield(c, :property)::Symbol
×
82
    elseif name === :field
1✔
83
        return getfield(c, :field)::Symbol
×
84
    elseif name === :method
×
85
        return getfield(c, :method)::Method
1✔
86
    elseif name === :bslash
×
87
        return getfield(c, :bslash)::String
×
88
    elseif name === :text
×
89
        return getfield(c, :text)::String
×
90
    elseif name === :key
×
91
        return getfield(c, :key)::String
×
92
    elseif name === :kwarg
×
93
        return getfield(c, :kwarg)::String
×
94
    end
95
    return getfield(c, name)
×
96
end
97

98
_completion_text(c::TextCompletion) = c.text
×
99
_completion_text(c::KeywordCompletion) = c.keyword
×
100
_completion_text(c::PathCompletion) = c.path
×
101
_completion_text(c::ModuleCompletion) = c.mod
×
102
_completion_text(c::PackageCompletion) = c.package
×
103
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
×
104
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
×
105
_completion_text(c::MethodCompletion) = repr(c.method)
1✔
106
_completion_text(c::BslashCompletion) = c.bslash
×
107
_completion_text(c::ShellCompletion) = c.text
×
108
_completion_text(c::DictCompletion) = c.key
×
109
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
×
110

111
completion_text(c) = _completion_text(c)::String
1✔
112

113
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
114

115
function completes_global(x, name)
×
116
    return startswith(x, name) && !('#' in x)
×
117
end
118

119
function appendmacro!(syms, macros, needle, endchar)
×
120
    for s in macros
×
121
        if endswith(s, needle)
×
122
            from = nextind(s, firstindex(s))
×
123
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
×
124
            push!(syms, s[from:to]*endchar)
×
125
        end
126
    end
×
127
end
128

129
function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, all::Bool = false, imported::Bool = false)
×
130
    ssyms = names(mod, all = all, imported = imported)
×
131
    filter!(ffunc, ssyms)
×
132
    syms = String[string(s) for s in ssyms]
×
133
    macros =  filter(x -> startswith(x, "@" * name), syms)
×
134
    appendmacro!(syms, macros, "_str", "\"")
×
135
    appendmacro!(syms, macros, "_cmd", "`")
×
136
    filter!(x->completes_global(x, name), syms)
×
137
    return [ModuleCompletion(mod, sym) for sym in syms]
×
138
end
139

140
# REPL Symbol Completions
141
function complete_symbol(sym::String, @nospecialize(ffunc), context_module::Module=Main)
×
142
    mod = context_module
×
143
    name = sym
×
144

145
    lookup_module = true
×
146
    t = Union{}
×
147
    val = nothing
×
148
    if something(findlast(in(non_identifier_chars), sym), 0) < something(findlast(isequal('.'), sym), 0)
×
149
        # Find module
150
        lookup_name, name = rsplit(sym, ".", limit=2)
×
151

152
        ex = Meta.parse(lookup_name, raise=false, depwarn=false)
×
153

154
        b, found = get_value(ex, context_module)
×
155
        if found
×
156
            val = b
×
157
            if isa(b, Module)
×
158
                mod = b
×
159
                lookup_module = true
×
160
            else
161
                lookup_module = false
×
162
                t = typeof(b)
×
163
            end
164
        else # If the value is not found using get_value, the expression contain an advanced expression
165
            lookup_module = false
×
166
            t, found = get_type(ex, context_module)
×
167
        end
168
        found || return Completion[]
×
169
    end
170

171
    suggestions = Completion[]
×
172
    if lookup_module
×
173
        # We will exclude the results that the user does not want, as well
174
        # as excluding Main.Main.Main, etc., because that's most likely not what
175
        # the user wants
176
        p = let mod=mod, modname=nameof(mod)
×
177
            s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool)
×
178
        end
179
        # Looking for a binding in a module
180
        if mod == context_module
×
181
            # Also look in modules we got through `using`
182
            mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector
×
183
            for m in mods
×
184
                append!(suggestions, filtered_mod_names(p, m::Module, name))
×
185
            end
×
186
            append!(suggestions, filtered_mod_names(p, mod, name, true, true))
×
187
        else
188
            append!(suggestions, filtered_mod_names(p, mod, name, true, false))
×
189
        end
190
    elseif val !== nothing # looking for a property of an instance
×
191
        for property in propertynames(val, false)
×
192
            # TODO: support integer arguments (#36872)
193
            if property isa Symbol && startswith(string(property), name)
×
194
                push!(suggestions, PropertyCompletion(val, property))
×
195
            end
196
        end
×
197
    else
198
        # Looking for a member of a type
199
        if t isa DataType && t != Any
×
200
            # Check for cases like Type{typeof(+)}
201
            if Base.isType(t)
×
202
                t = typeof(t.parameters[1])
×
203
            end
204
            # Only look for fields if this is a concrete type
205
            if isconcretetype(t)
×
206
                fields = fieldnames(t)
×
207
                for field in fields
×
208
                    isa(field, Symbol) || continue # Tuple type has ::Int field name
×
209
                    s = string(field)
×
210
                    if startswith(s, name)
×
211
                        push!(suggestions, FieldCompletion(t, field))
×
212
                    end
213
                end
×
214
            end
215
        end
216
    end
217
    suggestions
×
218
end
219

220
const sorted_keywords = [
221
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
222
    "const", "continue", "do", "else", "elseif", "end", "export", "false",
223
    "finally", "for", "function", "global", "if", "import",
224
    "let", "local", "macro", "module", "mutable struct",
225
    "primitive type", "quote", "return", "struct",
226
    "true", "try", "using", "while"]
227

228
function complete_keyword(s::Union{String,SubString{String}})
×
229
    r = searchsorted(sorted_keywords, s)
×
230
    i = first(r)
×
231
    n = length(sorted_keywords)
×
232
    while i <= n && startswith(sorted_keywords[i],s)
×
233
        r = first(r):i
×
234
        i += 1
×
235
    end
×
236
    Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]]
×
237
end
238

239
function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_escape=false)
×
240
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
241
        # if the path is just "~", don't consider the expanded username as a prefix
242
        if path == "~"
×
243
            dir, prefix = homedir(), ""
×
244
        else
245
            dir, prefix = splitdir(homedir() * path[2:end])
×
246
        end
247
    else
248
        dir, prefix = splitdir(path)
×
249
    end
250
    local files
×
251
    try
×
252
        if isempty(dir)
×
253
            files = readdir()
×
254
        elseif isdir(dir)
×
255
            files = readdir(dir)
×
256
        else
257
            return Completion[], 0:-1, false
×
258
        end
259
    catch
260
        return Completion[], 0:-1, false
×
261
    end
262

263
    matches = Set{String}()
×
264
    for file in files
×
265
        if startswith(file, prefix)
×
266
            id = try isdir(joinpath(dir, file)) catch; false end
×
267
            # joinpath is not used because windows needs to complete with double-backslash
268
            push!(matches, id ? file * (@static Sys.iswindows() ? "\\\\" : "/") : file)
×
269
        end
270
    end
×
271

272
    if use_envpath && length(dir) == 0
×
273
        # Look for files in PATH as well
274
        local pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":")
×
275

276
        for pathdir in pathdirs
×
277
            local actualpath
×
278
            try
×
279
                actualpath = realpath(pathdir)
×
280
            catch
281
                # Bash doesn't expect every folder in PATH to exist, so neither shall we
282
                continue
×
283
            end
284

285
            if actualpath != pathdir && in(actualpath,pathdirs)
×
286
                # Remove paths which (after resolving links) are in the env path twice.
287
                # Many distros eg. point /bin to /usr/bin but have both in the env path.
288
                continue
×
289
            end
290

291
            local filesinpath
×
292
            try
×
293
                filesinpath = readdir(pathdir)
×
294
            catch e
295
                # Bash allows dirs in PATH that can't be read, so we should as well.
296
                if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
×
297
                    continue
×
298
                else
299
                    # We only handle IOError and ArgumentError here
300
                    rethrow()
×
301
                end
302
            end
303

304
            for file in filesinpath
×
305
                # In a perfect world, we would filter on whether the file is executable
306
                # here, or even on whether the current user can execute the file in question.
307
                if startswith(file, prefix) && isfile(joinpath(pathdir, file))
×
308
                    push!(matches, file)
×
309
                end
310
            end
×
311
        end
×
312
    end
313

314
    matchList = Completion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches]
×
315
    startpos = pos - lastindex(prefix) + 1 - count(isequal(' '), prefix)
×
316
    # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`,
317
    # hence we need to add one to get the first index. This is also correct when considering
318
    # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`.
319
    return matchList, startpos:pos, !isempty(matchList)
×
320
end
321

322
function complete_expanduser(path::AbstractString, r)
×
323
    expanded =
×
324
        try expanduser(path)
×
325
        catch e
326
            e isa ArgumentError || rethrow()
×
327
            path
×
328
        end
329
    return Completion[PathCompletion(expanded)], r, path != expanded
×
330
end
331

332
# Returns a range that includes the method name in front of the first non
333
# closed start brace from the end of the string.
334
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
×
335
    braces = 0
×
336
    r = reverse(s)
×
337
    i = firstindex(r)
×
338
    in_single_quotes = false
×
339
    in_double_quotes = false
×
340
    in_back_ticks = false
×
341
    in_comment = 0
×
342
    while i <= ncodeunits(r)
×
343
        c, i = iterate(r, i)
×
344
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
×
345
            c, i = iterate(r, i) # consume '='
×
346
            new_comments = 1
×
347
            # handle #=#=#=#, by counting =# pairs
348
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
×
349
                c, i = iterate(r, i) # consume '#'
×
350
                iterate(r, i)[1] == '=' || break
×
351
                c, i = iterate(r, i) # consume '='
×
352
                new_comments += 1
×
353
            end
×
354
            if c == '='
×
355
                in_comment += new_comments
×
356
            else
357
                in_comment -= new_comments
×
358
            end
359
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
×
360
            if c == c_start
×
361
                braces += 1
×
362
            elseif c == c_end
×
363
                braces -= 1
×
364
            elseif c == '\''
×
365
                in_single_quotes = true
×
366
            elseif c == '"'
×
367
                in_double_quotes = true
×
368
            elseif c == '`'
×
369
                in_back_ticks = true
×
370
            end
371
        else
372
            if in_single_quotes &&
×
373
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
374
                in_single_quotes = false
×
375
            elseif in_double_quotes &&
×
376
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
377
                in_double_quotes = false
×
378
            elseif in_back_ticks &&
×
379
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
380
                in_back_ticks = false
×
381
            elseif in_comment > 0 &&
×
382
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
383
                # handle =#=#=#=, by counting #= pairs
384
                c, i = iterate(r, i) # consume '#'
×
385
                old_comments = 1
×
386
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
×
387
                    c, i = iterate(r, i) # consume '='
×
388
                    iterate(r, i)[1] == '#' || break
×
389
                    c, i = iterate(r, i) # consume '#'
×
390
                    old_comments += 1
×
391
                end
×
392
                if c == '#'
×
393
                    in_comment -= old_comments
×
394
                else
395
                    in_comment += old_comments
×
396
                end
397
            end
398
        end
399
        braces == 1 && break
×
400
    end
×
401
    braces != 1 && return 0:-1, -1
×
402
    method_name_end = reverseind(s, i)
×
403
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
×
404
    return (startind:lastindex(s), method_name_end)
×
405
end
406

407
# Returns the value in a expression if sym is defined in current namespace fn.
408
# This method is used to iterate to the value of a expression like:
409
# :(REPL.REPLCompletions.whitespace_chars) a `dump` of this expression
410
# will show it consist of Expr, QuoteNode's and Symbol's which all needs to
411
# be handled differently to iterate down to get the value of whitespace_chars.
412
function get_value(sym::Expr, fn)
1✔
413
    if sym.head === :quote || sym.head === :inert
2✔
414
        return sym.args[1], true
×
415
    end
416
    sym.head !== :. && return (nothing, false)
1✔
417
    for ex in sym.args
1✔
418
        ex, found = get_value(ex, fn)::Tuple{Any, Bool}
2✔
419
        !found && return (nothing, false)
2✔
420
        fn, found = get_value(ex, fn)::Tuple{Any, Bool}
2✔
421
        !found && return (nothing, false)
2✔
422
    end
3✔
423
    return (fn, true)
1✔
424
end
425
get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (getfield(fn, sym), true) : (nothing, false)
2✔
426
get_value(sym::QuoteNode, fn) = (sym.value, true)
1✔
427
get_value(sym::GlobalRef, fn) = get_value(sym.name, sym.mod)
×
428
get_value(sym, fn) = (sym, true)
1✔
429

430
# Return the type of a getfield call expression
431
function get_type_getfield(ex::Expr, fn::Module)
×
432
    length(ex.args) == 3 || return Any, false # should never happen, but just for safety
×
433
    fld, found = get_value(ex.args[3], fn)
×
434
    fld isa Symbol || return Any, false
×
435
    obj = ex.args[2]
×
436
    objt, found = get_type(obj, fn)
×
437
    found || return Any, false
×
438
    objt isa DataType || return Any, false
×
439
    hasfield(objt, fld) || return Any, false
×
440
    return fieldtype(objt, fld), true
×
441
end
442

443
# Determines the return type with the Compiler of a function call using the type information of the arguments.
444
function get_type_call(expr::Expr, fn::Module)
×
445
    f_name = expr.args[1]
×
446
    f, found = get_type(f_name, fn)
×
447
    found || return (Any, false) # If the function f is not found return Any.
×
448
    args = Any[]
×
449
    for i in 2:length(expr.args) # Find the type of the function arguments
×
450
        typ, found = get_type(expr.args[i], fn)
×
451
        found ? push!(args, typ) : push!(args, Any)
×
452
    end
×
453
    world = Base.get_world_counter()
×
454
    return_type = Core.Compiler.return_type(Tuple{f, args...}, world)
×
455
    return (return_type, true)
×
456
end
457

458
# Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (SubString{String}, true)
459
function try_get_type(sym::Expr, fn::Module)
1✔
460
    val, found = get_value(sym, fn)
1✔
461
    found && return Core.Typeof(val), found
1✔
462
    if sym.head === :call
×
463
        # getfield call is special cased as the evaluation of getfield provides good type information,
464
        # is inexpensive and it is also performed in the complete_symbol function.
465
        a1 = sym.args[1]
×
466
        if a1 === :getfield || a1 === GlobalRef(Core, :getfield)
×
467
            return get_type_getfield(sym, fn)
×
468
        end
469
        return get_type_call(sym, fn)
×
470
    elseif sym.head === :thunk
×
471
        thk = sym.args[1]
×
472
        rt = ccall(:jl_infer_thunk, Any, (Any, Any), thk::Core.CodeInfo, fn)
×
473
        rt !== Any && return (rt, true)
×
474
    elseif sym.head === :ref
×
475
        # some simple cases of `expand`
476
        return try_get_type(Expr(:call, GlobalRef(Base, :getindex), sym.args...), fn)
×
477
    elseif sym.head === :. && sym.args[2] isa QuoteNode # second check catches broadcasting
×
478
        return try_get_type(Expr(:call, GlobalRef(Core, :getfield), sym.args...), fn)
×
479
    elseif sym.head === :toplevel || sym.head === :block
×
480
        isempty(sym.args) && return (nothing, true)
×
481
        return try_get_type(sym.args[end], fn)
×
482
    elseif sym.head === :escape || sym.head === :var"hygienic-scope"
×
483
        return try_get_type(sym.args[1], fn)
×
484
    end
485
    return (Any, false)
×
486
end
487

488
try_get_type(other, fn::Module) = get_type(other, fn)
×
489

490
function get_type(sym::Expr, fn::Module)
1✔
491
    # try to analyze nests of calls. if this fails, try using the expanded form.
492
    val, found = try_get_type(sym, fn)
1✔
493
    found && return val, found
1✔
494
    # https://github.com/JuliaLang/julia/issues/27184
495
    if isexpr(sym, :macrocall)
×
496
        _, found = get_type(first(sym.args), fn)
×
497
        found || return Any, false
×
498
    end
499
    newsym = try
×
500
        macroexpand(fn, sym; recursive=false)
×
501
    catch e
502
        # user code failed in macroexpand (ignore it)
503
        return Any, false
×
504
    end
505
    val, found = try_get_type(newsym, fn)
×
506
    if !found
×
507
        newsym = try
×
508
            Meta.lower(fn, sym)
×
509
        catch e
510
            # user code failed in lowering (ignore it)
511
            return Any, false
×
512
        end
513
        val, found = try_get_type(newsym, fn)
×
514
    end
515
    return val, found
×
516
end
517

518
function get_type(sym, fn::Module)
×
519
    val, found = get_value(sym, fn)
×
520
    return found ? Core.Typeof(val) : Any, found
×
521
end
522

523
function get_type(T, found::Bool, default_any::Bool)
×
524
    return found ? T :
×
525
           default_any ? Any : throw(ArgumentError("argument not found"))
526
end
527

528
# Method completion on function call expression that look like :(max(1))
529
MAX_METHOD_COMPLETIONS::Int = 40
530
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
1✔
531
    funct, found = get_type(ex_org.args[1], context_module)::Tuple{Any,Bool}
1✔
532
    !found && return 2, funct, [], Set{Symbol}()
1✔
533

534
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
2✔
535
    return kwargs_flag, funct, args_ex, kwargs_ex
1✔
536
end
537

538
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
2✔
539
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
2✔
540
    out = Completion[]
1✔
541
    kwargs_flag == 2 && return out # one of the kwargs is invalid
1✔
542
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
1✔
543
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
1✔
544
    return out
1✔
545
end
546

547
MAX_ANY_METHOD_COMPLETIONS::Int = 10
548
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
×
549
    out = Completion[]
×
550
    args_ex, kwargs_ex, kwargs_flag = try
×
551
        # this may throw, since we set default_any to false
552
        complete_methods_args(ex_org, context_module, false, false)
×
553
    catch ex
554
        ex isa ArgumentError || rethrow()
×
555
        return out
×
556
    end
557
    kwargs_flag == 2 && return out # one of the kwargs is invalid
×
558

559
    # moreargs determines whether to accept more args, independently of the presence of a
560
    # semicolon for the ".?(" syntax
561
    moreargs && push!(args_ex, Vararg{Any})
×
562

563
    seen = Base.IdSet()
×
564
    for name in names(callee_module; all=true)
×
565
        if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) && !startswith(string(name), '#')
×
566
            func = getfield(callee_module, name)
×
567
            if !isa(func, Module)
×
568
                funct = Core.Typeof(func)
×
569
                if !in(funct, seen)
×
570
                    push!(seen, funct)
×
571
                    complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
×
572
                end
573
            elseif callee_module === Main && isa(func, Module)
×
574
                callee_module2 = func
×
575
                for name in names(callee_module2)
×
576
                    if !Base.isdeprecated(callee_module2, name) && isdefined(callee_module2, name) && !startswith(string(name), '#')
×
577
                        func = getfield(callee_module, name)
×
578
                        if !isa(func, Module)
×
579
                            funct = Core.Typeof(func)
×
580
                            if !in(funct, seen)
×
581
                                push!(seen, funct)
×
582
                                complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
×
583
                            end
584
                        end
585
                    end
586
                end
×
587
            end
588
        end
589
    end
×
590

591
    if !shift
×
592
        # Filter out methods where all arguments are `Any`
593
        filter!(out) do c
×
594
            isa(c, TextCompletion) && return false
×
595
            isa(c, MethodCompletion) || return true
×
596
            sig = Base.unwrap_unionall(c.method.sig)::DataType
×
597
            return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
×
598
        end
599
    end
600

601
    return out
×
602
end
603

604
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
×
605
    n = isexpr(x, :kw) ? x.args[1] : x
×
606
    if n isa Symbol
×
607
        push!(kwargs_ex, n)
×
608
        return kwargs_flag
×
609
    end
610
    possible_splat && isexpr(x, :...) && return kwargs_flag
×
611
    return 2 # The kwarg is invalid
×
612
end
613

614
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
1✔
615
    args_ex = Any[]
1✔
616
    kwargs_ex = Symbol[]
1✔
617
    kwargs_flag = 0
×
618
    # kwargs_flag is:
619
    # * 0 if there is no semicolon and no invalid kwarg
620
    # * 1 if there is a semicolon and no invalid kwarg
621
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
622
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
623
    for i in (1+!broadcasting):length(funargs)
1✔
624
        ex = funargs[i]
×
625
        if isexpr(ex, :parameters)
×
626
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
×
627
            for x in ex.args
×
628
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
×
629
            end
×
630
        elseif isexpr(ex, :kw)
×
631
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
×
632
        else
633
            if broadcasting
×
634
                # handle broadcasting, but only handle number of arguments instead of
635
                # argument types
636
                push!(args_ex, Any)
×
637
            else
638
                push!(args_ex, get_type(get_type(ex, context_module)..., default_any))
×
639
            end
640
        end
641
    end
×
642
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
1✔
643
end
644

645
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1✔
646

647
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
×
648
    if allow_broadcasting && is_broadcasting_expr(ex)
1✔
649
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
×
650
    end
651
    return detect_args_kwargs(ex.args, context_module, default_any, false)
1✔
652
end
653

654
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
1✔
655
    # Input types and number of arguments
656
    t_in = Tuple{funct, args_ex...}
1✔
657
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
1✔
658
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
659
    if !isa(m, Vector)
1✔
660
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
×
661
        return
×
662
    end
663
    for match in m
1✔
664
        # TODO: if kwargs_ex, filter out methods without kwargs?
665
        push!(out, MethodCompletion(match.spec_types, match.method))
1✔
666
    end
1✔
667
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
668
end
669

670
include("latex_symbols.jl")
671
include("emoji_symbols.jl")
672

673
const non_identifier_chars = [" \t\n\r\"\\'`\$><=:;|&{}()[],+-*/?%^~"...]
674
const whitespace_chars = [" \t\n\r"...]
675
# "\"'`"... is added to whitespace_chars as non of the bslash_completions
676
# characters contain any of these characters. It prohibits the
677
# bslash_completions function to try and complete on escaped characters in strings
678
const bslash_separators = [whitespace_chars..., "\"'`"...]
679

680
const subscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\_") && length(k)==3)
681
const subscript_regex = Regex("^\\\\_[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(subscripts)) * "]+\\z")
682
const superscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\^") && length(k)==3)
683
const superscript_regex = Regex("^\\\\\\^[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(superscripts)) * "]+\\z")
684

685
# Aux function to detect whether we're right after a
686
# using or import keyword
687
function afterusing(string::String, startpos::Int)
×
688
    (isempty(string) || startpos == 0) && return false
×
689
    str = string[1:prevind(string,startpos)]
×
690
    isempty(str) && return false
×
691
    rstr = reverse(str)
×
692
    r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
×
693
    r === nothing && return false
×
694
    fr = reverseind(str, last(r))
×
695
    return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
×
696
end
697

698
function close_path_completion(str, startpos, r, paths, pos)
×
699
    length(paths) == 1 || return false  # Only close if there's a single choice...
×
700
    _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path
×
701
    path = expanduser(replace(_path, r"\\ " => " "))
×
702
    # ...except if it's a directory...
703
    try
×
704
        isdir(path)
×
705
    catch e
706
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
×
707
    end && return false
708
    # ...and except if there's already a " at the cursor.
709
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
×
710
end
711

712

713
function bslash_completions(string::String, pos::Int)
×
714
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
×
715
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
×
716
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
717
        # latex / emoji symbol substitution
718
        s = string[slashpos:pos]
×
719
        latex = get(latex_symbols, s, "")
×
720
        if !isempty(latex) # complete an exact match
×
721
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
×
722
        elseif occursin(subscript_regex, s)
×
723
            sub = map(c -> subscripts[c], s[3:end])
×
724
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
×
725
        elseif occursin(superscript_regex, s)
×
726
            sup = map(c -> superscripts[c], s[3:end])
×
727
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
×
728
        end
729
        emoji = get(emoji_symbols, s, "")
×
730
        if !isempty(emoji)
×
731
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
×
732
        end
733
        # return possible matches; these cannot be mixed with regular
734
        # Julian completions as only latex / emoji symbols contain the leading \
735
        if startswith(s, "\\:") # emoji
×
736
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
×
737
        else # latex
738
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
×
739
        end
740
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
×
741
    end
742
    return (false, (Completion[], 0:-1, false))
×
743
end
744

745
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
×
746
    if tag === :string
×
747
        str_close = str*"\""
×
748
    elseif tag === :cmd
×
749
        str_close = str*"`"
×
750
    else
751
        str_close = str
×
752
    end
753

754
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
×
755
    isempty(frange) && return (nothing, nothing, nothing)
×
756
    obj = context_module
×
757
    for name in split(str[frange[1]:end_of_identifier], '.')
×
758
        Base.isidentifier(name) || return (nothing, nothing, nothing)
×
759
        sym = Symbol(name)
×
760
        isdefined(obj, sym) || return (nothing, nothing, nothing)
×
761
        obj = getfield(obj, sym)
×
762
    end
×
763
    (isa(obj, AbstractDict) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
×
764
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
×
765
                             lastindex(str)+1)
766
    return (obj::AbstractDict, str[begin_of_key:end], begin_of_key)
×
767
end
768

769
# This needs to be a separate non-inlined function, see #19441
770
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
×
771
    matches = String[]
×
772
    for key in keys(identifier)
×
773
        rkey = repr(key)
×
774
        startswith(rkey,partial_key) && push!(matches,rkey)
×
775
    end
×
776
    return matches
×
777
end
778

779
# Identify an argument being completed in a method call. If the argument is empty, method
780
# suggestions will be provided instead of argument completions.
781
function identify_possible_method_completion(partial, last_idx)
×
782
    fail = 0:-1, Expr(:nothing), 0:-1, 0
×
783

784
    # First, check that the last punctuation is either ',', ';' or '('
785
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
×
786
    idx_last_punct == 0 && return fail
×
787
    last_punct = partial[idx_last_punct]
×
788
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
×
789

790
    # Then, check that `last_punct` is only followed by an identifier or nothing
791
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
×
792
    before_last_word_start == 0 && return fail
×
793
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
×
794

795
    # Check that `last_punct` is either the last '(' or placed after a previous '('
796
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
×
797
    method_name_end ∈ frange || return fail
×
798

799
    # Strip the preceding ! operators, if any, and close the expression with a ')'
800
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
×
801
    ex = Meta.parse(s, raise=false, depwarn=false)
×
802
    isa(ex, Expr) || return fail
×
803

804
    # `wordrange` is the position of the last argument to complete
805
    wordrange = nextind(partial, before_last_word_start):last_idx
×
806
    return frange, ex, wordrange, method_name_end
×
807
end
808

809
# Provide completion for keyword arguments in function calls
810
function complete_keyword_argument(partial, last_idx, context_module)
×
811
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
×
812
    fail = Completion[], 0:-1, frange
×
813
    ex.head === :call || is_broadcasting_expr(ex) || return fail
×
814

815
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
×
816
    kwargs_flag == 2 && return fail # one of the previous kwargs is invalid
×
817

818
    methods = Completion[]
×
819
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
×
820
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
821
    # method calls compatible with the current arguments.
822

823
    # For each method corresponding to the function call, provide completion suggestions
824
    # for each keyword that starts like the last word and that is not already used
825
    # previously in the expression. The corresponding suggestion is "kwname=".
826
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
827
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
828
    last_word = partial[wordrange] # the word to complete
×
829
    kwargs = Set{String}()
×
830
    for m in methods
×
831
        m::MethodCompletion
×
832
        possible_kwargs = Base.kwarg_decl(m.method)
×
833
        current_kwarg_candidates = String[]
×
834
        for _kw in possible_kwargs
×
835
            kw = String(_kw)
×
836
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
×
837
                push!(current_kwarg_candidates, kw)
×
838
            end
839
        end
×
840
        union!(kwargs, current_kwarg_candidates)
×
841
    end
×
842

843
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
×
844
    append!(suggestions, complete_symbol(last_word, Returns(true), context_module))
×
845

846
    return sort!(suggestions, by=completion_text), wordrange
×
847
end
848

849
function project_deps_get_completion_candidates(pkgstarts::String, project_file::String)
×
850
    loading_candidates = String[]
×
851
    d = Base.parsed_toml(project_file)
×
852
    pkg = get(d, "name", nothing)::Union{String, Nothing}
×
853
    if pkg !== nothing && startswith(pkg, pkgstarts)
×
854
        push!(loading_candidates, pkg)
×
855
    end
856
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
×
857
    if deps !== nothing
×
858
        for (pkg, _) in deps
×
859
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
×
860
        end
×
861
    end
862
    return Completion[PackageCompletion(name) for name in loading_candidates]
×
863
end
864

865
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true)
×
866
    # First parse everything up to the current position
867
    partial = string[1:pos]
×
868
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
×
869

870
    # ?(x, y)TAB lists methods you can call with these objects
871
    # ?(x, y TAB lists methods that take these objects as the first two arguments
872
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
873
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
×
874
    if rexm !== nothing
×
875
        # Get the module scope
876
        if isempty(rexm.captures[1])
×
877
            callee_module = context_module
×
878
        else
879
            modname = Symbol(rexm.captures[1][1:end-1])
×
880
            if isdefined(context_module, modname)
×
881
                callee_module = getfield(context_module, modname)
×
882
                if !isa(callee_module, Module)
×
883
                    callee_module = context_module
×
884
                end
885
            else
886
                callee_module = context_module
×
887
            end
888
        end
889
        moreargs = !endswith(rexm.captures[2], ')')
×
890
        callstr = "_(" * rexm.captures[2]
×
891
        if moreargs
×
892
            callstr *= ')'
×
893
        end
894
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
×
895
        if isa(ex_org, Expr)
×
896
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
×
897
        end
898
    end
899

900
    # if completing a key in a Dict
901
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
×
902
    if identifier !== nothing
×
903
        matches = find_dict_matches(identifier, partial_key)
×
904
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
×
905
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
×
906
    end
907

908
    # otherwise...
909
    if inc_tag in [:cmd, :string]
×
910
        m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial))
×
911
        startpos = nextind(partial, reverseind(partial, m.offset))
×
912
        r = startpos:pos
×
913

914
        expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r)
×
915
        expanded[3] && return expanded  # If user expansion available, return it
×
916

917
        paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos)
×
918

919
        if inc_tag === :string && close_path_completion(string, startpos, r, paths, pos)
×
920
            paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"")
×
921
        end
922

923
        #Latex symbols can be completed for strings
924
        (success || inc_tag === :cmd) && return sort!(paths, by=p->p.path), r, success
×
925
    end
926

927
    ok, ret = bslash_completions(string, pos)
×
928
    ok && return ret
×
929

930
    # Make sure that only bslash_completions is working on strings
931
    inc_tag === :string && return Completion[], 0:-1, false
×
932
    if inc_tag === :other
×
933
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
×
934
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
×
935
            if ex.head === :call
×
936
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
×
937
            elseif is_broadcasting_expr(ex)
×
938
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
×
939
            end
940
        end
941
    elseif inc_tag === :comment
×
942
        return Completion[], 0:-1, false
×
943
    end
944

945
    # Check whether we can complete a keyword argument in a function call
946
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
×
947
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
×
948

949
    dotpos = something(findprev(isequal('.'), string, pos), 0)
×
950
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
×
951
    # strip preceding ! operator
952
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
×
953
        startpos += length(m.match)
×
954
    end
955

956
    ffunc = Returns(true)
×
957
    suggestions = Completion[]
×
958
    comp_keywords = true
×
959
    if afterusing(string, startpos)
×
960
        # We're right after using or import. Let's look only for packages
961
        # and modules we can reach from here
962

963
        # If there's no dot, we're in toplevel, so we should
964
        # also search for packages
965
        s = string[startpos:pos]
×
966
        if dotpos <= startpos
×
967
            for dir in Base.load_path()
×
968
                if basename(dir) in Base.project_names && isfile(dir)
×
969
                    append!(suggestions, project_deps_get_completion_candidates(s, dir))
×
970
                end
971
                isdir(dir) || continue
×
972
                for pname in readdir(dir)
×
973
                    if pname[1] != '.' && pname != "METADATA" &&
×
974
                        pname != "REQUIRE" && startswith(pname, s)
975
                        # Valid file paths are
976
                        #   <Mod>.jl
977
                        #   <Mod>/src/<Mod>.jl
978
                        #   <Mod>.jl/src/<Mod>.jl
979
                        if isfile(joinpath(dir, pname))
×
980
                            endswith(pname, ".jl") && push!(suggestions,
×
981
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
982
                        else
983
                            mod_name = if endswith(pname, ".jl")
×
984
                                pname[1:prevind(pname, end-2)]
×
985
                            else
986
                                pname
×
987
                            end
988
                            if isfile(joinpath(dir, pname, "src",
×
989
                                               "$mod_name.jl"))
990
                                push!(suggestions, PackageCompletion(mod_name))
×
991
                            end
992
                        end
993
                    end
994
                end
×
995
            end
×
996
        end
997
        ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
×
998
        comp_keywords = false
×
999
    end
1000
    startpos == 0 && (pos = -1)
×
1001
    dotpos < startpos && (dotpos = startpos - 1)
×
1002
    s = string[startpos:pos]
×
1003
    comp_keywords && append!(suggestions, complete_keyword(s))
×
1004
    # if the start of the string is a `.`, try to consume more input to get back to the beginning of the last expression
1005
    if 0 < startpos <= lastindex(string) && string[startpos] == '.'
×
1006
        i = prevind(string, startpos)
×
1007
        while 0 < i
×
1008
            c = string[i]
×
1009
            if c in (')', ']')
×
1010
                if c == ')'
×
1011
                    c_start = '('
×
1012
                    c_end = ')'
×
1013
                elseif c == ']'
×
1014
                    c_start = '['
×
1015
                    c_end = ']'
×
1016
                end
1017
                frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1018
                isempty(frange) && break # unbalanced parens
×
1019
                startpos = first(frange)
×
1020
                i = prevind(string, startpos)
×
1021
            elseif c in ('\'', '\"', '\`')
×
1022
                s = "$c$c"*string[startpos:pos]
×
1023
                break
×
1024
            else
1025
                break
×
1026
            end
1027
            s = string[startpos:pos]
×
1028
        end
×
1029
    end
1030
    append!(suggestions, complete_symbol(s, ffunc, context_module))
×
1031
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
×
1032
end
1033

1034
function shell_completions(string, pos)
×
1035
    # First parse everything up to the current position
1036
    scs = string[1:pos]
×
1037
    local args, last_parse
×
1038
    try
×
1039
        args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}}
×
1040
    catch
1041
        return Completion[], 0:-1, false
×
1042
    end
1043
    ex = args.args[end]::Expr
×
1044
    # Now look at the last thing we parsed
1045
    isempty(ex.args) && return Completion[], 0:-1, false
×
1046
    arg = ex.args[end]
×
1047
    if all(s -> isa(s, AbstractString), ex.args)
×
1048
        arg = arg::AbstractString
×
1049
        # Treat this as a path
1050

1051
        # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1052
        # we need to special case here.
1053
        # If the last char was a space, but shell_parse ignored it search on "".
1054
        ignore_last_word = arg != " " && scs[end] == ' '
×
1055
        prefix = ignore_last_word ? "" : join(ex.args)
×
1056

1057
        # Also try looking into the env path if the user wants to complete the first argument
1058
        use_envpath = !ignore_last_word && length(args.args) < 2
×
1059

1060
        return complete_path(prefix, pos, use_envpath=use_envpath, shell_escape=true)
×
1061
    elseif isexpr(arg, :incomplete) || isexpr(arg, :error)
×
1062
        partial = scs[last_parse]
×
1063
        ret, range = completions(partial, lastindex(partial))
×
1064
        range = range .+ (first(last_parse) - 1)
×
1065
        return ret, range, true
×
1066
    end
1067
    return Completion[], 0:-1, false
×
1068
end
1069

1070
function UndefVarError_hint(io::IO, ex::UndefVarError)
7✔
1071
    var = ex.var
7✔
1072
    if var === :or
7✔
1073
        print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.")
×
1074
    elseif var === :and
7✔
1075
        print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.")
×
1076
    elseif var === :help
7✔
1077
        println(io)
×
1078
        # Show friendly help message when user types help or help() and help is undefined
1079
        show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
×
1080
    elseif var === :quit
7✔
1081
        print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
1082
    end
1083
end
1084

1085
function __init__()
449✔
1086
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
449✔
1087
    nothing
449✔
1088
end
1089

1090
end # module
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc