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

JuliaLang / julia / #37493

pending completion
#37493

push

local

web-flow
effects: add docstrings to the type-based-effect-analysis queries (#49222)

5 of 5 new or added lines in 2 files covered. (100.0%)

71723 of 83028 relevant lines covered (86.38%)

33270911.92 hits per line

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

89.76
/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
1✔
14
end
15

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

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

24
struct ModuleCompletion <: Completion
25
    parent::Module
6,479✔
26
    mod::String
27
end
28

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

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

38
struct FieldCompletion <: Completion
39
    typ::DataType
27✔
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)
657✔
47
end
48

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

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

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

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

66
# interface definition
67
function Base.getproperty(c::Completion, name::Symbol)
145,091✔
68
    if name === :text
145,365✔
69
        return getfield(c, :text)::String
1✔
70
    elseif name === :keyword
145,364✔
71
        return getfield(c, :keyword)::String
3,825✔
72
    elseif name === :path
141,333✔
73
        return getfield(c, :path)::String
206✔
74
    elseif name === :parent
141,333✔
75
        return getfield(c, :parent)::Module
×
76
    elseif name === :mod
141,333✔
77
        return getfield(c, :mod)::String
139,385✔
78
    elseif name === :package
1,948✔
79
        return getfield(c, :package)::String
959✔
80
    elseif name === :property
989✔
81
        return getfield(c, :property)::Symbol
15✔
82
    elseif name === :field
974✔
83
        return getfield(c, :field)::Symbol
107✔
84
    elseif name === :method
294✔
85
        return getfield(c, :method)::Method
659✔
86
    elseif name === :bslash
226✔
87
        return getfield(c, :bslash)::String
68✔
88
    elseif name === :text
226✔
89
        return getfield(c, :text)::String
×
90
    elseif name === :key
226✔
91
        return getfield(c, :key)::String
118✔
92
    elseif name === :kwarg
108✔
93
        return getfield(c, :kwarg)::String
108✔
94
    end
95
    return getfield(c, name)
×
96
end
97

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

111
completion_text(c) = _completion_text(c)::String
145,340✔
112

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

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

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

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

140
# REPL Symbol Completions
141
function complete_symbol(sym::String, @nospecialize(ffunc), context_module::Module=Main)
122✔
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)
180✔
149
        # Find module
150
        lookup_name, name = rsplit(sym, ".", limit=2)
58✔
151

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

154
        b, found = get_value(ex, context_module)
58✔
155
        if found
58✔
156
            val = b
×
157
            if isa(b, Module)
24✔
158
                mod = b
11✔
159
                lookup_module = true
11✔
160
            else
161
                lookup_module = false
×
162
                t = typeof(b)
37✔
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)
34✔
167
        end
168
        found || return Completion[]
74✔
169
    end
170

171
    suggestions = Completion[]
106✔
172
    if lookup_module
106✔
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)
75✔
177
            s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool)
86,107✔
178
        end
179
        # Looking for a binding in a module
180
        if mod == context_module
75✔
181
            # Also look in modules we got through `using`
182
            mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector
65✔
183
            for m in mods
65✔
184
                append!(suggestions, filtered_mod_names(p, m::Module, name))
472✔
185
            end
463✔
186
            append!(suggestions, filtered_mod_names(p, mod, name, true, true))
85✔
187
        else
188
            append!(suggestions, filtered_mod_names(p, mod, name, true, false))
85✔
189
        end
190
    elseif val !== nothing # looking for a property of an instance
31✔
191
        for property in propertynames(val, false)
12✔
192
            # TODO: support integer arguments (#36872)
193
            if property isa Symbol && startswith(string(property), name)
24✔
194
                push!(suggestions, PropertyCompletion(val, property))
11✔
195
            end
196
        end
13✔
197
    else
198
        # Looking for a member of a type
199
        if t isa DataType && t != Any
19✔
200
            # Check for cases like Type{typeof(+)}
201
            if Base.isType(t)
15✔
202
                t = typeof(t.parameters[1])
1✔
203
            end
204
            # Only look for fields if this is a concrete type
205
            if isconcretetype(t)
15✔
206
                fields = fieldnames(t)
15✔
207
                for field in fields
15✔
208
                    isa(field, Symbol) || continue # Tuple type has ::Int field name
29✔
209
                    s = string(field)
27✔
210
                    if startswith(s, name)
