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

JuliaLang / julia / #37993

13 Jan 2025 06:37AM UTC coverage: 86.034% (+0.08%) from 85.957%
#37993

push

local

web-flow
Update builds of MPFR_jll and OpenSSL_jll (#57026)

51882 of 60304 relevant lines covered (86.03%)

11673494.92 hits per line

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

89.81
/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, named_completion
6

7
using Core: Const
8
# We want to insulate the REPLCompletion module from any changes the user may
9
# make to the compiler, since it runs by default and the system becomes unusable
10
# if it breaks.
11
const CC = Base.Compiler
12
using Base.Meta
13
using Base: propertynames, something, IdSet
14
using Base.Filesystem: _readdirx
15

16
using ..REPL.LineEdit: NamedCompletion
17

18
abstract type Completion end
19

20
struct TextCompletion <: Completion
21
    text::String
187✔
22
end
23

24
struct KeywordCompletion <: Completion
25
    keyword::String
181✔
26
end
27

28
struct KeyvalCompletion <: Completion
29
    keyval::String
15✔
30
end
31

32
struct PathCompletion <: Completion
33
    path::String
7,840✔
34
end
35

36
struct ModuleCompletion <: Completion
37
    parent::Module
459,326✔
38
    mod::String
39
end
40

41
struct PackageCompletion <: Completion
42
    package::String
262✔
43
end
44

45
struct PropertyCompletion <: Completion
46
    value
77✔
47
    property::Symbol
48
end
49

50
struct FieldCompletion <: Completion
51
    typ::DataType
18✔
52
    field::Symbol
53
end
54

55
struct MethodCompletion <: Completion
56
    tt # may be used by an external consumer to infer return type, etc.
57
    method::Method
58
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
4,368✔
59
end
60

61
struct BslashCompletion <: Completion
62
    completion::String # what is actually completed, for example "\trianglecdot"
7,826✔
63
    name::String # what is displayed, for example "â—¬ \trianglecdot"
64
end
65
BslashCompletion(completion::String) = BslashCompletion(completion, completion)
31✔
66

67
struct ShellCompletion <: Completion
68
    text::String
69
end
70

71
struct DictCompletion <: Completion
72
    dict::AbstractDict
124✔
73
    key::String
74
end
75

76
struct KeywordArgumentCompletion <: Completion
77
    kwarg::String
47✔
78
end
79

80
# interface definition
81
function Base.getproperty(c::Completion, name::Symbol)
82
    if name === :text
10,074,400✔
83
        return getfield(c, :text)::String
185✔
84
    elseif name === :keyword
10,072,116✔
85
        return getfield(c, :keyword)::String
2,099✔
86
    elseif name === :path
10,064,806✔
87
        return getfield(c, :path)::String
35,237✔
88
    elseif name === :parent
10,064,806✔
89
        return getfield(c, :parent)::Module
×
90
    elseif name === :mod
24,804✔
91
        return getfield(c, :mod)::String
10,040,002✔
92
    elseif name === :package
20,655✔
93
        return getfield(c, :package)::String
4,149✔
94
    elseif name === :property
20,655✔
95
        return getfield(c, :property)::Symbol
271✔
96
    elseif name === :field
20,384✔
97
        return getfield(c, :field)::Symbol
35✔
98
    elseif name === :method
15,979✔
99
        return getfield(c, :method)::Method
4,370✔
100
    elseif name === :bslash
15,979✔
101
        return getfield(c, :bslash)::String
×
102
    elseif name === :text
15,979✔
103
        return getfield(c, :text)::String
×
104
    elseif name === :key
15,979✔
105
        return getfield(c, :key)::String
124✔
106
    elseif name === :kwarg
15,855✔
107
        return getfield(c, :kwarg)::String
89✔
108
    end
109
    return getfield(c, name)
15,766✔
110
end
111

112
_completion_text(c::TextCompletion) = c.text
185✔
113
_completion_text(c::KeywordCompletion) = c.keyword
2,099✔
114
_completion_text(c::KeyvalCompletion) = c.keyval
114✔
115
_completion_text(c::PathCompletion) = c.path
7,280✔
116
_completion_text(c::ModuleCompletion) = c.mod
10,040,002✔
117
_completion_text(c::PackageCompletion) = c.package
4,149✔
118
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
271✔
119
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
35✔
120
_completion_text(c::MethodCompletion) = repr(c.method)
3,468✔
121
_completion_text(c::ShellCompletion) = c.text
×
122
_completion_text(c::DictCompletion) = c.key
124✔
123
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
89✔
124

125
completion_text(c) = _completion_text(c)::String
10,057,816✔
126

127
named_completion(c::BslashCompletion) = NamedCompletion(c.completion, c.name)
7,826✔
128

129
function named_completion(c)
906,819✔
130
    text = completion_text(c)::String
10,057,815✔
131
    return NamedCompletion(text, text)
10,057,815✔
132
end
133

134
named_completion_completion(c) = named_completion(c).completion::String
4,570,016✔
135

136
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
137

138
function completes_global(x, name)
139
    return startswith(x, name) && !('#' in x)
3,130,186✔
140
end
141

142
function appendmacro!(syms, macros, needle, endchar)
3,406✔
143
    for macsym in macros
3,406✔
144
        s = String(macsym)
60,202✔
145
        if endswith(s, needle)
60,202✔
146
            from = nextind(s, firstindex(s))
7,758✔
147
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,879✔
148
            push!(syms, s[from:to]*endchar)
7,758✔
149
        end
150
    end
60,202✔
151
end
152

153
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
1,703✔
154
                                    mod::Module, name::String, complete_internal_only::Bool)
155
    imported = usings = !complete_internal_only
1,703✔
156
    ssyms = names(mod; all=true, imported, usings)
1,703✔
157
    filter!(ffunc, ssyms)
1,703✔
158
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
1,845,954✔
159

160
    # don't complete string and command macros when the input matches the internal name like `r_` to `r"`
161
    if !startswith(name, "@")
1,703✔
162
        filter!(macros) do m
1,680✔
163
            s = String(m)
30,105✔
164
            if endswith(s, "_str") || endswith(s, "_cmd")
56,341✔
165
                occursin(name, first(s, length(s)-4))
3,883✔
166
            else
167
                true
168
            end
169
        end
170
    end
171

172
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
457,150✔
173
    appendmacro!(syms, macros, "_str", "\"")
1,703✔
174
    appendmacro!(syms, macros, "_cmd", "`")
1,703✔
175
    for sym in syms
1,703✔
176
        push!(suggestions, ModuleCompletion(mod, sym))
459,326✔
177
    end
459,326✔
178
    return suggestions
1,703✔
179
end
180

181
# REPL Symbol Completions
182
function complete_symbol!(suggestions::Vector{Completion},
3,562✔
183
                          @nospecialize(prefix), name::String, context_module::Module;
184
                          complete_modules_only::Bool=false,
185
                          shift::Bool=false)
186
    local mod, t, val
1,781✔
187
    complete_internal_only = false
1,781✔
188
    if prefix !== nothing
1,781✔
189
        res = repl_eval_ex(prefix, context_module)
744✔
190
        res === nothing && return Completion[]
744✔
191
        if res isa Const
735✔
192
            val = res.val
713✔
193
            if isa(val, Module)
713✔
194
                mod = val
666✔
195
                if !shift
