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

JuliaLang / julia / #38161

06 Aug 2025 11:47AM UTC coverage: 67.945% (-1.4%) from 69.341%
#38161

push

local

web-flow
remove jl_function_t typealias (#59216)

This has been an alias for a long time now, remove it since it is just
confusing/misleading to be different from jl_value_t.

41042 of 60405 relevant lines covered (67.94%)

6182855.41 hits per line

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

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

3
"""
4
Run Evaluate Print Loop (REPL)
5

6
Example minimal code
7

8
```julia
9
import REPL
10
term = REPL.Terminals.TTYTerminal("dumb", stdin, stdout, stderr)
11
repl = REPL.LineEditREPL(term, true)
12
REPL.run_repl(repl)
13
```
14
"""
15
module REPL
16

17
Base.Experimental.@optlevel 1
18
Base.Experimental.@max_methods 1
19

20
function UndefVarError_REPL_hint(io::IO, ex::UndefVarError)
×
21
    var = ex.var
×
22
    if var === :or
×
23
        print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.")
×
24
    elseif var === :and
×
25
        print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.")
×
26
    elseif var === :help
×
27
        println(io)
×
28
        # Show friendly help message when user types help or help() and help is undefined
29
        show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
×
30
    elseif var === :quit
×
31
        print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
32
    end
33
end
34

35
function __init__()
4✔
36
    Base.REPL_MODULE_REF[] = REPL
4✔
37
    Base.Experimental.register_error_hint(UndefVarError_REPL_hint, UndefVarError)
4✔
38
    return nothing
4✔
39
end
40

41
using Base.Meta, Sockets, StyledStrings
42
using JuliaSyntaxHighlighting
43
import InteractiveUtils
44
import FileWatching
45
import Base.JuliaSyntax: kind, @K_str, @KSet_str, Tokenize.tokenize
46

47
export
48
    AbstractREPL,
49
    BasicREPL,
50
    LineEditREPL,
51
    StreamREPL
52

53
public TerminalMenus
54

55
import Base:
56
    AbstractDisplay,
57
    display,
58
    show,
59
    AnyDict,
60
    ==
61

62
_displaysize(io::IO) = displaysize(io)::Tuple{Int,Int}
63✔
63

64
include("Terminals.jl")
65
using .Terminals
66

67
abstract type AbstractREPL end
68

69
include("options.jl")
70

71
include("LineEdit.jl")
72
using .LineEdit
73
import .LineEdit:
74
    CompletionProvider,
75
    HistoryProvider,
76
    add_history,
77
    complete_line,
78
    history_next,
79
    history_next_prefix,
80
    history_prev,
81
    history_prev_prefix,
82
    history_first,
83
    history_last,
84
    history_search,
85
    setmodifiers!,
86
    terminal,
87
    MIState,
88
    PromptState,
89
    mode_idx
90

91
include("SyntaxUtil.jl")
92
include("REPLCompletions.jl")
93
using .REPLCompletions
94

95
include("TerminalMenus/TerminalMenus.jl")
96
include("docview.jl")
97

98
include("Pkg_beforeload.jl")
99

100
@nospecialize # use only declared type signatures
101

102
answer_color(::AbstractREPL) = ""
×
103

104
const JULIA_PROMPT = "julia> "
105
const PKG_PROMPT = "pkg> "
106
const SHELL_PROMPT = "shell> "
107
const HELP_PROMPT = "help?> "
108

109
mutable struct REPLBackend
110
    "channel for AST"
111
    repl_channel::Channel{Any}
112
    "channel for results: (value, iserror)"
113
    response_channel::Channel{Any}
114
    "flag indicating the state of this backend"
115
    in_eval::Bool
116
    "transformation functions to apply before evaluating expressions"
117
    ast_transforms::Vector{Any}
118
    "current backend task"
119
    backend_task::Task
120

121
    REPLBackend(repl_channel, response_channel, in_eval, ast_transforms=copy(repl_ast_transforms)) =
73✔
122
        new(repl_channel, response_channel, in_eval, ast_transforms)
123
end
124
REPLBackend() = REPLBackend(Channel(1), Channel(1), false)
48✔
125

126
# A reference to a backend that is not mutable
127
struct REPLBackendRef
128
    repl_channel::Channel{Any}
23✔
129
    response_channel::Channel{Any}
130
end
131
REPLBackendRef(backend::REPLBackend) = REPLBackendRef(backend.repl_channel, backend.response_channel)
23✔
132

133
function destroy(ref::REPLBackendRef, state::Task)
134
    if istaskfailed(state)
22✔
135
        close(ref.repl_channel, TaskFailedException(state))
×
136
        close(ref.response_channel, TaskFailedException(state))
×
137
    end
138
    close(ref.repl_channel)
22✔
139
    close(ref.response_channel)
22✔
140
end
141

142
"""
143
    softscope(ex)
144

145
Return a modified version of the parsed expression `ex` that uses
146
the REPL's "soft" scoping rules for global syntax blocks.
147
"""
148
function softscope(@nospecialize ex)
370✔
149
    if ex isa Expr
370✔
150
        h = ex.head
239✔
151
        if h === :toplevel
239✔
152
            ex′ = Expr(h)
135✔
153
            map!(softscope, resize!(ex′.args, length(ex.args)), ex.args)
135✔
154
            return ex′
135✔
155
        elseif h in (:meta, :import, :using, :export, :module, :error, :incomplete, :thunk)
104✔
156
            return ex
2✔
157
        elseif h === :global && all(x->isa(x, Symbol), ex.args)
103✔
158
            return ex
1✔
159
        else
160
            return Expr(:block, Expr(:softscope, true), ex)
101✔
161
        end
162
    end
163
    return ex
131✔
164
end
165

166
# Temporary alias until Documenter updates
167
const softscope! = softscope
168

169
function print_qualified_access_warning(mod::Module, owner::Module, name::Symbol)
2✔
170
    @warn string(name, " is defined in ", owner, " and is not public in ", mod) maxlog = 1 _id = string("repl-warning-", mod, "-", owner, "-", name) _line = nothing _file = nothing _module = nothing
2✔
171
end
172

173
function has_ancestor(query::Module, target::Module)
2✔
174
    query == target && return true
38✔
175
    while true
30✔
176
        next = parentmodule(query)
30✔
177
        next == target && return true
30✔
178
        next == query && return false
28✔
179
        query = next
15✔
180
    end
15✔
181
end
182

183
retrieve_modules(::Module, ::Any) = (nothing,)
6✔
184
function retrieve_modules(current_module::Module, mod_name::Symbol)
96✔
185
    mod = try
96✔
186
        getproperty(current_module, mod_name)
96✔
187
    catch
188
        return (nothing,)
19✔
189
    end
190
    return (mod isa Module ? mod : nothing,)
77✔
191
end
192
retrieve_modules(current_module::Module, mod_name::QuoteNode) = retrieve_modules(current_module, mod_name.value)
41✔
193
function retrieve_modules(current_module::Module, mod_expr::Expr)
41✔
194
    if Meta.isexpr(mod_expr, :., 2)
41✔
195
        current_module = retrieve_modules(current_module, mod_expr.args[1])[1]
41✔
196
        current_module === nothing && return (nothing,)
41✔
197
        return (current_module, retrieve_modules(current_module, mod_expr.args[2])...)
41✔
198
    else
199
        return (nothing,)
×
200
    end
201
end
202

203
add_locals!(locals, ast::Any) = nothing
1✔
204
function add_locals!(locals, ast::Expr)
14✔
205
    for arg in ast.args
14✔
206
        add_locals!(locals, arg)
23✔
207
    end
23✔
208
    return nothing
14✔
209
end
210
function add_locals!(locals, ast::Symbol)
57✔
211
    push!(locals, ast)
57✔
212
    return nothing
57✔
213
end
214

215
function collect_names_to_warn!(warnings, locals, current_module::Module, ast)
1,165✔
216
    ast isa Expr || return
1,684✔
217

218
    # don't recurse through module definitions
219
    ast.head === :module && return
646✔
220

221
    if Meta.isexpr(ast, :., 2)
646✔
222
        mod_name, name_being_accessed = ast.args
61✔
223
        # retrieve the (possibly-nested) module being named here
224
        mods = retrieve_modules(current_module, mod_name)
61✔
225
        all(x -> x isa Module, mods) || return
169✔
226
        outer_mod = first(mods)
36✔
227
        mod = last(mods)
36✔
228
        if name_being_accessed isa QuoteNode
36✔
229
            name_being_accessed = name_being_accessed.value
36✔
230
        end
231
        name_being_accessed isa Symbol || return
36✔
232
        owner = try
36✔
233
            which(mod, name_being_accessed)
36✔
234
        catch
235
            return
×
236
        end
237
        # if `owner` is a submodule of `mod`, then don't warn. E.g. the name `parse` is present in the module `JSON`
238
        # but is owned by `JSON.Parser`; we don't warn if it is accessed as `JSON.parse`.
239
        has_ancestor(owner, mod) && return
61✔
240
        # Don't warn if the name is public in the module we are accessing it
241
        Base.ispublic(mod, name_being_accessed) && return
12✔
242
        # Don't warn if accessing names defined in Core from Base if they are present in Base (e.g. `Base.throw`).
243
        mod === Base && Base.ispublic(Core, name_being_accessed) && return
11✔
244
        push!(warnings, (; outer_mod, mod, owner, name_being_accessed))
11✔
245
        # no recursion
246
        return
11✔
247
    elseif Meta.isexpr(ast, :(=), 2)
585✔
248
        lhs, rhs = ast.args
47✔
249
        # any symbols we find on the LHS we will count as local. This can potentially be overzealous,
250
        # but we want to avoid false positives (unnecessary warnings) more than false negatives.
251
        add_locals!(locals, lhs)
47✔
252
        # we'll recurse into the RHS only
253
        return collect_names_to_warn!(warnings, locals, current_module, rhs)
47✔
254
    elseif Meta.isexpr(ast, :function) && length(ast.args) >= 1
538✔
255

256
        if Meta.isexpr(ast.args[1], :call, 2)
3✔
257
            func_name, func_args = ast.args[1].args
2✔
258
            # here we have a function definition and are inspecting it's arguments for local variables.
259
            # we will error on the conservative side by adding all symbols we find (regardless if they are local variables or possibly-global default values)
260
            add_locals!(locals, func_args)
2✔
261
        end
262
        # fall through to general recursion
263
    end
264

265
    for arg in ast.args
538✔
266
        collect_names_to_warn!(warnings, locals, current_module, arg)
998✔
267
    end
998✔
268

269
    return nothing
538✔
270
end
271

272
function collect_qualified_access_warnings(current_mod, ast)
120✔
273
    warnings = Set()
120✔
274
    locals = Set{Symbol}()
120✔
275
    collect_names_to_warn!(warnings, locals, current_mod, ast)
120✔
276
    filter!(warnings) do (; outer_mod)
120✔
277
        nameof(outer_mod) ∉ locals
11✔
278
    end
279
    return warnings
120✔
280
end
281

282
function warn_on_non_owning_accesses(current_mod, ast)
108✔
283
    warnings = collect_qualified_access_warnings(current_mod, ast)
108✔
284
    for (; outer_mod, mod, owner, name_being_accessed) in warnings
216✔
285
        print_qualified_access_warning(mod, owner, name_being_accessed)
2✔
286
    end
4✔
287
    return ast
108✔
288
end
289
warn_on_non_owning_accesses(ast) = warn_on_non_owning_accesses(Base.active_module(), ast)
106✔
290

291
const repl_ast_transforms = Any[softscope, warn_on_non_owning_accesses] # defaults for new REPL backends
292

293
# Allows an external package to add hooks into the code loading.
294
# The hook should take a Vector{Symbol} of package names and
295
# return true if all packages could be installed, false if not
296
# to e.g. install packages on demand
297
const install_packages_hooks = Any[]
298

299
# N.B.: Any functions starting with __repl_entry cut off backtraces when printing in the REPL.
300
# We need to do this for both the actual eval and macroexpand, since the latter can cause custom macro
301
# code to run (and error).
302
__repl_entry_lower_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Csize_t}) =
213✔
303
    Core._lower(ast, mod, toplevel_file[], toplevel_line[])[1]
