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

JuliaLang / julia / #38162

06 Aug 2025 08:25PM UTC coverage: 25.688% (-43.6%) from 69.336%
#38162

push

local

web-flow
fix runtime cglobal builtin function implementation (#59210)

This had failed to be updated for the LazyLibrary changes to codegen.

12976 of 50513 relevant lines covered (25.69%)

676965.51 hits per line

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

0.39
/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__()
2✔
36
    Base.REPL_MODULE_REF[] = REPL
2✔
37
    Base.Experimental.register_error_hint(UndefVarError_REPL_hint, UndefVarError)
2✔
38
    return nothing
2✔
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

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)) =
×
122
        new(repl_channel, response_channel, in_eval, ast_transforms)
123
end
124
REPLBackend() = REPLBackend(Channel(1), Channel(1), false)
×
125

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

133
function destroy(ref::REPLBackendRef, state::Task)
×
134
    if istaskfailed(state)
×
135
        close(ref.repl_channel, TaskFailedException(state))
×
136
        close(ref.response_channel, TaskFailedException(state))
×
137
    end
138
    close(ref.repl_channel)
×
139
    close(ref.response_channel)
×
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)
×
149
    if ex isa Expr
×
150
        h = ex.head
×
151
        if h === :toplevel
×
152
            ex′ = Expr(h)
×
153
            map!(softscope, resize!(ex′.args, length(ex.args)), ex.args)
×
154
            return ex′
×
155
        elseif h in (:meta, :import, :using, :export, :module, :error, :incomplete, :thunk)
×
156
            return ex
×
157
        elseif h === :global && all(x->isa(x, Symbol), ex.args)
×
158
            return ex
×
159
        else
160
            return Expr(:block, Expr(:softscope, true), ex)
×
161
        end
162
    end
163
    return ex
×
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)
×
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
×
171
end
172

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

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

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

215
function collect_names_to_warn!(warnings, locals, current_module::Module, ast)
×
216
    ast isa Expr || return
×
217

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

221
    if Meta.isexpr(ast, :., 2)
×
222
        mod_name, name_being_accessed = ast.args
×
223
        # retrieve the (possibly-nested) module being named here
224
        mods = retrieve_modules(current_module, mod_name)
×
225
        all(x -> x isa Module, mods) || return
×
226
        outer_mod = first(mods)
×
227
        mod = last(mods)
×
228
        if name_being_accessed isa QuoteNode
×
229
            name_being_accessed = name_being_accessed.value
×
230
        end
231
        name_being_accessed isa Symbol || return
×
232
        owner = try
×
233
            which(mod, name_being_accessed)
×
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
×
240
        # Don't warn if the name is public in the module we are accessing it
241
        Base.ispublic(mod, name_being_accessed) && return
×
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
×
244
        push!(warnings, (; outer_mod, mod, owner, name_being_accessed))
×
245
        # no recursion
246
        return
×
247
    elseif Meta.isexpr(ast, :(=), 2)
×
248
        lhs, rhs = ast.args
×
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)
×
252
        # we'll recurse into the RHS only
253
        return collect_names_to_warn!(warnings, locals, current_module, rhs)
×
254
    elseif Meta.isexpr(ast, :function) && length(ast.args) >= 1
×
255

256
        if Meta.isexpr(ast.args[1], :call, 2)
×
257
            func_name, func_args = ast.args[1].args
×
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)
×
261
        end
262
        # fall through to general recursion
263
    end
264

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

269
    return nothing
×
270
end
271

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

282
function warn_on_non_owning_accesses(current_mod, ast)
×
283
    warnings = collect_qualified_access_warnings(current_mod, ast)
×
284
    for (; outer_mod, mod, owner, name_being_accessed) in warnings
×
285
        print_qualified_access_warning(mod, owner, name_being_accessed)
×
286
    end
×
287
    return ast
×
288
end
289
warn_on_non_owning_accesses(ast) = warn_on_non_owning_accesses(Base.active_module(), ast)
×
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}) =
×
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}) =
×
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))
×
308
    if !isexpr(ast, :toplevel)
×
309
        ast = invokelatest(__repl_entry_lower_with_loc, mod, ast, toplevel_file, toplevel_line)
×
310
        check_for_missing_packages_and_run_hooks(ast)
×
311
        return invokelatest(__repl_entry_eval_expanded_with_loc, mod, ast, toplevel_file, toplevel_line)
