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

JuliaLang / julia / 1392

31 Dec 2025 01:18AM UTC coverage: 76.653% (+0.02%) from 76.638%
1392

push

buildkite

web-flow
update utf8proc to 2.11.3 (#60207)

62458 of 81482 relevant lines covered (76.65%)

23210278.09 hits per line

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

81.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)
12✔
21
    var = ex.var
12✔
22
    if var === :or
12✔
23
        print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.")
×
24
    elseif var === :and
12✔
25
        print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.")
×
26
    elseif var === :help
12✔
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
12✔
31
        print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
32
    end
33
end
34

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

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

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

54
public TerminalMenus
55

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

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

65
using Base.Terminals
66

67
abstract type AbstractREPL end
68

69
include("options.jl")
70
include("StylingPasses.jl")
71
using .StylingPasses
72

73
function histsearch end # To work around circular dependency
74

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

95
include("SyntaxUtil.jl")
96
include("REPLCompletions.jl")
97
using .REPLCompletions
98

99
include("TerminalMenus/TerminalMenus.jl")
100
include("docview.jl")
101

102
include("History/History.jl")
103
using .History
104

105
histsearch(args...) = runsearch(args...)
×
106

107
include("Pkg_beforeload.jl")
108

109
@nospecialize # use only declared type signatures
110

111
answer_color(::AbstractREPL) = ""
×
112

113
const JULIA_PROMPT = "julia> "
114
const PKG_PROMPT = "pkg> "
115
const SHELL_PROMPT = "shell> "
116
const HELP_PROMPT = "help?> "
117

118
mutable struct REPLBackend
119
    "channel for AST"
120
    repl_channel::Channel{Any}
121
    "channel for results: (value, iserror)"
122
    response_channel::Channel{Any}
123
    "flag indicating the state of this backend"
124
    in_eval::Bool
125
    "transformation functions to apply before evaluating expressions"
126
    ast_transforms::Vector{Any}
127
    "current backend task"
128
    backend_task::Task
129

130
    REPLBackend(repl_channel, response_channel, in_eval, ast_transforms=copy(repl_ast_transforms)) =
167✔
131
        new(repl_channel, response_channel, in_eval, ast_transforms)
132
end
133
REPLBackend() = REPLBackend(Channel(1), Channel(1), false)
110✔
134

135
# A reference to a backend that is not mutable
136
struct REPLBackendRef
137
    repl_channel::Channel{Any}
53✔
138
    response_channel::Channel{Any}
139
end
140
REPLBackendRef(backend::REPLBackend) = REPLBackendRef(backend.repl_channel, backend.response_channel)
53✔
141

142
function destroy(ref::REPLBackendRef, state::Task)
143
    if istaskfailed(state)
51✔
144
        close(ref.repl_channel, TaskFailedException(state))
1✔
145
        close(ref.response_channel, TaskFailedException(state))
1✔
146
    end
147
    close(ref.repl_channel)
51✔
148
    close(ref.response_channel)
51✔
149
end
150

151
"""
152
    softscope(ex)
153

154
Return a modified version of the parsed expression `ex` that uses
155
the REPL's "soft" scoping rules for global syntax blocks.
156
"""
157
function softscope(@nospecialize ex)
756✔
158
    if ex isa Expr
756✔
159
        h = ex.head
488✔
160
        if h === :toplevel
488✔
161
            ex′ = Expr(h)
276✔
162
            map!(softscope, resize!(ex′.args, length(ex.args)), ex.args)
276✔
163
            return ex′
276✔
164
        elseif h in (:meta, :import, :using, :export, :module, :error, :incomplete, :thunk)
212✔
165
            return ex
4✔
166
        elseif h === :global && all(x->isa(x, Symbol), ex.args)
210✔
167
            return ex
2✔
168
        else
169
            return Expr(:block, Expr(:softscope, true), ex)
206✔
170
        end
171
    end
172
    return ex
268✔
173
end
174

175
# Temporary alias until Documenter updates
176
const softscope! = softscope
177

178
function print_qualified_access_warning(mod::Module, owner::Module, name::Symbol)
4✔
179
    @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
4✔
180
end
181

182
function has_ancestor(query::Module, target::Module)
4✔
183
    query == target && return true
74✔
184
    while true
60✔
185
        next = parentmodule(query)
60✔
186
        next == target && return true
60✔
187
        next == query && return false
56✔
188
        query = next
30✔
189
    end
30✔
190
end
191

192
retrieve_modules(::Module, ::Any) = (nothing,)
12✔
193
function retrieve_modules(current_module::Module, mod_name::Symbol)
190✔
194
    mod = try
190✔
195
        getproperty(current_module, mod_name)
190✔
196
    catch
197
        return (nothing,)
38✔
198
    end
199
    return (mod isa Module ? mod : nothing,)
152✔
200
end
201
retrieve_modules(current_module::Module, mod_name::QuoteNode) = retrieve_modules(current_module, mod_name.value)
82✔
202
function retrieve_modules(current_module::Module, mod_expr::Expr)
86✔
203
    if Meta.isexpr(mod_expr, :., 2)
86✔
204
        current_module = retrieve_modules(current_module, mod_expr.args[1])[1]
82✔
205
        current_module === nothing && return (nothing,)
82✔
206
        return (current_module, retrieve_modules(current_module, mod_expr.args[2])...)
82✔
207
    else
208
        return (nothing,)
4✔
209
    end
210
end
211

212
add_locals!(locals, ast::Any) = nothing
2✔
213
function add_locals!(locals, ast::Expr)
28✔
214
    for arg in ast.args
28✔
215
        add_locals!(locals, arg)
46✔
216
    end
46✔
217
    return nothing
28✔
218
end
219
function add_locals!(locals, ast::Symbol)
114✔
220
    push!(locals, ast)
114✔
221
    return nothing
114✔
222
end
223

224
function collect_names_to_warn!(warnings, locals, current_module::Module, ast)
2,354✔
225
    ast isa Expr || return
3,406✔
226

227
    # don't recurse through module definitions
228
    ast.head === :module && return
1,302✔
229

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

265
        if Meta.isexpr(ast.args[1], :call, 2)
6✔
266
            func_name, func_args = ast.args[1].args
4✔
267
            # here we have a function definition and are inspecting it's arguments for local variables.
268
            # we will error on the conservative side by adding all symbols we find (regardless if they are local variables or possibly-global default values)
269
            add_locals!(locals, func_args)
4✔
270
        end
271
        # fall through to general recursion
272
    end
273

274
    for arg in ast.args
1,084✔
275
        collect_names_to_warn!(warnings, locals, current_module, arg)
2,016✔
276
    end
2,016✔
277

278
    return nothing
1,084✔
279
end
280

281
function collect_qualified_access_warnings(current_mod, ast)
244✔
282
    warnings = Set()
244✔
283
    locals = Set{Symbol}()
244✔
284
    collect_names_to_warn!(warnings, locals, current_mod, ast)
244✔
285
    filter!(warnings) do (; outer_mod)
244✔
286
        nameof(outer_mod) ∉ locals
22✔
287
    end
288
    return warnings
244✔
289
end
290

291
function warn_on_non_owning_accesses(current_mod, ast)
220✔
292
    warnings = collect_qualified_access_warnings(current_mod, ast)
220✔
293
    for (; outer_mod, mod, owner, name_being_accessed) in warnings
440✔
294
        print_qualified_access_warning(mod, owner, name_being_accessed)
4✔
295
    end
8✔
296
    return ast
220✔
297
end
298
warn_on_non_owning_accesses(ast) = warn_on_non_owning_accesses(Base.active_module(), ast)
216✔
299

300
const repl_ast_transforms = Any[softscope, warn_on_non_owning_accesses] # defaults for new REPL backends
301

302
# Allows an external package to add hooks into the code loading.
303
# The hook should take a Vector{Symbol} of package names and
304
# return true if all packages could be installed, false if not
305
# to e.g. install packages on demand
306
const install_packages_hooks = Any[]
307

308
# N.B.: Any functions starting with __repl_entry cut off backtraces when printing in the REPL.
309
# We need to do this for both the actual eval and macroexpand, since the latter can cause custom macro
310
# code to run (and error).
311
__repl_entry_lower_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Csize_t}) =
432✔
312
    Core._lower(ast, mod, toplevel_file[], toplevel_line[])[1]
313
__repl_entry_eval_expanded_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Csize_t}) =
430✔
314
    ccall(:jl_toplevel_eval_flex, Any, (Any, Any, Cint, Cint, Ptr{Ptr{UInt8}}, Ptr{Csize_t}), mod, ast, 1, 1, toplevel_file, toplevel_line)
315

316
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))
658✔
317
    if !isexpr(ast, :toplevel)
874✔
318
        ast = invokelatest(__repl_entry_lower_with_loc, mod, ast, toplevel_file, toplevel_line)