304
__repl_entry_eval_expanded_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Csize_t}) =
212✔
305
    ccall(:jl_toplevel_eval_flex, Any, (Any, Any, Cint, Cint, Ptr{Ptr{UInt8}}, Ptr{Csize_t}), mod, ast, 1, 1, toplevel_file, toplevel_line)
306

307
function toplevel_eval_with_hooks(mod::Module, @nospecialize(ast), toplevel_file=Ref{Ptr{UInt8}}(Base.unsafe_convert(Ptr{UInt8}, :REPL)), toplevel_line=Ref{Csize_t}(1))
339✔
308
    if !isexpr(ast, :toplevel)
445✔
309
        ast = invokelatest(__repl_entry_lower_with_loc, mod, ast, toplevel_file, toplevel_line)
213✔
310
        check_for_missing_packages_and_run_hooks(ast)
213✔
311
        return invokelatest(__repl_entry_eval_expanded_with_loc, mod, ast, toplevel_file, toplevel_line)
212✔
312
    end
313
    local value=nothing
126✔
314
    for i = 1:length(ast.args)
126✔
315
        value = toplevel_eval_with_hooks(mod, ast.args[i], toplevel_file, toplevel_line)
233✔
316
    end
334✔
317
    return value
120✔
318
end
319

320
function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module)
106✔
321
    lasterr = nothing
106✔
322
    Base.sigatomic_begin()
106✔
323
    while true
110✔
324
        try
110✔
325
            Base.sigatomic_end()
110✔
326
            if lasterr !== nothing
110✔
327
                put!(backend.response_channel, Pair{Any, Bool}(lasterr, true))
4✔
328
            else
329
                backend.in_eval = true
106✔
330
                for xf in backend.ast_transforms
106✔
331
                    ast = Base.invokelatest(xf, ast)
230✔
332
                end
230✔
333
                value = toplevel_eval_with_hooks(mod, ast)
106✔
334
                backend.in_eval = false
101✔
335
                setglobal!(Base.MainInclude, :ans, value)
101✔
336
                put!(backend.response_channel, Pair{Any, Bool}(value, false))
101✔
337
            end
338
            break
109✔
339
        catch err
340
            if lasterr !== nothing
4✔
341
                println("SYSTEM ERROR: Failed to report error to REPL frontend")
×
342
                println(err)
×
343
            end
344
            lasterr = current_exceptions()
4✔
345
        end
346
    end
4✔
347
    Base.sigatomic_end()
105✔
348
    nothing
105✔
349
end
350

351
function check_for_missing_packages_and_run_hooks(ast)
213✔
352
    isa(ast, Expr) || return
319✔
353
    mods = modules_to_be_loaded(ast)
107✔
354
    filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules
112✔
355
    if !isempty(mods)
107✔
356
        isempty(install_packages_hooks) && load_pkg()
1✔
357
        for f in install_packages_hooks
1✔
358
            Base.invokelatest(f, mods) && return
1✔
359
        end
×
360
    end
361
end
362

363
function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol})
781✔
364
    function add!(ctx)
807✔
365
        if ctx.head == :as
26✔
366
            ctx = ctx.args[1]
1✔
367
        end
368
        if ctx.args[1] != :. # don't include local import `import .Foo`
52✔
369
            push!(mods, ctx.args[1])
23✔
370
        end
371
    end
372
    ast.head === :quote && return mods # don't search if it's not going to be run during this eval
781✔
373
    if ast.head == :call
781✔
374
        if length(ast.args) == 5 && ast.args[1] === GlobalRef(Base, :_eval_import)
382✔
375
            ctx = ast.args[4]
9✔
376
            if ctx isa QuoteNode # i.e. `Foo: bar`
9✔
377
                ctx = ctx.value
4✔
378
            else
379
                ctx = ast.args[5].value
5✔
380
            end
381
            add!(ctx)
9✔
382
        elseif length(ast.args) == 3 && ast.args[1] == GlobalRef(Base, :_eval_using)
373✔
383
            add!(ast.args[3].value)
17✔
384
        end
385
    end
386
    if ast.head !== :thunk
781✔
387
        for arg in ast.args
651✔
388
            if isexpr(arg, (:block, :if))
1,449✔
389
                _modules_to_be_loaded!(arg, mods)
4✔
390
            end
391
        end
1,449✔
392
    else
393
        code = ast.args[1]
130✔
394
        for arg in code.code
130✔
395
            isa(arg, Expr) || continue
1,141✔
396
            _modules_to_be_loaded!(arg, mods)
647✔
397
        end
1,141✔
398
    end
399
end
400

401
function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[])
402
    _modules_to_be_loaded!(ast, mods)
260✔
403
    filter!(mod::Symbol -> !in(mod, (:Base, :Main, :Core)), mods) # Exclude special non-package modules
153✔
404
    return unique(mods)
130✔
405
end
406

407
"""
408
    start_repl_backend(repl_channel::Channel, response_channel::Channel)
409

410
    Starts loop for REPL backend
411
    Returns a REPLBackend with backend_task assigned
412

413
    Deprecated since sync / async behavior cannot be selected
414
"""
415
function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any}
×
416
                            ; get_module::Function = ()->Main)
417
    # Maintain legacy behavior of asynchronous backend
418
    backend = REPLBackend(repl_channel, response_channel, false)
×
419
    # Assignment will be made twice, but will be immediately available
420
    backend.backend_task = @async start_repl_backend(backend; get_module)
×
421
    return backend
×
422
end
423

424
"""
425
    start_repl_backend(backend::REPLBackend)
426

427
    Call directly to run backend loop on current Task.
428
    Use @async for run backend on new Task.
429

430
    Does not return backend until loop is finished.
431
"""
432
function start_repl_backend(backend::REPLBackend,  @nospecialize(consumer = x -> nothing); get_module::Function = ()->Main)
56✔
433
    backend.backend_task = Base.current_task()
25✔
434
    consumer(backend)
25✔
435
    repl_backend_loop(backend, get_module)
25✔
436
    return backend
24✔
437
end
438

439
function repl_backend_loop(backend::REPLBackend, get_module::Function)
25✔
440
    # include looks at this to determine the relative include path
441
    # nothing means cwd
442
    while true
189✔
443
        tls = task_local_storage()
189✔
444
        tls[:SOURCE_PATH] = nothing
189✔
445
        ast_or_func, show_value = take!(backend.repl_channel)
189✔
446
        if show_value == -1
189✔
447
            # exit flag
448
            break
24✔
449
        end
450
        if show_value == 2 # 2 indicates a function to be called
165✔
451
            f = ast_or_func
59✔
452
            try
59✔
453
                ret = f()
59✔
454
                put!(backend.response_channel, Pair{Any, Bool}(ret, false))
58✔
455
            catch
456
                put!(backend.response_channel, Pair{Any, Bool}(current_exceptions(), true))
1✔
457
            end
458
        else
459
            ast = ast_or_func
106✔
460
            eval_user_input(ast, backend, get_module())
106✔
461
        end
462
    end
164✔
463
    return nothing
24✔
464
end
465

466
SHOW_MAXIMUM_BYTES::Int = 1_048_576
467

468
# Limit printing during REPL display
469
mutable struct LimitIO{IO_t <: IO} <: IO
470
    io::IO_t
70✔
471
    maxbytes::Int
472
    n::Int # max bytes to write
473
end
474
LimitIO(io::IO, maxbytes) = LimitIO(io, maxbytes, 0)
70✔
475

476
struct LimitIOException <: Exception
477
    maxbytes::Int
6✔
478
end
479

480
function Base.showerror(io::IO, e::LimitIOException)
×
481
    print(io, "$LimitIOException: aborted printing after attempting to print more than $(Base.format_bytes(e.maxbytes)) within a `LimitIO`.")
×
482
end
483

484
Base.displaysize(io::LimitIO) = _displaysize(io.io)
28✔
485

486
function Base.write(io::LimitIO, v::UInt8)
151✔
487
    io.n > io.maxbytes && throw(LimitIOException(io.maxbytes))
1,670✔
488
    n_bytes = write(io.io, v)
3,185✔
489
    io.n += n_bytes
1,668✔
490
    return n_bytes
1,668✔
491
end
492

493
# Semantically, we only need to override `Base.write`, but we also
494
# override `unsafe_write` for performance.
495
function Base.unsafe_write(limiter::LimitIO, p::Ptr{UInt8}, nb::UInt)
496
    # already exceeded? throw
497
    limiter.n > limiter.maxbytes && throw(LimitIOException(limiter.maxbytes))
266✔
498
    remaining = limiter.maxbytes - limiter.n # >= 0
266✔
499

500
    # Not enough bytes left; we will print up to the limit, then throw
501
    if remaining < nb
266✔
502
        if remaining > 0
2✔
503
            Base.unsafe_write(limiter.io, p, remaining)
1✔
504
        end
505
        throw(LimitIOException(limiter.maxbytes))
2✔
506
    end
507

508
    # We won't hit the limit so we'll write the full `nb` bytes
