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

JuliaLang / julia / #37815

21 Jun 2024 05:00PM UTC coverage: 87.504% (+1.8%) from 85.685%
#37815

push

local

web-flow
Add docstring for inference barrier. (#54844)

`compilerbarrier` actually has a great docstring.
But it's most common form is via `inferencebarrier` which had none.

77230 of 88259 relevant lines covered (87.5%)

16071294.25 hits per line

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

88.1
/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 Core: Const
8
const CC = Core.Compiler
9
using Base.Meta
10
using Base: propertynames, something, IdSet
11
using Base.Filesystem: _readdirx
12

13
abstract type Completion end
14

15
struct TextCompletion <: Completion
16
    text::String
176✔
17
end
18

19
struct KeywordCompletion <: Completion
20
    keyword::String
181✔
21
end
22

23
struct KeyvalCompletion <: Completion
24
    keyval::String
15✔
25
end
26

27
struct PathCompletion <: Completion
28
    path::String
8,351✔
29
end
30

31
struct ModuleCompletion <: Completion
32
    parent::Module
623,015✔
33
    mod::String
34
end
35

36
struct PackageCompletion <: Completion
37
    package::String
262✔
38
end
39

40
struct PropertyCompletion <: Completion
41
    value
77✔
42
    property::Symbol
43
end
44

45
struct FieldCompletion <: Completion
46
    typ::DataType
18✔
47
    field::Symbol
48
end
49

50
struct MethodCompletion <: Completion
51
    tt # may be used by an external consumer to infer return type, etc.
52
    method::Method
53
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
4,443✔
54
end
55

56
struct BslashCompletion <: Completion
57
    bslash::String
7,826✔
58
end
59

60
struct ShellCompletion <: Completion
61
    text::String
62
end
63

64
struct DictCompletion <: Completion
65
    dict::AbstractDict
124✔
66
    key::String
67
end
68

69
struct KeywordArgumentCompletion <: Completion
70
    kwarg::String
47✔
71
end
72

73
# interface definition
74
function Base.getproperty(c::Completion, name::Symbol)
75
    if name === :text
8,768,573✔
76
        return getfield(c, :text)::String
176✔
77
    elseif name === :keyword
8,766,393✔
78
        return getfield(c, :keyword)::String
2,004✔
79
    elseif name === :path
8,758,568✔
80
        return getfield(c, :path)::String
34,538✔
81
    elseif name === :parent
8,758,568✔
82
        return getfield(c, :parent)::Module
×
83
    elseif name === :mod
15,559✔
84
        return getfield(c, :mod)::String
8,743,009✔
85
    elseif name === :package
11,537✔
86
        return getfield(c, :package)::String
4,022✔
87
    elseif name === :property
11,537✔
88
        return getfield(c, :property)::Symbol
271✔
89
    elseif name === :field
11,266✔
90
        return getfield(c, :field)::Symbol
35✔
91
    elseif name === :method
8,158✔
92
        return getfield(c, :method)::Method
4,445✔
93
    elseif name === :bslash
332✔
94
        return getfield(c, :bslash)::String
7,826✔
95
    elseif name === :text
332✔
96
        return getfield(c, :text)::String
×
97
    elseif name === :key
332✔
98
        return getfield(c, :key)::String
124✔
99
    elseif name === :kwarg
208✔
100
        return getfield(c, :kwarg)::String
97✔
101
    end
102
    return getfield(c, name)
111✔
103
end
104

105
_completion_text(c::TextCompletion) = c.text
176✔
106
_completion_text(c::KeywordCompletion) = c.keyword
1,877✔
107
_completion_text(c::KeyvalCompletion) = c.keyval
111✔
108
_completion_text(c::PathCompletion) = c.path
7,795✔
109
_completion_text(c::ModuleCompletion) = c.mod
4,876,292✔
110
_completion_text(c::PackageCompletion) = c.package
3,053✔
111
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
271✔
112
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
35✔
113
_completion_text(c::MethodCompletion) = repr(c.method)
3,061✔
114
_completion_text(c::BslashCompletion) = c.bslash
7,826✔
115
_completion_text(c::ShellCompletion) = c.text
×
116
_completion_text(c::DictCompletion) = c.key
124✔
117
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
97✔
118

119
completion_text(c) = _completion_text(c)::String
4,900,635✔
120

121
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
122

123
function completes_global(x, name)
124
    return startswith(x, name) && !('#' in x)
4,125,463✔
125
end
126

127
function appendmacro!(syms, macros, needle, endchar)
3,298✔
128
    for macsym in macros
3,298✔
129
        s = String(macsym)
53,664✔
130
        if endswith(s, needle)
53,664✔
131
            from = nextind(s, firstindex(s))
6,392✔
132
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,196✔
133
            push!(syms, s[from:to]*endchar)
6,392✔
134
        end
135
    end
53,664✔
136
end
137

138
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
1,649✔
139
                                    mod::Module, name::String, complete_internal_only::Bool)
140
    imported = usings = !complete_internal_only
1,649✔
141
    ssyms = names(mod; non_public=true, imported, usings)
1,649✔
142
    filter!(ffunc, ssyms)
1,649✔
143
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
2,374,290✔
144
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
621,468✔
145
    appendmacro!(syms, macros, "_str", "\"")
1,649✔
146
    appendmacro!(syms, macros, "_cmd", "`")
1,649✔
147
    for sym in syms
1,649✔
148
        push!(suggestions, ModuleCompletion(mod, sym))
623,015✔
149
    end
623,015✔
150
    return suggestions
1,649✔
151
end
152

153
# REPL Symbol Completions
154
function complete_symbol!(suggestions::Vector{Completion},
3,452✔
155
                          @nospecialize(prefix), name::String, context_module::Module;
156
                          complete_modules_only::Bool=false,
157
                          shift::Bool=false)
158
    local mod, t, val
1,726✔
159
    complete_internal_only = false
×
160
    if prefix !== nothing
1,726✔
161
        res = repl_eval_ex(prefix, context_module)
737✔
162
        res === nothing && return Completion[]
737✔
163
        if res isa Const
728✔
164
            val = res.val
706✔
165
            if isa(val, Module)
706✔
166
                mod = val
660✔
167
                if !shift
660✔
168
                    # when module is explicitly accessed, show internal bindings that are
169
                    # defined by the module, unless shift key is pressed
170
                    complete_internal_only = true
×
171
                end
172
            else
173
                t = typeof(val)
46✔
174
            end
175
        else
176
            t = CC.widenconst(res)
22✔
177
        end
178
    else
179
        mod = context_module
989✔
180
    end
181

182
    if @isdefined(mod) # lookup names available within the module
1,717✔
183
        let modname = nameof(mod),
1,649✔
184
            is_main = mod===Main
185
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
1,649✔
186
                if s === modname
2,422,764✔
187
                    return false # exclude `Main.Main.Main`, etc.
2,603✔
188
                elseif complete_modules_only && !completes_module(mod, s)
2,420,161✔
189
                    return false
46,556✔
190
                elseif is_main && s === :MainInclude
2,373,605✔
191
                    return false
964✔
192
                end
193
                return true
2,372,641✔
194
            end
195
        end