432✔
319
        check_for_missing_packages_and_run_hooks(ast)
432✔
320
        return invokelatest(__repl_entry_eval_expanded_with_loc, mod, ast, toplevel_file, toplevel_line)
430✔
321
    end
322
    local value=nothing
226✔
323
    for i = 1:length(ast.args)
230✔
324
        value = toplevel_eval_with_hooks(mod, ast.args[i], toplevel_file, toplevel_line)
442✔
325
    end
646✔
326
    return value
214✔
327
end
328

329
function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module)
216✔
330
    lasterr = nothing
216✔
331
    Base.sigatomic_begin()
216✔
332
    while true
224✔
333
        try
224✔
334
            Base.sigatomic_end()
224✔
335
            if lasterr !== nothing
224✔
336
                put!(backend.response_channel, Pair{Any, Bool}(lasterr, true))
8✔
337
            else
338
                backend.in_eval = true
216✔
339
                for xf in backend.ast_transforms
216✔
340
                    ast = Base.invokelatest(xf, ast)
468✔
341
                end
468✔
342
                value = toplevel_eval_with_hooks(mod, ast)
216✔
343
                backend.in_eval = false
206✔
344
                setglobal!(Base.MainInclude, :ans, value)
206✔
345
                put!(backend.response_channel, Pair{Any, Bool}(value, false))
206✔
346
            end
347
            break
222✔
348
        catch err
349
            if lasterr !== nothing
8✔
350
                println("SYSTEM ERROR: Failed to report error to REPL frontend")
×
351
                println(err)
×
352
            end
353
            lasterr = current_exceptions()
8✔
354
        end
355
    end
8✔
356
    Base.sigatomic_end()
214✔
357
    nothing
214✔
358
end
359

360
function check_for_missing_packages_and_run_hooks(ast)
432✔
361
    isa(ast, Expr) || return
650✔
362
    mods = modules_to_be_loaded(ast)
214✔
363
    filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules
216✔
364
    if !isempty(mods)
214✔
365
        isempty(install_packages_hooks) && load_pkg()
2✔
366
        for f in install_packages_hooks
2✔
367
            Base.invokelatest(f, mods) && return
2✔
368
        end
×
369
    end
370
end
371

372
function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol})
1,598✔
373
    function add!(ctx)
1,646✔
374
        if ctx.head == :as
48✔
375
            ctx = ctx.args[1]
2✔
376
        end
377
        if ctx.args[1] != :. # don't include local import `import .Foo`
96✔
378
            push!(mods, ctx.args[1])
42✔
379
        end
380
    end
381
    ast.head === :quote && return mods # don't search if it's not going to be run during this eval
1,598✔
382
    if ast.head == :call
1,598✔
383
        if length(ast.args) == 5 && ast.args[1] === GlobalRef(Base, :_eval_import)
856✔
384
            ctx = ast.args[4]
16✔
385
            if ctx isa QuoteNode # i.e. `Foo: bar`
16✔
386
                ctx = ctx.value
8✔
387
            else
388
                ctx = ast.args[5].value
8✔
389
            end
390
            add!(ctx)
16✔
391
        elseif length(ast.args) == 3 && ast.args[1] == GlobalRef(Base, :_eval_using)
840✔
392
            add!(ast.args[3].value)
32✔
393
        end
394
    end
395
    if ast.head !== :thunk
1,598✔
396
        for arg in ast.args
1,336✔
397
            if isexpr(arg, (:block, :if))
3,170✔
398
                _modules_to_be_loaded!(arg, mods)
8✔
399
            end
400
        end
3,170✔
401
    else
402
        code = ast.args[1]
262✔
403
        for arg in code.code
262✔
404
            isa(arg, Expr) || continue
2,322✔
405
            _modules_to_be_loaded!(arg, mods)
1,330✔
406
        end
2,322✔
407
    end
408
end
409

410
function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[])
411
    _modules_to_be_loaded!(ast, mods)
520✔
412
    filter!(mod::Symbol -> !in(mod, (:Base, :Main, :Core)), mods) # Exclude special non-package modules
302✔
413
    return unique(mods)
260✔
414
end
415

416
"""
417
    start_repl_backend(repl_channel::Channel, response_channel::Channel)
418

419
    Starts loop for REPL backend
420
    Returns a REPLBackend with backend_task assigned
421

422
    Deprecated since sync / async behavior cannot be selected
423
"""
424
function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any}
×
425
                            ; get_module::Function = ()->Main)
426
    # Maintain legacy behavior of asynchronous backend
427
    backend = REPLBackend(repl_channel, response_channel, false)
×
428
    # Assignment will be made twice, but will be immediately available
429
    backend.backend_task = @async start_repl_backend(backend; get_module)
×
430
    return backend
×
431
end
432

433
"""
434
    start_repl_backend(backend::REPLBackend)
435

436
    Call directly to run backend loop on current Task.
437
    Use @async for run backend on new Task.
438

439
    Does not return backend until loop is finished.
440
"""
441
function start_repl_backend(backend::REPLBackend,  @nospecialize(consumer = x -> nothing); get_module::Function = ()->Main)
126✔
442
    backend.backend_task = Base.current_task()
57✔
443
    consumer(backend)
57✔
444
    repl_backend_loop(backend, get_module)
57✔
445
    return backend
54✔
446
end
447

448
function repl_backend_loop(backend::REPLBackend, get_module::Function)
57✔
449
    # include looks at this to determine the relative include path
450
    # nothing means cwd
451
    while true
393✔
452
        tls = task_local_storage()
393✔
453
        tls[:SOURCE_PATH] = nothing
393✔
454
        ast_or_func, show_value = take!(backend.repl_channel)
393✔
455
        if show_value == -1
392✔
456
            # exit flag
457
            break
54✔
458
        end
459
        if show_value == 2 # 2 indicates a function to be called
338✔
460
            f = ast_or_func
122✔
461
            try
122✔
462
                ret = f()
122✔
463
                put!(backend.response_channel, Pair{Any, Bool}(ret, false))
120✔
464
            catch
465
                put!(backend.response_channel, Pair{Any, Bool}(current_exceptions(), true))
2✔
466
            end
467
        else
468
            ast = ast_or_func
216✔
469
            eval_user_input(ast, backend, get_module())
216✔
470
        end
471
    end
336✔
472
    return nothing
54✔
473
end
474

475
SHOW_MAXIMUM_BYTES::Int = 1_048_576
476

477
# Limit printing during REPL display
478
mutable struct LimitIO{IO_t <: IO} <: IO
479
    io::IO_t
144✔
480
    maxbytes::Int
481
    n::Int # max bytes to write
482
end
483
LimitIO(io::IO, maxbytes) = LimitIO(io, maxbytes, 0)
144✔
484

485
struct LimitIOException <: Exception
486
    maxbytes::Int
12✔
487
end
488

489
function Base.showerror(io::IO, e::LimitIOException)
×
490
    print(io, "$LimitIOException: aborted printing after attempting to print more than $(Base.format_bytes(e.maxbytes)) within a `LimitIO`.")
×
491
end
492

493
Base.displaysize(io::LimitIO) = _displaysize(io.io)
56✔
494

495
function Base.write(io::LimitIO, v::UInt8)
310✔
496
    io.n > io.maxbytes && throw(LimitIOException(io.maxbytes))
3,348✔
497
    n_bytes = write(io.io, v)
6,378✔
498
    io.n += n_bytes
3,344✔
499
    return n_bytes
3,344✔
500
end
501

502
# Semantically, we only need to override `Base.write`, but we also
503
# override `unsafe_write` for performance.
504
function Base.unsafe_write(limiter::LimitIO, p::Ptr{UInt8}, nb::UInt)
505
    # already exceeded? throw
506
    limiter.n > limiter.maxbytes && throw(LimitIOException(limiter.maxbytes))
552✔
507
    remaining = limiter.maxbytes - limiter.n # >= 0
552✔
508

509
    # Not enough bytes left; we will print up to the limit, then throw
510
    if remaining < nb
552✔
511
        if remaining > 0
4✔
512
            Base.unsafe_write(limiter.io, p, remaining)
2✔
513
        end
514
        throw(LimitIOException(limiter.maxbytes))
4✔
515
    end
516

517
    # We won't hit the limit so we'll write the full `nb` bytes
518
    bytes_written = Base.unsafe_write(limiter.io, p, nb)::Union{Int,UInt}
548✔
519
    limiter.n += bytes_written
548✔
520
    return bytes_written
548✔
521
end
522

523
struct REPLDisplay{Repl<:AbstractREPL} <: AbstractDisplay
524
    repl::Repl
78✔
525
end
526

527
function show_limited(io::IO, mime::MIME, x)
138✔
528
    try
138✔
529
        # We wrap in a LimitIO to limit the amount of printing.
530
        # We unpack `IOContext`s, since we will pass the properties on the outside.