509
    bytes_written = Base.unsafe_write(limiter.io, p, nb)::Union{Int,UInt}
264✔
510
    limiter.n += bytes_written
264✔
511
    return bytes_written
264✔
512
end
513

514
struct REPLDisplay{Repl<:AbstractREPL} <: AbstractDisplay
515
    repl::Repl
34✔
516
end
517

518
function show_limited(io::IO, mime::MIME, x)
67✔
519
    try
67✔
520
        # We wrap in a LimitIO to limit the amount of printing.
521
        # We unpack `IOContext`s, since we will pass the properties on the outside.
522
        inner = io isa IOContext ? io.io : io
67✔
523
        wrapped_limiter = IOContext(LimitIO(inner, SHOW_MAXIMUM_BYTES), io)
67✔
524
        # `show_repl` to allow the hook with special syntax highlighting
525
        show_repl(wrapped_limiter, mime, x)
67✔
526
    catch e
527
        e isa LimitIOException || rethrow()
5✔
528
        printstyled(io, """…[printing stopped after displaying $(Base.format_bytes(e.maxbytes)); call `show(stdout, MIME"text/plain"(), ans)` to print without truncation]"""; color=:light_yellow, bold=true)
3✔
529
    end
530
end
531

532
function display(d::REPLDisplay, mime::MIME"text/plain", x)
62✔
533
    x = Ref{Any}(x)
62✔
534
    with_repl_linfo(d.repl) do io
62✔
535
        io = IOContext(io, :limit => true, :module => Base.active_module(d)::Module)
121✔
536
        if d.repl isa LineEditREPL
62✔
537
            mistate = d.repl.mistate
59✔
538
            mode = LineEdit.mode(mistate)
59✔
539
            if mode isa LineEdit.Prompt
59✔
540
                LineEdit.write_output_prefix(io, mode, get(io, :color, false)::Bool)
236✔
541
            end
542
        end
543
        get(io, :color, false)::Bool && write(io, answer_color(d.repl))
68✔
544
        if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext)
62✔
545
            # this can override the :limit property set initially
546
            io = foldl(IOContext, d.repl.options.iocontext, init=io)
59✔
547
        end
548
        show_limited(io, mime, x[])
62✔
549
        println(io)
61✔
550
    end
551
    return nothing
61✔
552
end
553

554
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)
62✔
555

556
show_repl(io::IO, mime::MIME"text/plain", x) = show(io, mime, x)
65✔
557

558
function show_repl(io::IO, mime::MIME"text/plain", c::AbstractChar)
4✔
559
    show(io, mime, c) # Call the original Base.show
4✔
560
    # Check for LaTeX/emoji alias and print if found and using symbol_latex which is used in help?> mode
561
    latex = symbol_latex(string(c))
4✔
562
    if !isempty(latex)
4✔
563
        print(io, ", input as ")
2✔
564
        printstyled(io, latex, "<tab>"; color=:cyan)
2✔
565
    end
566
end
567

568
show_repl(io::IO, ::MIME"text/plain", ex::Expr) =
2✔
569
    print(io, JuliaSyntaxHighlighting.highlight(
570
        sprint(show, ex, context=IOContext(io, :color => false))))
571

572
function print_response(repl::AbstractREPL, response, show_value::Bool, have_color::Bool)
102✔
573
    repl.waserror = response[2]
102✔
574
    with_repl_linfo(repl) do io
204✔
575
        io = IOContext(io, :module => Base.active_module(repl)::Module)
201✔
576
        print_response(io, response, backend(repl), show_value, have_color, specialdisplay(repl))
102✔
577
    end
578
    return nothing
102✔
579
end
580

581
function repl_display_error(errio::IO, @nospecialize errval)
7✔
582
    # this will be set to true if types in the stacktrace are truncated
583
    limitflag = Ref(false)
7✔
584
    errio = IOContext(errio, :stacktrace_types_limited => limitflag)
7✔
585
    Base.invokelatest(Base.display_error, errio, errval)
7✔
586
    if limitflag[]
6✔
587
        print(errio, "Some type information was truncated. Use `show(err)` to see complete types.")
2✔
588
        println(errio)
2✔
589
    end
590
    return nothing
6✔
591
end
592

593
function print_response(errio::IO, response, backend::Union{REPLBackendRef,Nothing}, show_value::Bool, have_color::Bool, specialdisplay::Union{AbstractDisplay,Nothing}=nothing)
103✔
594
    Base.sigatomic_begin()
103✔
595
    val, iserr = response
103✔
596
    while true
103✔
597
        try
103✔
598
            Base.sigatomic_end()
103✔
599
            if iserr
103✔
600
                val = Base.scrub_repl_backtrace(val)
6✔
601
                Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val)
10✔
602
                repl_display_error(errio, val)
6✔
603
            else
604
                if val !== nothing && show_value
97✔
605
                    val2, iserr = if specialdisplay === nothing
62✔
606
                        # display calls may require being run on the main thread
607
                        call_on_backend(backend) do
93✔
608
                            Base.invokelatest(display, val)
48✔
609
                        end
610
                    else
611
                        call_on_backend(backend) do
76✔
612
                            Base.invokelatest(display, specialdisplay, val)
14✔
613
                        end
614
                    end
615
                    if iserr
62✔
616
                        println(errio, "Error showing value of type ", typeof(val), ":")
1✔
617
                        throw(val2)
1✔
618
                    end
619
                end
620
            end
621
            break
103✔
622
        catch ex
623
            if iserr
1✔
624
                println(errio) # an error during printing is likely to leave us mid-line
1✔
625
                println(errio, "SYSTEM (REPL): showing an error caused an error")
1✔
626
                try
1✔
627
                    excs = Base.scrub_repl_backtrace(current_exceptions())
1✔
628
                    setglobal!(Base.MainInclude, :err, excs)
1✔
629
                    repl_display_error(errio, excs)
2✔
630
                catch e
631
                    # at this point, only print the name of the type as a Symbol to
632
                    # minimize the possibility of further errors.
633
                    println(errio)
1✔
634
                    println(errio, "SYSTEM (REPL): caught exception of type ", typeof(e).name.name,
1✔
635
                            " while trying to handle a nested exception; giving up")
636
                end
637
                break
1✔
638
            end
639
            val = current_exceptions()
×
640
            iserr = true
×
641
        end
642
    end
×
643
    Base.sigatomic_end()
103✔
644
    nothing
103✔
645
end
646

647

648

649
"""
650
    run_repl(repl::AbstractREPL)
651
    run_repl(repl, consumer = backend->nothing; backend_on_current_task = true)
652

653
    Main function to start the REPL
654

655
    consumer is an optional function that takes a REPLBackend as an argument
656
"""
657
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend())
90✔
658
    backend_ref = REPLBackendRef(backend)
23✔
659
    cleanup = @task try
45✔
660
            destroy(backend_ref, t)
22✔
661
        catch e
662
            Core.print(Core.stderr, "\nINTERNAL ERROR: ")
×
663
            Core.println(Core.stderr, e)
×
664
            Core.println(Core.stderr, catch_backtrace())
×
665
        end
666
    get_module = () -> Base.active_module(repl)
125✔
667
    if backend_on_current_task
23✔
668
        t = @async run_frontend(repl, backend_ref)
46✔
669
        errormonitor(t)
23✔
670
        Base._wait2(t, cleanup)
23✔
671
        start_repl_backend(backend, consumer; get_module)
23✔
672
    else
673
        t = @async start_repl_backend(backend, consumer; get_module)
×
674
        errormonitor(t)
×
675
        Base._wait2(t, cleanup)
×
676
        run_frontend(repl, backend_ref)
×
677
    end
678
    return backend
22✔
679
end
680

681
## BasicREPL ##
682

683
mutable struct BasicREPL <: AbstractREPL
684
    terminal::TextTerminal
685
    waserror::Bool
686
    frontend_task::Task
687
    BasicREPL(t) = new(t, false)
3✔
688
end
689

690
outstream(r::BasicREPL) = r.terminal
6✔
691
hascolor(r::BasicREPL) = hascolor(r.terminal)
×
692

693
function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
3✔
694
    repl.frontend_task = current_task()
3✔
695
    d = REPLDisplay(repl)
3✔
696
    dopushdisplay = !in(d,Base.Multimedia.displays)
6✔
697
    dopushdisplay && pushdisplay(d)
3✔
698
    hit_eof = false
3✔
699
    while true
6✔
700
        Base.reseteof(repl.terminal)
6✔
701
        write(repl.terminal, JULIA_PROMPT)
6✔
702
        line = ""
6✔
703
        ast = nothing
6✔
704
        interrupted = false
6✔
705
        while true
6✔
706
            try
6✔
707
                line *= readline(repl.terminal, keep=true)
6✔
708
            catch e
709
                if isa(e,InterruptException)
×
710
                    try # raise the debugger if present
×
711
                        ccall(:jl_raise_debugger, Int, ())
×
712
                    catch
×
713
                    end
714
                    line = ""
×
715
                    interrupted = true
×
716
                    break
×
717
                elseif isa(e,EOFError)
×
718
                    hit_eof = true
×
719
                    break
×
720
                else
721
                    rethrow()
×
722
                end
723
            end
724
            ast = Base.parse_input_line(line)
12✔
725
            (isa(ast,Expr) && ast.head === :incomplete) || break
12✔
726
        end
×
727
        if !isempty(line)
6✔
728
            response = eval_on_backend(ast, backend)
4✔
729
            print_response(repl, response, !ends_with_semicolon(line), false)
3✔
730
        end
731
        write(repl.terminal, '\n')
5✔
732
        ((!interrupted && isempty(line)) || hit_eof) && break
8✔
733
    end
3✔
734
    # terminate backend
735
    put!(backend.repl_channel, (nothing, -1))
2✔
736
    dopushdisplay && popdisplay(d)
2✔
737
    nothing
2✔
738
end
739

740
## LineEditREPL ##
741

742
mutable struct LineEditREPL <: AbstractREPL
743
    t::TextTerminal
744
    hascolor::Bool
745
    prompt_color::String
746
    input_color::String
747
    answer_color::String
748
    shell_color::String
749
    help_color::String
750
    pkg_color::String
751
    history_file::Bool
752
    in_shell::Bool
753
    in_help::Bool
754
    envcolors::Bool
755
    waserror::Bool
756
    specialdisplay::Union{Nothing,AbstractDisplay}
757
    options::Options
758
    mistate::Union{MIState,Nothing}
759
    last_shown_line_infos::Vector{Tuple{String,Int}}
760
    interface::ModalInterface
761
    backendref::REPLBackendRef
762
    frontend_task::Task
763
    function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
27✔
764
        opts = Options()