54✔
211
                        push!(suggestions, FieldCompletion(t, field))
27✔
212
                    end
213
                end
29✔
214
            end
215
        end
216
    end
217
    suggestions
106✔
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}})
81✔
229
    r = searchsorted(sorted_keywords, s)
81✔
230
    i = first(r)
81✔
231
    n = length(sorted_keywords)
81✔
232
    while i <= n && startswith(sorted_keywords[i],s)
487✔
233
        r = first(r):i
178✔
234
        i += 1
178✔
235
    end
178✔
236
    Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]]
81✔
237
end
238

239
function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_escape=false)
82✔
240
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
41✔
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)
41✔
249
    end
250
    local files
×
251
    try
41✔
252
        if isempty(dir)
41✔
253
            files = readdir()
29✔
254
        elseif isdir(dir)
12✔
255
            files = readdir(dir)
11✔
256
        else
257
            return Completion[], 0:-1, false
41✔
258
        end
259
    catch
260
        return Completion[], 0:-1, false
×
261
    end
262

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

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

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

285
            if actualpath != pathdir && in(actualpath,pathdirs)
64✔
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
12✔
289
            end
290

291
            local filesinpath
×
292
            try
40✔
293
                filesinpath = readdir(pathdir)
41✔
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)
1✔
297
                    continue
1✔
298
                else
299
                    # We only handle IOError and ArgumentError here
300
                    rethrow()
×
301
                end
302
            end
303

304
            for file in filesinpath
51✔
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))
10,805✔
308
                    push!(matches, file)
3✔
309
                end
310
            end
7,955✔
311
        end
66✔
312
    end
313

314
    matchList = Completion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches]
229✔
315
    startpos = pos - lastindex(prefix) + 1 - count(isequal(' '), prefix)
40✔
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)
40✔
320
end
321

322
function complete_expanduser(path::AbstractString, r)
31✔
323
    expanded =
31✔
324
        try expanduser(path)
32✔
325
        catch e
326
            e isa ArgumentError || rethrow()
1✔
327
            path
32✔
328
        end
329
    return Completion[PathCompletion(expanded)], r, path != expanded
31✔
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=')')
1,004✔
335
    braces = 0
×
336
    r = reverse(s)
502✔
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)
8,153✔
343
        c, i = iterate(r, i)
15,830✔
344
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
7,915✔
345
            c, i = iterate(r, i) # consume '='
16✔
346
            new_comments = 1
×
347
            # handle #=#=#=#, by counting =# pairs
348
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
12✔
349
                c, i = iterate(r, i) # consume '#'
12✔
350
                iterate(r, i)[1] == '=' || break
6✔
351
                c, i = iterate(r, i) # consume '='
8✔
352
                new_comments += 1
4✔
353
            end
4✔
354
            if c == '='
8✔
355
                in_comment += new_comments
6✔
356
            else
357
                in_comment -= new_comments
10✔
358
            end
359
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
7,907✔
360
            if c == c_start
7,385✔
361
                braces += 1
311✔
362
            elseif c == c_end
7,074✔
363
                braces -= 1
47✔
364
            elseif c == '\''
7,027✔
365
                in_single_quotes = true
16✔
366
            elseif c == '"'
7,011✔
367
                in_double_quotes = true
73✔
368
            elseif c == '`'
6,938✔
369
                in_back_ticks = true
7,385✔
370
            end
371
        else
372
            if in_single_quotes &&
522✔
373
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
374
                in_single_quotes = false
16✔
375
            elseif in_double_quotes &&
506✔
376
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
377
                in_double_quotes = false
60✔
378
            elseif in_back_ticks &&
446✔
379
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
380
                in_back_ticks = false
7✔
381
            elseif in_comment > 0 &&
439✔
382
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
383
                # handle =#=#=#=, by counting #= pairs
384
                c, i = iterate(r, i) # consume '#'
12✔
385
                old_comments = 1
×
386
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
8✔
387
                    c, i = iterate(r, i) # consume '='
8✔
388
                    iterate(r, i)[1] == '#' || break
4✔
389
                    c, i = iterate(r, i) # consume '#'
4✔
390
                    old_comments += 1
2✔
391
                end
2✔
392
                if c == '#'
6✔
393
                    in_comment -= old_comments
4✔
394
                else
395
                    in_comment += old_comments
2✔
396
                end
397
            end
398
        end
399
        braces == 1 && break