531
        inner = io isa IOContext ? io.io : io
138✔
532
        wrapped_limiter = IOContext(LimitIO(inner, SHOW_MAXIMUM_BYTES), io)
138✔
533
        # `show_repl` to allow the hook with special syntax highlighting
534
        show_repl(wrapped_limiter, mime, x)
138✔
535
    catch e
536
        e isa LimitIOException || rethrow()
10✔
537
        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)
6✔
538
    end
539
end
540

541
function display(d::REPLDisplay, mime::MIME"text/plain", x)
128✔
542
    x = Ref{Any}(x)
128✔
543
    with_repl_linfo(d.repl) do io
128✔
544
        io = IOContext(io, :limit => true, :module => Base.active_module(d)::Module)
250✔
545
        if d.repl isa LineEditREPL
128✔
546
            mistate = d.repl.mistate
122✔
547
            mode = LineEdit.mode(mistate)
122✔
548
            if mode isa LineEdit.Prompt
122✔
549
                LineEdit.write_output_prefix(io, mode, get(io, :color, false)::Bool)
488✔
550
            end
551
        end
552
        get(io, :color, false)::Bool && write(io, answer_color(d.repl))
140✔
553
        if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext)
128✔
554
            # this can override the :limit property set initially
555
            io = foldl(IOContext, d.repl.options.iocontext, init=io)
122✔
556
        end
557
        show_limited(io, mime, x[])
128✔
558
        println(io)
126✔
559
    end
560
    return nothing
126✔
561
end
562

563
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)
128✔
564

565
show_repl(io::IO, mime::MIME"text/plain", x) = show(io, mime, x)
134✔
566

567
function show_repl(io::IO, mime::MIME"text/plain", c::AbstractChar)
8✔
568
    show(io, mime, c) # Call the original Base.show
8✔
569
    # Check for LaTeX/emoji alias and print if found and using symbol_latex which is used in help?> mode
570
    latex = symbol_latex(string(c))
8✔
571
    if !isempty(latex)
8✔
572
        print(io, ", input as ")
4✔
573
        printstyled(io, latex, "<tab>"; color=:cyan)
4✔
574
    end
575
end
576

577
show_repl(io::IO, ::MIME"text/plain", ex::Expr) =
4✔
578
    print(io, JuliaSyntaxHighlighting.highlight(
579
        sprint(show, ex, context=IOContext(io, :color => false))))
580

581
function print_response(repl::AbstractREPL, response, show_value::Bool, have_color::Bool)
208✔
582
    repl.waserror = response[2]
208✔
583
    with_repl_linfo(repl) do io
416✔
584
        io = IOContext(io, :module => Base.active_module(repl)::Module)
410✔
585
        print_response(io, response, backend(repl), show_value, have_color, specialdisplay(repl))
208✔
586
    end
587
    return nothing
208✔
588
end
589

590
# N.B.: Any functions starting with __repl_entry cut off backtraces when printing in the REPL.
591
__repl_entry_display(val) = Base.invokelatest(display, val)
96✔
592
__repl_entry_display(specialdisplay::Union{AbstractDisplay,Nothing}, val) = Base.invokelatest(display, specialdisplay, val)
32✔
593

594
function __repl_entry_display_error(errio::IO, @nospecialize errval)
16✔
595
    # this will be set to true if types in the stacktrace are truncated
596
    limitflag = Ref(false)
16✔
597
    errio = IOContext(errio, :stacktrace_types_limited => limitflag)
16✔
598
    Base.invokelatest(Base.display_error, errio, errval)
16✔
599
    if limitflag[]
12✔
600
        print(errio, "Some type information was truncated. Use `show(err)` to see complete types.")
4✔
601
        println(errio)
4✔
602
    end
603
    return nothing
12✔
604
end
605

606
function print_response(errio::IO, response, backend::Union{REPLBackendRef,Nothing}, show_value::Bool, have_color::Bool, specialdisplay::Union{AbstractDisplay,Nothing}=nothing)
210✔
607
    Base.sigatomic_begin()
210✔
608
    val, iserr = response
210✔
609
    if !iserr
210✔
610
        # display result
611
        try
198✔
612
            if val !== nothing && show_value
198✔
613
                Base.sigatomic_end() # allow display to be interrupted
128✔
614
                val_to_show = val
128✔
615
                val2, iserr = if specialdisplay === nothing
128✔
616
                    # display calls may require being run on the main thread
617
                    call_on_backend(backend) do
186✔
618
                        __repl_entry_display(val_to_show)
96✔
619
                    end
620
                else
621
                    call_on_backend(backend) do
160✔
622
                        __repl_entry_display(specialdisplay, val_to_show)
32✔
623
                    end
624
                end
625
                Base.sigatomic_begin()
128✔
626
                if iserr
128✔
627
                    println(errio)
2✔
628
                    println(errio, "Error showing value of type ", typeof(val), ":")
2✔
629
                    val = val2
2✔
630
                end
631
            end
632
        catch ex
633
            println(errio)
×
634
            println(errio, "SYSTEM (REPL): showing a value caused an error")
×
635
            val = current_exceptions()
×
636
            iserr = true
×
637
        end
638
    end
639
    if iserr
210✔
640
        # print error
641
        iserr = false
14✔
642
        while true
16✔
643
            try
16✔
644
                Base.sigatomic_end() # allow stacktrace printing to be interrupted
16✔
645
                val = Base.scrub_repl_backtrace(val)
16✔
646
                Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val)
28✔
647
                __repl_entry_display_error(errio, val)
16✔
648
                break
16✔
649
            catch ex
650
                println(errio) # an error during printing is likely to leave us mid-line
4✔
651
                if !iserr
4✔
652
                    println(errio, "SYSTEM (REPL): showing an error caused an error")
2✔
653
                    val = current_exceptions()
2✔
654
                    iserr = true
2✔
655
                else
656
                    println(errio, "SYSTEM (REPL): caught exception of type ", typeof(ex).name.name,
2✔
657
                        " while trying to print an exception; giving up")
658
                    break
4✔
659
                end
660
            end
661
        end
2✔
662
    end
663
    Base.sigatomic_end()
210✔
664
    nothing
210✔
665
end
666

667

668

669
"""
670
    run_repl(repl::AbstractREPL)
671
    run_repl(repl, consumer = backend->nothing; backend_on_current_task = true)
672

673
    Main function to start the REPL
674

675
    consumer is an optional function that takes a REPLBackend as an argument
676
"""
677
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend())
206✔
678
    backend_ref = REPLBackendRef(backend)
53✔
679
    get_module = () -> Base.active_module(repl)
261✔
680
    cleanup_task(backend_ref, t) = @task try
157✔
681
            destroy(backend_ref, t)
51✔
682
        catch e
683
            Core.print(Core.stderr, "\nINTERNAL ERROR: ")
×
684
            Core.println(Core.stderr, e)
×
685
            Core.println(Core.stderr, catch_backtrace())
×
686
        end
687
    if backend_on_current_task
53✔
688
        t = @async run_frontend(repl, backend_ref)
106✔
689
        cleanup = cleanup_task(backend_ref, t)
53✔
690
        errormonitor(t)
53✔
691
        Base._wait2(t, cleanup)
53✔
692
        start_repl_backend(backend, consumer; get_module)
53✔
693
    else
694
        t = @async start_repl_backend(backend, consumer; get_module)
×
695
        cleanup = cleanup_task(backend_ref, t)
×
696
        errormonitor(t)
×
697
        Base._wait2(t, cleanup)
×
698
        run_frontend(repl, backend_ref)
×
699
    end
700
    return backend
50✔
701
end
702

703
## BasicREPL ##
704

705
mutable struct BasicREPL <: AbstractREPL
706
    terminal::TextTerminal
707
    waserror::Bool
708
    frontend_task::Task
709
    BasicREPL(t) = new(t, false)
6✔
710
end
711

712
outstream(r::BasicREPL) = r.terminal
12✔
713
hascolor(r::BasicREPL) = hascolor(r.terminal)
×
714

715
function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
6✔
716
    repl.frontend_task = current_task()
6✔
717
    d = REPLDisplay(repl)
6✔
718
    dopushdisplay = !in(d,Base.Multimedia.displays)
12✔
719
    dopushdisplay && pushdisplay(d)
6✔
720
    hit_eof = false
6✔
721
    while true
12✔
722
        Base.reseteof(repl.terminal)
12✔
723
        write(repl.terminal, JULIA_PROMPT)
12✔
724
        line = ""
12✔
725
        ast = nothing
12✔
726
        interrupted = false
12✔
727
        while true
12✔
728
            try
12✔
729
                line *= readline(repl.terminal, keep=true)
12✔
730
            catch e
731
                if isa(e,InterruptException)
×
732
                    try # raise the debugger if present