×
312
    end
313
    local value=nothing
×
314
    for i = 1:length(ast.args)
×
315
        value = toplevel_eval_with_hooks(mod, ast.args[i], toplevel_file, toplevel_line)
×
316
    end
×
317
    return value
×
318
end
319

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

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

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

401
function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[])
×
402
    _modules_to_be_loaded!(ast, mods)
×
403
    filter!(mod::Symbol -> !in(mod, (:Base, :Main, :Core)), mods) # Exclude special non-package modules
×
404
    return unique(mods)
×
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)
×
433
    backend.backend_task = Base.current_task()
×
434
    consumer(backend)
×
435
    repl_backend_loop(backend, get_module)
×
436
    return backend
×
437
end
438

439
function repl_backend_loop(backend::REPLBackend, get_module::Function)
×
440
    # include looks at this to determine the relative include path
441
    # nothing means cwd
442
    while true
×
443
        tls = task_local_storage()
×
444
        tls[:SOURCE_PATH] = nothing
×
445
        ast_or_func, show_value = take!(backend.repl_channel)
×
446
        if show_value == -1
×
447
            # exit flag
448
            break
×
449
        end
450
        if show_value == 2 # 2 indicates a function to be called
×
451
            f = ast_or_func
×
452
            try
×
453
                ret = f()
×
454
                put!(backend.response_channel, Pair{Any, Bool}(ret, false))
×
455
            catch
456
                put!(backend.response_channel, Pair{Any, Bool}(current_exceptions(), true))
×
457
            end
458
        else
459
            ast = ast_or_func
×
460
            eval_user_input(ast, backend, get_module())
×
461
        end
462
    end
×
463
    return nothing
×
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
471
    maxbytes::Int
472
    n::Int # max bytes to write
473
end
474
LimitIO(io::IO, maxbytes) = LimitIO(io, maxbytes, 0)
×
475

476
struct LimitIOException <: Exception
477
    maxbytes::Int
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)
×
485

486
function Base.write(io::LimitIO, v::UInt8)
×
487
    io.n > io.maxbytes && throw(LimitIOException(io.maxbytes))
×
488
    n_bytes = write(io.io, v)
×
489
    io.n += n_bytes
×
490
    return n_bytes
×
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))
×
498
    remaining = limiter.maxbytes - limiter.n # >= 0
×
499

500
    # Not enough bytes left; we will print up to the limit, then throw
501
    if remaining < nb
×
502
        if remaining > 0
×
503
            Base.unsafe_write(limiter.io, p, remaining)
×
504
        end
505
        throw(LimitIOException(limiter.maxbytes))
×
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}
×
510
    limiter.n += bytes_written
×
511
    return bytes_written
×
512
end
513

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

518
function show_limited(io::IO, mime::MIME, x)
×
519
    try
×
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
×
523
        wrapped_limiter = IOContext(LimitIO(inner, SHOW_MAXIMUM_BYTES), io)
×
524
        # `show_repl` to allow the hook with special syntax highlighting
525
        show_repl(wrapped_limiter, mime, x)
×
526
    catch e
527
        e isa LimitIOException || rethrow()
×
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)
×
529
    end
530
end
531

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

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

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

558
function show_repl(io::IO, mime::MIME"text/plain", c::AbstractChar)
×
559
    show(io, mime, c) # Call the original Base.show
×
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))
×
562
    if !isempty(latex)
×
563
        print(io, ", input as ")
×
564
        printstyled(io, latex, "<tab>"; color=:cyan)
×
565
    end
566
end
567

568
show_repl(io::IO, ::MIME"text/plain", ex::Expr) =
×
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)
×
573
    repl.waserror = response[2]
×
574
    with_repl_linfo(repl) do io
×
575
        io = IOContext(io, :module => Base.active_module(repl)::Module)
×
576
        print_response(io, response, backend(repl), show_value, have_color, specialdisplay(repl))
×
577
    end
578
    return nothing
×
579
end
580

581
function repl_display_error(errio::IO, @nospecialize errval)
×
582
    # this will be set to true if types in the stacktrace are truncated
583
    limitflag = Ref(false)
×
584
    errio = IOContext(errio, :stacktrace_types_limited => limitflag)
×
585
    Base.invokelatest(Base.display_error, errio, errval)
×
586
    if limitflag[]
×
587
        print(errio, "Some type information was truncated. Use `show(err)` to see complete types.")