196
    elseif @isdefined(val) # looking for a property of an instance
68✔
197
        try
46✔
198
            for property in propertynames(val, false)
46✔
199
                # TODO: support integer arguments (#36872)
200
                if property isa Symbol && startswith(string(property), name)
83✔
201
                    push!(suggestions, PropertyCompletion(val, property))
77✔
202
                end
203
            end
83✔
204
        catch
1✔
205
        end
206
    elseif @isdefined(t) && field_completion_eligible(t)
22✔
207
        # Looking for a member of a type
208
        add_field_completions!(suggestions, name, t)
13✔
209
    end
210
    return suggestions
1,717✔
211
end
212

213
completes_module(mod::Module, x::Symbol) =
47,097✔
214
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
215

216
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
15✔
217
    if isa(t, Union)
15✔
218
        add_field_completions!(suggestions, name, t.a)
2✔
219
        add_field_completions!(suggestions, name, t.b)
2✔
220
    else
221
        @assert isconcretetype(t)
15✔
222
        fields = fieldnames(t)
15✔
223
        for field in fields
15✔
224
            isa(field, Symbol) || continue # Tuple type has ::Int field name
18✔
225
            s = string(field)
18✔
226
            if startswith(s, name)
18✔
227
                push!(suggestions, FieldCompletion(t, field))
18✔
228
            end
229
        end
18✔
230
    end
231
end
232

233
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
234

235
function field_completion_eligible(@nospecialize t)
4✔
236
    if isa(t, Union)
26✔
237
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
2✔
238
    end
239
    isconcretetype(t) || return false
31✔
240
    # field completion is correct only when `getproperty` fallbacks to `getfield`
241
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
17✔
242
    match === nothing && return false
17✔
243
    return match.method === GENERIC_PROPERTYNAMES_METHOD
17✔
244
end
245

246
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
1,346✔
247
    r = searchsorted(list, s)
1,346✔
248
    i = first(r)
1,346✔
249
    n = length(list)
1,346✔
250
    while i <= n && startswith(list[i],s)
1,542✔
251
        r = first(r):i
196✔
252
        i += 1
196✔
253
    end
196✔
254
    for kw in list[r]
1,346✔
255
        push!(suggestions, T(kw))
196✔
256
    end
196✔
257
    return suggestions
1,346✔
258
end
259

260
const sorted_keywords = [
261
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
262
    "const", "continue", "do", "else", "elseif", "end", "export",
263
    "finally", "for", "function", "global", "if", "import",
264
    "let", "local", "macro", "module", "mutable struct",
265
    "primitive type", "quote", "return", "struct",
266
    "try", "using", "while"]
267

268
complete_keyword!(suggestions::Vector{Completion}, s::String) =
604✔
269
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
270

271
const sorted_keyvals = ["false", "true"]
272

273
complete_keyval!(suggestions::Vector{Completion}, s::String) =
742✔
274
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
275

276
function do_raw_escape(s)
277
    # escape_raw_string with delim='`' and ignoring the rule for the ending \
278
    return replace(s, r"(\\+)`" => s"\1\\`")
×
279
end
280
function do_shell_escape(s)
281
    return Base.shell_escape_posixly(s)
4,281✔
282
end
283
function do_string_escape(s)
284
    return escape_string(s, ('\"','$'))
7,258✔
285
end
286

287
const PATH_cache_lock = Base.ReentrantLock()
288
const PATH_cache = Set{String}()
289
PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests
290
next_cache_update::Float64 = 0.0
291
function maybe_spawn_cache_PATH()
21✔
292
    global PATH_cache_task, next_cache_update
×
293
    @lock PATH_cache_lock begin
21✔
294
        PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return
21✔
295
        time() < next_cache_update && return
20✔
296
        PATH_cache_task = Threads.@spawn REPLCompletions.cache_PATH()
6✔
297
        Base.errormonitor(PATH_cache_task)
3✔
298
    end
299
end
300

301
# caches all reachable files in PATH dirs
302
function cache_PATH()
3✔
303
    path = get(ENV, "PATH", nothing)
6✔
304
    path isa String || return
3✔
305

306
    global next_cache_update
×
307

308
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
309
    # So keep track of what's added this time and at the end remove any that didn't appear this time from the global cache.
310
    this_PATH_cache = Set{String}()
3✔
311

312
    @debug "caching PATH files" PATH=path
3✔
313
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
3✔
314

315
    next_yield_time = time() + 0.01
3✔
316

317
    t = @elapsed for pathdir in pathdirs
3✔
318
        actualpath = try
18✔
319
            realpath(pathdir)
20✔
320
        catch ex
321
            ex isa Base.IOError || rethrow()
2✔
322
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
323
            continue
2✔
324
        end
325

326
        if actualpath != pathdir && in(actualpath, pathdirs)
27✔
327
            # Remove paths which (after resolving links) are in the env path twice.
328
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
329
            continue
3✔
330
        end
331

332
        path_entries = try
13✔
333
            _readdirx(pathdir)
14✔
334
        catch e
335
            # Bash allows dirs in PATH that can't be read, so we should as well.
336
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
337
                continue
1✔
338
            else
339
                # We only handle IOError and ArgumentError here
340
                rethrow()
×
341
            end
342
        end
343
        for entry in path_entries
12✔
344
            # In a perfect world, we would filter on whether the file is executable
345
            # here, or even on whether the current user can execute the file in question.
346
            try
2,440✔
347
                if isfile(entry)
4,880✔
348
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
2,395✔
349
                    push!(this_PATH_cache, entry.name)
2,396✔
350
                end
351
            catch e
352
                # `isfile()` can throw in rare cases such as when probing a
353
                # symlink that points to a file within a directory we do not
354
                # have read access to.
355
                if isa(e, Base.IOError)
1✔
356
                    continue
1✔
357
                else
358
                    rethrow()
×
359
                end
360
            end
361
            if time() >= next_yield_time
2,439✔
362
                yield() # to avoid blocking typing when -t1
2✔
363
                next_yield_time = time() + 0.01
2✔
364
            end
365
        end
2,440✔
366
    end
367

368
    @lock PATH_cache_lock begin
3✔
369
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
3✔
370
        next_cache_update = time() + 10 # earliest next update can run is 10s after
3✔
371
    end
372

373
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
3✔
374
    return PATH_cache
3✔
375
end
376

377
function complete_path(path::AbstractString;
1,128✔
378
                       use_envpath=false,
379
                       shell_escape=false,
380
                       raw_escape=false,
381
                       string_escape=false,
382
                       contract_user=false)
383
    @assert !(shell_escape && string_escape)
564✔
384
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
564✔
385
        # if the path is just "~", don't consider the expanded username as a prefix
386
        if path == "~"
×
387
            dir, prefix = homedir(), ""
×
388
        else
389
            dir, prefix = splitdir(homedir() * path[2:end])
×
390
        end
391
    else
392
        dir, prefix = splitdir(path)
564✔
393
    end
394
    entries = try
564✔
395
        if isempty(dir)
564✔
396
            _readdirx()
297✔
397
        elseif isdir(dir)
267✔
398
            _readdirx(dir)
108✔
399
        else
400
            return Completion[], dir, false
564✔
401
        end
402
    catch ex