27✔
765
        opts.hascolor = hascolor
27✔
766
        if !hascolor
27✔
767
            opts.beep_colors = [""]
×
768
        end
769
        new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
27✔
770
            in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
771
    end
772
end
773
outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t)
168✔
774
specialdisplay(r::LineEditREPL) = r.specialdisplay
99✔
775
specialdisplay(r::AbstractREPL) = nothing
3✔
776
terminal(r::LineEditREPL) = r.t
154✔
777
hascolor(r::LineEditREPL) = r.hascolor
199✔
778

779
LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
54✔
780
    LineEditREPL(t, hascolor,
781
        hascolor ? Base.text_colors[:green] : "",
782
        hascolor ? Base.input_color() : "",
783
        hascolor ? Base.answer_color() : "",
784
        hascolor ? Base.text_colors[:red] : "",
785
        hascolor ? Base.text_colors[:yellow] : "",
786
        hascolor ? Base.text_colors[:blue] : "",
787
        false, false, false, envcolors
788
    )
789

790
mutable struct REPLCompletionProvider <: CompletionProvider
791
    modifiers::LineEdit.Modifiers
24✔
792
end
793
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
24✔
794

795
mutable struct ShellCompletionProvider <: CompletionProvider end
24✔
796
struct LatexCompletions <: CompletionProvider end
797

798
Base.active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : mistate.active_module
5,078✔
799
Base.active_module(::AbstractREPL) = Main
12✔
800
Base.active_module(d::REPLDisplay) = Base.active_module(d.repl)
121✔
801

802
setmodifiers!(c::CompletionProvider, m::LineEdit.Modifiers) = nothing
×
803

804
setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m
×
805

806
"""
807
    activate(mod::Module=Main)
808

809
Set `mod` as the default contextual module in the REPL,
810
both for evaluating expressions and printing them.
811
"""
812
function activate(mod::Module=Main; interactive_utils::Bool=true)
×
813
    mistate = (Base.active_repl::LineEditREPL).mistate
×
814
    mistate === nothing && return nothing
×
815
    mistate.active_module = mod
×
816
    interactive_utils && Base.load_InteractiveUtils(mod)
×
817
    return nothing
×
818
end
819

820
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
32✔
821

822
# Convert inclusive-inclusive 1-based char indexing to inclusive-exclusive byte Region.
823
to_region(s, r) = first(r)-1 => (length(r) > 0 ? nextind(s, last(r))-1 : first(r)-1)
26✔
824

825
function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module; hint::Bool=false)
24✔
826
    full = LineEdit.input_string(s)
12✔
827
    ret, range, should_complete = completions(full, thisind(full, position(s)), mod, c.modifiers.shift, hint)
24✔
828
    range = to_region(full, range)
23✔
829
    c.modifiers = LineEdit.Modifiers()
12✔
830
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
12✔
831
end
832

833
function complete_line(c::ShellCompletionProvider, s::PromptState; hint::Bool=false)
4✔
834
    full = LineEdit.input_string(s)
2✔
835
    ret, range, should_complete = shell_completions(full, thisind(full, position(s)), hint)
4✔
836
    range = to_region(full, range)
3✔
837
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
2✔
838
end
839

840
function complete_line(c::LatexCompletions, s; hint::Bool=false)
×
841
    full = LineEdit.input_string(s)::String
×
842
    ret, range, should_complete = bslash_completions(full, thisind(full, position(s)), hint)[2]
×
843
    range = to_region(full, range)
×
844
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
845
end
846

847
with_repl_linfo(f, repl) = f(outstream(repl))
6✔
848
function with_repl_linfo(f, repl::LineEditREPL)
158✔
849
    linfos = Tuple{String,Int}[]
158✔
850
    io = IOContext(outstream(repl), :last_shown_line_infos => linfos)
158✔
851
    f(io)
158✔
852
    if !isempty(linfos)
157✔
853
        repl.last_shown_line_infos = linfos
6✔
854
    end
855
    nothing
157✔
856
end
857

858
mutable struct REPLHistoryProvider <: HistoryProvider
859
    history::Vector{String}
29✔
860
    file_path::String
861
    history_file::Union{Nothing,IO}
862
    start_idx::Int
863
    cur_idx::Int
864
    last_idx::Int
865
    last_buffer::IOBuffer
866
    last_mode::Union{Nothing,Prompt}
867
    mode_mapping::Dict{Symbol,Prompt}
868
    modes::Vector{Symbol}
869
end
870
REPLHistoryProvider(mode_mapping::Dict{Symbol}) =
29✔
871
    REPLHistoryProvider(String[], "", nothing, 0, 0, -1, IOBuffer(),
872
                        nothing, mode_mapping, UInt8[])
873

874
invalid_history_message(path::String) = """
×
875
Invalid history file ($path) format:
876
If you have a history file left over from an older version of Julia,
877
try renaming or deleting it.
878
Invalid character: """
879

880
munged_history_message(path::String) = """
×
881
Invalid history file ($path) format:
882
An editor may have converted tabs to spaces at line """
883

884
function hist_open_file(hp::REPLHistoryProvider)
885
    f = open(hp.file_path, read=true, write=true, create=true)
4✔
886
    hp.history_file = f
4✔
887
    seekend(f)
4✔
888
end
889

890
function hist_from_file(hp::REPLHistoryProvider, path::String)
8✔
891
    getline(lines, i) = i > length(lines) ? "" : lines[i]
284✔
892
    file_lines = readlines(path)
8✔
893
    countlines = 0
8✔
894
    while true
42✔
895
        # First parse the metadata that starts with '#' in particular the REPL mode
896
        countlines += 1
42✔
897
        line = getline(file_lines, countlines)
76✔
898
        mode = :julia
42✔
899
        isempty(line) && break
42✔
900
        line[1] != '#' &&
68✔
901
            error(invalid_history_message(path), repr(line[1]), " at line ", countlines)
902
        while !isempty(line)
102✔
903
            startswith(line, '#') || break
204✔
904
            if startswith(line, "# mode: ")
68✔
905
                mode = Symbol(SubString(line, 9))
68✔
906
            end
907
            countlines += 1
68✔
908
            line = getline(file_lines, countlines)
136✔
909
        end
68✔
910
        isempty(line) && break
34✔
911

912
        # Now parse the code for the current REPL mode
913
        line[1] == ' '  &&
68✔
914
            error(munged_history_message(path), countlines)
915
        line[1] != '\t' &&
68✔
916
            error(invalid_history_message(path), repr(line[1]), " at line ", countlines)
917
        lines = String[]
34✔
918
        while !isempty(line)
34✔
919
            push!(lines, chomp(SubString(line, 2)))
68✔
920
            next_line = getline(file_lines, countlines+1)
64✔
921
            isempty(next_line) && break
34✔
922
            first(next_line) == ' '  && error(munged_history_message(path), countlines)
60✔
923
            # A line not starting with a tab means we are done with code for this entry
924
            first(next_line) != '\t' && break
60✔
925
            countlines += 1
×
926
            line = getline(file_lines, countlines)
×
927
        end
×
928
        push!(hp.modes, mode)
34✔
929
        push!(hp.history, join(lines, '\n'))
34✔
930
    end
34✔
931
    hp.start_idx = length(hp.history)
8✔
932
    return hp
8✔
933
end
934

935
function add_history(hist::REPLHistoryProvider, s::PromptState)
117✔
936
    str = rstrip(takestring!(copy(s.input_buffer)))
117✔
937
    isempty(strip(str)) && return
117✔
938
    mode = mode_idx(hist, LineEdit.mode(s))
99✔
939
    !isempty(hist.history) &&
99✔
940
        isequal(mode, hist.modes[end]) && str == hist.history[end] && return
941
    push!(hist.modes, mode)
93✔
942
    push!(hist.history, str)
93✔
943
    hist.history_file === nothing && return
93✔
944
    entry = """
16✔
945
    # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time()))
946
    # mode: $mode
947
    $(replace(str, r"^"ms => "\t"))
948
    """
949
    try
16✔
950
        seekend(hist.history_file)
16✔
951
    catch err
952
        (err isa SystemError) || rethrow()
×
953
        # File handle might get stale after a while, especially under network file systems
954
        # If this doesn't fix it (e.g. when file is deleted), we'll end up rethrowing anyway
955
        hist_open_file(hist)
×
956
    end
957
    if isfile(hist.file_path)
16✔
958
        FileWatching.mkpidlock(hist.file_path  * ".pid", stale_age=3) do
16✔
959
            print(hist.history_file, entry)
32✔
960
            flush(hist.history_file)
16✔
961
        end
962
    else # handle eg devnull
963
        print(hist.history_file, entry)
×
964
        flush(hist.history_file)
×
965
    end
966
    nothing
16✔
967
end
968

969
function history_move(s::Union{LineEdit.MIState,LineEdit.PrefixSearchState}, hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx)
96✔
970
    max_idx = length(hist.history) + 1
136✔
971
    @assert 1 <= hist.cur_idx <= max_idx
96✔
972
    (1 <= idx <= max_idx) || return :none
98✔
973
    idx != hist.cur_idx || return :none
94✔
974

975
    # save the current line
976
    if save_idx == max_idx
94✔
977
        hist.last_mode = LineEdit.mode(s)
31✔
978
        hist.last_buffer = copy(LineEdit.buffer(s))
46✔
979
    else
980
        hist.history[save_idx] = LineEdit.input_string(s)
63✔
981
        hist.modes[save_idx] = mode_idx(hist, LineEdit.mode(s))
63✔
982
    end
983

984
    # load the saved line
985
    if idx == max_idx
94✔
986
        last_buffer = hist.last_buffer
10✔
987
        LineEdit.transition(s, hist.last_mode) do
14✔
988
            LineEdit.replace_line(s, last_buffer)
10✔
989
        end
990
        hist.last_mode = nothing
10✔
991
        hist.last_buffer = IOBuffer()
10✔
992
    else
993
        if haskey(hist.mode_mapping, hist.modes[idx])
168✔
994
            LineEdit.transition(s, hist.mode_mapping[hist.modes[idx]]) do
68✔
995
                LineEdit.replace_line(s, hist.history[idx])
68✔
996
            end
997
        else
998
            return :skip
16✔
999
        end
1000
    end
1001
    hist.cur_idx = idx
78✔
1002

1003
    return :ok
78✔
1004
end
1005

1006
# REPL History can also transitions modes
1007
function LineEdit.accept_result_newmode(hist::REPLHistoryProvider)
27✔
1008
    if 1 <= hist.cur_idx <= length(hist.modes)
27✔
1009
        return hist.mode_mapping[hist.modes[hist.cur_idx]]
23✔
1010
    end