×
588
        println(errio)
×
589
    end
590
    return nothing
×
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)
×
594
    Base.sigatomic_begin()
×
595
    val, iserr = response
×
596
    while true
×
597
        try
×
598
            Base.sigatomic_end()
×
599
            if iserr
×
600
                val = Base.scrub_repl_backtrace(val)
×
601
                Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val)
×
602
                repl_display_error(errio, val)
×
603
            else
604
                if val !== nothing && show_value
×
605
                    val2, iserr = if specialdisplay === nothing
×
606
                        # display calls may require being run on the main thread
607
                        call_on_backend(backend) do
×
608
                            Base.invokelatest(display, val)
×
609
                        end
610
                    else
611
                        call_on_backend(backend) do
×
612
                            Base.invokelatest(display, specialdisplay, val)
×
613
                        end
614
                    end
615
                    if iserr
×
616
                        println(errio, "Error showing value of type ", typeof(val), ":")
×
617
                        throw(val2)
×
618
                    end
619
                end
620
            end
621
            break
×
622
        catch ex
623
            if iserr
×
624
                println(errio) # an error during printing is likely to leave us mid-line
×
625
                println(errio, "SYSTEM (REPL): showing an error caused an error")
×
626
                try
×
627
                    excs = Base.scrub_repl_backtrace(current_exceptions())
×
628
                    setglobal!(Base.MainInclude, :err, excs)
×
629
                    repl_display_error(errio, excs)
×
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)
×
634
                    println(errio, "SYSTEM (REPL): caught exception of type ", typeof(e).name.name,
×
635
                            " while trying to handle a nested exception; giving up")
636
                end
637
                break
×
638
            end
639
            val = current_exceptions()
×
640
            iserr = true
×
641
        end
642
    end
×
643
    Base.sigatomic_end()
×
644
    nothing
×
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())
×
658
    backend_ref = REPLBackendRef(backend)
×
659
    cleanup = @task try
×
660
            destroy(backend_ref, t)
×
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)
×
667
    if backend_on_current_task
×
668
        t = @async run_frontend(repl, backend_ref)
×
669
        errormonitor(t)
×
670
        Base._wait2(t, cleanup)
×
671
        start_repl_backend(backend, consumer; get_module)
×
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
×
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)
×
688
end
689

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

693
function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
×
694
    repl.frontend_task = current_task()
×
695
    d = REPLDisplay(repl)
×
696
    dopushdisplay = !in(d,Base.Multimedia.displays)
×
697
    dopushdisplay && pushdisplay(d)
×
698
    hit_eof = false
×
699
    while true
×
700
        Base.reseteof(repl.terminal)
×
701
        write(repl.terminal, JULIA_PROMPT)
×
702
        line = ""
×
703
        ast = nothing
×
704
        interrupted = false
×
705
        while true
×
706
            try
×
707
                line *= readline(repl.terminal, keep=true)
×
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)
×
725
            (isa(ast,Expr) && ast.head === :incomplete) || break
×
726
        end
×
727
        if !isempty(line)
×
728
            response = eval_on_backend(ast, backend)
×
729
            print_response(repl, response, !ends_with_semicolon(line), false)
×
730
        end
731
        write(repl.terminal, '\n')
×
732
        ((!interrupted && isempty(line)) || hit_eof) && break
×
733
    end
×
734
    # terminate backend
735
    put!(backend.repl_channel, (nothing, -1))
×
736
    dopushdisplay && popdisplay(d)
×
737
    nothing
×
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)
×
764
        opts = Options()
×
765
        opts.hascolor = hascolor
×
766
        if !hascolor
×
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,
×
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)
×
774
specialdisplay(r::LineEditREPL) = r.specialdisplay
×
775
specialdisplay(r::AbstractREPL) = nothing
×
776
terminal(r::LineEditREPL) = r.t
×
777
hascolor(r::LineEditREPL) = r.hascolor
×
778

779
LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
×
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
792
end
793
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
×
794

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

798
Base.active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : mistate.active_module
×
799
Base.active_module(::AbstractREPL) = Main
×
800
Base.active_module(d::REPLDisplay) = Base.active_module(d.repl)
×
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])
×
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)
×
824

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

833
function complete_line(c::ShellCompletionProvider, s::PromptState; hint::Bool=false)
×
834
    full = LineEdit.input_string(s)