666✔
196
                    # when module is explicitly accessed, show internal bindings that are
197
                    # defined by the module, unless shift key is pressed
198
                    complete_internal_only = true
622✔
199
                end
200
            else
201
                t = typeof(val)
47✔
202
            end
203
        else
204
            t = CC.widenconst(res)
22✔
205
        end
206
    else
207
        mod = context_module
1,037✔
208
    end
209

210
    if @isdefined(mod) # lookup names available within the module
1,772✔
211
        let modname = nameof(mod),
1,703✔
212
            is_main = mod===Main
213
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
1,703✔
214
                if Base.isdeprecated(mod, s)
1,899,997✔
215
                    return false
×
216
                elseif s === modname
1,899,997✔
217
                    return false # exclude `Main.Main.Main`, etc.
2,701✔
218
                elseif complete_modules_only && !completes_module(mod, s)
1,897,296✔
219
                    return false
52,054✔
220
                elseif is_main && s === :MainInclude
1,845,242✔
221
                    return false
991✔
222
                end
223
                return true
1,844,251✔
224
            end
225
        end
226
    elseif @isdefined(val) # looking for a property of an instance
69✔
227
        try
47✔
228
            for property in propertynames(val, false)
47✔
229
                # TODO: support integer arguments (#36872)
230
                if property isa Symbol && startswith(string(property), name)
83✔
231
                    push!(suggestions, PropertyCompletion(val, property))
77✔
232
                end
233
            end
83✔
234
        catch
1✔
235
        end
236
    elseif @isdefined(t) && field_completion_eligible(t)
22✔
237
        # Looking for a member of a type
238
        add_field_completions!(suggestions, name, t)
13✔
239
    end
240
    return suggestions
1,772✔
241
end
242

243
completes_module(mod::Module, x::Symbol) =
52,555✔
244
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
245

246
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
15✔
247
    if isa(t, Union)
15✔
248
        add_field_completions!(suggestions, name, t.a)
2✔
249
        add_field_completions!(suggestions, name, t.b)
2✔
250
    else
251
        @assert isconcretetype(t)
15✔
252
        fields = fieldnames(t)
15✔
253
        for field in fields
15✔
254
            isa(field, Symbol) || continue # Tuple type has ::Int field name
18✔
255
            s = string(field)
18✔
256
            if startswith(s, name)
18✔
257
                push!(suggestions, FieldCompletion(t, field))
18✔
258
            end
259
        end
18✔
260
    end
261
end
262

263
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
264

265
function field_completion_eligible(@nospecialize t)
26✔
266
    if isa(t, Union)
26✔
267
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
2✔
268
    end
269
    isconcretetype(t) || return false
31✔
270
    # field completion is correct only when `getproperty` fallbacks to `getfield`
271
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
17✔
272
    match === nothing && return false
17✔
273
    return match.method === GENERIC_PROPERTYNAMES_METHOD
17✔
274
end
275

276
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
1,344✔
277
    r = searchsorted(list, s)
1,344✔
278
    i = first(r)
1,344✔
279
    n = length(list)
1,344✔
280
    while i <= n && startswith(list[i],s)
1,540✔
281
        r = first(r):i
196✔
282
        i += 1
196✔
283
    end
196✔
284
    for kw in list[r]
1,344✔
285
        push!(suggestions, T(kw))
196✔
286
    end
196✔
287
    return suggestions
1,344✔
288
end
289

290
const sorted_keywords = [
291
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
292
    "const", "continue", "do", "else", "elseif", "end", "export",
293
    "finally", "for", "function", "global", "if", "import",
294
    "let", "local", "macro", "module", "mutable struct",
295
    "primitive type", "quote", "return", "struct",
296
    "try", "using", "while"]
297

298
complete_keyword!(suggestions::Vector{Completion}, s::String) =
604✔
299
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
300

301
const sorted_keyvals = ["false", "true"]
302

303
complete_keyval!(suggestions::Vector{Completion}, s::String) =
740✔
304
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
305

306
function do_raw_escape(s)
307
    # escape_raw_string with delim='`' and ignoring the rule for the ending \
308
    return replace(s, r"(\\+)`" => s"\1\\`")
×
309
end
310
function do_shell_escape(s)
311
    return Base.shell_escape_posixly(s)
3,700✔
312
end
313
function do_string_escape(s)
314
    return escape_string(s, ('\"','$'))
7,390✔
315
end
316

317
const PATH_cache_lock = Base.ReentrantLock()
318
const PATH_cache = Set{String}()
319
PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests
320
next_cache_update::Float64 = 0.0
321
function maybe_spawn_cache_PATH()
21✔
322
    global PATH_cache_task, next_cache_update
21✔
323
    @lock PATH_cache_lock begin
21✔
324
        PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return
21✔
325
        time() < next_cache_update && return
20✔
326
        PATH_cache_task = Threads.@spawn begin
6✔
327
            REPLCompletions.cache_PATH()
3✔
328
            @lock PATH_cache_lock PATH_cache_task = nothing # release memory when done
3✔
329
        end
330
        Base.errormonitor(PATH_cache_task)
3✔
331
    end
332
end
333

334
# caches all reachable files in PATH dirs
335
function cache_PATH()
3✔
336
    path = get(ENV, "PATH", nothing)
3✔
337
    path isa String || return
3✔
338

339
    global next_cache_update
3✔
340

341
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
342
    # 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.
343
    this_PATH_cache = Set{String}()
3✔
344

345
    @debug "caching PATH files" PATH=path
3✔
346
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
3✔
347

348
    next_yield_time = time() + 0.01
3✔
349

350
    t = @elapsed for pathdir in pathdirs
3✔
351
        actualpath = try
18✔
352
            realpath(pathdir)
20✔
353
        catch ex
354
            ex isa Base.IOError || rethrow()
2✔
355
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
356
            continue
2✔
357
        end
358

359
        if actualpath != pathdir && in(actualpath, pathdirs)
27✔
360
            # Remove paths which (after resolving links) are in the env path twice.
361
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
362
            continue
3✔
363
        end
364

365
        path_entries = try
13✔
366
            _readdirx(pathdir)
14✔
367
        catch e
368
            # Bash allows dirs in PATH that can't be read, so we should as well.
369
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
370
                continue
1✔
371
            else
372
                # We only handle IOError and ArgumentError here
373
                rethrow()
×
374
            end
375
        end
376
        for entry in path_entries
12✔
377
            # In a perfect world, we would filter on whether the file is executable
378
            # here, or even on whether the current user can execute the file in question.
379
            try
2,469✔
380
                if isfile(entry)
2,469✔
381
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
2,399✔
382
                    push!(this_PATH_cache, entry.name)
2,400✔
383
                end
384
            catch e
385
                # `isfile()` can throw in rare cases such as when probing a
386
                # symlink that points to a file within a directory we do not
387
                # have read access to.
388
                if isa(e, Base.IOError)
1✔
389
                    continue
1✔
390
                else
391
                    rethrow()
×
392
                end
393
            end
394
            if time() >= next_yield_time
2,468✔
395
                yield() # to avoid blocking typing when -t1
1✔
396
                next_yield_time = time() + 0.01
1✔
397
            end
398
        end
2,469✔
399
    end
400

401
    @lock PATH_cache_lock begin
3✔
402
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
3✔
403
        next_cache_update = time() + 10 # earliest next update can run is 10s after
3✔
404
    end