×
733
                        ccall(:jl_raise_debugger, Int, ())
×
734
                    catch
×
735
                    end
736
                    line = ""
×
737
                    interrupted = true
×
738
                    break
×
739
                elseif isa(e,EOFError)
×
740
                    hit_eof = true
×
741
                    break
×
742
                else
743
                    rethrow()
×
744
                end
745
            end
746
            ast = parse_repl_input_line(line, repl)
12✔
747
            (isa(ast,Expr) && ast.head === :incomplete) || break
24✔
748
        end
×
749
        if !isempty(line)
12✔
750
            response = eval_on_backend(ast, backend)
8✔
751
            print_response(repl, response, !ends_with_semicolon(line), false)
6✔
752
        end
753
        write(repl.terminal, '\n')
10✔
754
        ((!interrupted && isempty(line)) || hit_eof) && break
16✔
755
    end
6✔
756
    # terminate backend
757
    put!(backend.repl_channel, (nothing, -1))
4✔
758
    dopushdisplay && popdisplay(d)
4✔
759
    nothing
4✔
760
end
761

762
## LineEditREPL ##
763

764
mutable struct LineEditREPL <: AbstractREPL
765
    t::TextTerminal
766
    hascolor::Bool
767
    prompt_color::String
768
    input_color::String
769
    answer_color::String
770
    shell_color::String
771
    help_color::String
772
    pkg_color::String
773
    history_file::Bool
774
    in_shell::Bool
775
    in_help::Bool
776
    envcolors::Bool
777
    waserror::Bool
778
    specialdisplay::Union{Nothing,AbstractDisplay}
779
    options::Options
780
    mistate::Union{MIState,Nothing}
781
    last_shown_line_infos::Vector{Tuple{String,Int}}
782
    interface::ModalInterface
783
    backendref::REPLBackendRef
784
    frontend_task::Task
785
    # Optional event to notify when the prompt is ready (used by precompilation)
786
    prompt_ready_event::Union{Nothing, Base.Event}
787
    function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
61✔
788
        opts = Options()
61✔
789
        opts.hascolor = hascolor
61✔
790
        if !hascolor
61✔
791
            opts.beep_colors = [""]
×
792
        end
793
        r = new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
61✔
794
            in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
795
        r.prompt_ready_event = nothing
61✔
796
        r
61✔
797
    end
798
end
799
outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t)
342✔
800
specialdisplay(r::LineEditREPL) = r.specialdisplay
202✔
801
specialdisplay(r::AbstractREPL) = nothing
6✔
802
terminal(r::LineEditREPL) = r.t
327✔
803
hascolor(r::LineEditREPL) = r.hascolor
406✔
804

805
LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
121✔
806
    LineEditREPL(t, hascolor,
807
        hascolor ? Base.text_colors[:green] : "",
808
        hascolor ? Base.input_color() : "",
809
        hascolor ? Base.answer_color() : "",
810
        hascolor ? Base.text_colors[:red] : "",
811
        hascolor ? Base.text_colors[:yellow] : "",
812
        hascolor ? Base.text_colors[:blue] : "",
813
        false, false, false, envcolors
814
    )
815

816
mutable struct REPLCompletionProvider <: CompletionProvider
817
    modifiers::LineEdit.Modifiers
55✔
818
end
819
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
55✔
820

821
mutable struct ShellCompletionProvider <: CompletionProvider end
55✔
822
struct LatexCompletions <: CompletionProvider end
823

824
Base.active_module(mistate::MIState) = mistate.active_module
6,577✔
825
Base.active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : Base.active_module(mistate)
12,532✔
826
Base.active_module(::AbstractREPL) = Main
36✔
827
Base.active_module(d::REPLDisplay) = Base.active_module(d.repl)
250✔
828

829
setmodifiers!(c::CompletionProvider, m::LineEdit.Modifiers) = nothing
×
830

831
setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m
×
832

833
"""
834
    activate(mod::Module=Main)
835

836
Set `mod` as the default contextual module in the REPL,
837
both for evaluating expressions and printing them.
838
"""
839
function activate(mod::Module=Main; interactive_utils::Bool=true)
×
840
    mistate = (Base.active_repl::LineEditREPL).mistate
×
841
    mistate === nothing && return nothing
×
842
    mistate.active_module = mod
×
843
    interactive_utils && Base.load_InteractiveUtils(mod)
×
844
    return nothing
×
845
end
846

847
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
8✔
848

849
# Convert inclusive-inclusive 1-based char indexing to inclusive-exclusive byte Region.
850
to_region(s, r) = first(r)-1 => (length(r) > 0 ? nextind(s, last(r))-1 : first(r)-1)
61✔
851

852
function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module; hint::Bool=false)
64✔
853
    full = LineEdit.input_string(s)
32✔
854
    ret, range, should_complete = completions(full, thisind(full, position(s)), mod, c.modifiers.shift, hint)
64✔
855
    range = to_region(full, range)
54✔
856
    c.modifiers = LineEdit.Modifiers()
32✔
857
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
32✔
858
end
859

860
function complete_line(c::ShellCompletionProvider, s::PromptState; hint::Bool=false)
8✔
861
    full = LineEdit.input_string(s)
4✔
862
    ret, range, should_complete = shell_completions(full, thisind(full, position(s)), hint)
8✔
863
    range = to_region(full, range)
7✔
864
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
4✔
865
end
866

867
function complete_line(c::LatexCompletions, s; hint::Bool=false)
×
868
    full = LineEdit.input_string(s)::String
×
869
    ret, range, should_complete = bslash_completions(full, thisind(full, position(s)), hint)[2]
×
870
    range = to_region(full, range)
×
871
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
872
end
873

874
with_repl_linfo(f, repl) = f(outstream(repl))
12✔
875
function with_repl_linfo(f, repl::LineEditREPL)
324✔
876
    linfos = Tuple{String,Int}[]
324✔
877
    io = IOContext(outstream(repl), :last_shown_line_infos => linfos)
324✔
878
    f(io)
324✔
879
    if !isempty(linfos)
322✔
880
        repl.last_shown_line_infos = linfos
12✔
881
    end
882
    nothing
322✔
883
end
884

885
mutable struct REPLHistoryProvider <: HistoryProvider
886
    history::HistoryFile
65✔
887
    start_idx::Int
888
    cur_idx::Int
889
    last_idx::Int
890
    last_buffer::IOBuffer
891
    last_mode::Union{Nothing,Prompt}
892
    mode_mapping::Dict{Symbol,Prompt}
893
end
894
REPLHistoryProvider(mode_mapping::Dict{Symbol}) =
65✔
895
    REPLHistoryProvider(HistoryFile(), 0, 0, -1, IOBuffer(),
896
                        nothing, mode_mapping)
897

898
function add_history(hist::REPLHistoryProvider, s::PromptState)
240✔
899
    str = rstrip(takestring!(copy(s.input_buffer)))
240✔
900
    isempty(strip(str)) && return
240✔
901
    mode = mode_idx(hist, LineEdit.mode(s))
202✔
902
    !isempty(hist.history) && isequal(mode, hist.history[end].mode) &&
202✔
903
        str == hist.history[end].content && return
904
    entry = HistEntry(mode, now(UTC), str, 0)
190✔
905
    push!(hist.history, entry)
190✔
906
    nothing
190✔
907
end
908

909
function history_move(s::Union{LineEdit.MIState,LineEdit.PrefixSearchState}, hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx)
176✔
910
    max_idx = length(hist.history) + 1
252✔
911
    @assert 1 <= hist.cur_idx <= max_idx
176✔
912
    (1 <= idx <= max_idx) || return :none
180✔
913
    idx != hist.cur_idx || return :none
172✔
914

915
    # save the current line
916
    if save_idx == max_idx
172✔
917
        hist.last_mode = LineEdit.mode(s)
66✔
918
        hist.last_buffer = copy(LineEdit.buffer(s))
96✔
919
    else
920
        # NOTE: Modifying the history is a bit funky, so
921
        # we reach into the internals of `HistoryFile`
922
        # to do so rather than implementing `setindex!`.
923
        oldrec = hist.history.records[save_idx]
106✔
924
        hist.history.records[save_idx] = HistEntry(
106✔
925
            mode_idx(hist, LineEdit.mode(s)),
926
            oldrec.date,
927
            LineEdit.input_string(s),
928
            oldrec.index)
929
    end
930

931
    # load the saved line
932
    if idx == max_idx
172✔
933
        last_buffer = hist.last_buffer
20✔
934
        LineEdit.transition(s, hist.last_mode) do
28✔
935
            LineEdit.replace_line(s, last_buffer)
20✔
936
        end
937
        hist.last_mode = nothing
20✔
938
        hist.last_buffer = IOBuffer()
20✔
939
    else
940
        if haskey(hist.mode_mapping, hist.history[idx].mode)