1011
    return nothing
4✔
1012
end
1013

1014
function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
34✔
1015
                      num::Int=1, save_idx::Int = hist.cur_idx)
1016
    num <= 0 && return history_next(s, hist, -num, save_idx)
58✔
1017
    hist.last_idx = -1
32✔
1018
    m = history_move(s, hist, hist.cur_idx-num, save_idx)
32✔
1019
    if m === :ok
32✔
1020
        LineEdit.move_input_start(s)
48✔
1021
        LineEdit.reset_key_repeats(s) do
24✔
1022
            LineEdit.move_line_end(s)
24✔
1023
        end
1024
        return LineEdit.refresh_line(s)
24✔
1025
    elseif m === :skip
8✔
1026
        return history_prev(s, hist, num+1, save_idx)
8✔
1027
    else
1028
        return Terminals.beep(s)
×
1029
    end
1030
end
1031

1032
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
26✔
1033
                      num::Int=1, save_idx::Int = hist.cur_idx)
1034
    if num == 0
44✔
1035
        Terminals.beep(s)
×
1036
        return
×
1037
    end
1038
    num < 0 && return history_prev(s, hist, -num, save_idx)
26✔
1039
    cur_idx = hist.cur_idx
24✔
1040
    max_idx = length(hist.history) + 1
24✔
1041
    if cur_idx == max_idx && 0 < hist.last_idx
24✔
1042
        # issue #6312
1043
        cur_idx = hist.last_idx
×
1044
        hist.last_idx = -1
×
1045
    end
1046
    m = history_move(s, hist, cur_idx+num, save_idx)
24✔
1047
    if m === :ok
24✔
1048
        LineEdit.move_input_end(s)
16✔
1049
        return LineEdit.refresh_line(s)
16✔
1050
    elseif m === :skip
8✔
1051
        return history_next(s, hist, num+1, save_idx)
6✔
1052
    else
1053
        return Terminals.beep(s)
2✔
1054
    end
1055
end
1056

1057
history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) =
6✔
1058
    history_prev(s, hist, hist.cur_idx - 1 -
1059
                 (hist.cur_idx > hist.start_idx+1 ? hist.start_idx : 0))
1060

1061
history_last(s::LineEdit.MIState, hist::REPLHistoryProvider) =
4✔
1062
    history_next(s, hist, length(hist.history) - hist.cur_idx + 1)
1063

1064
function history_move_prefix(s::LineEdit.PrefixSearchState,
38✔
1065
                             hist::REPLHistoryProvider,
1066
                             prefix::AbstractString,
1067
                             backwards::Bool,
1068
                             cur_idx::Int = hist.cur_idx)
1069
    cur_response = takestring!(copy(LineEdit.buffer(s)))
74✔
1070
    # when searching forward, start at last_idx
1071
    if !backwards && hist.last_idx > 0
38✔
1072
        cur_idx = hist.last_idx
1✔
1073
    end
1074
    hist.last_idx = -1
38✔
1075
    max_idx = length(hist.history)+1
38✔
1076
    idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx)
65✔
1077
    for idx in idxs
38✔
1078
        if (idx == max_idx) || (startswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || get(hist.mode_mapping, hist.modes[idx], nothing) !== LineEdit.mode(s)))
192✔
1079
            m = history_move(s, hist, idx)
36✔
1080
            if m === :ok
36✔
1081
                if idx == max_idx
34✔
1082
                    # on resuming the in-progress edit, leave the cursor where the user last had it
1083
                elseif isempty(prefix)
30✔
1084
                    # on empty prefix search, move cursor to the end
1085
                    LineEdit.move_input_end(s)
14✔
1086
                else
1087
                    # otherwise, keep cursor at the prefix position as a visual cue
1088
                    seek(LineEdit.buffer(s), sizeof(prefix))
16✔
1089
                end
1090
                LineEdit.refresh_line(s)
34✔
1091
                return :ok
34✔
1092
            elseif m === :skip
2✔
1093
                return history_move_prefix(s,hist,prefix,backwards,idx)
2✔
1094
            end
1095
        end
1096
    end
124✔
1097
    Terminals.beep(s)
2✔
1098
    nothing
2✔
1099
end
1100
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
5✔
1101
    history_move_prefix(s, hist, prefix, false)
1102
history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
31✔
1103
    history_move_prefix(s, hist, prefix, true)
1104

1105
function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer,
28✔
1106
                        backwards::Bool=false, skip_current::Bool=false)
1107

1108
    qpos = position(query_buffer)
28✔
1109
    qpos > 0 || return true
28✔
1110
    searchdata = beforecursor(query_buffer)
28✔
1111
    response_str = takestring!(copy(response_buffer))
28✔
1112

1113
    # Alright, first try to see if the current match still works
1114
    a = position(response_buffer) + 1 # position is zero-indexed
28✔
1115
    # FIXME: I'm pretty sure this is broken since it uses an index
1116
    # into the search data to index into the response string
1117
    b = a + sizeof(searchdata)
28✔
1118
    b = b ≤ ncodeunits(response_str) ? prevind(response_str, b) : b-1
48✔
1119
    b = min(lastindex(response_str), b) # ensure that b is valid
50✔
1120

1121
    searchstart = backwards ? b : a
28✔
1122
    if searchdata == response_str[a:b]
44✔
1123
        if skip_current
10✔
1124
            searchstart = backwards ? prevind(response_str, b) : nextind(response_str, a)
4✔
1125
        else
1126
            return true
6✔
1127
        end
1128
    end
1129

1130
    # Start searching
1131
    # First the current response buffer
1132
    if 1 <= searchstart <= lastindex(response_str)
36✔
1133
        match = backwards ? findprev(searchdata, response_str, searchstart) :
14✔
1134
                            findnext(searchdata, response_str, searchstart)
1135
        if match !== nothing
14✔
1136
            seek(response_buffer, first(match) - 1)
12✔
1137
            return true
6✔
1138
        end
1139
    end
1140

1141
    # Now search all the other buffers
1142
    idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):1:length(hist.history))
32✔
1143
    for idx in idxs
16✔
1144
        h = hist.history[idx]
40✔
1145
        match = backwards ? findlast(searchdata, h) : findfirst(searchdata, h)
80✔
1146
        if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.modes[idx])
54✔
1147
            truncate(response_buffer, 0)
12✔
1148
            write(response_buffer, h)
12✔
1149
            seek(response_buffer, first(match) - 1)
24✔
1150
            hist.cur_idx = idx
12✔
1151
            return true
12✔
1152
        end
1153
    end
52✔
1154

1155
    return false
4✔
1156
end
1157

1158
function history_reset_state(hist::REPLHistoryProvider)
1159
    if hist.cur_idx != length(hist.history) + 1
271✔
1160
        hist.last_idx = hist.cur_idx
130✔
1161
        hist.cur_idx = length(hist.history) + 1
130✔
1162
    end
1163
    nothing
271✔
1164
end
1165
LineEdit.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist)
240✔
1166

1167
function return_callback(s)
96✔
1168
    ast = Base.parse_input_line(takestring!(copy(LineEdit.buffer(s))), depwarn=false)
96✔
1169
    return !(isa(ast, Expr) && ast.head === :incomplete)
96✔
1170
end
1171

1172
find_hist_file() = get(ENV, "JULIA_HISTORY",
4✔
1173
                       !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") :
1174
                       error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set."))
1175

1176
backend(r::AbstractREPL) = hasproperty(r, :backendref) ? r.backendref : nothing
200✔
1177

1178

1179
function eval_on_backend(ast, backend::REPLBackendRef)
102✔
1180
    put!(backend.repl_channel, (ast, 1)) # (f, show_value)
102✔
1181
    return take!(backend.response_channel) # (val, iserr)
102✔
1182
end
1183
function call_on_backend(f, backend::REPLBackendRef)
14✔
1184
    applicable(f) || error("internal error: f is not callable")
59✔
1185
    put!(backend.repl_channel, (f, 2)) # (f, show_value) 2 indicates function (rather than ast)
59✔
1186
    return take!(backend.response_channel) # (val, iserr)
59✔
1187
end
1188
# if no backend just eval (used by tests)
1189
eval_on_backend(ast, backend::Nothing) = error("no backend for eval ast")
×
1190
function call_on_backend(f, backend::Nothing)
3✔
1191
    try
3✔
1192
        ret = f()
3✔
1193
        return (ret, false) # (val, iserr)
3✔
1194
    catch
1195
        return (current_exceptions(), true)
×
1196
    end
1197
end
1198

1199

1200
function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon::Bool = true)
96✔
1201
    return function do_respond(s::MIState, buf, ok::Bool)
230✔
1202
        if !ok
134✔
1203
            return transition(s, :abort)
20✔
1204
        end
1205
        line = String(take!(buf)::Vector{UInt8})
213✔
1206
        if !isempty(line) || pass_empty
129✔
1207
            reset(repl)
99✔
1208
            local response
1209
            try
99✔
1210
                ast = Base.invokelatest(f, line)
99✔
1211
                response = eval_on_backend(ast, backend(repl))
98✔
1212
            catch
1213
                response = Pair{Any, Bool}(current_exceptions(), true)
1✔
1214
            end
1215
            hide_output = suppress_on_semicolon && ends_with_semicolon(line)
99✔
1216
            print_response(repl, response, !hide_output, hascolor(repl))
99✔
1217
        end
1218
        prepare_next(repl)
114✔
1219
        reset_state(s)
114✔
1220
        return s.current_mode.sticky ? true : transition(s, main)
114✔
1221
    end
1222
end
1223

1224
function reset(repl::LineEditREPL)
99✔
1225
    raw!(repl.t, false)
99✔
1226
    hascolor(repl) && print(repl.t, Base.text_colors[:normal])
99✔
1227
    nothing
99✔
1228
end
1229

1230
function prepare_next(repl::LineEditREPL)
114✔
1231
    println(terminal(repl))
114✔
1232
end
1233

1234
function mode_keymap(julia_prompt::Prompt)
1235
    AnyDict(
26✔
1236
    '\b' => function (s::MIState,o...)
7✔
1237
        if isempty(s) || position(LineEdit.buffer(s)) == 0
7✔
1238
            buf = copy(LineEdit.buffer(s))
7✔
1239
            transition(s, julia_prompt) do
7✔
1240
                LineEdit.state(s, julia_prompt).input_buffer = buf
7✔
1241
            end
1242
        else
1243
            LineEdit.edit_backspace(s)
×
1244
        end
1245
    end,
1246
    "^C" => function (s::MIState,o...)
1247
        LineEdit.move_input_end(s)
1248
        LineEdit.refresh_line(s)
1249
        print(LineEdit.terminal(s), "^C\n\n")
1250
        transition(s, julia_prompt)
1251
        transition(s, :reset)
1252
        LineEdit.refresh_line(s)
1253
    end)