405

406
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
3✔
407
    return PATH_cache
3✔
408
end
409

410
function complete_path(path::AbstractString;
1,132✔
411
                       use_envpath=false,
412
                       shell_escape=false,
413
                       raw_escape=false,
414
                       string_escape=false,
415
                       contract_user=false)
416
    @assert !(shell_escape && string_escape)
566✔
417
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
566✔
418
        # if the path is just "~", don't consider the expanded username as a prefix
419
        if path == "~"
×
420
            dir, prefix = homedir(), ""
×
421
        else
422
            dir, prefix = splitdir(homedir() * path[2:end])
×
423
        end
424
    else
425
        dir, prefix = splitdir(path)
566✔
426
    end
427
    entries = try
566✔
428
        if isempty(dir)
566✔
429
            _readdirx()
297✔
430
        elseif isdir(dir)
538✔
431
            _readdirx(dir)
110✔
432
        else
433
            return Completion[], dir, false
566✔
434
        end
435
    catch ex
436
        ex isa Base.IOError || rethrow()
×
437
        return Completion[], dir, false
×
438
    end
439

440
    matches = Set{String}()
407✔
441
    for entry in entries
407✔
442
        if startswith(entry.name, prefix)
45,502✔
443
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
7,059✔
444
            push!(matches, is_dir ? entry.name * "/" : entry.name)
13,142✔
445
        end
446
    end
45,502✔
447

448
    if use_envpath && isempty(dir)
407✔
449
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
450
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
451
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
21✔
452
        @lock PATH_cache_lock begin
21✔
453
            for file in PATH_cache
42✔
454
                startswith(file, prefix) && push!(matches, file)
11,272✔
455
            end
22,544✔
456
        end
457
    end
458

459
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
407✔
460
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
407✔
461
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
7,480✔
462
    return matches, dir, !isempty(matches)
407✔
463
end
464

465
function complete_path(path::AbstractString,
×
466
                       pos::Int;
467
                       use_envpath=false,
468
                       shell_escape=false,
469
                       string_escape=false,
470
                       contract_user=false)
471
    ## TODO: enable this depwarn once Pkg is fixed
472
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
473
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape)
×
474
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
475
        # if the path is just "~", don't consider the expanded username as a prefix
476
        if path == "~"
×
477
            dir, prefix = homedir(), ""
×
478
        else
479
            dir, prefix = splitdir(homedir() * path[2:end])
×
480
        end
481
    else
482
        dir, prefix = splitdir(path)
×
483
    end
484
    startpos = pos - lastindex(prefix) + 1
×
485
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
486
        # emulation for unnecessarily complicated return value, since / is a
487
        # perfectly acceptable path character which does not require quoting
488
        # but is required by Pkg's awkward parser handling
489
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
490
    end
491
    return paths, startpos:pos, success
×
492
end
493

494
function complete_expanduser(path::AbstractString, r)
552✔
495
    expanded =
552✔
496
        try expanduser(path)
553✔
497
        catch e
498
            e isa ArgumentError || rethrow()
1✔
499
            path
553✔
500
        end
501
    return Completion[PathCompletion(expanded)], r, path != expanded
552✔
502
end
503

504
# Returns a range that includes the method name in front of the first non
505
# closed start brace from the end of the string.
506
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,842✔
507
    r = reverse(s)
2,921✔
508
    i = firstindex(r)
2,921✔
509
    braces = in_comment = 0
2,921✔
510
    in_single_quotes = in_double_quotes = in_back_ticks = false
2,921✔
511
    num_single_quotes_in_string = count('\'', s)
2,921✔
512
    while i <= ncodeunits(r)
68,118✔
513
        c, i = iterate(r, i)
131,776✔
514
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
65,888✔
515
            c, i = iterate(r, i) # consume '='
8✔
516
            new_comments = 1
4✔
517
            # handle #=#=#=#, by counting =# pairs
518
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
519
                c, i = iterate(r, i) # consume '#'
6✔
520
                iterate(r, i)[1] == '=' || break
3✔
521
                c, i = iterate(r, i) # consume '='
4✔
522
                new_comments += 1
2✔
523
            end
2✔
524
            if c == '='
4✔
525
                in_comment += new_comments
3✔
526
            else
527
                in_comment -= new_comments
1✔
528
            end
529
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
65,884✔
530
            if c == c_start
64,012✔
531
                braces += 1
1,016✔
532
            elseif c == c_end
62,996✔
533
                braces -= 1
325✔
534
            elseif c == '\'' && num_single_quotes_in_string % 2 == 0
62,671✔
535
                # ' can be a transpose too, so check if there are even number of 's in the string
536
                # TODO: This probably needs to be more robust
537
                in_single_quotes = true
15✔
538
            elseif c == '"'
62,656✔
539
                in_double_quotes = true
286✔
540
            elseif c == '`'
62,370✔
541
                in_back_ticks = true
8✔
542
            end
543
        else
544
            if in_single_quotes &&
1,872✔
545
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
546
                in_single_quotes = false
15✔
547
            elseif in_double_quotes &&
1,857✔
548
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
549
                in_double_quotes = false
232✔
550
            elseif in_back_ticks &&
1,625✔
551
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
552
                in_back_ticks = false
7✔
553
            elseif in_comment > 0 &&
1,618✔
554
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
555
                # handle =#=#=#=, by counting #= pairs
556
                c, i = iterate(r, i) # consume '#'
6✔
557
                old_comments = 1
3✔
558
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
559
                    c, i = iterate(r, i) # consume '='
4✔
560
                    iterate(r, i)[1] == '#' || break
2✔
561
                    c, i = iterate(r, i) # consume '#'
2✔
562
                    old_comments += 1
1✔
563
                end
1✔
564
                if c == '#'
3✔
565
                    in_comment -= old_comments
2✔
566
                else
567
                    in_comment += old_comments
1✔
568
                end
569
            end
570
        end
571
        braces == 1 && break
65,888✔
572
    end
65,197✔
573
    braces != 1 && return 0:-1, -1
2,921✔
574
    method_name_end = reverseind(s, i)
1,374✔
575
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,070✔
576
    return (startind:lastindex(s), method_name_end)
691✔
577
end
578

579
struct REPLCacheToken end
580

581
struct REPLInterpreter <: CC.AbstractInterpreter
582
    limit_aggressive_inference::Bool
583
    world::UInt
584
    inf_params::CC.InferenceParams
585
    opt_params::CC.OptimizationParams
586
    inf_cache::Vector{CC.InferenceResult}
587
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
524✔
588
                             world::UInt = Base.get_world_counter(),
589
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
590
                                 aggressive_constant_propagation=true),
591
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
592
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
593
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
518✔
594
    end
595
end
596
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
172,077✔
597
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
104✔
598
CC.get_inference_world(interp::REPLInterpreter) = interp.world
182,097✔
599
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
45,632✔
600
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
601

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

605
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
606
CC.transform_result_for_cache(::REPLInterpreter, ::CC.InferenceResult) = nothing
×
607

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

611
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
612
# for lines like `Mod.a.|` (where `|` is the cursor position).
613
# Aggressive binding resolution poses challenges for the inference cache validation
614
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
615
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
616
# resolution for top-level frame representing REPL input code and for child uncached frames
617
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
618
# those global bindings are not constant and may be mutated in the future, since:
619
# a.) "repl-frame"s are never cached, and
620
# b.) mutable values are never observed by any cached frames.
621
#
622
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
623
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
624
# Aggressive concrete evaluation allows us to get accurate type information about complex
625
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
626
# doesn't evaluate effectful expressions like `pop!(xs)`.
627
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
628
# present any cache validation issues because "repl-frame" is never cached.
629

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