7,915✔
400
    end
7,651✔
401
    braces != 1 && return 0:-1, -1
502✔
402
    method_name_end = reverseind(s, i)
264✔
403
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
281✔
404
    return (startind:lastindex(s), method_name_end)
264✔
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)
389✔
413
    if sym.head === :quote || sym.head === :inert
778✔
414
        return sym.args[1], true
×
415
    end
416
    sym.head !== :. && return (nothing, false)
389✔
417
    for ex in sym.args
149✔
418
        ex, found = get_value(ex, fn)::Tuple{Any, Bool}
287✔
419
        !found && return (nothing, false)
287✔
420
        fn, found = get_value(ex, fn)::Tuple{Any, Bool}
274✔
421
        !found && return (nothing, false)
274✔
422
    end
402✔
423
    return (fn, true)
132✔
424
end
425
get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (getfield(fn, sym), true) : (nothing, false)
425✔
426
get_value(sym::QuoteNode, fn) = (sym.value, true)
149✔
427
get_value(sym::GlobalRef, fn) = get_value(sym.name, sym.mod)
26✔
428
get_value(sym, fn) = (sym, true)
257✔
429

430
# Return the type of a getfield call expression
431
function get_type_getfield(ex::Expr, fn::Module)
13✔
432
    length(ex.args) == 3 || return Any, false # should never happen, but just for safety
13✔
433
    fld, found = get_value(ex.args[3], fn)
13✔
434
    fld isa Symbol || return Any, false
13✔
435
    obj = ex.args[2]
13✔
436
    objt, found = get_type(obj, fn)
13✔
437
    found || return Any, false
13✔
438
    objt isa DataType || return Any, false
15✔
439
    hasfield(objt, fld) || return Any, false
19✔
440
    return fieldtype(objt, fld), true
3✔
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)
52✔
445
    f_name = expr.args[1]
52✔
446
    f, found = get_type(f_name, fn)
52✔
447
    found || return (Any, false) # If the function f is not found return Any.
58✔
448
    args = Any[]
46✔
449
    for i in 2:length(expr.args) # Find the type of the function arguments
84✔
450
        typ, found = get_type(expr.args[i], fn)
71✔
451
        found ? push!(args, typ) : push!(args, Any)
72✔
452
    end
104✔
453
    world = Base.get_world_counter()
46✔
454
    return_type = Core.Compiler.return_type(Tuple{f, args...}, world)
46✔
455
    return (return_type, true)
46✔
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)
328✔
460
    val, found = get_value(sym, fn)
328✔
461
    found && return Core.Typeof(val), found
328✔
462
    if sym.head === :call
210✔
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]
65✔
466
        if a1 === :getfield || a1 === GlobalRef(Core, :getfield)
130✔
467
            return get_type_getfield(sym, fn)
13✔
468
        end
469
        return get_type_call(sym, fn)
52✔
470
    elseif sym.head === :thunk
145✔
471
        thk = sym.args[1]
25✔
472
        rt = ccall(:jl_infer_thunk, Any, (Any, Any), thk::Core.CodeInfo, fn)
25✔
473
        rt !== Any && return (rt, true)
25✔
474
    elseif sym.head === :ref
120✔
475
        # some simple cases of `expand`
476
        return try_get_type(Expr(:call, GlobalRef(Base, :getindex), sym.args...), fn)
22✔
477
    elseif sym.head === :. && sym.args[2] isa QuoteNode # second check catches broadcasting
98✔
478
        return try_get_type(Expr(:call, GlobalRef(Core, :getfield), sym.args...), fn)
13✔
479
    elseif sym.head === :toplevel || sym.head === :block
170✔
480
        isempty(sym.args) && return (nothing, true)
4✔
481
        return try_get_type(sym.args[end], fn)
3✔
482
    elseif sym.head === :escape || sym.head === :var"hygienic-scope"
162✔
483
        return try_get_type(sym.args[1], fn)
×
484
    end
485
    return (Any, false)
89✔
486
end
487

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

490
function get_type(sym::Expr, fn::Module)
218✔
491
    # try to analyze nests of calls. if this fails, try using the expanded form.
492
    val, found = try_get_type(sym, fn)
218✔
493
    found && return val, found
218✔
494
    # https://github.com/JuliaLang/julia/issues/27184
495
    if isexpr(sym, :macrocall)
54✔
496
        _, found = get_type(first(sym.args), fn)