304✔
941
            LineEdit.transition(s, hist.mode_mapping[hist.history[idx].mode]) do
120✔
942
                LineEdit.replace_line(s, hist.history[idx].content)
120✔
943
            end
944
        else
945
            return :skip
32✔
946
        end
947
    end
948
    hist.cur_idx = idx
140✔
949

950
    return :ok
140✔
951
end
952

953
# REPL History can also transitions modes
954
function LineEdit.accept_result_newmode(hist::REPLHistoryProvider)
22✔
955
    if 1 <= hist.cur_idx <= length(hist.history)
22✔
956
        return hist.mode_mapping[hist.history[hist.cur_idx].mode]
18✔
957
    end
958
    return nothing
4✔
959
end
960

961
function history_do_initialize(hist::REPLHistoryProvider)
962
    isempty(hist.history) || return false
245✔
963
    update!(hist.history)
21✔
964
    hist.start_idx = length(hist.history) + 1
21✔
965
    hist.cur_idx = hist.start_idx
21✔
966
    hist.last_idx = -1
21✔
967
    true
968
end
969

970
function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
60✔
971
                      num::Int=1, save_idx::Int = hist.cur_idx)
972
    num <= 0 && return history_next(s, hist, -num, save_idx)
100✔
973
    hist.last_idx = -1
56✔
974
    m = history_move(s, hist, hist.cur_idx-num, save_idx)
56✔
975
    if m === :ok
56✔
976
        LineEdit.move_input_start(s)
80✔
977
        LineEdit.reset_key_repeats(s) do
40✔
978
            LineEdit.move_line_end(s)
40✔
979
        end
980
        return LineEdit.refresh_line(s)
40✔
981
    elseif m === :skip
16✔
982
        return history_prev(s, hist, num+1, save_idx)
16✔
983
    else
984
        return Terminals.beep(s)
×
985
    end
986
end
987

988
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
48✔
989
                      num::Int=1, save_idx::Int = hist.cur_idx)
990
    if num == 0
80✔
991
        Terminals.beep(s)
×
992
        return
×
993
    end
994
    num < 0 && return history_prev(s, hist, -num, save_idx)
48✔
995
    history_do_initialize(hist)
88✔
996
    cur_idx = hist.cur_idx
44✔
997
    max_idx = length(hist.history) + 1
44✔
998
    if cur_idx == max_idx && 0 < hist.last_idx
44✔
999
        # issue #6312
1000
        cur_idx = hist.last_idx
×
1001
        hist.last_idx = -1
×
1002
    end
1003
    m = history_move(s, hist, cur_idx+num, save_idx)
44✔
1004
    if m === :ok
44✔
1005
        LineEdit.move_input_end(s)
28✔
1006
        return LineEdit.refresh_line(s)
28✔
1007
    elseif m === :skip
16✔
1008
        return history_next(s, hist, num+1, save_idx)
12✔
1009
    else
1010
        return Terminals.beep(s)
4✔
1011
    end
1012
end
1013

1014
history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) =
12✔
1015
    history_prev(s, hist, hist.cur_idx - 1 -
1016
                 (hist.cur_idx > hist.start_idx+1 ? hist.start_idx : 0))
1017

1018
history_last(s::LineEdit.MIState, hist::REPLHistoryProvider) =
8✔
1019
    history_next(s, hist, length(update!(hist.history)) - hist.cur_idx + 1)
1020

1021
function history_move_prefix(s::LineEdit.PrefixSearchState,
72✔
1022
                             hist::REPLHistoryProvider,
1023
                             prefix::AbstractString,
1024
                             backwards::Bool,
1025
                             cur_idx::Int = hist.cur_idx)
1026
    if history_do_initialize(hist)
208✔
1027
        cur_idx = hist.cur_idx
4✔
1028
    end
1029
    cur_response = takestring!(copy(LineEdit.buffer(s)))
72✔
1030
    # when searching forward, start at last_idx
1031
    if !backwards && hist.last_idx > 0
72✔
1032
        cur_idx = hist.last_idx
2✔
1033
    end
1034
    hist.last_idx = -1
72✔
1035
    max_idx = length(hist.history)+1
72✔
1036
    idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx)
122✔
1037
    for idx in idxs
72✔
1038
        if (idx == max_idx) || (startswith(hist.history[idx].content, prefix) && (hist.history[idx].content != cur_response || get(hist.mode_mapping, hist.history[idx].mode, nothing) !== LineEdit.mode(s)))
376✔
1039
            m = history_move(s, hist, idx)
68✔
1040
            if m === :ok
68✔
1041
                if idx == max_idx
64✔
1042
                    # on resuming the in-progress edit, leave the cursor where the user last had it
1043
                elseif isempty(prefix)
56✔
1044
                    # on empty prefix search, move cursor to the end
1045
                    LineEdit.move_input_end(s)
28✔
1046
                else
1047
                    # otherwise, keep cursor at the prefix position as a visual cue
1048
                    seek(LineEdit.buffer(s), sizeof(prefix))
28✔
1049
                end
1050
                LineEdit.refresh_line(s)
64✔
1051
                return :ok
64✔
1052
            elseif m === :skip
4✔
1053
                return history_move_prefix(s,hist,prefix,backwards,idx)
4✔
1054
            end
1055
        end
1056
    end
248✔
1057
    Terminals.beep(s)
4✔
1058
    nothing
4✔
1059
end
1060
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
10✔
1061
    history_move_prefix(s, hist, prefix, false)
1062
history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
58✔
1063
    history_move_prefix(s, hist, prefix, true)
1064

1065
function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer,
×
1066
                        backwards::Bool=false, skip_current::Bool=false)
1067

1068
    qpos = position(query_buffer)
×
1069
    qpos > 0 || return true
×
1070
    searchdata = beforecursor(query_buffer)
×
1071
    response_str = takestring!(copy(response_buffer))
×
1072

1073
    # Alright, first try to see if the current match still works
1074
    a = position(response_buffer) + 1 # position is zero-indexed
×
1075
    # FIXME: I'm pretty sure this is broken since it uses an index
1076
    # into the search data to index into the response string
1077
    b = a + sizeof(searchdata)
×
1078
    b = b ≤ ncodeunits(response_str) ? prevind(response_str, b) : b-1
×
1079
    b = min(lastindex(response_str), b) # ensure that b is valid
×
1080

1081
    searchstart = backwards ? b : a
×
1082
    if searchdata == response_str[a:b]
×
1083
        if skip_current
×
1084
            searchstart = backwards ? prevind(response_str, b) : nextind(response_str, a)
×
1085
        else
1086
            return true
×
1087
        end
1088
    end
1089

1090
    # Start searching
1091
    # First the current response buffer
1092
    if 1 <= searchstart <= lastindex(response_str)
×
1093
        match = backwards ? findprev(searchdata, response_str, searchstart) :
×
1094
                            findnext(searchdata, response_str, searchstart)
1095
        if match !== nothing
×
1096
            seek(response_buffer, first(match) - 1)
×
1097
            return true
×
1098
        end
1099
    end
1100

1101
    # Now search all the other buffers
1102
    idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):1:length(hist.history))
×
1103
    for idx in idxs
×
1104
        h = hist.history[idx].content
×
1105
        match = backwards ? findlast(searchdata, h) : findfirst(searchdata, h)
×
1106
        if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.history[idx].mode)
×
1107
            truncate(response_buffer, 0)
×
1108
            write(response_buffer, h)
×
1109
            seek(response_buffer, first(match) - 1)
×
1110
            hist.cur_idx = idx
×
1111
            return true
×
1112
        end
1113
    end
×
1114

1115
    return false
×
1116
end
1117

1118
function history_reset_state(hist::REPLHistoryProvider)
1119
    if hist.cur_idx != length(hist.history) + 1
324✔
1120
        hist.last_idx = hist.cur_idx
259✔
1121
        hist.cur_idx = length(hist.history) + 1
259✔
1122
    end
1123
    nothing
324✔
1124
end
1125
LineEdit.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist)
259✔
1126

1127
function parse_repl_input_line(line::String, repl; kwargs...)
796✔
1128
    # N.B.: This re-latches the syntax version for `Main`. If `Base.active_module` is not `Main`,
1129
    # then this does not affect the parser used for that module. We could probably skip this step
1130
    # in that case, but let's just be consistent on the off chance that the active module tries
1131
    # to `include(Main, ...)` or similar.
1132
    @Base.ScopedValues.with Base.MainInclude.main_parser=>Base.parser_for_active_project() Base.parse_input_line(line;
398✔
1133
        mod=Base.active_module(repl), kwargs...)
1134
end
1135

1136
function return_callback(s)
204✔
1137
    ast = parse_repl_input_line(takestring!(copy(LineEdit.buffer(s))), s; depwarn=false)
204✔
1138
    return !(isa(ast, Expr) && ast.head === :incomplete)
204✔
1139
end
1140