635
function is_call_graph_uncached(sv::CC.InferenceState)
251,939✔
636
    CC.is_cached(sv) && return false
353,812✔
637
    parent = CC.frame_parent(sv)
514,229✔
638
    parent === nothing && return true
262,290✔
639
    return is_call_graph_uncached(parent::CC.InferenceState)
251,939✔
640
end
641

642
isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g))
7,137✔
643

644
# aggressive global binding resolution within `repl_frame`
645
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, bailed::Bool,
646
                                    sv::CC.InferenceState)
647
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
128,635✔
648
        if isdefined_globalref(g)
7,137✔
649
            return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
7,131✔
650
                CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL), nothing)
651
        end
652
        return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
6✔
653
            CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS), nothing)
654
    end
655
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool,
57,249✔
656
                                              sv::CC.InferenceState)
657
end
658

659
function is_repl_frame_getproperty(sv::CC.InferenceState)
660
    def = sv.linfo.def
×
661
    def isa Method || return false
×
662
    def.name === :getproperty || return false
×
663
    CC.is_cached(sv) && return false
×
664
    return is_repl_frame(CC.frame_parent(sv))
×
665
end
666

667
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
668
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
18,411✔
669
                              argtypes::Vector{Any}, sv::CC.InferenceState)
670
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
18,411✔
671
        if length(argtypes) == 2
×
672
            a1, a2 = argtypes
×
673
            if isa(a1, Const) && isa(a2, Const)
×
674
                a1val, a2val = a1.val, a2.val
×
675
                if isa(a1val, Module) && isa(a2val, Symbol)
×
676
                    g = GlobalRef(a1val, a2val)
×
677
                    if isdefined_globalref(g)
×
678
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
×
679
                    end
680
                    return Union{}
×
681
                end
682
            end
683
        end
684
    end
685
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
18,411✔
686
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
687
end
688

689
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
690
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
37,683✔
691
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
692
                                   sv::CC.InferenceState)
693
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
75,307✔
694
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
3,224✔
695
        result = CC.MethodCallResult(result.rt, result.exct, neweffects, result.edge,
6,446✔
696
                                     result.edgecycle, result.edgelimited, result.volatile_inf_result)
697
    end
698
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
75,366✔
699
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
700
                                            sv::CC.InferenceState)
701
    if ret === :semi_concrete_eval
37,683✔
702
        # while the base eligibility check probably won't permit semi-concrete evaluation
703
        # for `REPLInterpreter` (given it completely turns off optimization),
704
        # this ensures we don't inadvertently enter irinterp
705
        ret = :none
×
706
    end
707
    return ret
37,683✔
708
end
709

710
# allow constant propagation for mutable constants
711
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
712
    if !interp.limit_aggressive_inference
37,142✔
713
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
74,243✔
714
    end
715
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
95✔
716
end
717

718
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
719
    @ccall jl_resolve_globals_in_ir(
512✔
720
        #=jl_array_t *stmts=# src.code::Any,
721
        #=jl_module_t *m=# mod::Any,
722
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
723
        #=int binding_effects=# 0::Int)::Cvoid
724
    return src
×
725
end
726

727
# lower `ex` and run type inference on the resulting top-level expression
728
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,760✔
729
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
2,759✔
730
        # get the inference result for the last expression
731
        ex = ex.args[end]
3✔
732
    end
733
    lwr = try
1,380✔
734
        Meta.lower(context_module, ex)
1,380✔
735
    catch # macro expansion failed, etc.
736
        return nothing
5✔
737
    end
738
    if lwr isa Symbol
1,375✔
739
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
730✔
740
    end
741
    lwr isa Expr || return Const(lwr) # `ex` is literal
748✔
742
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
572✔
743
    src = lwr.args[1]::Core.CodeInfo
512✔
744

745
    resolve_toplevel_symbols!(src, context_module)
512✔
746
    # construct top-level `MethodInstance`
747
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
512✔
748

749
    interp = REPLInterpreter(limit_aggressive_inference)
512✔
750
    result = CC.InferenceResult(mi)
512✔
751
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
512✔
752

753
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
754
    #      potential invalidations of `Core.Compiler` methods.
755
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
512✔
756

757
    result = frame.result.result
512✔
758
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
512✔
759
    return result
503✔
760
end
761

762
# `COMPLETION_WORLD[]` will be initialized within `__init__`
763
# (to allow us to potentially remove REPL from the sysimage in the future).
764
# Note that inference from the `code_typed` call below will use the current world age
765
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
766
# when the given world age is higher than the current one.
767
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
768

769
# Generate code cache for `REPLInterpreter` now:
770
# This code cache will be available at the world of `COMPLETION_WORLD`,
771
# assuming no invalidation will happen before initializing REPL.
772
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
773
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
774

775
# Method completion on function call expression that look like :(max(1))
776
MAX_METHOD_COMPLETIONS::Int = 40
777
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
312✔
778
    funct = repl_eval_ex(ex_org.args[1], context_module)
312✔
779
    funct === nothing && return 2, nothing, [], Set{Symbol}()
312✔
780
    funct = CC.widenconst(funct)
307✔
781
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
307✔
782
    return kwargs_flag, funct, args_ex, kwargs_ex
307✔
783
end
784

785
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
1✔
786
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
142✔
787
    out = Completion[]
141✔
788
    kwargs_flag == 2 && return out # one of the kwargs is invalid
141✔
789
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
128✔
790
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
186✔
791
    return out
128✔
792
end
793

794
MAX_ANY_METHOD_COMPLETIONS::Int = 10
795
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
63✔
796
    push!(exploredmodules, callee_module)
77✔
797
    for name in names(callee_module; all=true, imported=true)
63✔
798
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
28,699✔
799
            func = getfield(callee_module, name)
2,232✔
800
            if !isa(func, Module)
2,232✔
801
                funct = Core.Typeof(func)
3,914✔
802
                push!(seen, funct)
2,117✔
803
            elseif isa(func, Module) && func ∉ exploredmodules
115✔
804
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
49✔
805
            end
806
        end
807
    end
14,367✔
808
end
809
function recursive_explore_names(callee_module::Module, initial_module::Module)
810
    seen = IdSet{Any}()
14✔
811
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
812
    seen
×
813
end
814

815
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
14✔
816
    out = Completion[]
14✔
817
    args_ex, kwargs_ex, kwargs_flag = try
14✔
818
        # this may throw, since we set default_any to false
819
        complete_methods_args(ex_org, context_module, false, false)
14✔
820
    catch ex
821
        ex isa ArgumentError || rethrow()
×
822
        return out
14✔
823
    end
824
    kwargs_flag == 2 && return out # one of the kwargs is invalid
14✔
825

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

830
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
831
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,829✔
832
    end
3,644✔
833

834
    if !shift
14✔
835
        # Filter out methods where all arguments are `Any`
836
        filter!(out) do c
2✔
837
            isa(c, TextCompletion) && return false
12✔
838
            isa(c, MethodCompletion) || return true
12✔
839
            sig = Base.unwrap_unionall(c.method.sig)::DataType
12✔
840
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
20✔
841
        end
842
    end
843

844
    return out
14✔
845
end
846

847
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
848
    n = isexpr(x, :kw) ? x.args[1] : x
55✔
849
    if n isa Symbol
55✔
850
        push!(kwargs_ex, n)
41✔
851
        return kwargs_flag
41✔
852
    end
853
    possible_splat && isexpr(x, :...) && return kwargs_flag
14✔
854
    return 2 # The kwarg is invalid
10✔
855
end
856

857
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
321✔
858
    args_ex = Any[]
321✔
859
    kwargs_ex = Symbol[]
321✔
860
    kwargs_flag = 0
321✔
861
    # kwargs_flag is:
862
    # * 0 if there is no semicolon and no invalid kwarg
863
    # * 1 if there is a semicolon and no invalid kwarg
864
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
865
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
866
    for i in (1+!broadcasting):length(funargs)
477✔
867
        ex = funargs[i]
297✔
868
        if isexpr(ex, :parameters)
297✔
869
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
60✔
870
            for x in ex.args
60✔
871
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
872
            end
33✔
873
        elseif isexpr(ex, :kw)
237✔
874
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
875
        else
876
            if broadcasting
215✔
877
                # handle broadcasting, but only handle number of arguments instead of
878
                # argument types
879
                push!(args_ex, Any)
5✔
880
            else
881
                argt = repl_eval_ex(ex, context_module)
210✔
882
                if argt !== nothing
210✔
883
                    push!(args_ex, CC.widenconst(argt))
184✔
884
                elseif default_any
26✔
885
                    push!(args_ex, Any)
26✔
886
                else
887
                    throw(ArgumentError("argument not found"))
×
888
                end
889
            end
890
        end
891
    end
429✔
892
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
321✔
893
end
894

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

897
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
898
    if allow_broadcasting && is_broadcasting_expr(ex)
321✔
899
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
900
    end
901
    return detect_args_kwargs(ex.args, context_module, default_any, false)
315✔
902
end
903

904
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
2,126✔
905
    # Input types and number of arguments
906
    t_in = Tuple{funct, args_ex...}
2,126✔
907
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
2,126✔
908
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
909
    if !isa(m, Vector)
2,126✔
910
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
187✔
911
        return
187✔
912
    end
913
    for match in m
1,939✔
914
        # TODO: if kwargs_ex, filter out methods without kwargs?
915
        push!(out, MethodCompletion(match.spec_types, match.method))
4,368✔
916
    end
4,368✔
917
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
918
end
919

920
include("latex_symbols.jl")
921
include("emoji_symbols.jl")
922

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

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

935
# Aux function to detect whether we're right after a using or import keyword
936
function get_import_mode(s::String)
1,642✔
937
    # allow all of these to start with leading whitespace and macros like @eval and @eval(
938
    # ^\s*(?:@\w+\s*(?:\(\s*)?)?
939

940
    # match simple cases like `using |` and `import  |`
941
    mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s)