1254
end
1255

1256
repl_filename(repl, hp::REPLHistoryProvider) = "REPL[$(max(length(hp.history)-hp.start_idx, 1))]"
88✔
1257
repl_filename(repl, hp) = "REPL"
×
1258

1259
const JL_PROMPT_PASTE = Ref(true)
1260
enable_promptpaste(v::Bool) = JL_PROMPT_PASTE[] = v
×
1261

1262
function contextual_prompt(repl::LineEditREPL, prompt::Union{String,Function})
1263
    function ()
2,336✔
1264
        mod = Base.active_module(repl)
4,566✔
1265
        prefix = mod == Main ? "" : string('(', mod, ") ")
2,314✔
1266
        pr = prompt isa String ? prompt : prompt()
2,287✔
1267
        prefix * pr
2,287✔
1268
    end
1269
end
1270

1271
setup_interface(
68✔
1272
    repl::LineEditREPL;
1273
    # those keyword arguments may be deprecated eventually in favor of the Options mechanism
1274
    hascolor::Bool = repl.options.hascolor,
1275
    extra_repl_keymap::Any = repl.options.extra_keymap
1276
) = setup_interface(repl, hascolor, extra_repl_keymap)
1277

1278

1279
# This non keyword method can be precompiled which is important
1280
function setup_interface(
24✔
1281
    repl::LineEditREPL,
1282
    hascolor::Bool,
1283
    extra_repl_keymap::Any, # Union{Dict,Vector{<:Dict}},
1284
)
1285
    # The precompile statement emitter has problem outputting valid syntax for the
1286
    # type of `Union{Dict,Vector{<:Dict}}` (see #28808).
1287
    # This function is however important to precompile for REPL startup time, therefore,
1288
    # make the type Any and just assert that we have the correct type below.
1289
    @assert extra_repl_keymap isa Union{Dict,Vector{<:Dict}}
24✔
1290

1291
    ###
1292
    #
1293
    # This function returns the main interface that describes the REPL
1294
    # functionality, it is called internally by functions that setup a
1295
    # Terminal-based REPL frontend.
1296
    #
1297
    # See run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
1298
    # for usage
1299
    #
1300
    ###
1301

1302
    ###
1303
    # We setup the interface in two stages.
1304
    # First, we set up all components (prompt,rsearch,shell,help)
1305
    # Second, we create keymaps with appropriate transitions between them
1306
    #   and assign them to the components
1307
    #
1308
    ###
1309

1310
    ############################### Stage I ################################
1311

1312
    # This will provide completions for REPL and help mode
1313
    replc = REPLCompletionProvider()
24✔
1314

1315
    # Set up the main Julia prompt
1316
    julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT);
48✔
1317
        # Copy colors from the prompt object
1318
        prompt_prefix = hascolor ? repl.prompt_color : "",
1319
        prompt_suffix = hascolor ?
1320
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1321
        repl = repl,
1322
        complete = replc,
1323
        on_enter = return_callback)
1324

1325
    # Setup help mode
1326
    help_mode = Prompt(contextual_prompt(repl, HELP_PROMPT),
48✔
1327
        prompt_prefix = hascolor ? repl.help_color : "",
1328
        prompt_suffix = hascolor ?
1329
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1330
        repl = repl,
1331
        complete = replc,
1332
        # When we're done transform the entered line into a call to helpmode function
1333
        on_done = respond(line::String->helpmode(outstream(repl), line, repl.mistate.active_module),
2✔
1334
                          repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false))
1335

1336

1337
    # Set up shell mode
1338
    shell_mode = Prompt(SHELL_PROMPT;
48✔
1339
        prompt_prefix = hascolor ? repl.shell_color : "",
1340
        prompt_suffix = hascolor ?
1341
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1342
        repl = repl,
1343
        complete = ShellCompletionProvider(),
1344
        # Transform "foo bar baz" into `foo bar baz` (shell quoting)
1345
        # and pass into Base.repl_cmd for processing (handles `ls` and `cd`
1346
        # special)
1347
        on_done = respond(repl, julia_prompt) do line
1348
            Expr(:call, :(Base.repl_cmd),
9✔
1349
                :(Base.cmd_gen($(Base.shell_parse(line::String)[1]))),
1350
                outstream(repl))
1351
        end,
1352
        sticky = true)
1353

1354
    # Set up dummy Pkg mode that will be replaced once Pkg is loaded
1355
    # use 6 dots to occupy the same space as the most likely "@v1.xx" env name
1356
    dummy_pkg_mode = Prompt(Pkg_promptf,
48✔
1357
        prompt_prefix = hascolor ? repl.pkg_color : "",
1358
        prompt_suffix = hascolor ?
1359
        (repl.envcolors ? Base.input_color : repl.input_color) : "",
1360
        repl = repl,
1361
        complete = LineEdit.EmptyCompletionProvider(),
1362
        on_done = respond(line->nothing, repl, julia_prompt),
1363
        on_enter = function (s::MIState)
1364
                # This is hit when the user tries to execute a command before the real Pkg mode has been
1365
                # switched to. Ok to do this even if Pkg is loading on the other task because of the loading lock.
1366
                REPLExt = load_pkg()
1367
                if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1368
                    for mode in repl.interface.modes
1369
                        if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1370
                            # pkg mode
1371
                            buf = copy(LineEdit.buffer(s))
1372
                            transition(s, mode) do
1373
                                LineEdit.state(s, mode).input_buffer = buf
1374
                            end
1375
                        end
1376
                    end
1377
                end
1378
                return true
1379
            end,
1380
        sticky = true)
1381

1382

1383
    ################################# Stage II #############################
1384

1385
    # Setup history
1386
    # We will have a unified history for all REPL modes
1387
    hp = REPLHistoryProvider(Dict{Symbol,Prompt}(:julia => julia_prompt,
24✔
1388
                                                 :shell => shell_mode,
1389
                                                 :help  => help_mode,
1390
                                                 :pkg  => dummy_pkg_mode))
1391
    if repl.history_file
24✔
1392
        try
4✔
1393
            hist_path = find_hist_file()
4✔
1394
            mkpath(dirname(hist_path))
4✔
1395
            hp.file_path = hist_path
4✔
1396
            hist_open_file(hp)
4✔
1397
            finalizer(replc) do replc
4✔
1398
                close(hp.history_file)
4✔
1399
            end
1400
            hist_from_file(hp, hist_path)
4✔
1401
        catch
1402
            # use REPL.hascolor to avoid using the local variable with the same name
1403
            print_response(repl, Pair{Any, Bool}(current_exceptions(), true), true, REPL.hascolor(repl))
×
1404
            println(outstream(repl))
×
1405
            @info "Disabling history file for this session"
×
1406
            repl.history_file = false
×
1407
        end
1408
    end
1409
    history_reset_state(hp)
24✔
1410
    julia_prompt.hist = hp
24✔
1411
    shell_mode.hist = hp
24✔
1412
    help_mode.hist = hp
24✔
1413
    dummy_pkg_mode.hist = hp
24✔
1414

1415
    julia_prompt.on_done = respond(x->Base.parse_input_line(x,filename=repl_filename(repl,hp)), repl, julia_prompt)
112✔
1416

1417

1418
    search_prompt, skeymap = LineEdit.setup_search_keymap(hp)
24✔
1419
    search_prompt.complete = LatexCompletions()
24✔
1420

1421
    shell_prompt_len = length(SHELL_PROMPT)
24✔
1422
    help_prompt_len = length(HELP_PROMPT)
24✔
1423
    jl_prompt_regex = Regex("^In \\[[0-9]+\\]: |^(?:\\(.+\\) )?$JULIA_PROMPT")
24✔
1424
    pkg_prompt_regex = Regex("^(?:\\(.+\\) )?$PKG_PROMPT")
24✔
1425

1426
    # Canonicalize user keymap input
1427
    if isa(extra_repl_keymap, Dict)
24✔
1428
        extra_repl_keymap = AnyDict[extra_repl_keymap]
×
1429
    end
1430