1141
find_hist_file() = get(ENV, "JULIA_HISTORY",
9✔
1142
                       !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") :
1143
                       error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set."))
1144

1145
backend(r::AbstractREPL) = hasproperty(r, :backendref) && isdefined(r, :backendref) ? r.backendref : nothing
408✔
1146

1147

1148
function eval_on_backend(ast, backend::REPLBackendRef)
208✔
1149
    put!(backend.repl_channel, (ast, 1)) # (f, show_value)
208✔
1150
    return take!(backend.response_channel) # (val, iserr)
208✔
1151
end
1152
function call_on_backend(f, backend::REPLBackendRef)
122✔
1153
    applicable(f) || error("internal error: f is not callable")
122✔
1154
    put!(backend.repl_channel, (f, 2)) # (f, show_value) 2 indicates function (rather than ast)
122✔
1155
    return take!(backend.response_channel) # (val, iserr)
122✔
1156
end
1157
# if no backend just eval (used by tests)
1158
eval_on_backend(ast, backend::Nothing) = error("no backend for eval ast")
×
1159
function call_on_backend(f, backend::Nothing)
6✔
1160
    try
6✔
1161
        ret = f()
6✔
1162
        return (ret, false) # (val, iserr)
6✔
1163
    catch
1164
        return (current_exceptions(), true)
×
1165
    end
1166
end
1167

1168

1169
function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon::Bool = true)
220✔
1170
    return function do_respond(s::MIState, buf, ok::Bool)
499✔
1171
        if !ok
279✔
1172
            return transition(s, :abort)
46✔
1173
        end
1174
        line = String(take!(buf)::Vector{UInt8})
435✔
1175
        if !isempty(line) || pass_empty
264✔
1176
            reset(repl)
202✔
1177
            local response
1178
            try
202✔
1179
                ast = Base.invokelatest(f, line)
202✔
1180
                response = eval_on_backend(ast, backend(repl))
400✔
1181
            catch
1182
                response = Pair{Any, Bool}(current_exceptions(), true)
2✔
1183
            end
1184
            hide_output = suppress_on_semicolon && ends_with_semicolon(line)
202✔
1185
            print_response(repl, response, !hide_output, hascolor(repl))
202✔
1186
        end
1187
        prepare_next(repl)
233✔
1188
        reset_state(s)
233✔
1189
        return s.current_mode.sticky ? true : transition(s, main)
233✔
1190
    end
1191
end
1192

1193
function reset(repl::LineEditREPL)
202✔
1194
    raw!(repl.t, false)
202✔
1195
    hascolor(repl) && print(repl.t, Base.text_colors[:normal])
202✔
1196
    nothing
202✔
1197
end
1198

1199
function prepare_next(repl::LineEditREPL)
233✔
1200
    println(terminal(repl))
233✔
1201
end
1202

1203
function mode_keymap(julia_prompt::Prompt)
1204
    AnyDict(
55✔
1205
    '\b' => function (s::MIState,o...)
12✔
1206
        if isempty(s) || position(LineEdit.buffer(s)) == 0
12✔
1207
            let buf = copy(LineEdit.buffer(s))
12✔
1208
                transition(s, julia_prompt) do
12✔
1209
                    LineEdit.state(s, julia_prompt).input_buffer = buf
12✔
1210
                end
1211
            end
1212
        else
1213
            buf = LineEdit.buffer(s)
×
1214
            if LineEdit.try_remove_paired_delimiter(buf)
×
1215
                return LineEdit.refresh_line(s)
×
1216
            end
1217
            LineEdit.edit_backspace(s)
×
1218
        end
1219
    end,
1220
    "^C" => function (s::MIState,o...)
1221
        LineEdit.move_input_end(s)
1222
        LineEdit.refresh_line(s)
1223
        print(LineEdit.terminal(s), "^C\n\n")
1224
        transition(s, julia_prompt)
1225
        transition(s, :reset)
1226
        LineEdit.refresh_line(s)
1227
    end)
1228
end
1229

1230
repl_filename(repl, hp::REPLHistoryProvider) = "REPL[$(max(length(hp.history)-hp.start_idx, 1))]"
182✔
1231
repl_filename(repl, hp) = "REPL"
×
1232

1233
const JL_PROMPT_PASTE = Ref(true)
1234
enable_promptpaste(v::Bool) = JL_PROMPT_PASTE[] = v
×
1235

1236
function contextual_prompt(repl::LineEditREPL, prompt::Union{String,Function})
1237
    function ()
5,767✔
1238
        mod = Base.active_module(repl)
11,294✔
1239
        prefix = mod == Main ? "" : string('(', mod, ") ")
5,710✔
1240
        pr = prompt isa String ? prompt : prompt()
5,655✔
1241
        prefix * pr
5,655✔
1242
    end
1243
end
1244

1245
setup_interface(
157✔
1246
    repl::LineEditREPL;
1247
    # those keyword arguments may be deprecated eventually in favor of the Options mechanism
1248
    hascolor::Bool = repl.options.hascolor,
1249
    extra_repl_keymap::Any = repl.options.extra_keymap
1250
) = setup_interface(repl, hascolor, extra_repl_keymap)
1251

1252

1253
# This non keyword method can be precompiled which is important
1254
function setup_interface(
55✔
1255
    repl::LineEditREPL,
1256
    hascolor::Bool,
1257
    extra_repl_keymap::Any, # Union{Dict,Vector{<:Dict}},
1258
)
1259
    # The precompile statement emitter has problem outputting valid syntax for the
1260
    # type of `Union{Dict,Vector{<:Dict}}` (see #28808).
1261
    # This function is however important to precompile for REPL startup time, therefore,
1262
    # make the type Any and just assert that we have the correct type below.
1263
    @assert extra_repl_keymap isa Union{Dict,Vector{<:Dict}}
55✔
1264

1265
    ###
1266
    #
1267
    # This function returns the main interface that describes the REPL
1268
    # functionality, it is called internally by functions that setup a
1269
    # Terminal-based REPL frontend.
1270
    #
1271
    # See run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
1272
    # for usage
1273
    #
1274
    ###
1275

1276
    ###
1277
    # We setup the interface in two stages.
1278
    # First, we set up all components (prompt,rsearch,shell,help)
1279
    # Second, we create keymaps with appropriate transitions between them
1280
    #   and assign them to the components
1281
    #
1282
    ###
1283

1284
    ############################### Stage I ################################
1285

1286
    # This will provide completions for REPL and help mode
1287
    replc = REPLCompletionProvider()
55✔
1288

1289
    # Set up the main Julia prompt
1290
    julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT);
109✔
1291
        # Copy colors from the prompt object
1292
        prompt_prefix = hascolor ? repl.prompt_color : "",
1293
        prompt_suffix = hascolor ?
1294
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1295
        repl = repl,
1296
        complete = replc,
1297
        on_enter = return_callback,
1298
        styling_passes = StylingPasses.StylingPass[
1299
            StylingPasses.SyntaxHighlightPass(),
1300
            StylingPasses.EnclosingParenHighlightPass()
1301
        ])
1302

1303
    # Setup help mode
1304
    help_mode = Prompt(contextual_prompt(repl, HELP_PROMPT),
109✔
1305
        prompt_prefix = hascolor ? repl.help_color : "",
1306
        prompt_suffix = hascolor ?
1307
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1308
        repl = repl,
1309
        complete = replc,
1310
        # When we're done transform the entered line into a call to helpmode function
1311
        on_done = respond(line::String->helpmode(outstream(repl), line, Base.active_module(repl)),
4✔
1312
                          repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false))
1313

1314

1315
    # Set up shell mode
1316
    shell_mode = Prompt(SHELL_PROMPT;
109✔
1317
        prompt_prefix = hascolor ? repl.shell_color : "",
1318
        prompt_suffix = hascolor ?
1319
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1320
        repl = repl,
1321
        complete = ShellCompletionProvider(),
1322
        # Transform "foo bar baz" into `foo bar baz` (shell quoting)
1323
        # and pass into Base.repl_cmd for processing (handles `ls` and `cd`
1324
        # special)
1325
        on_done = respond(repl, julia_prompt) do line
1326
            Expr(:call, :(Base.repl_cmd),
16✔
1327
                :(Base.cmd_gen($(Base.shell_parse(line::String)[1]))),
1328
                outstream(repl))
1329
        end,
1330
        sticky = true)
1331

1332
    # Set up dummy Pkg mode that will be replaced once Pkg is loaded
1333
    # use 6 dots to occupy the same space as the most likely "@v1.xx" env name