1,642✔
942
    if mod_import_match_simple !== nothing
1,642✔
943
        if mod_import_match_simple[1] == "using"
12✔
944
            return :using_module
4✔
945
        else
946
            return :import_module
2✔
947
        end
948
    end
949
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
950
    mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
1,636✔
951
    if mod_import_match !== nothing
1,636✔
952
        if mod_import_match.captures[1] == "using"
62✔
953
            return :using_module
24✔
954
        else
955
            return :import_module
7✔
956
        end
957
    end
958
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
959
    name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
1,605✔
960
    if name_import_match !== nothing
1,605✔
961
        if name_import_match[1] == "using"
10✔
962
            return :using_name
5✔
963
        else
964
            return :import_name
×
965
        end
966
    end
967
    return nothing
1,600✔
968
end
969

970
function close_path_completion(dir, path, str, pos)
18✔
971
    path = unescape_string(replace(path, "\\\$"=>"\$"))
36✔
972
    path = joinpath(dir, path)
18✔
973
    # ...except if it's a directory...
974
    Base.isaccessibledir(path) && return false
18✔
975
    # ...and except if there's already a " at the cursor.
976
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
8✔
977
end
978

979
function bslash_completions(string::String, pos::Int, hint::Bool=false)
2,072✔
980
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
4,202✔
981
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
2,100✔
982
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
983
        # latex / emoji symbol substitution
984
        s = string[slashpos:pos]
112✔
985
        latex = get(latex_symbols, s, "")
56✔
986
        if !isempty(latex) # complete an exact match
56✔
987
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
22✔
988
        elseif occursin(subscript_regex, s)
34✔
989
            sub = map(c -> subscripts[c], s[3:end])
62✔
990
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
991
        elseif occursin(superscript_regex, s)
28✔
992
            sup = map(c -> superscripts[c], s[3:end])
14✔
993
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
994
        end
995
        emoji = get(emoji_symbols, s, "")
27✔
996
        if !isempty(emoji)
27✔
997
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
998
        end
999
        # return possible matches; these cannot be mixed with regular
1000
        # Julian completions as only latex / emoji symbols contain the leading \
1001
        symbol_dict = startswith(s, "\\:") ? emoji_symbols : latex_symbols
25✔
1002
        namelist = Iterators.filter(k -> startswith(k, s), keys(symbol_dict))
62,419✔
1003
        completions = Completion[BslashCompletion(name, "$(symbol_dict[name]) $name") for name in sort!(collect(namelist))]
25✔
1004
        return (true, (completions, slashpos:pos, true))
25✔
1005
    end
1006
    return (false, (Completion[], 0:-1, false))
2,016✔
1007
end
1008

1009
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,213✔
1010
    if tag === :string
2,213✔
1011
        str_close = str*"\""
168✔
1012
    elseif tag === :cmd
2,043✔
1013
        str_close = str*"`"
5✔
1014
    else
1015
        str_close = str
2,038✔
1016
    end
1017
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,211✔
1018
    isempty(frange) && return (nothing, nothing, nothing)
2,211✔
1019
    objstr = str[1:end_of_identifier]
214✔
1020
    objex = Meta.parse(objstr, raise=false, depwarn=false)
107✔
1021
    objt = repl_eval_ex(objex, context_module)
107✔
1022
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
131✔
1023
    obj = objt.val
83✔
1024
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
84✔
1025
    (Base.haslength(obj) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
83✔
1026
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
1027
                             lastindex(str)+1)
1028
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
1029
end
1030

1031
# This needs to be a separate non-inlined function, see #19441
1032
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
1033
    matches = String[]
80✔
1034
    for key in keys(identifier)
122✔
1035
        rkey = repr(key)
933✔
1036
        startswith(rkey,partial_key) && push!(matches,rkey)
933✔
1037
    end
1,410✔
1038
    return matches
80✔
1039
end
1040

1041
# Identify an argument being completed in a method call. If the argument is empty, method
1042
# suggestions will be provided instead of argument completions.
1043
function identify_possible_method_completion(partial, last_idx)
2,809✔
1044
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,809✔
1045

1046
    # First, check that the last punctuation is either ',', ';' or '('
1047
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,635✔
1048
    idx_last_punct == 0 && return fail
2,809✔
1049
    last_punct = partial[idx_last_punct]
4,560✔
1050
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,349✔
1051

1052
    # Then, check that `last_punct` is only followed by an identifier or nothing