1431
    repl_keymap = AnyDict(
24✔
1432
        ';' => function (s::MIState,o...)
55✔
1433
            if isempty(s) || position(LineEdit.buffer(s)) == 0
103✔
1434
                buf = copy(LineEdit.buffer(s))
7✔
1435
                transition(s, shell_mode) do
7✔
1436
                    LineEdit.state(s, shell_mode).input_buffer = buf
7✔
1437
                end
1438
            else
1439
                edit_insert(s, ';')
48✔
1440
                LineEdit.check_show_hint(s)
48✔
1441
            end
1442
        end,
1443
        '?' => function (s::MIState,o...)
1✔
1444
            if isempty(s) || position(LineEdit.buffer(s)) == 0
1✔
1445
                buf = copy(LineEdit.buffer(s))
1✔
1446
                transition(s, help_mode) do
1✔
1447
                    LineEdit.state(s, help_mode).input_buffer = buf
1✔
1448
                end
1449
            else
1450
                edit_insert(s, '?')
×
1451
                LineEdit.check_show_hint(s)
×
1452
            end
1453
        end,
1454
        ']' => function (s::MIState,o...)
3✔
1455
            if isempty(s) || position(LineEdit.buffer(s)) == 0
6✔
1456
                buf = copy(LineEdit.buffer(s))
×
1457
                transition(s, dummy_pkg_mode) do
×
1458
                    LineEdit.state(s, dummy_pkg_mode).input_buffer = buf
1459
                end
1460
                # load Pkg on another thread if available so that typing in the dummy Pkg prompt
1461
                # isn't blocked, but instruct the main REPL task to do the transition via s.async_channel
1462
                t_replswitch = Threads.@spawn begin
×
1463
                    REPLExt = load_pkg()
1464
                    if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1465
                        put!(s.async_channel,
1466
                            function (s::MIState)
1467
                                LineEdit.mode(s) === dummy_pkg_mode || return :ok
1468
                                for mode in repl.interface.modes
1469
                                    if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1470
                                        buf = copy(LineEdit.buffer(s))
1471
                                        transition(s, mode) do
1472
                                            LineEdit.state(s, mode).input_buffer = buf
1473
                                        end
1474
                                        if !isempty(s)
1475
                                            @invokelatest(LineEdit.check_show_hint(s))
1476
                                        end
1477
                                        break
1478
                                    end
1479
                                end
1480
                                return :ok
1481
                            end
1482
                        )
1483
                    end
1484
                end
1485
                Base.errormonitor(t_replswitch)
×
1486
            else
1487
                edit_insert(s, ']')
3✔
1488
                LineEdit.check_show_hint(s)
3✔
1489
            end
1490
        end,
1491

1492
        # Bracketed Paste Mode
1493
        "\e[200~" => (s::MIState,o...)->begin
8✔
1494
            input = LineEdit.bracketed_paste(s) # read directly from s until reaching the end-bracketed-paste marker
8✔
1495
            sbuffer = LineEdit.buffer(s)
8✔
1496
            curspos = position(sbuffer)
8✔
1497
            seek(sbuffer, 0)
16✔
1498
            shouldeval = (bytesavailable(sbuffer) == curspos && !occursin(UInt8('\n'), sbuffer))
8✔
1499
            seek(sbuffer, curspos)
16✔
1500
            if curspos == 0
8✔
1501
                # if pasting at the beginning, strip leading whitespace
1502
                input = lstrip(input)
7✔
1503
            end
1504
            if !shouldeval
8✔
1505
                # when pasting in the middle of input, just paste in place
1506
                # don't try to execute all the WIP, since that's rather confusing
1507
                # and is often ill-defined how it should behave
1508
                edit_insert(s, input)
×
1509
                return
×
1510
            end
1511
            LineEdit.push_undo(s)
8✔
1512
            edit_insert(sbuffer, input)
8✔
1513
            input = String(take!(sbuffer))
16✔
1514
            oldpos = firstindex(input)
8✔
1515
            firstline = true
8✔
1516
            isprompt_paste = false
8✔
1517
            curr_prompt_len = 0
8✔
1518
            pasting_help = false
8✔
1519

1520
            while oldpos <= lastindex(input) # loop until all lines have been executed
50✔
1521
                if JL_PROMPT_PASTE[]
24✔
1522
                    # Check if the next statement starts with a prompt i.e. "julia> ", in that case
1523
                    # skip it. But first skip whitespace unless pasting in a docstring which may have
1524
                    # indented prompt examples that we don't want to execute
1525
                    while input[oldpos] in (pasting_help ? ('\n') : ('\n', ' ', '\t'))
86✔
1526
                        oldpos = nextind(input, oldpos)
48✔
1527
                        oldpos >= sizeof(input) && return
24✔
1528
                    end
24✔
1529
                    substr = SubString(input, oldpos)
48✔
1530
                    # Check if input line starts with "julia> ", remove it if we are in prompt paste mode
1531
                    if (firstline || isprompt_paste) && startswith(substr, jl_prompt_regex)
24✔
1532
                        detected_jl_prompt = match(jl_prompt_regex, substr).match
11✔
1533
                        isprompt_paste = true
11✔
1534
                        curr_prompt_len = sizeof(detected_jl_prompt)
11✔
1535
                        oldpos += curr_prompt_len
11✔
1536
                        transition(s, julia_prompt)
11✔
1537
                        pasting_help = false
11✔
1538
                    # Check if input line starts with "pkg> " or "(...) pkg> ", remove it if we are in prompt paste mode and switch mode
1539
                    elseif (firstline || isprompt_paste) && startswith(substr, pkg_prompt_regex)
13✔
1540
                        detected_pkg_prompt = match(pkg_prompt_regex, substr).match
×
1541
                        isprompt_paste = true
×
1542
                        curr_prompt_len = sizeof(detected_pkg_prompt)
×
1543
                        oldpos += curr_prompt_len
×
1544
                        Base.active_repl.interface.modes[1].keymap_dict[']'](s, o...)
×
1545
                        pasting_help = false
×
1546
                    # Check if input line starts with "shell> ", remove it if we are in prompt paste mode and switch mode
1547
                    elseif (firstline || isprompt_paste) && startswith(substr, SHELL_PROMPT)
13✔
1548
                        isprompt_paste = true
2✔
1549
                        oldpos += shell_prompt_len
2✔
1550
                        curr_prompt_len = shell_prompt_len
2✔
1551
                        transition(s, shell_mode)
2✔
1552
                        pasting_help = false
2✔
1553
                    # Check if input line starts with "help?> ", remove it if we are in prompt paste mode and switch mode
1554
                    elseif (firstline || isprompt_paste) && startswith(substr, HELP_PROMPT)
11✔
1555
                        isprompt_paste = true
1✔
1556
                        oldpos += help_prompt_len
1✔
1557
                        curr_prompt_len = help_prompt_len
1✔
1558
                        transition(s, help_mode)
1✔
1559
                        pasting_help = true
1✔
1560
                    # If we are prompt pasting and current statement does not begin with a mode prefix, skip to next line
1561
                    elseif isprompt_paste
10✔
1562
                        while input[oldpos] != '\n'
316✔
1563
                            oldpos = nextind(input, oldpos)
302✔
1564
                            oldpos >= sizeof(input) && return
151✔
1565
                        end
149✔
1566
                        continue
7✔
1567
                    end
1568
                end
1569
                dump_tail = false
15✔
1570
                nl_pos = findfirst('\n', input[oldpos:end])
30✔
1571
                if s.current_mode == julia_prompt
15✔
1572
                    ast, pos = Meta.parse(input, oldpos, raise=false, depwarn=false)
12✔
1573
                    if (isa(ast, Expr) && (ast.head === :error || ast.head === :incomplete)) ||
22✔
1574
                            (pos > ncodeunits(input) && !endswith(input, '\n'))
1575
                        # remaining text is incomplete (an error, or parser ran to the end but didn't stop with a newline):
1576
                        # Insert all the remaining text as one line (might be empty)
1577
                        dump_tail = true
5✔
1578
                    end
1579
                elseif isnothing(nl_pos) # no newline at end, so just dump the tail into the prompt and don't execute
6✔
1580
                    dump_tail = true
×
1581
                elseif s.current_mode == shell_mode # handle multiline shell commands
3✔
1582
                    lines = split(input[oldpos:end], '\n')
4✔
1583
                    pos = oldpos + sizeof(lines[1]) + 1
2✔
1584
                    if length(lines) > 1
2✔
1585
                        for line in lines[2:end]
2✔
1586
                            # to be recognized as a multiline shell command, the lines must be indented to the
1587
                            # same prompt position
1588
                            if !startswith(line, ' '^curr_prompt_len)
3✔
1589
                                break
2✔
1590
                            end
1591
                            pos += sizeof(line) + 1
1✔
1592
                        end
1✔
1593
                    end
1594
                else
1595
                    pos = oldpos + nl_pos
1✔
1596
                end
1597
                if dump_tail
15✔
1598
                    tail = input[oldpos:end]
10✔
1599
                    if !firstline
5✔
1600
                        # strip leading whitespace, but only if it was the result of executing something
1601
                        # (avoids modifying the user's current leading wip line)
1602
                        tail = lstrip(tail)
1✔
1603
                    end
1604
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
5✔
1605
                        tail = replace(tail, r"^"m * ' '^curr_prompt_len => "")
7✔
1606
                    end
1607
                    LineEdit.replace_line(s, tail, true)
5✔
1608
                    LineEdit.refresh_line(s)
5✔
1609
                    break
5✔
1610
                end
1611
                # get the line and strip leading and trailing whitespace
1612
                line = strip(input[oldpos:prevind(input, pos)])
20✔
1613
                if !isempty(line)
10✔
1614
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
10✔
1615
                        line = replace(line, r"^"m * ' '^curr_prompt_len => "")
10✔
1616
                    end
1617
                    # put the line on the screen and history
1618
                    LineEdit.replace_line(s, line)
20✔
1619
                    LineEdit.commit_line(s)
10✔
1620
                    # execute the statement
1621
                    terminal = LineEdit.terminal(s) # This is slightly ugly but ok for now
10✔
1622
                    raw!(terminal, false) && disable_bracketed_paste(terminal)
10✔
1623
                    @invokelatest LineEdit.mode(s).on_done(s, LineEdit.buffer(s), true)
10✔
1624
                    raw!(terminal, true) && enable_bracketed_paste(terminal)
10✔
1625
                    LineEdit.push_undo(s) # when the last line is incomplete
10✔
1626
                end
1627
                oldpos = pos
10✔
1628
                firstline = false
10✔
1629
            end
17✔
1630
        end,
1631

1632
        # Open the editor at the location of a stackframe or method
1633
        # This is accessing a contextual variable that gets set in
1634
        # the show_backtrace and show_method_table functions.
1635
        "^Q" => (s::MIState, o...) -> begin
1636
            linfos = repl.last_shown_line_infos
1637
            str = String(take!(LineEdit.buffer(s)))
1638
            n = tryparse(Int, str)
1639
            n === nothing && @goto writeback
1640
            if n <= 0 || n > length(linfos) || startswith(linfos[n][1], "REPL[")
1641
                @goto writeback
1642
            end
1643
            try
1644
                InteractiveUtils.edit(Base.fixup_stdlib_path(linfos[n][1]), linfos[n][2])
1645
            catch ex
1646
                ex isa ProcessFailedException || ex isa Base.IOError || ex isa SystemError || rethrow()
1647
                @info "edit failed" _exception=ex
1648
            end
1649
            LineEdit.refresh_line(s)
1650
            return
1651
            @label writeback
1652
            write(LineEdit.buffer(s), str)
1653
            return
1654
        end,
1655
    )
1656

1657
    prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt)
24✔
1658

1659
    a = Dict{Any,Any}[skeymap, repl_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
144✔
1660
    prepend!(a, extra_repl_keymap)
24✔
1661

1662
    julia_prompt.keymap_dict = LineEdit.keymap(a)
24✔
1663

1664
    mk = mode_keymap(julia_prompt)
24✔
1665

1666
    b = Dict{Any,Any}[skeymap, mk, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
144✔
1667
    prepend!(b, extra_repl_keymap)
24✔
1668

1669
    shell_mode.keymap_dict = help_mode.keymap_dict = dummy_pkg_mode.keymap_dict = LineEdit.keymap(b)
24✔
1670

1671
    allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, dummy_pkg_mode, search_prompt, prefix_prompt]
24✔
1672
    return ModalInterface(allprompts)
24✔
1673
end
1674

1675
function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
20✔
1676
    repl.frontend_task = current_task()
20✔
1677
    d = REPLDisplay(repl)
20✔
1678
    dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays)
34✔
1679
    dopushdisplay && pushdisplay(d)
20✔
1680
    if !isdefined(repl,:interface)
20✔
1681
        interface = repl.interface = setup_interface(repl)
24✔
1682
    else
1683
        interface = repl.interface
8✔
1684
    end
1685
    repl.backendref = backend
20✔
1686
    repl.mistate = LineEdit.init_state(terminal(repl), interface)