403
        ex isa Base.IOError || rethrow()
×
404
        return Completion[], dir, false
×
405
    end
406

407
    matches = Set{String}()
405✔
408
    for entry in entries
405✔
409
        if startswith(entry.name, prefix)
46,573✔
410
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
15,246✔
411
            push!(matches, is_dir ? entry.name * "/" : entry.name)
14,277✔
412
        end
413
    end
46,573✔
414

415
    if use_envpath && isempty(dir)
405✔
416
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
417
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
418
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
21✔
419
        @lock PATH_cache_lock begin
21✔
420
            for file in PATH_cache
42✔
421
                startswith(file, prefix) && push!(matches, file)
9,727✔
422
            end
19,454✔
423
        end
424
    end
425

426
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
405✔
427
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
×
428
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
7,997✔
429
    return matches, dir, !isempty(matches)
405✔
430
end
431

432
function complete_path(path::AbstractString,
×
433
                       pos::Int;
434
                       use_envpath=false,
435
                       shell_escape=false,
436
                       string_escape=false,
437
                       contract_user=false)
438
    ## TODO: enable this depwarn once Pkg is fixed
439
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
440
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape)
×
441
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
442
        # if the path is just "~", don't consider the expanded username as a prefix
443
        if path == "~"
×
444
            dir, prefix = homedir(), ""
×
445
        else
446
            dir, prefix = splitdir(homedir() * path[2:end])
×
447
        end
448
    else
449
        dir, prefix = splitdir(path)
×
450
    end
451
    startpos = pos - lastindex(prefix) + 1
×
452
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
453
        # emulation for unnecessarily complicated return value, since / is a
454
        # perfectly acceptable path character which does not require quoting
455
        # but is required by Pkg's awkward parser handling
456
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
457
    end
458
    return paths, startpos:pos, success
×
459
end
460

461
function complete_expanduser(path::AbstractString, r)
548✔
462
    expanded =
548✔
463
        try expanduser(path)
549✔
464
        catch e
465
            e isa ArgumentError || rethrow()
1✔
466
            path
549✔
467
        end
468
    return Completion[PathCompletion(expanded)], r, path != expanded
548✔
469
end
470

471
# Returns a range that includes the method name in front of the first non
472
# closed start brace from the end of the string.
473
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,622✔
474
    r = reverse(s)
2,811✔
475
    i = firstindex(r)
×
476
    braces = in_comment = 0
×
477
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
478
    while i <= ncodeunits(r)
64,258✔
479
        c, i = iterate(r, i)
124,270✔
480
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
62,135✔
481
            c, i = iterate(r, i) # consume '='
8✔
482
            new_comments = 1
×
483
            # handle #=#=#=#, by counting =# pairs
484
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
485
                c, i = iterate(r, i) # consume '#'
6✔
486
                iterate(r, i)[1] == '=' || break
3✔
487
                c, i = iterate(r, i) # consume '='
4✔
488
                new_comments += 1
2✔
489
            end
2✔
490
            if c == '='
4✔
491
                in_comment += new_comments
3✔
492
            else
493
                in_comment -= new_comments
1✔
494
            end
495
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
62,131✔
496
            if c == c_start
60,366✔
497
                braces += 1
958✔
498
            elseif c == c_end
59,408✔
499
                braces -= 1
270✔
500
            elseif c == '\''
59,138✔
501
                in_single_quotes = true
×
502
            elseif c == '"'
59,122✔
503
                in_double_quotes = true
×
504
            elseif c == '`'
58,844✔
505
                in_back_ticks = true
×
506
            end
507
        else
508
            if in_single_quotes &&
1,765✔
509
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
510
                in_single_quotes = false
×
511
            elseif in_double_quotes &&
1,750✔
512
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
513
                in_double_quotes = false
×
514
            elseif in_back_ticks &&
1,522✔
515
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
516
                in_back_ticks = false
×
517
            elseif in_comment > 0 &&
1,515✔
518
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
519
                # handle =#=#=#=, by counting #= pairs
520
                c, i = iterate(r, i) # consume '#'
6✔
521
                old_comments = 1
×
522
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
523
                    c, i = iterate(r, i) # consume '='
4✔
524
                    iterate(r, i)[1] == '#' || break
2✔
525
                    c, i = iterate(r, i) # consume '#'
2✔
526
                    old_comments += 1
1✔
527
                end
1✔
528
                if c == '#'
3✔
529
                    in_comment -= old_comments
2✔
530
                else
531
                    in_comment += old_comments
1✔
532
                end
533
            end
534
        end
535
        braces == 1 && break
62,135✔
536
    end
61,447✔
537
    braces != 1 && return 0:-1, -1
2,811✔
538
    method_name_end = reverseind(s, i)
1,368✔
539
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,066✔
540
    return (startind:lastindex(s), method_name_end)
688✔
541
end
542

543
struct REPLCacheToken end
544

545
struct REPLInterpreter <: CC.AbstractInterpreter
546
    limit_aggressive_inference::Bool
547
    world::UInt
548
    inf_params::CC.InferenceParams
549
    opt_params::CC.OptimizationParams
550
    inf_cache::Vector{CC.InferenceResult}
551
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
521✔
552
                             world::UInt = Base.get_world_counter(),
553
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
554
                                 aggressive_constant_propagation=true,
555
                                 unoptimize_throw_blocks=false),
556
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
557
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
558
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
515✔
559
    end
560
end
561
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
351,240✔
562
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
563
CC.get_inference_world(interp::REPLInterpreter) = interp.world
153,013✔
564
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
70,831✔
565
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
566

567
# REPLInterpreter is only used for type analysis, so it should disable optimization entirely
568
CC.may_optimize(::REPLInterpreter) = false
×
569

570
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
571
CC.transform_result_for_cache(::REPLInterpreter, ::Core.MethodInstance, ::CC.WorldRange, ::CC.InferenceResult) = nothing
×
572

573
# REPLInterpreter analyzes a top-level frame, so better to not bail out from it
574
CC.bail_out_toplevel_call(::REPLInterpreter, ::CC.InferenceLoopState, ::CC.InferenceState) = false
×
575

576
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
577
# for lines like `Mod.a.|` (where `|` is the cursor position).
578
# Aggressive binding resolution poses challenges for the inference cache validation
579
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
580
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
581
# resolution for top-level frame representing REPL input code and for child uncached frames
582
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
583
# those global bindings are not constant and may be mutated in the future, since:
584
# a.) "repl-frame"s are never cached, and
585
# b.) mutable values are never observed by any cached frames.
586
#
587
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
588
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
589
# Aggressive concrete evaluation allows us to get accurate type information about complex
590
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
591
# doesn't evaluate effectful expressions like `pop!(xs)`.
592
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
593
# present any cache validation issues because "repl-frame" is never cached.
594

595
# `REPLInterpreter` is specifically used by `repl_eval_ex`, where all top-level frames are
596
# `repl_frame` always. However, this assumption wouldn't stand if `REPLInterpreter` were to
597
# be employed, for instance, by `typeinf_ext_toplevel`.
598
is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode === CC.CACHE_MODE_NULL
191✔
599

600
function is_call_graph_uncached(sv::CC.InferenceState)
425,786✔
601
    CC.is_cached(sv) && return false