1053
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,598✔
1054
    before_last_word_start == 0 && return fail
799✔
1055
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
903✔
1056

1057
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1058
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
695✔
1059
    method_name_end ∈ frange || return fail
906✔
1060

1061
    # Strip the preceding ! operators, if any, and close the expression with a ')'
1062
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
968✔
1063
    ex = Meta.parse(s, raise=false, depwarn=false)
484✔
1064
    isa(ex, Expr) || return fail
484✔
1065

1066
    # `wordrange` is the position of the last argument to complete
1067
    wordrange = nextind(partial, before_last_word_start):last_idx
624✔
1068
    return frange, ex, wordrange, method_name_end
484✔
1069
end
1070

1071
# Provide completion for keyword arguments in function calls
1072
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
3,622✔
1073
                                   shift::Bool=false)
1074
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,811✔
1075
    fail = Completion[], 0:-1, frange
1,811✔
1076
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,454✔
1077

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

1081
    methods = Completion[]
169✔
1082
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, shift ? -1 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
304✔
1083
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
1084
    # method calls compatible with the current arguments.
1085

1086
    # For each method corresponding to the function call, provide completion suggestions
1087
    # for each keyword that starts like the last word and that is not already used
1088
    # previously in the expression. The corresponding suggestion is "kwname=".
1089
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1090
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
1091
    last_word = partial[wordrange] # the word to complete
338✔
1092
    kwargs = Set{String}()
169✔
1093
    for m in methods
169✔
1094
        m::MethodCompletion
892✔
1095
        possible_kwargs = Base.kwarg_decl(m.method)
890✔
1096
        current_kwarg_candidates = String[]
890✔
1097
        for _kw in possible_kwargs
890✔
1098
            kw = String(_kw)
464✔
1099
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
497✔
1100
                push!(current_kwarg_candidates, kw)
67✔
1101
            end
1102
        end
464✔
1103
        union!(kwargs, current_kwarg_candidates)
890✔
1104
    end
890✔
1105

1106
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
214✔
1107

1108
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1109
    if kwargs_flag == 0
167✔
1110
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
136✔
1111
        complete_keyval!(suggestions, last_word)
136✔
1112
    end
1113

1114
    return sort!(suggestions, by=named_completion_completion), wordrange
167✔
1115
end
1116

1117
function get_loading_candidates(pkgstarts::String, project_file::String)
1✔
1118
    loading_candidates = String[]
1✔
1119
    d = Base.parsed_toml(project_file)
1✔
1120
    pkg = get(d, "name", nothing)::Union{String, Nothing}
1✔
1121
    if pkg !== nothing && startswith(pkg, pkgstarts)
1✔
1122
        push!(loading_candidates, pkg)
1✔
1123
    end
1124
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
1✔
1125
    if deps !== nothing
1✔
1126
        for (pkg, _) in deps
2✔
1127
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
1✔
1128
        end
1✔
1129
    end
1130
    return loading_candidates
1✔
1131
end
1132

1133
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1134
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1135
        push!(suggestions, PackageCompletion(name))
2✔
1136
    end
2✔
1137
    return suggestions
1✔
1138
end
1139

1140
function complete_identifiers!(suggestions::Vector{Completion},
3,290✔
1141
                               context_module::Module, string::String, name::String,
1142
                               pos::Int, separatorpos::Int, startpos::Int;
1143
                               comp_keywords::Bool=false,
1144
                               complete_modules_only::Bool=false,
1145
                               shift::Bool=false)
1146
    if comp_keywords
1,645✔
1147
        complete_keyword!(suggestions, name)
604✔
1148
        complete_keyval!(suggestions, name)
604✔
1149
    end
1150
    if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
1,650✔
1151
        s = string[1:prevind(string, separatorpos)]
1,714✔
1152
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
1153
        prefix = Meta.parse(s, raise=false, depwarn=false)
857✔
1154
        if isexpr(prefix, :incomplete)
857✔
1155
            s = string[startpos:pos]
1,224✔
1156
            # Heuristic to find the start of the expression. TODO: This would be better
1157
            # done with a proper error-recovering parser.
1158
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,224✔
1159
                i = prevind(string, startpos)
6✔
1160
                while 0 < i
6✔
1161
                    c = string[i]
12✔
1162
                    if c in (')', ']')
12✔
1163
                        if c == ')'
×
1164
                            c_start = '('
×
1165
                            c_end = ')'
×
1166
                        elseif c == ']'
×
1167
                            c_start = '['
×
1168
                            c_end = ']'
×
1169
                        end
1170
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1171
                        isempty(frange) && break # unbalanced parens
×
1172
                        startpos = first(frange)
×
1173
                        i = prevind(string, startpos)
×
1174
                    elseif c in ('\'', '\"', '\`')
12✔
1175
                        s = "$c$c"*string[startpos:pos]
×
1176
                        break
×
1177
                    else
1178
                        break
6✔
1179
                    end
1180
                    s = string[startpos:pos]
×
1181
                end
×
1182
            end
1183
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,208✔
1184
                lookup_name, name = rsplit(s, ".", limit=2)
550✔
1185
                name = String(name)
550✔
1186
                prefix = Meta.parse(lookup_name, raise=false, depwarn=false)
550✔
1187
            end
1188
            isexpr(prefix, :incomplete) && (prefix = nothing)
658✔
1189
        elseif isexpr(prefix, (:using, :import))
199✔
1190
            arglast = prefix.args[end] # focus on completion to the last argument
16✔
1191
            if isexpr(arglast, :.)
16✔
1192
                # We come here for cases like:
1193
                # - `string`: "using Mod1.Mod2.M"
1194
                # - `ex`: :(using Mod1.Mod2)
1195
                # - `name`: "M"
1196
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1197
                # complete for inner modules whose name starts with `M`.
1198
                # Note that `complete_modules_only=true` is set within `completions`
1199
                prefix = nothing
16✔
1200
                firstdot = true
16✔
1201
                for arg = arglast.args
16✔
1202
                    if arg === :.
32✔
1203
                        # override `context_module` if multiple `.` accessors are used
1204
                        if firstdot
15✔
1205
                            firstdot = false
12✔
1206
                        else
1207
                            context_module = parentmodule(context_module)
3✔
1208
                        end
1209
                    elseif arg isa Symbol
17✔
1210
                        if prefix === nothing
17✔
1211
                            prefix = arg
16✔
1212
                        else
1213
                            prefix = Expr(:., prefix, QuoteNode(arg))
1✔
1214
                        end
1215
                    else # invalid expression
1216
                        prefix = nothing
×
1217
                        break
×
1218
                    end
1219
                end
32✔
1220
            end
1221
        elseif isexpr(prefix, :call) && length(prefix.args) > 1
183✔
1222
            isinfix = s[end] != ')'
39✔
1223
            # A complete call expression that does not finish with ')' is an infix call.
1224
            if !isinfix
20✔
1225
                # Handle infix call argument completion of the form bar + foo(qux).
1226
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
30✔
1227
                if !isempty(frange) # if find_start_brace fails to find the brace just continue
15✔
1228
                    isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == prefix.args[end]
15✔
1229
                end
1230
            end
1231
            if isinfix
20✔
1232
                prefix = prefix.args[end]
8✔
1233
            end
1234
        elseif isexpr(prefix, :macrocall) && length(prefix.args) > 1
163✔
1235
            # allow symbol completions within potentially incomplete macrocalls