×
835
    ret, range, should_complete = shell_completions(full, thisind(full, position(s)), hint)
×
836
    range = to_region(full, range)
×
837
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
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))
×
848
function with_repl_linfo(f, repl::LineEditREPL)
×
849
    linfos = Tuple{String,Int}[]
×
850
    io = IOContext(outstream(repl), :last_shown_line_infos => linfos)
×
851
    f(io)
×
852
    if !isempty(linfos)
×
853
        repl.last_shown_line_infos = linfos
×
854
    end
855
    nothing
×
856
end
857

858
mutable struct REPLHistoryProvider <: HistoryProvider
859
    history::Vector{String}
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}) =
×
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)
×
886
    hp.history_file = f
×
887
    seekend(f)
×
888
end
889

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

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

935
function add_history(hist::REPLHistoryProvider, s::PromptState)
×
936
    str = rstrip(takestring!(copy(s.input_buffer)))
×
937
    isempty(strip(str)) && return
×
938
    mode = mode_idx(hist, LineEdit.mode(s))
×
939
    !isempty(hist.history) &&
×
940
        isequal(mode, hist.modes[end]) && str == hist.history[end] && return
941
    push!(hist.modes, mode)
×
942
    push!(hist.history, str)
×
943
    hist.history_file === nothing && return
×
944
    entry = """
×
945
    # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time()))
946
    # mode: $mode
947
    $(replace(str, r"^"ms => "\t"))
×
948
    """
949
    try
×
950
        seekend(hist.history_file)
×
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)
×
958
        FileWatching.mkpidlock(hist.file_path  * ".pid", stale_age=3) do
×
959
            print(hist.history_file, entry)
×
960
            flush(hist.history_file)
×
961
        end
962
    else # handle eg devnull
963
        print(hist.history_file, entry)
×
964
        flush(hist.history_file)
×
965
    end
966
    nothing
×
967
end
968

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

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

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

1003
    return :ok
×
1004
end
1005

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

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

1032
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
×
1033
                      num::Int=1, save_idx::Int = hist.cur_idx)
1034
    if num == 0
×
1035
        Terminals.beep(s)
×
1036
        return
×
1037
    end
1038
    num < 0 && return history_prev(s, hist, -num, save_idx)
×
1039
    cur_idx = hist.cur_idx
×
1040
    max_idx = length(hist.history) + 1
×
1041
    if cur_idx == max_idx && 0 < hist.last_idx
×
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)
×
1047
    if m === :ok
×
1048
        LineEdit.move_input_end(s)
×
1049
        return LineEdit.refresh_line(s)
×
1050
    elseif m === :skip
×
1051
        return history_next(s, hist, num+1, save_idx)
×
1052
    else
1053
        return Terminals.beep(s)
×
1054
    end
1055
end
1056

1057
history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) =
×
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) =
×
1062
    history_next(s, hist, length(hist.history) - hist.cur_idx + 1)
1063

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

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

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

1113
    # Alright, first try to see if the current match still works
1114
    a = position(response_buffer) + 1 # position is zero-indexed
×
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)
×
1118
    b = b ≤ ncodeunits(response_str) ? prevind(response_str, b) : b-1
×
1119
    b = min(lastindex(response_str), b) # ensure that b is valid
×
1120

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

1130
    # Start searching
1131
    # First the current response buffer
1132
    if 1 <= searchstart <= lastindex(response_str)
×
1133
        match = backwards ? findprev(searchdata, response_str, searchstart) :
×
1134
                            findnext(searchdata, response_str, searchstart)
1135
        if match !== nothing
×
1136
            seek(response_buffer, first(match) - 1)
×
1137
            return true
×
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))
×
1143
    for idx in idxs
×
1144
        h = hist.history[idx]
×
1145
        match = backwards ? findlast(searchdata, h) : findfirst(searchdata, h)
×
1146
        if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.modes[idx])
×
1147
            truncate(response_buffer, 0)
×
1148
            write(response_buffer, h)
×
1149
            seek(response_buffer, first(match) - 1)
×
1150
            hist.cur_idx = idx
×
1151
            return true
×
1152
        end
1153
    end
×
1154

1155
    return false
×
1156
end
1157

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

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