588,965✔
602
    parent = sv.parent
434,058✔
603
    parent === nothing && return true
434,058✔
604
    return is_call_graph_uncached(parent::CC.InferenceState)
425,786✔
605
end
606

607
# aggressive global binding resolution within `repl_frame`
608
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
609
                                    sv::CC.InferenceState)
610
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
205,225✔
611
        if CC.isdefined_globalref(g)
5,581✔
612
            return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL)
5,575✔
613
        end
614
        return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
6✔
615
    end
616
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
97,098✔
617
                                              sv::CC.InferenceState)
618
end
619

620
function is_repl_frame_getproperty(sv::CC.InferenceState)
1✔
621
    def = sv.linfo.def
1✔
622
    def isa Method || return false
1✔
623
    def.name === :getproperty || return false
1✔
624
    CC.is_cached(sv) && return false
1✔
625
    return is_repl_frame(sv.parent)
1✔
626
end
627

628
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
629
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
30,281✔
630
                              argtypes::Vector{Any}, sv::CC.InferenceState)
631
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
30,363✔
632
        if length(argtypes) == 2
57✔
633
            a1, a2 = argtypes
57✔
634
            if isa(a1, Const) && isa(a2, Const)
57✔
635
                a1val, a2val = a1.val, a2.val
57✔
636
                if isa(a1val, Module) && isa(a2val, Symbol)
57✔
637
                    g = GlobalRef(a1val, a2val)
57✔
638
                    if CC.isdefined_globalref(g)
57✔
639
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
57✔
640
                    end
641
                    return Union{}
×
642
                end
643
            end
644
        end
645
    end
646
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
30,224✔
647
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
648
end
649

650
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
651
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
60,608✔
652
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
653
                                   sv::CC.InferenceState)
654
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
121,159✔
655
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
2,644✔
656
        result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited,
5,286✔
657
                                     result.edge, neweffects)
658
    end
659
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
121,216✔
660
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
661
                                            sv::CC.InferenceState)
662
    if ret === :semi_concrete_eval
60,608✔
663
        # while the base eligibility check probably won't permit semi-concrete evaluation
664
        # for `REPLInterpreter` (given it completely turns off optimization),
665
        # this ensures we don't inadvertently enter irinterp
666
        ret = :none
×
667
    end
668
    return ret
60,608✔
669
end
670

671
# allow constant propagation for mutable constants
672
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
673
    if !interp.limit_aggressive_inference
59,880✔
674
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
119,747✔
675
    end
676
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
99✔
677
end
678