1236
            if s[end] ≠ '`' && s[end] ≠ ')'
43✔
1237
                prefix = prefix.args[end]
12✔
1238
            end
1239
        end
1240
    else
1241
        prefix = nothing
788✔
1242
    end
1243
    complete_symbol!(suggestions, prefix, name, context_module; complete_modules_only, shift)
1,645✔
1244
    return suggestions
1,645✔
1245
end
1246

1247
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,224✔
1248
    # First parse everything up to the current position
1249
    partial = string[1:pos]
4,855✔
1250
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,223✔
1251

1252
    if !hint # require a tab press for completion of these
2,223✔
1253
        # ?(x, y)TAB lists methods you can call with these objects
1254
        # ?(x, y TAB lists methods that take these objects as the first two arguments
1255
        # MyModule.?(x, y)TAB restricts the search to names in MyModule
1256
        rexm = match(r"(\w+\.|)\?\((.*)$", partial)
419✔
1257
        if rexm !== nothing
419✔
1258
            # Get the module scope
1259
            if isempty(rexm.captures[1])
28✔
1260
                callee_module = context_module
1✔
1261
            else
1262
                modname = Symbol(rexm.captures[1][1:end-1])
13✔
1263
                if isdefined(context_module, modname)
13✔
1264
                    callee_module = getfield(context_module, modname)
13✔
1265
                    if !isa(callee_module, Module)
13✔
1266
                        callee_module = context_module
×
1267
                    end
1268
                else
1269
                    callee_module = context_module
×
1270
                end
1271
            end
1272
            moreargs = !endswith(rexm.captures[2], ')')
14✔
1273
            callstr = "_(" * rexm.captures[2]
14✔
1274
            if moreargs
14✔
1275
                callstr *= ')'
8✔
1276
            end
1277
            ex_org = Meta.parse(callstr, raise=false, depwarn=false)
14✔
1278
            if isa(ex_org, Expr)
14✔
1279
                return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
14✔
1280
            end
1281
        end
1282
    end
1283

1284
    # if completing a key in a Dict
1285
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,289✔
1286
    if identifier !== nothing
2,209✔
1287
        matches = find_dict_matches(identifier, partial_key)
80✔
1288
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1289
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1290
    end
1291

1292
    suggestions = Completion[]
2,145✔
1293

1294
    # Check if this is a var"" string macro that should be completed like
1295
    # an identifier rather than a string.
1296
    # TODO: It would be nice for the parser to give us more information here
1297
    # so that we can lookup the macro by identity rather than pattern matching
1298
    # its invocation.
1299
    varrange = findprev("var\"", string, pos)
2,145✔
1300

1301
    expanded = nothing
2,145✔
1302
    was_expanded = false
2,145✔
1303

1304
    if varrange !== nothing
2,145✔
1305
        ok, ret = bslash_completions(string, pos)
3✔
1306
        ok && return ret
3✔
1307
        startpos = first(varrange) + 4
3✔
1308
        separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1309
        name = string[startpos:pos]
5✔
1310
        complete_identifiers!(suggestions, context_module, string, name,
3✔
1311
                              pos, separatorpos, startpos;
1312
                              shift)
1313
        return sort!(unique!(named_completion, suggestions), by=named_completion_completion), (separatorpos+1):pos, true
3✔
1314
    elseif inc_tag === :cmd
2,142✔
1315
        # TODO: should this call shell_completions instead of partially reimplementing it?
1316
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
1✔
1317
            startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1318
            r = startpos:pos
1✔
1319
            scs::String = string[r]
2✔
1320

1321
            expanded = complete_expanduser(scs, r)
1✔
1322
            was_expanded = expanded[3]
1✔
1323
            if was_expanded
1✔
1324
                scs = (only(expanded[1])::PathCompletion).path
×
1325
                # If tab press, ispath and user expansion available, return it now
1326
                # otherwise see if we can complete the path further before returning with expanded ~
1327
                !hint && ispath(scs) && return expanded::Completions
×
1328
            end
1329

1330
            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✔
1331
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1332
            # assumes the path isn't further quoted within the cmd backticks.
1333
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
1✔
1334
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
1✔
1335

1336
            if success && !isempty(dir)
1✔
1337
                let dir = do_raw_escape(do_shell_escape(dir))
×
1338
                    # if escaping of dir matches scs prefix, remove that from the completions
1339
                    # otherwise make it the whole completion
1340
                    if endswith(dir, "/") && startswith(scs, dir)
×
1341
                        r = (startpos + sizeof(dir)):pos
×
1342
                    elseif startswith(scs, dir * "/")
×
1343
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1344
                    else
1345
                        map!(paths, paths) do c::PathCompletion
×
1346
                            p = dir * "/" * c.path
×
1347
                            was_expanded && (p = contractuser(p))
×
1348
                            return PathCompletion(p)
×
1349
                        end
1350
                    end
1351
                end
1352
            end
1353
            if isempty(paths) && !hint && was_expanded
1✔
1354
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1355
                return expanded::Completions
×
1356
            else
1357
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
1✔
1358
            end
1359
        end
1360
    elseif inc_tag === :string
2,141✔
1361
        # Find first non-escaped quote
1362
        let m = match(r"\"(?!\\)", reverse(partial))
147✔
1363
            startpos = nextind(partial, reverseind(partial, m.offset))
294✔
1364
            r = startpos:pos
170✔
1365
            scs::String = string[r]
271✔
1366

1367
            expanded = complete_expanduser(scs, r)
147✔
1368
            was_expanded = expanded[3]
147✔
1369
            if was_expanded
147✔
1370
                scs = (only(expanded[1])::PathCompletion).path
6✔
1371
                # If tab press, ispath and user expansion available, return it now
1372
                # otherwise see if we can complete the path further before returning with expanded ~
1373
                !hint && ispath(scs) && return expanded::Completions
6✔
1374
            end
1375

1376
            path = try
144✔
1377
                unescape_string(replace(scs, "\\\$"=>"\$"))
265✔
1378
            catch ex
1379
                ex isa ArgumentError || rethrow()
×
1380
                nothing
144✔
1381
            end
1382
            if !isnothing(path)
288✔
1383
                paths, dir, success = complete_path(path::String, string_escape=true)
144✔
1384

1385
                if length(paths) == 1
144✔
1386
                    p = (paths[1]::PathCompletion).path
18✔
1387
                    hint && was_expanded && (p = contractuser(p))
18✔
1388
                    if close_path_completion(dir, p, path, pos)
18✔
1389
                        paths[1] = PathCompletion(p * "\"")
8✔
1390
                    end
1391
                end
1392

1393
                if success && !isempty(dir)
144✔
1394
                    let dir = do_string_escape(dir)
22✔
1395
                        # if escaping of dir matches scs prefix, remove that from the completions
1396
                        # otherwise make it the whole completion
1397
                        if endswith(dir, "/") && startswith(scs, dir)
11✔
1398
                            r = (startpos + sizeof(dir)):pos
1✔
1399
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
10✔
1400
                            was_expanded && (dir = contractuser(dir))
10✔
1401
                            r = nextind(string, startpos + sizeof(dir)):pos
10✔
1402
                        else
1403
                            map!(paths, paths) do c::PathCompletion
×
1404
                                p = dir * "/" * c.path
×
1405
                                hint && was_expanded && (p = contractuser(p))