23✔
497
        found || return Any, false
26✔
498
    end
499
    newsym = try
51✔
500
        macroexpand(fn, sym; recursive=false)
61✔
501
    catch e
502
        # user code failed in macroexpand (ignore it)
503
        return Any, false
10✔
504
    end
505
    val, found = try_get_type(newsym, fn)
41✔
506
    if !found
41✔
507
        newsym = try
34✔
508
            Meta.lower(fn, sym)
34✔
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)
34✔
514
    end
515
    return val, found
41✔
516
end
517

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

523
function get_type(T, found::Bool, default_any::Bool)
137✔
524
    return found ? T :
137✔
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)
113✔
531
    funct, found = get_type(ex_org.args[1], context_module)::Tuple{Any,Bool}
113✔
532
    !found && return 2, funct, [], Set{Symbol}()
113✔
533

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

538
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
×
539
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
80✔
540
    out = Completion[]
80✔
541
    kwargs_flag == 2 && return out # one of the kwargs is invalid
80✔
542
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
70✔
543
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
71✔
544
    return out
70✔
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)
13✔
549
    out = Completion[]
13✔
550
    args_ex, kwargs_ex, kwargs_flag = try
13✔
551
        # this may throw, since we set default_any to false
552
        complete_methods_args(ex_org, context_module, false, false)
13✔
553
    catch ex
554
        ex isa ArgumentError || rethrow()
×
555
        return out
13✔
556
    end
557
    kwargs_flag == 2 && return out # one of the kwargs is invalid
13✔
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})
13✔
562

563
    seen = Base.IdSet()
13✔
564
    for name in names(callee_module; all=true)
13✔
565
        if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) && !startswith(string(name), '#')
1,287✔
566
            func = getfield(callee_module, name)
611✔
567
            if !isa(func, Module)
611✔
568
                funct = Core.Typeof(func)
1,118✔
569
                if !in(funct, seen)
585✔
570
                    push!(seen, funct)
585✔
571
                    complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
585✔
572
                end
573
            elseif callee_module === Main && isa(func, Module)
26✔
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
1,300✔
590

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

601
    return out
13✔
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
57✔
606
    if n isa Symbol
55✔
607
        push!(kwargs_ex, n)
41✔
608
        return kwargs_flag
41✔
609
    end
610
    possible_splat && isexpr(x, :...) && return kwargs_flag
14✔
611
    return 2 # The kwarg is invalid
10✔
612
end
613

614
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
126✔
615
    args_ex = Any[]
126✔
616
    kwargs_ex = Symbol[]
126✔
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)
236✔
624
        ex = funargs[i]
218✔
625
        if isexpr(ex, :parameters)
332✔
626
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
54✔
627
            for x in ex.args
79✔
628
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
629
            end
62✔
630
        elseif isexpr(ex, :kw)
278✔
631
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
632
        else
633
            if broadcasting
142✔
634
                # handle broadcasting, but only handle number of arguments instead of
635
                # argument types
636
                push!(args_ex, Any)
5✔
637
            else
638
                push!(args_ex, get_type(get_type(ex, context_module)..., default_any))
137✔
639
            end
640
        end
641
    end
326✔
642
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
126✔
643
end
644

645
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
208✔
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)
113✔
649
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
650
    end
651
    return detect_args_kwargs(ex.args, context_module, default_any, false)
120✔
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)
688✔
655
    # Input types and number of arguments
656
    t_in = Tuple{funct, args_ex...}
688✔
657
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
688✔
658
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
659
    if !isa(m, Vector)
688✔
660
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
1✔
661
        return
1✔
662
    end
663
    for match in m
1,141✔
664
        # TODO: if kwargs_ex, filter out methods without kwargs?
665
        push!(out, MethodCompletion(match.spec_types, match.method))
657✔
666
    end
657✔
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)
89✔
688
    (isempty(string) || startpos == 0) && return false
89✔
689
    str = string[1:prevind(string,startpos)]
132✔
690
    isempty(str) && return false
86✔
691
    rstr = reverse(str)
46✔
692
    r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
46✔
693
    r === nothing && return false
46✔
694
    fr = reverseind(str, last(r))
9✔
695
    return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
9✔
696
end
697

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

712

713
function bslash_completions(string::String, pos::Int)
244✔
714
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
282✔
715
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
266✔
716
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
717
        # latex / emoji symbol substitution