679
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
680
    @ccall jl_resolve_globals_in_ir(
509✔
681
        #=jl_array_t *stmts=# src.code::Any,
682
        #=jl_module_t *m=# mod::Any,
683
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
684
        #=int binding_effects=# 0::Int)::Cvoid
685
    return src
×
686
end
687

688
# lower `ex` and run type inference on the resulting top-level expression
689
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,740✔
690
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
2,739✔
691
        # get the inference result for the last expression
692
        ex = ex.args[end]
3✔
693
    end
694
    lwr = try
1,370✔
695
        Meta.lower(context_module, ex)
1,370✔
696
    catch # macro expansion failed, etc.
697
        return nothing
5✔
698
    end
699
    if lwr isa Symbol
1,365✔
700
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
724✔
701
    end
702
    lwr isa Expr || return Const(lwr) # `ex` is literal
743✔
703
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
569✔
704
    src = lwr.args[1]::Core.CodeInfo
509✔
705

706
    resolve_toplevel_symbols!(src, context_module)
509✔
707
    # construct top-level `MethodInstance`
708
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
509✔
709

710
    interp = REPLInterpreter(limit_aggressive_inference)
509✔
711
    result = CC.InferenceResult(mi)
509✔
712
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
509✔
713

714
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
715
    #      potential invalidations of `Core.Compiler` methods.
716
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
509✔
717

718
    result = frame.result.result
509✔
719
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
509✔
720
    return result
500✔
721
end
722

723
# `COMPLETION_WORLD[]` will be initialized within `__init__`
724
# (to allow us to potentially remove REPL from the sysimage in the future).
725
# Note that inference from the `code_typed` call below will use the current world age
726
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
727
# when the given world age is higher than the current one.
728
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
729

730
# Generate code cache for `REPLInterpreter` now:
731
# This code cache will be available at the world of `COMPLETION_WORLD`,
732
# assuming no invalidation will happen before initializing REPL.
733
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
734
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
735

736
# Method completion on function call expression that look like :(max(1))
737
MAX_METHOD_COMPLETIONS::Int = 40
738
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
311✔
739
    funct = repl_eval_ex(ex_org.args[1], context_module)
311✔
740
    funct === nothing && return 2, nothing, [], Set{Symbol}()
311✔
741
    funct = CC.widenconst(funct)
306✔
742
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
306✔
743
    return kwargs_flag, funct, args_ex, kwargs_ex
306✔
744
end
745

746
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
1✔
747
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
141✔
748
    out = Completion[]
140✔
749
    kwargs_flag == 2 && return out # one of the kwargs is invalid
140✔
750
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
127✔
751
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
184✔
752
    return out
127✔
753
end
754

755
MAX_ANY_METHOD_COMPLETIONS::Int = 10
756
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
64✔
757
    push!(exploredmodules, callee_module)
78✔
758
    for name in names(callee_module; non_public=true, imported=true)
64✔
759
        if isdefined(initial_module, name) # TODO use `usings=true` instead here?
6,678✔
760
            func = getglobal(initial_module, name)
2,031✔
761
            if !isa(func, Module)
2,031✔
762
                funct = Core.Typeof(func)
3,541✔
763
                push!(seen, funct)
1,914✔
764
            elseif isa(func, Module) && func ∉ exploredmodules
117✔
765
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
50✔
766
            end
767
        end
768
    end
6,678✔
769
end
770
function recursive_explore_names(callee_module::Module, initial_module::Module)
771
    seen = IdSet{Any}()
14✔
772
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
773
    seen
×
774
end
775

776
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
14✔
777
    out = Completion[]
14✔
778
    args_ex, kwargs_ex, kwargs_flag = try
14✔
779
        # this may throw, since we set default_any to false
780
        complete_methods_args(ex_org, context_module, false, false)
14✔
781
    catch ex
782
        ex isa ArgumentError || rethrow()
×
783
        return out
14✔
784
    end
785
    kwargs_flag == 2 && return out # one of the kwargs is invalid
14✔
786

787
    # moreargs determines whether to accept more args, independently of the presence of a
788
    # semicolon for the ".?(" syntax
789
    moreargs && push!(args_ex, Vararg{Any})
14✔
790

791
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
792
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,620✔
793
    end
3,226✔
794

795
    if !shift
14✔
796
        # Filter out methods where all arguments are `Any`
797
        filter!(out) do c
2✔
798
            isa(c, TextCompletion) && return false
12✔
799
            isa(c, MethodCompletion) || return true
12✔
800
            sig = Base.unwrap_unionall(c.method.sig)::DataType
12✔
801
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
20✔
802
        end
803
    end
804

805
    return out
14✔
806
end
807

808
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
809
    n = isexpr(x, :kw) ? x.args[1] : x
55✔
810
    if n isa Symbol
55✔
811
        push!(kwargs_ex, n)
41✔
812
        return kwargs_flag
41✔
813
    end
814
    possible_splat && isexpr(x, :...) && return kwargs_flag
13✔
815
    return 2 # The kwarg is invalid
10✔
816
end
817

818
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
320✔
819
    args_ex = Any[]
320✔
820
    kwargs_ex = Symbol[]
320✔
821
    kwargs_flag = 0
×
822
    # kwargs_flag is:
823
    # * 0 if there is no semicolon and no invalid kwarg
824
    # * 1 if there is a semicolon and no invalid kwarg
825
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
826
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
827
    for i in (1+!broadcasting):length(funargs)
476✔
828
        ex = funargs[i]
295✔
829
        if isexpr(ex, :parameters)
295✔
830
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
59✔
831
            for x in ex.args
59✔
832
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
833
            end
33✔
834
        elseif isexpr(ex, :kw)
236✔
835
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
836
        else
837
            if broadcasting
214✔
838
                # handle broadcasting, but only handle number of arguments instead of
839
                # argument types
840
                push!(args_ex, Any)
5✔
841
            else
842
                argt = repl_eval_ex(ex, context_module)
209✔
843
                if argt !== nothing
209✔
844
                    push!(args_ex, CC.widenconst(argt))
183✔
845
                elseif default_any
26✔
846
                    push!(args_ex, Any)
26✔
847
                else
848
                    throw(ArgumentError("argument not found"))
×
849
                end
850
            end
851
        end
852
    end
426✔
853
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
320✔
854
end
855

856
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,895✔
857

858
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
859
    if allow_broadcasting && is_broadcasting_expr(ex)
306✔
860
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
861
    end
862
    return detect_args_kwargs(ex.args, context_module, default_any, false)
314✔
863
end
864

865
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
1,916✔
866
    # Input types and number of arguments
867
    t_in = Tuple{funct, args_ex...}
1,916✔
868
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
1,916✔
869
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
870
    if !isa(m, Vector)
1,916✔
871
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
176✔
872
        return
176✔
873
    end
874
    for match in m
1,740✔
875
        # TODO: if kwargs_ex, filter out methods without kwargs?
876
        push!(out, MethodCompletion(match.spec_types, match.method))
4,443✔
877
    end
4,443✔
878
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
879
end
880

881
include("latex_symbols.jl")
882
include("emoji_symbols.jl")
883

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

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

896
# Aux function to detect whether we're right after a using or import keyword
897
function get_import_mode(s::String)
1,585✔
898
    # match simple cases like `using |` and `import  |`
899
    mod_import_match_simple = match(r"^\b(using|import)\s*$", s)
1,585✔
900
    if mod_import_match_simple !== nothing
1,585✔
901
        if mod_import_match_simple[1] == "using"
12✔
902
            return :using_module
4✔
903
        else
904
            return :import_module
2✔
905
        end
906
    end
907
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
908
    mod_import_match = match(r"^\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
1,579✔
909
    if mod_import_match !== nothing
1,579✔
910
        if mod_import_match.captures[1] == "using"
54✔
911
            return :using_module
20✔
912
        else
913
            return :import_module
7✔
914
        end
915
    end
916
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
917
    name_import_match = match(r"^\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
1,552✔
918
    if name_import_match !== nothing
1,552✔
919
        if name_import_match[1] == "using"
10✔
920
            return :using_name
5✔
921
        else
922
            return :import_name
×
923
        end
924
    end
925
    return nothing
1,547✔
926
end
927

928
function close_path_completion(dir, paths, str, pos)
142✔
929
    length(paths) == 1 || return false  # Only close if there's a single choice...
268✔
930
    path = (paths[1]::PathCompletion).path
16✔
931
    path = unescape_string(replace(path, "\\\$"=>"\$"))
32✔
932
    path = joinpath(dir, path)
16✔
933
    # ...except if it's a directory...
934
    try
16✔
935
        isdir(path)
17✔
936
    catch e
937
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
17✔
938
    end && return false
939
    # ...and except if there's already a " at the cursor.
940
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
6✔
941
end
942

943
function bslash_completions(string::String, pos::Int, hint::Bool=false)
2,014✔
944
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
4,086✔
945
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
2,042✔
946
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
947
        # latex / emoji symbol substitution
948
        s = string[slashpos:pos]
112✔
949
        latex = get(latex_symbols, s, "")
78✔
950
        if !isempty(latex) # complete an exact match
56✔
951
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
22✔
952
        elseif occursin(subscript_regex, s)
34✔
953
            sub = map(c -> subscripts[c], s[3:end])
37✔
954
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
955
        elseif occursin(superscript_regex, s)
28✔
956
            sup = map(c -> superscripts[c], s[3:end])
8✔
957
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
958
        end
959
        emoji = get(emoji_symbols, s, "")
29✔
960
        if !isempty(emoji)
27✔
961
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
962
        end
963
        # return possible matches; these cannot be mixed with regular
964
        # Julian completions as only latex / emoji symbols contain the leading \
965
        if startswith(s, "\\:") # emoji
25✔
966
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
1,243✔
967
        else # latex
968
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
61,176✔
969
        end
970
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
25✔
971
    end
972
    return (false, (Completion[], 0:-1, false))
1,958✔
973
end
974

975
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,149✔
976
    if tag === :string
2,149✔
977
        str_close = str*"\""
164✔
978
    elseif tag === :cmd
1,984✔
979
        str_close = str*"`"
5✔
980
    else
981
        str_close = str
×
982
    end
983
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,148✔
984
    isempty(frange) && return (nothing, nothing, nothing)
2,148✔
985
    objstr = str[1:end_of_identifier]
212✔
986
    objex = Meta.parse(objstr, raise=false, depwarn=false)
106✔
987
    objt = repl_eval_ex(objex, context_module)
106✔
988
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
130✔
989
    obj = objt.val
82✔
990
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
83✔
991
    length(obj)::Int < 1_000_000 || return (nothing, nothing, nothing)
81✔
992
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
993
                             lastindex(str)+1)
994
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
995
end
996

997
# This needs to be a separate non-inlined function, see #19441
998
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
999
    matches = String[]
80✔
1000
    for key in keys(identifier)
122✔
1001
        rkey = repr(key)
933✔
1002
        startswith(rkey,partial_key) && push!(matches,rkey)
933✔
1003
    end
1,410✔
1004
    return matches
80✔
1005
end
1006

1007
# Identify an argument being completed in a method call. If the argument is empty, method
1008
# suggestions will be provided instead of argument completions.
1009
function identify_possible_method_completion(partial, last_idx)
2,747✔
1010
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,747✔
1011

1012
    # First, check that the last punctuation is either ',', ';' or '('
1013
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,391✔
1014
    idx_last_punct == 0 && return fail
2,747✔
1015
    last_punct = partial[idx_last_punct]
4,436✔
1016
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,225✔
1017

1018
    # Then, check that `last_punct` is only followed by an identifier or nothing
1019
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,506✔
1020
    before_last_word_start == 0 && return fail
753✔
1021
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
857✔
1022

1023
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1024
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
649✔
1025
    method_name_end ∈ frange || return fail
815✔
1026

1027
    # Strip the preceding ! operators, if any, and close the expression with a ')'
1028
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
966✔
1029
    ex = Meta.parse(s, raise=false, depwarn=false)
483✔
1030
    isa(ex, Expr) || return fail
483✔
1031

1032
    # `wordrange` is the position of the last argument to complete
1033
    wordrange = nextind(partial, before_last_word_start):last_idx
622✔
1034
    return frange, ex, wordrange, method_name_end
483✔
1035
end
1036

1037
# Provide completion for keyword arguments in function calls
1038
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
3,508✔
1039
                                   shift::Bool=false)
1040
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,754✔
1041
    fail = Completion[], 0:-1, frange
1,754✔
1042
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,340✔
1043

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

1047
    methods = Completion[]
169✔
1048
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
169✔
1049
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
1050
    # method calls compatible with the current arguments.
1051

1052
    # For each method corresponding to the function call, provide completion suggestions
1053
    # for each keyword that starts like the last word and that is not already used
1054
    # previously in the expression. The corresponding suggestion is "kwname=".
1055
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1056
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
1057
    last_word = partial[wordrange] # the word to complete
338✔
1058
    kwargs = Set{String}()
169✔
1059
    for m in methods
169✔
1060
        m::MethodCompletion
1,372✔
1061
        possible_kwargs = Base.kwarg_decl(m.method)
1,372✔
1062
        current_kwarg_candidates = String[]
1,372✔
1063
        for _kw in possible_kwargs
1,372✔
1064
            kw = String(_kw)
478✔
1065
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
511✔
1066
                push!(current_kwarg_candidates, kw)
67✔
1067
            end
1068
        end
478✔
1069
        union!(kwargs, current_kwarg_candidates)
1,372✔
1070
    end
1,372✔
1071

1072
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
1073

1074
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1075
    if kwargs_flag == 0
169✔
1076
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
138✔
1077
        complete_keyval!(suggestions, last_word)
138✔
1078
    end
1079

1080
    return sort!(suggestions, by=completion_text), wordrange
169✔
1081
end
1082

1083
function get_loading_candidates(pkgstarts::String, project_file::String)
1✔
1084
    loading_candidates = String[]
1✔
1085
    d = Base.parsed_toml(project_file)
1✔
1086
    pkg = get(d, "name", nothing)::Union{String, Nothing}
2✔
1087
    if pkg !== nothing && startswith(pkg, pkgstarts)
1✔
1088
        push!(loading_candidates, pkg)
1✔
1089
    end
1090
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2✔
1091
    if deps !== nothing
1✔
1092
        for (pkg, _) in deps
2✔
1093
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
1✔
1094
        end
1✔
1095
    end
1096
    return loading_candidates
1✔
1097
end
1098

1099
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1100
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1101
        push!(suggestions, PackageCompletion(name))
2✔
1102
    end
2✔
1103
    return suggestions
1✔
1104
end
1105

1106
function complete_identifiers!(suggestions::Vector{Completion},
3,176✔
1107
                               context_module::Module, string::String, name::String,
1108
                               pos::Int, separatorpos::Int, startpos::Int;
1109
                               comp_keywords::Bool=false,
1110
                               complete_modules_only::Bool=false,
1111
                               shift::Bool=false)
1112
    if comp_keywords
1,588✔
1113
        complete_keyword!(suggestions, name)
604✔
1114
        complete_keyval!(suggestions, name)
604✔
1115
    end
1116
    if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
1,593✔
1117
        s = string[1:prevind(string, separatorpos)]
1,654✔
1118
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
1119
        prefix = Meta.parse(s, raise=false, depwarn=false)
827✔
1120
        if isexpr(prefix, :incomplete)
827✔
1121
            s = string[startpos:pos]
1,197✔
1122
            # Heuristic to find the start of the expression. TODO: This would be better
1123
            # done with a proper error-recovering parser.
1124
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,197✔
1125
                i = prevind(string, startpos)
2✔
1126
                while 0 < i
2✔
1127
                    c = string[i]
4✔
1128
                    if c in (')', ']')
4✔
1129
                        if c == ')'
×
1130
                            c_start = '('
×
1131
                            c_end = ')'
×
1132
                        elseif c == ']'
×
1133
                            c_start = '['
×
1134
                            c_end = ']'
×
1135
                        end
1136
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1137
                        isempty(frange) && break # unbalanced parens
×
1138
                        startpos = first(frange)
×
1139
                        i = prevind(string, startpos)
×
1140
                    elseif c in ('\'', '\"', '\`')
4✔
1141
                        s = "$c$c"*string[startpos:pos]
×
1142
                        break
×
1143
                    else
1144
                        break
×
1145
                    end
1146
                    s = string[startpos:pos]
×
1147
                end
×
1148
            end
1149
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,181✔
1150
                lookup_name, name = rsplit(s, ".", limit=2)
546✔
1151
                name = String(name)
546✔
1152
                prefix = Meta.parse(lookup_name, raise=false, depwarn=false)
546✔
1153
            end
1154
            isexpr(prefix, :incomplete) && (prefix = nothing)
635✔
1155
        elseif isexpr(prefix, (:using, :import))
192✔
1156
            arglast = prefix.args[end] # focus on completion to the last argument
16✔
1157
            if isexpr(arglast, :.)
16✔
1158
                # We come here for cases like:
1159
                # - `string`: "using Mod1.Mod2.M"
1160
                # - `ex`: :(using Mod1.Mod2)
1161
                # - `name`: "M"
1162
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1163
                # complete for inner modules whose name starts with `M`.
1164
                # Note that `complete_modules_only=true` is set within `completions`
1165
                prefix = nothing
16✔
1166
                firstdot = true
×
1167
                for arg = arglast.args
16✔
1168
                    if arg === :.
32✔
1169
                        # override `context_module` if multiple `.` accessors are used
1170
                        if firstdot
15✔
1171
                            firstdot = false
×
1172
                        else
1173
                            context_module = parentmodule(context_module)
3✔
1174
                        end
1175
                    elseif arg isa Symbol
17✔
1176
                        if prefix === nothing
17✔
1177
                            prefix = arg
16✔
1178
                        else
1179
                            prefix = Expr(:., prefix, QuoteNode(arg))
1✔
1180
                        end
1181
                    else # invalid expression
1182
                        prefix = nothing
×
1183
                        break
×
1184
                    end
1185
                end
32✔
1186
            end
1187
        elseif isexpr(prefix, :call) && length(prefix.args) > 1
176✔
1188
            isinfix = s[end] != ')'
37✔
1189
            # A complete call expression that does not finish with ')' is an infix call.
1190
            if !isinfix
19✔
1191
                # Handle infix call argument completion of the form bar + foo(qux).
1192
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
28✔
1193
                isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == prefix.args[end]
14✔
1194
            end
1195
            if isinfix
19✔
1196
                prefix = prefix.args[end]
8✔
1197
            end
1198
        elseif isexpr(prefix, :macrocall) && length(prefix.args) > 1
157✔
1199
            # allow symbol completions within potentially incomplete macrocalls
1200
            if s[end] ≠ '`' && s[end] ≠ ')'
43✔
1201
                prefix = prefix.args[end]
12✔
1202
            end
1203
        end
1204
    else
1205
        prefix = nothing
×
1206
    end
1207
    complete_symbol!(suggestions, prefix, name, context_module; complete_modules_only, shift)
1,588✔
1208
    return suggestions
1,588✔
1209
end
1210

1211
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,162✔
1212
    # First parse everything up to the current position
1213
    partial = string[1:pos]
4,716✔
1214
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,161✔
1215

1216
    # ?(x, y)TAB lists methods you can call with these objects
1217
    # ?(x, y TAB lists methods that take these objects as the first two arguments
1218
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
1219
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
2,161✔
1220
    if rexm !== nothing
2,161✔
1221
        # Get the module scope
1222
        if isempty(rexm.captures[1])
28✔
1223
            callee_module = context_module
×
1224
        else
1225
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
1226
            if isdefined(context_module, modname)
13✔
1227
                callee_module = getfield(context_module, modname)
13✔
1228
                if !isa(callee_module, Module)
13✔
1229
                    callee_module = context_module
×
1230
                end
1231
            else
1232
                callee_module = context_module
×
1233
            end
1234
        end
1235
        moreargs = !endswith(rexm.captures[2], ')')
14✔
1236
        callstr = "_(" * rexm.captures[2]
14✔
1237
        if moreargs
14✔
1238
            callstr *= ')'
8✔
1239
        end
1240
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
14✔
1241
        if isa(ex_org, Expr)
14✔
1242
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
14✔
1243
        end
1244
    end
1245

1246
    # if completing a key in a Dict
1247
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,227✔
1248
    if identifier !== nothing
2,147✔
1249
        matches = find_dict_matches(identifier, partial_key)
80✔
1250
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1251
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1252
    end
1253

1254
    suggestions = Completion[]
2,083✔
1255

1256
    # Check if this is a var"" string macro that should be completed like
1257
    # an identifier rather than a string.
1258
    # TODO: It would be nice for the parser to give us more information here
1259
    # so that we can lookup the macro by identity rather than pattern matching
1260
    # its invocation.
1261
    varrange = findprev("var\"", string, pos)
2,083✔
1262

1263
    expanded = nothing
×
1264
    was_expanded = false
2,083✔
1265

1266
    if varrange !== nothing
2,083✔
1267
        ok, ret = bslash_completions(string, pos)
3✔
1268
        ok && return ret
3✔
1269
        startpos = first(varrange) + 4
3✔
1270
        separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1271
        name = string[startpos:pos]
5✔
1272
        complete_identifiers!(suggestions, context_module, string, name,
3✔
1273
                              pos, separatorpos, startpos;
1274
                              shift)
1275
        return sort!(unique!(completion_text, suggestions), by=completion_text), (separatorpos+1):pos, true
3✔
1276
    elseif inc_tag === :cmd
2,080✔
1277
        # TODO: should this call shell_completions instead of partially reimplementing it?
1278
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
1✔
1279
            startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1280
            r = startpos:pos
1✔
1281
            scs::String = string[r]
2✔
1282

1283
            expanded = complete_expanduser(scs, r)
1✔
1284
            was_expanded = expanded[3]
1✔
1285
            if was_expanded
1✔
1286
                scs = (only(expanded[1])::PathCompletion).path
×
1287
                # If tab press, ispath and user expansion available, return it now
1288
                # otherwise see if we can complete the path further before returning with expanded ~
1289
                !hint && ispath(scs) && return expanded::Completions
×
1290
            end
1291

1292
            path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many
1✔
1293
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1294
            # assumes the path isn't further quoted within the cmd backticks.
1295
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
1✔
1296
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
1✔
1297

1298
            if success && !isempty(dir)
1✔
1299
                let dir = do_raw_escape(do_shell_escape(dir))
×
1300
                    # if escaping of dir matches scs prefix, remove that from the completions
1301
                    # otherwise make it the whole completion
1302
                    if endswith(dir, "/") && startswith(scs, dir)
×
1303
                        r = (startpos + sizeof(dir)):pos
×
1304
                    elseif startswith(scs, dir * "/")
×
1305
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1306
                    else
1307
                        map!(paths, paths) do c::PathCompletion
×
1308
                            p = dir * "/" * c.path
×
1309
                            was_expanded && (p = contractuser(p))
×
1310
                            return PathCompletion(p)
×
1311
                        end
1312
                    end
1313
                end
1314
            end
1315
            if isempty(paths) && !hint && was_expanded
1✔
1316
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1317
                return expanded::Completions
×
1318
            else
1319
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
1✔
1320
            end
1321
        end
1322
    elseif inc_tag === :string
2,079✔
1323
        # Find first non-escaped quote
1324
        let m = match(r"\"(?!\\)", reverse(partial))
143✔
1325
            startpos = nextind(partial, reverseind(partial, m.offset))
286✔
1326
            r = startpos:pos
166✔
1327
            scs::String = string[r]
263✔
1328

1329
            expanded = complete_expanduser(scs, r)
143✔
1330
            was_expanded = expanded[3]
143✔
1331
            if was_expanded
143✔
1332
                scs = (only(expanded[1])::PathCompletion).path
2✔
1333
                # If tab press, ispath and user expansion available, return it now
1334
                # otherwise see if we can complete the path further before returning with expanded ~
1335
                !hint && ispath(scs) && return expanded::Completions
2✔
1336
            end
1337

1338
            path = try
142✔
1339
                unescape_string(replace(scs, "\\\$"=>"\$"))
261✔
1340
            catch ex
1341
                ex isa ArgumentError || rethrow()
×
1342
                nothing
142✔
1343
            end
1344
            if !isnothing(path)
284✔
1345
                paths, dir, success = complete_path(path::String, string_escape=true)
142✔
1346

1347
                if close_path_completion(dir, paths, path, pos)
142✔
1348
                    p = (paths[1]::PathCompletion).path * "\""
6✔
1349
                    hint && was_expanded && (p = contractuser(p))
6✔
1350
                    paths[1] = PathCompletion(p)
6✔
1351
                end
1352

1353
                if success && !isempty(dir)
142✔
1354
                    let dir = do_string_escape(dir)
18✔
1355
                        # if escaping of dir matches scs prefix, remove that from the completions
1356
                        # otherwise make it the whole completion
1357
                        if endswith(dir, "/") && startswith(scs, dir)
9✔
1358
                            r = (startpos + sizeof(dir)):pos
1✔
1359
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
8✔
1360
                            was_expanded && (dir = contractuser(dir))
8✔
1361
                            r = nextind(string, startpos + sizeof(dir)):pos
8✔
1362
                        else
1363
                            map!(paths, paths) do c::PathCompletion
×
1364
                                p = dir * "/" * c.path
×
1365
                                hint && was_expanded && (p = contractuser(p))
×
1366
                                return PathCompletion(p)
×
1367
                            end
1368
                        end
1369
                    end
1370
                end
1371

1372
                # Fallthrough allowed so that Latex symbols can be completed in strings
1373
                if success
142✔
1374
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
26,784✔
1375
                elseif !hint && was_expanded
71✔
1376
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1377
                    return expanded::Completions
1✔
1378
                end
1379
            end
1380
        end
1381
    end
1382
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1383
    was_expanded && return expanded::Completions
2,006✔
1384

1385
    ok, ret = bslash_completions(string, pos)
2,006✔
1386
    ok && return ret
2,006✔
1387

1388
    # Make sure that only bslash_completions is working on strings
1389
    inc_tag === :string && return Completion[], 0:-1, false
1,955✔
1390
    if inc_tag === :other
1,894✔
1391
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
993✔
1392
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
993✔
1393
            if ex.head === :call
139✔
1394
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
136✔
1395
            elseif is_broadcasting_expr(ex)
3✔
1396
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1397
            end
1398
        end
1399
    elseif inc_tag === :comment
901✔
1400
        return Completion[], 0:-1, false
1✔
1401
    end
1402

1403
    # Check whether we can complete a keyword argument in a function call
1404
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
3,339✔
1405
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,923✔
1406

1407
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,809✔
1408
    # strip preceding ! operator
1409
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,585✔
1410
        startpos += length(m.match)
2✔
1411
    end
1412

1413
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
2,409✔
1414
    namepos = max(startpos, separatorpos+1)
1,585✔
1415
    name = string[namepos:pos]
2,773✔
1416
    import_mode = get_import_mode(string)
1,585✔
1417
    if import_mode === :using_module || import_mode === :import_module
3,146✔
1418
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1419
        # Let's look only for packages and modules we can reach from here
1420

1421
        # If there's no dot, we're in toplevel, so we should
1422
        # also search for packages
1423
        s = string[startpos:pos]
62✔
1424
        if separatorpos <= startpos
33✔
1425
            for dir in Base.load_path()
21✔
1426
                if basename(dir) in Base.project_names && isfile(dir)
48✔
1427
                    complete_loading_candidates!(suggestions, s, dir)
1✔
1428
                end
1429
                isdir(dir) || continue
24✔
1430
                for entry in _readdirx(dir)
22✔
1431
                    pname = entry.name
1,283✔
1432
                    if pname[1] != '.' && pname != "METADATA" &&
2,566✔
1433
                        pname != "REQUIRE" && startswith(pname, s)
1434
                        # Valid file paths are
1435
                        #   <Mod>.jl
1436
                        #   <Mod>/src/<Mod>.jl
1437
                        #   <Mod>.jl/src/<Mod>.jl
1438
                        if isfile(entry)
522✔
1439
                            endswith(pname, ".jl") && push!(suggestions,
×
1440
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1441
                        else
1442
                            mod_name = if endswith(pname, ".jl")
261✔
1443
                                pname[1:prevind(pname, end-2)]
×
1444
                            else
1445
                                pname
261✔
1446
                            end
1447
                            if isfile(joinpath(entry, "src",
261✔
1448
                                               "$mod_name.jl"))
1449
                                push!(suggestions, PackageCompletion(mod_name))
260✔
1450
                            end
1451
                        end
1452
                    end
1453
                end
1,283✔
1454
            end
24✔
1455
        end
1456
        comp_keywords = false
33✔
1457
        complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
33✔
1458
    elseif import_mode === :using_name || import_mode === :import_name
3,099✔
1459
        # `using Foo: |` and `import Foo: bar, baz|`
1460
        separatorpos = findprev(isequal(':'), string, pos)::Int
5✔
1461
        comp_keywords = false
×
1462
        complete_modules_only = false
×
1463
    else
1464
        comp_keywords = !isempty(name) && startpos > separatorpos
1,547✔
1465
        complete_modules_only = false
×
1466
    end
1467

1468
    complete_identifiers!(suggestions, context_module, string, name,
1,585✔
1469
                          pos, separatorpos, startpos;
1470
                          comp_keywords, complete_modules_only, shift)
1471
    return sort!(unique!(completion_text, suggestions), by=completion_text), namepos:pos, true
1,585✔
1472
end
1473

1474
function shell_completions(string, pos, hint::Bool=false)
442✔
1475
    # First parse everything up to the current position
1476
    scs = string[1:pos]
904✔
1477
    args, last_arg_start = try
442✔
1478
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
460✔
1479
    catch ex
1480
        ex isa ArgumentError || ex isa ErrorException || rethrow()
36✔
1481
        return Completion[], 0:-1, false
442✔
1482
    end
1483
    ex = args.args[end]::Expr
424✔
1484
    # Now look at the last thing we parsed
1485
    isempty(ex.args) && return Completion[], 0:-1, false
424✔
1486
    lastarg = ex.args[end]
424✔
1487
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1488
    # we need to special case here.
1489
    # If the last char was a space, but shell_parse ignored it search on "".
1490
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
846✔
1491
        partial = string[last_arg_start:pos]
4✔
1492
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
4✔
1493
        range = range .+ (last_arg_start - 1)
2✔
1494
        return ret, range, true
2✔
1495
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
422✔
1496
        r = pos+1:pos
34✔
1497
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
17✔
1498
        return paths, r, success
17✔
1499
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
815✔
1500
        # Join these and treat this as a path
1501
        path::String = join(ex.args)
404✔
1502
        r = last_arg_start:pos
404✔
1503

1504
        # Also try looking into the env path if the user wants to complete the first argument
1505
        use_envpath = length(args.args) < 2
404✔
1506

1507
        expanded = complete_expanduser(path, r)
404✔
1508
        was_expanded = expanded[3]
404✔
1509
        if was_expanded
404✔
1510
            path = (only(expanded[1])::PathCompletion).path
2✔
1511
            # If tab press, ispath and user expansion available, return it now
1512
            # otherwise see if we can complete the path further before returning with expanded ~
1513
            !hint && ispath(path) && return expanded::Completions
2✔
1514
        end
1515

1516
        paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true, contract_user=was_expanded)
404✔
1517

1518
        if success && !isempty(dir)
404✔
1519
            let dir = do_shell_escape(dir)
98✔
1520
                # if escaping of dir matches scs prefix, remove that from the completions
1521
                # otherwise make it the whole completion
1522
                partial = string[last_arg_start:pos]
196✔
1523
                if endswith(dir, "/") && startswith(partial, dir)
98✔
1524
                    r = (last_arg_start + sizeof(dir)):pos
14✔
1525
                elseif startswith(partial, dir * "/")
86✔
1526
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
82✔
1527
                else
1528
                    map!(paths, paths) do c::PathCompletion
4✔
1529
                        return PathCompletion(dir * "/" * c.path)
4✔
1530
                    end
1531
                end
1532
            end
1533
        end
1534
        # if ~ was expanded earlier and the incomplete string isn't a path
1535
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1536
        # i.e. require two tab presses to expand user
1537
        if was_expanded && !ispath(path)
404✔
1538
            map!(paths, paths) do c::PathCompletion
×
1539
                PathCompletion(contractuser(c.path))
×
1540
            end
1541
        end
1542
        return paths, r, success
404✔
1543
    end
1544
    return Completion[], 0:-1, false
1✔
1545
end
1546

1547
function __init__()
5✔
1548
    COMPLETION_WORLD[] = Base.get_world_counter()
5✔
1549
    return nothing
5✔
1550
end
1551

1552
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