1334
    dummy_pkg_mode = Prompt(Pkg_promptf,
109✔
1335
        prompt_prefix = hascolor ? repl.pkg_color : "",
1336
        prompt_suffix = hascolor ?
1337
        (repl.envcolors ? Base.input_color : repl.input_color) : "",
1338
        repl = repl,
1339
        complete = LineEdit.EmptyCompletionProvider(),
1340
        on_done = respond(line->nothing, repl, julia_prompt),
1341
        on_enter = function (s::MIState)
1342
                # This is hit when the user tries to execute a command before the real Pkg mode has been
1343
                # switched to. Ok to do this even if Pkg is loading on the other task because of the loading lock.
1344
                REPLExt = load_pkg()
1345
                if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1346
                    for mode in repl.interface.modes
1347
                        if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1348
                            # pkg mode
1349
                            let buf = copy(LineEdit.buffer(s))
1350
                                transition(s, mode) do
1351
                                    LineEdit.state(s, mode).input_buffer = buf
1352
                                end
1353
                            end
1354
                        end
1355
                    end
1356
                end
1357
                return true
1358
            end,
1359
        sticky = true)
1360

1361

1362
    ################################# Stage II #############################
1363

1364
    # Setup history
1365
    # We will have a unified history for all REPL modes
1366
    hp = REPLHistoryProvider(Dict{Symbol,Prompt}(:julia => julia_prompt,
55✔
1367
                                                 :shell => shell_mode,
1368
                                                 :help  => help_mode,
1369
                                                 :pkg  => dummy_pkg_mode))
1370
    if repl.history_file
55✔
1371
        try
9✔
1372
            path = find_hist_file()
9✔
1373
            mkpath(dirname(path))
9✔
1374
            hp.history = HistoryFile(path)
9✔
1375
            errormonitor(@async history_do_initialize(hp))
18✔
1376
            finalizer(replc) do replc
9✔
1377
                close(hp.history)
8✔
1378
            end
1379
        catch
1380
            # use REPL.hascolor to avoid using the local variable with the same name
1381
            print_response(repl, Pair{Any, Bool}(current_exceptions(), true), true, REPL.hascolor(repl))
×
1382
            println(outstream(repl))
×
1383
            @info "Disabling history file for this session"
×
1384
            repl.history_file = false
×
1385
        end
1386
    end
1387
    history_reset_state(hp)
55✔
1388
    julia_prompt.hist = hp
55✔
1389
    shell_mode.hist = hp
55✔
1390
    help_mode.hist = hp
55✔
1391
    dummy_pkg_mode.hist = hp
55✔
1392

1393
    julia_prompt.on_done = respond(x->parse_repl_input_line(x, repl; filename=repl_filename(repl,hp)), repl, julia_prompt)
237✔
1394

1395
    shell_prompt_len = length(SHELL_PROMPT)
55✔
1396
    help_prompt_len = length(HELP_PROMPT)
55✔
1397
    jl_prompt_regex = Regex("^In \\[[0-9]+\\]: |^(?:\\(.+\\) )?$JULIA_PROMPT")
55✔
1398
    pkg_prompt_regex = Regex("^(?:\\(.+\\) )?$PKG_PROMPT")
55✔
1399

1400
    # Canonicalize user keymap input
1401
    if isa(extra_repl_keymap, Dict)
55✔
1402
        extra_repl_keymap = AnyDict[extra_repl_keymap]
×
1403
    end
1404

1405
    repl_keymap = AnyDict(
55✔
1406
        ';' => function (s::MIState,o...)
108✔
1407
            if isempty(s) || position(LineEdit.buffer(s)) == 0
204✔
1408
                let buf = copy(LineEdit.buffer(s))
12✔
1409
                    transition(s, shell_mode) do
12✔
1410
                        LineEdit.state(s, shell_mode).input_buffer = buf
12✔
1411
                    end
1412
                end
1413
            else
1414
                edit_insert(s, ';')
96✔
1415
                LineEdit.check_show_hint(s)
96✔
1416
            end
1417
        end,
1418
        '?' => function (s::MIState,o...)
2✔
1419
            if isempty(s) || position(LineEdit.buffer(s)) == 0
2✔
1420
                let buf = copy(LineEdit.buffer(s))
2✔
1421
                    transition(s, help_mode) do
2✔
1422
                        LineEdit.state(s, help_mode).input_buffer = buf
2✔
1423
                    end
1424
                end
1425
            else
1426
                edit_insert(s, '?')
×
1427
                LineEdit.check_show_hint(s)
×
1428
            end
1429
        end,
1430
        ']' => function (s::MIState,o...)
6✔
1431
            if isempty(s) || position(LineEdit.buffer(s)) == 0
12✔
1432
                let buf = copy(LineEdit.buffer(s))
×
1433
                    transition(s, dummy_pkg_mode) do
×
1434
                        LineEdit.state(s, dummy_pkg_mode).input_buffer = buf
1435
                    end
1436
                end
1437
                # load Pkg on another thread if available so that typing in the dummy Pkg prompt
1438
                # isn't blocked, but instruct the main REPL task to do the transition via s.async_channel
1439
                t_replswitch = Threads.@spawn begin
×
1440
                    REPLExt = load_pkg()
1441
                    if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1442
                        put!(s.async_channel,
1443
                            function (s::MIState)
1444
                                LineEdit.mode(s) === dummy_pkg_mode || return :ok
1445
                                for mode in repl.interface.modes
1446
                                    if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1447
                                        let buf = copy(LineEdit.buffer(s))
1448
                                            transition(s, mode) do
1449
                                                LineEdit.state(s, mode).input_buffer = buf
1450
                                            end
1451
                                        end
1452
                                        if !isempty(s)
1453
                                            @invokelatest(LineEdit.check_show_hint(s))
1454
                                        end
1455
                                        break
1456
                                    end
1457
                                end
1458
                                return :ok
1459
                            end
1460
                        )
1461
                    end
1462
                end
1463
                Base.errormonitor(t_replswitch)
×
1464
            else
1465
                # Use bracket insertion if enabled, otherwise just insert
1466
                if repl.options.auto_insert_closing_bracket
6✔
1467
                    buf = LineEdit.buffer(s)
×
1468
                    if !eof(buf) && LineEdit.peek(buf, Char) == ']'
×
1469
                        LineEdit.edit_move_right(buf)
×
1470
                    else
1471
                        edit_insert(buf, ']')
×
1472
                    end
1473
                    LineEdit.refresh_line(s)
×
1474
                else
1475
                    edit_insert(s, ']')
6✔
1476
                end
1477
                LineEdit.check_show_hint(s)
6✔
1478
            end
1479
        end,
1480

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

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

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

1646
    prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt)
55✔
1647

1648
    # Build keymap list - add bracket insertion if enabled
1649
    base_keymaps = Dict{Any,Any}[repl_keymap, prefix_keymap, LineEdit.history_keymap]
165✔
1650
    if repl.options.auto_insert_closing_bracket
55✔
1651
        push!(base_keymaps, LineEdit.bracket_insert_keymap)
1✔
1652
    end
1653
    push!(base_keymaps, LineEdit.default_keymap, LineEdit.escape_defaults)
55✔
1654

1655
    a = base_keymaps
55✔
1656
    prepend!(a, extra_repl_keymap)
55✔
1657

1658
    julia_prompt.keymap_dict = LineEdit.keymap(a)
55✔
1659

1660
    mk = mode_keymap(julia_prompt)
55✔
1661

1662
    # Build keymap list for other modes
1663
    mode_base_keymaps = Dict{Any,Any}[mk, prefix_keymap, LineEdit.history_keymap]
165✔
1664
    if repl.options.auto_insert_closing_bracket
55✔
1665
        push!(mode_base_keymaps, LineEdit.bracket_insert_keymap)
1✔
1666
    end
1667
    push!(mode_base_keymaps, LineEdit.default_keymap, LineEdit.escape_defaults)
55✔
1668

1669
    b = mode_base_keymaps
55✔
1670
    prepend!(b, extra_repl_keymap)
55✔
1671

1672
    shell_mode.keymap_dict = help_mode.keymap_dict = dummy_pkg_mode.keymap_dict = LineEdit.keymap(b)
55✔
1673

1674
    allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, dummy_pkg_mode, prefix_prompt]
55✔
1675
    return ModalInterface(allprompts)
55✔
1676
end
1677

1678
function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
47✔
1679
    repl.frontend_task = current_task()
47✔
1680
    d = REPLDisplay(repl)
47✔
1681
    dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays)
80✔
1682
    dopushdisplay && pushdisplay(d)
47✔
1683
    if !isdefined(repl,:interface)
47✔
1684
        interface = repl.interface = setup_interface(repl)
54✔
1685
    else
1686
        interface = repl.interface
20✔
1687
    end
1688
    repl.backendref = backend
47✔
1689
    repl.mistate = LineEdit.init_state(terminal(repl), interface)
47✔
1690
    # Copy prompt_ready_event from repl to mistate (used by precompilation)
1691
    if isdefined(repl, :prompt_ready_event) && repl.prompt_ready_event !== nothing