1172
find_hist_file() = get(ENV, "JULIA_HISTORY",
×
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
×
1177

1178

1179
function eval_on_backend(ast, backend::REPLBackendRef)
×
1180
    put!(backend.repl_channel, (ast, 1)) # (f, show_value)
×
1181
    return take!(backend.response_channel) # (val, iserr)
×
1182
end
1183
function call_on_backend(f, backend::REPLBackendRef)
×
1184
    applicable(f) || error("internal error: f is not callable")
×
1185
    put!(backend.repl_channel, (f, 2)) # (f, show_value) 2 indicates function (rather than ast)
×
1186
    return take!(backend.response_channel) # (val, iserr)
×
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)
×
1191
    try
×
1192
        ret = f()
×
1193
        return (ret, false) # (val, iserr)
×
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)
×
1201
    return function do_respond(s::MIState, buf, ok::Bool)
×
1202
        if !ok
×
1203
            return transition(s, :abort)
×
1204
        end
1205
        line = String(take!(buf)::Vector{UInt8})
×
1206
        if !isempty(line) || pass_empty
×
1207
            reset(repl)
×
1208
            local response
×
1209
            try
×
1210
                ast = Base.invokelatest(f, line)
×
1211
                response = eval_on_backend(ast, backend(repl))
×
1212
            catch
1213
                response = Pair{Any, Bool}(current_exceptions(), true)
×
1214
            end
1215
            hide_output = suppress_on_semicolon && ends_with_semicolon(line)
×
1216
            print_response(repl, response, !hide_output, hascolor(repl))
×
1217
        end
1218
        prepare_next(repl)
×
1219
        reset_state(s)
×
1220
        return s.current_mode.sticky ? true : transition(s, main)
×
1221
    end
1222
end
1223

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

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

1234
function mode_keymap(julia_prompt::Prompt)
×
1235
    AnyDict(
×
1236
    '\b' => function (s::MIState,o...)
×
1237
        if isempty(s) || position(LineEdit.buffer(s)) == 0
×
1238
            buf = copy(LineEdit.buffer(s))
×
1239
            transition(s, julia_prompt) do
×
1240
                LineEdit.state(s, julia_prompt).input_buffer = buf
×
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))]"
×
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 ()
×
1264
        mod = Base.active_module(repl)
×
1265
        prefix = mod == Main ? "" : string('(', mod, ") ")
×
1266
        pr = prompt isa String ? prompt : prompt()
×
1267
        prefix * pr
×
1268
    end
1269
end
1270

1271
setup_interface(
×
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(
×
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}}
×
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()
×
1314

1315
    # Set up the main Julia prompt
1316
    julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT);
×
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),
×
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),
×
1334
                          repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false))
1335

1336

1337
    # Set up shell mode
1338
    shell_mode = Prompt(SHELL_PROMPT;
×
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),
×
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,
×
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,
×
1388
                                                 :shell => shell_mode,
1389
                                                 :help  => help_mode,
1390
                                                 :pkg  => dummy_pkg_mode))
1391
    if repl.history_file
×
1392
        try
×
1393
            hist_path = find_hist_file()
×
1394
            mkpath(dirname(hist_path))
×
1395
            hp.file_path = hist_path
×
1396
            hist_open_file(hp)
×
1397
            finalizer(replc) do replc
×
1398
                close(hp.history_file)
×
1399
            end
1400
            hist_from_file(hp, hist_path)
×
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)
×
1410
    julia_prompt.hist = hp
×
1411
    shell_mode.hist = hp
×
1412
    help_mode.hist = hp
×
1413
    dummy_pkg_mode.hist = hp
×
1414

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

1417

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

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

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

1431
    repl_keymap = AnyDict(
×
1432
        ';' => function (s::MIState,o...)
×
1433
            if isempty(s) || position(LineEdit.buffer(s)) == 0
×
1434
                buf = copy(LineEdit.buffer(s))
×
1435
                transition(s, shell_mode) do
×
1436
                    LineEdit.state(s, shell_mode).input_buffer = buf
×
1437
                end
1438
            else
1439
                edit_insert(s, ';')
×
1440
                LineEdit.check_show_hint(s)
×
1441
            end
1442
        end,
1443
        '?' => function (s::MIState,o...)
×
1444
            if isempty(s) || position(LineEdit.buffer(s)) == 0
×
1445
                buf = copy(LineEdit.buffer(s))
×
1446
                transition(s, help_mode) do
×
1447
                    LineEdit.state(s, help_mode).input_buffer = buf
×
1448
                end
1449
            else
1450
                edit_insert(s, '?')
×
1451
                LineEdit.check_show_hint(s)
×
1452
            end
1453
        end,
1454
        ']' => function (s::MIState,o...)
×
1455
            if isempty(s) || position(LineEdit.buffer(s)) == 0
×
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, ']')
×
1488
                LineEdit.check_show_hint(s)