718
        s = string[slashpos:pos]
72✔
719
        latex = get(latex_symbols, s, "")
55✔
720
        if !isempty(latex) # complete an exact match
36✔
721
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
19✔
722
        elseif occursin(subscript_regex, s)
17✔
723
            sub = map(c -> subscripts[c], s[3:end])
8✔
724
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
1✔
725
        elseif occursin(superscript_regex, s)
16✔
726
            sup = map(c -> superscripts[c], s[3:end])
8✔
727
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
728
        end
729
        emoji = get(emoji_symbols, s, "")
17✔
730
        if !isempty(emoji)
15✔
731
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
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
26✔
736
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
2,371✔
737
        else # latex
738
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
55,882✔
739
        end
740
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
13✔
741
    end
742
    return (false, (Completion[], 0:-1, false))
208✔
743
end
744

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

754
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
317✔
755
    isempty(frange) && return (nothing, nothing, nothing)
317✔
756
    obj = context_module
×
757
    for name in split(str[frange[1]:end_of_identifier], '.')
81✔
758
        Base.isidentifier(name) || return (nothing, nothing, nothing)
121✔
759
        sym = Symbol(name)
117✔
760
        isdefined(obj, sym) || return (nothing, nothing, nothing)
117✔
761
        obj = getfield(obj, sym)
117✔
762
    end
196✔
763
    (isa(obj, AbstractDict) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
80✔
764
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
152✔
765
                             lastindex(str)+1)
766
    return (obj::AbstractDict, str[begin_of_key:end], begin_of_key)
78✔
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)
77✔
771
    matches = String[]
77✔
772
    for key in keys(identifier)
116✔
773
        rkey = repr(key)
924✔
774
        startswith(rkey,partial_key) && push!(matches,rkey)
924✔
775
    end
1,430✔
776
    return matches
77✔
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)
282✔
782
    fail = 0:-1, Expr(:nothing), 0:-1, 0
282✔
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
1,426✔
786
    idx_last_punct == 0 && return fail
282✔
787
    last_punct = partial[idx_last_punct]
526✔
788
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
432✔
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)
328✔
792
    before_last_word_start == 0 && return fail
164✔
793
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
170✔
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])
158✔
797
    method_name_end ∈ frange || return fail
168✔
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) * ')'
296✔
801
    ex = Meta.parse(s, raise=false, depwarn=false)
148✔
802
    isa(ex, Expr) || return fail
148✔
803

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

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

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

818
    methods = Completion[]
33✔
819
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
33✔
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
66✔
829
    kwargs = Set{String}()
33✔
830
    for m in methods
33✔
831
        m::MethodCompletion
86✔
832
        possible_kwargs = Base.kwarg_decl(m.method)
86✔
833
        current_kwarg_candidates = String[]
86✔
834
        for _kw in possible_kwargs
88✔
835
            kw = String(_kw)
198✔
836
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
231✔
837
                push!(current_kwarg_candidates, kw)
51✔
838
            end
839
        end
282✔
840
        union!(kwargs, current_kwarg_candidates)
86✔
841
    end
119✔
842

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

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

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

865
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true)
652✔
866
    # First parse everything up to the current position
867
    partial = string[1:pos]
978✔
868
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
329✔
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)
329✔
874
    if rexm !== nothing
329✔
875
        # Get the module scope
876
        if isempty(rexm.captures[1])
26✔
877
            callee_module = context_module
×
878
        else
879
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
880
            if isdefined(context_module, modname)
13✔
881
                callee_module = getfield(context_module, modname)
13✔
882
                if !isa(callee_module, Module)
13✔
883
                    callee_module = context_module
13✔
884
                end
885
            else
886
                callee_module = context_module
×
887
            end
888
        end
889
        moreargs = !endswith(rexm.captures[2], ')')
14✔
890
        callstr = "_(" * rexm.captures[2]
13✔
891
        if moreargs
13✔
892
            callstr *= ')'
7✔
893
        end
894
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
13✔
895
        if isa(ex_org, Expr)
13✔
896
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
13✔
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)
393✔
902
    if identifier !== nothing
316✔
903
        matches = find_dict_matches(identifier, partial_key)
77✔
904
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
81✔
905
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
77✔
906
    end
907

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

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

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

919
        if inc_tag === :string && close_path_completion(string, startpos, r, paths, pos)
29✔
920
            paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"")