47✔
1692
        repl.mistate.prompt_ready_event = repl.prompt_ready_event
×
1693
    end
1694
    run_interface(terminal(repl), interface, repl.mistate)
47✔
1695
    # Terminate Backend
1696
    put!(backend.repl_channel, (nothing, -1))
46✔
1697
    dopushdisplay && popdisplay(d)
46✔
1698
    nothing
46✔
1699
end
1700

1701
## StreamREPL ##
1702

1703
mutable struct StreamREPL <: AbstractREPL
1704
    stream::IO
1705
    prompt_color::String
1706
    input_color::String
1707
    answer_color::String
1708
    waserror::Bool
1709
    frontend_task::Task
1710
    StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false)
×
1711
end
1712
StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.input_color(), Base.answer_color())
×
1713
run_repl(stream::IO) = run_repl(StreamREPL(stream))
×
1714

1715
outstream(s::StreamREPL) = s.stream
×
1716
hascolor(s::StreamREPL) = get(s.stream, :color, false)::Bool
×
1717

1718
answer_color(r::LineEditREPL) = r.envcolors ? Base.answer_color() : r.answer_color
×
1719
answer_color(r::StreamREPL) = r.answer_color
×
1720
input_color(r::LineEditREPL) = r.envcolors ? Base.input_color() : r.input_color
×
1721
input_color(r::StreamREPL) = r.input_color
×
1722

1723
# heuristic function to decide if the presence of a semicolon
1724
# at the end of the expression was intended for suppressing output
1725
function ends_with_semicolon(code)
262✔
1726
    semi = false
262✔
1727
    for tok in tokenize(code)
262✔
1728
        kind(tok) in KSet"Whitespace NewlineWs Comment EndMarker" && continue
4,958✔
1729
        semi = kind(tok) == K";"
1,923✔
1730
    end
2,673✔
1731
    return semi
262✔
1732
end
1733

1734
function banner(io::IO = stdout; short = false)
8✔
1735
    if Base.GIT_VERSION_INFO.tagged_commit
4✔
1736
        commit_string = Base.TAGGED_RELEASE_BANNER
×
1737
    elseif isempty(Base.GIT_VERSION_INFO.commit)
4✔
1738
        commit_string = ""
×
1739
    else
1740
        days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24)))
4✔
1741
        days = max(0, days)
4✔
1742
        unit = days == 1 ? "day" : "days"
4✔
1743
        distance = Base.GIT_VERSION_INFO.fork_master_distance
4✔
1744
        commit = Base.GIT_VERSION_INFO.commit_short
4✔
1745

1746
        if distance == 0
4✔
1747
            commit_string = "Commit $(commit) ($(days) $(unit) old master)"
4✔
1748
        else
1749
            branch = Base.GIT_VERSION_INFO.branch
×
1750
            commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))"
×
1751
        end
1752
    end
1753

1754
    commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))"
4✔
1755

1756
    if get(io, :color, false)::Bool
4✔
1757
        c = Base.text_colors
×
1758
        tx = c[:normal] # text
×
1759
        jl = c[:normal] # julia
×
1760
        d1 = c[:bold] * c[:blue]    # first dot
×
1761
        d2 = c[:bold] * c[:red]     # second dot
×
1762
        d3 = c[:bold] * c[:green]   # third dot
×
1763
        d4 = c[:bold] * c[:magenta] # fourth dot
×
1764

1765
        if short
×
1766
            print(io,"""
×
1767
              $(d3)o$(tx)  | Version $(VERSION)$(commit_date)
1768
             $(d2)o$(tx) $(d4)o$(tx) | $(commit_string)
1769
            """)
1770
        else
1771
            print(io,"""               $(d3)_$(tx)
×
1772
               $(d1)_$(tx)       $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx)     |  Documentation: https://docs.julialang.org
1773
              $(d1)(_)$(jl)     | $(d2)(_)$(tx) $(d4)(_)$(tx)    |
1774
               $(jl)_ _   _| |_  __ _$(tx)   |  Type \"?\" for help, \"]?\" for Pkg help.
1775
              $(jl)| | | | | | |/ _` |$(tx)  |
1776
              $(jl)| | |_| | | | (_| |$(tx)  |  Version $(VERSION)$(commit_date)
1777
             $(jl)_/ |\\__'_|_|_|\\__'_|$(tx)  |  $(commit_string)
1778
            $(jl)|__/$(tx)                   |
1779

1780
            """)
1781
        end
1782
    else
1783
        if short
4✔
1784
            print(io,"""
2✔
1785
              o  |  Version $(VERSION)$(commit_date)
1786
             o o |  $(commit_string)
1787
            """)
1788
        else
1789
            print(io,"""
2✔
1790
                           _
1791
               _       _ _(_)_     |  Documentation: https://docs.julialang.org
1792
              (_)     | (_) (_)    |
1793
               _ _   _| |_  __ _   |  Type \"?\" for help, \"]?\" for Pkg help.
1794
              | | | | | | |/ _` |  |
1795
              | | |_| | | | (_| |  |  Version $(VERSION)$(commit_date)
1796
             _/ |\\__'_|_|_|\\__'_|  |  $(commit_string)
1797
            |__/                   |
1798

1799
            """)
1800
        end
1801
    end
1802
end
1803

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

1835
module Numbered
1836

1837
using ..REPL
1838

1839
__current_ast_transforms() = Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1840

1841
function repl_eval_counter(hp)
1,134✔
1842
    return length(hp.history) - hp.start_idx
1,134✔
1843
end
1844

1845
function out_transform(@nospecialize(x), n::Ref{Int})
32✔
1846
    return Expr(:block, # avoid line numbers or scope that would leak into the output and change the meaning of x
32✔
1847
        :(local __temp_val_a72df459 = $x),
1848
        Expr(:call, capture_result, n, :__temp_val_a72df459),
1849
        :__temp_val_a72df459)
1850
end
1851

1852
function create_global_out!(mod)
2✔
1853
    if !isdefinedglobal(mod, :Out)
2✔
1854
        out = Dict{Int, Any}()
2✔
1855
        @eval mod begin
2✔
1856
            const Out = $(out)
1857
            export Out
1858
        end
1859
        return out
2✔
1860
    end
1861
    return getglobal(mod, :Out)
×
1862
end
1863

1864
function capture_result(n::Ref{Int}, @nospecialize(x))
32✔
1865
    n = n[]
32✔
1866
    mod = Base.MainInclude
32✔
1867
    # TODO: This invokelatest is only required due to backdated constants
1868
    # and should be removed after
1869
    out = isdefinedglobal(mod, :Out) ? invokelatest(getglobal, mod, :Out) : invokelatest(create_global_out!, mod)
34✔
1870
    if x !== out && x !== nothing # remove this?
32✔
1871
        out[n] = x
28✔
1872
    end
1873
    nothing
32✔
1874
end
1875

1876
function set_prompt(repl::LineEditREPL, n::Ref{Int})
2✔
1877
    julia_prompt = repl.interface.modes[1]
2✔
1878
    julia_prompt.prompt = REPL.contextual_prompt(repl, function()
1,136✔
1879
        n[] = repl_eval_counter(julia_prompt.hist)+1
1,134✔
1880
        string("In [", n[], "]: ")
1,134✔
1881
    end)
1882
    nothing
2✔
1883
end
1884

1885
function set_output_prefix(repl::LineEditREPL, n::Ref{Int})
2✔
1886
    julia_prompt = repl.interface.modes[1]
2✔
1887
    if REPL.hascolor(repl)
2✔
1888
        julia_prompt.output_prefix_prefix = Base.text_colors[:red]
2✔
1889
    end
1890
    julia_prompt.output_prefix = () -> string("Out[", n[], "]: ")
30✔
1891
    nothing
2✔
1892
end
1893

1894
function __current_ast_transforms(backend)
1895
    if backend === nothing
2✔
1896
        Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1897
    else
1898
        backend.ast_transforms
2✔
1899
    end
1900
end
1901

1902
function numbered_prompt!(repl::LineEditREPL=Base.active_repl::LineEditREPL, backend=nothing)
1903
    n = Ref{Int}(0)
2✔
1904
    set_prompt(repl, n)
2✔
1905
    set_output_prefix(repl, n)
2✔
1906
    push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n))
34✔
1907
    return
2✔
1908
end
1909

1910
"""
1911
    Out[n]
1912

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

1916
See also [`ans`](@ref).
1917
"""
1918
Base.MainInclude.Out
1919

1920
end
1921

1922
import .Numbered.numbered_prompt!
1923

1924
# this assignment won't survive precompilation,
1925
# but will stick if REPL is baked into a sysimg.
1926
# Needs to occur after this module is finished.
1927
Base.REPL_MODULE_REF[] = REPL
1928

1929
if Base.generating_output()
1930
   include("precompile.jl")
1931
end
1932

1933
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