×
1489
            end
1490
        end,
1491

1492
        # Bracketed Paste Mode
1493
        "\e[200~" => (s::MIState,o...)->begin
×
1494
            input = LineEdit.bracketed_paste(s) # read directly from s until reaching the end-bracketed-paste marker
×
1495
            sbuffer = LineEdit.buffer(s)
×
1496
            curspos = position(sbuffer)
×
1497
            seek(sbuffer, 0)
×
1498
            shouldeval = (bytesavailable(sbuffer) == curspos && !occursin(UInt8('\n'), sbuffer))
×
1499
            seek(sbuffer, curspos)
×
1500
            if curspos == 0
×
1501
                # if pasting at the beginning, strip leading whitespace
1502
                input = lstrip(input)
×
1503
            end
1504
            if !shouldeval
×
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)
×
1512
            edit_insert(sbuffer, input)
×
1513
            input = String(take!(sbuffer))
×
1514
            oldpos = firstindex(input)
×
1515
            firstline = true
×
1516
            isprompt_paste = false
×
1517
            curr_prompt_len = 0
×
1518
            pasting_help = false
×
1519

1520
            while oldpos <= lastindex(input) # loop until all lines have been executed
×
1521
                if JL_PROMPT_PASTE[]
×
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'))
×
1526
                        oldpos = nextind(input, oldpos)
×
1527
                        oldpos >= sizeof(input) && return
×
1528
                    end
×
1529
                    substr = SubString(input, oldpos)
×
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)
×
1532
                        detected_jl_prompt = match(jl_prompt_regex, substr).match
×
1533
                        isprompt_paste = true
×
1534
                        curr_prompt_len = sizeof(detected_jl_prompt)
×
1535
                        oldpos += curr_prompt_len
×
1536
                        transition(s, julia_prompt)
×
1537
                        pasting_help = false
×
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)
×
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)
×
1548
                        isprompt_paste = true
×
1549
                        oldpos += shell_prompt_len
×
1550
                        curr_prompt_len = shell_prompt_len
×
1551
                        transition(s, shell_mode)
×
1552
                        pasting_help = false
×
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)
×
1555
                        isprompt_paste = true
×
1556
                        oldpos += help_prompt_len
×
1557
                        curr_prompt_len = help_prompt_len
×
1558
                        transition(s, help_mode)
×
1559
                        pasting_help = true
×
1560
                    # If we are prompt pasting and current statement does not begin with a mode prefix, skip to next line
1561
                    elseif isprompt_paste
×
1562
                        while input[oldpos] != '\n'
×
1563
                            oldpos = nextind(input, oldpos)
×
1564
                            oldpos >= sizeof(input) && return
×
1565
                        end
×
1566
                        continue
×
1567
                    end
1568
                end
1569
                dump_tail = false
×
1570
                nl_pos = findfirst('\n', input[oldpos:end])
×
1571
                if s.current_mode == julia_prompt
×
1572
                    ast, pos = Meta.parse(input, oldpos, raise=false, depwarn=false)
×
1573
                    if (isa(ast, Expr) && (ast.head === :error || ast.head === :incomplete)) ||
×
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
×
1578
                    end
1579
                elseif isnothing(nl_pos) # no newline at end, so just dump the tail into the prompt and don't execute
×
1580
                    dump_tail = true
×
1581
                elseif s.current_mode == shell_mode # handle multiline shell commands
×
1582
                    lines = split(input[oldpos:end], '\n')
×
1583
                    pos = oldpos + sizeof(lines[1]) + 1
×
1584
                    if length(lines) > 1
×
1585
                        for line in lines[2:end]
×
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)
×
1589
                                break
×
1590
                            end
1591
                            pos += sizeof(line) + 1
×
1592
                        end
×
1593
                    end
1594
                else
1595
                    pos = oldpos + nl_pos
×
1596
                end
1597
                if dump_tail
×
1598
                    tail = input[oldpos:end]
×
1599
                    if !firstline
×
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)
×
1603
                    end