2✔
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
29✔
925
    end
926

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

930
    # Make sure that only bslash_completions is working on strings
931
    inc_tag === :string && return Completion[], 0:-1, false
208✔
932
    if inc_tag === :other
203✔
933
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
160✔
934
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
160✔
935
            if ex.head === :call
80✔
936
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
77✔
937
            elseif is_broadcasting_expr(ex)
3✔
938
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
939
            end
940
        end
941
    elseif inc_tag === :comment
43✔
942
        return Completion[], 0:-1, false
1✔
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)
211✔
947
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
155✔
948

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

956
    ffunc = Returns(true)
89✔
957
    suggestions = Completion[]
89✔
958
    comp_keywords = true
×
959
    if afterusing(string, startpos)
89✔
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]
15✔
966
        if dotpos <= startpos
8✔
967
            for dir in Base.load_path()
7✔
968
                if basename(dir) in Base.project_names && isfile(dir)
43✔
969
                    append!(suggestions, project_deps_get_completion_candidates(s, dir))
8✔
970
                end
971
                isdir(dir) || continue
17✔
972
                for pname in readdir(dir)
8✔
973
                    if pname[1] != '.' && pname != "METADATA" &&
800✔
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))
65✔
980
                            endswith(pname, ".jl") && push!(suggestions,
×
981
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
982
                        else
983
                            mod_name = if endswith(pname, ".jl")
65✔
984
                                pname[1:prevind(pname, end-2)]
×
985
                            else
986
                                pname
65✔
987
                            end
988
                            if isfile(joinpath(dir, pname, "src",
65✔
989
                                               "$mod_name.jl"))
990
                                push!(suggestions, PackageCompletion(mod_name))
64✔
991
                            end
992
                        end
993
                    end
994
                end
423✔
995
            end
24✔
996
        end
997
        ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
8,246✔
998
        comp_keywords = false
×
999
    end
1000
    startpos == 0 && (pos = -1)
89✔
1001
    dotpos < startpos && (dotpos = startpos - 1)
89✔
1002
    s = string[startpos:pos]
172✔
1003
    comp_keywords && append!(suggestions, complete_keyword(s))
89✔
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] == '.'
172✔
1006
        i = prevind(string, startpos)
24✔
1007
        while 0 < i
51✔
1008
            c = string[i]
60✔
1009
            if c in (')', ']')
41✔
1010
                if c == ')'
27✔
1011
                    c_start = '('
×
1012
                    c_end = ')'
22✔
1013
                elseif c == ']'
5✔
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)
54✔
1018
                isempty(frange) && break # unbalanced parens
27✔
1019
                startpos = first(frange)
27✔
1020
                i = prevind(string, startpos)
27✔
1021
            elseif c in ('\'', '\"', '\`')
12✔
1022
                s = "$c$c"*string[startpos:pos]
×
1023
                break
×
1024
            else
1025
                break
×
1026
            end
1027
            s = string[startpos:pos]
54✔
1028
        end
27✔
1029
    end
1030
    append!(suggestions, complete_symbol(s, ffunc, context_module))
170✔
1031
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
89✔
1032
end
1033

1034
function shell_completions(string, pos)
15✔
1035
    # First parse everything up to the current position
1036
    scs = string[1:pos]
30✔
1037
    local args, last_parse
×
1038
    try
15✔
1039
        args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}}
15✔
1040
    catch
1041
        return Completion[], 0:-1, false
×
1042
    end
1043
    ex = args.args[end]::Expr
15✔
1044
    # Now look at the last thing we parsed
1045
    isempty(ex.args) && return Completion[], 0:-1, false
15✔
1046
    arg = ex.args[end]
15✔
1047
    if all(s -> isa(s, AbstractString), ex.args)
44✔
1048
        arg = arg::AbstractString
12✔
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] == ' '
23✔
1055
        prefix = ignore_last_word ? "" : join(ex.args)
23✔
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
12✔
1059

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

1070
function UndefVarError_hint(io::IO, ex::UndefVarError)
5✔
1071
    var = ex.var
5✔
1072
    if var === :or
5✔
1073
        print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.")
×
1074
    elseif var === :and
5✔
1075
        print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.")
×
1076
    elseif var === :help
5✔
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
5✔
1081
        print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
1082
    end
1083
end
1084

1085
function __init__()
446✔
1086
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
446✔
1087
    nothing
446✔
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