20✔
1687
    run_interface(terminal(repl), interface, repl.mistate)
20✔
1688
    # Terminate Backend
1689
    put!(backend.repl_channel, (nothing, -1))
20✔
1690
    dopushdisplay && popdisplay(d)
20✔
1691
    nothing
20✔
1692
end
1693

1694
## StreamREPL ##
1695

1696
mutable struct StreamREPL <: AbstractREPL
1697
    stream::IO
1698
    prompt_color::String
1699
    input_color::String
1700
    answer_color::String
1701
    waserror::Bool
1702
    frontend_task::Task
1703
    StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false)
×
1704
end
1705
StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.input_color(), Base.answer_color())
×
1706
run_repl(stream::IO) = run_repl(StreamREPL(stream))
×
1707

1708
outstream(s::StreamREPL) = s.stream
×
1709
hascolor(s::StreamREPL) = get(s.stream, :color, false)::Bool
×
1710

1711
answer_color(r::LineEditREPL) = r.envcolors ? Base.answer_color() : r.answer_color
×
1712
answer_color(r::StreamREPL) = r.answer_color
×
1713
input_color(r::LineEditREPL) = r.envcolors ? Base.input_color() : r.input_color
×
1714
input_color(r::StreamREPL) = r.input_color
×
1715

1716
# heuristic function to decide if the presence of a semicolon
1717
# at the end of the expression was intended for suppressing output
1718
function ends_with_semicolon(code)
129✔
1719
    semi = false
129✔
1720
    for tok in tokenize(code)
129✔
1721
        kind(tok) in KSet"Whitespace NewlineWs Comment EndMarker" && continue
2,590✔
1722
        semi = kind(tok) == K";"
1,019✔
1723
    end
1,392✔
1724
    return semi
129✔
1725
end
1726

1727
function banner(io::IO = stdout; short = false)
4✔
1728
    if Base.GIT_VERSION_INFO.tagged_commit
2✔
1729
        commit_string = Base.TAGGED_RELEASE_BANNER
×
1730
    elseif isempty(Base.GIT_VERSION_INFO.commit)
2✔
1731
        commit_string = ""
×
1732
    else
1733
        days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24)))
2✔
1734
        days = max(0, days)
2✔
1735
        unit = days == 1 ? "day" : "days"
2✔
1736
        distance = Base.GIT_VERSION_INFO.fork_master_distance
2✔
1737
        commit = Base.GIT_VERSION_INFO.commit_short
2✔
1738

1739
        if distance == 0
2✔
1740
            commit_string = "Commit $(commit) ($(days) $(unit) old master)"
2✔
1741
        else
1742
            branch = Base.GIT_VERSION_INFO.branch
×
1743
            commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))"
×
1744
        end
1745
    end
1746

1747
    commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))"
2✔
1748

1749
    if get(io, :color, false)::Bool
2✔
1750
        c = Base.text_colors
×
1751
        tx = c[:normal] # text
×
1752
        jl = c[:normal] # julia
×
1753
        d1 = c[:bold] * c[:blue]    # first dot
×
1754
        d2 = c[:bold] * c[:red]     # second dot
×
1755
        d3 = c[:bold] * c[:green]   # third dot
×
1756
        d4 = c[:bold] * c[:magenta] # fourth dot
×
1757

1758
        if short
×
1759
            print(io,"""
×
1760
              $(d3)o$(tx)  | Version $(VERSION)$(commit_date)
1761
             $(d2)o$(tx) $(d4)o$(tx) | $(commit_string)
1762
            """)
1763
        else
1764
            print(io,"""               $(d3)_$(tx)
×
1765
               $(d1)_$(tx)       $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx)     |  Documentation: https://docs.julialang.org
1766
              $(d1)(_)$(jl)     | $(d2)(_)$(tx) $(d4)(_)$(tx)    |
1767
               $(jl)_ _   _| |_  __ _$(tx)   |  Type \"?\" for help, \"]?\" for Pkg help.
1768
              $(jl)| | | | | | |/ _` |$(tx)  |
1769
              $(jl)| | |_| | | | (_| |$(tx)  |  Version $(VERSION)$(commit_date)
1770
             $(jl)_/ |\\__'_|_|_|\\__'_|$(tx)  |  $(commit_string)
1771
            $(jl)|__/$(tx)                   |
1772

1773
            """)
1774
        end
1775
    else
1776
        if short
2✔
1777
            print(io,"""
1✔
1778
              o  |  Version $(VERSION)$(commit_date)
1779
             o o |  $(commit_string)
1780
            """)
1781
        else
1782
            print(io,"""
1✔
1783
                           _
1784
               _       _ _(_)_     |  Documentation: https://docs.julialang.org
1785
              (_)     | (_) (_)    |
1786
               _ _   _| |_  __ _   |  Type \"?\" for help, \"]?\" for Pkg help.
1787
              | | | | | | |/ _` |  |
1788
              | | |_| | | | (_| |  |  Version $(VERSION)$(commit_date)
1789
             _/ |\\__'_|_|_|\\__'_|  |  $(commit_string)
1790
            |__/                   |
1791

1792
            """)
1793
        end
1794
    end
1795
end
1796

1797
function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
×
1798
    repl.frontend_task = current_task()
×
1799
    have_color = hascolor(repl)
×
1800
    banner(repl.stream)
×
1801
    d = REPLDisplay(repl)
×
1802
    dopushdisplay = !in(d,Base.Multimedia.displays)
×
1803
    dopushdisplay && pushdisplay(d)
×
1804
    while !eof(repl.stream)::Bool
×
1805
        if have_color
×
1806
            print(repl.stream,repl.prompt_color)
×
1807
        end
1808
        print(repl.stream, JULIA_PROMPT)
×
1809
        if have_color
×
1810
            print(repl.stream, input_color(repl))
×
1811
        end
1812
        line = readline(repl.stream, keep=true)
×
1813
        if !isempty(line)
×
1814
            ast = Base.parse_input_line(line)
×
1815
            if have_color
×
1816
                print(repl.stream, Base.color_normal)
×
1817
            end
1818
            response = eval_on_backend(ast, backend)
×
1819
            print_response(repl, response, !ends_with_semicolon(line), have_color)
×
1820
        end
1821
    end
×
1822
    # Terminate Backend
1823
    put!(backend.repl_channel, (nothing, -1))
×
1824
    dopushdisplay && popdisplay(d)
×
1825
    nothing
×
1826
end
1827

1828
module Numbered
1829

1830
using ..REPL
1831

1832
__current_ast_transforms() = Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1833

1834
function repl_eval_counter(hp)
567✔
1835
    return length(hp.history) - hp.start_idx
567✔
1836
end
1837

1838
function out_transform(@nospecialize(x), n::Ref{Int})
16✔
1839
    return Expr(:toplevel, get_usings!([], x)..., quote
16✔
1840
        let __temp_val_a72df459 = $x
1841
            $capture_result($n, __temp_val_a72df459)
1842
            __temp_val_a72df459
1843
        end
1844
    end)
1845
end
1846

1847
function get_usings!(usings, ex)
25✔
1848
    ex isa Expr || return usings
25✔
1849
    # get all `using` and `import` statements which are at the top level
1850
    for (i, arg) in enumerate(ex.args)
25✔
1851
        if Base.isexpr(arg, :toplevel)
48✔
1852
            get_usings!(usings, arg)
9✔
1853
        elseif Base.isexpr(arg, [:using, :import])
64✔
1854
            push!(usings, popat!(ex.args, i))
2✔
1855
        end
1856
    end
71✔
1857
    return usings
25✔
1858
end
1859

1860
function create_global_out!(mod)
1✔
1861
    if !isdefinedglobal(mod, :Out)
1✔
1862
        out = Dict{Int, Any}()
1✔
1863
        @eval mod begin
1✔
1864
            const Out = $(out)
1865
            export Out
1866
        end
1867
        return out
1✔
1868
    end
1869
    return getglobal(mod, :Out)
×
1870
end
1871

1872
function capture_result(n::Ref{Int}, @nospecialize(x))
16✔
1873
    n = n[]
16✔
1874
    mod = Base.MainInclude
16✔
1875
    # TODO: This invokelatest is only required due to backdated constants
1876
    # and should be removed after
1877
    out = isdefinedglobal(mod, :Out) ? invokelatest(getglobal, mod, :Out) : invokelatest(create_global_out!, mod)
17✔
1878
    if x !== out && x !== nothing # remove this?
16✔
1879
        out[n] = x
14✔
1880
    end
1881
    nothing
16✔
1882
end
1883

1884
function set_prompt(repl::LineEditREPL, n::Ref{Int})
1✔
1885
    julia_prompt = repl.interface.modes[1]
1✔
1886
    julia_prompt.prompt = REPL.contextual_prompt(repl, function()
568✔
1887
        n[] = repl_eval_counter(julia_prompt.hist)+1
567✔
1888
        string("In [", n[], "]: ")
567✔
1889
    end)
1890
    nothing
1✔
1891
end
1892

1893
function set_output_prefix(repl::LineEditREPL, n::Ref{Int})
1✔
1894
    julia_prompt = repl.interface.modes[1]
1✔
1895
    if REPL.hascolor(repl)
1✔
1896
        julia_prompt.output_prefix_prefix = Base.text_colors[:red]
1✔
1897
    end
1898
    julia_prompt.output_prefix = () -> string("Out[", n[], "]: ")
15✔
1899
    nothing
1✔
1900
end
1901

1902
function __current_ast_transforms(backend)
1903
    if backend === nothing
1✔
1904
        Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1905
    else
1906
        backend.ast_transforms
1✔
1907
    end
1908
end
1909

1910
function numbered_prompt!(repl::LineEditREPL=Base.active_repl::LineEditREPL, backend=nothing)
1911
    n = Ref{Int}(0)
1✔
1912
    set_prompt(repl, n)
1✔
1913
    set_output_prefix(repl, n)
1✔
1914
    push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n))
17✔
1915
    return
1✔
1916
end
1917

1918
"""
1919
    Out[n]
1920

1921
A variable referring to all previously computed values, automatically imported to the interactive prompt.
1922
Only defined and exists while using [Numbered prompt](@ref Numbered-prompt).
1923

1924
See also [`ans`](@ref).
1925
"""
1926
Base.MainInclude.Out
1927

1928
end
1929

1930
import .Numbered.numbered_prompt!
1931

1932
# this assignment won't survive precompilation,
1933
# but will stick if REPL is baked into a sysimg.
1934
# Needs to occur after this module is finished.
1935
Base.REPL_MODULE_REF[] = REPL
1936

1937
if Base.generating_output()
1938
    include("precompile.jl")
1939
end
1940

1941
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