1604
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
×
1605
                        tail = replace(tail, r"^"m * ' '^curr_prompt_len => "")
×
1606
                    end
1607
                    LineEdit.replace_line(s, tail, true)
×
1608
                    LineEdit.refresh_line(s)
×
1609
                    break
×
1610
                end
1611
                # get the line and strip leading and trailing whitespace
1612
                line = strip(input[oldpos:prevind(input, pos)])
×
1613
                if !isempty(line)
×
1614
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
×
1615
                        line = replace(line, r"^"m * ' '^curr_prompt_len => "")
×
1616
                    end
1617
                    # put the line on the screen and history
1618
                    LineEdit.replace_line(s, line)
×
1619
                    LineEdit.commit_line(s)
×
1620
                    # execute the statement
1621
                    terminal = LineEdit.terminal(s) # This is slightly ugly but ok for now
×
1622
                    raw!(terminal, false) && disable_bracketed_paste(terminal)
×
1623
                    @invokelatest LineEdit.mode(s).on_done(s, LineEdit.buffer(s), true)
×
1624
                    raw!(terminal, true) && enable_bracketed_paste(terminal)
×
1625
                    LineEdit.push_undo(s) # when the last line is incomplete
×
1626
                end
1627
                oldpos = pos
×
1628
                firstline = false
×
1629
            end
×
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)
×
1658

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

1662
    julia_prompt.keymap_dict = LineEdit.keymap(a)
×
1663

1664
    mk = mode_keymap(julia_prompt)
×
1665

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

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

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

1675
function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
×
1676
    repl.frontend_task = current_task()
×
1677
    d = REPLDisplay(repl)
×
1678
    dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays)
×
1679
    dopushdisplay && pushdisplay(d)
×
1680
    if !isdefined(repl,:interface)
×
1681
        interface = repl.interface = setup_interface(repl)
×
1682
    else
1683
        interface = repl.interface
×
1684
    end
1685
    repl.backendref = backend
×
1686
    repl.mistate = LineEdit.init_state(terminal(repl), interface)
×
1687
    run_interface(terminal(repl), interface, repl.mistate)
×
1688
    # Terminate Backend
1689
    put!(backend.repl_channel, (nothing, -1))
×
1690
    dopushdisplay && popdisplay(d)
×
1691
    nothing
×
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)
×
1719
    semi = false
×
1720
    for tok in tokenize(code)
×
1721
        kind(tok) in KSet"Whitespace NewlineWs Comment EndMarker" && continue
×
1722
        semi = kind(tok) == K";"
×
1723
    end
×
1724
    return semi
×
1725
end
1726

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

1739
        if distance == 0
×
1740
            commit_string = "Commit $(commit) ($(days) $(unit) old master)"
×
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]))"
×
1748

1749
    if get(io, :color, false)::Bool
×
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
×
1777
            print(io,"""
×
1778
              o  |  Version $(VERSION)$(commit_date)
1779
             o o |  $(commit_string)
1780
            """)
1781
        else
1782
            print(io,"""
×
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)
×
1835
    return length(hp.history) - hp.start_idx
×
1836
end
1837

1838
function out_transform(@nospecialize(x), n::Ref{Int})
×
1839
    return Expr(:toplevel, get_usings!([], x)..., quote
×
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)
×
1848
    ex isa Expr || return usings
×
1849
    # get all `using` and `import` statements which are at the top level
1850
    for (i, arg) in enumerate(ex.args)
×
1851
        if Base.isexpr(arg, :toplevel)
×
1852
            get_usings!(usings, arg)
×
1853
        elseif Base.isexpr(arg, [:using, :import])
×
1854
            push!(usings, popat!(ex.args, i))
×
1855
        end
1856
    end
×
1857
    return usings
×
1858
end
1859

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

1872
function capture_result(n::Ref{Int}, @nospecialize(x))
×
1873
    n = n[]
×
1874
    mod = Base.MainInclude
×
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)
×
1878
    if x !== out && x !== nothing # remove this?
×
1879
        out[n] = x
×
1880
    end
1881
    nothing
×
1882
end
1883

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

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

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

1910
function numbered_prompt!(repl::LineEditREPL=Base.active_repl::LineEditREPL, backend=nothing)
×
1911
    n = Ref{Int}(0)
×
1912
    set_prompt(repl, n)
×
1913
    set_output_prefix(repl, n)
×
1914
    push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n))
×
1915
    return
×
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