×
1406
                                return PathCompletion(p)
×
1407
                            end
1408
                        end
1409
                    end
1410
                end
1411

1412
                # Fallthrough allowed so that Latex symbols can be completed in strings
1413
                if success
144✔
1414
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
28,000✔
1415
                elseif !hint && was_expanded
71✔
1416
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1417
                    return expanded::Completions
1✔
1418
                end
1419
            end
1420
        end
1421
    end
1422
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1423
    was_expanded && return expanded::Completions
2,064✔
1424

1425
    ok, ret = bslash_completions(string, pos)
2,064✔
1426
    ok && return ret
2,064✔
1427

1428
    # Make sure that only bslash_completions is working on strings
1429
    inc_tag === :string && return Completion[], 0:-1, false
2,013✔
1430
    if inc_tag === :other
1,952✔
1431
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
998✔
1432
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
998✔
1433
            if ex.head === :call
140✔
1434
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
137✔
1435
            elseif is_broadcasting_expr(ex)
3✔
1436
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1437
            end
1438
        end
1439
    elseif inc_tag === :comment
954✔
1440
        return Completion[], 0:-1, false
1✔
1441
    end
1442

1443
    # Check whether we can complete a keyword argument in a function call
1444
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
3,453✔
1445
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,976✔
1446

1447
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,919✔
1448
    # strip preceding ! operator
1449
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,642✔
1450
        startpos += length(m.match)
2✔
1451
    end
1452

1453
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
2,496✔
1454
    namepos = max(startpos, separatorpos+1)
1,642✔
1455
    name = string[namepos:pos]
2,839✔
1456
    import_mode = get_import_mode(string)
1,642✔
1457
    if import_mode === :using_module || import_mode === :import_module
3,256✔
1458
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1459
        # Let's look only for packages and modules we can reach from here
1460

1461
        # If there's no dot, we're in toplevel, so we should
1462
        # also search for packages
1463
        s = string[startpos:pos]
70✔
1464
        if separatorpos <= startpos
37✔
1465
            for dir in Base.load_path()
25✔
1466
                if basename(dir) in Base.project_names && isfile(dir)
29✔
1467
                    complete_loading_candidates!(suggestions, s, dir)
1✔
1468
                end
1469
                isdir(dir) || continue
56✔
1470
                for entry in _readdirx(dir)
26✔
1471
                    pname = entry.name
1,527✔
1472
                    if pname[1] != '.' && pname != "METADATA" &&
3,054✔
1473
                        pname != "REQUIRE" && startswith(pname, s)
1474
                        # Valid file paths are
1475
                        #   <Mod>.jl
1476
                        #   <Mod>/src/<Mod>.jl
1477
                        #   <Mod>.jl/src/<Mod>.jl
1478
                        if isfile(entry)
261✔
1479
                            endswith(pname, ".jl") && push!(suggestions,
×
1480
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1481
                        else
1482
                            mod_name = if endswith(pname, ".jl")
261✔
1483
                                pname[1:prevind(pname, end-2)]
×
1484
                            else
1485
                                pname
522✔
1486
                            end
1487
                            if isfile(joinpath(entry, "src",
522✔
1488
                                               "$mod_name.jl"))
1489
                                push!(suggestions, PackageCompletion(mod_name))
260✔
1490
                            end
1491
                        end
1492
                    end
1493
                end
1,527✔
1494
            end
28✔
1495
        end
1496
        comp_keywords = false
37✔
1497
        complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
37✔
1498
    elseif import_mode === :using_name || import_mode === :import_name
3,205✔
1499
        # `using Foo: |` and `import Foo: bar, baz|`
1500
        separatorpos = findprev(isequal(':'), string, pos)::Int
5✔
1501
        comp_keywords = false
5✔
1502
        complete_modules_only = false
5✔
1503
    else
1504
        comp_keywords = !isempty(name) && startpos > separatorpos
1,600✔
1505
        complete_modules_only = false
1,600✔
1506
    end
1507

1508
    complete_identifiers!(suggestions, context_module, string, name,
1,642✔
1509
                          pos, separatorpos, startpos;
1510
                          comp_keywords, complete_modules_only, shift)
1511
    return sort!(unique!(named_completion, suggestions), by=named_completion_completion), namepos:pos, true
1,642✔
1512
end
1513

1514
function shell_completions(string, pos, hint::Bool=false)
442✔
1515
    # First parse everything up to the current position
1516
    scs = string[1:pos]
904✔
1517
    args, last_arg_start = try
442✔
1518
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
460✔
1519
    catch ex
1520
        ex isa ArgumentError || ex isa ErrorException || rethrow()
36✔
1521
        return Completion[], 0:-1, false
442✔
1522
    end
1523
    ex = args.args[end]::Expr
424✔
1524
    # Now look at the last thing we parsed
1525
    isempty(ex.args) && return Completion[], 0:-1, false
424✔
1526
    lastarg = ex.args[end]
424✔
1527
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1528
    # we need to special case here.
1529
    # If the last char was a space, but shell_parse ignored it search on "".
1530
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
846✔
1531
        partial = string[last_arg_start:pos]
4✔
1532
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
4✔
1533
        range = range .+ (last_arg_start - 1)
2✔
1534
        return ret, range, true
2✔
1535
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
422✔
1536
        r = pos+1:pos
34✔
1537
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
17✔
1538
        return paths, r, success
17✔
1539
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
815✔
1540
        # Join these and treat this as a path
1541
        path::String = join(ex.args)
404✔
1542
        r = last_arg_start:pos
404✔
1543

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

1547
        expanded = complete_expanduser(path, r)
404✔
1548
        was_expanded = expanded[3]
404✔
1549
        if was_expanded
404✔
1550
            path = (only(expanded[1])::PathCompletion).path
2✔
1551
            # If tab press, ispath and user expansion available, return it now
1552
            # otherwise see if we can complete the path further before returning with expanded ~
1553
            !hint && ispath(path) && return expanded::Completions
2✔
1554
        end
1555

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

1558
        if success && !isempty(dir)
404✔
1559
            let dir = do_shell_escape(dir)
98✔
1560
                # if escaping of dir matches scs prefix, remove that from the completions
1561
                # otherwise make it the whole completion
1562
                partial = string[last_arg_start:pos]
196✔
1563
                if endswith(dir, "/") && startswith(partial, dir)
98✔
1564
                    r = (last_arg_start + sizeof(dir)):pos
14✔
1565
                elseif startswith(partial, dir * "/")
86✔
1566
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
82✔
1567
                else
1568
                    map!(paths, paths) do c::PathCompletion
4✔
1569
                        return PathCompletion(dir * "/" * c.path)
4✔
1570
                    end
1571
                end
1572
            end
1573
        end
1574
        # if ~ was expanded earlier and the incomplete string isn't a path
1575
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1576
        # i.e. require two tab presses to expand user
1577
        if was_expanded && !ispath(path)
404✔
1578
            map!(paths, paths) do c::PathCompletion
×
1579
                PathCompletion(contractuser(c.path))
×
1580
            end
1581
        end
1582
        return paths, r, success
404✔
1583
    end
1584
    return Completion[], 0:-1, false
1✔
1585
end
1586

1587
function __init__()
4✔
1588
    COMPLETION_WORLD[] = Base.get_world_counter()
4✔
1589
    return nothing
4✔
1590
end
1591

1592
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